stouputils 1.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. stouputils/__init__.py +40 -0
  2. stouputils/__main__.py +86 -0
  3. stouputils/_deprecated.py +37 -0
  4. stouputils/all_doctests.py +160 -0
  5. stouputils/applications/__init__.py +22 -0
  6. stouputils/applications/automatic_docs.py +634 -0
  7. stouputils/applications/upscaler/__init__.py +39 -0
  8. stouputils/applications/upscaler/config.py +128 -0
  9. stouputils/applications/upscaler/image.py +247 -0
  10. stouputils/applications/upscaler/video.py +287 -0
  11. stouputils/archive.py +344 -0
  12. stouputils/backup.py +488 -0
  13. stouputils/collections.py +244 -0
  14. stouputils/continuous_delivery/__init__.py +27 -0
  15. stouputils/continuous_delivery/cd_utils.py +243 -0
  16. stouputils/continuous_delivery/github.py +522 -0
  17. stouputils/continuous_delivery/pypi.py +130 -0
  18. stouputils/continuous_delivery/pyproject.py +147 -0
  19. stouputils/continuous_delivery/stubs.py +86 -0
  20. stouputils/ctx.py +408 -0
  21. stouputils/data_science/config/get.py +51 -0
  22. stouputils/data_science/config/set.py +125 -0
  23. stouputils/data_science/data_processing/image/__init__.py +66 -0
  24. stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
  25. stouputils/data_science/data_processing/image/axis_flip.py +58 -0
  26. stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
  27. stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
  28. stouputils/data_science/data_processing/image/blur.py +59 -0
  29. stouputils/data_science/data_processing/image/brightness.py +54 -0
  30. stouputils/data_science/data_processing/image/canny.py +110 -0
  31. stouputils/data_science/data_processing/image/clahe.py +92 -0
  32. stouputils/data_science/data_processing/image/common.py +30 -0
  33. stouputils/data_science/data_processing/image/contrast.py +53 -0
  34. stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
  35. stouputils/data_science/data_processing/image/denoise.py +378 -0
  36. stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
  37. stouputils/data_science/data_processing/image/invert.py +64 -0
  38. stouputils/data_science/data_processing/image/laplacian.py +60 -0
  39. stouputils/data_science/data_processing/image/median_blur.py +52 -0
  40. stouputils/data_science/data_processing/image/noise.py +59 -0
  41. stouputils/data_science/data_processing/image/normalize.py +65 -0
  42. stouputils/data_science/data_processing/image/random_erase.py +66 -0
  43. stouputils/data_science/data_processing/image/resize.py +69 -0
  44. stouputils/data_science/data_processing/image/rotation.py +80 -0
  45. stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
  46. stouputils/data_science/data_processing/image/sharpening.py +55 -0
  47. stouputils/data_science/data_processing/image/shearing.py +64 -0
  48. stouputils/data_science/data_processing/image/threshold.py +64 -0
  49. stouputils/data_science/data_processing/image/translation.py +71 -0
  50. stouputils/data_science/data_processing/image/zoom.py +83 -0
  51. stouputils/data_science/data_processing/image_augmentation.py +118 -0
  52. stouputils/data_science/data_processing/image_preprocess.py +183 -0
  53. stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
  54. stouputils/data_science/data_processing/technique.py +481 -0
  55. stouputils/data_science/dataset/__init__.py +45 -0
  56. stouputils/data_science/dataset/dataset.py +292 -0
  57. stouputils/data_science/dataset/dataset_loader.py +135 -0
  58. stouputils/data_science/dataset/grouping_strategy.py +296 -0
  59. stouputils/data_science/dataset/image_loader.py +100 -0
  60. stouputils/data_science/dataset/xy_tuple.py +696 -0
  61. stouputils/data_science/metric_dictionnary.py +106 -0
  62. stouputils/data_science/metric_utils.py +847 -0
  63. stouputils/data_science/mlflow_utils.py +206 -0
  64. stouputils/data_science/models/abstract_model.py +149 -0
  65. stouputils/data_science/models/all.py +85 -0
  66. stouputils/data_science/models/base_keras.py +765 -0
  67. stouputils/data_science/models/keras/all.py +38 -0
  68. stouputils/data_science/models/keras/convnext.py +62 -0
  69. stouputils/data_science/models/keras/densenet.py +50 -0
  70. stouputils/data_science/models/keras/efficientnet.py +60 -0
  71. stouputils/data_science/models/keras/mobilenet.py +56 -0
  72. stouputils/data_science/models/keras/resnet.py +52 -0
  73. stouputils/data_science/models/keras/squeezenet.py +233 -0
  74. stouputils/data_science/models/keras/vgg.py +42 -0
  75. stouputils/data_science/models/keras/xception.py +38 -0
  76. stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
  77. stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
  78. stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
  79. stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
  80. stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
  81. stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
  82. stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
  83. stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
  84. stouputils/data_science/models/keras_utils/visualizations.py +416 -0
  85. stouputils/data_science/models/model_interface.py +939 -0
  86. stouputils/data_science/models/sandbox.py +116 -0
  87. stouputils/data_science/range_tuple.py +234 -0
  88. stouputils/data_science/scripts/augment_dataset.py +77 -0
  89. stouputils/data_science/scripts/exhaustive_process.py +133 -0
  90. stouputils/data_science/scripts/preprocess_dataset.py +70 -0
  91. stouputils/data_science/scripts/routine.py +168 -0
  92. stouputils/data_science/utils.py +285 -0
  93. stouputils/decorators.py +605 -0
  94. stouputils/image.py +441 -0
  95. stouputils/installer/__init__.py +18 -0
  96. stouputils/installer/common.py +67 -0
  97. stouputils/installer/downloader.py +101 -0
  98. stouputils/installer/linux.py +144 -0
  99. stouputils/installer/main.py +223 -0
  100. stouputils/installer/windows.py +136 -0
  101. stouputils/io.py +486 -0
  102. stouputils/parallel.py +483 -0
  103. stouputils/print.py +482 -0
  104. stouputils/py.typed +1 -0
  105. stouputils/stouputils/__init__.pyi +15 -0
  106. stouputils/stouputils/_deprecated.pyi +12 -0
  107. stouputils/stouputils/all_doctests.pyi +46 -0
  108. stouputils/stouputils/applications/__init__.pyi +2 -0
  109. stouputils/stouputils/applications/automatic_docs.pyi +106 -0
  110. stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
  111. stouputils/stouputils/applications/upscaler/config.pyi +18 -0
  112. stouputils/stouputils/applications/upscaler/image.pyi +109 -0
  113. stouputils/stouputils/applications/upscaler/video.pyi +60 -0
  114. stouputils/stouputils/archive.pyi +67 -0
  115. stouputils/stouputils/backup.pyi +109 -0
  116. stouputils/stouputils/collections.pyi +86 -0
  117. stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
  118. stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
  119. stouputils/stouputils/continuous_delivery/github.pyi +162 -0
  120. stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
  121. stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
  122. stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
  123. stouputils/stouputils/ctx.pyi +211 -0
  124. stouputils/stouputils/decorators.pyi +252 -0
  125. stouputils/stouputils/image.pyi +172 -0
  126. stouputils/stouputils/installer/__init__.pyi +5 -0
  127. stouputils/stouputils/installer/common.pyi +39 -0
  128. stouputils/stouputils/installer/downloader.pyi +24 -0
  129. stouputils/stouputils/installer/linux.pyi +39 -0
  130. stouputils/stouputils/installer/main.pyi +57 -0
  131. stouputils/stouputils/installer/windows.pyi +31 -0
  132. stouputils/stouputils/io.pyi +213 -0
  133. stouputils/stouputils/parallel.pyi +216 -0
  134. stouputils/stouputils/print.pyi +136 -0
  135. stouputils/stouputils/version_pkg.pyi +15 -0
  136. stouputils/version_pkg.py +189 -0
  137. stouputils-1.14.0.dist-info/METADATA +178 -0
  138. stouputils-1.14.0.dist-info/RECORD +140 -0
  139. stouputils-1.14.0.dist-info/WHEEL +4 -0
  140. stouputils-1.14.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,252 @@
1
+ from .ctx import MeasureTime as MeasureTime, Muffle as Muffle
2
+ from .print import error as error, progress as progress, warning as warning
3
+ from collections.abc import Callable as Callable
4
+ from enum import Enum
5
+ from typing import Any, Literal
6
+
7
+ def measure_time(func: Callable[..., Any] | None = None, *, printer: Callable[..., None] = ..., message: str = '', perf_counter: bool = True, is_generator: bool = False) -> Callable[..., Any]:
8
+ ''' Decorator that will measure the execution time of a function and print it with the given print function
9
+
10
+ \tArgs:
11
+ \t\tfunc\t\t\t(Callable[..., Any] | None): Function to decorate
12
+ \t\tprinter\t\t\t(Callable):\tFunction to use to print the execution time (e.g. debug, info, warning, error, etc.)
13
+ \t\tmessage\t\t\t(str):\t\tMessage to display with the execution time (e.g. "Execution time of Something"),
14
+ \t\t\tdefaults to "Execution time of {func.__name__}"
15
+ \t\tperf_counter\t(bool):\t\tWhether to use time.perf_counter_ns or time.time_ns
16
+ \t\t\tdefaults to True (use time.perf_counter_ns)
17
+ \t\tis_generator\t(bool):\t\tWhether the function is a generator or not (default: False)
18
+ \t\t\tWhen True, the decorator will yield from the function instead of returning it.
19
+
20
+ \tReturns:
21
+ \t\tCallable: Decorator to measure the time of the function.
22
+
23
+ \tExamples:
24
+ \t\t.. code-block:: python
25
+
26
+ \t\t\t> @measure_time(printer=info)
27
+ \t\t\t> def test():
28
+ \t\t\t> pass
29
+ \t\t\t> test() # [INFO HH:MM:SS] Execution time of test: 0.000ms (400ns)
30
+ \t'''
31
+
32
+ class LogLevels(Enum):
33
+ """ Log level for the errors in the decorator handle_error() """
34
+ NONE = 0
35
+ WARNING = 1
36
+ WARNING_TRACEBACK = 2
37
+ ERROR_TRACEBACK = 3
38
+ RAISE_EXCEPTION = 4
39
+
40
+ force_raise_exception: bool
41
+
42
+ def handle_error(func: Callable[..., Any] | None = None, *, exceptions: tuple[type[BaseException], ...] | type[BaseException] = ..., message: str = '', error_log: LogLevels = ..., sleep_time: float = 0.0) -> Callable[..., Any]:
43
+ ''' Decorator that handle an error with different log levels.
44
+
45
+ \tArgs:
46
+ \t\tfunc (Callable[..., Any] | None): \tFunction to decorate
47
+ \t\texceptions\t(tuple[type[BaseException]], ...):\tExceptions to handle
48
+ \t\tmessage\t\t(str):\t\t\t\t\t\t\t\tMessage to display with the error. (e.g. "Error during something")
49
+ \t\terror_log\t(LogLevels):\t\t\t\t\t\tLog level for the errors
50
+ \t\t\tLogLevels.NONE:\t\t\t\t\tNone
51
+ \t\t\tLogLevels.WARNING:\t\t\t\tShow as warning
52
+ \t\t\tLogLevels.WARNING_TRACEBACK:\tShow as warning with traceback
53
+ \t\t\tLogLevels.ERROR_TRACEBACK:\t\tShow as error with traceback
54
+ \t\t\tLogLevels.RAISE_EXCEPTION:\t\tRaise exception
55
+ \t\tsleep_time\t(float):\t\t\t\t\t\t\tTime to sleep after the error (e.g. 0.0 to not sleep, 1.0 to sleep for 1 second)
56
+
57
+ \tExamples:
58
+ \t\t>>> @handle_error
59
+ \t\t... def might_fail():
60
+ \t\t... raise ValueError("Let\'s fail")
61
+
62
+ \t\t>>> @handle_error(error_log=LogLevels.WARNING)
63
+ \t\t... def test():
64
+ \t\t... raise ValueError("Let\'s fail")
65
+ \t\t>>> # test()\t# [WARNING HH:MM:SS] Error during test: (ValueError) Let\'s fail
66
+ \t'''
67
+ def timeout(func: Callable[..., Any] | None = None, *, seconds: float = 60.0, message: str = '') -> Callable[..., Any]:
68
+ ''' Decorator that raises a TimeoutError if the function runs longer than the specified timeout.
69
+
70
+ \tNote: This decorator uses SIGALRM on Unix systems, which only works in the main thread.
71
+ \tOn Windows or in non-main threads, it will fall back to a polling-based approach.
72
+
73
+ \tArgs:
74
+ \t\tfunc\t\t(Callable[..., Any] | None):\tFunction to apply timeout to
75
+ \t\tseconds\t\t(float):\t\t\t\t\t\tTimeout duration in seconds (default: 60.0)
76
+ \t\tmessage\t\t(str):\t\t\t\t\t\t\tCustom timeout message (default: "Function \'{func_name}\' timed out after {seconds} seconds")
77
+
78
+ \tReturns:
79
+ \t\tCallable[..., Any]: Decorator that enforces timeout on the function
80
+
81
+ \tRaises:
82
+ \t\tTimeoutError: If the function execution exceeds the timeout duration
83
+
84
+ \tExamples:
85
+ \t\t>>> @timeout(seconds=2.0)
86
+ \t\t... def slow_function():
87
+ \t\t... time.sleep(5)
88
+ \t\t>>> slow_function() # Raises TimeoutError after 2 seconds
89
+ \t\tTraceback (most recent call last):
90
+ \t\t\t...
91
+ \t\tTimeoutError: Function \'slow_function\' timed out after 2.0 seconds
92
+
93
+ \t\t>>> @timeout(seconds=1.0, message="Custom timeout message")
94
+ \t\t... def another_slow_function():
95
+ \t\t... time.sleep(3)
96
+ \t\t>>> another_slow_function() # Raises TimeoutError after 1 second
97
+ \t\tTraceback (most recent call last):
98
+ \t\t\t...
99
+ \t\tTimeoutError: Custom timeout message
100
+ \t'''
101
+ def retry(func: Callable[..., Any] | None = None, *, exceptions: tuple[type[BaseException], ...] | type[BaseException] = ..., max_attempts: int = 10, delay: float = 1.0, backoff: float = 1.0, message: str = '') -> Callable[..., Any]:
102
+ ''' Decorator that retries a function when specific exceptions are raised.
103
+
104
+ \tArgs:
105
+ \t\tfunc\t\t\t(Callable[..., Any] | None):\t\tFunction to retry
106
+ \t\texceptions\t\t(tuple[type[BaseException], ...]):\tExceptions to catch and retry on
107
+ \t\tmax_attempts\t(int | None):\t\t\t\t\t\tMaximum number of attempts (None for infinite retries)
108
+ \t\tdelay\t\t\t(float):\t\t\t\t\t\t\tInitial delay in seconds between retries (default: 1.0)
109
+ \t\tbackoff\t\t\t(float):\t\t\t\t\t\t\tMultiplier for delay after each retry (default: 1.0 for constant delay)
110
+ \t\tmessage\t\t\t(str):\t\t\t\t\t\t\t\tCustom message to display before ", retrying" (default: "{ExceptionName} encountered while running {func_name}")
111
+
112
+ \tReturns:
113
+ \t\tCallable[..., Any]: Decorator that retries the function on specified exceptions
114
+
115
+ \tExamples:
116
+ \t\t>>> import os
117
+ \t\t>>> @retry(exceptions=PermissionError, max_attempts=3, delay=0.1)
118
+ \t\t... def write_file():
119
+ \t\t... with open("test.txt", "w") as f:
120
+ \t\t... f.write("test")
121
+
122
+ \t\t>>> @retry(exceptions=(OSError, IOError), delay=0.5, backoff=2.0)
123
+ \t\t... def network_call():
124
+ \t\t... pass
125
+
126
+ \t\t>>> @retry(max_attempts=5, delay=1.0)
127
+ \t\t... def might_fail():
128
+ \t\t... pass
129
+ \t'''
130
+ def simple_cache(func: Callable[..., Any] | None = None, *, method: Literal['str', 'pickle'] = 'str') -> Callable[..., Any]:
131
+ ''' Decorator that caches the result of a function based on its arguments.
132
+
133
+ \tThe str method is often faster than the pickle method (by a little) but not as accurate with complex objects.
134
+
135
+ \tArgs:
136
+ \t\tfunc (Callable[..., Any] | None): Function to cache
137
+ \t\tmethod (Literal["str", "pickle"]): The method to use for caching.
138
+ \tReturns:
139
+ \t\tCallable[..., Any]: A decorator that caches the result of a function.
140
+ \tExamples:
141
+ \t\t>>> @simple_cache
142
+ \t\t... def test1(a: int, b: int) -> int:
143
+ \t\t... return a + b
144
+
145
+ \t\t>>> @simple_cache(method="str")
146
+ \t\t... def test2(a: int, b: int) -> int:
147
+ \t\t... return a + b
148
+ \t\t>>> test2(1, 2)
149
+ \t\t3
150
+ \t\t>>> test2(1, 2)
151
+ \t\t3
152
+ \t\t>>> test2(3, 4)
153
+ \t\t7
154
+
155
+ \t\t>>> @simple_cache
156
+ \t\t... def factorial(n: int) -> int:
157
+ \t\t... return n * factorial(n - 1) if n else 1
158
+ \t\t>>> factorial(10) # no previously cached result, makes 11 recursive calls
159
+ \t\t3628800
160
+ \t\t>>> factorial(5) # no new calls, just returns the cached result
161
+ \t\t120
162
+ \t\t>>> factorial(12) # two new recursive calls, factorial(10) is cached
163
+ \t\t479001600
164
+ \t'''
165
+ def abstract(func: Callable[..., Any] | None = None, *, error_log: LogLevels = ...) -> Callable[..., Any]:
166
+ """ Decorator that marks a function as abstract.
167
+
168
+ \tContrary to the abstractmethod decorator from the abc module that raises a TypeError
169
+ \twhen you try to instantiate a class that has abstract methods, this decorator raises
170
+ \ta NotImplementedError ONLY when the decorated function is called, indicating that the function
171
+ \tmust be implemented by a subclass.
172
+
173
+ \tArgs:
174
+ \t\tfunc (Callable[..., Any] | None): The function to mark as abstract
175
+ \t\terror_log (LogLevels): Log level for the error handling
176
+ \t\t\tLogLevels.NONE: None
177
+ \t\t\tLogLevels.WARNING: Show as warning
178
+ \t\t\tLogLevels.WARNING_TRACEBACK: Show as warning with traceback
179
+ \t\t\tLogLevels.ERROR_TRACEBACK: Show as error with traceback
180
+ \t\t\tLogLevels.RAISE_EXCEPTION: Raise exception
181
+
182
+ \tReturns:
183
+ \t\tCallable[..., Any]: Decorator that raises NotImplementedError when called
184
+
185
+ \tExamples:
186
+ \t\t>>> class Base:
187
+ \t\t... @abstract
188
+ \t\t... def method(self):
189
+ \t\t... pass
190
+ \t\t>>> Base().method()
191
+ \t\tTraceback (most recent call last):
192
+ \t\t\t...
193
+ \t\tNotImplementedError: Function 'method' is abstract and must be implemented by a subclass
194
+ \t"""
195
+ def deprecated(func: Callable[..., Any] | None = None, *, message: str = '', version: str = '', error_log: LogLevels = ...) -> Callable[..., Any]:
196
+ ''' Decorator that marks a function as deprecated.
197
+
198
+ \tArgs:
199
+ \t\tfunc (Callable[..., Any] | None): Function to mark as deprecated
200
+ \t\tmessage (str): Additional message to display with the deprecation warning
201
+ \t\tversion (str): Version since when the function is deprecated (e.g. "v1.2.0")
202
+ \t\terror_log (LogLevels): Log level for the deprecation warning
203
+ \t\t\tLogLevels.NONE: None
204
+ \t\t\tLogLevels.WARNING: Show as warning
205
+ \t\t\tLogLevels.WARNING_TRACEBACK: Show as warning with traceback
206
+ \t\t\tLogLevels.ERROR_TRACEBACK: Show as error with traceback
207
+ \t\t\tLogLevels.RAISE_EXCEPTION: Raise exception
208
+ \tReturns:
209
+ \t\tCallable[..., Any]: Decorator that marks a function as deprecated
210
+
211
+ \tExamples:
212
+ \t\t>>> @deprecated
213
+ \t\t... def old_function():
214
+ \t\t... pass
215
+
216
+ \t\t>>> @deprecated(message="Use \'new_function()\' instead", error_log=LogLevels.WARNING)
217
+ \t\t... def another_old_function():
218
+ \t\t... pass
219
+ \t'''
220
+ def silent(func: Callable[..., Any] | None = None, *, mute_stderr: bool = False) -> Callable[..., Any]:
221
+ ''' Decorator that makes a function silent (disable stdout, and stderr if specified).
222
+
223
+ \tAlternative to stouputils.ctx.Muffle.
224
+
225
+ \tArgs:
226
+ \t\tfunc\t\t\t(Callable[..., Any] | None):\tFunction to make silent
227
+ \t\tmute_stderr\t\t(bool):\t\t\t\t\t\t\tWhether to mute stderr or not
228
+
229
+ \tExamples:
230
+ \t\t>>> @silent
231
+ \t\t... def test():
232
+ \t\t... print("Hello, world!")
233
+ \t\t>>> test()
234
+
235
+ \t\t>>> @silent(mute_stderr=True)
236
+ \t\t... def test2():
237
+ \t\t... print("Hello, world!")
238
+ \t\t>>> test2()
239
+
240
+ \t\t>>> silent(print)("Hello, world!")
241
+ \t'''
242
+ def _get_func_name(func: Callable[..., Any]) -> str:
243
+ ''' Get the name of a function, returns "<unknown>" if the name cannot be retrieved. '''
244
+ def _get_wrapper_name(decorator_name: str, func: Callable[..., Any]) -> str:
245
+ ''' Get a descriptive name for a wrapper function.
246
+
247
+ \tArgs:
248
+ \t\tdecorator_name\t(str):\t\t\t\t\tName of the decorator
249
+ \t\tfunc\t\t\t(Callable[..., Any]):\tFunction being decorated
250
+ \tReturns:
251
+ \t\tstr: Combined name for the wrapper function (e.g., "stouputils.decorators.handle_error@function_name")
252
+ \t'''
@@ -0,0 +1,172 @@
1
+ import numpy as np
2
+ from .io import super_open as super_open
3
+ from .print import debug as debug, info as info
4
+ from PIL import Image
5
+ from collections.abc import Callable
6
+ from numpy.typing import NDArray
7
+ from typing import Any, TypeVar
8
+
9
+ PIL_Image_or_NDArray = TypeVar('PIL_Image_or_NDArray', bound='Image.Image | NDArray[np.number]')
10
+
11
+ def image_resize[PIL_Image_or_NDArray](image: PIL_Image_or_NDArray, max_result_size: int, resampling: Image.Resampling | None = None, min_or_max: Callable[[int, int], int] = ..., return_type: type[PIL_Image_or_NDArray] | str = 'same', keep_aspect_ratio: bool = True) -> Any:
12
+ ''' Resize an image while preserving its aspect ratio by default.
13
+ \tScales the image so that its largest dimension equals max_result_size.
14
+
15
+ \tArgs:
16
+ \t\timage (Image.Image | np.ndarray): The image to resize.
17
+ \t\tmax_result_size (int): Maximum size for the largest dimension.
18
+ \t\tresampling (Image.Resampling | None): PIL resampling filter to use (default: Image.Resampling.LANCZOS).
19
+ \t\tmin_or_max (Callable): Function to use to get the minimum or maximum of the two ratios.
20
+ \t\treturn_type (type | str): Type of the return value (Image.Image, np.ndarray, or "same" to match input type).
21
+ \t\tkeep_aspect_ratio (bool): Whether to keep the aspect ratio.
22
+ \tReturns:
23
+ \t\tImage.Image | NDArray[np.number]: The resized image with preserved aspect ratio.
24
+ \tExamples:
25
+ \t\t>>> # Test with (height x width x channels) numpy array
26
+ \t\t>>> import numpy as np
27
+ \t\t>>> array = np.random.randint(0, 255, (100, 50, 3), dtype=np.uint8)
28
+ \t\t>>> image_resize(array, 100).shape
29
+ \t\t(100, 50, 3)
30
+ \t\t>>> image_resize(array, 100, min_or_max=max).shape
31
+ \t\t(100, 50, 3)
32
+ \t\t>>> image_resize(array, 100, min_or_max=min).shape
33
+ \t\t(200, 100, 3)
34
+
35
+ \t\t>>> # Test with PIL Image
36
+ \t\t>>> from PIL import Image
37
+ \t\t>>> pil_image: Image.Image = Image.new(\'RGB\', (200, 100))
38
+ \t\t>>> image_resize(pil_image, 50).size
39
+ \t\t(50, 25)
40
+ \t\t>>> # Test with different return types
41
+ \t\t>>> resized_array = image_resize(array, 50, return_type=np.ndarray)
42
+ \t\t>>> isinstance(resized_array, np.ndarray)
43
+ \t\tTrue
44
+ \t\t>>> resized_array.shape
45
+ \t\t(50, 25, 3)
46
+ \t\t>>> # Test with different resampling methods
47
+ \t\t>>> image_resize(pil_image, 50, resampling=Image.Resampling.NEAREST).size
48
+ \t\t(50, 25)
49
+ \t'''
50
+ def auto_crop[PIL_Image_or_NDArray](image: PIL_Image_or_NDArray, mask: NDArray[np.bool_] | None = None, threshold: int | float | Callable[[NDArray[np.number]], int | float] | None = None, return_type: type[PIL_Image_or_NDArray] | str = 'same', contiguous: bool = True) -> Any:
51
+ ''' Automatically crop an image to remove zero or uniform regions.
52
+
53
+ \tThis function crops the image to keep only the region where pixels are non-zero
54
+ \t(or above a threshold). It can work with a mask or directly analyze the image.
55
+
56
+ \tArgs:
57
+ \t\timage (Image.Image | NDArray):\t The image to crop.
58
+ \t\tmask (NDArray[bool] | None): Optional binary mask indicating regions to keep.
59
+ \t\tthreshold (int | float | Callable): Threshold value or function (default: np.min).
60
+ \t\treturn_type (type | str): Type of the return value (Image.Image, NDArray[np.number], or "same" to match input type).
61
+ \t\tcontiguous (bool): If True (default), crop to bounding box. If False, remove entire rows/columns with no content.
62
+ \tReturns:
63
+ \t\tImage.Image | NDArray[np.number]: The cropped image.
64
+
65
+ \tExamples:
66
+ \t\t>>> # Test with numpy array with zeros on edges
67
+ \t\t>>> import numpy as np
68
+ \t\t>>> array = np.zeros((100, 100, 3), dtype=np.uint8)
69
+ \t\t>>> array[20:80, 30:70] = 255 # White rectangle in center
70
+ \t\t>>> cropped = auto_crop(array, return_type=np.ndarray)
71
+ \t\t>>> cropped.shape
72
+ \t\t(60, 40, 3)
73
+
74
+ \t\t>>> # Test with custom mask
75
+ \t\t>>> mask = np.zeros((100, 100), dtype=bool)
76
+ \t\t>>> mask[10:90, 10:90] = True
77
+ \t\t>>> cropped_with_mask = auto_crop(array, mask=mask, return_type=np.ndarray)
78
+ \t\t>>> cropped_with_mask.shape
79
+ \t\t(80, 80, 3)
80
+
81
+ \t\t>>> # Test with PIL Image
82
+ \t\t>>> from PIL import Image
83
+ \t\t>>> pil_image = Image.new(\'RGB\', (100, 100), (0, 0, 0))
84
+ \t\t>>> from PIL import ImageDraw
85
+ \t\t>>> draw = ImageDraw.Draw(pil_image)
86
+ \t\t>>> draw.rectangle([25, 25, 75, 75], fill=(255, 255, 255))
87
+ \t\t>>> cropped_pil = auto_crop(pil_image)
88
+ \t\t>>> cropped_pil.size
89
+ \t\t(51, 51)
90
+
91
+ \t\t>>> # Test with threshold
92
+ \t\t>>> array_gray = np.ones((100, 100), dtype=np.uint8) * 10
93
+ \t\t>>> array_gray[20:80, 30:70] = 255
94
+ \t\t>>> cropped_threshold = auto_crop(array_gray, threshold=50, return_type=np.ndarray)
95
+ \t\t>>> cropped_threshold.shape
96
+ \t\t(60, 40)
97
+
98
+ \t\t>>> # Test with callable threshold (using lambda to avoid min value)
99
+ \t\t>>> array_gray2 = np.ones((100, 100), dtype=np.uint8) * 10
100
+ \t\t>>> array_gray2[20:80, 30:70] = 255
101
+ \t\t>>> cropped_max = auto_crop(array_gray2, threshold=lambda x: 50, return_type=np.ndarray)
102
+ \t\t>>> cropped_max.shape
103
+ \t\t(60, 40)
104
+
105
+ \t>>> # Test with non-contiguous crop
106
+ \t>>> array_sparse = np.zeros((100, 100, 3), dtype=np.uint8)
107
+ \t>>> array_sparse[10, 10] = 255
108
+ \t>>> array_sparse[50, 50] = 255
109
+ \t>>> array_sparse[90, 90] = 255
110
+ \t>>> cropped_contiguous = auto_crop(array_sparse, contiguous=True, return_type=np.ndarray)
111
+ \t>>> cropped_contiguous.shape # Bounding box from (10,10) to (90,90)
112
+ \t(81, 81, 3)
113
+ \t>>> cropped_non_contiguous = auto_crop(array_sparse, contiguous=False, return_type=np.ndarray)
114
+ \t>>> cropped_non_contiguous.shape # Only rows/cols 10, 50, 90
115
+ \t(3, 3, 3)
116
+
117
+ \t>>> # Test with 3D crop on depth dimension
118
+ \t>>> array_3d = np.zeros((50, 50, 10), dtype=np.uint8)
119
+ \t>>> array_3d[10:40, 10:40, 2:8] = 255 # Content only in depth slices 2-7
120
+ \t>>> cropped_3d = auto_crop(array_3d, contiguous=True, return_type=np.ndarray)
121
+ \t>>> cropped_3d.shape # Should crop all 3 dimensions
122
+ \t(30, 30, 6)
123
+ \t'''
124
+ def numpy_to_gif(path: str, array: NDArray[np.integer | np.floating | np.bool_], duration: int = 100, loop: int = 0, mkdir: bool = True, **kwargs: Any) -> None:
125
+ ''' Generate a \'.gif\' file from a numpy array for 3D/4D visualization.
126
+
127
+ \tArgs:
128
+ \t\tpath (str): Path to the output .gif file.
129
+ \t\tarray (NDArray): Numpy array to be dumped (must be 3D or 4D).
130
+ \t\t\t3D: (depth, height, width) - e.g. (64, 1024, 1024)
131
+ \t\t\t4D: (depth, height, width, channels) - e.g. (50, 64, 1024, 3)
132
+ \t\tduration (int): Duration between frames in milliseconds.
133
+ \t\tloop (int): Number of loops (0 = infinite).
134
+ \t\tmkdir (bool): Create the directory if it does not exist.
135
+ \t\t**kwargs (Any): Additional keyword arguments for PIL.Image.save().
136
+
137
+ \tExamples:
138
+
139
+ \t\t.. code-block:: python
140
+
141
+ \t\t\t> # 3D array example
142
+ \t\t\t> array = np.random.randint(0, 256, (10, 100, 100), dtype=np.uint8)
143
+ \t\t\t> numpy_to_gif("output_10_frames_100x100.gif", array, duration=200, loop=0)
144
+
145
+ \t\t\t> # 4D array example (batch of 3D images)
146
+ \t\t\t> array_4d = np.random.randint(0, 256, (5, 10, 100, 3), dtype=np.uint8)
147
+ \t\t\t> numpy_to_gif("output_50_frames_100x100.gif", array_4d, duration=200)
148
+
149
+ \t\t\t> total_duration = 1000 # 1 second
150
+ \t\t\t> numpy_to_gif("output_1s.gif", array, duration=total_duration // len(array))
151
+ \t'''
152
+ def numpy_to_obj(path: str, array: NDArray[np.integer | np.floating | np.bool_], threshold: float = 0.5, step_size: int = 1, pad_array: bool = True, verbose: int = 0) -> None:
153
+ ''' Generate a \'.obj\' file from a numpy array for 3D visualization using marching cubes.
154
+
155
+ \tArgs:
156
+ \t\tpath (str): Path to the output .obj file.
157
+ \t\tarray (NDArray): Numpy array to be dumped (must be 3D).
158
+ \t\tthreshold (float): Threshold level for marching cubes (0.5 for binary data).
159
+ \t\tstep_size (int): Step size for marching cubes (higher = simpler mesh, faster generation).
160
+ \t\tpad_array (bool): If True, pad array with zeros to ensure closed volumes for border cells.
161
+ \t\tverbose (int): Verbosity level (0 = no output, 1 = some output, 2 = full output).
162
+
163
+ \tExamples:
164
+
165
+ \t\t.. code-block:: python
166
+
167
+ \t\t\t> array = np.random.rand(64, 64, 64) > 0.5 # Binary volume
168
+ \t\t\t> numpy_to_obj("output_mesh.obj", array, threshold=0.5, step_size=2, pad_array=True, verbose=1)
169
+
170
+ \t\t\t> array = my_3d_data # Some 3D numpy array (e.g. human lung scan)
171
+ \t\t\t> numpy_to_obj("output_mesh.obj", array, threshold=0.3)
172
+ \t'''
@@ -0,0 +1,5 @@
1
+ from .common import *
2
+ from .downloader import *
3
+ from .linux import *
4
+ from .main import *
5
+ from .windows import *
@@ -0,0 +1,39 @@
1
+ from ..print import warning as warning
2
+ from typing import Literal
3
+
4
+ def prompt_for_path(prompt_message: str, default_path: str) -> str:
5
+ """ Prompt the user to override a default path.
6
+
7
+ \tArgs:
8
+ \t\tprompt_message (str): The message to display to the user.
9
+ \t\tdefault_path (str): The default path to suggest.
10
+
11
+ \tReturns:
12
+ \t\tstr: The path entered by the user, or the default path if they pressed Enter.
13
+ \t"""
14
+ def ask_install_type(ask_global: int, default_local_path: str, default_global_path: str | None) -> Literal['g', 'l']:
15
+ ''' Determine the installation type (global \'g\' or local \'l\') based on user input.
16
+
17
+ \tArgs:
18
+ \t\task_global (int): 0 = ask, 1 = force global, 2 = force local.
19
+ \t\tdefault_local_path (str): The default local path.
20
+ \t\tdefault_global_path (str | None): The default global path (if applicable).
21
+
22
+ \tReturns:
23
+ \t\tLiteral["g", "l"]: \'g\' for global install, \'l\' for local install.
24
+
25
+ \tExamples:
26
+ \t\t.. code-block:: python
27
+
28
+ \t\t\t> # Ask the user while providing default paths
29
+ \t\t\t> install_choice: str = ask_install_type(0, f"{os.getcwd()}/MyProgram", "C:\\Program Files\\MyProgram")
30
+ \t\t\tg
31
+
32
+ \t\t\t> # Don\'t ask, force global
33
+ \t\t\t> install_choice: str = ask_install_type(1, ...)
34
+ \t\t\tg
35
+
36
+ \t\t\t> # Don\'t ask, force local
37
+ \t\t\t> install_choice: str = ask_install_type(2, ...)
38
+ \t\t\tl
39
+ \t'''
@@ -0,0 +1,24 @@
1
+ from ..print import info as info, warning as warning
2
+ from .main import install_program as install_program
3
+
4
+ def download_executable(download_urls: dict[str, str], program_name: str, append_to_path: str = '') -> bool:
5
+ """ Ask the user if they want to download the program (ex: waifu2x-ncnn-vulkan).
6
+ \tIf yes, try to download the program from the GitHub releases page.
7
+
8
+ \tArgs:
9
+ \t\tdownload_urls (dict[str, str]): The URLs to download the program from.
10
+ \t\tprogram_name (str): The name of the program to download.
11
+
12
+ \tReturns:
13
+ \t\tbool: True if the program is now ready to use, False otherwise.
14
+ \t"""
15
+ def check_executable(executable: str, executable_help_text: str, download_urls: dict[str, str], append_to_path: str = '') -> None:
16
+ ''' Check if the executable exists, optionally download it if it doesn\'t.
17
+
18
+ \tArgs:
19
+ \t\texecutable (str): The path to the executable.
20
+ \t\texecutable_help_text (str): The help text to check for in the executable\'s output.
21
+ \t\tdownload_urls (dict[str, str]): The URLs to download the executable from.
22
+ \t\tappend_to_path (str): The path to append to the executable\'s path.
23
+ \t\t\t(ex: "bin" if executables are in the bin folder)
24
+ \t'''
@@ -0,0 +1,39 @@
1
+ from ..decorators import LogLevels as LogLevels, handle_error as handle_error
2
+ from ..io import clean_path as clean_path
3
+ from ..print import debug as debug, info as info, warning as warning
4
+ from .common import ask_install_type as ask_install_type, prompt_for_path as prompt_for_path
5
+
6
+ def add_to_path_linux(install_path: str) -> bool:
7
+ """ Suggest how to add install_path to PATH on Linux.
8
+
9
+ \tChecks the current shell and provides instructions for adding the path
10
+ \tto the appropriate configuration file (e.g., .bashrc, .zshrc, config.fish).
11
+
12
+ \tArgs:
13
+ \t\tinstall_path (str): The path to add to the PATH environment variable.
14
+
15
+ \tReturns:
16
+ \t\tbool: True if instructions were provided, False otherwise (e.g., unknown shell).
17
+ \t"""
18
+ def check_admin_linux() -> bool:
19
+ """ Check if the script is running with root privileges on Linux/macOS.
20
+
21
+ \tReturns:
22
+ \t\tbool: True if the effective user ID is 0 (root), False otherwise.
23
+ \t"""
24
+ def get_install_path_linux(program_name: str, ask_global: int = 0, add_path: bool = True, append_to_path: str = '', default_global: str = '/usr/local/bin') -> str:
25
+ ''' Get the installation path for the program on Linux/macOS.
26
+
27
+ \tArgs:
28
+ \t\tprogram_name (str): The name of the program to install.
29
+ \t\task_global (int): 0 = ask for anything, 1 = install globally, 2 = install locally
30
+ \t\tadd_path (bool): Whether to add the program to the PATH environment variable. (Only if installed globally)
31
+ \t\tappend_to_path (str): String to append to the installation path when adding to PATH.
32
+ \t\t\t(ex: "bin" if executables are in the bin folder)
33
+ \t\tdefault_global (str): The default global installation path.
34
+ \t\t\t(Default is "/usr/local/bin" which is the most common location for executables on Linux/macOS,
35
+ \t\t\tcould be "/opt" or any other directory)
36
+
37
+ \tReturns:
38
+ \t\tstr: The chosen installation path, or an empty string if installation is cancelled.
39
+ \t'''
@@ -0,0 +1,57 @@
1
+ from .common import *
2
+ from .linux import *
3
+ from .windows import *
4
+ from ..decorators import LogLevels as LogLevels, handle_error as handle_error
5
+ from ..print import info as info, warning as warning
6
+ from collections.abc import Callable as Callable
7
+
8
+ def extract_archive(extraction_path: str, temp_dir: str, extract_func: Callable[[str], None], get_file_list_func: Callable[[], list[str]]) -> None:
9
+ """ Helper function to extract archive files with consistent handling.
10
+
11
+ \tArgs:
12
+ \t\textraction_path (str): Path where files should be extracted
13
+ \t\ttemp_dir (str): Temporary directory for intermediate extraction
14
+ \t\textract_func (Callable[[str], None]): Function to extract the archive
15
+ \t\tget_file_list_func (Callable[[], list[str]]): Function to get the list of files in the archive
16
+ \t"""
17
+ def get_install_path(program_name: str, platform_str: str = ..., ask_global: int = 0, add_path: bool = True, append_to_path: str = '') -> str:
18
+ ''' Get the installation path for the program on the current platform.
19
+
20
+ \tArgs:
21
+ \t\tprogram_name (str): The name of the program to install.
22
+ \t\tplatform_str (str): The platform to get the installation path for.
23
+ \t\task_global (int): Whether to ask the user for a path, 0 = ask, 1 = install globally, 2 = install locally.
24
+ \t\tadd_path (bool): Whether to add the program to the PATH environment variable.
25
+ \t\tappend_to_path (str): String to append to the installation path when adding to PATH.
26
+ \t\t\t(ex: "bin" if executables are in the bin folder)
27
+
28
+ \tReturns:
29
+ \t\tstr: The installation path for the program.
30
+ \t'''
31
+ def add_to_path(install_path: str, platform_str: str = ...) -> bool:
32
+ ''' Add the program to the PATH environment variable.
33
+
34
+ \tArgs:
35
+ \t\tinstall_path (str): The path to the program to add to the PATH environment variable.
36
+ \t\tplatform_str (str): The platform you are running on (ex: "Windows", "Linux", "Darwin", ...),
37
+ \t\t\twe use this to determine the installation path if not provided.
38
+
39
+ \tReturns:
40
+ \t\tbool: True if add to PATH was successful, False otherwise.
41
+ \t'''
42
+ def install_program(input_path: str, install_path: str = '', platform_str: str = ..., program_name: str = '', add_path: bool = True, append_to_path: str = '') -> bool:
43
+ ''' Install a program to a specific path from a local zip file or URL.
44
+
45
+ \tArgs:
46
+ \t\tinput_path (str): Path to a zip file or a download URL.
47
+ \t\tinstall_path (str): The directory to extract the program into, we ask user for a path if not provided.
48
+ \t\tplatform_str (str): The platform you are running on (ex: "Windows", "Linux", "Darwin", ...),
49
+ \t\t\twe use this to determine the installation path if not provided.
50
+ \t\tadd_path (bool): Whether to add the program to the PATH environment variable.
51
+ \t\tprogram_name (str): Override the program name, we get it from the input path if not provided.
52
+ \t\tappend_to_path (str): String to append to the installation path when adding to PATH.
53
+ \t\t\t(ex: "bin" if executables are in the bin folder)
54
+
55
+ \tReturns:
56
+ \t\tbool: True if installation was successful, False otherwise.
57
+ \t'''
@@ -0,0 +1,31 @@
1
+ from ..decorators import LogLevels as LogLevels, handle_error as handle_error
2
+ from ..io import clean_path as clean_path
3
+ from ..print import debug as debug, info as info, warning as warning
4
+ from .common import ask_install_type as ask_install_type, prompt_for_path as prompt_for_path
5
+
6
+ def add_to_path_windows(install_path: str) -> bool | None:
7
+ """ Add install_path to the User PATH environment variable on Windows.
8
+
9
+ \tArgs:
10
+ \t\tinstall_path (str): The path to add to the User PATH environment variable.
11
+
12
+ \tReturns:
13
+ \t\tbool | None: True if the path was added to the User PATH environment variable, None otherwise.
14
+ \t"""
15
+ def check_admin_windows() -> bool:
16
+ """ Check if the script is running with administrator privileges on Windows. """
17
+ def get_install_path_windows(program_name: str, ask_global: int = 0, add_path: bool = True, append_to_path: str = '', default_global: str = ...) -> str:
18
+ ''' Get the installation path for the program
19
+
20
+ \tArgs:
21
+ \t\tprogram_name (str): The name of the program to install.
22
+ \t\task_global (int): 0 = ask for anything, 1 = install globally, 2 = install locally
23
+ \t\tadd_path (bool): Whether to add the program to the PATH environment variable. (Only if installed globally)
24
+ \t\tappend_to_path (str): String to append to the installation path when adding to PATH.
25
+ \t\t\t(ex: "bin" if executables are in the bin folder)
26
+ \t\tdefault_global (str): The default global installation path.
27
+ \t\t\t(Default is "C:\\Program Files" which is the most common location for executables on Windows)
28
+
29
+ \tReturns:
30
+ \t\tstr: The installation path.
31
+ \t'''