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.
Files changed (72) hide show
  1. dbdicom/__init__.py +3 -25
  2. dbdicom/api.py +496 -0
  3. dbdicom/const.py +144 -0
  4. dbdicom/database.py +133 -0
  5. dbdicom/dataset.py +471 -0
  6. dbdicom/dbd.py +1290 -0
  7. dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  8. dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  9. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  10. dbdicom/external/dcm4che/bin/emf2sf +57 -57
  11. dbdicom/register.py +402 -0
  12. dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
  13. dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +206 -160
  14. dbdicom/sop_classes/mr_image.py +338 -0
  15. dbdicom/sop_classes/parametric_map.py +381 -0
  16. dbdicom/sop_classes/secondary_capture.py +140 -0
  17. dbdicom/sop_classes/segmentation.py +311 -0
  18. dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
  19. dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
  20. dbdicom/utils/arrays.py +142 -0
  21. dbdicom/utils/files.py +0 -20
  22. dbdicom/utils/image.py +43 -466
  23. dbdicom/utils/pydicom_dataset.py +386 -0
  24. dbdicom-0.3.16.dist-info/METADATA +26 -0
  25. dbdicom-0.3.16.dist-info/RECORD +54 -0
  26. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/WHEEL +1 -1
  27. dbdicom/create.py +0 -450
  28. dbdicom/ds/__init__.py +0 -10
  29. dbdicom/ds/create.py +0 -63
  30. dbdicom/ds/dataset.py +0 -841
  31. dbdicom/ds/dictionaries.py +0 -620
  32. dbdicom/ds/types/mr_image.py +0 -267
  33. dbdicom/ds/types/parametric_map.py +0 -226
  34. dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
  35. dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
  36. dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
  37. dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
  38. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
  39. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
  40. dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
  41. dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
  42. dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
  43. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
  44. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
  45. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
  46. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
  47. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
  48. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
  49. dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
  50. dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
  51. dbdicom/manager.py +0 -2077
  52. dbdicom/message.py +0 -119
  53. dbdicom/record.py +0 -1526
  54. dbdicom/types/database.py +0 -107
  55. dbdicom/types/instance.py +0 -184
  56. dbdicom/types/patient.py +0 -40
  57. dbdicom/types/series.py +0 -816
  58. dbdicom/types/study.py +0 -58
  59. dbdicom/utils/variables.py +0 -155
  60. dbdicom/utils/vreg.py +0 -2626
  61. dbdicom/wrappers/__init__.py +0 -7
  62. dbdicom/wrappers/dipy.py +0 -462
  63. dbdicom/wrappers/elastix.py +0 -855
  64. dbdicom/wrappers/numpy.py +0 -119
  65. dbdicom/wrappers/scipy.py +0 -1413
  66. dbdicom/wrappers/skimage.py +0 -1030
  67. dbdicom/wrappers/sklearn.py +0 -151
  68. dbdicom/wrappers/vreg.py +0 -273
  69. dbdicom-0.2.0.dist-info/METADATA +0 -276
  70. dbdicom-0.2.0.dist-info/RECORD +0 -81
  71. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info/licenses}/LICENSE +0 -0
  72. {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
-