qmenta-anon 2.2.1.dev385__tar.gz → 2.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qmenta-anon
3
- Version: 2.2.1.dev385
3
+ Version: 2.3.0
4
4
  Summary: The qmenta-anon library is used to anonymize files before transferring them to the QMENTA platform.
5
5
  Author: QMENTA
6
6
  Author-email: dev@qmenta.com
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
17
17
  Requires-Dist: coverage[toml] (>=7.3.0,<8.0.0)
18
18
  Requires-Dist: nibabel (>=5.3.2,<6.0.0)
19
- Requires-Dist: numpy (<2.0)
19
+ Requires-Dist: numpy (>2)
20
20
  Requires-Dist: pydicom (>=2.3.0,<3.0.0)
21
21
  Requires-Dist: pytest (>=7.4.0,<8.0.0)
22
22
  Requires-Dist: pytest-cov (>=6.1.1,<7.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qmenta-anon"
3
- version = "2.2.1.dev385"
3
+ version = "2.3.0"
4
4
  description = "The qmenta-anon library is used to anonymize files before transferring them to the QMENTA platform."
5
5
  authors = ["QMENTA <dev@qmenta.com>"]
6
6
  classifiers = [
@@ -17,7 +17,7 @@ packages = [
17
17
  python = "^3.10"
18
18
  pydicom = "^2.3.0"
19
19
  nibabel = "^5.3.2"
20
- numpy = "<2.0"
20
+ numpy = ">2"
21
21
  setuptools = "^80.3.1"
22
22
  pytest = "^7.4.0"
23
23
  coverage = {version = "^7.3.0", extras = ["toml"]}
@@ -330,13 +330,53 @@ def anonymise_dicom_dataset(dcm, actions=None):
330
330
 
331
331
 
332
332
  def anonymise(filename, actions=None):
333
- header = pydicom.read_file(filename, force=True)
333
+ try:
334
+ header = pydicom.read_file(filename)
335
+ invalid_dicom = False
336
+ except pydicom.errors.InvalidDicomError:
337
+ # 1. Read the legacy file
338
+ header = pydicom.read_file(filename, force=True)
339
+ invalid_dicom = True
340
+
341
+ # 2. Assess and safely default endianness and VR
342
+ if not hasattr(header, 'is_little_endian') or header.is_little_endian is None:
343
+ header.is_little_endian = True
344
+
345
+ if not hasattr(header, 'is_implicit_VR') or header.is_implicit_VR is None:
346
+ header.is_implicit_VR = True
347
+
348
+ # 3. Create the modern file meta header
349
+ if not hasattr(header, 'file_meta') or not header.file_meta:
350
+ header.file_meta = pydicom.dataset.FileMetaDataset()
351
+
352
+ # Dynamically assign the correct TransferSyntaxUID based on the dataset's properties
353
+ if header.is_little_endian and header.is_implicit_VR:
354
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
355
+ elif header.is_little_endian and not header.is_implicit_VR:
356
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
357
+ elif not header.is_little_endian and not header.is_implicit_VR:
358
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRBigEndian
359
+ else:
360
+ # Fallback for the highly unusual Implicit VR Big Endian
361
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
362
+
363
+ # Copy the SOP UIDs to the header
364
+ if 'SOPClassUID' in header:
365
+ header.file_meta.MediaStorageSOPClassUID = header.SOPClassUID
366
+
367
+ if 'SOPInstanceUID' in header:
368
+ header.file_meta.MediaStorageSOPInstanceUID = header.SOPInstanceUID
369
+ else:
370
+ header.file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
334
371
 
335
372
  actions = actions or DICOM_ANON_MIN_SUPP_55
336
373
  anonymise_dicom_dataset(header, actions)
337
374
  _updateMetaInfo(header)
338
375
 
339
- header.save_as(filename)
376
+ if invalid_dicom:
377
+ header.save_as(filename, write_like_original=False)
378
+ else:
379
+ header.save_as(filename)
340
380
  return {"OK": True, "error_tags": []}
341
381
 
342
382
 
@@ -559,7 +599,45 @@ class RelativeTimeAnonymiser:
559
599
  when trying to anonymise multiple DICOM headers of which the
560
600
  datetimes to anonymise span more than 24h.
561
601
  """
562
- header = pydicom.read_file(filename, force=True)
602
+ try:
603
+ header = pydicom.read_file(filename)
604
+ invalid_dicom = False
605
+ except pydicom.errors.InvalidDicomError:
606
+ # 1. Read the legacy file
607
+ header = pydicom.read_file(filename, force=True)
608
+ invalid_dicom = True
609
+
610
+ # 2. Assess and safely default endianness and VR
611
+ if not hasattr(header, 'is_little_endian') or header.is_little_endian is None:
612
+ header.is_little_endian = True
613
+
614
+ if not hasattr(header, 'is_implicit_VR') or header.is_implicit_VR is None:
615
+ header.is_implicit_VR = True
616
+
617
+ # 3. Create the modern file meta header
618
+ if not hasattr(header, 'file_meta') or not header.file_meta:
619
+ header.file_meta = pydicom.dataset.FileMetaDataset()
620
+
621
+ # Dynamically assign the correct TransferSyntaxUID based on the dataset's properties
622
+ if header.is_little_endian and header.is_implicit_VR:
623
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
624
+ elif header.is_little_endian and not header.is_implicit_VR:
625
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
626
+ elif not header.is_little_endian and not header.is_implicit_VR:
627
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRBigEndian
628
+ else:
629
+ # Fallback for the highly unusual Implicit VR Big Endian
630
+ header.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
631
+
632
+ # Copy the SOP UIDs to the header
633
+ if 'SOPClassUID' in header:
634
+ header.file_meta.MediaStorageSOPClassUID = header.SOPClassUID
635
+
636
+ if 'SOPInstanceUID' in header:
637
+ header.file_meta.MediaStorageSOPInstanceUID = header.SOPInstanceUID
638
+ else:
639
+ header.file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
640
+
563
641
  self.anonymise_datetime(header)
564
642
 
565
643
  actions = actions or DICOM_ANON_MIN_SUPP_55
@@ -572,4 +650,7 @@ class RelativeTimeAnonymiser:
572
650
  anonymise_dicom_dataset(header, restricted_actions)
573
651
  _updateMetaInfo(header)
574
652
 
575
- header.save_as(filename)
653
+ if invalid_dicom:
654
+ header.save_as(filename, write_like_original=False)
655
+ else:
656
+ header.save_as(filename)