stouputils 1.10.2__tar.gz → 1.10.4__tar.gz

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 (139) hide show
  1. {stouputils-1.10.2 → stouputils-1.10.4}/PKG-INFO +1 -1
  2. {stouputils-1.10.2 → stouputils-1.10.4}/pyproject.toml +1 -1
  3. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/image.py +28 -13
  4. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/image.pyi +14 -5
  5. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/parallel.py +8 -6
  6. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/parallel.pyi +5 -5
  7. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/print.py +3 -2
  8. {stouputils-1.10.2 → stouputils-1.10.4}/.gitignore +0 -0
  9. {stouputils-1.10.2 → stouputils-1.10.4}/LICENSE +0 -0
  10. {stouputils-1.10.2 → stouputils-1.10.4}/README.md +0 -0
  11. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/__init__.py +0 -0
  12. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/__init__.pyi +0 -0
  13. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/__main__.py +0 -0
  14. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/_deprecated.py +0 -0
  15. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/_deprecated.pyi +0 -0
  16. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/all_doctests.py +0 -0
  17. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/all_doctests.pyi +0 -0
  18. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/__init__.py +0 -0
  19. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/__init__.pyi +0 -0
  20. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/automatic_docs.py +0 -0
  21. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/automatic_docs.pyi +0 -0
  22. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/__init__.py +0 -0
  23. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/__init__.pyi +0 -0
  24. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/config.py +0 -0
  25. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/config.pyi +0 -0
  26. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/image.py +0 -0
  27. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/image.pyi +0 -0
  28. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/video.py +0 -0
  29. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/applications/upscaler/video.pyi +0 -0
  30. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/archive.py +0 -0
  31. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/archive.pyi +0 -0
  32. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/backup.py +0 -0
  33. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/backup.pyi +0 -0
  34. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/collections.py +0 -0
  35. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/collections.pyi +0 -0
  36. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/__init__.py +0 -0
  37. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/__init__.pyi +0 -0
  38. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/cd_utils.py +0 -0
  39. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/cd_utils.pyi +0 -0
  40. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/github.py +0 -0
  41. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/github.pyi +0 -0
  42. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/pypi.py +0 -0
  43. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/pypi.pyi +0 -0
  44. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/pyproject.py +0 -0
  45. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/pyproject.pyi +0 -0
  46. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/stubs.py +0 -0
  47. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/continuous_delivery/stubs.pyi +0 -0
  48. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/ctx.py +0 -0
  49. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/ctx.pyi +0 -0
  50. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/config/get.py +0 -0
  51. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/config/set.py +0 -0
  52. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/__init__.py +0 -0
  53. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
  54. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
  55. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
  56. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
  57. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/blur.py +0 -0
  58. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/brightness.py +0 -0
  59. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/canny.py +0 -0
  60. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/clahe.py +0 -0
  61. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/common.py +0 -0
  62. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/contrast.py +0 -0
  63. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
  64. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/denoise.py +0 -0
  65. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
  66. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/invert.py +0 -0
  67. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
  68. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
  69. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/noise.py +0 -0
  70. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/normalize.py +0 -0
  71. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
  72. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/resize.py +0 -0
  73. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/rotation.py +0 -0
  74. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
  75. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
  76. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/shearing.py +0 -0
  77. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/threshold.py +0 -0
  78. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/translation.py +0 -0
  79. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image/zoom.py +0 -0
  80. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
  81. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
  82. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
  83. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/data_processing/technique.py +0 -0
  84. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/dataset/__init__.py +0 -0
  85. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/dataset/dataset.py +0 -0
  86. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/dataset/dataset_loader.py +0 -0
  87. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
  88. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/dataset/image_loader.py +0 -0
  89. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/dataset/xy_tuple.py +0 -0
  90. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/metric_dictionnary.py +0 -0
  91. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/metric_utils.py +0 -0
  92. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/mlflow_utils.py +0 -0
  93. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/abstract_model.py +0 -0
  94. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/all.py +0 -0
  95. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/base_keras.py +0 -0
  96. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/all.py +0 -0
  97. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/convnext.py +0 -0
  98. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/densenet.py +0 -0
  99. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/efficientnet.py +0 -0
  100. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/mobilenet.py +0 -0
  101. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/resnet.py +0 -0
  102. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/squeezenet.py +0 -0
  103. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/vgg.py +0 -0
  104. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras/xception.py +0 -0
  105. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
  106. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
  107. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
  108. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
  109. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
  110. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
  111. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
  112. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
  113. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/keras_utils/visualizations.py +0 -0
  114. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/model_interface.py +0 -0
  115. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/models/sandbox.py +0 -0
  116. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/range_tuple.py +0 -0
  117. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/scripts/augment_dataset.py +0 -0
  118. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/scripts/exhaustive_process.py +0 -0
  119. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
  120. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/scripts/routine.py +0 -0
  121. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/data_science/utils.py +0 -0
  122. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/decorators.py +0 -0
  123. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/decorators.pyi +0 -0
  124. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/__init__.py +0 -0
  125. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/__init__.pyi +0 -0
  126. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/common.py +0 -0
  127. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/common.pyi +0 -0
  128. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/downloader.py +0 -0
  129. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/downloader.pyi +0 -0
  130. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/linux.py +0 -0
  131. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/linux.pyi +0 -0
  132. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/main.py +0 -0
  133. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/main.pyi +0 -0
  134. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/windows.py +0 -0
  135. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/installer/windows.pyi +0 -0
  136. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/io.py +0 -0
  137. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/io.pyi +0 -0
  138. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/print.pyi +0 -0
  139. {stouputils-1.10.2 → stouputils-1.10.4}/stouputils/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stouputils
3
- Version: 1.10.2
3
+ Version: 1.10.4
4
4
  Summary: Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more.
5
5
  Project-URL: Homepage, https://github.com/Stoupy51/stouputils
6
6
  Project-URL: Issues, https://github.com/Stoupy51/stouputils/issues
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "stouputils"
8
- version = "1.10.2"
8
+ version = "1.10.4"
9
9
  description = "Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.12"
@@ -12,7 +12,7 @@ See stouputils.data_science.data_processing for lots more image processing utili
12
12
  # Imports
13
13
  import os
14
14
  from collections.abc import Callable
15
- from typing import TYPE_CHECKING, Any, cast
15
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
16
16
 
17
17
  from .io import super_open
18
18
  from .print import debug, info
@@ -22,14 +22,15 @@ if TYPE_CHECKING:
22
22
  from numpy.typing import NDArray
23
23
  from PIL import Image
24
24
 
25
+ PIL_Image_or_NDArray = TypeVar("PIL_Image_or_NDArray", bound="Image.Image | NDArray[np.number]")
25
26
 
26
27
  # Functions
27
28
  def image_resize(
28
- image: "Image.Image | NDArray[np.number]",
29
+ image: PIL_Image_or_NDArray,
29
30
  max_result_size: int,
30
31
  resampling: "Image.Resampling | None" = None,
31
32
  min_or_max: Callable[[int, int], int] = max,
32
- return_type: type["Image.Image | NDArray[np.number]"] | str = "same",
33
+ return_type: type[PIL_Image_or_NDArray] | str = "same",
33
34
  keep_aspect_ratio: bool = True,
34
35
  ) -> Any:
35
36
  """ Resize an image while preserving its aspect ratio by default.
@@ -114,17 +115,17 @@ def image_resize(
114
115
  return new_image
115
116
  else:
116
117
  return np.array(new_image)
117
- elif return_type == np.ndarray:
118
+ elif return_type != Image.Image:
118
119
  return np.array(new_image)
119
120
  else:
120
121
  return new_image
121
122
 
122
123
 
123
124
  def auto_crop(
124
- image: "Image.Image | NDArray[np.number]",
125
+ image: PIL_Image_or_NDArray,
125
126
  mask: "NDArray[np.bool_] | None" = None,
126
127
  threshold: int | float | Callable[["NDArray[np.number]"], int | float] | None = None,
127
- return_type: type["Image.Image | NDArray[np.number]"] | str = "same",
128
+ return_type: type[PIL_Image_or_NDArray] | str = "same",
128
129
  contiguous: bool = True,
129
130
  ) -> Any:
130
131
  """ Automatically crop an image to remove zero or uniform regions.
@@ -231,7 +232,7 @@ def auto_crop(
231
232
 
232
233
  # Return original if no content found
233
234
  if not (np.any(rows_with_content) and np.any(cols_with_content)):
234
- return image_array if return_type == np.ndarray else (image if original_was_pil else Image.fromarray(image_array))
235
+ return image_array if return_type != Image.Image else (image if original_was_pil else Image.fromarray(image_array))
235
236
 
236
237
  # Crop based on contiguous parameter
237
238
  if contiguous:
@@ -257,7 +258,7 @@ def auto_crop(
257
258
  # Return in requested format
258
259
  if return_type == "same":
259
260
  return Image.fromarray(cropped_array) if original_was_pil else cropped_array
260
- return cropped_array if return_type == np.ndarray else Image.fromarray(cropped_array)
261
+ return cropped_array if return_type != Image.Image else Image.fromarray(cropped_array)
261
262
 
262
263
 
263
264
  def numpy_to_gif(
@@ -268,11 +269,13 @@ def numpy_to_gif(
268
269
  mkdir: bool = True,
269
270
  **kwargs: Any
270
271
  ) -> None:
271
- """ Generate a '.gif' file from a numpy array for 3D visualization.
272
+ """ Generate a '.gif' file from a numpy array for 3D/4D visualization.
272
273
 
273
274
  Args:
274
275
  path (str): Path to the output .gif file.
275
- array (NDArray): Numpy array to be dumped (must be 3D with depth as first axis, e.g. 64x1024x1024).
276
+ array (NDArray): Numpy array to be dumped (must be 3D or 4D).
277
+ 3D: (depth, height, width) - e.g. (64, 1024, 1024)
278
+ 4D: (depth, height, width, channels) - e.g. (50, 64, 1024, 3)
276
279
  duration (int): Duration between frames in milliseconds.
277
280
  loop (int): Number of loops (0 = infinite).
278
281
  mkdir (bool): Create the directory if it does not exist.
@@ -282,9 +285,14 @@ def numpy_to_gif(
282
285
 
283
286
  .. code-block:: python
284
287
 
288
+ > # 3D array example
285
289
  > array = np.random.randint(0, 256, (10, 100, 100), dtype=np.uint8)
286
290
  > numpy_to_gif("output_10_frames_100x100.gif", array, duration=200, loop=0)
287
291
 
292
+ > # 4D array example (batch of 3D images)
293
+ > array_4d = np.random.randint(0, 256, (5, 10, 100, 3), dtype=np.uint8)
294
+ > numpy_to_gif("output_50_frames_100x100.gif", array_4d, duration=200)
295
+
288
296
  > total_duration = 1000 # 1 second
289
297
  > numpy_to_gif("output_1s.gif", array, duration=total_duration // len(array))
290
298
  """
@@ -293,11 +301,15 @@ def numpy_to_gif(
293
301
  from PIL import Image
294
302
 
295
303
  # Assertions
296
- assert array.ndim == 3, f"The input array must be 3D, got shape {array.shape} instead."
304
+ assert array.ndim in (3, 4), f"The input array must be 3D or 4D, got shape {array.shape} instead."
305
+ if array.ndim == 4:
306
+ assert array.shape[-1] in (1, 3), f"For 4D arrays, the last dimension must be 1 or 3 (channels), got shape {array.shape} instead."
297
307
 
298
308
  # Create directory if needed
299
309
  if mkdir:
300
- os.makedirs(os.path.dirname(path), exist_ok=True)
310
+ dirname: str = os.path.dirname(path)
311
+ if dirname != "":
312
+ os.makedirs(dirname, exist_ok=True)
301
313
 
302
314
  # Normalize array if outside [0-255] range to [0-1]
303
315
  array = array.astype(np.float32)
@@ -308,7 +320,10 @@ def numpy_to_gif(
308
320
  # Scale to [0-255] if in [0-1] range
309
321
  mini, maxi = np.min(array), np.max(array)
310
322
  if mini >= 0.0 and maxi <= 1.0:
311
- array = (array * 255).astype(np.uint8)
323
+ array = (array * 255)
324
+
325
+ # Ensure array is uint8 for PIL compatibility
326
+ array = array.astype(np.uint8)
312
327
 
313
328
  # Convert each slice to PIL Image
314
329
  pil_images: list[Image.Image] = [
@@ -4,9 +4,11 @@ from .print import debug as debug, info as info
4
4
  from PIL import Image
5
5
  from collections.abc import Callable
6
6
  from numpy.typing import NDArray
7
- from typing import Any
7
+ from typing import Any, TypeVar
8
8
 
9
- def image_resize(image: Image.Image | NDArray[np.number], max_result_size: int, resampling: Image.Resampling | None = None, min_or_max: Callable[[int, int], int] = ..., return_type: type[Image.Image | NDArray[np.number]] | str = 'same', keep_aspect_ratio: bool = True) -> Any:
9
+ PIL_Image_or_NDArray = TypeVar('PIL_Image_or_NDArray', bound='Image.Image | NDArray[np.number]')
10
+
11
+ def image_resize(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:
10
12
  ''' Resize an image while preserving its aspect ratio by default.
11
13
  \tScales the image so that its largest dimension equals max_result_size.
12
14
 
@@ -45,7 +47,7 @@ def image_resize(image: Image.Image | NDArray[np.number], max_result_size: int,
45
47
  \t\t>>> image_resize(pil_image, 50, resampling=Image.Resampling.NEAREST).size
46
48
  \t\t(50, 25)
47
49
  \t'''
48
- def auto_crop(image: Image.Image | NDArray[np.number], mask: NDArray[np.bool_] | None = None, threshold: int | float | Callable[[NDArray[np.number]], int | float] | None = None, return_type: type[Image.Image | NDArray[np.number]] | str = 'same', contiguous: bool = True) -> Any:
50
+ def auto_crop(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:
49
51
  ''' Automatically crop an image to remove zero or uniform regions.
50
52
 
51
53
  \tThis function crops the image to keep only the region where pixels are non-zero
@@ -120,11 +122,13 @@ def auto_crop(image: Image.Image | NDArray[np.number], mask: NDArray[np.bool_] |
120
122
  \t(30, 30, 6)
121
123
  \t'''
122
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:
123
- ''' Generate a \'.gif\' file from a numpy array for 3D visualization.
125
+ ''' Generate a \'.gif\' file from a numpy array for 3D/4D visualization.
124
126
 
125
127
  \tArgs:
126
128
  \t\tpath (str): Path to the output .gif file.
127
- \t\tarray (NDArray): Numpy array to be dumped (must be 3D with depth as first axis, e.g. 64x1024x1024).
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)
128
132
  \t\tduration (int): Duration between frames in milliseconds.
129
133
  \t\tloop (int): Number of loops (0 = infinite).
130
134
  \t\tmkdir (bool): Create the directory if it does not exist.
@@ -134,9 +138,14 @@ def numpy_to_gif(path: str, array: NDArray[np.integer | np.floating | np.bool_],
134
138
 
135
139
  \t\t.. code-block:: python
136
140
 
141
+ \t\t\t> # 3D array example
137
142
  \t\t\t> array = np.random.randint(0, 256, (10, 100, 100), dtype=np.uint8)
138
143
  \t\t\t> numpy_to_gif("output_10_frames_100x100.gif", array, duration=200, loop=0)
139
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
+
140
149
  \t\t\t> total_duration = 1000 # 1 second
141
150
  \t\t\t> numpy_to_gif("output_1s.gif", array, duration=total_duration // len(array))
142
151
  \t'''
@@ -14,7 +14,7 @@ I highly encourage you to read the function docstrings to understand when to use
14
14
  # Imports
15
15
  import os
16
16
  import time
17
- from collections.abc import Callable
17
+ from collections.abc import Callable, Iterable
18
18
  from typing import Any, TypeVar, cast
19
19
 
20
20
  from .ctx import SetMPStartMethod
@@ -36,7 +36,7 @@ R = TypeVar("R")
36
36
  # Functions
37
37
  def multiprocessing(
38
38
  func: Callable[..., R] | list[Callable[..., R]],
39
- args: list[T],
39
+ args: Iterable[T],
40
40
  use_starmap: bool = False,
41
41
  chunksize: int = 1,
42
42
  desc: str = "",
@@ -54,7 +54,7 @@ def multiprocessing(
54
54
 
55
55
  Args:
56
56
  func (Callable | list[Callable]): Function to execute, or list of functions (one per argument)
57
- args (list): List of arguments to pass to the function(s)
57
+ args (Iterable): Iterable of arguments to pass to the function(s)
58
58
  use_starmap (bool): Whether to use starmap or not (Defaults to False):
59
59
  True means the function will be called like func(\*args[i]) instead of func(args[i])
60
60
  chunksize (int): Number of arguments to process at a time
@@ -109,6 +109,7 @@ def multiprocessing(
109
109
  from tqdm.contrib.concurrent import process_map # pyright: ignore[reportUnknownVariableType]
110
110
 
111
111
  # Handle parameters
112
+ args = list(args) # Ensure we have a list (not other iterable)
112
113
  if max_workers == -1:
113
114
  max_workers = CPU_COUNT
114
115
  if isinstance(max_workers, float):
@@ -154,7 +155,7 @@ def multiprocessing(
154
155
 
155
156
  def multithreading(
156
157
  func: Callable[..., R] | list[Callable[..., R]],
157
- args: list[T],
158
+ args: Iterable[T],
158
159
  use_starmap: bool = False,
159
160
  desc: str = "",
160
161
  max_workers: int | float = CPU_COUNT,
@@ -171,7 +172,7 @@ def multithreading(
171
172
 
172
173
  Args:
173
174
  func (Callable | list[Callable]): Function to execute, or list of functions (one per argument)
174
- args (list): List of arguments to pass to the function(s)
175
+ args (Iterable): Iterable of arguments to pass to the function(s)
175
176
  use_starmap (bool): Whether to use starmap or not (Defaults to False):
176
177
  True means the function will be called like func(\*args[i]) instead of func(args[i])
177
178
  desc (str): Description displayed in the progress bar
@@ -222,6 +223,7 @@ def multithreading(
222
223
  from tqdm.auto import tqdm
223
224
 
224
225
  # Handle parameters
226
+ args = list(args) # Ensure we have a list (not other iterable)
225
227
  if max_workers == -1:
226
228
  max_workers = CPU_COUNT
227
229
  if isinstance(max_workers, float):
@@ -431,7 +433,7 @@ def _handle_parameters(
431
433
  if isinstance(func, list):
432
434
  func = cast(list[Callable[[T], R]], func)
433
435
  assert len(func) == len(args), f"Length mismatch: {len(func)} functions but {len(args)} arguments"
434
- args = [(f, arg) for f, arg in zip(func, args, strict=False)] # type: ignore
436
+ args = [(f, arg if use_starmap else (arg,)) for f, arg in zip(func, args, strict=False)] # type: ignore
435
437
  func = _starmap # type: ignore
436
438
 
437
439
  # If use_starmap is True, we use the _starmap function
@@ -1,6 +1,6 @@
1
1
  from .ctx import SetMPStartMethod as SetMPStartMethod
2
2
  from .print import BAR_FORMAT as BAR_FORMAT, MAGENTA as MAGENTA
3
- from collections.abc import Callable
3
+ from collections.abc import Callable, Iterable
4
4
  from typing import Any, TypeVar
5
5
 
6
6
  def doctest_square(x: int) -> int: ...
@@ -10,7 +10,7 @@ CPU_COUNT: int
10
10
  T = TypeVar('T')
11
11
  R = TypeVar('R')
12
12
 
13
- def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
13
+ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: Iterable[T], use_starmap: bool = False, chunksize: int = 1, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
14
14
  ''' Method to execute a function in parallel using multiprocessing
15
15
 
16
16
  \t- For CPU-bound operations where the GIL (Global Interpreter Lock) is a bottleneck.
@@ -19,7 +19,7 @@ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[
19
19
 
20
20
  \tArgs:
21
21
  \t\tfunc\t\t\t\t(Callable | list[Callable]):\tFunction to execute, or list of functions (one per argument)
22
- \t\targs\t\t\t\t(list):\t\t\t\tList of arguments to pass to the function(s)
22
+ \t\targs\t\t\t\t(Iterable):\t\t\tIterable of arguments to pass to the function(s)
23
23
  \t\tuse_starmap\t\t\t(bool):\t\t\t\tWhether to use starmap or not (Defaults to False):
24
24
  \t\t\tTrue means the function will be called like func(\\*args[i]) instead of func(args[i])
25
25
  \t\tchunksize\t\t\t(int):\t\t\t\tNumber of arguments to process at a time
@@ -66,7 +66,7 @@ def multiprocessing(func: Callable[..., R] | list[Callable[..., R]], args: list[
66
66
  \t\t\t. )
67
67
  \t\t\t[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
68
68
  \t'''
69
- def multithreading(func: Callable[..., R] | list[Callable[..., R]], args: list[T], use_starmap: bool = False, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
69
+ def multithreading(func: Callable[..., R] | list[Callable[..., R]], args: Iterable[T], use_starmap: bool = False, desc: str = '', max_workers: int | float = ..., delay_first_calls: float = 0, color: str = ..., bar_format: str = ..., ascii: bool = False) -> list[R]:
70
70
  ''' Method to execute a function in parallel using multithreading, you should use it:
71
71
 
72
72
  \t- For I/O-bound operations where the GIL is not a bottleneck, such as network requests or disk operations.
@@ -75,7 +75,7 @@ def multithreading(func: Callable[..., R] | list[Callable[..., R]], args: list[T
75
75
 
76
76
  \tArgs:
77
77
  \t\tfunc\t\t\t\t(Callable | list[Callable]):\tFunction to execute, or list of functions (one per argument)
78
- \t\targs\t\t\t\t(list):\t\t\t\tList of arguments to pass to the function(s)
78
+ \t\targs\t\t\t\t(Iterable):\t\t\tIterable of arguments to pass to the function(s)
79
79
  \t\tuse_starmap\t\t\t(bool):\t\t\t\tWhether to use starmap or not (Defaults to False):
80
80
  \t\t\tTrue means the function will be called like func(\\*args[i]) instead of func(args[i])
81
81
  \t\tdesc\t\t\t\t(str):\t\t\t\tDescription displayed in the progress bar
@@ -410,7 +410,7 @@ def is_same_print(*args: Any, **kwargs: Any) -> bool:
410
410
  if previous_args_kwards == (args, kwargs):
411
411
  nb_values += 1
412
412
  return True
413
- except (TypeError, ValueError):
413
+ except Exception:
414
414
  # Comparison failed (e.g., comparing DataFrames or other complex objects)
415
415
  # Use str() for comparison instead
416
416
  current_str: str = str((args, kwargs))
@@ -470,7 +470,8 @@ def show_version(main_package: str = "stouputils", primary_color: str = CYAN, se
470
470
 
471
471
  # Get Python version
472
472
  python_version: str = f" Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} "
473
- separator_length: int = longest_name_length + longest_version_length + 4
473
+ minimum_separator_length: int = len(python_version) + 10 # Always at least 5 dashes on each side
474
+ separator_length: int = max(minimum_separator_length, longest_name_length + longest_version_length + 4)
474
475
  python_text_length: int = len(python_version)
475
476
  left_dashes: int = (separator_length - python_text_length) // 2
476
477
  right_dashes: int = separator_length - python_text_length - left_dashes
File without changes
File without changes
File without changes