dbdicom 0.3.1__tar.gz → 0.3.2__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.

Potentially problematic release.


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

Files changed (63) hide show
  1. {dbdicom-0.3.1/src/dbdicom.egg-info → dbdicom-0.3.2}/PKG-INFO +2 -4
  2. {dbdicom-0.3.1 → dbdicom-0.3.2}/pyproject.toml +4 -4
  3. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/api.py +24 -1
  4. dbdicom-0.3.2/src/dbdicom/database.py +122 -0
  5. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/dataset.py +16 -34
  6. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/dbd.py +92 -167
  7. dbdicom-0.3.2/src/dbdicom/register.py +608 -0
  8. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/mr_image.py +122 -130
  9. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/parametric_map.py +93 -22
  10. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/utils/image.py +7 -6
  11. {dbdicom-0.3.1 → dbdicom-0.3.2/src/dbdicom.egg-info}/PKG-INFO +2 -4
  12. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom.egg-info/SOURCES.txt +2 -0
  13. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom.egg-info/requires.txt +1 -3
  14. dbdicom-0.3.2/tests/test_api.py +25 -0
  15. {dbdicom-0.3.1 → dbdicom-0.3.2}/tests/test_dcm4che.py +2 -4
  16. dbdicom-0.3.1/src/dbdicom/register.py +0 -527
  17. {dbdicom-0.3.1 → dbdicom-0.3.2}/LICENSE +0 -0
  18. {dbdicom-0.3.1 → dbdicom-0.3.2}/MANIFEST.in +0 -0
  19. {dbdicom-0.3.1 → dbdicom-0.3.2}/README.rst +0 -0
  20. {dbdicom-0.3.1 → dbdicom-0.3.2}/setup.cfg +0 -0
  21. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/__init__.py +0 -0
  22. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/const.py +0 -0
  23. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/__init__.py +0 -0
  24. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  25. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/README.md +0 -0
  26. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/__init__.py +0 -0
  27. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  28. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/bin/__init__.py +0 -0
  29. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  30. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/bin/deidentify +0 -0
  31. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/bin/deidentify.bat +0 -0
  32. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/bin/emf2sf +0 -0
  33. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/bin/emf2sf.bat +0 -0
  34. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/etc/__init__.py +0 -0
  35. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/etc/emf2sf/__init__.py +0 -0
  36. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/etc/emf2sf/log4j.properties +0 -0
  37. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/__init__.py +0 -0
  38. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/commons-cli-1.4.jar +0 -0
  39. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/dcm4che-core-5.23.1.jar +0 -0
  40. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/dcm4che-emf-5.23.1.jar +0 -0
  41. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/dcm4che-tool-common-5.23.1.jar +0 -0
  42. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/dcm4che-tool-emf2sf-5.23.1.jar +0 -0
  43. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/log4j-1.2.17.jar +0 -0
  44. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/macosx-x86-64/libopencv_java.jnilib +0 -0
  45. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/slf4j-api-1.7.30.jar +0 -0
  46. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/slf4j-log4j12-1.7.30.jar +0 -0
  47. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/windows-x86/clib_jiio.dll +0 -0
  48. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/windows-x86/clib_jiio_sse2.dll +0 -0
  49. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/windows-x86/clib_jiio_util.dll +0 -0
  50. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/windows-x86/opencv_java.dll +0 -0
  51. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/external/dcm4che/lib/windows-x86-64/opencv_java.dll +0 -0
  52. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/ct_image.py +0 -0
  53. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/enhanced_mr_image.py +0 -0
  54. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/secondary_capture.py +0 -0
  55. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/segmentation.py +0 -0
  56. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/ultrasound_multiframe_image.py +0 -0
  57. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/sop_classes/xray_angiographic_image.py +0 -0
  58. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/utils/arrays.py +0 -0
  59. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/utils/dcm4che.py +0 -0
  60. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/utils/files.py +0 -0
  61. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom/utils/variables.py +0 -0
  62. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom.egg-info/dependency_links.txt +0 -0
  63. {dbdicom-0.3.1 → dbdicom-0.3.2}/src/dbdicom.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbdicom
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A pythonic interface for reading and writing DICOM databases
5
5
  Author-email: Steven Sourbron <s.sourbron@sheffield.ac.uk>, Ebony Gunwhy <e.gunwhy@sheffield.ac.uk>
6
6
  Project-URL: Homepage, https://openmiblab.github.io/dbdicom/
@@ -22,7 +22,5 @@ Requires-Dist: importlib-resources
22
22
  Requires-Dist: numpy
23
23
  Requires-Dist: pandas
24
24
  Requires-Dist: vreg
25
- Requires-Dist: pydicom
26
- Requires-Dist: python-gdcm
27
- Requires-Dist: pylibjpeg-libjpeg
25
+ Requires-Dist: pydicom[basic,pixeldata]
28
26
  Dynamic: license-file
@@ -7,16 +7,16 @@ requires = ['setuptools>=61.2']
7
7
 
8
8
  [project]
9
9
  name = "dbdicom"
10
- version = "0.3.1"
10
+ version = "0.3.2"
11
11
  dependencies = [
12
12
  "tqdm",
13
13
  "importlib-resources",
14
14
  "numpy",
15
15
  "pandas", # make obsolete
16
16
  'vreg',
17
- "pydicom",
18
- "python-gdcm",
19
- "pylibjpeg-libjpeg",
17
+ "pydicom[basic,pixeldata]",
18
+ #"python-gdcm",
19
+ #"pylibjpeg-libjpeg",
20
20
  ]
21
21
 
22
22
  # optional information
@@ -1,5 +1,4 @@
1
1
 
2
- import numpy as np
3
2
  import vreg
4
3
 
5
4
  from dbdicom.dbd import DataBaseDicom
@@ -16,6 +15,15 @@ def open(path:str) -> DataBaseDicom:
16
15
  """
17
16
  return DataBaseDicom(path)
18
17
 
18
+ def to_json(path):
19
+ """Summarise the contents of the DICOM folder in a json file
20
+
21
+ Args:
22
+ path (str): path to the DICOM folder
23
+ """
24
+ dbd = open(path)
25
+ dbd.close()
26
+
19
27
  def print(path):
20
28
  """Print the contents of the DICOM folder
21
29
 
@@ -42,6 +50,21 @@ def summary(path) -> dict:
42
50
  return s
43
51
 
44
52
 
53
+ def tree(path) -> dict:
54
+ """Return the structure of the database as a dictionary tree.
55
+
56
+ Args:
57
+ path (str): path to the DICOM folder
58
+
59
+ Returns:
60
+ dict: Nested dictionary with summary information on the database.
61
+ """
62
+ dbd = open(path)
63
+ s = dbd.register
64
+ dbd.close()
65
+ return s
66
+
67
+
45
68
  def patients(path, name:str=None, contains:str=None, isin:list=None)->list:
46
69
  """Return a list of patients in the DICOM folder.
47
70
 
@@ -0,0 +1,122 @@
1
+ import os
2
+ from tqdm import tqdm
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import pydicom
7
+
8
+ import dbdicom.utils.dcm4che as dcm4che
9
+ import dbdicom.utils.files as filetools
10
+ import dbdicom.dataset as dbdataset
11
+
12
+
13
+ COLUMNS = [
14
+ # Identifiers (unique)
15
+ 'PatientID',
16
+ 'StudyInstanceUID',
17
+ 'SeriesInstanceUID',
18
+ 'SOPInstanceUID',
19
+ # Human-readable identifiers (not unique)
20
+ 'PatientName',
21
+ 'StudyDescription',
22
+ 'StudyDate',
23
+ 'SeriesDescription',
24
+ 'SeriesNumber',
25
+ 'InstanceNumber',
26
+ ]
27
+
28
+ def read(path):
29
+ files = filetools.all_files(path)
30
+ tags = COLUMNS + ['NumberOfFrames'] # + ['SOPClassUID']
31
+ array = []
32
+ dicom_files = []
33
+ for i, file in tqdm(enumerate(files), total=len(files), desc='Reading DICOM folder'):
34
+ try:
35
+ ds = pydicom.dcmread(file, force=True, specific_tags=tags+['Rows'])
36
+ except:
37
+ pass
38
+ else:
39
+ if isinstance(ds, pydicom.dataset.FileDataset):
40
+ if 'TransferSyntaxUID' in ds.file_meta:
41
+ if not 'Rows' in ds: # Image only
42
+ continue
43
+ row = dbdataset.get_values(ds, tags)
44
+ array.append(row)
45
+ index = os.path.relpath(file, path)
46
+ dicom_files.append(index)
47
+ df = pd.DataFrame(array, index = dicom_files, columns = tags)
48
+ df = _multiframe_to_singleframe(path, df)
49
+ dbtree = _tree(df)
50
+ return dbtree
51
+
52
+
53
+ def _multiframe_to_singleframe(path, df):
54
+ """Converts all multiframe files in the folder into single-frame files.
55
+
56
+ Reads all the multi-frame files in the folder,
57
+ converts them to singleframe files, and delete the original multiframe file.
58
+ """
59
+ singleframe = df.NumberOfFrames.isnull()
60
+ multiframe = singleframe == False
61
+ nr_multiframe = multiframe.sum()
62
+ if nr_multiframe != 0:
63
+ for relpath in tqdm(df[multiframe].index.values, desc="Converting multiframe file " + relpath):
64
+ filepath = os.path.join(path, relpath)
65
+ singleframe_files = dcm4che.split_multiframe(filepath)
66
+ if singleframe_files != []:
67
+ # add the single frame files to the dataframe
68
+ dfnew = read(singleframe_files, df.columns, path)
69
+ df = pd.concat([df, dfnew])
70
+ # delete the original multiframe
71
+ os.remove(filepath)
72
+ # drop the file also if the conversion has failed
73
+ df.drop(index=relpath, inplace=True)
74
+ df.drop('NumberOfFrames', axis=1, inplace=True)
75
+ return df
76
+
77
+
78
+ def _tree(df):
79
+ # A human-readable summary tree
80
+
81
+ df.sort_values(['PatientID','StudyInstanceUID','SeriesNumber'], inplace=True)
82
+ df = df.fillna('None')
83
+ summary = []
84
+
85
+ for uid_patient in df.PatientID.unique():
86
+ df_patient = df[df.PatientID == uid_patient]
87
+ patient_name = df_patient.PatientName.values[0]
88
+ patient = {
89
+ 'PatientName': patient_name,
90
+ 'PatientID': uid_patient,
91
+ 'studies': [],
92
+ }
93
+ summary.append(patient)
94
+ for uid_study in df_patient.StudyInstanceUID.unique():
95
+ df_study = df_patient[df_patient.StudyInstanceUID == uid_study]
96
+ study_desc = df_study.StudyDescription.values[0]
97
+ study_date = df_study.StudyDate.values[0]
98
+ study = {
99
+ 'StudyDescription': study_desc,
100
+ 'StudyDate': study_date,
101
+ 'StudyInstanceUID': uid_study,
102
+ 'series': [],
103
+ }
104
+ patient['studies'].append(study)
105
+ for uid_sery in df_study.SeriesInstanceUID.unique():
106
+ df_series = df_study[df_study.SeriesInstanceUID == uid_sery]
107
+ series_desc = df_series.SeriesDescription.values[0]
108
+ series_nr = int(df_series.SeriesNumber.values[0])
109
+ series = {
110
+ 'SeriesNumber': series_nr,
111
+ 'SeriesDescription': series_desc,
112
+ 'SeriesInstanceUID': uid_sery,
113
+ 'instances': {},
114
+ }
115
+ study['series'].append(series)
116
+ for uid_instance in df_series.SOPInstanceUID.unique():
117
+ df_instance = df_series[df_series.SOPInstanceUID == uid_instance]
118
+ instance_nr = int(df_instance.InstanceNumber.values[0])
119
+ relpath = df_instance.index[0]
120
+ series['instances'][instance_nr]=relpath
121
+
122
+ return summary
@@ -7,13 +7,12 @@ import struct
7
7
  from tqdm import tqdm
8
8
 
9
9
  import numpy as np
10
- import pandas as pd
11
10
  import pydicom
12
11
  from pydicom.util.codify import code_file
13
12
  import pydicom.config
14
- from pydicom.dataset import Dataset
15
13
  import vreg
16
14
 
15
+
17
16
  import dbdicom.utils.image as image
18
17
  import dbdicom.utils.variables as variables
19
18
  from dbdicom.sop_classes import (
@@ -74,6 +73,8 @@ def new_dataset(sop_class):
74
73
  return xray_angiographic_image.default()
75
74
  if sop_class == 'UltrasoundMultiFrameImage':
76
75
  return ultrasound_multiframe_image.default()
76
+ if sop_class == 'ParametricMap':
77
+ return parametric_map.default()
77
78
  else:
78
79
  raise ValueError(
79
80
  f"DICOM class {sop_class} is not currently supported"
@@ -238,7 +239,7 @@ def codify(source_file, save_file, **kwargs):
238
239
  file.close()
239
240
 
240
241
 
241
- def read_data(files, tags, path=None, images_only=False):
242
+ def read_data(files, tags, path=None, images_only=False): # obsolete??
242
243
 
243
244
  if np.isscalar(files):
244
245
  files = [files]
@@ -266,34 +267,6 @@ def read_data(files, tags, path=None, images_only=False):
266
267
 
267
268
 
268
269
 
269
- def read_dataframe(files, tags, path=None, images_only=False):
270
- if np.isscalar(files):
271
- files = [files]
272
- if np.isscalar(tags):
273
- tags = [tags]
274
- array = []
275
- dicom_files = []
276
- for i, file in tqdm(enumerate(files), desc='Reading DICOM folder'):
277
- try:
278
- ds = pydicom.dcmread(file, force=True, specific_tags=tags+['Rows'])
279
- except:
280
- pass
281
- else:
282
- if isinstance(ds, pydicom.dataset.FileDataset):
283
- if 'TransferSyntaxUID' in ds.file_meta:
284
- if images_only:
285
- if not 'Rows' in ds:
286
- continue
287
- row = get_values(ds, tags)
288
- array.append(row)
289
- if path is None:
290
- index = file
291
- else:
292
- index = os.path.relpath(file, path)
293
- dicom_files.append(index)
294
- df = pd.DataFrame(array, index = dicom_files, columns = tags)
295
- return df
296
-
297
270
 
298
271
  def _add_new(ds, tag, value, VR='OW'):
299
272
  if not isinstance(tag, pydicom.tag.BaseTag):
@@ -586,7 +559,7 @@ def pixel_data(ds):
586
559
  try:
587
560
  array = ds.pixel_array
588
561
  except:
589
- return None
562
+ raise ValueError("Dataset has no pixel data.")
590
563
  array = array.astype(np.float32)
591
564
  slope = float(getattr(ds, 'RescaleSlope', 1))
592
565
  intercept = float(getattr(ds, 'RescaleIntercept', 0))
@@ -595,7 +568,7 @@ def pixel_data(ds):
595
568
  return np.transpose(array)
596
569
 
597
570
 
598
- def set_pixel_data(ds, array, value_range=None):
571
+ def set_pixel_data(ds, array):
599
572
  if array is None:
600
573
  raise ValueError('The pixel array cannot be set to an empty value.')
601
574
 
@@ -611,7 +584,7 @@ def set_pixel_data(ds, array, value_range=None):
611
584
  # if array.ndim >= 3: # remove spurious dimensions of 1
612
585
  # array = np.squeeze(array)
613
586
 
614
- array = image.clip(array.astype(np.float32), value_range=value_range)
587
+ array = image.clip(array.astype(np.float32))
615
588
  array, slope, intercept = image.scale_to_range(array, ds.BitsAllocated)
616
589
  array = np.transpose(array)
617
590
 
@@ -635,6 +608,15 @@ def volume(ds, multislice=False):
635
608
  def set_volume(ds, volume:vreg.Volume3D, multislice=False):
636
609
  if volume is None:
637
610
  raise ValueError('The volume cannot be set to an empty value.')
611
+ try:
612
+ mod = SOPCLASSMODULE[ds.SOPClassUID]
613
+ except KeyError:
614
+ raise ValueError(
615
+ f"DICOM class {ds.SOPClassUID} is not currently supported."
616
+ )
617
+ if hasattr(mod, 'set_volume'):
618
+ return getattr(mod, 'set_volume')(ds, volume)
619
+
638
620
  image = np.squeeze(volume.values)
639
621
  if image.ndim != 2:
640
622
  raise ValueError("Can only write 2D images to a dataset.")