autoencodix 0.2.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.

Potentially problematic release.


This version of autoencodix might be problematic. Click here for more details.

Files changed (91) hide show
  1. autoencodix/__init__.py +27 -0
  2. autoencodix/base/.ipynb_checkpoints/_base_pipeline-checkpoint.py +1274 -0
  3. autoencodix/base/.ipynb_checkpoints/_base_trainer-checkpoint.py +247 -0
  4. autoencodix/base/__init__.py +21 -0
  5. autoencodix/base/_base_autoencoder.py +138 -0
  6. autoencodix/base/_base_dataset.py +103 -0
  7. autoencodix/base/_base_evaluator.py +34 -0
  8. autoencodix/base/_base_loss.py +311 -0
  9. autoencodix/base/_base_pipeline.py +1355 -0
  10. autoencodix/base/_base_predictor.py +6 -0
  11. autoencodix/base/_base_preprocessor.py +1033 -0
  12. autoencodix/base/_base_trainer.py +259 -0
  13. autoencodix/base/_base_visualizer.py +578 -0
  14. autoencodix/base/sampleconfig.yaml +132 -0
  15. autoencodix/configs/__init__.py +21 -0
  16. autoencodix/configs/default_config.py +652 -0
  17. autoencodix/configs/disentanglix_config.py +15 -0
  18. autoencodix/configs/maskix_config.py +39 -0
  19. autoencodix/configs/ontix_config.py +35 -0
  20. autoencodix/configs/stackix_config.py +33 -0
  21. autoencodix/configs/vanillix_config.py +18 -0
  22. autoencodix/configs/varix_config.py +15 -0
  23. autoencodix/configs/xmodalix_config.py +43 -0
  24. autoencodix/disentanglix.py +72 -0
  25. autoencodix/evaluate/.ipynb_checkpoints/evaluate-checkpoint.py +532 -0
  26. autoencodix/evaluate/__init__.py +4 -0
  27. autoencodix/evaluate/_general_evaluator.py +730 -0
  28. autoencodix/evaluate/_xmodalix_evaluator.py +376 -0
  29. autoencodix/imagix.py +76 -0
  30. autoencodix/losses/disentanglix_loss.py +215 -0
  31. autoencodix/losses/maskix_loss.py +94 -0
  32. autoencodix/losses/vanillix_loss.py +28 -0
  33. autoencodix/losses/varix_loss.py +102 -0
  34. autoencodix/losses/xmodal_loss.py +304 -0
  35. autoencodix/maskix.py +108 -0
  36. autoencodix/modeling/.ipynb_checkpoints/__init__-checkpoint.py +16 -0
  37. autoencodix/modeling/.ipynb_checkpoints/_captum_forward-checkpoint.py +16 -0
  38. autoencodix/modeling/__init__.py +20 -0
  39. autoencodix/modeling/_captum_forward.py +20 -0
  40. autoencodix/modeling/_classifier.py +45 -0
  41. autoencodix/modeling/_imagevae_architecture.py +296 -0
  42. autoencodix/modeling/_imgfast_architecture.py +288 -0
  43. autoencodix/modeling/_layer_factory.py +71 -0
  44. autoencodix/modeling/_maskix_architecture.py +190 -0
  45. autoencodix/modeling/_ontix_architecture.py +305 -0
  46. autoencodix/modeling/_vanillix_architecture.py +136 -0
  47. autoencodix/modeling/_varix_architecture.py +192 -0
  48. autoencodix/notebooks/__init__.py +0 -0
  49. autoencodix/ontix.py +136 -0
  50. autoencodix/py.typed +0 -0
  51. autoencodix/stackix.py +122 -0
  52. autoencodix/trainers/__init__.py +6 -0
  53. autoencodix/trainers/_general_trainer.py +525 -0
  54. autoencodix/trainers/_maskix_trainer.py +116 -0
  55. autoencodix/trainers/_ontix_trainer.py +77 -0
  56. autoencodix/trainers/_stackix_orchestrator.py +522 -0
  57. autoencodix/trainers/_stackix_trainer.py +209 -0
  58. autoencodix/trainers/_xmodal_trainer.py +1006 -0
  59. autoencodix/utils/.ipynb_checkpoints/_explainer-checkpoint.py +118 -0
  60. autoencodix/utils/.ipynb_checkpoints/_model_output-checkpoint.py +30 -0
  61. autoencodix/utils/.ipynb_checkpoints/_utils-checkpoint.py +626 -0
  62. autoencodix/utils/.ipynb_checkpoints/adata_converter-checkpoint.py +99 -0
  63. autoencodix/utils/.ipynb_checkpoints/feature_importance-checkpoint.py +823 -0
  64. autoencodix/utils/__init__.py +22 -0
  65. autoencodix/utils/_annealer.py +193 -0
  66. autoencodix/utils/_bulkreader.py +232 -0
  67. autoencodix/utils/_explainer.py +246 -0
  68. autoencodix/utils/_imgreader.py +404 -0
  69. autoencodix/utils/_internals.py +23 -0
  70. autoencodix/utils/_llm_explainer.py +318 -0
  71. autoencodix/utils/_losses.py +20 -0
  72. autoencodix/utils/_model_output.py +30 -0
  73. autoencodix/utils/_result.py +393 -0
  74. autoencodix/utils/_screader.py +76 -0
  75. autoencodix/utils/_traindynamics.py +187 -0
  76. autoencodix/utils/_utils.py +690 -0
  77. autoencodix/utils/adata_converter.py +111 -0
  78. autoencodix/utils/example_data.py +398 -0
  79. autoencodix/utils/feature_importance.py +829 -0
  80. autoencodix/utils/prompts.py +21 -0
  81. autoencodix/vanillix.py +141 -0
  82. autoencodix/varix.py +80 -0
  83. autoencodix/visualize/__init__.py +5 -0
  84. autoencodix/visualize/_general_visualizer.py +913 -0
  85. autoencodix/visualize/_imagix_visualizer.py +111 -0
  86. autoencodix/visualize/_xmodal_visualizer.py +841 -0
  87. autoencodix/visualize/visualize.py +1214 -0
  88. autoencodix/xmodalix.py +318 -0
  89. autoencodix-0.2.0.dist-info/METADATA +143 -0
  90. autoencodix-0.2.0.dist-info/RECORD +91 -0
  91. autoencodix-0.2.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,404 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import List, Literal, Optional, Union, Dict, Tuple
4
+
5
+ import cv2
6
+ import numpy as np
7
+ import pandas as pd
8
+ from autoencodix.configs.default_config import DefaultConfig, DataInfo
9
+ from autoencodix.data._imgdataclass import ImgData
10
+
11
+
12
+ class ImageProcessingError(Exception):
13
+ pass
14
+
15
+
16
+ class ImageSizeFinder:
17
+ """Finds nearest quadratic image size that is dividable by 2^number_of_layers.ArithmeticError
18
+
19
+ Nearest quadratic image size is based on the given image size in the config file.
20
+
21
+ Attributes:
22
+ config: Configuration object
23
+ width: request image width by user.
24
+ height: requested image height by user.
25
+
26
+ """
27
+
28
+ def __init__(self, config: DefaultConfig):
29
+ """Inits the ImageSizeFinder
30
+
31
+ Args:
32
+ config: Configuration object.
33
+ """
34
+ self.config = config
35
+ found_image_type = False
36
+ for data_type in config.data_config.data_info.keys():
37
+ if config.data_config.data_info[data_type].data_type == "IMG":
38
+ self.width = config.data_config.data_info[data_type].img_width_resize
39
+ self.height = config.data_config.data_info[data_type].img_height_resize
40
+ found_image_type = True
41
+ if not found_image_type:
42
+ raise ValueError("You need to provide a DATA_TYPE of with the TYPE key IMG")
43
+
44
+ self.n_conv_layers = 5
45
+
46
+ def _image_size_is_legal(self) -> bool:
47
+ """Checks if image size is dividable by 2^number_of_layers to the givensize in the config.
48
+
49
+ Returns:
50
+ bool if image size is allowed.
51
+ """
52
+ return self.width % 2**self.n_conv_layers == 0
53
+
54
+ def get_nearest_quadratic_image_size(self):
55
+ """Finds nearest quadratic image size that is dividable by 2^number_of_layers
56
+
57
+ Nearest quadratic image size is based on the given image size in the config file.
58
+
59
+ Returns:
60
+ Tuple of ints width and height with widht=height.
61
+ Raies:
62
+ ValueError: if not allowed image size can be found.
63
+ """
64
+
65
+ if self._image_size_is_legal():
66
+ print(
67
+ f"Given image size is possible, rescaling images to: {self.width}x{self.height}"
68
+ )
69
+ return self.width, self.height
70
+ running_image_size = self.width
71
+ while_loop_counter = 0
72
+ while not running_image_size % 2**self.n_conv_layers == 0:
73
+ running_image_size += 1
74
+ while_loop_counter += 1
75
+ if while_loop_counter > 10000:
76
+ raise ValueError(
77
+ f"Could not find a quadratic image size that is dividable by 2^{self.n_conv_layers}"
78
+ )
79
+ print(
80
+ f"Given image size{self.width}x{self.height} is not possible, rescaling to: {running_image_size}x{running_image_size}"
81
+ )
82
+ if running_image_size is None:
83
+ raise ValueError(
84
+ f"Could not find a quadratic image size that is dividable by 2^{self.n_conv_layers}"
85
+ )
86
+
87
+ return running_image_size, running_image_size
88
+
89
+
90
+ class ImageDataReader:
91
+ """Reads and processes image data.
92
+
93
+ Reads all images from the specified directory, processes them,
94
+ and returns a list of ImgData objects.
95
+ """
96
+
97
+ def __init__(self, config: DefaultConfig):
98
+ self.config = config
99
+
100
+ def validate_image_path(self, image_path: Union[str, Path]) -> bool:
101
+ """Checks if file extension is allowed:
102
+
103
+ Allowed are (independent of capitalization):
104
+ - jpg
105
+ - jpeg
106
+ - png
107
+ - tif
108
+ - tiff
109
+
110
+ Args:
111
+ image_path: path or str of image to read
112
+ """
113
+ path = Path(image_path) if isinstance(image_path, str) else image_path
114
+ return (
115
+ path.exists()
116
+ and path.is_file()
117
+ and path.suffix.lower() in {".jpg", ".jpeg", ".png", ".tif", ".tiff"}
118
+ )
119
+
120
+ def parse_image_to_tensor(
121
+ self,
122
+ image_path: Union[str, Path],
123
+ to_h: Optional[int] = None,
124
+ to_w: Optional[int] = None,
125
+ ) -> np.ndarray:
126
+ """Reads an image from the given path, optionally resizes it, and converts it to a tensor.
127
+
128
+ Args:
129
+ image_path: The path to the image file.
130
+ to_h: The desired height of the output tensor, by default None.
131
+ to_w: The desired width of the output tensor, by default None.
132
+
133
+ Returns:
134
+ The processed image as a tensor.
135
+
136
+ Raises:
137
+ FileNotFoundError: If the image path is invalid or the image cannot be read.
138
+ ImageProcessingError: If the image format is unsupported or an unexpected error occurs during processing.
139
+ """
140
+
141
+ if not self.validate_image_path(image_path):
142
+ raise FileNotFoundError(f"Invalid image path: {image_path}")
143
+ image_path = Path(image_path)
144
+ SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".tif", ".tiff"}
145
+ if image_path.suffix.lower() not in SUPPORTED_EXTENSIONS:
146
+ raise ImageProcessingError(
147
+ f"Unsupported image format: {image_path.suffix}. "
148
+ f"Supported formats are: {', '.join(SUPPORTED_EXTENSIONS)}"
149
+ )
150
+ try:
151
+ if image_path.suffix.lower() in {".tif", ".tiff"}:
152
+ image = cv2.imread(str(image_path), cv2.IMREAD_UNCHANGED)
153
+ else:
154
+ image = cv2.imread(str(image_path))
155
+
156
+ if image is None:
157
+ raise FileNotFoundError(f"Failed to read image: {image_path}")
158
+
159
+ h, w, _ = image.shape[:3]
160
+ if to_h is None:
161
+ to_h = h
162
+ if to_w is None:
163
+ to_w = w
164
+
165
+ if not (2 <= len(image.shape) <= 3):
166
+ raise ImageProcessingError(
167
+ f"Image has unsupported shape: {image.shape}. "
168
+ "Supported shapes are 2D and 3D."
169
+ )
170
+
171
+ image = cv2.resize(image, (to_w, to_h), interpolation=cv2.INTER_AREA)
172
+
173
+ if len(image.shape) == 3:
174
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
175
+ if len(image.shape) == 2:
176
+ image = np.expand_dims(image, axis=2)
177
+
178
+ image = image.transpose(2, 0, 1)
179
+ return image
180
+
181
+ except Exception as e:
182
+ raise e
183
+
184
+ def read_all_images_from_dir(
185
+ self,
186
+ img_dir: str,
187
+ to_h: Optional[int],
188
+ to_w: Optional[int],
189
+ annotation_df: pd.DataFrame,
190
+ is_paired: Union[bool, None] = None,
191
+ ) -> List[ImgData]:
192
+ """Reads all images from a specified directory, processes them, returns list of ImgData objects.
193
+
194
+ Args:
195
+ img_dir: The directory containing the images.
196
+ to_h: The desired height of the output tensors.
197
+ to_w: The desired width of the output tensors.
198
+ annotation_df: DataFrame containing image annotations.
199
+ is_paired: Whether the images are paired with annotations.
200
+
201
+ Returns:
202
+ List of processed image data objects.
203
+
204
+ Raises:
205
+ ValueError: If the annotation DataFrame is missing required columns.
206
+ """
207
+ if self.config.img_path_col not in annotation_df.columns:
208
+ raise ValueError(
209
+ f" The defined column for image paths: {self.config.img_path_col} column is missing in the annotation_df\
210
+ you can define this in the config via the param `img_path_col`"
211
+ )
212
+
213
+ SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".tif", ".tiff"}
214
+ paths = [
215
+ os.path.join(img_dir, f)
216
+ for f in os.listdir(img_dir)
217
+ if Path(f).suffix.lower() in SUPPORTED_EXTENSIONS
218
+ ]
219
+ if is_paired or is_paired is None:
220
+ paths = [
221
+ p
222
+ for p in paths
223
+ if os.path.basename(p)
224
+ in annotation_df[self.config.img_path_col].tolist()
225
+ ]
226
+ imgs = []
227
+ for p in paths:
228
+ img = self.parse_image_to_tensor(image_path=p, to_h=to_h, to_w=to_w)
229
+ img_path = os.path.basename(p)
230
+ subset: Union[pd.Series, pd.DataFrame] = annotation_df[
231
+ annotation_df[self.config.img_path_col] == img_path
232
+ ]
233
+ if not subset.empty:
234
+ imgs.append(
235
+ ImgData(
236
+ img=img,
237
+ sample_id=str(subset.index[0]),
238
+ annotation=subset,
239
+ )
240
+ )
241
+ return imgs
242
+
243
+ def read_annotation_file(self, data_info: DataInfo) -> pd.DataFrame:
244
+ """Reads annotation file and returns DataFrame with file contents
245
+ Args:
246
+ data_info: specific part of the Configuration object for input data
247
+ Returns:
248
+ DataFrame with annotation data.
249
+
250
+ """
251
+ anno_file = (
252
+ os.path.join(data_info.file_path)
253
+ if data_info.extra_anno_file is None
254
+ else os.path.join(data_info.extra_anno_file)
255
+ )
256
+ sep = data_info.sep
257
+ if anno_file.endswith(".parquet"):
258
+ annotation = pd.read_parquet(anno_file)
259
+ elif anno_file.endswith((".csv", ".txt", ".tsv")):
260
+ annotation = pd.read_csv(anno_file, sep=sep, index_col=0, engine="python")
261
+ else:
262
+ raise ValueError(f"Unsupported file type for: {anno_file}")
263
+ return annotation
264
+
265
+ def read_data(
266
+ self, config: DefaultConfig
267
+ ) -> Tuple[Dict[str, List[ImgData]], Dict[str, pd.DataFrame]]:
268
+ """Read image data from the specified directory based on configuration.
269
+
270
+ Args:
271
+ config: The configuration object containing the data configuration.
272
+
273
+ Returns:
274
+ A Tuple of Dicts:
275
+ 1. Dict with type of image data as key and actual List of ImgData as value.
276
+ 2. Dict with type of image data as key and DataFrame of annotation data as value.
277
+
278
+ Raises:
279
+ Exception: If no image data is found in the configuration or other validation errors occur.
280
+ """
281
+ # Find all image data sources in config
282
+ image_sources = {
283
+ k: v
284
+ for k, v in config.data_config.data_info.items()
285
+ if v.data_type == "IMG"
286
+ }
287
+
288
+ if not image_sources:
289
+ raise ValueError("No image data found in the configuration.")
290
+
291
+ result = {}
292
+ annotation = {}
293
+ for key, img_info in image_sources.items():
294
+ try:
295
+ result[key], annotation[key] = self._read_data(config, img_info)
296
+ print(f"Successfully loaded {len(result[key])} images for {key}")
297
+ except Exception as e:
298
+ print(f"Error loading images for {key}: {str(e)}")
299
+ # Decide whether to raise or continue based on your requirements
300
+
301
+ return result, annotation
302
+
303
+ def _read_data(
304
+ self, config: DefaultConfig, img_info: DataInfo
305
+ ) -> Tuple[List[ImgData], pd.DataFrame]:
306
+ """Read data for a specific image source.
307
+
308
+ Args:
309
+ config: The configuration object containing the data configuration.
310
+ img_info: The specific image info configuration.
311
+
312
+ Returns:
313
+ A Tuple of Dicts:
314
+ 1. Dict with type of image data as key and actual List of ImgData as value.
315
+ 2. Dict with type of image data as key and DataFrame of annotation data as value.
316
+
317
+ """
318
+ img_dir = img_info.file_path
319
+ img_size_finder: ImageSizeFinder = ImageSizeFinder(config)
320
+ to_h, to_w = img_size_finder.get_nearest_quadratic_image_size()
321
+
322
+ if img_info.extra_anno_file is not None:
323
+ # Use image-specific annotation file if provided
324
+ annotation = self.read_annotation_file(img_info)
325
+ else:
326
+ # Otherwise use the global annotation file
327
+ try:
328
+ anno_info = next(
329
+ f
330
+ for f in config.data_config.data_info.values()
331
+ if f.data_type == "ANNOTATION"
332
+ )
333
+ annotation = self.read_annotation_file(anno_info)
334
+ except StopIteration:
335
+ raise ValueError("No annotation data found in the configuration.")
336
+
337
+ images = self.read_all_images_from_dir(
338
+ img_dir=img_dir,
339
+ to_h=to_h,
340
+ to_w=to_w,
341
+ annotation_df=annotation,
342
+ is_paired=config.requires_paired,
343
+ )
344
+ annotations: pd.DataFrame = pd.concat([img.annotation for img in images])
345
+
346
+ return images, annotations
347
+
348
+
349
+ class ImageNormalizer:
350
+ """Implements Normalization analog to other data modalites for ImageData"""
351
+
352
+ @staticmethod
353
+ def normalize_image(
354
+ image: np.ndarray, method: Literal["STANDARD", "MINMAX", "ROBUST", "NONE"]
355
+ ) -> np.ndarray:
356
+ """Performs Image Normalization.
357
+
358
+ Supported methods are:
359
+ - STANDARD: (analog to StandardScaler of sklearn).
360
+ - MINMAX: (analog to MinMaxSclaer of sklearn).
361
+ - ROBUST: (analog to RobustScaler of sklearn).
362
+ - NONE: no normalization.
363
+
364
+ Args:
365
+ image: input image as array.
366
+ method: indicator string of which method to use
367
+ Returns:
368
+ The normalized images as np.ndarray
369
+ Raises:
370
+ ValueError: if unsupported normalization method is provided or Normalization fails for any other reason.
371
+ """
372
+ try:
373
+ if method == "NONE":
374
+ return image
375
+
376
+ if method == "MINMAX":
377
+ # Create a copy of the image for normalization
378
+ normalized = image.astype(np.float32)
379
+ cv2.normalize(
380
+ normalized,
381
+ normalized,
382
+ alpha=0,
383
+ beta=1,
384
+ norm_type=cv2.NORM_MINMAX,
385
+ dtype=cv2.CV_32F,
386
+ )
387
+ return normalized
388
+
389
+ elif method == "STANDARD":
390
+ mean = np.mean(image, axis=(1, 2), keepdims=True)
391
+ std = np.std(image, axis=(1, 2), keepdims=True)
392
+ return (image - mean) / (std + 1e-8)
393
+
394
+ elif method == "ROBUST":
395
+ median = np.median(image, axis=(1, 2), keepdims=True)
396
+ q75, q25 = np.percentile(image, [75, 25], axis=(1, 2), keepdims=True)
397
+ iqr = q75 - q25
398
+ return (image - median) / (iqr + 1e-8)
399
+
400
+ else:
401
+ raise ValueError(f"Unsupported normalization method: {method}")
402
+
403
+ except Exception as e:
404
+ raise ValueError(f"Failed to normalize image: {str(e)}")
@@ -0,0 +1,23 @@
1
+ """
2
+ Utility to Map Architectures to Trainers.
3
+ Only used for training sub datasets in XModalix as of now.
4
+ """
5
+
6
+ from typing import Dict, Type
7
+
8
+ from autoencodix.modeling._imagevae_architecture import ImageVAEArchitecture
9
+ from autoencodix.modeling._ontix_architecture import OntixArchitecture
10
+ from autoencodix.modeling._vanillix_architecture import VanillixArchitecture
11
+ from autoencodix.modeling._varix_architecture import VarixArchitecture
12
+ from autoencodix.modeling._maskix_architecture import MaskixArchitectureVanilla
13
+ from autoencodix.trainers._general_trainer import GeneralTrainer
14
+ from autoencodix.trainers._ontix_trainer import OntixTrainer
15
+ from autoencodix.trainers._maskix_trainer import MaskixTrainer
16
+
17
+ model_trainer_map: Dict[Type, Type] = {
18
+ VarixArchitecture: GeneralTrainer,
19
+ ImageVAEArchitecture: GeneralTrainer,
20
+ VanillixArchitecture: GeneralTrainer,
21
+ OntixArchitecture: OntixTrainer,
22
+ MaskixArchitectureVanilla: MaskixTrainer,
23
+ }