dbdicom 0.3.8__tar.gz → 0.3.10__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.
- {dbdicom-0.3.8/src/dbdicom.egg-info → dbdicom-0.3.10}/PKG-INFO +1 -1
- {dbdicom-0.3.8 → dbdicom-0.3.10}/pyproject.toml +1 -1
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/api.py +60 -46
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/database.py +2 -2
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/dataset.py +30 -316
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/dbd.py +302 -162
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/register.py +21 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/enhanced_mr_image.py +190 -271
- dbdicom-0.3.10/src/dbdicom/utils/arrays.py +128 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/utils/image.py +13 -13
- dbdicom-0.3.10/src/dbdicom/utils/pydicom_dataset.py +386 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10/src/dbdicom.egg-info}/PKG-INFO +1 -1
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom.egg-info/SOURCES.txt +4 -2
- dbdicom-0.3.10/tests/test_api.py +225 -0
- dbdicom-0.3.10/tests/test_sop_classes.py +33 -0
- dbdicom-0.3.10/tests/test_utils.py +101 -0
- dbdicom-0.3.8/src/dbdicom/utils/arrays.py +0 -40
- dbdicom-0.3.8/src/dbdicom/utils/variables.py +0 -161
- dbdicom-0.3.8/tests/test_api.py +0 -103
- {dbdicom-0.3.8 → dbdicom-0.3.10}/LICENSE +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/MANIFEST.in +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/README.rst +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/setup.cfg +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/const.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/README.md +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/bin/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/bin/deidentify +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/bin/deidentify.bat +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/bin/emf2sf +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/bin/emf2sf.bat +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/etc/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/etc/emf2sf/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/etc/emf2sf/log4j.properties +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/__init__.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/commons-cli-1.4.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/dcm4che-core-5.23.1.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/dcm4che-emf-5.23.1.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/dcm4che-tool-common-5.23.1.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/dcm4che-tool-emf2sf-5.23.1.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/log4j-1.2.17.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/macosx-x86-64/libopencv_java.jnilib +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/slf4j-api-1.7.30.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/slf4j-log4j12-1.7.30.jar +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/windows-x86/clib_jiio.dll +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/windows-x86/clib_jiio_sse2.dll +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/windows-x86/clib_jiio_util.dll +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/windows-x86/opencv_java.dll +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/external/dcm4che/lib/windows-x86-64/opencv_java.dll +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/ct_image.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/mr_image.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/parametric_map.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/secondary_capture.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/segmentation.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/ultrasound_multiframe_image.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/sop_classes/xray_angiographic_image.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/utils/dcm4che.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom/utils/files.py +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom.egg-info/dependency_links.txt +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom.egg-info/requires.txt +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/src/dbdicom.egg-info/top_level.txt +0 -0
- {dbdicom-0.3.8 → dbdicom-0.3.10}/tests/test_dcm4che.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbdicom
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
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/
|
|
@@ -4,8 +4,7 @@ import zipfile
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Union
|
|
6
6
|
from tqdm import tqdm
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import numpy as np
|
|
9
8
|
import vreg
|
|
10
9
|
|
|
11
10
|
from dbdicom.dbd import DataBaseDicom
|
|
@@ -164,16 +163,22 @@ def series(entity:str | list, desc:str=None, contains:str=None, isin:list=None)-
|
|
|
164
163
|
"To retrieve a series, the entity must be a database, patient or study."
|
|
165
164
|
)
|
|
166
165
|
|
|
167
|
-
def copy(from_entity:list, to_entity
|
|
168
|
-
"""Copy a DICOM
|
|
166
|
+
def copy(from_entity:list, to_entity=None):
|
|
167
|
+
"""Copy a DICOM entity (patient, study or series)
|
|
169
168
|
|
|
170
169
|
Args:
|
|
171
170
|
from_entity (list): entity to copy
|
|
172
|
-
to_entity (list): entity after copying.
|
|
171
|
+
to_entity (list, optional): entity after copying. If this is not
|
|
172
|
+
provided, a copy will be made in the same study and returned.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
entity: the copied entity. If th to_entity is provided, this is
|
|
176
|
+
returned.
|
|
173
177
|
"""
|
|
174
178
|
dbd = open(from_entity[0])
|
|
175
|
-
dbd.copy(from_entity, to_entity)
|
|
179
|
+
from_entity_copy = dbd.copy(from_entity, to_entity)
|
|
176
180
|
dbd.close()
|
|
181
|
+
return from_entity_copy
|
|
177
182
|
|
|
178
183
|
|
|
179
184
|
def delete(entity:list):
|
|
@@ -216,24 +221,42 @@ def split_series(series:list, attr:Union[str, tuple], key=None)->list:
|
|
|
216
221
|
return split_series
|
|
217
222
|
|
|
218
223
|
|
|
219
|
-
def volume(
|
|
220
|
-
"""Read volume
|
|
224
|
+
def volume(series:list, dims:list=None, verbose=1) -> vreg.Volume3D:
|
|
225
|
+
"""Read volume from a series.
|
|
221
226
|
|
|
222
227
|
Args:
|
|
223
|
-
|
|
228
|
+
series (list, str): DICOM entity to read
|
|
224
229
|
dims (list, optional): Non-spatial dimensions of the volume. Defaults to None.
|
|
230
|
+
verbose (bool, optional): If set to 1, shows progress bar. Defaults to 1.
|
|
225
231
|
|
|
226
232
|
Returns:
|
|
227
|
-
vreg.Volume3D
|
|
228
|
-
a volume, else a list of volumes.
|
|
233
|
+
vreg.Volume3D.
|
|
229
234
|
"""
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
dbd = open(entity[0])
|
|
233
|
-
vol = dbd.volume(entity, dims)
|
|
235
|
+
dbd = open(series[0])
|
|
236
|
+
vol = dbd.volume(series, dims, verbose)
|
|
234
237
|
dbd.close()
|
|
235
238
|
return vol
|
|
236
239
|
|
|
240
|
+
|
|
241
|
+
def values(series:list, *attr, dims:list=None, verbose=1) -> Union[np.ndarray, list]:
|
|
242
|
+
"""Read the values of some attributes from a DICOM series
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
series (list): DICOM series to read.
|
|
246
|
+
attr (tuple, optional): DICOM attributes to read.
|
|
247
|
+
dims (list, optional): Dimensions to sort the values.
|
|
248
|
+
If dims is not provided, values are sorted by
|
|
249
|
+
InstanceNumber.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
tuple: arrays with values for the attributes.
|
|
253
|
+
"""
|
|
254
|
+
dbd = open(series[0])
|
|
255
|
+
values = dbd.values(series, *attr, dims=dims, verbose=verbose)
|
|
256
|
+
dbd.close()
|
|
257
|
+
return values
|
|
258
|
+
|
|
259
|
+
|
|
237
260
|
def write_volume(vol:Union[vreg.Volume3D, tuple], series:list, ref:list=None):
|
|
238
261
|
"""Write a vreg.Volume3D to a DICOM series
|
|
239
262
|
|
|
@@ -246,7 +269,26 @@ def write_volume(vol:Union[vreg.Volume3D, tuple], series:list, ref:list=None):
|
|
|
246
269
|
dbd.write_volume(vol, series, ref)
|
|
247
270
|
dbd.close()
|
|
248
271
|
|
|
249
|
-
|
|
272
|
+
|
|
273
|
+
def edit(series:list, new_values:dict, dims:list=None, verbose=1):
|
|
274
|
+
"""Edit attribute values in a DICOM series
|
|
275
|
+
|
|
276
|
+
Warning: this function edits all values as requested. Please take care
|
|
277
|
+
when editing attributes that affect the DICOM file organisation, such as
|
|
278
|
+
UIDs, as this could corrupt the database.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
series (list): DICOM series to edit
|
|
282
|
+
new_values (dict): dictionary with attribute: value pairs to write to the series
|
|
283
|
+
dims (list, optional): Non-spatial dimensions of the volume. Defaults to None.
|
|
284
|
+
verbose (bool, optional): If set to 1, shows progress bar. Defaults to 1.
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
dbd = open(series[0])
|
|
288
|
+
dbd.edit(series, new_values, dims=dims, verbose=verbose)
|
|
289
|
+
dbd.close()
|
|
290
|
+
|
|
291
|
+
def to_nifti(series:list, file:str, dims:list=None, verbose=1):
|
|
250
292
|
"""Save a DICOM series in nifti format.
|
|
251
293
|
|
|
252
294
|
Args:
|
|
@@ -254,9 +296,10 @@ def to_nifti(series:list, file:str, dims:list=None):
|
|
|
254
296
|
file (str): file path of the nifti file.
|
|
255
297
|
dims (list, optional): Non-spatial dimensions of the volume.
|
|
256
298
|
Defaults to None.
|
|
299
|
+
verbose (bool, optional): If set to 1, shows progress bar. Defaults to 1.
|
|
257
300
|
"""
|
|
258
301
|
dbd = open(series[0])
|
|
259
|
-
dbd.to_nifti(series, file, dims)
|
|
302
|
+
dbd.to_nifti(series, file, dims, verbose)
|
|
260
303
|
dbd.close()
|
|
261
304
|
|
|
262
305
|
def from_nifti(file:str, series:list, ref:list=None):
|
|
@@ -272,35 +315,6 @@ def from_nifti(file:str, series:list, ref:list=None):
|
|
|
272
315
|
dbd.close()
|
|
273
316
|
|
|
274
317
|
|
|
275
|
-
def values(series:list, attr=None, dims:list=None, coords=False) -> Union[dict, tuple]:
|
|
276
|
-
"""Read the values of some or all attributes from a DICOM series
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
series (list or str): DICOM series to read. This can also
|
|
280
|
-
be a path to a folder containing DICOM files, or a
|
|
281
|
-
patient or study to read all series in that patient or
|
|
282
|
-
study. In those cases a list is returned.
|
|
283
|
-
attr (list, optional): list of DICOM attributes to read.
|
|
284
|
-
dims (list, optional): Dimensions to sort the attributes.
|
|
285
|
-
If dims is not provided, values are sorted by
|
|
286
|
-
InstanceNumber.
|
|
287
|
-
coords (bool): If set to True, the coordinates of the
|
|
288
|
-
attributes are returned alongside the values
|
|
289
|
-
|
|
290
|
-
Returns:
|
|
291
|
-
dict or tuple: values as a dictionary in the last
|
|
292
|
-
return value, where each value is a numpy array with
|
|
293
|
-
the required dimensions. If coords is set to True,
|
|
294
|
-
these are returned too.
|
|
295
|
-
"""
|
|
296
|
-
if isinstance(series, str):
|
|
297
|
-
series = [series]
|
|
298
|
-
dbd = open(series[0])
|
|
299
|
-
array = dbd.values(series, attr, dims, coords)
|
|
300
|
-
dbd.close()
|
|
301
|
-
return array
|
|
302
|
-
|
|
303
|
-
|
|
304
318
|
def files(entity:list) -> list:
|
|
305
319
|
"""Read the files in a DICOM entity
|
|
306
320
|
|
|
@@ -7,7 +7,7 @@ import pydicom
|
|
|
7
7
|
|
|
8
8
|
import dbdicom.utils.dcm4che as dcm4che
|
|
9
9
|
import dbdicom.utils.files as filetools
|
|
10
|
-
|
|
10
|
+
from dbdicom.utils.pydicom_dataset import get_values
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
COLUMNS = [
|
|
@@ -41,7 +41,7 @@ def read(path):
|
|
|
41
41
|
if 'TransferSyntaxUID' in ds.file_meta:
|
|
42
42
|
if not 'Rows' in ds: # Image only
|
|
43
43
|
continue
|
|
44
|
-
row =
|
|
44
|
+
row = get_values(ds, tags)
|
|
45
45
|
array.append(row)
|
|
46
46
|
index = os.path.relpath(file, path)
|
|
47
47
|
dicom_files.append(index)
|
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
# https://www.aliza-dicom-viewer.com/download/datasets
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
-
from datetime import datetime
|
|
6
5
|
import struct
|
|
7
6
|
from tqdm import tqdm
|
|
8
7
|
|
|
9
8
|
import numpy as np
|
|
10
9
|
import pydicom
|
|
11
10
|
from pydicom.util.codify import code_file
|
|
11
|
+
from pydicom.tag import Tag
|
|
12
12
|
import pydicom.config
|
|
13
13
|
import vreg
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
from dbdicom.utils.pydicom_dataset import get_values, set_values
|
|
16
16
|
import dbdicom.utils.image as image
|
|
17
|
-
import dbdicom.utils.variables as variables
|
|
18
17
|
from dbdicom.sop_classes import (
|
|
19
18
|
xray_angiographic_image,
|
|
20
19
|
ct_image,
|
|
@@ -50,15 +49,15 @@ SOPCLASSMODULE = {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
def read_dataset(file):
|
|
52
|
+
# def read_dataset(file):
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
# try:
|
|
55
|
+
# ds = pydicom.dcmread(file)
|
|
56
|
+
# # ds = pydicom.dcmread(file, force=True) # more robust but hides corrupted data
|
|
57
|
+
# except Exception:
|
|
58
|
+
# raise FileNotFoundError('File not found')
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
# return ds
|
|
62
61
|
|
|
63
62
|
|
|
64
63
|
def new_dataset(sop_class):
|
|
@@ -81,149 +80,6 @@ def new_dataset(sop_class):
|
|
|
81
80
|
)
|
|
82
81
|
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
def get_values(ds, tags):
|
|
86
|
-
"""Return a list of values for a dataset"""
|
|
87
|
-
|
|
88
|
-
# https://pydicom.github.io/pydicom/stable/guides/element_value_types.html
|
|
89
|
-
if np.isscalar(tags):
|
|
90
|
-
return get_values(ds, [tags])[0]
|
|
91
|
-
|
|
92
|
-
row = []
|
|
93
|
-
for tag in tags:
|
|
94
|
-
value = None
|
|
95
|
-
|
|
96
|
-
# If the tag is provided as string
|
|
97
|
-
if isinstance(tag, str):
|
|
98
|
-
if hasattr(ds, tag):
|
|
99
|
-
pydcm_value = ds[tag].value
|
|
100
|
-
try:
|
|
101
|
-
VR = pydicom.datadict.dictionary_VR(tag)
|
|
102
|
-
except:
|
|
103
|
-
VR = None
|
|
104
|
-
value = to_set_type(pydcm_value, VR) # ELIMINATE THIS STEP - return pydicom datatypes
|
|
105
|
-
|
|
106
|
-
# If the tag is a tuple of hexadecimal values
|
|
107
|
-
else:
|
|
108
|
-
if tag in ds:
|
|
109
|
-
try:
|
|
110
|
-
VR = pydicom.datadict.dictionary_VR(tag)
|
|
111
|
-
except:
|
|
112
|
-
VR = None
|
|
113
|
-
value = to_set_type(ds[tag].value, VR)
|
|
114
|
-
|
|
115
|
-
# If a tag is not present in the dataset, check if it can be derived
|
|
116
|
-
if value is None:
|
|
117
|
-
value = derive_data_element(ds, tag)
|
|
118
|
-
|
|
119
|
-
row.append(value)
|
|
120
|
-
return row
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def set_values(ds, tags, values, VR=None, coords=None):
|
|
124
|
-
|
|
125
|
-
if np.isscalar(tags):
|
|
126
|
-
tags = [tags]
|
|
127
|
-
values = [values]
|
|
128
|
-
VR = [VR]
|
|
129
|
-
elif VR is None:
|
|
130
|
-
VR = [None] * len(tags)
|
|
131
|
-
|
|
132
|
-
if coords is not None:
|
|
133
|
-
tags += list(coords.keys())
|
|
134
|
-
values += list(coords.values())
|
|
135
|
-
|
|
136
|
-
for i, tag in enumerate(tags):
|
|
137
|
-
|
|
138
|
-
if values[i] is None:
|
|
139
|
-
if isinstance(tag, str):
|
|
140
|
-
if hasattr(ds, tag):
|
|
141
|
-
del ds[tag]
|
|
142
|
-
else: # hexadecimal tuple
|
|
143
|
-
if tag in ds:
|
|
144
|
-
del ds[tag]
|
|
145
|
-
|
|
146
|
-
elif isinstance(tag, str):
|
|
147
|
-
if hasattr(ds, tag):
|
|
148
|
-
ds[tag].value = format_value(values[i], tag=tag)
|
|
149
|
-
else:
|
|
150
|
-
_add_new(ds, tag, values[i], VR=VR[i])
|
|
151
|
-
|
|
152
|
-
else: # hexadecimal tuple
|
|
153
|
-
if tag in ds:
|
|
154
|
-
ds[tag].value = format_value(values[i], tag=tag)
|
|
155
|
-
else:
|
|
156
|
-
_add_new(ds, tag, values[i], VR=VR[i])
|
|
157
|
-
|
|
158
|
-
#_set_derived_data_element(ds, tag, values[i])
|
|
159
|
-
|
|
160
|
-
return ds
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def value(ds, tags):
|
|
165
|
-
# Same as get_values but without VR lookup
|
|
166
|
-
|
|
167
|
-
# https://pydicom.github.io/pydicom/stable/guides/element_value_types.html
|
|
168
|
-
if np.isscalar(tags):
|
|
169
|
-
return get_values(ds, [tags])[0]
|
|
170
|
-
|
|
171
|
-
row = []
|
|
172
|
-
for tag in tags:
|
|
173
|
-
value = None
|
|
174
|
-
|
|
175
|
-
# If the tag is provided as string
|
|
176
|
-
if isinstance(tag, str):
|
|
177
|
-
|
|
178
|
-
if hasattr(ds, tag):
|
|
179
|
-
value = to_set_type(ds[tag].value)
|
|
180
|
-
|
|
181
|
-
# If the tag is a tuple of hexadecimal values
|
|
182
|
-
else:
|
|
183
|
-
if tag in ds:
|
|
184
|
-
value = to_set_type(ds[tag].value)
|
|
185
|
-
|
|
186
|
-
# If a tag is not present in the dataset, check if it can be derived
|
|
187
|
-
if value is None:
|
|
188
|
-
value = derive_data_element(ds, tag)
|
|
189
|
-
|
|
190
|
-
row.append(value)
|
|
191
|
-
return row
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def set_value(ds, tags, values):
|
|
195
|
-
# Same as set_values but without VR lookup
|
|
196
|
-
# This excludes new private tags - set those using add_private()
|
|
197
|
-
if np.isscalar(tags):
|
|
198
|
-
tags = [tags]
|
|
199
|
-
values = [values]
|
|
200
|
-
|
|
201
|
-
for i, tag in enumerate(tags):
|
|
202
|
-
|
|
203
|
-
if values[i] is None:
|
|
204
|
-
if isinstance(tag, str):
|
|
205
|
-
if hasattr(ds, tag):
|
|
206
|
-
del ds[tag]
|
|
207
|
-
else: # hexadecimal tuple
|
|
208
|
-
if tag in ds:
|
|
209
|
-
del ds[tag]
|
|
210
|
-
|
|
211
|
-
elif isinstance(tag, str):
|
|
212
|
-
if hasattr(ds, tag):
|
|
213
|
-
ds[tag].value = check_value(values[i], tag)
|
|
214
|
-
else:
|
|
215
|
-
add_new(ds, tag, values[i])
|
|
216
|
-
|
|
217
|
-
else: # hexadecimal tuple
|
|
218
|
-
if tag in ds:
|
|
219
|
-
ds[tag].value = check_value(values[i], tag)
|
|
220
|
-
else:
|
|
221
|
-
add_new(ds, tag, values[i])
|
|
222
|
-
|
|
223
|
-
return ds
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
83
|
def write(ds, file, status=None):
|
|
228
84
|
# check if directory exists and create it if not
|
|
229
85
|
dir = os.path.dirname(file)
|
|
@@ -268,164 +124,12 @@ def read_data(files, tags, path=None, images_only=False): # obsolete??
|
|
|
268
124
|
|
|
269
125
|
|
|
270
126
|
|
|
271
|
-
|
|
272
|
-
def _add_new(ds, tag, value, VR='OW'):
|
|
273
|
-
if not isinstance(tag, pydicom.tag.BaseTag):
|
|
274
|
-
tag = pydicom.tag.Tag(tag)
|
|
275
|
-
if not tag.is_private: # Add a new data element
|
|
276
|
-
value_repr = pydicom.datadict.dictionary_VR(tag)
|
|
277
|
-
if value_repr == 'US or SS':
|
|
278
|
-
if value >= 0:
|
|
279
|
-
value_repr = 'US'
|
|
280
|
-
else:
|
|
281
|
-
value_repr = 'SS'
|
|
282
|
-
elif value_repr == 'OB or OW':
|
|
283
|
-
value_repr = 'OW'
|
|
284
|
-
ds.add_new(tag, value_repr, format_value(value, value_repr))
|
|
285
|
-
else:
|
|
286
|
-
if (tag.group, 0x0010) not in ds:
|
|
287
|
-
ds.private_block(tag.group, 'dbdicom ' + str(tag.group), create=True)
|
|
288
|
-
ds.add_new(tag, VR, format_value(value, VR))
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def add_new(ds, tag, value):
|
|
292
|
-
if not isinstance(tag, pydicom.tag.BaseTag):
|
|
293
|
-
tag = pydicom.tag.Tag(tag)
|
|
294
|
-
if tag.is_private:
|
|
295
|
-
raise ValueError("if you want to add a private data element, use "
|
|
296
|
-
"dataset.add_private()")
|
|
297
|
-
# Add a new data element
|
|
298
|
-
value_repr = pydicom.datadict.dictionary_VR(tag)
|
|
299
|
-
if value_repr == 'US or SS':
|
|
300
|
-
if value >= 0:
|
|
301
|
-
value_repr = 'US'
|
|
302
|
-
else:
|
|
303
|
-
value_repr = 'SS'
|
|
304
|
-
elif value_repr == 'OB or OW':
|
|
305
|
-
value_repr = 'OW'
|
|
306
|
-
ds.add_new(tag, value_repr, format_value(value, value_repr))
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def add_private(ds, tag, value, VR):
|
|
311
|
-
if not isinstance(tag, pydicom.tag.BaseTag):
|
|
312
|
-
tag = pydicom.tag.Tag(tag)
|
|
313
|
-
if (tag.group, 0x0010) not in ds:
|
|
314
|
-
ds.private_block(tag.group, 'dbdicom ' + str(tag.group), create=True)
|
|
315
|
-
ds.add_new(tag, VR, format_value(value, VR))
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def derive_data_element(ds, tag):
|
|
319
|
-
"""Tags that are not required but can be derived from other required tags"""
|
|
320
|
-
|
|
321
|
-
if tag == 'SliceLocation' or tag == (0x0020, 0x1041):
|
|
322
|
-
if 'ImageOrientationPatient' in ds and 'ImagePositionPatient' in ds:
|
|
323
|
-
return image.slice_location(
|
|
324
|
-
ds['ImageOrientationPatient'].value,
|
|
325
|
-
ds['ImagePositionPatient'].value,
|
|
326
|
-
)
|
|
327
|
-
# To be extended ad hoc with other tags that can be derived
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def format_value(value, VR=None, tag=None):
|
|
332
|
-
|
|
333
|
-
# If the change below is made (TM, DA, DT) then this needs to
|
|
334
|
-
# convert those to string before setting
|
|
335
|
-
|
|
336
|
-
# Slow - dictionary lookup for every value write
|
|
337
|
-
|
|
338
|
-
if VR is None:
|
|
339
|
-
VR = pydicom.datadict.dictionary_VR(tag)
|
|
340
|
-
|
|
341
|
-
if VR == 'LO':
|
|
342
|
-
if len(value) > 64:
|
|
343
|
-
return value[-64:]
|
|
344
|
-
#return value[:64]
|
|
345
|
-
if VR == 'TM':
|
|
346
|
-
return variables.seconds_to_str(value)
|
|
347
|
-
if VR == 'DA':
|
|
348
|
-
if not is_valid_dicom_date(value):
|
|
349
|
-
return '99991231'
|
|
127
|
+
# def new_uid(n=None):
|
|
350
128
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def is_valid_dicom_date(da_str: str) -> bool:
|
|
356
|
-
if not isinstance(da_str, str) or len(da_str) != 8 or not da_str.isdigit():
|
|
357
|
-
return False
|
|
358
|
-
try:
|
|
359
|
-
datetime.strptime(da_str, "%Y%m%d")
|
|
360
|
-
return True
|
|
361
|
-
except ValueError:
|
|
362
|
-
return False
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
def check_value(value, tag):
|
|
366
|
-
|
|
367
|
-
# If the change below is made (TM, DA, DT) then this needs to
|
|
368
|
-
# convert those to string before setting
|
|
369
|
-
|
|
370
|
-
LO = [
|
|
371
|
-
'SeriesDescription',
|
|
372
|
-
'StudyDescription',
|
|
373
|
-
]
|
|
374
|
-
TM = [
|
|
375
|
-
'AcquisitionTime',
|
|
376
|
-
]
|
|
377
|
-
|
|
378
|
-
if tag in LO:
|
|
379
|
-
if len(value) > 64:
|
|
380
|
-
return value[-64:]
|
|
381
|
-
if tag in TM:
|
|
382
|
-
return variables.seconds_to_str(value)
|
|
383
|
-
|
|
384
|
-
return value
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
def to_set_type(value, VR=None):
|
|
388
|
-
"""
|
|
389
|
-
Convert pydicom datatypes to the python datatypes used to set the parameter.
|
|
390
|
-
"""
|
|
391
|
-
# Not a good idea to modify pydicom set/get values. confusing and requires extra VR lookups
|
|
392
|
-
|
|
393
|
-
if VR == 'TM':
|
|
394
|
-
# pydicom sometimes returns string values for TM data types
|
|
395
|
-
if isinstance(value, str):
|
|
396
|
-
return variables.str_to_seconds(value)
|
|
397
|
-
|
|
398
|
-
if value.__class__.__name__ == 'MultiValue':
|
|
399
|
-
return [to_set_type(v, VR) for v in value]
|
|
400
|
-
if value.__class__.__name__ == 'PersonName':
|
|
401
|
-
return str(value)
|
|
402
|
-
if value.__class__.__name__ == 'Sequence':
|
|
403
|
-
return [ds for ds in value]
|
|
404
|
-
if value.__class__.__name__ == 'TM':
|
|
405
|
-
return variables.time_to_seconds(value) # return datetime.time
|
|
406
|
-
if value.__class__.__name__ == 'UID':
|
|
407
|
-
return str(value)
|
|
408
|
-
if value.__class__.__name__ == 'IS':
|
|
409
|
-
return int(value)
|
|
410
|
-
if value.__class__.__name__ == 'DT':
|
|
411
|
-
return variables.datetime_to_str(value) # return datetime.datetime
|
|
412
|
-
if value.__class__.__name__ == 'DA': # return datetime.date
|
|
413
|
-
return variables.date_to_str(value)
|
|
414
|
-
if value.__class__.__name__ == 'DSfloat':
|
|
415
|
-
return float(value)
|
|
416
|
-
if value.__class__.__name__ == 'DSdecimal':
|
|
417
|
-
return int(value)
|
|
418
|
-
|
|
419
|
-
return value
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def new_uid(n=None):
|
|
423
|
-
|
|
424
|
-
if n is None:
|
|
425
|
-
return pydicom.uid.generate_uid()
|
|
426
|
-
else:
|
|
427
|
-
return [pydicom.uid.generate_uid() for _ in range(n)]
|
|
428
|
-
|
|
129
|
+
# if n is None:
|
|
130
|
+
# return pydicom.uid.generate_uid()
|
|
131
|
+
# else:
|
|
132
|
+
# return [pydicom.uid.generate_uid() for _ in range(n)]
|
|
429
133
|
|
|
430
134
|
|
|
431
135
|
|
|
@@ -638,6 +342,15 @@ def set_pixel_data(ds, array):
|
|
|
638
342
|
def volume(ds):
|
|
639
343
|
return vreg.volume(pixel_data(ds), affine(ds))
|
|
640
344
|
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def is_valid_dicom_tag(value):
|
|
348
|
+
try:
|
|
349
|
+
tag = Tag(value)
|
|
350
|
+
return pydicom.datadict.dictionary_keyword(tag) != ''
|
|
351
|
+
except Exception:
|
|
352
|
+
return False
|
|
353
|
+
|
|
641
354
|
def set_volume(ds, volume:vreg.Volume3D):
|
|
642
355
|
if volume is None:
|
|
643
356
|
raise ValueError('The volume cannot be set to an empty value.')
|
|
@@ -657,17 +370,18 @@ def set_volume(ds, volume:vreg.Volume3D):
|
|
|
657
370
|
set_affine(ds, volume.affine)
|
|
658
371
|
if volume.coords is not None:
|
|
659
372
|
# All other dimensions should have size 1
|
|
660
|
-
coords =
|
|
373
|
+
coords = [c.reshape(-1) for c in volume.coords]
|
|
661
374
|
for i, d in enumerate(volume.dims):
|
|
662
|
-
|
|
663
|
-
set_values(ds, d, coords[i,0])
|
|
664
|
-
except KeyError:
|
|
375
|
+
if not is_valid_dicom_tag(d):
|
|
665
376
|
raise ValueError(
|
|
666
377
|
"Cannot write volume to DICOM. "
|
|
667
378
|
f"Volume dimension {d} is not a recognized DICOM data-element. "
|
|
668
|
-
f"Use Volume3D.set_dims() with proper DICOM
|
|
669
|
-
"
|
|
379
|
+
f"Use Volume3D.set_dims() with proper DICOM "
|
|
380
|
+
"tags to change the dimensions."
|
|
670
381
|
)
|
|
382
|
+
else:
|
|
383
|
+
set_values(ds, d, coords[i][0])
|
|
384
|
+
|
|
671
385
|
|
|
672
386
|
|
|
673
387
|
def image_type(ds):
|