quadra 2.5.0__py3-none-any.whl → 2.6.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.
quadra/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "2.5.0"
1
+ __version__ = "2.6.0"
2
2
 
3
3
 
4
4
  def get_version():
@@ -57,7 +57,6 @@ trainer:
57
57
  scheduler:
58
58
  patience: 20
59
59
  factor: 0.9
60
- verbose: False
61
60
  threshold: 0.01
62
61
 
63
62
  callbacks:
@@ -1,5 +1,4 @@
1
1
  _target_: torch.optim.lr_scheduler.ReduceLROnPlateau
2
2
  patience: 20
3
3
  factor: 0.5
4
- verbose: False
5
4
  threshold: 0.01
@@ -1,5 +1,4 @@
1
1
  _target_: torch.optim.lr_scheduler.ReduceLROnPlateau
2
2
  patience: 20
3
3
  factor: 0.995
4
- verbose: False
5
4
  threshold: 0.01
quadra/tasks/anomaly.py CHANGED
@@ -244,7 +244,7 @@ class AnomalibDetection(Generic[AnomalyDataModuleT], LightningTask[AnomalyDataMo
244
244
  ):
245
245
  threshold = torch.tensor(100.0)
246
246
  else:
247
- threshold = self.module.image_metrics.F1Score.threshold
247
+ threshold = self.module.image_metrics.F1Score.threshold # type: ignore[union-attr,assignment]
248
248
 
249
249
  # The output of the prediction is a normalized score so the cumulative histogram is displayed with the
250
250
  # normalized scores
@@ -328,6 +328,22 @@ class AnomalibDetection(Generic[AnomalyDataModuleT], LightningTask[AnomalyDataMo
328
328
  else:
329
329
  utils.upload_file_tensorboard(a, tensorboard_logger)
330
330
 
331
+ def execute(self):
332
+ """Execute the experiment and all the steps."""
333
+ self.prepare()
334
+ self.train()
335
+ # When training in fp16 mixed precision, export function casts model weights from fp32 to fp16,
336
+ # for this reason, predictions logits could slightly change and predictions could be inconsistent between
337
+ # test and generated report.
338
+ # Performing export before test allows to have consistent results in test metrics and generated report.
339
+ if self.config.export is not None and len(self.config.export.types) > 0:
340
+ self.export()
341
+ if self.run_test:
342
+ self.test()
343
+ if self.report:
344
+ self.generate_report()
345
+ self.finalize()
346
+
331
347
 
332
348
  class AnomalibEvaluation(Evaluation[AnomalyDataModule]):
333
349
  """Evaluation task for Anomalib.
@@ -445,12 +461,22 @@ class AnomalibEvaluation(Evaluation[AnomalyDataModule]):
445
461
  training_threshold = self.model_data[f"{self.training_threshold_type}_threshold"]
446
462
  optimal_threshold = self.metadata["threshold"]
447
463
 
448
- normalized_optimal_threshold = cast(float, normalize_anomaly_score(optimal_threshold, training_threshold))
449
-
450
464
  os.makedirs(os.path.join(self.report_path, "predictions"), exist_ok=True)
451
465
  os.makedirs(os.path.join(self.report_path, "heatmaps"), exist_ok=True)
452
466
 
453
467
  anomaly_scores = self.metadata["anomaly_scores"].cpu().numpy()
468
+
469
+ # The reason I have to expand dims and cast the optimal threshold to anomaly_scores dtype is because
470
+ # of internal roundings performed differently by numpy and python
471
+ # Particularly the normalized_optimal_threshold computed directly using float values might be higher than the
472
+ # actual value obtained by the anomaly_scores
473
+ normalized_optimal_threshold = cast(
474
+ np.ndarray,
475
+ normalize_anomaly_score(
476
+ np.expand_dims(np.array(optimal_threshold, dtype=anomaly_scores.dtype), -1), training_threshold
477
+ ),
478
+ ).item()
479
+
454
480
  anomaly_scores = normalize_anomaly_score(anomaly_scores, training_threshold)
455
481
 
456
482
  if not isinstance(anomaly_scores, np.ndarray):
quadra/utils/anomaly.py CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ from typing import cast
9
+
8
10
  try:
9
11
  from typing import Any, TypeAlias
10
12
  except ImportError:
@@ -43,6 +45,39 @@ def normalize_anomaly_score(raw_score: MapOrValue, threshold: float) -> MapOrVal
43
45
  else:
44
46
  normalized_score = 200.0 - ((raw_score / threshold) * 100.0)
45
47
 
48
+ # Ensures that the normalized scores are consistent with the raw scores
49
+ # For all the items whose prediction changes after normalization, force the normalized score to be
50
+ # consistent with the prediction made on the raw score by clipping the score:
51
+ # - to 100.0 if the prediction was "anomaly" on the raw score and "good" on the normalized score
52
+ # - to 99.99 if the prediction was "good" on the raw score and "anomaly" on the normalized score
53
+ score = raw_score
54
+ if isinstance(score, torch.Tensor):
55
+ score = score.cpu().numpy()
56
+ # Anomalib classify as anomaly if anomaly_score gte threshold
57
+ is_anomaly_mask = score >= threshold
58
+ is_not_anomaly_mask = np.bitwise_not(is_anomaly_mask)
59
+ if isinstance(normalized_score, torch.Tensor):
60
+ if normalized_score.dim() == 0:
61
+ normalized_score = (
62
+ normalized_score.clamp(min=100.0) if is_anomaly_mask else normalized_score.clamp(max=99.99)
63
+ )
64
+ else:
65
+ normalized_score[is_anomaly_mask] = normalized_score[is_anomaly_mask].clamp(min=100.0)
66
+ normalized_score[is_not_anomaly_mask] = normalized_score[is_not_anomaly_mask].clamp(max=99.99)
67
+ elif isinstance(normalized_score, np.ndarray) or np.isscalar(normalized_score):
68
+ if np.isscalar(normalized_score) or normalized_score.ndim == 0: # type: ignore[union-attr]
69
+ normalized_score = (
70
+ np.clip(normalized_score, a_min=100.0, a_max=None)
71
+ if is_anomaly_mask
72
+ else np.clip(normalized_score, a_min=None, a_max=99.99)
73
+ )
74
+ else:
75
+ normalized_score = cast(np.ndarray, normalized_score)
76
+ normalized_score[is_anomaly_mask] = np.clip(normalized_score[is_anomaly_mask], a_min=100.0, a_max=None)
77
+ normalized_score[is_not_anomaly_mask] = np.clip(
78
+ normalized_score[is_not_anomaly_mask], a_min=None, a_max=99.99
79
+ )
80
+
46
81
  if isinstance(normalized_score, torch.Tensor):
47
82
  return torch.clamp(normalized_score, 0.0, 1000.0)
48
83
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quadra
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: Deep Learning experiment orchestration library
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -22,7 +22,7 @@ Provides-Extra: onnx
22
22
  Requires-Dist: albumentations (>=1.3,<1.4)
23
23
  Requires-Dist: anomalib-orobix (==0.7.0.dev150)
24
24
  Requires-Dist: boto3 (>=1.26,<1.27)
25
- Requires-Dist: grad-cam-orobix (==1.5.3.dev001)
25
+ Requires-Dist: grad-cam-orobix (==1.5.3.dev1)
26
26
  Requires-Dist: h5py (>=3.8,<3.9)
27
27
  Requires-Dist: hydra_colorlog (>=1.2,<1.3)
28
28
  Requires-Dist: hydra_core (>=1.3,<1.4)
@@ -35,7 +35,7 @@ Requires-Dist: numpy (<2)
35
35
  Requires-Dist: nvitop (>=0.11,<0.12)
36
36
  Requires-Dist: onnx (==1.15.0) ; extra == "onnx"
37
37
  Requires-Dist: onnxconverter-common (>=1.14.0,<2.0.0) ; extra == "onnx"
38
- Requires-Dist: onnxruntime_gpu (==1.20.0) ; extra == "onnx"
38
+ Requires-Dist: onnxruntime_gpu (==1.23.2) ; extra == "onnx"
39
39
  Requires-Dist: onnxsim (==0.4.28) ; extra == "onnx"
40
40
  Requires-Dist: opencv_python_headless (>=4.7.0,<4.8.0)
41
41
  Requires-Dist: pandas (<2.0)
@@ -44,17 +44,17 @@ Requires-Dist: pydantic (>=1.10.10)
44
44
  Requires-Dist: python_dotenv (>=0.21,<0.22)
45
45
  Requires-Dist: pytorch_lightning (>=2.4,<2.5)
46
46
  Requires-Dist: rich (>=13.2,<13.3)
47
- Requires-Dist: scikit_learn (>=1.2,<1.3)
47
+ Requires-Dist: scikit_learn (>=1.6,<1.7)
48
48
  Requires-Dist: scikit_multilearn (>=0.2,<0.3)
49
49
  Requires-Dist: seaborn (>=0.12,<0.13)
50
50
  Requires-Dist: segmentation_models_pytorch-orobix (==0.3.3.dev1)
51
51
  Requires-Dist: tensorboard (>=2.11,<2.12)
52
52
  Requires-Dist: timm (==0.9.12)
53
- Requires-Dist: torch (==2.6.0)
53
+ Requires-Dist: torch (==2.8.0)
54
54
  Requires-Dist: torchinfo (>=1.8,<1.9)
55
55
  Requires-Dist: torchmetrics (>=0.10,<0.11)
56
56
  Requires-Dist: torchsummary (>=1.5,<1.6)
57
- Requires-Dist: torchvision (>=0.21,<0.22)
57
+ Requires-Dist: torchvision (==0.23)
58
58
  Requires-Dist: tripy (>=1.0,<1.1)
59
59
  Requires-Dist: typing_extensions (==4.11.0) ; python_version < "3.10"
60
60
  Requires-Dist: xxhash (>=3.2,<3.3)
@@ -1,4 +1,4 @@
1
- quadra/__init__.py,sha256=my_GwHSTlCXOqpoWuPRTAF1_rKRUMylmeA4eWoMa96g,112
1
+ quadra/__init__.py,sha256=ylxTD-7xItBBl8y254g9lUc-ehYh4LCjfEoDhXhBfUw,112
2
2
  quadra/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  quadra/callbacks/anomalib.py,sha256=WLBEGhZA9HoP4Yh9UbbC2GzDOKYTkvU9EY1lkZcV7Fs,11971
4
4
  quadra/callbacks/lightning.py,sha256=qvtzDiv8ZUV7K11gKHKWCyo-a9XR_Jm_M-IEicTM1Yo,20242
@@ -59,7 +59,7 @@ quadra/configs/experiment/base/anomaly/fastflow.yaml,sha256=jN3TeJXPGT7_GOhu2Eac
59
59
  quadra/configs/experiment/base/anomaly/inference.yaml,sha256=aLS3U0yC0Yb17BD1NSl-nhjJ9fcV6jDmxVQrrwZoMYI,446
60
60
  quadra/configs/experiment/base/anomaly/padim.yaml,sha256=5trGY5kL7gKzRTQuWT-LHYVPf4-q9J2I_-196noIZd4,829
61
61
  quadra/configs/experiment/base/anomaly/patchcore.yaml,sha256=a795iOcdH6kSoeA7ufChjBGWYdBn0ztRVtLBk1vpI3Q,841
62
- quadra/configs/experiment/base/classification/classification.yaml,sha256=EYr_nGKvRe5XkBkX6T83g9Qg9dlwXnaTwEMvthBK7ZU,1307
62
+ quadra/configs/experiment/base/classification/classification.yaml,sha256=MNWrzDPpTIBc5gfIyeZmEiLqeI-FPjXJt2APD4gY0gU,1290
63
63
  quadra/configs/experiment/base/classification/classification_evaluation.yaml,sha256=r_pWUr9MbZYXlJlKIZIRb3GQfuGkuFOMjSOiZrt5fdU,463
64
64
  quadra/configs/experiment/base/classification/multilabel_classification.yaml,sha256=I8g8TUMW1q6uoa_pT_V9swwsVzS4ouZxUN3Dz424bJQ,774
65
65
  quadra/configs/experiment/base/classification/sklearn_classification.yaml,sha256=Mun6mabSBtFoOo1zE96syfoVNBmonmSmw7HAfEx3D5s,519
@@ -148,8 +148,8 @@ quadra/configs/optimizer/adamw.yaml,sha256=bofKJKqkjhtCf07KrARdUxMpQO6ux8UKGG2c0
148
148
  quadra/configs/optimizer/default.yaml,sha256=_JNfHj0JnkggcHKorPNJ_dSAJdkifNapve8VlHRcIVg,69
149
149
  quadra/configs/optimizer/lars.yaml,sha256=NwBKZNLQ5lbPAiWNPgmX2W4ap0PW3h-LpWKF6Qz35Vs,138
150
150
  quadra/configs/optimizer/sgd.yaml,sha256=_JNfHj0JnkggcHKorPNJ_dSAJdkifNapve8VlHRcIVg,69
151
- quadra/configs/scheduler/default.yaml,sha256=RamJr7-wjTHw6xCV9pseRfh72yScMV7B0gGvwsZv9ko,109
152
- quadra/configs/scheduler/rop.yaml,sha256=8t1vR5IX8d21ZC9S8vXAeAs0OtzvOcemV2gWqbbfoAM,111
151
+ quadra/configs/scheduler/default.yaml,sha256=vmLBhrgBLs9DDXC69CRIbTkcKiGT11vfrq-hJNrYWy4,94
152
+ quadra/configs/scheduler/rop.yaml,sha256=Z--NTA5X60asZ3nm85dNucJL7h1J4cICtryA9Bc-KbI,96
153
153
  quadra/configs/scheduler/step.yaml,sha256=0pj1rENjeeke8XQUhdrWrKD7eaoQ2a9-H25Deww9w3o,67
154
154
  quadra/configs/scheduler/warmrestart.yaml,sha256=cD7pPYFwBgCqLTCnYkrOW24Fd-8CiLgs88rsF0NHoyw,90
155
155
  quadra/configs/scheduler/warmup.yaml,sha256=2vv928tgNOE7yelnBSL2DVslqS3vqGz39LVXnZCJkPU,198
@@ -248,7 +248,7 @@ quadra/schedulers/__init__.py,sha256=mQivr18c0j36hpV3Lm8nlyBVKFevWp8TtLuTfvI9kQc
248
248
  quadra/schedulers/base.py,sha256=T1EdrLOJ0i9MzWoLCkrNA0uypm7hJ-L6NFhjIXFB6NE,1462
249
249
  quadra/schedulers/warmup.py,sha256=chzzrK7OqqlicBCxiF4CqMYNrWu6nflIbRE-C86Jrw0,4962
250
250
  quadra/tasks/__init__.py,sha256=tmAfMoH0k3UC7r2pNrgbBa1Pfc3tpLl3IObFF6Z0eRE,820
251
- quadra/tasks/anomaly.py,sha256=RHeiM1vZF1zsva37iYdiGx_HLgdAp8lXnmUzXja69YU,24638
251
+ quadra/tasks/anomaly.py,sha256=40ciWnkle45Hl6SjY_4OHWdT2Mb1Qfoog60alZG-uQ0,25899
252
252
  quadra/tasks/base.py,sha256=piYlTFtvqH-4s4oEq4GczdAs_gL29UHAJGsOC5Sd3Bc,14187
253
253
  quadra/tasks/classification.py,sha256=_GQOPMGuOZ_uLA9jFhLEaJkW_Sid_WKHNn9ALXnGNmo,53407
254
254
  quadra/tasks/patch.py,sha256=nzo8o-ei7iF1Iarvd8-c08s0Rs_lPvVPDLAbkFMx-Qw,20251
@@ -258,7 +258,7 @@ quadra/trainers/README.md,sha256=XtpbUOxwvPpOUL7E5s2JHjRgwT-CRKTxsBeUSXrg9BU,248
258
258
  quadra/trainers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
259
259
  quadra/trainers/classification.py,sha256=YeJ0z7Vk0-dsMTcoKBxSdSA0rxtilEcQTp-Zq9Xi1hw,7042
260
260
  quadra/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
261
- quadra/utils/anomaly.py,sha256=49vFvT5-4SxczsEM2Akcut_M1DDwKlOVdGv36oLTgR0,4067
261
+ quadra/utils/anomaly.py,sha256=uyzrFTz5QFTyIEbOT81A6OxXbHceGeiGP-JP3qr65D8,6044
262
262
  quadra/utils/classification.py,sha256=dKFuv4RywWhvhstOnEOnaf-6qcViUK0dTgah9m9mw2Q,24917
263
263
  quadra/utils/deprecation.py,sha256=zF_S-yqenaZxRBOudhXts0mX763WjEUWCnHd09TZnwY,852
264
264
  quadra/utils/evaluation.py,sha256=oooRJPu1AaHhOwvB1Y6SFjQ645OkgrDzKtUvwWq8oq4,19005
@@ -293,8 +293,8 @@ quadra/utils/validator.py,sha256=wmVXycB90VNyAbKBUVncFCxK4nsYiOWJIY3ISXwxYCY,463
293
293
  quadra/utils/visualization.py,sha256=yYm7lPziUOlybxigZ2qTycNewb67Q80H4hjQGWUh788,16094
294
294
  quadra/utils/vit_explainability.py,sha256=Gh6BHaDEzWxOjJp1aqvCxLt9Rb8TXd5uKXOAx7-acUk,13351
295
295
  hydra_plugins/quadra_searchpath_plugin.py,sha256=AAn4TzR87zUK7nwSsK-KoqALiPtfQ8FvX3fgZPTGIJ0,1189
296
- quadra-2.5.0.dist-info/METADATA,sha256=s-yGU-tHTS03MQtY5lXMvPv739qgVmz4ZvdDJs_J4FE,17632
297
- quadra-2.5.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
298
- quadra-2.5.0.dist-info/entry_points.txt,sha256=sRYonBZyx-sAJeWcQNQoVQIU5lm02cnCQt6b15k0WHU,43
299
- quadra-2.5.0.dist-info/licenses/LICENSE,sha256=8cTbQtcWa02YJoSpMeV_gxj3jpMTkxvl-w3WJ5gV_QE,11342
300
- quadra-2.5.0.dist-info/RECORD,,
296
+ quadra-2.6.0.dist-info/METADATA,sha256=eAbmWIkIwEc5poNJHwM_9gEu4gH6uXmGlDv2HOfO7iA,17624
297
+ quadra-2.6.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
298
+ quadra-2.6.0.dist-info/entry_points.txt,sha256=sRYonBZyx-sAJeWcQNQoVQIU5lm02cnCQt6b15k0WHU,43
299
+ quadra-2.6.0.dist-info/licenses/LICENSE,sha256=8cTbQtcWa02YJoSpMeV_gxj3jpMTkxvl-w3WJ5gV_QE,11342
300
+ quadra-2.6.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any