dbdicom 0.3.8__py3-none-any.whl → 0.3.9__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 +6 -4
- dbdicom/database.py +2 -2
- dbdicom/dataset.py +29 -315
- dbdicom/dbd.py +52 -38
- dbdicom/sop_classes/enhanced_mr_image.py +190 -271
- dbdicom/utils/image.py +13 -13
- dbdicom/utils/pydicom_dataset.py +386 -0
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/METADATA +1 -1
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/RECORD +12 -12
- dbdicom/utils/variables.py +0 -161
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/WHEEL +0 -0
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/licenses/LICENSE +0 -0
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/top_level.txt +0 -0
dbdicom/dbd.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
import json
|
|
4
3
|
from typing import Union
|
|
5
4
|
import zipfile
|
|
@@ -9,12 +8,18 @@ from tqdm import tqdm
|
|
|
9
8
|
import numpy as np
|
|
10
9
|
import vreg
|
|
11
10
|
from pydicom.dataset import Dataset
|
|
11
|
+
import pydicom
|
|
12
12
|
|
|
13
13
|
import dbdicom.utils.arrays
|
|
14
14
|
import dbdicom.dataset as dbdataset
|
|
15
15
|
import dbdicom.database as dbdatabase
|
|
16
16
|
import dbdicom.register as register
|
|
17
17
|
import dbdicom.const as const
|
|
18
|
+
from dbdicom.utils.pydicom_dataset import (
|
|
19
|
+
get_values,
|
|
20
|
+
set_values,
|
|
21
|
+
set_value,
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
|
|
@@ -201,12 +206,13 @@ class DataBaseDicom():
|
|
|
201
206
|
return register.series(self.register, entity, desc, contains, isin)
|
|
202
207
|
|
|
203
208
|
|
|
204
|
-
def volume(self, entity:Union[list, str], dims:list=None) -> Union[vreg.Volume3D, list]:
|
|
209
|
+
def volume(self, entity:Union[list, str], dims:list=None, verbose=1) -> Union[vreg.Volume3D, list]:
|
|
205
210
|
"""Read volume or volumes.
|
|
206
211
|
|
|
207
212
|
Args:
|
|
208
213
|
entity (list, str): DICOM entity to read
|
|
209
214
|
dims (list, optional): Non-spatial dimensions of the volume. Defaults to None.
|
|
215
|
+
verbose (bool, optional): If set to 1, shows progress bar. Defaults to 1.
|
|
210
216
|
|
|
211
217
|
Returns:
|
|
212
218
|
vreg.Volume3D | list: If the entity is a series this returns
|
|
@@ -229,12 +235,14 @@ class DataBaseDicom():
|
|
|
229
235
|
# Read dicom files
|
|
230
236
|
values = []
|
|
231
237
|
volumes = []
|
|
232
|
-
for f in tqdm(files, desc='Reading volume..'):
|
|
233
|
-
ds =
|
|
234
|
-
values.append(
|
|
238
|
+
for f in tqdm(files, desc='Reading volume..', disable=(verbose==0)):
|
|
239
|
+
ds = pydicom.dcmread(f)
|
|
240
|
+
values.append(get_values(ds, dims))
|
|
235
241
|
volumes.append(dbdataset.volume(ds))
|
|
236
242
|
|
|
237
243
|
# Format as mesh
|
|
244
|
+
# coords = np.stack(values, axis=-1, dtype=object)
|
|
245
|
+
values = [np.array(v, dtype=object) for v in values] # object array to allow for mixed types
|
|
238
246
|
coords = np.stack(values, axis=-1)
|
|
239
247
|
coords, inds = dbdicom.utils.arrays.meshvals(coords)
|
|
240
248
|
vols = np.array(volumes)
|
|
@@ -292,7 +300,7 @@ class DataBaseDicom():
|
|
|
292
300
|
ref_mgr = DataBaseDicom(ref[0])
|
|
293
301
|
files = register.files(ref_mgr.register, ref)
|
|
294
302
|
ref_mgr.close()
|
|
295
|
-
ds =
|
|
303
|
+
ds = pydicom.dcmread(files[0])
|
|
296
304
|
|
|
297
305
|
# Get the attributes of the destination series
|
|
298
306
|
attr = self._series_attributes(series)
|
|
@@ -307,15 +315,17 @@ class DataBaseDicom():
|
|
|
307
315
|
i=0
|
|
308
316
|
vols = vol.separate().reshape(-1)
|
|
309
317
|
for vt in tqdm(vols, desc='Writing volume..'):
|
|
310
|
-
|
|
318
|
+
slices = vt.split()
|
|
319
|
+
for sl in slices:
|
|
311
320
|
dbdataset.set_volume(ds, sl)
|
|
312
|
-
|
|
321
|
+
sl_coords = [sl.coords[i,...].ravel()[0] for i in range(len(sl.dims))]
|
|
322
|
+
set_value(ds, sl.dims, sl_coords)
|
|
313
323
|
self._write_dataset(ds, attr, n + 1 + i)
|
|
314
324
|
i+=1
|
|
315
325
|
return self
|
|
316
326
|
|
|
317
327
|
|
|
318
|
-
def to_nifti(self, series:list, file:str, dims=None):
|
|
328
|
+
def to_nifti(self, series:list, file:str, dims=None, verbose=1):
|
|
319
329
|
"""Save a DICOM series in nifti format.
|
|
320
330
|
|
|
321
331
|
Args:
|
|
@@ -323,8 +333,10 @@ class DataBaseDicom():
|
|
|
323
333
|
file (str): file path of the nifti file.
|
|
324
334
|
dims (list, optional): Non-spatial dimensions of the volume.
|
|
325
335
|
Defaults to None.
|
|
336
|
+
verbose (bool, optional): If set to 1, shows progress bar. Defaults to 1.
|
|
337
|
+
|
|
326
338
|
"""
|
|
327
|
-
vol = self.volume(series, dims)
|
|
339
|
+
vol = self.volume(series, dims, verbose)
|
|
328
340
|
vreg.write_nifti(vol, file)
|
|
329
341
|
return self
|
|
330
342
|
|
|
@@ -390,12 +402,12 @@ class DataBaseDicom():
|
|
|
390
402
|
if attr is not None:
|
|
391
403
|
values = np.empty(len(files), dtype=dict)
|
|
392
404
|
for i, f in tqdm(enumerate(files), desc='Reading pixel data..'):
|
|
393
|
-
ds =
|
|
394
|
-
coords_array.append(
|
|
405
|
+
ds = pydicom.dcmread(f)
|
|
406
|
+
coords_array.append(get_values(ds, dims))
|
|
395
407
|
# save as dict so numpy does not stack as arrays
|
|
396
408
|
arrays[i] = {'pixel_data': dbdataset.pixel_data(ds)}
|
|
397
409
|
if attr is not None:
|
|
398
|
-
values[i] = {'values':
|
|
410
|
+
values[i] = {'values': get_values(ds, params)}
|
|
399
411
|
|
|
400
412
|
# Format as mesh
|
|
401
413
|
coords_array = np.stack([v for v in coords_array], axis=-1)
|
|
@@ -483,7 +495,7 @@ class DataBaseDicom():
|
|
|
483
495
|
if attr is None:
|
|
484
496
|
# If attributes are not provided, read all
|
|
485
497
|
# attributes from the first file
|
|
486
|
-
ds =
|
|
498
|
+
ds = pydicom.dcmread(files[0])
|
|
487
499
|
exclude = ['PixelData', 'FloatPixelData', 'DoubleFloatPixelData']
|
|
488
500
|
params = []
|
|
489
501
|
param_labels = []
|
|
@@ -504,10 +516,10 @@ class DataBaseDicom():
|
|
|
504
516
|
coords_array = []
|
|
505
517
|
values = np.empty(len(files), dtype=dict)
|
|
506
518
|
for i, f in tqdm(enumerate(files), desc='Reading values..'):
|
|
507
|
-
ds =
|
|
508
|
-
coords_array.append(
|
|
519
|
+
ds = pydicom.dcmread(f)
|
|
520
|
+
coords_array.append(get_values(ds, dims))
|
|
509
521
|
# save as dict so numpy does not stack as arrays
|
|
510
|
-
values[i] = {'values':
|
|
522
|
+
values[i] = {'values': get_values(ds, params)}
|
|
511
523
|
|
|
512
524
|
# Format as mesh
|
|
513
525
|
coords_array = np.stack([v for v in coords_array], axis=-1)
|
|
@@ -670,8 +682,8 @@ class DataBaseDicom():
|
|
|
670
682
|
files = []
|
|
671
683
|
values = []
|
|
672
684
|
for f in tqdm(all_files, desc=f'Reading {attr}'):
|
|
673
|
-
ds =
|
|
674
|
-
v =
|
|
685
|
+
ds = pydicom.dcmread(f)
|
|
686
|
+
v = get_values(ds, attr)
|
|
675
687
|
if key is not None:
|
|
676
688
|
v = key(v)
|
|
677
689
|
if v in values:
|
|
@@ -701,8 +713,8 @@ class DataBaseDicom():
|
|
|
701
713
|
files = register.files(self.register, entity)
|
|
702
714
|
v = np.empty((len(files), len(attributes)), dtype=object)
|
|
703
715
|
for i, f in enumerate(files):
|
|
704
|
-
ds =
|
|
705
|
-
v[i,:] =
|
|
716
|
+
ds = pydicom.dcmread(f)
|
|
717
|
+
v[i,:] = get_values(ds, attributes)
|
|
706
718
|
return v
|
|
707
719
|
|
|
708
720
|
def _copy_patient(self, from_patient, to_patient):
|
|
@@ -757,7 +769,7 @@ class DataBaseDicom():
|
|
|
757
769
|
# Copy the files to the new series
|
|
758
770
|
for i, f in tqdm(enumerate(files), total=len(files), desc=f'Copying series {to_series[1:]}'):
|
|
759
771
|
# Read dataset and assign new properties
|
|
760
|
-
ds =
|
|
772
|
+
ds = pydicom.dcmread(f)
|
|
761
773
|
self._write_dataset(ds, attr, n + 1 + i)
|
|
762
774
|
|
|
763
775
|
def _max_study_id(self, patient_id):
|
|
@@ -807,8 +819,8 @@ class DataBaseDicom():
|
|
|
807
819
|
# If the patient exists and has files, read from file
|
|
808
820
|
files = register.files(self.register, patient)
|
|
809
821
|
attr = const.PATIENT_MODULE
|
|
810
|
-
ds =
|
|
811
|
-
vals =
|
|
822
|
+
ds = pydicom.dcmread(files[0])
|
|
823
|
+
vals = get_values(ds, attr)
|
|
812
824
|
except:
|
|
813
825
|
# If the patient does not exist, generate values
|
|
814
826
|
if patient in self.patients():
|
|
@@ -827,8 +839,8 @@ class DataBaseDicom():
|
|
|
827
839
|
# If the study exists and has files, read from file
|
|
828
840
|
files = register.files(self.register, study)
|
|
829
841
|
attr = const.STUDY_MODULE
|
|
830
|
-
ds =
|
|
831
|
-
vals =
|
|
842
|
+
ds = pydicom.dcmread(files[0])
|
|
843
|
+
vals = get_values(ds, attr)
|
|
832
844
|
except register.AmbiguousError as e:
|
|
833
845
|
raise register.AmbiguousError(e)
|
|
834
846
|
except:
|
|
@@ -836,9 +848,9 @@ class DataBaseDicom():
|
|
|
836
848
|
if study[:-1] not in self.patients():
|
|
837
849
|
study_id = 1
|
|
838
850
|
else:
|
|
839
|
-
study_id = 1 + self._max_study_id(study[
|
|
851
|
+
study_id = 1 + self._max_study_id(study[1])
|
|
840
852
|
attr = ['StudyInstanceUID', 'StudyDescription', 'StudyID']
|
|
841
|
-
study_uid =
|
|
853
|
+
study_uid = pydicom.uid.generate_uid()
|
|
842
854
|
study_desc = study[-1] if isinstance(study[-1], str) else study[-1][0]
|
|
843
855
|
#study_date = datetime.today().strftime('%Y%m%d')
|
|
844
856
|
vals = [study_uid, study_desc, str(study_id)]
|
|
@@ -851,8 +863,8 @@ class DataBaseDicom():
|
|
|
851
863
|
# If the series exists and has files, read from file
|
|
852
864
|
files = register.files(self.register, series)
|
|
853
865
|
attr = const.SERIES_MODULE
|
|
854
|
-
ds =
|
|
855
|
-
vals =
|
|
866
|
+
ds = pydicom.dcmread(files[0])
|
|
867
|
+
vals = get_values(ds, attr)
|
|
856
868
|
except register.AmbiguousError as e:
|
|
857
869
|
raise register.AmbiguousError(e)
|
|
858
870
|
except:
|
|
@@ -864,7 +876,7 @@ class DataBaseDicom():
|
|
|
864
876
|
else:
|
|
865
877
|
series_number = 1 + self._max_series_number(study_uid)
|
|
866
878
|
attr = ['SeriesInstanceUID', 'SeriesDescription', 'SeriesNumber']
|
|
867
|
-
series_uid =
|
|
879
|
+
series_uid = pydicom.uid.generate_uid()
|
|
868
880
|
series_desc = series[-1] if isinstance(series[-1], str) else series[-1][0]
|
|
869
881
|
vals = [series_uid, series_desc, int(series_number)]
|
|
870
882
|
return study_attr | {attr[i]:vals[i] for i in range(len(attr)) if vals[i] is not None}
|
|
@@ -872,9 +884,9 @@ class DataBaseDicom():
|
|
|
872
884
|
|
|
873
885
|
def _write_dataset(self, ds:Dataset, attr:dict, instance_nr:int):
|
|
874
886
|
# Set new attributes
|
|
875
|
-
attr['SOPInstanceUID'] =
|
|
887
|
+
attr['SOPInstanceUID'] = pydicom.uid.generate_uid()
|
|
876
888
|
attr['InstanceNumber'] = str(instance_nr)
|
|
877
|
-
|
|
889
|
+
set_values(ds, list(attr.keys()), list(attr.values()))
|
|
878
890
|
# Save results in a new file
|
|
879
891
|
rel_dir = os.path.join(
|
|
880
892
|
f"Patient__{attr['PatientID']}",
|
|
@@ -882,7 +894,7 @@ class DataBaseDicom():
|
|
|
882
894
|
f"Series__{attr['SeriesNumber']}__{attr['SeriesDescription']}",
|
|
883
895
|
)
|
|
884
896
|
os.makedirs(os.path.join(self.path, rel_dir), exist_ok=True)
|
|
885
|
-
rel_path = os.path.join(rel_dir,
|
|
897
|
+
rel_path = os.path.join(rel_dir, pydicom.uid.generate_uid() + '.dcm')
|
|
886
898
|
dbdataset.write(ds, os.path.join(self.path, rel_path))
|
|
887
899
|
# Add an entry in the register
|
|
888
900
|
register.add_instance(self.register, attr, rel_path)
|
|
@@ -899,11 +911,13 @@ class DataBaseDicom():
|
|
|
899
911
|
)
|
|
900
912
|
os.makedirs(zip_dir, exist_ok=True)
|
|
901
913
|
for sr in st['series']:
|
|
914
|
+
zip_file = os.path.join(
|
|
915
|
+
zip_dir,
|
|
916
|
+
f"Series__{sr['SeriesNumber']}__{sr['SeriesDescription']}.zip",
|
|
917
|
+
)
|
|
918
|
+
if os.path.exists(zip_file):
|
|
919
|
+
continue
|
|
902
920
|
try:
|
|
903
|
-
zip_file = os.path.join(
|
|
904
|
-
zip_dir,
|
|
905
|
-
f"Series__{sr['SeriesNumber']}__{sr['SeriesDescription']}.zip",
|
|
906
|
-
)
|
|
907
921
|
with zipfile.ZipFile(zip_file, 'w') as zipf:
|
|
908
922
|
for rel_path in sr['instances'].values():
|
|
909
923
|
file = os.path.join(self.path, rel_path)
|