progress_mon.py 6.96 KB
Newer Older
1
2
# -*- coding: utf-8 -*-

3
4
# py_tools_ds - A collection of geospatial data analysis tools that simplify standard
# operations when handling geospatial raster and vector data as well as projections.
5
#
6
7
8
9
# Copyright (C) 2016-2021
# - Daniel Scheffler (GFZ Potsdam, daniel.scheffler@gfz-potsdam.de)
# - Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences Potsdam,
#   Germany (https://www.gfz-potsdam.de/)
10
11
12
13
14
#
# This software was developed within the context of the GeoMultiSens project funded
# by the German Federal Ministry of Education and Research
# (project grant code: 01 IS 14 010 A-C).
#
15
16
17
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
18
#
19
#   http://www.apache.org/licenses/LICENSE-2.0
20
#
21
22
23
24
25
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
26

27
import sys
28
from time import time
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
29
from datetime import timedelta
30

31
32
__author__ = "Daniel Scheffler"

33

34
class Timer(object):
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
35
    def __init__(self, timeout=None, use_as_callback=False):
36
        self.starttime = time()
37
        self.endtime = self.starttime + timeout if timeout else None
38
        self.timeout = timeout
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
39
        self.use_as_cb = use_as_callback
40
41
42
43
44

    @property
    def timed_out(self):
        if self.endtime:
            if time() > self.endtime:
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
45
                if self.use_as_cb:
46
47
48
                    # raise a KeyBoardInterrupt instead of a TimeOutError
                    # as this is catchable by gdal.GetLastException()
                    raise KeyboardInterrupt()
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
49
50
                else:
                    return True
51
            else:
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
52
53
54
55
                if self.use_as_cb:
                    pass
                else:
                    return False
Daniel Scheffler's avatar
Daniel Scheffler committed
56
57
        else:
            return False
58

59
60
    @property
    def elapsed(self):
61
62
        return str(timedelta(seconds=time() - self.starttime)).split('.')[0]
        # return '%.2f sek' %(time()-self.starttime)
63
64

    def __call__(self, percent01, message, user_data):
Daniel Scheffler's avatar
Daniel Scheffler committed
65
        """Allow Timer instances to be callable and thus to be used as callback function, e.g., for GDAL.
66
67
68
69
70
71
72
73
74
75

        :param percent01:   this is not used but expected when used as GDAL callback
        :param message:     this is not used but expected when used as GDAL callback
        :param user_data:   this is not used but expected when used as GDAL callback
        :return:
        """
        return self.timed_out


class ProgressBar(object):
76
    def __init__(self, prefix='', suffix='Complete', decimals=1, barLength=50, show_elapsed=True,
77
                 timeout=None, use_as_callback=False, out=sys.stderr):
Daniel Scheffler's avatar
Daniel Scheffler committed
78
79
80
81
        """Call an instance of this class in a loop to create terminal progress bar.

        NOTE: This class can also be used as callback function, e.g. for GDAL.
              Just pass an instance of ProgressBar to the respective callback keyword.
82
83
84
85
86
87
88
89
90
91

        :param prefix:         prefix string (Str)
        :param suffix:         suffix string (Str)
        :param decimals:       positive number of decimals in percent complete (Int)
        :param barLength:      character length of bar (Int)
        :param show_elapsed:   displays the elapsed time right after the progress bar (bool)
        :param timeout:        breaks the process after a given time in seconds (float)

        http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
        """
92
93
94
95
        self.prefix = prefix
        self.suffix = suffix
        self.decimals = decimals
        self.barLength = barLength
96
        self.show_elapsed = show_elapsed
97
        self.timeout = timeout
98
99
        self.Timer = Timer(timeout=timeout)
        self.use_as_cb = use_as_callback
100
        self.out = out
101
102

    def print_progress(self, percent):
Daniel Scheffler's avatar
Daniel Scheffler committed
103
104
105
        """Print progress.

        - based on http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console.
106
107
108
109
110

        :param percent: <float> a number between 0 and 100
        :return:
        """
        if self.Timer.timed_out:
111
            self.out.flush()
Daniel Scheffler's avatar
Bugfix    
Daniel Scheffler committed
112
            if self.use_as_cb:
113
114
115
                # raise a KeyBoardInterrupt instead of a TimeOutError
                # as this is catchable by gdal.GetLastException()
                raise KeyboardInterrupt()
116

117
118
        formatStr = "{0:." + str(self.decimals) + "f}"
        percents = formatStr.format(percent)
119
120
        filledLength = int(round(self.barLength * percent / 100))
        # bar         = '█' * filledLength + '-' * (barLength - filledLength) # this is not compatible to shell console
121
122
123
        bar = '=' * filledLength + '-' * (self.barLength - filledLength)

        # reset the cursor to the beginning of the line and allows to write over what was previously on the line
124
        self.out.write('\r')
125

126
        # [%s/%s] numberDone
127
        suffix = self.suffix if not self.show_elapsed else '%s  => %s' % (self.suffix, self.Timer.elapsed)
128
        self.out.write('%s |%s| %s%s %s' % (self.prefix, bar, percents, '%', suffix))
129
130

        if percent >= 100.:
131
            self.out.write('\n')
132

133
        self.out.flush()
134
135

    def __call__(self, percent01, message, user_data):
136
        """Allow ProgressBar instances to be callable and thus to be used as callback function, e.g., for GDAL.
137

138
139
140
141
142
        :param percent01:   a float number between 0 and 1
        :param message:     this is not used but expected when used as GDAL callback
        :param user_data:   this is not used but expected when used as GDAL callback
        :return:
        """
143
        self.print_progress(percent01 * 100)
144
145
146
147


def tqdm_hook(t):
    """
Daniel Scheffler's avatar
Daniel Scheffler committed
148

149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    Wraps tqdm instance. Don't forget to close() or __exit__()
    the tqdm instance once you're done with it (easiest using `with` syntax).

    Example
    -------

    > with tqdm(...) as t:
    ...  reporthook = my_hook(t)
    ...  urllib.urlretrieve(..., reporthook=reporthook)

    http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
    """
    last_b = [0]

    def inner(b=1, bsize=1, tsize=None):
        """
        b  : int, optional
            Number of blocks just transferred [default: 1].
        bsize  : int, optional
            Size of each block (in tqdm units) [default: 1].
        tsize  : int, optional
            Total size (in tqdm units). If [default: None] remains unchanged.
        """
        if tsize is not None:
            t.total = tsize
        t.update((b - last_b[0]) * bsize)
        last_b[0] = b
176

177
178
179
180
    return inner


def printPercentage(i, i_total):
Daniel Scheffler's avatar
Daniel Scheffler committed
181
    """Print a percentage counter from within a loop.
182
183
184
185
186
187
188
189
190
191
192
193

    Example:
    for i in range(100+1):
        time.sleep(0.1)
        printPercentage(i)

    :param i:
    :param i_total:
    :return:

    http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
    """
194
195
    sys.stdout.write(('=' * i) + ('' * (i_total - i)) + ("\r [ %d" % i + "% ] "))
    sys.stdout.flush()