dbdicom 0.3.2__py3-none-any.whl → 0.3.4__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 dbdicom might be problematic. Click here for more details.

dbdicom/api.py CHANGED
@@ -1,3 +1,4 @@
1
+ from typing import Union
1
2
 
2
3
  import vreg
3
4
 
@@ -87,14 +88,14 @@ def patients(path, name:str=None, contains:str=None, isin:list=None)->list:
87
88
  return p
88
89
 
89
90
 
90
- def studies(entity:str | list, name:str=None, contains:str=None, isin:list=None)->list:
91
+ def studies(entity:str | list, desc:str=None, contains:str=None, isin:list=None)->list:
91
92
  """Return a list of studies in the DICOM folder.
92
93
 
93
94
  Args:
94
95
  entity (str or list): path to a DICOM folder (to search in
95
96
  the whole folder), or a two-element list identifying a
96
97
  patient (to search studies of a given patient).
97
- name (str, optional): value of StudyDescription, to search for
98
+ desc (str, optional): value of StudyDescription, to search for
98
99
  studies with a given description. Defaults to None.
99
100
  contains (str, optional): substring of StudyDescription, to
100
101
  search for studies based on part of their description.
@@ -107,12 +108,12 @@ def studies(entity:str | list, name:str=None, contains:str=None, isin:list=None)
107
108
  """
108
109
  if isinstance(entity, str): # path = folder
109
110
  dbd = open(entity)
110
- s = dbd.studies(entity, name, contains, isin)
111
+ s = dbd.studies(entity, desc, contains, isin)
111
112
  dbd.close()
112
113
  return s
113
114
  elif len(entity)==2: # path = patient
114
115
  dbd = open(entity[0])
115
- s = dbd.studies(entity, name, contains, isin)
116
+ s = dbd.studies(entity, desc, contains, isin)
116
117
  dbd.close()
117
118
  return s
118
119
  else:
@@ -121,7 +122,7 @@ def studies(entity:str | list, name:str=None, contains:str=None, isin:list=None)
121
122
  "with a folder and a patient name."
122
123
  )
123
124
 
124
- def series(entity:str | list, name:str=None, contains:str=None, isin:list=None)->list:
125
+ def series(entity:str | list, desc:str=None, contains:str=None, isin:list=None)->list:
125
126
  """Return a list of series in the DICOM folder.
126
127
 
127
128
  Args:
@@ -129,7 +130,7 @@ def series(entity:str | list, name:str=None, contains:str=None, isin:list=None)-
129
130
  the whole folder), or a list identifying a
130
131
  patient or a study (to search series of a given patient
131
132
  or study).
132
- name (str, optional): value of SeriesDescription, to search for
133
+ desc (str, optional): value of SeriesDescription, to search for
133
134
  series with a given description. Defaults to None.
134
135
  contains (str, optional): substring of SeriesDescription, to
135
136
  search for series based on part of their description.
@@ -142,12 +143,12 @@ def series(entity:str | list, name:str=None, contains:str=None, isin:list=None)-
142
143
  """
143
144
  if isinstance(entity, str): # path = folder
144
145
  dbd = open(entity)
145
- s = dbd.series(entity, name, contains, isin)
146
+ s = dbd.series(entity, desc, contains, isin)
146
147
  dbd.close()
147
148
  return s
148
149
  elif len(entity) in [2,3]:
149
150
  dbd = open(entity[0])
150
- s = dbd.series(entity, name, contains, isin)
151
+ s = dbd.series(entity, desc, contains, isin)
151
152
  dbd.close()
152
153
  return s
153
154
  else:
@@ -189,42 +190,54 @@ def move(from_entity:list, to_entity:list):
189
190
  dbd.delete(from_entity)
190
191
  dbd.close()
191
192
 
193
+ def split_series(series:list, attr:Union[str, tuple])->dict:
194
+ """
195
+ Split a series into multiple series
196
+
197
+ Args:
198
+ series (list): series to split.
199
+ attr (str or tuple): dicom attribute to split the series by.
200
+ Returns:
201
+ dict: dictionary with keys the unique values found (ascending)
202
+ and as values the series corresponding to that value.
203
+ """
204
+ dbd = open(series[0])
205
+ split_series = dbd.split_series(series, attr)
206
+ dbd.close()
207
+ return split_series
208
+
192
209
 
193
- def volume(series:list, dims:list=None, multislice=False) -> vreg.Volume3D:
194
- """Read a vreg.Volume3D from a DICOM series
210
+ def volume(entity:Union[list, str], dims:list=None) -> Union[vreg.Volume3D, list]:
211
+ """Read volume or volumes.
195
212
 
196
213
  Args:
197
- series (list): DICOM series to read
214
+ entity (list, str): DICOM entity to read
198
215
  dims (list, optional): Non-spatial dimensions of the volume. Defaults to None.
199
- multislice (bool, optional): Whether the data are to be read
200
- as multislice or not. In multislice data the voxel size
201
- is taken from the slice gap rather thsan the slice thickness. Defaults to False.
202
216
 
203
217
  Returns:
204
- vreg.Volume3D: vole read from the series.
218
+ vreg.Volume3D | list: If the entity is a series this returns
219
+ a volume, else a list of volumes.
205
220
  """
206
- dbd = open(series[0])
207
- vol = dbd.volume(series, dims, multislice)
221
+ if isinstance(entity, str):
222
+ entity = [entity]
223
+ dbd = open(entity[0])
224
+ vol = dbd.volume(entity, dims)
208
225
  dbd.close()
209
226
  return vol
210
227
 
211
- def write_volume(vol:vreg.Volume3D, series:list, ref:list=None,
212
- multislice=False):
228
+ def write_volume(vol:Union[vreg.Volume3D, tuple], series:list, ref:list=None):
213
229
  """Write a vreg.Volume3D to a DICOM series
214
230
 
215
231
  Args:
216
- vol (vreg.Volume3D): Volume to write to the series.
232
+ vol (vreg.Volume3D or tuple): Volume to write to the series.
217
233
  series (list): DICOM series to read
218
234
  dims (list, optional): Non-spatial dimensions of the volume. Defaults to None.
219
- multislice (bool, optional): Whether the data are to be read
220
- as multislice or not. In multislice data the voxel size
221
- is taken from the slice gap rather thsan the slice thickness. Defaults to False.
222
235
  """
223
236
  dbd = open(series[0])
224
- dbd.write_volume(vol, series, ref, multislice)
237
+ dbd.write_volume(vol, series, ref)
225
238
  dbd.close()
226
239
 
227
- def to_nifti(series:list, file:str, dims:list=None, multislice=False):
240
+ def to_nifti(series:list, file:str, dims:list=None):
228
241
  """Save a DICOM series in nifti format.
229
242
 
230
243
  Args:
@@ -232,27 +245,21 @@ def to_nifti(series:list, file:str, dims:list=None, multislice=False):
232
245
  file (str): file path of the nifti file.
233
246
  dims (list, optional): Non-spatial dimensions of the volume.
234
247
  Defaults to None.
235
- multislice (bool, optional): Whether the data are to be read
236
- as multislice or not. In multislice data the voxel size
237
- is taken from the slice gap rather thaan the slice thickness. Defaults to False.
238
248
  """
239
249
  dbd = open(series[0])
240
- dbd.to_nifti(series, file, dims, multislice)
250
+ dbd.to_nifti(series, file, dims)
241
251
  dbd.close()
242
252
 
243
- def from_nifti(file:str, series:list, ref:list=None, multislice=False):
253
+ def from_nifti(file:str, series:list, ref:list=None):
244
254
  """Create a DICOM series from a nifti file.
245
255
 
246
256
  Args:
247
257
  file (str): file path of the nifti file.
248
258
  series (list): DICOM series to create
249
259
  ref (list): DICOM series to use as template.
250
- multislice (bool, optional): Whether the data are to be written
251
- as multislice or not. In multislice data the voxel size
252
- is written in the slice gap rather thaan the slice thickness. Defaults to False.
253
260
  """
254
261
  dbd = open(series[0])
255
- dbd.from_nifti(file, series, ref, multislice)
262
+ dbd.from_nifti(file, series, ref)
256
263
  dbd.close()
257
264
 
258
265
  def pixel_data(series:list, dims:list=None, include:list=None) -> tuple:
@@ -270,6 +277,8 @@ def pixel_data(series:list, dims:list=None, include:list=None) -> tuple:
270
277
  is provide these are returned as a dictionary in a third
271
278
  return value.
272
279
  """
280
+ if isinstance(series, str):
281
+ series = [series]
273
282
  dbd = open(series[0])
274
283
  array = dbd.pixel_data(series, dims, include)
275
284
  dbd.close()
@@ -289,11 +298,12 @@ def unique(pars:list, entity:list) -> dict:
289
298
  """Return a list of unique values for a DICOM entity
290
299
 
291
300
  Args:
292
- pars (list): attributes to return.
301
+ pars (list, str/tuple): attribute or attributes to return.
293
302
  entity (list): DICOM entity to search (Patient, Study or Series)
294
303
 
295
304
  Returns:
296
- dict: dictionary with unique values for each attribute.
305
+ dict: if a pars is a list, this returns a dictionary with
306
+ unique values for each attribute. If pars is a scalar this returnes a list of values
297
307
  """
298
308
  dbd = open(entity[0])
299
309
  u = dbd.unique(pars, entity)
dbdicom/database.py CHANGED
@@ -20,6 +20,7 @@ COLUMNS = [
20
20
  'PatientName',
21
21
  'StudyDescription',
22
22
  'StudyDate',
23
+ 'StudyID',
23
24
  'SeriesDescription',
24
25
  'SeriesNumber',
25
26
  'InstanceNumber',
@@ -77,6 +78,7 @@ def _multiframe_to_singleframe(path, df):
77
78
 
78
79
  def _tree(df):
79
80
  # A human-readable summary tree
81
+ # TODO: Add version number
80
82
 
81
83
  df.sort_values(['PatientID','StudyInstanceUID','SeriesNumber'], inplace=True)
82
84
  df = df.fillna('None')
@@ -94,10 +96,12 @@ def _tree(df):
94
96
  for uid_study in df_patient.StudyInstanceUID.unique():
95
97
  df_study = df_patient[df_patient.StudyInstanceUID == uid_study]
96
98
  study_desc = df_study.StudyDescription.values[0]
99
+ study_id = df_study.StudyID.values[0]
97
100
  study_date = df_study.StudyDate.values[0]
98
101
  study = {
99
102
  'StudyDescription': study_desc,
100
103
  'StudyDate': study_date,
104
+ 'StudyID': study_id,
101
105
  'StudyInstanceUID': uid_study,
102
106
  'series': [],
103
107
  }
dbdicom/dataset.py CHANGED
@@ -229,7 +229,8 @@ def write(ds, file, status=None):
229
229
  dir = os.path.dirname(file)
230
230
  if not os.path.exists(dir):
231
231
  os.makedirs(dir)
232
- ds.save_as(file, write_like_original=False)
232
+ #ds.save_as(file, write_like_original=False) # deprecated
233
+ ds.save_as(file, enforce_file_format=True)
233
234
 
234
235
 
235
236
  def codify(source_file, save_file, **kwargs):
@@ -513,32 +514,24 @@ def set_lut(ds, RGB):
513
514
 
514
515
 
515
516
 
516
- def affine(ds, multislice=False):
517
- if multislice:
518
- return image.affine_matrix(
519
- get_values(ds, 'ImageOrientationPatient'),
520
- get_values(ds, 'ImagePositionPatient'),
521
- get_values(ds, 'PixelSpacing'),
522
- get_values(ds, 'SpacingBetweenSlices'),
523
- )
524
- else:
525
- return image.affine_matrix(
526
- get_values(ds, 'ImageOrientationPatient'),
527
- get_values(ds, 'ImagePositionPatient'),
528
- get_values(ds, 'PixelSpacing'),
529
- get_values(ds, 'SliceThickness'),
530
- )
517
+ def affine(ds):
518
+ # Spacing Between Slices is not required so can be absent
519
+ slice_spacing = ds.get("SpacingBetweenSlices")
520
+ if slice_spacing is None:
521
+ slice_spacing = ds.get("SliceThickness")
522
+ return image.affine_matrix(
523
+ get_values(ds, 'ImageOrientationPatient'),
524
+ get_values(ds, 'ImagePositionPatient'),
525
+ get_values(ds, 'PixelSpacing'),
526
+ slice_spacing,
527
+ )
531
528
 
532
-
533
- def set_affine(ds, affine, multislice=False):
529
+ def set_affine(ds, affine):
534
530
  if affine is None:
535
531
  raise ValueError('The affine cannot be set to an empty value')
536
532
  v = image.dismantle_affine_matrix(affine)
537
533
  set_values(ds, 'PixelSpacing', v['PixelSpacing'])
538
- if multislice:
539
- set_values(ds, 'SpacingBetweenSlices', v['SliceThickness'])
540
- else:
541
- set_values(ds, 'SliceThickness', v['SliceThickness'])
534
+ set_values(ds, 'SpacingBetweenSlices', v['SpacingBetweenSlices'])
542
535
  set_values(ds, 'ImageOrientationPatient', v['ImageOrientationPatient'])
543
536
  set_values(ds, 'ImagePositionPatient', v['ImagePositionPatient'])
544
537
  set_values(ds, 'SliceLocation', np.dot(v['ImagePositionPatient'], v['slice_cosine']))
@@ -602,10 +595,10 @@ def set_pixel_data(ds, array):
602
595
  ds.PixelData = array.tobytes()
603
596
 
604
597
 
605
- def volume(ds, multislice=False):
606
- return vreg.volume(pixel_data(ds), affine(ds, multislice))
598
+ def volume(ds):
599
+ return vreg.volume(pixel_data(ds), affine(ds))
607
600
 
608
- def set_volume(ds, volume:vreg.Volume3D, multislice=False):
601
+ def set_volume(ds, volume:vreg.Volume3D):
609
602
  if volume is None:
610
603
  raise ValueError('The volume cannot be set to an empty value.')
611
604
  try:
@@ -621,7 +614,7 @@ def set_volume(ds, volume:vreg.Volume3D, multislice=False):
621
614
  if image.ndim != 2:
622
615
  raise ValueError("Can only write 2D images to a dataset.")
623
616
  set_pixel_data(ds, image)
624
- set_affine(ds, volume.affine, multislice)
617
+ set_affine(ds, volume.affine)
625
618
  if volume.coords is not None:
626
619
  # All other dimensions should have size 1
627
620
  coords = volume.coords.reshape((volume.coords.shape[0], -1))