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/manager.py DELETED
@@ -1,2077 +0,0 @@
1
- """
2
- Maintains an index of all files on disk.
3
- """
4
-
5
- import os
6
- import copy
7
- import timeit
8
- #from tkinter import N
9
- import pandas as pd
10
- import numpy as np
11
- import nibabel as nib
12
-
13
- from dbdicom.message import StatusBar, Dialog
14
- import dbdicom.utils.files as filetools
15
- import dbdicom.utils.dcm4che as dcm4che
16
- import dbdicom.utils.image as dbimage
17
- import dbdicom.ds.dataset as dbdataset
18
- from dbdicom.ds.create import read_dataset, SOPClass, new_dataset
19
- from dbdicom.ds.dataset import DbDataset
20
-
21
- class DatabaseCorrupted(Exception):
22
- pass
23
-
24
-
25
-
26
- class Manager():
27
- """Programming interface for reading and writing a DICOM folder."""
28
-
29
- # TODO: Add AccessionNumber so studies can be sorted correctly without reading the files
30
- # Note this makes all existing pkl files unusable - ensure backwards compatibility.
31
-
32
- # The column labels of the register
33
- columns = [
34
- 'PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID', 'SOPClassUID',
35
- 'PatientName', 'StudyDescription', 'StudyDate', 'SeriesDescription', 'SeriesNumber', 'InstanceNumber',
36
- 'ImageOrientationPatient', 'ImagePositionPatient', 'PixelSpacing', 'SliceThickness', 'SliceLocation', 'AcquisitionTime',
37
- ]
38
-
39
- # Non-UID subset of column labels with their respective indices
40
- # These are non-critical and can be set manually by users
41
- _descriptives = {
42
- 'PatientName': 5,
43
- 'StudyDescription': 6,
44
- 'StudyDate': 7,
45
- 'SeriesDescription': 8,
46
- 'ImageOrientationPatient':11,
47
- 'ImagePositionPatient':12,
48
- 'PixelSpacing':13,
49
- 'SliceThickness':14,
50
- 'SliceLocation':15,
51
- 'AcquisitionTime':16,
52
- }
53
-
54
- def default(self):
55
- return [None, None, None, None, None,
56
- None, None, None, None, int(-1), int(-1),
57
- None, None, None, float(-1.0), float(-1.0), None,
58
- ]
59
-
60
-
61
- def __init__(self, path=None, dataframe=None, status=StatusBar(), dialog=Dialog()):
62
- """Initialise the folder with a path and objects to message to the user.
63
-
64
- When used inside a GUI, status and dialog should be instances of the status bar and
65
- dialog class defined in `wezel`.
66
-
67
- path = None: The index manages data in memory
68
- dataframe = None: no database open
69
- """
70
- if dataframe is None:
71
- #dataframe = pd.DataFrame(index=[], columns=self.columns)
72
- dataframe = pd.DataFrame(index=[], columns=self.columns+['removed','created']) # Added 28/05/2023
73
- # THIS NEEDS A MECHANISM TO PREVENT ANOTHER Manager to open the same database.
74
- self.status = status
75
- self.dialog = dialog
76
- self.path = path
77
- self.register = dataframe
78
- self.dataset = {}
79
-
80
- def scan(self, unzip=False):
81
- """
82
- Reads all files in the folder and summarises key attributes in a table for faster access.
83
- """
84
- # Take unzip out until test is developed - less essential feature
85
- # if unzip:
86
- # filetools._unzip_files(self.path, self.status)
87
-
88
- #self.read_dataframe()
89
-
90
- if self.path is None:
91
- self.register = pd.DataFrame(index=[], columns=self.columns)
92
- self.dataset = {}
93
- return
94
- files = filetools.all_files(self.path)
95
- self.register = dbdataset.read_dataframe(
96
- files,
97
- self.columns+['NumberOfFrames'],
98
- self.status,
99
- path=self.path,
100
- message='Reading database..',
101
- images_only = True)
102
- self.register['removed'] = False
103
- self.register['created'] = False
104
- # No support for multiframe data at the moment
105
- self._multiframe_to_singleframe()
106
- self.register.drop('NumberOfFrames', axis=1, inplace=True)
107
- # For now ensure all series have just a single CIOD
108
- self._split_series()
109
- #self.save()
110
- return self
111
-
112
-
113
- def _split_series(self):
114
- """
115
- Split series with multiple SOP Classes.
116
-
117
- If a series contain instances from different SOP Classes,
118
- these are separated out into multiple series with identical SOP Classes.
119
- """
120
- df = self.register
121
- df = df[df.removed == False]
122
-
123
- # For each series, check if there are multiple
124
- # SOP Classes in the series and split them if yes.
125
- all_series = df.SeriesInstanceUID.unique()
126
- for s, series in enumerate(all_series):
127
- msg = 'Splitting series with multiple data types'
128
- self.status.progress(s+1, len(all_series), message=msg)
129
- df_series = df[df.SeriesInstanceUID == series]
130
- sop_classes = df_series.SOPClassUID.unique()
131
- if len(sop_classes) > 1:
132
- # For each sop_class, create a new series and move all
133
- # instances of that sop_class to the new series
134
- study = self.parent(series)
135
- series_desc = df_series.SeriesDescription.values[0]
136
- for i, sop_class in enumerate(sop_classes[1:]):
137
- desc = series_desc + ' [' + str(i+1) + ']'
138
- new_series, _ = self.new_series(parent=study, SeriesDescription=desc)
139
- df_sop_class = df_series[df_series.SOPClassUID == sop_class]
140
- instances = df_sop_class.SOPInstanceUID.values.tolist()
141
- moved = self.move_to_series(instances, new_series)
142
-
143
-
144
- def _multiframe_to_singleframe(self):
145
- """Converts all multiframe files in the folder into single-frame files.
146
-
147
- Reads all the multi-frame files in the folder,
148
- converts them to singleframe files, and delete the original multiframe file.
149
- """
150
- if self.path is None:
151
- # Low priority - we are not creating multiframe data from scratch yet
152
- # So will always be loaded from disk initially where the solution exists.
153
- # Solution: save data in a temporary file, use the filebased conversion,
154
- # the upload the solution and delete the temporary file.
155
- raise ValueError('Multi-frame to single-frame conversion does not yet exist from data in memory')
156
- singleframe = self.register.NumberOfFrames.isnull()
157
- multiframe = singleframe == False
158
- nr_multiframe = multiframe.sum()
159
- if nr_multiframe != 0:
160
- cnt=0
161
- for relpath in self.register[multiframe].index.values:
162
- cnt+=1
163
- msg = "Converting multiframe file " + relpath
164
- self.status.progress(cnt, nr_multiframe, message=msg)
165
- #
166
- # Create these in the dbdicom folder, not in the original folder.
167
- #
168
- filepath = os.path.join(self.path, relpath)
169
- singleframe_files = dcm4che.split_multiframe(filepath)
170
- if singleframe_files != []:
171
- # add the single frame files to the dataframe
172
- df = dbdataset.read_dataframe(singleframe_files, self.columns, path=self.path)
173
- df['removed'] = False
174
- df['created'] = False
175
- self.register = pd.concat([self.register, df])
176
- # delete the original multiframe
177
- os.remove(filepath)
178
- # drop the file also if the conversion has failed
179
- self.register.drop(index=relpath, inplace=True)
180
-
181
- def _pkl(self):
182
- """ Returns the file path of the .pkl file"""
183
- if self.path is None:
184
- return None
185
- filename = os.path.basename(os.path.normpath(self.path)) + ".pkl"
186
- return os.path.join(self.path, filename)
187
-
188
- def npy(self, uid):
189
- # Not in use - default path for temporary storage in numoy format
190
- path = os.path.join(self.path, "dbdicom_npy")
191
- if not os.path.isdir(path):
192
- os.mkdir(path)
193
- file = os.path.join(path, uid + '.npy')
194
- return file
195
-
196
- def _write_df(self):
197
- """ Writes the dataFrame as a .pkl file"""
198
- if self.path is None:
199
- return
200
- file = self._pkl()
201
- self.register.to_pickle(file)
202
-
203
- def _read_df(self):
204
- """Reads the dataFrame from a .pkl file """
205
- if self.path is None:
206
- return
207
- file = self._pkl()
208
- self.register = pd.read_pickle(file)
209
-
210
- def write_csv(self, file):
211
- """ Writes the dataFrame as a .csv file for visual inspection"""
212
- self.register.to_csv(file)
213
-
214
- def filepath(self, key):
215
- """Return the full filepath for a given relative path.
216
-
217
- Returns None for data that live in memory only."""
218
- # Needs a formal test for completeness
219
- if self.path is None:
220
- return None
221
- return os.path.join(self.path, key)
222
-
223
- def filepaths(self, *args, **kwargs):
224
- """Return a list of full filepaths for all dicom files in the folder"""
225
- # Needs a formal test for completeness
226
- return [self.filepath(key) for key in self.keys(*args, **kwargs)]
227
-
228
- def open(self, path=None, unzip=False):
229
- """Opens a DICOM folder for read and write.
230
-
231
- Reads the contents of the folder and summarises all DICOM files
232
- in a dataframe for faster access next time. The dataframe is saved
233
- as a pkl file when the folder is closed with `.close()`.
234
- All non-DICOM files in the folder are ignored.
235
-
236
- Args:
237
- path: The full path to the directory that is to be opened.
238
-
239
- """
240
- if path is not None:
241
- self.path = path
242
- if self.path is None:
243
- raise ValueError('Cannot open database - no path is specified')
244
- if os.path.exists(self._pkl()):
245
- try:
246
- self._read_df()
247
- except:
248
- # If the file is corrupted, delete it and load again
249
- os.remove(self._pkl())
250
- self.scan(unzip=unzip)
251
- else:
252
- self.scan(unzip=unzip)
253
- return self
254
-
255
-
256
- def type(self, uid=None, key=None):
257
- """Is the UID a patient, study, series or dataset"""
258
-
259
- if uid is None:
260
- return None
261
- if uid == 'Database':
262
- return uid
263
-
264
- if key is None:
265
- df = self.register
266
- type = df.columns[df.isin([uid]).any()].values
267
- if type.size == 0: # uid does not exists in the database
268
- return None
269
- else:
270
- type = type[0]
271
- else:
272
- df = self.register.loc[key,:]
273
- type = df[df.isin([uid])].index[0]
274
-
275
- if type == 'PatientID':
276
- return 'Patient'
277
- if type == 'StudyInstanceUID':
278
- return 'Study'
279
- if type == 'SeriesInstanceUID':
280
- return 'Series'
281
- if type == 'SOPInstanceUID':
282
- return 'Instance'
283
-
284
-
285
- def tree(self, depth=3):
286
-
287
- df = self.register
288
- if df is None:
289
- raise ValueError('Cannot build tree - no database open')
290
- df = df[df.removed == False]
291
- df.sort_values(['PatientName','StudyDate','SeriesNumber','InstanceNumber'], inplace=True)
292
-
293
- database = {'uid': self.path}
294
- database['patients'] = []
295
- for uid_patient in df.PatientID.dropna().unique():
296
- patient = {'uid': uid_patient}
297
- database['patients'].append(patient)
298
- if depth >= 1:
299
- df_patient = df[df.PatientID == uid_patient]
300
- patient['key'] = df_patient.index[0]
301
- patient['studies'] = []
302
- for uid_study in df_patient.StudyInstanceUID.dropna().unique():
303
- study = {'uid': uid_study}
304
- patient['studies'].append(study)
305
- if depth >= 2:
306
- df_study = df_patient[df_patient.StudyInstanceUID == uid_study]
307
- study['key'] = df_study.index[0]
308
- study['series'] = []
309
- for uid_sery in df_study.SeriesInstanceUID.dropna().unique():
310
- series = {'uid': uid_sery}
311
- study['series'].append(series)
312
- if depth == 3:
313
- df_series = df_study[df_study.SeriesInstanceUID == uid_sery]
314
- series['key'] = df_series.index[0]
315
- return database
316
-
317
-
318
- def keys(self,
319
- uid = None,
320
- patient = None,
321
- study = None,
322
- series = None,
323
- instance = None,
324
- dropna = False):
325
- """Return a list of indices for all dicom datasets managed by the index.
326
-
327
- These indices are strings with unique relative paths
328
- that either link to an existing file in the database or can be used for
329
- writing a database that is in memory.
330
- """
331
-
332
- df = self.register
333
- if df is None:
334
- raise ValueError('Cant return dicom files - no database open')
335
-
336
- # If no arguments are provided
337
- if (uid is None) & (patient is None) & (study is None) & (series is None) & (instance is None):
338
- return []
339
-
340
- if isinstance(uid, list):
341
- if 'Database' in uid:
342
- return self.keys('Database', dropna=dropna)
343
-
344
- not_deleted = df.removed == False
345
-
346
- if uid == 'Database':
347
- keys = not_deleted[not_deleted].index.tolist()
348
- if dropna:
349
- keys = [key for key in keys if self.register.at[key,'SOPInstanceUID'] is not None]
350
- return keys
351
-
352
- # If arguments are provided, create a list of unique datasets
353
- # keys = []
354
- if uid is not None:
355
- if not isinstance(uid, list):
356
- uid = [uid]
357
- uid = [i for i in uid if i is not None]
358
- rows = np.isin(df, uid).any(axis=1) & not_deleted
359
- if patient is not None:
360
- if not isinstance(patient, list):
361
- rows = (df.PatientID==patient) & not_deleted
362
- else:
363
- patient = [i for i in patient if i is not None]
364
- rows = df.PatientID.isin(patient) & not_deleted
365
- if study is not None:
366
- if not isinstance(study, list):
367
- rows = (df.StudyInstanceUID==study) & not_deleted
368
- else:
369
- study = [i for i in study if i is not None]
370
- rows = df.StudyInstanceUID.isin(study) & not_deleted
371
- if series is not None:
372
- if not isinstance(series, list):
373
- rows = (df.SeriesInstanceUID==series) & not_deleted
374
- else:
375
- series = [i for i in series if i is not None]
376
- rows = df.SeriesInstanceUID.isin(series) & not_deleted
377
- if instance is not None:
378
- if not isinstance(instance, list):
379
- rows = (df.SOPInstanceUID==instance) & not_deleted
380
- else:
381
- instance = [i for i in instance if i is not None]
382
- rows = df.SOPInstanceUID.isin(instance) & not_deleted
383
-
384
- keys = df.index[rows].tolist()
385
- if dropna:
386
- keys = [key for key in keys if self.register.at[key,'SOPInstanceUID'] is not None]
387
- return keys
388
-
389
- def value(self, key, column):
390
- try:
391
- if isinstance(key, pd.Index):
392
- return self.register.loc[key, column].values
393
- if not isinstance(key, list) and not isinstance(column, list):
394
- return self.register.at[key, column]
395
- else:
396
- return self.register.loc[key, column].values
397
- except:
398
- return None
399
-
400
- def parent(self, uid=None):
401
- # For consistency with other definitions
402
- # Allow uid to be list and return list if multiple parents are found
403
- """Returns the UID of the parent object"""
404
-
405
- keys = self.keys(uid)
406
- if keys == []:
407
- return None
408
- row = self.register.loc[keys[0]].values.tolist()
409
- i = row.index(uid)
410
- if self.columns[i] == 'PatientID':
411
- return 'Database'
412
- else:
413
- return row[i-1]
414
-
415
-
416
- def filter(self, uids=None, **kwargs):
417
- uids = [id for id in uids if id is not None]
418
- if not kwargs:
419
- return uids
420
- vals = list(kwargs.values())
421
- attr = list(kwargs.keys())
422
- return [id for id in uids if self.get_values(attr, uid=id) == vals]
423
- #return [id for id in uids if function(self.get_values(attr, uid=id), vals)]
424
-
425
-
426
- def filter_instances(self, df, **kwargs):
427
- df.dropna(inplace=True)
428
- if not kwargs:
429
- return df
430
- vals = list(kwargs.values())
431
- attr = list(kwargs.keys())
432
- keys = [key for key in df.index if self.get_values(attr, [key]) == vals]
433
- return df[keys]
434
-
435
-
436
- def instances(self, uid=None, keys=None, sort=True, sortby=None, images=False, **kwargs):
437
- if keys is None:
438
- keys = self.keys(uid)
439
- if sort:
440
- if sortby is None:
441
- sortby = ['PatientName', 'StudyDescription', 'SeriesNumber', 'InstanceNumber']
442
- df = self.register.loc[keys, sortby + ['SOPInstanceUID']]
443
- df.sort_values(sortby, inplace=True)
444
- df = df.SOPInstanceUID
445
- else:
446
- df = self.register.loc[keys,'SOPInstanceUID']
447
- df = self.filter_instances(df, **kwargs)
448
- if images == True:
449
- keys = [key for key in df.index if self.get_values('Rows', [key]) is not None]
450
- df = df[keys]
451
- return df
452
-
453
-
454
- def series(self, uid=None, keys=None, sort=True, sortby=['PatientName', 'StudyDescription', 'SeriesNumber'], **kwargs):
455
- if keys is None:
456
- keys = self.keys(uid)
457
- if sort:
458
- if not isinstance(sortby, list):
459
- sortby = [sortby]
460
- df = self.register.loc[keys, sortby + ['SeriesInstanceUID']]
461
- df.sort_values(sortby, inplace=True)
462
- df = df.SeriesInstanceUID
463
- else:
464
- df = self.register.loc[keys,'SeriesInstanceUID']
465
- uids = df.unique().tolist()
466
- return self.filter(uids, **kwargs)
467
-
468
-
469
- def studies(self, uid=None, keys=None, sort=True, sortby=['PatientName', 'StudyDescription'], **kwargs):
470
- if keys is None:
471
- keys = self.keys(uid)
472
- if sort:
473
- df = self.register.loc[keys, sortby + ['StudyInstanceUID']]
474
- df.sort_values(sortby, inplace=True)
475
- df = df.StudyInstanceUID
476
- else:
477
- df = self.register.loc[keys,'StudyInstanceUID']
478
- uids = df.unique().tolist()
479
- return self.filter(uids, **kwargs)
480
-
481
-
482
- def patients(self, uid=None, keys=None, sort=True, sortby=['PatientName'], **kwargs):
483
- if keys is None:
484
- keys = self.keys(uid)
485
- if sort:
486
- df = self.register.loc[keys, sortby + ['PatientID']]
487
- df.sort_values(sortby, inplace=True)
488
- df = df.PatientID
489
- else:
490
- df = self.register.loc[keys,'PatientID']
491
- uids = df.unique().tolist()
492
- return self.filter(uids, **kwargs)
493
-
494
-
495
- def get_instance_dataset(self, key):
496
-
497
- """Gets a datasets for a single instance
498
-
499
- Datasets in memory will be returned.
500
- If they are not in memory, and the database exists on disk, they will be read from disk.
501
- If they are not in memory, and the database does not exist on disk, an exception is raised.
502
- """
503
- if key in self.dataset:
504
- # If in memory, get from memory
505
- return self.dataset[key]
506
- # If not in memory, read from disk
507
- file = self.filepath(key)
508
- if file is None: # No dataset assigned yet
509
- return
510
- if not os.path.exists(file): # New instance, series, study or patient
511
- return
512
- return read_dataset(file, self.dialog)
513
-
514
-
515
- def get_dataset(self, uid, keys=None, message=None):
516
- """Gets a list of datasets for a single record
517
-
518
- Datasets in memory will be returned.
519
- If they are not in memory, and the database exists on disk, they will be read from disk.
520
- If they are not in memory, and the database does not exist on disk, an exception is raised.
521
- """
522
- if uid is None: # empty record
523
- return
524
- if keys is None:
525
- keys = self.keys(uid)
526
- dataset = []
527
- for key in keys:
528
- ds = self.get_instance_dataset(key)
529
- dataset.append(ds)
530
- if self.type(uid, keys[0]) == 'Instance':
531
- if dataset == []:
532
- return
533
- else:
534
- return dataset[0]
535
- else:
536
- return dataset
537
-
538
-
539
- def _get_values(self, keys, attr):
540
- """Helper function"""
541
-
542
- #ds = self._get_dataset(instances)
543
- ds = None
544
- for key in keys:
545
- ds = self.get_instance_dataset(key)
546
- if ds is not None:
547
- break
548
- if ds is None:
549
- return [None] * len(attr)
550
- else:
551
- return ds.get_values(attr)
552
-
553
-
554
- def series_header(self, key):
555
- """Attributes and values inherited from series, study and patient"""
556
-
557
- attr_patient = ['PatientID', 'PatientName']
558
- attr_study = ['StudyInstanceUID', 'StudyDescription', 'StudyDate']
559
- attr_series = ['SeriesInstanceUID', 'SeriesDescription', 'SeriesNumber']
560
-
561
- parent = self.register.at[key, 'SeriesInstanceUID']
562
- keys = self.keys(series=parent, dropna=True)
563
- if keys != []:
564
- attr = list(set(dbdataset.module_patient() + dbdataset.module_study() + dbdataset.module_series()))
565
- vals = self._get_values(keys, attr)
566
- else:
567
- parent = self.register.at[key, 'StudyInstanceUID']
568
- keys = self.keys(study=parent, dropna=True)
569
- if keys != []:
570
- attr = list(set(dbdataset.module_patient() + dbdataset.module_study()))
571
- vals = self._get_values(keys, attr)
572
- attr += attr_series
573
- vals += self.value(key, attr_series).tolist()
574
- else:
575
- parent = self.register.at[key, 'PatientID']
576
- keys = self.keys(patient=parent, dropna=True)
577
- if keys != []:
578
- attr = dbdataset.module_patient()
579
- vals = self._get_values(keys, attr)
580
- attr += attr_study + attr_series
581
- vals += self.value(key, attr_study + attr_series).tolist()
582
- else:
583
- attr = attr_patient + attr_study + attr_series
584
- vals = self.value(key, attr).tolist()
585
- return attr, vals
586
-
587
-
588
- def study_header(self, key):
589
- """Attributes and values inherited from series, study and patient"""
590
-
591
- attr_patient = ['PatientID', 'PatientName']
592
- attr_study = ['StudyInstanceUID', 'StudyDescription', 'StudyDate']
593
-
594
- parent = self.register.at[key, 'StudyInstanceUID']
595
- keys = self.keys(study=parent, dropna=True)
596
- if keys != []:
597
- attr = list(set(dbdataset.module_patient() + dbdataset.module_study()))
598
- vals = self._get_values(keys, attr)
599
- else:
600
- parent = self.register.at[key, 'PatientID']
601
- keys = self.keys(patient=parent, dropna=True)
602
- if keys != []:
603
- attr = dbdataset.module_patient()
604
- vals = self._get_values(keys, attr)
605
- attr += attr_study
606
- vals += self.value(key, attr_study).tolist()
607
- else:
608
- attr = attr_patient + attr_study
609
- vals = self.value(key, attr).tolist()
610
- return attr, vals
611
-
612
-
613
- def patient_header(self, key):
614
- """Attributes and values inherited from series, study and patient"""
615
-
616
- attr_patient = ['PatientID', 'PatientName']
617
-
618
- parent = self.register.at[key, 'PatientID']
619
- keys = self.keys(patient=parent, dropna=True)
620
- if keys != []:
621
- attr = dbdataset.module_patient()
622
- vals = self._get_values(keys, attr)
623
- else:
624
- attr = attr_patient
625
- vals = self.value(key, attr).tolist()
626
- return attr, vals
627
-
628
-
629
- def label(self, uid=None, key=None, type=None):
630
- """Return a label to describe a row as Patient, Study, Series or Instance"""
631
-
632
- if self.register is None:
633
- raise ValueError('Cant provide labels - no database open')
634
-
635
- if uid is None:
636
- if key is None:
637
- return ''
638
-
639
- if uid == 'Database':
640
- if self.path is None:
641
- return 'Database [in memory]'
642
- else:
643
- return 'Database [' + self.path + ']'
644
-
645
- if type is None:
646
- type = self.type(uid)
647
-
648
- if type == 'Patient':
649
- if key is None:
650
- key = self.keys(patient=uid)[0]
651
- row = self.register.loc[key]
652
- name = row.PatientName
653
- #id = row.PatientID
654
- label = str(name)
655
- #label += ' [' + str(id) + ']'
656
- return type + " {}".format(label)
657
- if type == 'Study':
658
- if key is None:
659
- key = self.keys(study=uid)[0]
660
- row = self.register.loc[key]
661
- descr = row.StudyDescription
662
- date = row.StudyDate
663
- label = str(descr)
664
- label += ' [' + str(date) + ']'
665
- return type + " {}".format(label)
666
- if type == 'Series':
667
- if key is None:
668
- key = self.keys(series=uid)[0]
669
- row = self.register.loc[key]
670
- descr = row.SeriesDescription
671
- nr = row.SeriesNumber
672
- label = str(nr).zfill(3)
673
- label += ' [' + str(descr) + ']'
674
- return type + " {}".format(label)
675
- if type == 'Instance':
676
- if key is None:
677
- key = self.keys(instance=uid)[0]
678
- row = self.register.loc[key]
679
- nr = row.InstanceNumber
680
- label = str(nr).zfill(6)
681
- return SOPClass(row.SOPClassUID) + " {}".format(label)
682
-
683
-
684
- def print_database(self):
685
- print('---------- DATABASE --------------')
686
- if self.path is None:
687
- print('Location: ', 'In memory')
688
- else:
689
- print('Location: ', self.path)
690
- for patient in self.patients('Database'):
691
- print(' ' + self.label(patient, type='Patient'))
692
- for study in self.studies(patient):
693
- print(' ' + self.label(study, type='Study'))
694
- for series in self.series(study):
695
- print(' ' + self.label(series, type='Series'))
696
- print(' Nr of instances: ' + str(len(self.instances(series))))
697
- print('----------------------------------')
698
-
699
-
700
- def print_patient(self, patient):
701
- print('---------- PATIENT -------------')
702
- print('' + self.label(patient, type='Patient'))
703
- for study in self.studies(patient):
704
- print(' ' + self.label(study, type='Study'))
705
- for series in self.series(study):
706
- print(' ' + self.label(series, type='Series'))
707
- print(' Nr of instances: ' + str(len(self.instances(series))))
708
- print('--------------------------------')
709
-
710
-
711
- def print_study(self, study):
712
- print('---------- STUDY ---------------')
713
- print('' + self.label(study, type='Study'))
714
- for series in self.series(study):
715
- print(' ' + self.label(series, type='Series'))
716
- print(' Nr of instances: ' + str(len(self.instances(series))))
717
- print('--------------------------------')
718
-
719
-
720
- def print_series(self, series):
721
- print('---------- SERIES --------------')
722
- instances = self.instances(series)
723
- print('' + self.label(series, type='Series'))
724
- print(' Nr of instances: ' + str(len(instances)))
725
- for instance in self.instances(series):
726
- print(' ' + self.label(instance, type='Instance'))
727
- print('--------------------------------')
728
-
729
-
730
- def print_instance(self, instance):
731
- print('---------- INSTANCE -------------')
732
- print('' + self.label(instance, type='Instance'))
733
- print('--------------------------------')
734
-
735
-
736
- def print(self, uid='Database', name='Database'):
737
- if name=='Database':
738
- self.print_database()
739
- elif name=='PatientID':
740
- self.print_patient(uid)
741
- elif name=='StudyInstanceUID':
742
- self.print_study(uid)
743
- elif name=='SeriesInstanceUID':
744
- self.print_series(uid)
745
- elif name=='SOPInstanceUID':
746
- self.print_instance(uid)
747
-
748
-
749
- def read(self, *args, keys=None, message=None, **kwargs):
750
- """Read the dataset from disk.
751
- """
752
- if keys is None:
753
- keys = self.keys(*args, **kwargs)
754
- for i, key in enumerate(keys):
755
- #if message is not None:
756
- # self.status.progress(i, len(keys), message)
757
- # do not read if they are already in memory
758
- # this could overwrite changes made in memory only
759
- if not key in self.dataset:
760
- instance_uid = self.value(key, 'SOPInstanceUID')
761
- ds = self.get_dataset(instance_uid, [key])
762
- if ds is not None:
763
- self.dataset[key] = ds
764
-
765
- def write(self, *args, keys=None, message=None, **kwargs):
766
- """Writing data from memory to disk.
767
-
768
- This does nothing if the data are not in memory, or if the database does not exist on disk.
769
- """
770
- if keys is None:
771
- keys = self.keys(*args, **kwargs)
772
- for i, key in enumerate(keys):
773
- if key in self.dataset:
774
- file = self.filepath(key)
775
- if file is not None:
776
- self.dataset[key].write(file, self.status)
777
-
778
- def clear(self, *args, keys=None, **kwargs):
779
- """Clear all data from memory"""
780
-
781
- # Instances are only cleared from memory if the database exists on disk.
782
- if self.path is None:
783
- return
784
-
785
- if keys is None:
786
- keys = self.keys(*args, **kwargs)
787
- # write to disk first so that any changes made in memory are not lost
788
- self.write(*args, keys=keys, **kwargs)
789
- # then delete the instances from memory
790
- for key in keys:
791
- self.dataset.pop(key, None)
792
-
793
- def close(self):
794
- """Close an open database.
795
- """
796
-
797
- #if not self.is_open():
798
- if self.register is None:
799
- return True
800
- # This is the case where the database exists in memory only
801
- # Needs testing..
802
- if self.path is None:
803
- reply = self.dialog.question(
804
- title = 'Closing DICOM folder',
805
- message = 'Save changes before closing?',
806
- cancel = True,
807
- )
808
- if reply == "Cancel":
809
- return False
810
- elif reply == "Yes":
811
- path = self.dialog.directory('Please enter the full path to an existing folder')
812
- if path is None:
813
- return False
814
- self.path = path
815
- self.save()
816
- #self.save('Database')
817
- return self.close()
818
- elif reply == "No":
819
- return True
820
-
821
- if not self.is_saved():
822
- reply = self.dialog.question(
823
- title = 'Closing DICOM folder',
824
- message = 'Save changes before closing?',
825
- cancel = True,
826
- )
827
- if reply == "Cancel":
828
- return False
829
- if reply == "Yes":
830
- self.save()
831
- #self.save('Database')
832
- elif reply == "No":
833
- self.restore()
834
-
835
- self._write_df()
836
- self.write()
837
- self.register = None
838
- self.path = None
839
- return True
840
-
841
-
842
- def is_saved(self):
843
- """Check if the folder is saved.
844
-
845
- Returns:
846
- True if the folder is saved and False otherwise.
847
- """
848
- # Needs a formal test for completeness
849
- if (self.register.removed==True).any():
850
- return False
851
- if (self.register.created==True).any():
852
- return False
853
- return True
854
-
855
- def is_open(self):
856
- """Check if a database is currently open, either in memory or on disk
857
-
858
- Returns:
859
- True if a database is open and False otherwise.
860
- """
861
- # Needs a formal test for completeness
862
- return self.register is not None
863
-
864
-
865
- def delete(self, *args, keys=None, **kwargs):
866
- """Deletes some datasets
867
-
868
- Deleted datasets are stashed and can be recovered with restore()
869
- Using save() will delete them permanently
870
- """
871
- if keys is None:
872
- keys = self.keys(*args, **kwargs)
873
- self.register.loc[keys,'removed'] = True
874
-
875
-
876
- def save(self, rows=None):
877
-
878
- self.status.message('Saving changes..')
879
-
880
- created = self.register.created & (self.register.removed==False)
881
- removed = self.register.removed
882
- if rows is not None:
883
- created = created & rows
884
- removed = removed & rows
885
- created = created[created].index
886
- removed = removed[removed].index
887
-
888
- # delete datasets marked for removal
889
- for key in removed.tolist():
890
- # delete in memory
891
- if key in self.dataset:
892
- del self.dataset[key]
893
- # delete on disk
894
- file = self.filepath(key)
895
- if file is not None:
896
- if os.path.exists(file):
897
- os.remove(file)
898
- # and drop then from the dataframe
899
- self.register.drop(index=removed, inplace=True)
900
-
901
- # for new or edited data, mark as saved.
902
- self.register.loc[created, 'created'] = False
903
-
904
- self._write_df()
905
- self.write()
906
-
907
-
908
- def restore(self, rows=None):
909
-
910
- created = self.register.created
911
- removed = self.register.removed & (self.register.created==False)
912
- if rows is not None:
913
- created = created & rows
914
- removed = removed & rows
915
- created = created[created].index
916
- removed = removed[removed].index
917
-
918
- # permanently delete newly created datasets
919
- for key in created.tolist():
920
- # delete in memory
921
- if key in self.dataset:
922
- del self.dataset[key]
923
- # if on disk, delete files
924
- file = self.filepath(key)
925
- if file is not None:
926
- if os.path.exists(file):
927
- os.remove(file)
928
- self.register.drop(index=created, inplace=True)
929
-
930
- # Restore those that were marked for removal
931
- self.register.loc[removed, 'removed'] = False
932
-
933
- self._write_df()
934
- # self.write()
935
-
936
-
937
- def new_row(self, data, key=None):
938
- if key is None:
939
- key = self.new_key()
940
- if key in self.register.index:
941
- self.register.loc[key,self.columns] = data
942
- else:
943
- df = pd.DataFrame([data], [key], columns=self.columns)
944
- df['removed'] = False
945
- df['created'] = True
946
- try:
947
- self.register = pd.concat([self.register, df])
948
- except:
949
- msg = 'Cannot update the header \n'
950
- msg += 'Some of the new values are of the incorrect type.\n'
951
- raise TypeError(msg)
952
- return key
953
-
954
- def delete_row(self, key):
955
- if self.register.at[key, 'created']:
956
- # If the row was newly created, it can be dropped
957
- self.register.drop(index=key, inplace=True)
958
- else:
959
- # If this is the first modification, mark for removal
960
- self.register.at[key, 'removed'] == True
961
-
962
-
963
- def drop_placeholder_row(self, parent_key, missing='SOPInstanceUID'):
964
- # If a parent has more than one children, and one of them is None, then delete that row.
965
- if missing == 'SOPInstanceUID':
966
- parent_uid = self.value(parent_key, 'SeriesInstanceUID')
967
- parent_keys = self.keys(series=parent_uid)
968
- elif missing == 'SeriesInstanceUID':
969
- parent_uid = self.value(parent_key, 'StudyInstanceUID')
970
- parent_keys = self.keys(study=parent_uid)
971
- elif missing == 'StudyInstanceUID':
972
- parent_uid = self.value(parent_key, 'PatientID')
973
- parent_keys = self.keys(patient=parent_uid)
974
- elif missing == 'PatientID':
975
- parent_keys = self.register.index
976
- if len(parent_keys) > 1:
977
- df = self.register.loc[parent_keys, missing]
978
- empty = df[df.values == None].index
979
- if len(empty) == 1:
980
- self.delete_row(empty[0])
981
-
982
-
983
- def update_row_data(self, key, data):
984
-
985
- # If the row has been created or modified, use existing row
986
- if self.register.at[key, 'created'] == True:
987
- for i, c in enumerate(self.columns): # Same as above but faster
988
- try:
989
- self.register.at[key, c] = data[i]
990
- except:
991
- msg = 'Cannot write header value in register. \n'
992
- msg += 'The value of ' + c +' is of incorrect type.\n'
993
- msg += 'Value: ' + str(data[i])
994
- raise TypeError(msg)
995
-
996
- # If the row has never been modified, save in new row and remove current
997
- else:
998
- self.register.at[key, 'removed'] = True
999
- key = self.new_row(data)
1000
-
1001
- return key
1002
-
1003
-
1004
- def clone_study_data(self, key, **kwargs):
1005
- data = self.default()
1006
- data[0] = self.value(key, 'PatientID')
1007
- data[1] = dbdataset.new_uid()
1008
- data[5] = self.value(key, 'PatientName')
1009
- data[6] = kwargs['StudyDescription'] if 'StudyDescription' in kwargs else 'New Study'
1010
- for val in kwargs:
1011
- if val in self._descriptives:
1012
- data[self._descriptives[val]] = kwargs[val]
1013
- return data
1014
-
1015
- def clone_series_data(self, key, study, **kwargs):
1016
- data = self.register.loc[key, self.columns].values.tolist()
1017
- data[2] = dbdataset.new_uid()
1018
- data[3] = self.default()[3]
1019
- data[4] = self.default()[4]
1020
- data[8] = kwargs['SeriesDescription'] if 'SeriesDescription' in kwargs else 'New Series'
1021
- data[9] = self.new_series_number(study)
1022
- data[10] = self.default()[10]
1023
- for val in kwargs:
1024
- if val in self._descriptives:
1025
- data[self._descriptives[val]] = kwargs[val]
1026
- return data
1027
-
1028
-
1029
- def new_patient(self, parent='Database', **kwargs):
1030
- data = self.default()
1031
- data[0] = dbdataset.new_uid()
1032
- data[5] = kwargs['PatientName'] if 'PatientName' in kwargs else 'New Patient'
1033
- for val in kwargs:
1034
- if val in self._descriptives:
1035
- data[self._descriptives[val]] = kwargs[val]
1036
- key = self.new_row(data)
1037
- return data[0], key
1038
-
1039
-
1040
- def new_study(self, parent=None, key=None, **kwargs):
1041
- if key is None:
1042
- if parent is None:
1043
- parent, key = self.new_patient()
1044
- elif self.type(parent) != 'Patient':
1045
- parent, key = self.new_patient(parent)
1046
- else:
1047
- key = self.keys(patient=parent)[0]
1048
- data = self.clone_study_data(key, **kwargs)
1049
- if self.value(key, 'StudyInstanceUID') is None:
1050
- key = self.update_row_data(key, data)
1051
- else:
1052
- key = self.new_row(data)
1053
- return data[1], key
1054
-
1055
-
1056
- def new_series(self, parent=None, key=None, **kwargs):
1057
- if key is None:
1058
- if parent is None:
1059
- parent, key = self.new_study()
1060
- elif self.type(parent) != 'Study':
1061
- #parent = self.studies(parent)[0]
1062
- parent, key = self.new_study(parent)
1063
- else:
1064
- key = self.keys(study=parent)[0]
1065
- data = self.clone_series_data(key, parent, **kwargs)
1066
- if self.value(key, 'SeriesInstanceUID') is None:
1067
- key = self.update_row_data(key, data) # Empty study
1068
- else:
1069
- key = self.new_row(data) # Study with existing series
1070
- return data[2], key
1071
-
1072
-
1073
- def new_instance(self, parent=None, dataset=None, key=None, **kwargs):
1074
-
1075
- if key is None:
1076
- if parent is None:
1077
- parent, key = self.new_series()
1078
- keys = self.keys(series=parent)
1079
- elif self.type(parent) != 'Series':
1080
- # parent = self.series(parent)[0]
1081
- parent, key = self.new_series(parent)
1082
- keys = self.keys(series=parent)
1083
- else:
1084
- keys = self.keys(series=parent)
1085
- key = keys[0]
1086
- else:
1087
- if parent is None:
1088
- parent = self.register.at[key, 'SeriesInstanceUID']
1089
- keys = self.keys(series=parent)
1090
-
1091
- # Find largest instance number
1092
- n = self.register.loc[keys,'InstanceNumber'].values
1093
- n = n[n != -1]
1094
- max_number=0 if n.size==0 else np.amax(n)
1095
-
1096
- # Populate attributes in index file
1097
- data = self.value(key, self.columns)
1098
- data[3] = dbdataset.new_uid()
1099
- data[4] = self.default()[4]
1100
- #data[10] = 1 + len(self.instances(parent))
1101
- #data[10] = 1 + len(self.instances(keys=self.keys(series=parent)))
1102
- data[10] = 1 + max_number
1103
- for val in kwargs:
1104
- if val in self._descriptives:
1105
- data[self._descriptives[val]] = kwargs[val]
1106
-
1107
- if self.value(key, 'SOPInstanceUID') is None:
1108
- # Empty series
1109
- key = self.update_row_data(key, data)
1110
- else:
1111
- # Series with existing instances
1112
- key = self.new_row(data)
1113
-
1114
- if dataset is not None:
1115
- self.set_instance_dataset(data[3], dataset, key)
1116
-
1117
- return data[3], key
1118
-
1119
-
1120
- def set_instance_dataset(self, instance, ds, key=None):
1121
-
1122
- if isinstance(ds, list):
1123
- if len(ds) > 1:
1124
- raise ValueError('Cannot set multiple datasets to a single instance')
1125
- else:
1126
- ds = ds[0]
1127
- if key is None:
1128
- keys = self.keys(instance)
1129
- if keys == []: # instance does not exist
1130
- return
1131
- key = keys[0]
1132
-
1133
- data = self.register.loc[key, self.columns]
1134
- data[4] = ds.SOPClassUID
1135
- key = self.update_row_data(key, data)
1136
- ds.set_values(self.columns, data)
1137
- self.dataset[key] = ds
1138
- return key
1139
-
1140
-
1141
- def set_dataset(self, uid, dataset, keys=None):
1142
-
1143
- if keys is None:
1144
- parent_keys = self.keys(uid)
1145
- else:
1146
- parent_keys = keys
1147
-
1148
- # LOOKUP!!!
1149
- # ELIMINATE
1150
- if self.type(uid, parent_keys[0]) == 'Instance':
1151
- self.set_instance_dataset(uid, dataset, parent_keys[0])
1152
- return
1153
-
1154
- if not isinstance(dataset, list):
1155
- dataset = [dataset]
1156
-
1157
- attr, vals = self.series_header(parent_keys[0])
1158
- instances = self.value(parent_keys, 'SOPInstanceUID').tolist()
1159
-
1160
- for ds in dataset:
1161
- try:
1162
- ind = instances.index(ds.SOPInstanceUID)
1163
- except:
1164
- #If there is no corresponding instance, save dataset in new instance
1165
-
1166
- # Set parent modules
1167
- ds.set_values(attr, vals)
1168
-
1169
- # Create updated row data
1170
- key = parent_keys[0]
1171
- data = self.value(key, self.columns)
1172
- data[3] = dbdataset.new_uid()
1173
- data[4] = ds.SOPClassUID
1174
- nrs = self.value(parent_keys, 'InstanceNumber')
1175
- nrs = [n for n in nrs if n != -1]
1176
- if nrs == []:
1177
- data[10] = 1
1178
- else:
1179
- data[10] = 1 + max(nrs)
1180
-
1181
- # Add to database in memory as a new row
1182
- key = self.new_row(data)
1183
- ds.set_values(self.columns, data)
1184
- self.dataset[key] = ds
1185
-
1186
- else: # If the instance is already in the object
1187
-
1188
- key = parent_keys[ind]
1189
- data = self.value(key, self.columns)
1190
- data[4] = ds.SOPClassUID
1191
- key = self.update_row_data(key, data)
1192
- self.dataset[key] = ds
1193
-
1194
- # If the series is empty and new instances have been added then delete the row
1195
- self.drop_placeholder_row(parent_keys[0], missing='SOPInstanceUID')
1196
-
1197
-
1198
-
1199
- def delete_studies(self, studies: list):
1200
- """Delete a list of studies"""
1201
-
1202
- for study in studies:
1203
- keys = self.keys(study=study)
1204
- self.register.loc[keys,'removed'] = True
1205
- # If this was the last study in the patient
1206
- # keep the patient as an empty patient
1207
- patient = self.register.at[keys[0], 'PatientID']
1208
- patient = (self.register.removed == False) & (self.register.PatientID == patient)
1209
- patient_studies = self.register.StudyInstanceUID[patient]
1210
- patient_studies_cnt = len(patient_studies.unique())
1211
- if patient_studies_cnt == 0:
1212
- row = self.default()
1213
- row[0] = self.register.at[keys[0], 'PatientID']
1214
- row[5] = self.register.at[keys[0], 'PatientName']
1215
- self.new_row(row)
1216
-
1217
-
1218
- def delete_series(self, series: list):
1219
- """Delete a list of series"""
1220
-
1221
- for sery in series:
1222
- keys = self.keys(series=sery)
1223
- self.register.loc[keys,'removed'] = True
1224
- # If this was the last series in the study
1225
- # keep the study as an empty study
1226
- study = self.register.at[keys[0], 'StudyInstanceUID']
1227
- study = (self.register.removed == False) & (self.register.StudyInstanceUID == study)
1228
- study_series = self.register.SeriesInstanceUID[study]
1229
- study_series_cnt = len(study_series.unique())
1230
- if study_series_cnt == 0:
1231
- row = self.default()
1232
- row[0] = self.register.at[keys[0], 'PatientID']
1233
- row[1] = self.register.at[keys[0], 'StudyInstanceUID']
1234
- row[5] = self.register.at[keys[0], 'PatientName']
1235
- row[6] = self.register.at[keys[0], 'StudyDescription']
1236
- row[7] = self.register.at[keys[0], 'StudyDate']
1237
- self.new_row(row)
1238
-
1239
-
1240
- def new_key(self):
1241
- # Generate a new key
1242
- return os.path.join('dbdicom', dbdataset.new_uid() + '.dcm')
1243
-
1244
-
1245
- def copy_instance_to_series(self, instance_key, target_keys, tmp, **kwargs):
1246
- """Copy instances to another series"""
1247
-
1248
- attributes, values = self.series_header(target_keys[0])
1249
- self.append_kwargs(kwargs, attributes, values)
1250
-
1251
- n = self.register.loc[target_keys,'InstanceNumber'].values
1252
- n = n[n != -1]
1253
- max_number=0 if n.size==0 else np.amax(n)
1254
-
1255
- new_instance = dbdataset.new_uid()
1256
- new_key = self.new_key()
1257
- ds = self.get_instance_dataset(instance_key)
1258
-
1259
- if ds is None:
1260
- row = self.value(instance_key, self.columns).tolist()
1261
- row = self.copy_series_data(target_keys[0], row)
1262
- row[3] = new_instance
1263
- row[10] = 1 + max_number
1264
- for val in kwargs:
1265
- if val in self._descriptives:
1266
- row[self._descriptives[val]] = kwargs[val]
1267
- else:
1268
- if instance_key in self.dataset:
1269
- ds = copy.deepcopy(ds)
1270
- self.dataset[new_key] = ds
1271
- ds.set_values(
1272
- attributes + ['SOPInstanceUID', 'InstanceNumber'],
1273
- values + [new_instance, 1+max_number])
1274
- if not instance_key in self.dataset:
1275
- ds.write(self.filepath(new_key), self.status)
1276
- row = ds.get_values(self.columns)
1277
-
1278
- self.drop_placeholder_row(target_keys[0], missing='SOPInstanceUID')
1279
- self.new_row(row, new_key)
1280
-
1281
- return new_instance
1282
-
1283
- def new_instance_number(self, series):
1284
- series_keys = self.keys(series=series)
1285
- n = self.register.loc[series_keys,'InstanceNumber'].values
1286
- n = n[n != -1]
1287
- max_number=0 if n.size==0 else np.amax(n)
1288
- return max_number + 1
1289
-
1290
- def copy_to_series(self, uids, target, **kwargs):
1291
- """Copy instances to another series"""
1292
-
1293
- target_keys = self.keys(series=target)
1294
-
1295
- attributes, values = self.series_header(target_keys[0])
1296
- self.append_kwargs(kwargs, attributes, values)
1297
-
1298
- max_number = self.new_instance_number(target)
1299
- keys = self.keys(uids)
1300
- new_instances = dbdataset.new_uid(len(keys))
1301
-
1302
- for i, key in enumerate(keys):
1303
-
1304
- if len(keys) > 1:
1305
- self.status.progress(i+1, len(keys), message='Copying to series..')
1306
-
1307
- new_key = self.new_key()
1308
- instance_uid = self.value(key, 'SOPInstanceUID')
1309
- ds = self.get_dataset(instance_uid, [key])
1310
- if ds is None:
1311
- row = self.value(key, self.columns).tolist()
1312
- row = self.copy_series_data(target_keys[0], row)
1313
- row[3] = new_instances[i]
1314
- row[10] = i + max_number
1315
- for val in kwargs:
1316
- if val in self._descriptives:
1317
- row[self._descriptives[val]] = kwargs[val]
1318
- else:
1319
- if key in self.dataset:
1320
- ds = copy.deepcopy(ds)
1321
- self.dataset[new_key] = ds
1322
- ds.set_values(
1323
- attributes + ['SOPInstanceUID', 'InstanceNumber'],
1324
- values + [new_instances[i], i + max_number])
1325
- if not key in self.dataset:
1326
- ds.write(self.filepath(new_key), self.status)
1327
- row = ds.get_values(self.columns)
1328
-
1329
- # Add new data for the dataframe
1330
- self.new_row(row, new_key)
1331
-
1332
- # If the series is empty and new instances have been added, then delete the row
1333
- self.drop_placeholder_row(target_keys[0], missing='SOPInstanceUID')
1334
-
1335
- if len(keys) > 1:
1336
- self.status.hide()
1337
-
1338
- if len(new_instances) == 1:
1339
- return new_instances[0]
1340
- else:
1341
- return new_instances
1342
-
1343
-
1344
- def new_series_number(self, study):
1345
- study_keys = self.keys(study=study)
1346
- n = self.value(study_keys, 'SeriesNumber')
1347
- n = n[n != -1]
1348
- max_number=0 if n.size==0 else np.amax(n)
1349
- return max_number + 1
1350
-
1351
-
1352
- def copy_to_study(self, uid, target, **kwargs):
1353
- """Copy series to another study"""
1354
-
1355
- target_keys = self.keys(study=target)
1356
- target_key = target_keys[0]
1357
- attributes, values = self.study_header(target_key)
1358
- self.append_kwargs(kwargs, attributes, values)
1359
-
1360
- max_number = self.new_series_number(target)
1361
- all_series = self.series(uid)
1362
- new_series = dbdataset.new_uid(len(all_series))
1363
-
1364
- for s, series in enumerate(all_series):
1365
-
1366
- new_number = s + max_number
1367
- series_keys = self.keys(series=series)
1368
- for k, key in enumerate(series_keys):
1369
-
1370
- msg = 'Copying series ' + self.value(key, 'SeriesDescription')
1371
- msg += ' (' + str(s+1) + '/' + str(len(all_series)) + ')'
1372
- self.status.progress(k+1, len(series_keys), msg)
1373
-
1374
- new_key = self.new_key()
1375
- instance_uid = self.value(key, 'SOPInstanceUID')
1376
- ds = self.get_dataset(instance_uid, [key])
1377
- if ds is None:
1378
- # Fill in any register data provided
1379
- row = self.value(key, self.columns).tolist()
1380
- row = self.copy_study_data(target_key, row)
1381
- row[2] = new_series[s]
1382
- #row[3] = dbdataset.new_uid()
1383
- row[9] = new_number
1384
- for val in kwargs:
1385
- if val in self._descriptives:
1386
- row[self._descriptives[val]] = kwargs[val]
1387
- else:
1388
-
1389
- # If the series exists in memory, create a copy in memory
1390
- if key in self.dataset:
1391
- ds = copy.deepcopy(ds)
1392
- self.dataset[new_key] = ds
1393
-
1394
- # Generate new UIDs
1395
- ds.set_values(
1396
- attributes + ['SeriesInstanceUID', 'SeriesNumber', 'SOPInstanceUID'],
1397
- values + [new_series[s], new_number, dbdataset.new_uid()])
1398
-
1399
- # If the series is not in memory, create a copy on disk
1400
- if not key in self.dataset:
1401
- ds.write(self.filepath(new_key), self.status)
1402
-
1403
- # Get row values to add to dataframe
1404
- row = ds.get_values(self.columns)
1405
-
1406
- # Get new data for the dataframe
1407
- self.new_row(row, new_key)
1408
-
1409
- # Update the dataframe in the index
1410
-
1411
- # If the study is empty and new series have been added
1412
- # then delete the row
1413
- self.drop_placeholder_row(target_key, missing='SeriesInstanceUID')
1414
- self.status.hide()
1415
-
1416
- if len(new_series) == 1:
1417
- return new_series[0]
1418
- else:
1419
- return new_series
1420
-
1421
-
1422
- def copy_to_patient(self, uid, target_key, **kwargs):
1423
- """Copy studies to another patient"""
1424
-
1425
- attributes, values = self.patient_header(target_key)
1426
- self.append_kwargs(kwargs, attributes, values)
1427
-
1428
- all_studies = self.studies(uid)
1429
- new_studies = dbdataset.new_uid(len(all_studies))
1430
-
1431
- for s, study in enumerate(all_studies):
1432
- all_series = self.series(study)
1433
- if all_series == []:
1434
- # Create an empty study
1435
- new_key = self.new_key()
1436
- key = self.keys(study=study)[0]
1437
- row = self.value(key, self.columns).tolist()
1438
- row[0] = self.value(target_key, 'PatientID')
1439
- row[1] = new_studies[s]
1440
- row[5] = self.value(target_key, 'PatientName')
1441
- row[6] = self.value(target_key, 'StudyDescription')
1442
- row[7] = self.value(target_key, 'StudyDate')
1443
- for val in kwargs:
1444
- if val in self._descriptives:
1445
- row[self._descriptives[val]] = kwargs[val]
1446
- # Get new data for the dataframe
1447
- self.new_row(row, new_key)
1448
- for series in all_series:
1449
- new_series_uid = dbdataset.new_uid()
1450
- for key in self.keys(series=series):
1451
- new_key = self.new_key()
1452
- instance_uid = self.value(key, 'SOPInstanceUID')
1453
- ds = self.get_dataset(instance_uid, [key])
1454
- if ds is None:
1455
- row = self.value(key, self.columns).tolist()
1456
- row[0] = self.value(target_key, 'PatientID')
1457
- row[1] = new_studies[s]
1458
- row[2] = new_series_uid
1459
- row[3] = dbdataset.new_uid()
1460
- row[5] = self.value(target_key, 'PatientName')
1461
- for val in kwargs:
1462
- if val in self._descriptives:
1463
- row[self._descriptives[val]] = kwargs[val]
1464
- else:
1465
- if key in self.dataset:
1466
- ds = copy.deepcopy(ds)
1467
- self.dataset[new_key] = ds
1468
- ds.set_values(
1469
- attributes + ['StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID'],
1470
- values + [new_studies[s], new_series_uid, dbdataset.new_uid()])
1471
- if not key in self.dataset:
1472
- ds.write(self.filepath(new_key), self.status)
1473
- row = ds.get_values(self.columns)
1474
-
1475
- # Get new data for the dataframe
1476
- self.new_row(row, new_key)
1477
-
1478
- # If the patient is empty and new studies have been added, then delete the row
1479
- self.drop_placeholder_row(target_key, missing='StudyInstanceUID')
1480
-
1481
- if len(new_studies) == 1:
1482
- return new_studies[0]
1483
- else:
1484
- return new_studies
1485
-
1486
-
1487
- def copy_to_database(self, uid, **kwargs):
1488
- """Copy patient to the database"""
1489
-
1490
- all_patients = self.patients(uid)
1491
- new_patients = dbdataset.new_uid(len(all_patients))
1492
-
1493
- for i, patient in enumerate(all_patients):
1494
- keys = self.keys(patient=patient)
1495
- new_patient_uid = new_patients[i]
1496
- new_patient_name = 'Copy of ' + self.value(keys[0], 'PatientName')
1497
- for study in self.studies(patient):
1498
- new_study_uid = dbdataset.new_uid()
1499
- for sery in self.series(study):
1500
- new_series_uid = dbdataset.new_uid()
1501
- for key in self.keys(series=sery):
1502
- new_instance_uid = dbdataset.new_uid()
1503
- new_key = self.new_key()
1504
- instance_uid = self.value(key, 'SOPInstanceUID')
1505
- ds = self.get_dataset(instance_uid, [key])
1506
- if ds is None:
1507
- row = self.value(key, self.columns).tolist()
1508
- row[0] = new_patient_uid
1509
- row[1] = new_study_uid
1510
- row[2] = new_series_uid
1511
- row[3] = new_instance_uid
1512
- row[5] = new_patient_name
1513
- for val in kwargs:
1514
- if val in self._descriptives:
1515
- row[self._descriptives[val]] = kwargs[val]
1516
- else:
1517
- #TODO: Simplify with set_dataset_values()
1518
- if key in self.dataset:
1519
- ds = copy.deepcopy(ds)
1520
- self.dataset[new_key] = ds
1521
- ds.set_values(
1522
- list(kwargs.keys())+['PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID', 'PatientName'],
1523
- list(kwargs.values())+[new_patient_uid, new_study_uid, new_series_uid, new_instance_uid, new_patient_name])
1524
- if not key in self.dataset:
1525
- ds.write(self.filepath(new_key), self.status)
1526
- row = ds.get_values(self.columns)
1527
-
1528
- # Get new data for the dataframe
1529
- self.new_row(row, new_key)
1530
-
1531
- if len(new_patients) == 1:
1532
- return new_patients[0]
1533
- else:
1534
- return new_patients
1535
-
1536
-
1537
- def copy_series_data(self, key, row):
1538
- row[0] = self.register.at[key, 'PatientID']
1539
- row[1] = self.register.at[key, 'StudyInstanceUID']
1540
- row[2] = self.register.at[key, 'SeriesInstanceUID']
1541
- row[5] = self.register.at[key, 'PatientName']
1542
- row[6] = self.register.at[key, 'StudyDescription']
1543
- row[7] = self.register.at[key, 'StudyDate']
1544
- row[8] = self.register.at[key, 'SeriesDescription']
1545
- row[9] = self.register.at[key, 'SeriesNumber']
1546
- return row
1547
-
1548
-
1549
- def preserve_series_record(self, key):
1550
- # If this is the last instance in the series,
1551
- # keep the series as an empty series.
1552
- source_series = self.register.at[key, 'SeriesInstanceUID']
1553
- source_series = (self.register.removed == False) & (self.register.SeriesInstanceUID == source_series)
1554
- source_series_instances = self.register.SOPInstanceUID[source_series]
1555
- source_series_instances_cnt = source_series_instances.shape[0]
1556
- if source_series_instances_cnt == 1:
1557
- row = self.default()
1558
- row = self.copy_series_data(key, row)
1559
- self.new_row(row)
1560
-
1561
-
1562
- def append_kwargs(self, kwargs, attributes, values):
1563
- for key in kwargs:
1564
- try:
1565
- ind = attributes.index(key)
1566
- except:
1567
- attributes.append(key)
1568
- values.append(kwargs[key])
1569
- else:
1570
- values[ind] = kwargs[key]
1571
-
1572
-
1573
- def move_to_series(self, uid, target, **kwargs):
1574
- """Copy datasets to another series"""
1575
-
1576
- target_keys = self.keys(series=target)
1577
- if target_keys == []:
1578
- msg = 'Moving data to a series that does not exist in the database'
1579
- raise ValueError(msg)
1580
-
1581
- attributes, values = self.series_header(target_keys[0])
1582
- self.append_kwargs(kwargs, attributes, values)
1583
-
1584
- n = self.value(target_keys, 'InstanceNumber')
1585
- n = n[n != -1]
1586
- max_number=0 if n.size==0 else np.amax(n)
1587
-
1588
- keys = self.keys(uid)
1589
-
1590
- for i, key in enumerate(keys):
1591
-
1592
- self.status.progress(i+1, len(keys), message='Moving dataset..')
1593
- self.preserve_series_record(key)
1594
- instance_uid = self.value(key, 'SOPInstanceUID')
1595
- ds = self.get_dataset(instance_uid, [key])
1596
-
1597
- if ds is None:
1598
- row = self.value(key, self.columns).tolist()
1599
- row = self.copy_series_data(target_keys[0], row)
1600
- row[10] = i + 1 + max_number
1601
- for val in kwargs:
1602
- if val in self._descriptives:
1603
- row[self._descriptives[val]] = kwargs[val]
1604
- self.update_row_data(key, row)
1605
- else:
1606
- self.set_dataset_values(ds, key, attributes+['InstanceNumber'], values+[i+1+max_number])
1607
-
1608
- # If the series is empty and new instances have been added, then delete the row
1609
- self.drop_placeholder_row(target_keys[0], 'SOPInstanceUID')
1610
-
1611
- if len(keys) == 1:
1612
- return self.value(keys, 'SOPInstanceUID')
1613
- else:
1614
- return list(self.value(keys, 'SOPInstanceUID'))
1615
-
1616
-
1617
- def copy_study_data(self, key, row):
1618
- row[0] = self.register.at[key, 'PatientID']
1619
- row[1] = self.register.at[key, 'StudyInstanceUID']
1620
- row[5] = self.register.at[key, 'PatientName']
1621
- row[6] = self.register.at[key, 'StudyDescription']
1622
- row[7] = self.register.at[key, 'StudyDate']
1623
- return row
1624
-
1625
-
1626
- def preserve_study_record(self, key):
1627
- # If this is the last series in the study
1628
- # The create a new row for the empty study
1629
- source_study = self.register.at[key, 'StudyInstanceUID']
1630
- source_study_series = (self.register.removed == False) & (self.register.StudyInstanceUID == source_study)
1631
- source_study_series = self.register.SeriesInstanceUID[source_study_series]
1632
- source_study_series_cnt = len(source_study_series.unique())
1633
- if source_study_series_cnt == 1:
1634
- row = self.default()
1635
- row = self.copy_study_data(key, row)
1636
- self.new_row(row)
1637
-
1638
- def move_to_study(self, uid, target, **kwargs):
1639
- """Copy series to another study"""
1640
-
1641
- target_keys = self.keys(study=target)
1642
-
1643
- attributes, values = self.study_header(target_keys[0])
1644
- self.append_kwargs(kwargs, attributes, values)
1645
-
1646
- n = self.value(target_keys, 'SeriesNumber')
1647
- n = n[n != -1]
1648
- max_number=0 if n.size==0 else np.amax(n)
1649
-
1650
- all_series = self.series(uid)
1651
-
1652
- for s, series in enumerate(all_series):
1653
-
1654
- self.status.progress(s+1, len(all_series), message='Moving series..')
1655
- new_number = s + 1 + max_number
1656
- keys = self.keys(series=series)
1657
- self.preserve_study_record(keys[0])
1658
-
1659
- for key in keys:
1660
-
1661
- instance_uid = self.value(key, 'SOPInstanceUID')
1662
- ds = self.get_dataset(instance_uid, [key])
1663
-
1664
- # If the instance is empty, just replace study data in the register.
1665
- if ds is None:
1666
- row = self.value(key, self.columns).tolist()
1667
- row = self.copy_study_data(target_keys[0], row)
1668
- row[9] = new_number
1669
- for val in kwargs:
1670
- if val in self._descriptives:
1671
- row[self._descriptives[val]] = kwargs[val]
1672
- self.update_row_data(key, row)
1673
-
1674
- # Else set the values in the dataset and register.
1675
- else:
1676
- self.set_dataset_values(ds, key, attributes+['SeriesNumber'], values+[new_number])
1677
-
1678
- self.drop_placeholder_row(target_keys[0], 'SeriesInstanceUID')
1679
-
1680
- if len(all_series) == 1:
1681
- return all_series[0]
1682
- else:
1683
- return all_series
1684
-
1685
-
1686
- def copy_patient_data(self, key, row):
1687
- row[0] = self.register.at[key, 'PatientID']
1688
- row[5] = self.register.at[key, 'PatientName']
1689
- return row
1690
-
1691
-
1692
- def preserve_patient_record(self, key):
1693
- # If this is the last study in the patient, create a new row for the empty patient record.
1694
- source_patient = self.register.at[key, 'PatientID']
1695
- source_patient = (self.register.removed == False) & (self.register.PatientID == source_patient)
1696
- source_patient_studies = self.register.StudyInstanceUID[source_patient]
1697
- source_patient_studies_cnt = len(source_patient_studies.unique())
1698
- if source_patient_studies_cnt == 1:
1699
- row = self.default()
1700
- row = self.copy_patient_data(key, row)
1701
- self.new_row(row)
1702
-
1703
-
1704
- def move_to_patient(self, uid, target, **kwargs):
1705
- """Copy series to another study"""
1706
-
1707
- target_keys = self.keys(patient=target)
1708
- attributes, values = self.patient_header(target_keys[0])
1709
- self.append_kwargs(kwargs, attributes, values)
1710
- all_studies = self.studies(uid)
1711
-
1712
- for s, study in enumerate(all_studies):
1713
-
1714
- self.status.progress(s+1, len(all_studies), message='Moving study..')
1715
- keys = self.keys(study=study)
1716
- self.preserve_patient_record(keys[0])
1717
-
1718
- for series in self.series(keys=keys):
1719
-
1720
- # Move all instances one-by-one to new patient
1721
- for key in self.keys(series=series):
1722
-
1723
- instance_uid = self.value(key, 'SOPInstanceUID')
1724
- ds = self.get_dataset(instance_uid, [key])
1725
-
1726
- # If the instance is empty, just update the register.
1727
- if ds is None:
1728
- row = self.value(key, self.columns).tolist()
1729
- row = self.copy_patient_data(target_keys[0], row)
1730
- for val in kwargs:
1731
- if val in self._descriptives:
1732
- row[self._descriptives[val]] = kwargs[val]
1733
- self.update_row_data(key, row)
1734
-
1735
- # Else set the values in the dataset and register.
1736
- else:
1737
- self.set_dataset_values(ds, key, attributes, values)
1738
-
1739
- self.drop_placeholder_row(target_keys[0], 'StudyInstanceUID')
1740
-
1741
- if len(all_studies) == 1:
1742
- return all_studies[0]
1743
- else:
1744
- return all_studies
1745
-
1746
-
1747
- def move_to(self, source, target, **kwargs):
1748
-
1749
- type = self.type(target)
1750
- if type == 'Patient':
1751
- return self.move_to_patient(source, target, **kwargs)
1752
- if type == 'Study':
1753
- return self.move_to_study(source, target, **kwargs)
1754
- if type == 'Series':
1755
- return self.move_to_series(source, target, **kwargs)
1756
- if type == 'Instance':
1757
- raise ValueError('Cannot move to an instance. Please move to series, study or patient.')
1758
-
1759
-
1760
- def create_new_instance(self, key, ds):
1761
- series_uid = self.value(key, 'SeriesInstanceUID')
1762
- if series_uid is None:
1763
- study_uid = self.value(key, 'StudyInstanceUID')
1764
- if study_uid is None:
1765
- patient_uid = self.value(key, 'PatientID')
1766
- if patient_uid is None:
1767
- _, new_key = self.new_instance('Database', ds)
1768
- else:
1769
- _, new_key = self.new_instance(patient_uid, ds)
1770
- else:
1771
- _, new_key = self.new_instance(study_uid, ds)
1772
- else:
1773
- _, new_key = self.new_instance(series_uid, ds)
1774
- return new_key
1775
-
1776
-
1777
-
1778
-
1779
- def save_dataset(self, key, ds):
1780
- if key in self.dataset:
1781
- self.dataset[key] = ds
1782
- else:
1783
- path = self.filepath(key)
1784
- ds.write(path, self.status)
1785
-
1786
-
1787
- def set_dataset_values(self, ds, key, attributes, values):
1788
-
1789
- # If the dataset is in memory and has not yet been modified, then edit a copy.
1790
- if key in self.dataset:
1791
- if not self.value(key, 'created'):
1792
- ds = copy.deepcopy(ds)
1793
-
1794
- # Change the values and get the register row data
1795
- ds.set_values(attributes, values)
1796
- row = ds.get_values(self.columns)
1797
-
1798
- # Update the register and save the modified dataset
1799
- key = self.update_row_data(key, row)
1800
- self.save_dataset(key, ds)
1801
- return key # added
1802
-
1803
- # def force_get_dataset(self, key):
1804
-
1805
- # # Get a dataset for the instance, and create one in memory if needed.
1806
- # instance_uid = self.value(key, 'SOPInstanceUID')
1807
-
1808
- # # If the record is empty, create a new instance and a dataset in memory
1809
- # if instance_uid is None:
1810
- # ds = new_dataset('MRImage')
1811
- # new_key = self.create_new_instance(key, ds)
1812
- # return ds, new_key
1813
-
1814
- # # If a dataset exists, return it.
1815
- # ds = self.get_dataset(instance_uid, [key])
1816
- # if ds is not None:
1817
- # return ds, key
1818
-
1819
- # # If the instance has no data yet, create a dataset in memory.
1820
- # ds = new_dataset('MRImage')
1821
- # new_key = self.set_instance_dataset(instance_uid, ds, key)
1822
- # return ds, key
1823
-
1824
- # def _set_values(self, attributes, values, keys=None, uid=None):
1825
- # """Set values in a dataset"""
1826
- # # PASSES ALL TESTS but creates datasets when attributes of empty records are set
1827
-
1828
- # uids = ['PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID']
1829
- # uids = [i for i in uids if i in attributes]
1830
- # if uids != []:
1831
- # raise ValueError('UIDs cannot be set using set_value(). Use copy_to() or move_to() instead.')
1832
-
1833
- # if keys is None:
1834
- # keys = self.keys(uid)
1835
-
1836
- # for key in keys:
1837
-
1838
- # # Get the dataset, and create one if needed
1839
- # ds, new_key = self.force_get_dataset(key)
1840
-
1841
- # # Set the new values
1842
- # self.set_dataset_values(ds, new_key, attributes, values)
1843
-
1844
- # return new_key
1845
-
1846
-
1847
- def set_row_values(self, key, attributes, values):
1848
- if not isinstance(values, list):
1849
- values = [values]
1850
- attributes = [attributes]
1851
- row = self.value(key, self.columns).tolist()
1852
- for i, attr in enumerate(attributes):
1853
- if attr in self._descriptives:
1854
- row[self._descriptives[attr]] = values[i]
1855
- self.update_row_data(key, row)
1856
-
1857
-
1858
- def set_values(self, attributes, values, keys=None, uid=None):
1859
- """Set values in a dataset"""
1860
-
1861
- uids = ['PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID']
1862
- uids = [i for i in uids if i in attributes]
1863
- if uids != []:
1864
- raise ValueError('UIDs cannot be set using set_value(). Use copy_to() or move_to() instead.')
1865
-
1866
- if keys is None:
1867
- keys = self.keys(uid)
1868
-
1869
- for key in keys:
1870
-
1871
- # Get the dataset
1872
- instance_uid = self.value(key, 'SOPInstanceUID')
1873
- if instance_uid is None:
1874
- ds = None
1875
- else:
1876
- ds = self.get_dataset(instance_uid, [key])
1877
-
1878
- if ds is None:
1879
- # Update register entries only
1880
- self.set_row_values(key, attributes, values)
1881
- else:
1882
- # Set the new values
1883
- self.set_dataset_values(ds, key, attributes, values)
1884
-
1885
-
1886
- def get_values(self, attributes, keys=None, uid=None):
1887
-
1888
- if keys is None:
1889
- keys = self.keys(uid)
1890
- if keys == []:
1891
- return
1892
-
1893
- # Single attribute
1894
- if not isinstance(attributes, list):
1895
-
1896
- if attributes in self.columns:
1897
- value = [self.register.at[key, attributes] for key in keys]
1898
- # Get unique elements
1899
- value = [x for i, x in enumerate(value) if i==value.index(x)]
1900
- else:
1901
- value = []
1902
- for i, key in enumerate(keys):
1903
- instance_uid = self.value(key, 'SOPInstanceUID')
1904
- ds = self.get_dataset(instance_uid, [key])
1905
- if ds is None:
1906
- v = None
1907
- else:
1908
- v = ds.get_values(attributes)
1909
- if v not in value:
1910
- value.append(v)
1911
- if len(value) == 1:
1912
- return value[0]
1913
- try:
1914
- value.sort() # added 30/12/22
1915
- except:
1916
- pass
1917
- return value
1918
-
1919
- # Multiple attributes
1920
- # Create a np array v with values for each instance and attribute
1921
- if set(attributes) <= set(self.columns):
1922
- v = self.value(keys, attributes)
1923
- else:
1924
- v = np.empty((len(keys), len(attributes)), dtype=object)
1925
- for i, key in enumerate(keys):
1926
- instance_uid = self.value(key, 'SOPInstanceUID')
1927
- ds = self.get_dataset(instance_uid, [key])
1928
- if isinstance(ds, list):
1929
- instances = self.register.SOPInstanceUID == instance_uid
1930
- msg = 'Multiple instances with the same SOPInstanceUID \n'
1931
- msg += instance_uid + '\n'
1932
- msg += str(self.register.loc[instances].transpose())
1933
- raise DatabaseCorrupted(msg)
1934
- if ds is None:
1935
- v[i,:] = [None] * len(attributes)
1936
- else:
1937
- v[i,:] = ds.get_values(attributes)
1938
-
1939
- # Return a list with unique values for each attribute
1940
- values = []
1941
- for a in range(v.shape[1]):
1942
- va = v[:,a]
1943
- va = va[va != np.array(None)]
1944
- #va = np.unique(va)
1945
- va = list(va)
1946
- # Get unique values
1947
- va = [x for i, x in enumerate(va) if i==va.index(x)]
1948
- #if va.size == 0:
1949
- if len(va) == 0:
1950
- va = None
1951
- elif len(va) == 1:
1952
- #elif va.size == 1:
1953
- va = va[0]
1954
- else:
1955
- #va = list(va)
1956
- try:
1957
- va.sort() # added 30/12/22
1958
- except:
1959
- pass
1960
- values.append(va)
1961
- return values
1962
-
1963
-
1964
- def import_datasets(self, files):
1965
-
1966
- # Read manager data
1967
- df = dbdataset.read_dataframe(files, self.columns, self.status)
1968
- df['removed'] = False
1969
- df['created'] = True
1970
-
1971
- # Do not import SOPInstances that are already in the database
1972
- uids = df.SOPInstanceUID.values.tolist()
1973
- keys = self.keys(instance=uids)
1974
- if keys != []:
1975
- do_not_import = self.value(keys, 'SOPInstanceUID')
1976
- rows = df.SOPInstanceUID.isin(do_not_import)
1977
- df.drop(df[rows].index, inplace=True)
1978
- if df.empty:
1979
- return
1980
-
1981
- # Add those that are left to the database
1982
- files = df.index.tolist()
1983
- for i, file in enumerate(files):
1984
- self.status.progress(i+1, len(files), 'Copying files..')
1985
- new_key = self.new_key()
1986
- ds = dbdataset.read(file)
1987
- ds.write(self.filepath(new_key), self.status)
1988
- df.rename(index={file:new_key}, inplace=True)
1989
- self.register = pd.concat([self.register, df])
1990
-
1991
- # return the UIDs of the new instances
1992
- return df.SOPInstanceUID.values.tolist()
1993
-
1994
-
1995
- def import_datasets_from_nifti(self, files, study=None):
1996
-
1997
- if study is None:
1998
- study, _ = self.new_study()
1999
-
2000
- # Create new
2001
- nifti_series = None
2002
- for i, file in enumerate(files):
2003
-
2004
- # Read the nifti file
2005
- nim = nib.load(file)
2006
- sx, sy, sz = nim.header.get_zooms() # spacing
2007
-
2008
- # If a dicom header is stored, get it
2009
- # Else create one from scratch
2010
- try:
2011
- dcmext = nim.header.extensions
2012
- dataset = DbDataset(dcmext[0].get_content())
2013
- except:
2014
- dataset = new_dataset()
2015
-
2016
- # Read the array and reshape to 3D
2017
- array = np.squeeze(nim.get_fdata())
2018
- array.reshape((array.shape[0], array.shape[1], -1))
2019
- n_slices = array.shape[-1]
2020
-
2021
- # If there is only one slice,
2022
- # load it into the nifti series.
2023
- if n_slices == 1:
2024
- if nifti_series is None:
2025
- desc = os.path.basename(file)
2026
- nifti_series, _ = self.new_series(study, SeriesDescription=desc)
2027
- affine = dbimage.affine_to_RAH(nim.affine)
2028
- dataset.set_pixel_array(array[:,:,0])
2029
- dataset.set_values('affine_matrix', affine)
2030
- #dataset.set_values('PixelSpacing', [sy, sx])
2031
- self.new_instance(nifti_series, dataset)
2032
-
2033
- # If there are multiple slices in the file,
2034
- # Create a new series and save all files in there.
2035
- else:
2036
- desc = os.path.basename(file)
2037
- series, _ = self.new_series(study, SeriesDescription=desc)
2038
- affine = dbimage.affine_to_RAH(nim.affine)
2039
- for z in range(n_slices):
2040
- ds = copy.deepcopy(dataset)
2041
- ds.set_pixel_array(array[:,:,z])
2042
- ds.set_values('affine_matrix', affine)
2043
- #ds.set_values('PixelSpacing', [sy, sx])
2044
- self.new_instance(series, ds)
2045
-
2046
-
2047
- def export_datasets(self, uids, database):
2048
-
2049
- files = self.filepaths(uids)
2050
- database.import_datasets(files)
2051
-
2052
-
2053
- # Helper functions to hide the register from classes other than manager
2054
- # Consider removing after eliminating dataframe
2055
-
2056
- def _empty(self):
2057
- return self.register.empty
2058
-
2059
- def _dbloc(self):
2060
- return self.register.removed==False
2061
-
2062
- def _keys(self, loc):
2063
- return self.register.index[loc]
2064
-
2065
- def _at(self, row, col):
2066
- return self.register.at[row, col]
2067
-
2068
- def _extract(self, rows):
2069
- return self.register.loc[rows,:]
2070
-
2071
- def _loc(self, name, uid):
2072
- df = self.register
2073
- return (df.removed==False) & (df[name]==uid)
2074
-
2075
- def _extract_record(self, name, uid):
2076
- return self.register[name] == uid
2077
-