dbdicom 0.2.0__py3-none-any.whl → 0.3.16__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.
- dbdicom/__init__.py +3 -25
- dbdicom/api.py +496 -0
- dbdicom/const.py +144 -0
- dbdicom/database.py +133 -0
- dbdicom/dataset.py +471 -0
- dbdicom/dbd.py +1290 -0
- dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/bin/emf2sf +57 -57
- dbdicom/register.py +402 -0
- dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
- dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +206 -160
- dbdicom/sop_classes/mr_image.py +338 -0
- dbdicom/sop_classes/parametric_map.py +381 -0
- dbdicom/sop_classes/secondary_capture.py +140 -0
- dbdicom/sop_classes/segmentation.py +311 -0
- dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
- dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
- dbdicom/utils/arrays.py +142 -0
- dbdicom/utils/files.py +0 -20
- dbdicom/utils/image.py +43 -466
- dbdicom/utils/pydicom_dataset.py +386 -0
- dbdicom-0.3.16.dist-info/METADATA +26 -0
- dbdicom-0.3.16.dist-info/RECORD +54 -0
- {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/WHEEL +1 -1
- dbdicom/create.py +0 -450
- dbdicom/ds/__init__.py +0 -10
- dbdicom/ds/create.py +0 -63
- dbdicom/ds/dataset.py +0 -841
- dbdicom/ds/dictionaries.py +0 -620
- dbdicom/ds/types/mr_image.py +0 -267
- dbdicom/ds/types/parametric_map.py +0 -226
- dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
- dbdicom/manager.py +0 -2077
- dbdicom/message.py +0 -119
- dbdicom/record.py +0 -1526
- dbdicom/types/database.py +0 -107
- dbdicom/types/instance.py +0 -184
- dbdicom/types/patient.py +0 -40
- dbdicom/types/series.py +0 -816
- dbdicom/types/study.py +0 -58
- dbdicom/utils/variables.py +0 -155
- dbdicom/utils/vreg.py +0 -2626
- dbdicom/wrappers/__init__.py +0 -7
- dbdicom/wrappers/dipy.py +0 -462
- dbdicom/wrappers/elastix.py +0 -855
- dbdicom/wrappers/numpy.py +0 -119
- dbdicom/wrappers/scipy.py +0 -1413
- dbdicom/wrappers/skimage.py +0 -1030
- dbdicom/wrappers/sklearn.py +0 -151
- dbdicom/wrappers/vreg.py +0 -273
- dbdicom-0.2.0.dist-info/METADATA +0 -276
- dbdicom-0.2.0.dist-info/RECORD +0 -81
- {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info/licenses}/LICENSE +0 -0
- {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/top_level.txt +0 -0
dbdicom/record.py
DELETED
|
@@ -1,1526 +0,0 @@
|
|
|
1
|
-
# Importing annotations to handle or sign in import type hints
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
# Import packages
|
|
5
|
-
import numpy as np
|
|
6
|
-
import pandas as pd
|
|
7
|
-
import dbdicom.ds.dataset as dbdataset
|
|
8
|
-
from dbdicom.utils.files import export_path
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Record():
|
|
13
|
-
|
|
14
|
-
name = 'Record'
|
|
15
|
-
|
|
16
|
-
def __init__(self, create, manager, uid='Database', key=None, **kwargs):
|
|
17
|
-
|
|
18
|
-
self._key = key
|
|
19
|
-
self._mute = False
|
|
20
|
-
self.uid = uid
|
|
21
|
-
self.attributes = kwargs
|
|
22
|
-
self.manager = manager
|
|
23
|
-
self.new = create
|
|
24
|
-
|
|
25
|
-
def __eq__(self, other):
|
|
26
|
-
if other is None:
|
|
27
|
-
return False
|
|
28
|
-
return self.uid == other.uid
|
|
29
|
-
|
|
30
|
-
def __getattr__(self, attribute):
|
|
31
|
-
return self.get_values(attribute)
|
|
32
|
-
|
|
33
|
-
def __getitem__(self, attributes):
|
|
34
|
-
return self.get_values(attributes)
|
|
35
|
-
|
|
36
|
-
def __setattr__(self, attribute, value):
|
|
37
|
-
if attribute in ['_key','_mute', 'uid', 'manager', 'attributes', 'new']:
|
|
38
|
-
self.__dict__[attribute] = value
|
|
39
|
-
else:
|
|
40
|
-
self.set_values([attribute], [value])
|
|
41
|
-
|
|
42
|
-
def __setitem__(self, attributes, values):
|
|
43
|
-
self.set_values(attributes, values)
|
|
44
|
-
|
|
45
|
-
def loc(self):
|
|
46
|
-
return self.manager._loc(self.name, self.uid)
|
|
47
|
-
# df = self.manager.register
|
|
48
|
-
# return (df.removed==False) & (df[self.name]==self.uid)
|
|
49
|
-
|
|
50
|
-
def keys(self):
|
|
51
|
-
loc = self.loc()
|
|
52
|
-
keys = self.manager._keys(loc)
|
|
53
|
-
# keys = self.manager.register.index[self.loc()]
|
|
54
|
-
if len(keys) == 0:
|
|
55
|
-
if self.name == 'Database':
|
|
56
|
-
return keys
|
|
57
|
-
else:
|
|
58
|
-
raise Exception("This record has no data")
|
|
59
|
-
else:
|
|
60
|
-
self._key = keys[0]
|
|
61
|
-
return keys
|
|
62
|
-
|
|
63
|
-
def _set_key(self):
|
|
64
|
-
loc = self.loc()
|
|
65
|
-
all_keys = self.manager._keys(loc)
|
|
66
|
-
if len(all_keys) == 0:
|
|
67
|
-
msg = 'This record has been removed from the database and can no longer be accessed.'
|
|
68
|
-
raise ValueError(msg)
|
|
69
|
-
self._key = all_keys[0]
|
|
70
|
-
|
|
71
|
-
def key(self):
|
|
72
|
-
try:
|
|
73
|
-
key_removed = self.manager._at(self._key, 'removed')
|
|
74
|
-
except:
|
|
75
|
-
self._set_key()
|
|
76
|
-
else:
|
|
77
|
-
if key_removed:
|
|
78
|
-
self._set_key()
|
|
79
|
-
return self._key
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
def status(self):
|
|
83
|
-
return self.manager.status
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def dialog(self):
|
|
87
|
-
return self.manager.dialog
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Properties
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def print(self):
|
|
95
|
-
"""Print a summary of the record and its contents.
|
|
96
|
-
|
|
97
|
-
See Also:
|
|
98
|
-
:func:`~path`
|
|
99
|
-
|
|
100
|
-
Example:
|
|
101
|
-
Print a summary of a database:
|
|
102
|
-
|
|
103
|
-
>>> database = db.database_hollywood()
|
|
104
|
-
>>> database.print()
|
|
105
|
-
---------- DATABASE --------------
|
|
106
|
-
Location: In memory
|
|
107
|
-
Patient James Bond
|
|
108
|
-
Study MRI [19821201]
|
|
109
|
-
Series 001 [Localizer]
|
|
110
|
-
Nr of instances: 0
|
|
111
|
-
Series 002 [T2w]
|
|
112
|
-
Nr of instances: 0
|
|
113
|
-
Study Xray [19821205]
|
|
114
|
-
Series 001 [Chest]
|
|
115
|
-
Nr of instances: 0
|
|
116
|
-
Series 002 [Head]
|
|
117
|
-
Nr of instances: 0
|
|
118
|
-
Patient Scarface
|
|
119
|
-
Study MRI [19850105]
|
|
120
|
-
Series 001 [Localizer]
|
|
121
|
-
Nr of instances: 0
|
|
122
|
-
Series 002 [T2w]
|
|
123
|
-
Nr of instances: 0
|
|
124
|
-
Study Xray [19850106]
|
|
125
|
-
Series 001 [Chest]
|
|
126
|
-
Nr of instances: 0
|
|
127
|
-
Series 002 [Head]
|
|
128
|
-
Nr of instances: 0
|
|
129
|
-
----------------------------------
|
|
130
|
-
|
|
131
|
-
Or print a summary of any record in the hierarchy:
|
|
132
|
-
|
|
133
|
-
>>> patients = database.patients(PatientName='Scarface')
|
|
134
|
-
>>> patients[0].print()
|
|
135
|
-
---------- PATIENT -------------
|
|
136
|
-
Patient Scarface
|
|
137
|
-
Study MRI [19850105]
|
|
138
|
-
Series 001 [Localizer]
|
|
139
|
-
Nr of instances: 0
|
|
140
|
-
Series 002 [T2w]
|
|
141
|
-
Nr of instances: 0
|
|
142
|
-
Study Xray [19850106]
|
|
143
|
-
Series 001 [Chest]
|
|
144
|
-
Nr of instances: 0
|
|
145
|
-
Series 002 [Head]
|
|
146
|
-
Nr of instances: 0
|
|
147
|
-
--------------------------------
|
|
148
|
-
"""
|
|
149
|
-
self.manager.print(self.uid, self.name)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def path(self) -> str:
|
|
153
|
-
"""Directory of the DICOM database
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
str: full path to the directory
|
|
157
|
-
|
|
158
|
-
See Also:
|
|
159
|
-
:func:`~print`
|
|
160
|
-
|
|
161
|
-
Example:
|
|
162
|
-
Create a new database in memory:
|
|
163
|
-
|
|
164
|
-
>>> database = db.database()
|
|
165
|
-
>>> print(database.path())
|
|
166
|
-
None
|
|
167
|
-
|
|
168
|
-
Open an existing DICOM database:
|
|
169
|
-
|
|
170
|
-
>>> database = db.database('path\\to\\DICOM\\database')
|
|
171
|
-
>>> print(database.path())
|
|
172
|
-
path\to\DICOM\database
|
|
173
|
-
"""
|
|
174
|
-
return self.manager.path
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def empty(self)->bool:
|
|
178
|
-
"""Check if the record has data.
|
|
179
|
-
|
|
180
|
-
Returns:
|
|
181
|
-
bool: False if the record has data, True if not
|
|
182
|
-
|
|
183
|
-
See Also:
|
|
184
|
-
:func:`~print`
|
|
185
|
-
:func:`~path`
|
|
186
|
-
|
|
187
|
-
Example:
|
|
188
|
-
|
|
189
|
-
Check if a database on disk is empty:
|
|
190
|
-
|
|
191
|
-
>>> database = db.database('path\\to\\database')
|
|
192
|
-
>>> print(database.empty)
|
|
193
|
-
False
|
|
194
|
-
|
|
195
|
-
Create a new database from scratch and verify that it is empty:
|
|
196
|
-
|
|
197
|
-
>>> database = db.database()
|
|
198
|
-
>>> print(database.empty())
|
|
199
|
-
True
|
|
200
|
-
|
|
201
|
-
Creating a new series in the database, and verify that it is no longer empty:
|
|
202
|
-
|
|
203
|
-
>>> series = database.new_series()
|
|
204
|
-
>>> print(database.empty())
|
|
205
|
-
False
|
|
206
|
-
|
|
207
|
-
Verify that the new series is empty:
|
|
208
|
-
|
|
209
|
-
>>> print(series.empty())
|
|
210
|
-
True
|
|
211
|
-
|
|
212
|
-
Populate the series with a numpy array and verify that it is now no longer empty:
|
|
213
|
-
|
|
214
|
-
>>> zeros = np.zeros((3, 2, 128, 128))
|
|
215
|
-
>>> series.set_ndarray(zeros)
|
|
216
|
-
>>> print(series.empty())
|
|
217
|
-
False
|
|
218
|
-
"""
|
|
219
|
-
if self.manager.register.empty:
|
|
220
|
-
return True
|
|
221
|
-
return self.children() == []
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def files(self) -> list:
|
|
225
|
-
"""Return a list of all DICOM files saved in the database
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
list: A list of absolute filepaths to valid DICOM files
|
|
229
|
-
|
|
230
|
-
See Also:
|
|
231
|
-
:func:`~print`
|
|
232
|
-
:func:`~path`
|
|
233
|
-
:func:`~empty`
|
|
234
|
-
|
|
235
|
-
Example:
|
|
236
|
-
|
|
237
|
-
A new database in memory has no files on disk:
|
|
238
|
-
|
|
239
|
-
>>> database = db.database()
|
|
240
|
-
>>> print(database.files())
|
|
241
|
-
[]
|
|
242
|
-
|
|
243
|
-
If a series is created in memory, there are no files on disk:
|
|
244
|
-
|
|
245
|
-
>>> series = db.zeros((3,128,128))
|
|
246
|
-
>>> print(series.files())
|
|
247
|
-
[]
|
|
248
|
-
|
|
249
|
-
If a series is created in memory, then written to disk, there are files associated. Since the default format is single-frame MRImage, there are 3 files in this case:
|
|
250
|
-
|
|
251
|
-
>>> series.write('path\\to\\DICOM\\database')
|
|
252
|
-
>>> print(series.files())
|
|
253
|
-
['path\\to\\DICOM\\database\\dbdicom\\1.2.826.0.1.3680043.8.498.10200622747714198480020099226433338888.dcm', 'path\\to\\DICOM\\database\\dbdicom\\1.2.826.0.1.3680043.8.498.95074529334441498207488699470663781148.dcm', 'path\\to\\DICOM\\database\\dbdicom\\1.2.826.0.1.3680043.8.498.30452523525370800574103459899629273584.dcm']
|
|
254
|
-
"""
|
|
255
|
-
files = [self.manager.filepath(key) for key in self.keys()]
|
|
256
|
-
files = [f for f in files if f is not None] # Added 29/05/23 - check if this creates issues
|
|
257
|
-
return files
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def label(self)->str:
|
|
261
|
-
"""Return a human-readable label describing the record.
|
|
262
|
-
|
|
263
|
-
Returns:
|
|
264
|
-
str: label with descriptive information.
|
|
265
|
-
|
|
266
|
-
See Also:
|
|
267
|
-
:func:`~print`
|
|
268
|
-
|
|
269
|
-
Example:
|
|
270
|
-
Print the label of a default series:
|
|
271
|
-
|
|
272
|
-
>>> series = db.zeros((3,128,128), SeriesDescription='Empty demo')
|
|
273
|
-
>>> print(series.label())
|
|
274
|
-
Series 001 [Empty demo]
|
|
275
|
-
"""
|
|
276
|
-
return self.manager.label(self.uid, key=self.key(), type=self.__class__.__name__)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
# Navigating the tree
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def parent(self):
|
|
284
|
-
"""Return the parent of the record.
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
Record: The parent object.
|
|
288
|
-
|
|
289
|
-
See Also:
|
|
290
|
-
:func:`~children`
|
|
291
|
-
:func:`~siblings`
|
|
292
|
-
:func:`~series`
|
|
293
|
-
:func:`~studies`
|
|
294
|
-
:func:`~patients`
|
|
295
|
-
:func:`~database`
|
|
296
|
-
|
|
297
|
-
Example:
|
|
298
|
-
Find the parent of a study:
|
|
299
|
-
|
|
300
|
-
>>> study = db.study()
|
|
301
|
-
>>> patient = study.parent()
|
|
302
|
-
>>> print(patient.PatientName)
|
|
303
|
-
New Patient
|
|
304
|
-
"""
|
|
305
|
-
# Note this function is reimplemented in all subclasses.
|
|
306
|
-
# It is included in the Record class only for documentation purposes.
|
|
307
|
-
return None
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def children(self, **kwargs)->list:
|
|
311
|
-
"""Return all children of the record.
|
|
312
|
-
|
|
313
|
-
Args:
|
|
314
|
-
kwargs: Provide any number of valid DICOM (tag, value) pair as keywords to filter the list.
|
|
315
|
-
|
|
316
|
-
Returns:
|
|
317
|
-
list: A list of all children.
|
|
318
|
-
|
|
319
|
-
See Also:
|
|
320
|
-
:func:`~parent`
|
|
321
|
-
:func:`~siblings`
|
|
322
|
-
:func:`~series`
|
|
323
|
-
:func:`~studies`
|
|
324
|
-
:func:`~patients`
|
|
325
|
-
:func:`~database`
|
|
326
|
-
|
|
327
|
-
Example:
|
|
328
|
-
Find the patients of a given database:
|
|
329
|
-
|
|
330
|
-
>>> database = db.database_hollywood()
|
|
331
|
-
>>> patients = database.children()
|
|
332
|
-
>>> print([p.PatientName for p in patients])
|
|
333
|
-
['James Bond', 'Scarface']
|
|
334
|
-
|
|
335
|
-
Find all patients with a given name:
|
|
336
|
-
|
|
337
|
-
>>> patients = database.children(PatientName='James Bond')
|
|
338
|
-
>>> print([p.PatientName for p in patients])
|
|
339
|
-
['James Bond']
|
|
340
|
-
|
|
341
|
-
Find the studies that have been performed on a given patient:
|
|
342
|
-
>>> studies = patients[0].children()
|
|
343
|
-
>>> print([s.StudyDescription for s in studies])
|
|
344
|
-
['MRI', 'Xray']
|
|
345
|
-
"""
|
|
346
|
-
# Note this function is reimplemented in all subclasses.
|
|
347
|
-
# It is included in the Record class for documentation purposes.
|
|
348
|
-
return []
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
def siblings(self, **kwargs)->list:
|
|
352
|
-
"""Return all siblings of the record.
|
|
353
|
-
|
|
354
|
-
Args:
|
|
355
|
-
kwargs: Provide any number of valid DICOM (tag, value) pair as keywords to filter the list.
|
|
356
|
-
|
|
357
|
-
Returns:
|
|
358
|
-
list: A list of all siblings.
|
|
359
|
-
|
|
360
|
-
See Also:
|
|
361
|
-
:func:`~parent`
|
|
362
|
-
:func:`~children`
|
|
363
|
-
:func:`~series`
|
|
364
|
-
:func:`~studies`
|
|
365
|
-
:func:`~patients`
|
|
366
|
-
:func:`~database`
|
|
367
|
-
|
|
368
|
-
Example:
|
|
369
|
-
Retrieve a study from a database, and find all other studies performed on the same patient:
|
|
370
|
-
|
|
371
|
-
>>> database = db.database_hollywood()
|
|
372
|
-
>>> study = database.studies()[0]
|
|
373
|
-
>>> print([s.StudyDescription for s in study.siblings()])
|
|
374
|
-
['Xray']
|
|
375
|
-
"""
|
|
376
|
-
siblings = self.parent().children(**kwargs)
|
|
377
|
-
siblings.remove(self)
|
|
378
|
-
return siblings
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def series(self, sort=True, sortby=['PatientName', 'StudyDescription', 'SeriesNumber'], **kwargs)->list:
|
|
382
|
-
"""Return a list of series under the record.
|
|
383
|
-
|
|
384
|
-
If the record is a study, this returns the record's children. If it is a patient, this returns a list the record's grand children.
|
|
385
|
-
|
|
386
|
-
Args:
|
|
387
|
-
sort (bool, optional): Set to False to return an unsorted list (faster). Defaults to True.
|
|
388
|
-
sortby (list, optional): list of DICOM keywords to sort the result. This argument is ignored if sort=False. Defaults to ['PatientName', 'StudyDescription', 'SeriesNumber'].
|
|
389
|
-
kwargs (keyword arguments, optional): Set any number of valid DICOM (tag, value) pairs as keywords to filer the list. The result will only contain series with the appropriate values
|
|
390
|
-
|
|
391
|
-
Returns:
|
|
392
|
-
list: A list of dbdicom Series objects.
|
|
393
|
-
|
|
394
|
-
See Also:
|
|
395
|
-
:func:`~parent`
|
|
396
|
-
:func:`~children`
|
|
397
|
-
:func:`~siblings`
|
|
398
|
-
:func:`~studies`
|
|
399
|
-
:func:`~patients`
|
|
400
|
-
:func:`~database`
|
|
401
|
-
|
|
402
|
-
Example:
|
|
403
|
-
Find all series in a database, and print their labels:
|
|
404
|
-
|
|
405
|
-
>>> database = db.database_hollywood()
|
|
406
|
-
>>> series_list = database.series()
|
|
407
|
-
>>> print([s.label() for s in series_list])
|
|
408
|
-
['Series 001 [Localizer]', 'Series 002 [T2w]', 'Series 001 [Chest]', 'Series 002 [Head]', 'Series 001 [Localizer]', 'Series 002 [T2w]', 'Series 001 [Chest]', 'Series 002 [Head]']
|
|
409
|
-
|
|
410
|
-
Find all series with a given SeriesDescription:
|
|
411
|
-
|
|
412
|
-
>>> series_list = database.series(SeriesDescription='Chest')
|
|
413
|
-
>>> print([s.label() for s in series_list])
|
|
414
|
-
['Series 001 [Chest]', 'Series 001 [Chest]']
|
|
415
|
-
|
|
416
|
-
Find all series with a given SeriesDescription of a given Patient:
|
|
417
|
-
|
|
418
|
-
>>> series_list = database.series(SeriesDescription='Chest', PatientName='James Bond')
|
|
419
|
-
>>> print([s.label() for s in series_list])
|
|
420
|
-
['Series 001 [Chest]']
|
|
421
|
-
"""
|
|
422
|
-
series = self.manager.series(keys=self.keys(), sort=sort, sortby=sortby, **kwargs)
|
|
423
|
-
return [self.record('Series', uid) for uid in series]
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
def studies(self, sort=True, sortby=['PatientName', 'StudyDescription'], **kwargs)->list:
|
|
427
|
-
"""Return a list of studies under the record.
|
|
428
|
-
|
|
429
|
-
If the record is a patient, this returns the record's children. If it is a series, this returns the parent study.
|
|
430
|
-
|
|
431
|
-
Args:
|
|
432
|
-
sort (bool, optional): Set to False to return an unsorted list (faster). Defaults to True.
|
|
433
|
-
sortby (list, optional): list of DICOM keywords to sort the result. This argument is ignored if sort=False. Defaults to ['PatientName', 'StudyDescription'].
|
|
434
|
-
kwargs (keyword arguments, optional): Set any number of valid DICOM (tag, value) pairs as keywords to filer the list. The result will only contain studies with the appropriate values.
|
|
435
|
-
|
|
436
|
-
Returns:
|
|
437
|
-
list: A list of dbdicom Study objects.
|
|
438
|
-
|
|
439
|
-
See Also:
|
|
440
|
-
:func:`~parent`
|
|
441
|
-
:func:`~children`
|
|
442
|
-
:func:`~siblings`
|
|
443
|
-
:func:`~series`
|
|
444
|
-
:func:`~patients`
|
|
445
|
-
:func:`~database`
|
|
446
|
-
|
|
447
|
-
Example:
|
|
448
|
-
Find all studies in a database:
|
|
449
|
-
|
|
450
|
-
>>> database = db.database_hollywood()
|
|
451
|
-
>>> studies_list = database.studies()
|
|
452
|
-
>>> print([s.label() for s in studies_list])
|
|
453
|
-
['Study MRI [19821201]', 'Study Xray [19821205]', 'Study MRI [19850105]', 'Study Xray [19850106]']
|
|
454
|
-
|
|
455
|
-
Find all studies of a given Patient:
|
|
456
|
-
|
|
457
|
-
>>> studies_list = database.studies(PatientName='James Bond')
|
|
458
|
-
>>> print([s.label() for s in studies_list])
|
|
459
|
-
['Study MRI [19821201]', 'Study Xray [19821205]']
|
|
460
|
-
"""
|
|
461
|
-
studies = self.manager.studies(keys=self.keys(), sort=sort, sortby=sortby, **kwargs)
|
|
462
|
-
return [self.record('Study', uid) for uid in studies]
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
def patients(self, sort=True, sortby=['PatientName'], **kwargs)->list:
|
|
466
|
-
"""Return a list of patients under the record.
|
|
467
|
-
|
|
468
|
-
If the record is a database, this returns the children. If it is a series or a study, this returns the parent patient.
|
|
469
|
-
|
|
470
|
-
Args:
|
|
471
|
-
sort (bool, optional): Set to False to return an unsorted list (faster). Defaults to True.
|
|
472
|
-
sortby (list, optional): list of DICOM keywords to sort the result. This argument is ignored if sort=False. Defaults to ['PatientName'].
|
|
473
|
-
kwargs (keyword arguments, optional): Set any number of valid DICOM (tag, value) pairs as keywords to filer the list. The result will only contain patients with the appropriate values.
|
|
474
|
-
|
|
475
|
-
Returns:
|
|
476
|
-
list: A list of dbdicom Patient objects.
|
|
477
|
-
|
|
478
|
-
See Also:
|
|
479
|
-
:func:`~parent`
|
|
480
|
-
:func:`~children`
|
|
481
|
-
:func:`~siblings`
|
|
482
|
-
:func:`~series`
|
|
483
|
-
:func:`~studies`
|
|
484
|
-
:func:`~database`
|
|
485
|
-
|
|
486
|
-
Example:
|
|
487
|
-
Find all patients in a database:
|
|
488
|
-
|
|
489
|
-
>>> database = db.database_hollywood()
|
|
490
|
-
>>> patients_list = database.patients()
|
|
491
|
-
>>> print([s.label() for s in patients_list])
|
|
492
|
-
['Patient James Bond', 'Patient Scarface']
|
|
493
|
-
|
|
494
|
-
Find all patients with a given name:
|
|
495
|
-
|
|
496
|
-
>>> patients_list = database.patients(PatientName='James Bond')
|
|
497
|
-
>>> print([s.label() for s in patients_list])
|
|
498
|
-
['Patient James Bond']
|
|
499
|
-
"""
|
|
500
|
-
patients = self.manager.patients(keys=self.keys(), sort=sort, sortby=sortby, **kwargs)
|
|
501
|
-
return [self.record('Patient', uid) for uid in patients]
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
def database(self):
|
|
505
|
-
"""Return the database of the record.
|
|
506
|
-
|
|
507
|
-
Returns:
|
|
508
|
-
Database: Database of the record
|
|
509
|
-
|
|
510
|
-
See Also:
|
|
511
|
-
:func:`~parent`
|
|
512
|
-
:func:`~children`
|
|
513
|
-
:func:`~siblings`
|
|
514
|
-
:func:`~series`
|
|
515
|
-
:func:`~studies`
|
|
516
|
-
|
|
517
|
-
Example:
|
|
518
|
-
Get the database of a study:
|
|
519
|
-
|
|
520
|
-
>>> study = db.study()
|
|
521
|
-
>>> database = study.database()
|
|
522
|
-
>>> print(database.label())
|
|
523
|
-
Database [in memory]
|
|
524
|
-
"""
|
|
525
|
-
return self.record('Database')
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
# Edit a record
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def new_patient(self, **kwargs):
|
|
532
|
-
"""Create a new patient.
|
|
533
|
-
|
|
534
|
-
Args:
|
|
535
|
-
kwargs (optional): Any valid DICOM (tag, value) pair can be assigned up front as properties of the new patient.
|
|
536
|
-
|
|
537
|
-
Returns:
|
|
538
|
-
Patient: instance of the new patient
|
|
539
|
-
|
|
540
|
-
See Also:
|
|
541
|
-
:func:`~new_study`
|
|
542
|
-
:func:`~new_series`
|
|
543
|
-
:func:`~new_pibling`
|
|
544
|
-
|
|
545
|
-
Example:
|
|
546
|
-
Create a new patient in a database:
|
|
547
|
-
|
|
548
|
-
>>> database = db.database()
|
|
549
|
-
>>> database.print()
|
|
550
|
-
---------- DATABASE --------------
|
|
551
|
-
Location: In memory
|
|
552
|
-
----------------------------------
|
|
553
|
-
|
|
554
|
-
>>> nemo = database.new_patient(PatientName='Nemo')
|
|
555
|
-
>>> dory = database.new_patient(PatientName='Dory')
|
|
556
|
-
>>> database.print()
|
|
557
|
-
---------- DATABASE --------------
|
|
558
|
-
Location: In memory
|
|
559
|
-
Patient Dory
|
|
560
|
-
Patient Nemo
|
|
561
|
-
----------------------------------
|
|
562
|
-
|
|
563
|
-
A lower-level record can also create a new patient. Create a new series and show its default database:
|
|
564
|
-
|
|
565
|
-
>>> series = db.series()
|
|
566
|
-
>>> series.database().print()
|
|
567
|
-
---------- DATABASE --------------
|
|
568
|
-
Location: In memory
|
|
569
|
-
Patient New Patient
|
|
570
|
-
Study New Study [None]
|
|
571
|
-
Series 001 [New Series]
|
|
572
|
-
Nr of instances: 0
|
|
573
|
-
----------------------------------
|
|
574
|
-
|
|
575
|
-
The series can create new patients in its database directly:
|
|
576
|
-
|
|
577
|
-
>>> dory = series.new_patient(PatientName='Dory')
|
|
578
|
-
>>> nemo = series.new_patient(PatientName='Nemo')
|
|
579
|
-
>>> series.print()
|
|
580
|
-
---------- DATABASE --------------
|
|
581
|
-
Location: In memory
|
|
582
|
-
Patient Dory
|
|
583
|
-
Patient Nemo
|
|
584
|
-
Patient New Patient
|
|
585
|
-
Study New Study [None]
|
|
586
|
-
Series 001 [New Series]
|
|
587
|
-
Nr of instances: 0
|
|
588
|
-
----------------------------------
|
|
589
|
-
"""
|
|
590
|
-
attr = {**kwargs, **self.attributes}
|
|
591
|
-
uid, key = self.manager.new_patient(parent=self.uid, **attr)
|
|
592
|
-
return self.record('Patient', uid, key, **attr)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
def new_study(self, **kwargs):
|
|
596
|
-
"""Create a new study.
|
|
597
|
-
|
|
598
|
-
Args:
|
|
599
|
-
kwargs (optional): Any valid DICOM (tag, value) pair can be assigned up front as properties of the new study.
|
|
600
|
-
|
|
601
|
-
Returns:
|
|
602
|
-
Study: instance of the new study
|
|
603
|
-
|
|
604
|
-
See Also:
|
|
605
|
-
:func:`~new_patient`
|
|
606
|
-
:func:`~new_series`
|
|
607
|
-
:func:`~new_pibling`
|
|
608
|
-
|
|
609
|
-
Example:
|
|
610
|
-
Create a new study in a patient:
|
|
611
|
-
|
|
612
|
-
>>> dory = db.patient(PatientName='Dory')
|
|
613
|
-
>>> dory.print()
|
|
614
|
-
---------- PATIENT -------------
|
|
615
|
-
Patient Dory
|
|
616
|
-
--------------------------------
|
|
617
|
-
|
|
618
|
-
>>> fMRI = dory.new_study(StudyDescription='fMRI', StudyDate='20091001')
|
|
619
|
-
>>> CThead = dory.new_study(StudyDescription='CT head', StudyDate='20091002')
|
|
620
|
-
>>> dory.print()
|
|
621
|
-
---------- PATIENT -------------
|
|
622
|
-
Patient Dory
|
|
623
|
-
Study CT head [20091002]
|
|
624
|
-
Study fMRI [20091001]
|
|
625
|
-
--------------------------------
|
|
626
|
-
|
|
627
|
-
Any other record can also create a new study. Missing intermediate generations are created automatically:
|
|
628
|
-
|
|
629
|
-
>>> database = db.database()
|
|
630
|
-
>>> database.print()
|
|
631
|
-
---------- DATABASE --------------
|
|
632
|
-
Location: In memory
|
|
633
|
-
----------------------------------
|
|
634
|
-
|
|
635
|
-
>>> fMRI = database.new_study(StudyDescription='fMRI')
|
|
636
|
-
>>> database.print()
|
|
637
|
-
---------- DATABASE --------------
|
|
638
|
-
Location: In memory
|
|
639
|
-
Patient New Patient
|
|
640
|
-
Study fMRI [None]
|
|
641
|
-
----------------------------------
|
|
642
|
-
"""
|
|
643
|
-
attr = {**kwargs, **self.attributes}
|
|
644
|
-
uid, key = self.manager.new_study(parent=self.uid, key=self.key(),**attr)
|
|
645
|
-
return self.record('Study', uid, key, **attr)
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
def new_series(self, **kwargs):
|
|
649
|
-
"""Create a new series.
|
|
650
|
-
|
|
651
|
-
Args:
|
|
652
|
-
kwargs (optional): Any valid DICOM (tag, value) pair can be assigned up front as properties of the new series.
|
|
653
|
-
|
|
654
|
-
Returns:
|
|
655
|
-
Series: instance of the new series
|
|
656
|
-
|
|
657
|
-
See Also:
|
|
658
|
-
:func:`~new_patient`
|
|
659
|
-
:func:`~new_study`
|
|
660
|
-
:func:`~new_pibling`
|
|
661
|
-
|
|
662
|
-
Example:
|
|
663
|
-
Consider an empty study:
|
|
664
|
-
|
|
665
|
-
>>> fMRI = db.study(StudyDescription='fMRI', StudyDate='20230203')
|
|
666
|
-
>>> fMRI.print()
|
|
667
|
-
---------- STUDY ---------------
|
|
668
|
-
Study fMRI [20230203]
|
|
669
|
-
--------------------------------
|
|
670
|
-
|
|
671
|
-
Create two new series in the study:
|
|
672
|
-
|
|
673
|
-
>>> rstate = fMRI.new_series(SeriesDescription='Resting state')
|
|
674
|
-
>>> ftap = fMRI.new_series(SeriesDescription='Finger tap')
|
|
675
|
-
>>> fMRI.print()
|
|
676
|
-
---------- STUDY ---------------
|
|
677
|
-
Study fMRI [20230203]
|
|
678
|
-
Series 001 [Resting state]
|
|
679
|
-
Nr of instances: 0
|
|
680
|
-
Series 002 [Finger tap]
|
|
681
|
-
Nr of instances: 0
|
|
682
|
-
--------------------------------
|
|
683
|
-
|
|
684
|
-
Any other record can also create a new series. Missing intermediate generations are created automatically:
|
|
685
|
-
|
|
686
|
-
>>> database = db.database()
|
|
687
|
-
>>> database.print()
|
|
688
|
-
---------- DATABASE --------------
|
|
689
|
-
Location: In memory
|
|
690
|
-
----------------------------------
|
|
691
|
-
|
|
692
|
-
>>> rstate = database.new_series(SeriesDescription='Resting state')
|
|
693
|
-
>>> ftap = database.new_series(SeriesDescription='Finger tap')
|
|
694
|
-
>>> database.print()
|
|
695
|
-
---------- DATABASE --------------
|
|
696
|
-
Location: In memory
|
|
697
|
-
Patient New Patient
|
|
698
|
-
Study New Study [None]
|
|
699
|
-
Series 001 [Resting state]
|
|
700
|
-
Nr of instances: 0
|
|
701
|
-
Patient New Patient
|
|
702
|
-
Study New Study [None]
|
|
703
|
-
Series 001 [Finger tap]
|
|
704
|
-
Nr of instances: 0
|
|
705
|
-
----------------------------------
|
|
706
|
-
|
|
707
|
-
Note since any missing levels in the hierarchy are automatically created, these new series now end up in different patients.
|
|
708
|
-
|
|
709
|
-
"""
|
|
710
|
-
attr = {**kwargs, **self.attributes}
|
|
711
|
-
uid, key = self.manager.new_series(parent=self.uid, **attr)
|
|
712
|
-
return self.record('Series', uid, key, **attr)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
def new_child(self, **kwargs):
|
|
716
|
-
"""Create a new child of the record.
|
|
717
|
-
|
|
718
|
-
Args:
|
|
719
|
-
kwargs: Any valid DICOM (tag, value) pair to assign to the new sibling.
|
|
720
|
-
|
|
721
|
-
See Also:
|
|
722
|
-
:func:`~new_patient`
|
|
723
|
-
:func:`~new_study`
|
|
724
|
-
:func:`~new_series`
|
|
725
|
-
:func:`~new_sibling`
|
|
726
|
-
:func:`~new_pibling`
|
|
727
|
-
|
|
728
|
-
Example:
|
|
729
|
-
Consider an empty study:
|
|
730
|
-
|
|
731
|
-
>>> fMRI = db.study(StudyDescription='fMRI', StudyDate='20230203')
|
|
732
|
-
>>> fMRI.print()
|
|
733
|
-
---------- STUDY ---------------
|
|
734
|
-
Study fMRI [20230203]
|
|
735
|
-
--------------------------------
|
|
736
|
-
|
|
737
|
-
Create two new series in the study:
|
|
738
|
-
|
|
739
|
-
>>> rstate = fMRI.new_child(SeriesDescription='Resting state')
|
|
740
|
-
>>> ftap = fMRI.new_child(SeriesDescription='Finger tap')
|
|
741
|
-
>>> fMRI.print()
|
|
742
|
-
---------- STUDY ---------------
|
|
743
|
-
Study fMRI [20230203]
|
|
744
|
-
Series 001 [Resting state]
|
|
745
|
-
Nr of instances: 0
|
|
746
|
-
Series 002 [Finger tap]
|
|
747
|
-
Nr of instances: 0
|
|
748
|
-
--------------------------------
|
|
749
|
-
|
|
750
|
-
Note the same result could also be obtained by calling :func:`~new_series` on the study.
|
|
751
|
-
"""
|
|
752
|
-
# Note this function is implemented in all subclasses - included here for documentation purposes.
|
|
753
|
-
pass
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
def new_sibling(self, suffix:str=None, **kwargs):
|
|
757
|
-
"""Create a new sibling of the record under the same parent.
|
|
758
|
-
|
|
759
|
-
Args:
|
|
760
|
-
kwargs: Any valid DICOM (tag, value) pair to assign to the new sibling.
|
|
761
|
-
|
|
762
|
-
Raises:
|
|
763
|
-
RuntimeError: when called on a Record of type Database. New records can only be created within an existing database.
|
|
764
|
-
|
|
765
|
-
See Also:
|
|
766
|
-
:func:`~new_patient`
|
|
767
|
-
:func:`~new_study`
|
|
768
|
-
:func:`~new_series`
|
|
769
|
-
:func:`~new_pibling`
|
|
770
|
-
|
|
771
|
-
Example:
|
|
772
|
-
Create a sibling series under the same study:
|
|
773
|
-
|
|
774
|
-
>>> rstate = db.series(SeriesDescription='Resting state')
|
|
775
|
-
>>> ftap = rstate.new_sibling(SeriesDescription='Finger tap')
|
|
776
|
-
>>> rstate.parent().print()
|
|
777
|
-
---------- STUDY ---------------
|
|
778
|
-
Study New Study [None]
|
|
779
|
-
Series 001 [Resting state]
|
|
780
|
-
Nr of instances: 0
|
|
781
|
-
Series 002 [Finger tap]
|
|
782
|
-
Nr of instances: 0
|
|
783
|
-
--------------------------------
|
|
784
|
-
"""
|
|
785
|
-
# Note this function is implemented in all subclasses - included here for documentation purposes.
|
|
786
|
-
# Note the suffix argument is deprecated and should not be used.
|
|
787
|
-
pass
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
def new_pibling(self, **kwargs):
|
|
791
|
-
"""Create a new sibling of the parent record (pibling).
|
|
792
|
-
|
|
793
|
-
Args:
|
|
794
|
-
kwargs (optional): Any valid DICOM (tag, value) pair can be assigned up front as properties of the new pibling.
|
|
795
|
-
|
|
796
|
-
Returns:
|
|
797
|
-
Record: instance of the new parent
|
|
798
|
-
|
|
799
|
-
See Also:
|
|
800
|
-
:func:`~new_patient`
|
|
801
|
-
:func:`~new_study`
|
|
802
|
-
:func:`~new_series`
|
|
803
|
-
|
|
804
|
-
Example:
|
|
805
|
-
Use a series to create a new study directly. A use case is where image processing results derived from a series should be saved in a separate study under the same patient.
|
|
806
|
-
|
|
807
|
-
>>> fMRI = db.study(StudyDescription='fMRI', StudyDate='202305010')
|
|
808
|
-
>>> rstate = fMRI.new_series(SeriesDescription='Resting state')
|
|
809
|
-
>>> rstate_results = rstate.new_pibling(StudyDescription='fMRI resting state analysis', StudyDate='20230603')
|
|
810
|
-
>>> rstate.patient().print()
|
|
811
|
-
---------- PATIENT -------------
|
|
812
|
-
Patient New Patient
|
|
813
|
-
Study New Study [None]
|
|
814
|
-
Series 001 [Resting state]
|
|
815
|
-
Nr of instances: 0
|
|
816
|
-
Study fMRI resting state analysis [20230603]
|
|
817
|
-
--------------------------------
|
|
818
|
-
"""
|
|
819
|
-
type = self.__class__.__name__
|
|
820
|
-
if type == 'Database':
|
|
821
|
-
return None
|
|
822
|
-
if type == 'Patient':
|
|
823
|
-
return None
|
|
824
|
-
return self.parent().new_sibling(**kwargs)
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
def remove(self):
|
|
828
|
-
"""Remove a record from the database.
|
|
829
|
-
|
|
830
|
-
See Also:
|
|
831
|
-
:func:`~copy`
|
|
832
|
-
:func:`~copy_to`
|
|
833
|
-
:func:`~move_to`
|
|
834
|
-
|
|
835
|
-
Example:
|
|
836
|
-
Create a new study in an empty database, then remove it again:
|
|
837
|
-
|
|
838
|
-
>>> database = db.database()
|
|
839
|
-
>>> study = database.new_study(StudyDescription='Demo Study')
|
|
840
|
-
>>> database.print()
|
|
841
|
-
---------- DATABASE --------------
|
|
842
|
-
Location: In memory
|
|
843
|
-
Patient New Patient
|
|
844
|
-
Study Demo Study [None]
|
|
845
|
-
----------------------------------
|
|
846
|
-
|
|
847
|
-
>>> study.remove()
|
|
848
|
-
>>> database.print()
|
|
849
|
-
---------- DATABASE --------------
|
|
850
|
-
Location: In memory
|
|
851
|
-
Patient New Patient
|
|
852
|
-
----------------------------------
|
|
853
|
-
|
|
854
|
-
A record that has been removed from the database can no longer be accessed. Any attempt to do so will raise an error:
|
|
855
|
-
|
|
856
|
-
>>> print(study.label())
|
|
857
|
-
ValueError: This record has been removed from the database and can no longer be accessed.
|
|
858
|
-
|
|
859
|
-
Note:
|
|
860
|
-
Removing a record will also remove all of its children, and this will be permanent after saving the record with :func:`~save`.
|
|
861
|
-
|
|
862
|
-
If a record has been removed accidentally in an interactive session, use :func:`~restore` to revert back to the last saved state.
|
|
863
|
-
"""
|
|
864
|
-
self.manager.delete(self.uid, keys=self.keys())
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
def move_to(self, parent):
|
|
868
|
-
"""Move the record to another parent.
|
|
869
|
-
|
|
870
|
-
Args:
|
|
871
|
-
parent: parent where the record will be moved to.
|
|
872
|
-
|
|
873
|
-
See Also:
|
|
874
|
-
:func:`~remove`
|
|
875
|
-
:func:`~copy`
|
|
876
|
-
:func:`~copy_to`
|
|
877
|
-
|
|
878
|
-
Example:
|
|
879
|
-
Create a database with two studies and a single series in one:
|
|
880
|
-
|
|
881
|
-
>>> demo = db.series(SeriesDescription='!!WATCH ME MOVE!!')
|
|
882
|
-
>>> test = demo.new_pibling(StudyDescription='Test')
|
|
883
|
-
>>> series.patient().print()
|
|
884
|
-
---------- PATIENT -------------
|
|
885
|
-
Patient New Patient
|
|
886
|
-
Study New Study [None]
|
|
887
|
-
Series 001 [!!WATCH ME MOVE!!]
|
|
888
|
-
Nr of instances: 0
|
|
889
|
-
Study Test [None]
|
|
890
|
-
--------------------------------
|
|
891
|
-
|
|
892
|
-
Now move the series to the other study:
|
|
893
|
-
|
|
894
|
-
>>> series.move_to(study)
|
|
895
|
-
>>> series.patient().print()
|
|
896
|
-
---------- PATIENT -------------
|
|
897
|
-
Patient New Patient
|
|
898
|
-
Study New Study [None]
|
|
899
|
-
Study Test [None]
|
|
900
|
-
Series 001 [Demo]
|
|
901
|
-
Nr of instances: 0
|
|
902
|
-
--------------------------------
|
|
903
|
-
|
|
904
|
-
"""
|
|
905
|
-
move_to(self, parent)
|
|
906
|
-
return self
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
def copy_to(self, parent, **kwargs):
|
|
912
|
-
"""Return a copy of the record under another parent.
|
|
913
|
-
|
|
914
|
-
Args:
|
|
915
|
-
parent: parent where the copy will be placed.
|
|
916
|
-
kwargs (optional): Any valid DICOM (tag, value) pair can be assigned up front as properties of the copy.
|
|
917
|
-
|
|
918
|
-
Returns:
|
|
919
|
-
Record: copy of the same type.
|
|
920
|
-
|
|
921
|
-
See Also:
|
|
922
|
-
:func:`~remove`
|
|
923
|
-
:func:`~copy`
|
|
924
|
-
:func:`~move_to`
|
|
925
|
-
|
|
926
|
-
Example:
|
|
927
|
-
Create a database with a single patient/study/series:
|
|
928
|
-
|
|
929
|
-
>>> series = db.series(SeriesDescription='Demo')
|
|
930
|
-
>>> series.patient().print()
|
|
931
|
-
---------- PATIENT -------------
|
|
932
|
-
Patient New Patient
|
|
933
|
-
Study New Study [None]
|
|
934
|
-
Series 001 [Demo]
|
|
935
|
-
Nr of instances: 0
|
|
936
|
-
--------------------------------
|
|
937
|
-
|
|
938
|
-
Create a new study *Copies* under the same patient, and copy the the *Demo* series into it:
|
|
939
|
-
|
|
940
|
-
>>> study = series.new_pibling(StudyDescription='Copies')
|
|
941
|
-
>>> copy = series.copy_to(study, SeriesDescription='Copy of Demo')
|
|
942
|
-
|
|
943
|
-
The same patient now has two studies, each with a single series:
|
|
944
|
-
|
|
945
|
-
>>> series.patient().print()
|
|
946
|
-
---------- PATIENT -------------
|
|
947
|
-
Patient New Patient
|
|
948
|
-
Study Copies [None]
|
|
949
|
-
Series 001 [Copy of Demo]
|
|
950
|
-
Nr of instances: 0
|
|
951
|
-
Study New Study [None]
|
|
952
|
-
Series 001 [Demo]
|
|
953
|
-
Nr of instances: 0
|
|
954
|
-
--------------------------------
|
|
955
|
-
"""
|
|
956
|
-
return parent._copy_from(self, **kwargs)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
def copy(self, **kwargs):
|
|
960
|
-
"""Return a copy of the record under the same parent.
|
|
961
|
-
|
|
962
|
-
Args:
|
|
963
|
-
kwargs (optional): Any valid DICOM (tag, value) pair can be assigned up front as properties of the copy.
|
|
964
|
-
|
|
965
|
-
Returns:
|
|
966
|
-
Record: copy of the same type.
|
|
967
|
-
|
|
968
|
-
See Also:
|
|
969
|
-
:func:`~remove`
|
|
970
|
-
:func:`~copy_to`
|
|
971
|
-
:func:`~move_to`
|
|
972
|
-
|
|
973
|
-
Example:
|
|
974
|
-
Create a new DICOM study and build two copies in the same patient, assigning a new study description on the fly:
|
|
975
|
-
|
|
976
|
-
>>> study = db.study(StudyDescription='Original', StudyDate='20001231')
|
|
977
|
-
>>> copy1 = study.copy(StudyDescription='Copy 1')
|
|
978
|
-
>>> copy2 = study.copy(StudyDescription='Copy 2')
|
|
979
|
-
>>> study.parent().print()
|
|
980
|
-
---------- PATIENT -------------
|
|
981
|
-
Patient New Patient
|
|
982
|
-
Study Copy 1 [20001231]
|
|
983
|
-
Study Copy 2 [20001231]
|
|
984
|
-
Study Original [20001231]
|
|
985
|
-
--------------------------------
|
|
986
|
-
"""
|
|
987
|
-
return self.copy_to(self.parent(), **kwargs)
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
# Load and save
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
def restore(self):
|
|
994
|
-
"""Restore the record to the last changed state.
|
|
995
|
-
|
|
996
|
-
.. warning::
|
|
997
|
-
|
|
998
|
-
Restoring is irreversible! Any edits made to the record since the last time it was saved will be lost.
|
|
999
|
-
|
|
1000
|
-
See Also:
|
|
1001
|
-
:func:`~save`
|
|
1002
|
-
|
|
1003
|
-
Create a new patient and change the name:
|
|
1004
|
-
|
|
1005
|
-
>>> patient = db.patient(PatientName='James Bond')
|
|
1006
|
-
>>> patient.PatientName = 'Scarface'
|
|
1007
|
-
>>> print(patient.PatientName)
|
|
1008
|
-
Scarface
|
|
1009
|
-
|
|
1010
|
-
Calling restore will undo the changes:
|
|
1011
|
-
|
|
1012
|
-
>>> patient.restore()
|
|
1013
|
-
>>> print(patient.PatientName)
|
|
1014
|
-
James Bond
|
|
1015
|
-
"""
|
|
1016
|
-
rows = self.manager._extract_record(self.name, self.uid)
|
|
1017
|
-
self.manager.restore(rows)
|
|
1018
|
-
self.write()
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
def save(self, path=None):
|
|
1022
|
-
"""Save any changes made to the record.
|
|
1023
|
-
|
|
1024
|
-
.. warning::
|
|
1025
|
-
|
|
1026
|
-
Saving is irreversible! Any edits made to the record before saving cannot be undone.
|
|
1027
|
-
|
|
1028
|
-
See Also:
|
|
1029
|
-
:func:`~restore`
|
|
1030
|
-
|
|
1031
|
-
Example:
|
|
1032
|
-
Create a new patient, change the name, and save:
|
|
1033
|
-
|
|
1034
|
-
>>> patient = db.patient(PatientName='James Bond')
|
|
1035
|
-
>>> patient.PatientName = 'Scarface'
|
|
1036
|
-
>>> patient.save()
|
|
1037
|
-
|
|
1038
|
-
At this point the original information can no longer be restored. Calling restore does not revert back to the original:
|
|
1039
|
-
|
|
1040
|
-
>>> patient.restore()
|
|
1041
|
-
>>> print(patient.PatientName)
|
|
1042
|
-
Scarface
|
|
1043
|
-
"""
|
|
1044
|
-
rows = self.manager._extract_record(self.name, self.uid)
|
|
1045
|
-
self.manager.save(rows)
|
|
1046
|
-
self.write(path)
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
def load(self):
|
|
1050
|
-
"""Load the record into memory.
|
|
1051
|
-
|
|
1052
|
-
After loading the record into memory, all subsequent changes will be made in memory only. Call clear() to write any changes to disk and remove it from memory.
|
|
1053
|
-
|
|
1054
|
-
Note:
|
|
1055
|
-
If the record already exists in memory, read() does nothing. This is to avoid that any changes made after reading are overwritten.
|
|
1056
|
-
|
|
1057
|
-
See Also:
|
|
1058
|
-
:func:`~clear`
|
|
1059
|
-
|
|
1060
|
-
Example:
|
|
1061
|
-
|
|
1062
|
-
As an example, we can verify that editing data in memory is faster than on disk. We'll need the time package and a large series on disk:
|
|
1063
|
-
|
|
1064
|
-
>>> from time import time
|
|
1065
|
-
>>> path = 'path\\to\\empty\\folder'
|
|
1066
|
-
>>> series = db.zeros((20,20,256,256), in_database=db.database(path))
|
|
1067
|
-
|
|
1068
|
-
Now measure the time it takes to set the slice locations to a constant value:
|
|
1069
|
-
|
|
1070
|
-
>>> t=time(); series.SliceLocation=1; print(time()-t)
|
|
1071
|
-
17.664631605148315
|
|
1072
|
-
|
|
1073
|
-
Since the series was created on disk, this is editing on disk. Now load the series into memory and perform the same steps:
|
|
1074
|
-
|
|
1075
|
-
>>> series.load()
|
|
1076
|
-
>>> t=time(); series.SliceLocation=1; print(time()-t)
|
|
1077
|
-
2.3518126010894775
|
|
1078
|
-
|
|
1079
|
-
On the machine where this was executed, the same computation runs more than 10 times faster in memory.
|
|
1080
|
-
"""
|
|
1081
|
-
self.manager.read(self.uid, keys=self.keys())
|
|
1082
|
-
return self
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
def clear(self):
|
|
1086
|
-
"""Clear the record from memory.
|
|
1087
|
-
|
|
1088
|
-
This will write the record to disk and clear it from memory. After this step, subsequent calculations will be performed from disk.
|
|
1089
|
-
|
|
1090
|
-
Note:
|
|
1091
|
-
If the record does not exist in memory, or if its database does not have a path on disk associated, read() does nothing.
|
|
1092
|
-
|
|
1093
|
-
See Also:
|
|
1094
|
-
:func:`~read`
|
|
1095
|
-
|
|
1096
|
-
Example:
|
|
1097
|
-
|
|
1098
|
-
As an example, we can verify that editing data in memory is faster than on disk. We'll need the time package and a large series in memory. We also provide a path to a directory for writing data:
|
|
1099
|
-
|
|
1100
|
-
>>> from time import time
|
|
1101
|
-
>>> series = db.zeros((20,20,256,256))
|
|
1102
|
-
>>> series.database().set_path(path)
|
|
1103
|
-
|
|
1104
|
-
Now measure the time it takes to set the slice locations to a constant value:
|
|
1105
|
-
|
|
1106
|
-
>>> t=time(); series.SliceLocation=1; print(time()-t)
|
|
1107
|
-
1.9060208797454834
|
|
1108
|
-
|
|
1109
|
-
Since the series was created in memory, this is editing in memory. Now we clear the series from memory and perform the same computation:
|
|
1110
|
-
|
|
1111
|
-
>>> series.clear()
|
|
1112
|
-
>>> t=time(); series.SliceLocation=1; print(time()-t)
|
|
1113
|
-
17.933974981307983
|
|
1114
|
-
|
|
1115
|
-
The computation is now run from disk and is 10 times slower because of the need to read and write the files.
|
|
1116
|
-
"""
|
|
1117
|
-
self.manager.clear(self.uid, keys=self.keys())
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
def progress(self, value: float, maximum: float, message: str=None):
|
|
1121
|
-
"""Print progress message to the terminal..
|
|
1122
|
-
|
|
1123
|
-
Args:
|
|
1124
|
-
value (float): current status
|
|
1125
|
-
maximum (float): maximal value
|
|
1126
|
-
message (str, optional): Message to include in the update. Defaults to None.
|
|
1127
|
-
|
|
1128
|
-
Note:
|
|
1129
|
-
When working through a terminal this could easily be replicated with a print statement. The advantage of using the progress interface is that the code does not need to be changed when the computation is run through a graphical user interface (assuming this uses a compatible API).
|
|
1130
|
-
|
|
1131
|
-
Another advantage is that messaging can be muted/unmuted using .mute() and .unmute(), for instance when the object is passed to a subroutine.
|
|
1132
|
-
|
|
1133
|
-
See Also:
|
|
1134
|
-
:func:`~message`
|
|
1135
|
-
:func:`~mute`
|
|
1136
|
-
:func:`~unmute`
|
|
1137
|
-
|
|
1138
|
-
Example:
|
|
1139
|
-
>>> nr_of_slices = 3
|
|
1140
|
-
>>> series = db.zeros((nr_of_slices,128,128))
|
|
1141
|
-
>>> for slice in range(nr_of_slices):
|
|
1142
|
-
series.progress(1+slice, nr_of_slices, 'Looping over slices')
|
|
1143
|
-
Looping over slices [33 %]
|
|
1144
|
-
Looping over slices [67 %]
|
|
1145
|
-
Looping over slices [100 %]
|
|
1146
|
-
"""
|
|
1147
|
-
if not self._mute:
|
|
1148
|
-
self.manager.status.progress(value, maximum, message=message)
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
def message(self, message: str):
|
|
1152
|
-
"""Print a message to the user.
|
|
1153
|
-
|
|
1154
|
-
Args:
|
|
1155
|
-
message (str): Message to be printed.
|
|
1156
|
-
|
|
1157
|
-
Note:
|
|
1158
|
-
When working through a terminal a print statement would have exactly the same effect. The advantage of using the message interface is that the code does not need to be changed when the computation is run through a graphical user interface (assuming this uses a compatible API).
|
|
1159
|
-
|
|
1160
|
-
Another advantage is that messaging can be muted/unmuted using .mute() and .unmute() for instance when the object is passed to a subroutine.
|
|
1161
|
-
|
|
1162
|
-
See Also:
|
|
1163
|
-
:func:`~progress`
|
|
1164
|
-
:func:`~mute`
|
|
1165
|
-
:func:`~unmute`
|
|
1166
|
-
|
|
1167
|
-
Example:
|
|
1168
|
-
|
|
1169
|
-
>>> series.message('Starting computation..')
|
|
1170
|
-
Starting computation..
|
|
1171
|
-
|
|
1172
|
-
After muting the same statment does not send a message:
|
|
1173
|
-
|
|
1174
|
-
>>> series.mute()
|
|
1175
|
-
>>> series.message('Starting computation..')
|
|
1176
|
-
|
|
1177
|
-
Unmute to reactivate sending messages:
|
|
1178
|
-
|
|
1179
|
-
>>> series.unmute()
|
|
1180
|
-
>>> series.message('Starting computation..')
|
|
1181
|
-
Starting computation..
|
|
1182
|
-
"""
|
|
1183
|
-
if not self._mute:
|
|
1184
|
-
self.manager.status.message(message)
|
|
1185
|
-
|
|
1186
|
-
def mute(self):
|
|
1187
|
-
"""Prevent the object from sending status updates to the user
|
|
1188
|
-
|
|
1189
|
-
See Also:
|
|
1190
|
-
:func:`~unmute`
|
|
1191
|
-
:func:`~message`
|
|
1192
|
-
:func:`~progress`
|
|
1193
|
-
|
|
1194
|
-
Example:
|
|
1195
|
-
>>> series = db.zeros((3,128,128))
|
|
1196
|
-
>>> print('My message: ')
|
|
1197
|
-
>>> series.message('Hello World')
|
|
1198
|
-
>>> series.mute()
|
|
1199
|
-
>>> print('My message: ')
|
|
1200
|
-
>>> series.message('Hello World')
|
|
1201
|
-
|
|
1202
|
-
My message:
|
|
1203
|
-
Hello World
|
|
1204
|
-
My message:
|
|
1205
|
-
"""
|
|
1206
|
-
self._mute = True
|
|
1207
|
-
|
|
1208
|
-
def unmute(self):
|
|
1209
|
-
"""Allow the object from sending status updates to the user
|
|
1210
|
-
|
|
1211
|
-
Note:
|
|
1212
|
-
Records are unmuted by default, so unmuting is only necessary after a previouse call to mute(). Unmuting has no effect when the record is already unmuted.
|
|
1213
|
-
|
|
1214
|
-
See Also:
|
|
1215
|
-
:func:`~mute`
|
|
1216
|
-
:func:`~message`
|
|
1217
|
-
:func:`~progress`
|
|
1218
|
-
|
|
1219
|
-
Example:
|
|
1220
|
-
>>> series = db.zeros((3,128,128))
|
|
1221
|
-
>>> print('My message: ')
|
|
1222
|
-
>>> series.message('Hello World')
|
|
1223
|
-
>>> series.mute()
|
|
1224
|
-
>>> print('My message: ')
|
|
1225
|
-
>>> series.message('Hello World')
|
|
1226
|
-
>>> series.unmute()
|
|
1227
|
-
>>> print('My message: ')
|
|
1228
|
-
>>> series.message('Hello World')
|
|
1229
|
-
|
|
1230
|
-
My message:
|
|
1231
|
-
Hello World
|
|
1232
|
-
My message:
|
|
1233
|
-
My message:
|
|
1234
|
-
Hello World
|
|
1235
|
-
"""
|
|
1236
|
-
self._mute = False
|
|
1237
|
-
|
|
1238
|
-
def type(self):
|
|
1239
|
-
return self.__class__.__name__
|
|
1240
|
-
|
|
1241
|
-
def exists(self):
|
|
1242
|
-
#if self.manager.register is None:
|
|
1243
|
-
if not self.manager.is_open():
|
|
1244
|
-
return False
|
|
1245
|
-
try:
|
|
1246
|
-
keys = self.keys().tolist()
|
|
1247
|
-
except:
|
|
1248
|
-
return False
|
|
1249
|
-
return keys != []
|
|
1250
|
-
|
|
1251
|
-
def record(self, type, uid='Database', key=None, **kwargs):
|
|
1252
|
-
return self.new(self.manager, uid, type, key=key, **kwargs)
|
|
1253
|
-
|
|
1254
|
-
def register(self):
|
|
1255
|
-
return self.manager._extract(self.keys())
|
|
1256
|
-
#return self.manager.register.loc[self.keys(),:]
|
|
1257
|
-
|
|
1258
|
-
def instances(self, sort=True, sortby=None, **kwargs):
|
|
1259
|
-
inst = self.manager.instances(keys=self.keys(), sort=sort, sortby=sortby, **kwargs)
|
|
1260
|
-
return [self.record('Instance', uid, key) for key, uid in inst.items()]
|
|
1261
|
-
|
|
1262
|
-
def images(self, sort=True, sortby=None, **kwargs):
|
|
1263
|
-
inst = self.manager.instances(keys=self.keys(), sort=sort, sortby=sortby, images=True, **kwargs)
|
|
1264
|
-
return [self.record('Instance', uid, key) for key, uid in inst.items()]
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
# This needs a test whether the instance is an image - else move to the next
|
|
1269
|
-
def image(self, **kwargs):
|
|
1270
|
-
return self.instance(**kwargs)
|
|
1271
|
-
|
|
1272
|
-
# Needs a unit test
|
|
1273
|
-
def instance(self, uid=None, key=None):
|
|
1274
|
-
if key is not None:
|
|
1275
|
-
#uid = self.manager.register.at[key, 'SOPInstanceUID']
|
|
1276
|
-
uid = self.manager._at(key, 'SOPInstanceUID')
|
|
1277
|
-
if uid is None:
|
|
1278
|
-
return
|
|
1279
|
-
return self.record('Instance', uid, key=key)
|
|
1280
|
-
if uid is not None:
|
|
1281
|
-
return self.record('Instance', uid)
|
|
1282
|
-
key = self.key()
|
|
1283
|
-
#uid = self.manager.register.at[key, 'SOPInstanceUID']
|
|
1284
|
-
uid = self.manager._at(key, 'SOPInstanceUID')
|
|
1285
|
-
return self.record('Instance', uid, key=key)
|
|
1286
|
-
|
|
1287
|
-
# Needs a unit test
|
|
1288
|
-
def sery(self, uid=None, key=None):
|
|
1289
|
-
if key is not None:
|
|
1290
|
-
#uid = self.manager.register.at[key, 'SeriesInstanceUID']
|
|
1291
|
-
uid = self.manager._at(key, 'SeriesInstanceUID')
|
|
1292
|
-
if uid is None:
|
|
1293
|
-
return
|
|
1294
|
-
return self.record('Series', uid, key=key)
|
|
1295
|
-
if uid is not None:
|
|
1296
|
-
return self.record('Series', uid)
|
|
1297
|
-
key = self.key()
|
|
1298
|
-
#uid = self.manager.register.at[key, 'SeriesInstanceUID']
|
|
1299
|
-
uid = self.manager._at(key, 'SeriesInstanceUID')
|
|
1300
|
-
return self.record('Series', uid, key=key)
|
|
1301
|
-
|
|
1302
|
-
# Needs a unit test
|
|
1303
|
-
def study(self, uid=None, key=None):
|
|
1304
|
-
if key is not None:
|
|
1305
|
-
#uid = self.manager.register.at[key, 'StudyInstanceUID']
|
|
1306
|
-
uid = self.manager._at(key, 'StudyInstanceUID')
|
|
1307
|
-
if uid is None:
|
|
1308
|
-
return
|
|
1309
|
-
return self.record('Study', uid, key=key)
|
|
1310
|
-
if uid is not None:
|
|
1311
|
-
return self.record('Study', uid)
|
|
1312
|
-
key = self.key()
|
|
1313
|
-
#uid = self.manager.register.at[key, 'StudyInstanceUID']
|
|
1314
|
-
uid = self.manager._at(key, 'StudyInstanceUID')
|
|
1315
|
-
return self.record('Study', uid, key=key)
|
|
1316
|
-
|
|
1317
|
-
# Needs a unit test
|
|
1318
|
-
def patient(self, uid=None, key=None):
|
|
1319
|
-
if key is not None:
|
|
1320
|
-
#uid = self.manager.register.at[key, 'PatientID']
|
|
1321
|
-
uid = self.manager._at(key, 'PatientID')
|
|
1322
|
-
if uid is None:
|
|
1323
|
-
return
|
|
1324
|
-
return self.record('Patient', uid, key=key)
|
|
1325
|
-
if uid is not None:
|
|
1326
|
-
return self.record('Patient', uid)
|
|
1327
|
-
key = self.key()
|
|
1328
|
-
#uid = self.manager.register.at[key, 'PatientID']
|
|
1329
|
-
uid = self.manager._at(key, 'PatientID')
|
|
1330
|
-
return self.record('Patient', uid, key=key)
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
def read(self): # Obsolete - replace by load()
|
|
1336
|
-
return self.load()
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
def write(self, path=None):
|
|
1340
|
-
if path is not None:
|
|
1341
|
-
self.manager.path = path
|
|
1342
|
-
try:
|
|
1343
|
-
keys = self.keys()
|
|
1344
|
-
except: # empty database
|
|
1345
|
-
pass
|
|
1346
|
-
else:
|
|
1347
|
-
self.manager.write(self.uid, keys=keys)
|
|
1348
|
-
self.manager._write_df()
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
def new_instance(self, dataset=None, **kwargs):
|
|
1356
|
-
attr = {**kwargs, **self.attributes}
|
|
1357
|
-
uid, key = self.manager.new_instance(parent=self.uid, dataset=dataset, **attr)
|
|
1358
|
-
return self.record('Instance', uid, key, **attr)
|
|
1359
|
-
|
|
1360
|
-
def set_values(self, attributes, values):
|
|
1361
|
-
keys = self.keys()
|
|
1362
|
-
self._key = self.manager.set_values(attributes, values, keys)
|
|
1363
|
-
|
|
1364
|
-
def get_values(self, attributes):
|
|
1365
|
-
return self.manager.get_values(attributes, self.keys())
|
|
1366
|
-
|
|
1367
|
-
def get_dataset(self):
|
|
1368
|
-
ds = self.manager.get_dataset(self.uid, self.keys())
|
|
1369
|
-
return ds
|
|
1370
|
-
|
|
1371
|
-
def set_dataset(self, dataset):
|
|
1372
|
-
self.manager.set_dataset(self.uid, dataset, self.keys())
|
|
1373
|
-
|
|
1374
|
-
def export_as_dicom(self, path):
|
|
1375
|
-
if self.name == 'Database':
|
|
1376
|
-
folder = 'Database'
|
|
1377
|
-
else:
|
|
1378
|
-
folder = self.label()
|
|
1379
|
-
path = export_path(path, folder)
|
|
1380
|
-
for child in self.children():
|
|
1381
|
-
child.export_as_dicom(path)
|
|
1382
|
-
|
|
1383
|
-
def export_as_png(self, path):
|
|
1384
|
-
if self.name == 'Database':
|
|
1385
|
-
folder = 'Database'
|
|
1386
|
-
else:
|
|
1387
|
-
folder = self.label()
|
|
1388
|
-
path = export_path(path, folder)
|
|
1389
|
-
for child in self.children():
|
|
1390
|
-
child.export_as_png(path)
|
|
1391
|
-
|
|
1392
|
-
def export_as_csv(self, path):
|
|
1393
|
-
if self.name == 'Database':
|
|
1394
|
-
folder = 'Database'
|
|
1395
|
-
else:
|
|
1396
|
-
folder = self.label()
|
|
1397
|
-
path = export_path(path, folder)
|
|
1398
|
-
for child in self.children():
|
|
1399
|
-
child.export_as_csv(path)
|
|
1400
|
-
|
|
1401
|
-
def export_as_nifti(self, path):
|
|
1402
|
-
if self.name == 'Database':
|
|
1403
|
-
folder = 'Database'
|
|
1404
|
-
else:
|
|
1405
|
-
folder = self.label()
|
|
1406
|
-
path = export_path(path, folder)
|
|
1407
|
-
for child in self.children():
|
|
1408
|
-
child.export_as_nifti(path)
|
|
1409
|
-
|
|
1410
|
-
# def sort(self, sortby=['StudyDate','SeriesNumber','InstanceNumber']):
|
|
1411
|
-
# self.manager.register.sort_values(sortby, inplace=True)
|
|
1412
|
-
|
|
1413
|
-
def read_dataframe(*args, **kwargs):
|
|
1414
|
-
return read_dataframe(*args, **kwargs)
|
|
1415
|
-
|
|
1416
|
-
def series_data(self):
|
|
1417
|
-
attr = dbdataset.module_series()
|
|
1418
|
-
vals = self[attr]
|
|
1419
|
-
return attr, vals
|
|
1420
|
-
|
|
1421
|
-
def study_data(self):
|
|
1422
|
-
attr = dbdataset.module_study()
|
|
1423
|
-
vals = self[attr]
|
|
1424
|
-
return attr, vals
|
|
1425
|
-
|
|
1426
|
-
def patient_data(self):
|
|
1427
|
-
attr = dbdataset.module_patient()
|
|
1428
|
-
vals = self[attr]
|
|
1429
|
-
return attr, vals
|
|
1430
|
-
|
|
1431
|
-
# def tree(*args, **kwargs):
|
|
1432
|
-
# return tree(*args, **kwargs)
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
#
|
|
1437
|
-
# Functions on a list of records of the same database
|
|
1438
|
-
#
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
def copy_to(records, target):
|
|
1442
|
-
if not isinstance(records, list):
|
|
1443
|
-
return records.copy_to(target)
|
|
1444
|
-
copy = []
|
|
1445
|
-
desc = target.label()
|
|
1446
|
-
for r, record in enumerate(records):
|
|
1447
|
-
record.status.progress(r+1, len(records), 'Copying ' + desc)
|
|
1448
|
-
copy_record = record.copy_to(target)
|
|
1449
|
-
if isinstance(copy_record, list):
|
|
1450
|
-
copy += copy_record
|
|
1451
|
-
else:
|
|
1452
|
-
copy.append(copy_record)
|
|
1453
|
-
record.status.hide()
|
|
1454
|
-
return copy
|
|
1455
|
-
|
|
1456
|
-
def move_to(records, target):
|
|
1457
|
-
#if type(records) is np.ndarray:
|
|
1458
|
-
# records = records.tolist()
|
|
1459
|
-
if not isinstance(records, list):
|
|
1460
|
-
records = [records]
|
|
1461
|
-
mgr = records[0].manager
|
|
1462
|
-
uids = [rec.uid for rec in records]
|
|
1463
|
-
mgr.move_to(uids, target.uid, **target.attributes)
|
|
1464
|
-
return records
|
|
1465
|
-
|
|
1466
|
-
def group(records, into=None, inplace=False):
|
|
1467
|
-
if not isinstance(records, list):
|
|
1468
|
-
records = [records]
|
|
1469
|
-
if into is None:
|
|
1470
|
-
into = records[0].new_pibling()
|
|
1471
|
-
if inplace:
|
|
1472
|
-
move_to(records, into)
|
|
1473
|
-
else:
|
|
1474
|
-
copy_to(records, into)
|
|
1475
|
-
return into
|
|
1476
|
-
|
|
1477
|
-
def merge(records, into=None, inplace=False):
|
|
1478
|
-
if not isinstance(records, list):
|
|
1479
|
-
records = [records]
|
|
1480
|
-
children = []
|
|
1481
|
-
for record in records:
|
|
1482
|
-
children += record.children()
|
|
1483
|
-
new_series = group(children, into=into, inplace=inplace)
|
|
1484
|
-
if inplace:
|
|
1485
|
-
for record in records:
|
|
1486
|
-
record.remove()
|
|
1487
|
-
return new_series
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
#
|
|
1491
|
-
# Read and write
|
|
1492
|
-
#
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
def read_dataframe(record, tags):
|
|
1498
|
-
if set(tags) <= set(record.manager.columns):
|
|
1499
|
-
return record.register()[tags]
|
|
1500
|
-
instances = record.instances()
|
|
1501
|
-
return _read_dataframe_from_instance_array_values(instances, tags)
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
def read_dataframe_from_instance_array(instances, tags):
|
|
1505
|
-
mgr = instances[0].manager
|
|
1506
|
-
if set(tags) <= set(mgr.columns):
|
|
1507
|
-
keys = [i.key() for _, i in np.ndenumerate(instances)]
|
|
1508
|
-
return mgr._extract(keys)[tags]
|
|
1509
|
-
return _read_dataframe_from_instance_array_values(instances, tags)
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
def _read_dataframe_from_instance_array_values(instances, tags):
|
|
1513
|
-
indices = []
|
|
1514
|
-
data = []
|
|
1515
|
-
for i, instance in enumerate(instances):
|
|
1516
|
-
index = instance.key()
|
|
1517
|
-
values = instance.get_values(tags)
|
|
1518
|
-
indices.append(index)
|
|
1519
|
-
data.append(values)
|
|
1520
|
-
instance.progress(i+1, len(instances), 'Reading dataframe..')
|
|
1521
|
-
return pd.DataFrame(data, index=indices, columns=tags)
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|