dbdicom 0.2.1__py3-none-any.whl → 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dbdicom might be problematic. Click here for more details.
- dbdicom/__init__.py +4 -3
- dbdicom/create.py +34 -97
- dbdicom/dro.py +174 -0
- dbdicom/ds/dataset.py +31 -4
- dbdicom/ds/types/mr_image.py +18 -7
- dbdicom/extensions/__init__.py +9 -0
- dbdicom/{wrappers → extensions}/dipy.py +191 -205
- dbdicom/extensions/elastix.py +503 -0
- dbdicom/extensions/matplotlib.py +107 -0
- dbdicom/extensions/numpy.py +271 -0
- dbdicom/{wrappers → extensions}/scipy.py +130 -31
- dbdicom/{wrappers → extensions}/skimage.py +1 -1
- dbdicom/extensions/sklearn.py +243 -0
- dbdicom/extensions/vreg.py +1390 -0
- dbdicom/external/dcm4che/bin/emf2sf +57 -57
- dbdicom/manager.py +70 -36
- dbdicom/pipelines.py +66 -0
- dbdicom/record.py +266 -43
- dbdicom/types/instance.py +54 -19
- dbdicom/types/series.py +2183 -412
- dbdicom/utils/image.py +256 -48
- dbdicom-0.2.4.dist-info/METADATA +89 -0
- {dbdicom-0.2.1.dist-info → dbdicom-0.2.4.dist-info}/RECORD +26 -41
- {dbdicom-0.2.1.dist-info → dbdicom-0.2.4.dist-info}/WHEEL +1 -1
- dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
- dbdicom/utils/vreg.py +0 -2626
- dbdicom/wrappers/__init__.py +0 -7
- dbdicom/wrappers/elastix.py +0 -855
- dbdicom/wrappers/numpy.py +0 -119
- dbdicom/wrappers/sklearn.py +0 -151
- dbdicom/wrappers/vreg.py +0 -273
- dbdicom-0.2.1.dist-info/METADATA +0 -276
- {dbdicom-0.2.1.dist-info → dbdicom-0.2.4.dist-info}/LICENSE +0 -0
- {dbdicom-0.2.1.dist-info → dbdicom-0.2.4.dist-info}/top_level.txt +0 -0
dbdicom/record.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Importing annotations to handle or sign in import type hints
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import os
|
|
5
|
+
import datetime
|
|
6
|
+
|
|
4
7
|
# Import packages
|
|
5
8
|
import numpy as np
|
|
6
9
|
import pandas as pd
|
|
7
10
|
import dbdicom.ds.dataset as dbdataset
|
|
11
|
+
from dbdicom.ds import MRImage
|
|
8
12
|
from dbdicom.utils.files import export_path
|
|
9
13
|
|
|
10
14
|
|
|
@@ -15,12 +19,14 @@ class Record():
|
|
|
15
19
|
|
|
16
20
|
def __init__(self, create, manager, uid='Database', key=None, **kwargs):
|
|
17
21
|
|
|
22
|
+
self._logfile = None
|
|
18
23
|
self._key = key
|
|
19
24
|
self._mute = False
|
|
20
25
|
self.uid = uid
|
|
21
26
|
self.attributes = kwargs
|
|
22
27
|
self.manager = manager
|
|
23
28
|
self.new = create
|
|
29
|
+
|
|
24
30
|
|
|
25
31
|
def __eq__(self, other):
|
|
26
32
|
if other is None:
|
|
@@ -34,13 +40,13 @@ class Record():
|
|
|
34
40
|
return self.get_values(attributes)
|
|
35
41
|
|
|
36
42
|
def __setattr__(self, attribute, value):
|
|
37
|
-
if attribute in ['_key','_mute', 'uid', 'manager', 'attributes', 'new']:
|
|
43
|
+
if attribute in ['_key','_mute', 'uid', 'manager', 'attributes', 'new', '_logfile']:
|
|
38
44
|
self.__dict__[attribute] = value
|
|
39
45
|
else:
|
|
40
|
-
self.
|
|
46
|
+
self._set_values([attribute], [value])
|
|
41
47
|
|
|
42
48
|
def __setitem__(self, attributes, values):
|
|
43
|
-
self.
|
|
49
|
+
self._set_values(attributes, values)
|
|
44
50
|
|
|
45
51
|
def loc(self):
|
|
46
52
|
return self.manager._loc(self.name, self.uid)
|
|
@@ -86,7 +92,80 @@ class Record():
|
|
|
86
92
|
def dialog(self):
|
|
87
93
|
return self.manager.dialog
|
|
88
94
|
|
|
95
|
+
def set_log(self, filepath:str=None):
|
|
96
|
+
"""Set a new file for logging.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
filepath: full path to a log file. If not provided the current log file is removed. Alternatively the value 'Default' can be assigned, in which case a standard file at the same location of the database is automatically opened. Defaults to None.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
FileNotFoundError: if the log file cannot be written to.
|
|
103
|
+
|
|
104
|
+
See also:
|
|
105
|
+
`log`
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
|
|
109
|
+
Set a new log file:
|
|
110
|
+
|
|
111
|
+
>>> record.set_log('path/to/logfile')
|
|
112
|
+
|
|
113
|
+
and start logging:
|
|
114
|
+
|
|
115
|
+
>>> record.log('Starting new calculation...)
|
|
116
|
+
|
|
117
|
+
Alternatively, start a new log at the default location:
|
|
118
|
+
|
|
119
|
+
>>> record.set_log('Default')
|
|
120
|
+
"""
|
|
121
|
+
if filepath is None:
|
|
122
|
+
self._logfile = None
|
|
123
|
+
return
|
|
124
|
+
if filepath == 'Default':
|
|
125
|
+
# Use default log name
|
|
126
|
+
self._logfile = os.path.join(self.manager.path, "activity_log.txt")
|
|
127
|
+
else:
|
|
128
|
+
self._logfile = filepath
|
|
129
|
+
try:
|
|
130
|
+
file = open(self._logfile, 'a')
|
|
131
|
+
file.write(str(datetime.datetime.now())[0:19] + "Starting a new log..")
|
|
132
|
+
file.close()
|
|
133
|
+
except:
|
|
134
|
+
msg = 'Cannot write to log ' + self._logfile
|
|
135
|
+
raise FileNotFoundError(msg)
|
|
136
|
+
|
|
137
|
+
def log(self, message:str):
|
|
138
|
+
"""Write an entry in the log file.
|
|
139
|
+
|
|
140
|
+
If no logfile is set, this function only writes a message in the terminal.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
message (str): text message to be written in the log file. The function automatically includes some timing information so this does not need to be included in the message.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
FileNotFoundError: if the log file cannot be written to.
|
|
89
147
|
|
|
148
|
+
See also:
|
|
149
|
+
`set_log`
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
Set a default file for logging and write a first message:
|
|
153
|
+
|
|
154
|
+
>>> record.set_log('Default')
|
|
155
|
+
>>> record.log('Starting new calculation...)
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
self.message(message)
|
|
159
|
+
if self._logfile is None:
|
|
160
|
+
return
|
|
161
|
+
try:
|
|
162
|
+
file = open(self._logfile, 'a')
|
|
163
|
+
file.write("\n"+str(datetime.datetime.now())[0:19] + ": " + message)
|
|
164
|
+
file.close()
|
|
165
|
+
except:
|
|
166
|
+
msg = 'Cannot write to log ' + self._logfile
|
|
167
|
+
raise FileNotFoundError(msg)
|
|
168
|
+
|
|
90
169
|
|
|
91
170
|
# Properties
|
|
92
171
|
|
|
@@ -100,7 +179,7 @@ class Record():
|
|
|
100
179
|
Example:
|
|
101
180
|
Print a summary of a database:
|
|
102
181
|
|
|
103
|
-
>>> database = db.database_hollywood()
|
|
182
|
+
>>> database = db.dro.database_hollywood()
|
|
104
183
|
>>> database.print()
|
|
105
184
|
---------- DATABASE --------------
|
|
106
185
|
Location: In memory
|
|
@@ -212,7 +291,7 @@ class Record():
|
|
|
212
291
|
Populate the series with a numpy array and verify that it is now no longer empty:
|
|
213
292
|
|
|
214
293
|
>>> zeros = np.zeros((3, 2, 128, 128))
|
|
215
|
-
>>> series.
|
|
294
|
+
>>> series.set_pixel_values(zeros)
|
|
216
295
|
>>> print(series.empty())
|
|
217
296
|
False
|
|
218
297
|
"""
|
|
@@ -327,7 +406,7 @@ class Record():
|
|
|
327
406
|
Example:
|
|
328
407
|
Find the patients of a given database:
|
|
329
408
|
|
|
330
|
-
>>> database = db.database_hollywood()
|
|
409
|
+
>>> database = db.dro.database_hollywood()
|
|
331
410
|
>>> patients = database.children()
|
|
332
411
|
>>> print([p.PatientName for p in patients])
|
|
333
412
|
['James Bond', 'Scarface']
|
|
@@ -368,7 +447,7 @@ class Record():
|
|
|
368
447
|
Example:
|
|
369
448
|
Retrieve a study from a database, and find all other studies performed on the same patient:
|
|
370
449
|
|
|
371
|
-
>>> database = db.database_hollywood()
|
|
450
|
+
>>> database = db.dro.database_hollywood()
|
|
372
451
|
>>> study = database.studies()[0]
|
|
373
452
|
>>> print([s.StudyDescription for s in study.siblings()])
|
|
374
453
|
['Xray']
|
|
@@ -402,7 +481,7 @@ class Record():
|
|
|
402
481
|
Example:
|
|
403
482
|
Find all series in a database, and print their labels:
|
|
404
483
|
|
|
405
|
-
>>> database = db.database_hollywood()
|
|
484
|
+
>>> database = db.dro.database_hollywood()
|
|
406
485
|
>>> series_list = database.series()
|
|
407
486
|
>>> print([s.label() for s in series_list])
|
|
408
487
|
['Series 001 [Localizer]', 'Series 002 [T2w]', 'Series 001 [Chest]', 'Series 002 [Head]', 'Series 001 [Localizer]', 'Series 002 [T2w]', 'Series 001 [Chest]', 'Series 002 [Head]']
|
|
@@ -447,7 +526,7 @@ class Record():
|
|
|
447
526
|
Example:
|
|
448
527
|
Find all studies in a database:
|
|
449
528
|
|
|
450
|
-
>>> database = db.database_hollywood()
|
|
529
|
+
>>> database = db.dro.database_hollywood()
|
|
451
530
|
>>> studies_list = database.studies()
|
|
452
531
|
>>> print([s.label() for s in studies_list])
|
|
453
532
|
['Study MRI [19821201]', 'Study Xray [19821205]', 'Study MRI [19850105]', 'Study Xray [19850106]']
|
|
@@ -486,7 +565,7 @@ class Record():
|
|
|
486
565
|
Example:
|
|
487
566
|
Find all patients in a database:
|
|
488
567
|
|
|
489
|
-
>>> database = db.database_hollywood()
|
|
568
|
+
>>> database = db.dro.database_hollywood()
|
|
490
569
|
>>> patients_list = database.patients()
|
|
491
570
|
>>> print([s.label() for s in patients_list])
|
|
492
571
|
['Patient James Bond', 'Patient Scarface']
|
|
@@ -906,8 +985,6 @@ class Record():
|
|
|
906
985
|
return self
|
|
907
986
|
|
|
908
987
|
|
|
909
|
-
|
|
910
|
-
|
|
911
988
|
def copy_to(self, parent, **kwargs):
|
|
912
989
|
"""Return a copy of the record under another parent.
|
|
913
990
|
|
|
@@ -1385,6 +1462,7 @@ class Record():
|
|
|
1385
1462
|
My message:
|
|
1386
1463
|
"""
|
|
1387
1464
|
self._mute = True
|
|
1465
|
+
self.status.muted = True
|
|
1388
1466
|
|
|
1389
1467
|
def unmute(self):
|
|
1390
1468
|
"""Allow the object from sending status updates to the user
|
|
@@ -1415,6 +1493,7 @@ class Record():
|
|
|
1415
1493
|
Hello World
|
|
1416
1494
|
"""
|
|
1417
1495
|
self._mute = False
|
|
1496
|
+
self.status.muted = False
|
|
1418
1497
|
|
|
1419
1498
|
def type(self):
|
|
1420
1499
|
return self.__class__.__name__
|
|
@@ -1436,8 +1515,8 @@ class Record():
|
|
|
1436
1515
|
return self.manager._extract(self.keys())
|
|
1437
1516
|
#return self.manager.register.loc[self.keys(),:]
|
|
1438
1517
|
|
|
1439
|
-
def instances(self, sort=True, sortby=None, **kwargs):
|
|
1440
|
-
inst = self.manager.instances(keys=self.keys(), sort=sort, sortby=sortby, **kwargs)
|
|
1518
|
+
def instances(self, sort=True, sortby=None, select={}, **kwargs):
|
|
1519
|
+
inst = self.manager.instances(keys=self.keys(), sort=sort, sortby=sortby, select=select, **kwargs)
|
|
1441
1520
|
return [self.record('Instance', uid, key) for key, uid in inst.items()]
|
|
1442
1521
|
|
|
1443
1522
|
def images(self, sort=True, sortby=None, **kwargs):
|
|
@@ -1529,21 +1608,26 @@ class Record():
|
|
|
1529
1608
|
self.manager._write_df()
|
|
1530
1609
|
|
|
1531
1610
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
1611
|
def new_instance(self, dataset=None, **kwargs):
|
|
1537
1612
|
attr = {**kwargs, **self.attributes}
|
|
1538
1613
|
uid, key = self.manager.new_instance(parent=self.uid, dataset=dataset, **attr)
|
|
1539
1614
|
return self.record('Instance', uid, key, **attr)
|
|
1540
1615
|
|
|
1541
|
-
def
|
|
1616
|
+
def _set_values(self, attributes, values):
|
|
1542
1617
|
keys = self.keys()
|
|
1543
1618
|
self._key = self.manager.set_values(attributes, values, keys)
|
|
1544
1619
|
|
|
1545
1620
|
def get_values(self, attributes):
|
|
1546
1621
|
return self.manager.get_values(attributes, self.keys())
|
|
1622
|
+
|
|
1623
|
+
def init_dataset(self, dtype='mri'):
|
|
1624
|
+
if dtype=='mri':
|
|
1625
|
+
ds = MRImage()
|
|
1626
|
+
else: # dummy option for now
|
|
1627
|
+
ds = MRImage()
|
|
1628
|
+
for a in self.attributes:
|
|
1629
|
+
ds.set_values(a, self.attributes[a])
|
|
1630
|
+
return ds
|
|
1547
1631
|
|
|
1548
1632
|
def get_dataset(self):
|
|
1549
1633
|
ds = self.manager.get_dataset(self.uid, self.keys())
|
|
@@ -1552,8 +1636,6 @@ class Record():
|
|
|
1552
1636
|
def set_dataset(self, dataset):
|
|
1553
1637
|
self.manager.set_dataset(self.uid, dataset, self.keys())
|
|
1554
1638
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
1639
|
def read_dataframe(*args, **kwargs):
|
|
1558
1640
|
return read_dataframe(*args, **kwargs)
|
|
1559
1641
|
|
|
@@ -1582,14 +1664,55 @@ class Record():
|
|
|
1582
1664
|
#
|
|
1583
1665
|
|
|
1584
1666
|
|
|
1585
|
-
def copy_to(records,
|
|
1667
|
+
def copy_to(records:list, parent:Record):
|
|
1668
|
+
"""Copy a list of records to a new parent.
|
|
1669
|
+
|
|
1670
|
+
Args:
|
|
1671
|
+
records (list): list of Records of the same type
|
|
1672
|
+
parent (Record): location for the copies.
|
|
1673
|
+
|
|
1674
|
+
See also:
|
|
1675
|
+
`copy`
|
|
1676
|
+
`move_to`
|
|
1677
|
+
|
|
1678
|
+
Example:
|
|
1679
|
+
|
|
1680
|
+
Consider the hollywood demo database:
|
|
1681
|
+
|
|
1682
|
+
>>> database = db.dro.database_hollywood()
|
|
1683
|
+
|
|
1684
|
+
There are currently two MRI studies in the database:
|
|
1685
|
+
|
|
1686
|
+
>>> MRIs = database.studies(StudyDescription='MRI)
|
|
1687
|
+
>>> len(MRIs)
|
|
1688
|
+
2
|
|
1689
|
+
|
|
1690
|
+
Create a new patient and copy the MRI studies there:
|
|
1691
|
+
|
|
1692
|
+
>>> tarantino = database.new_patient(PatientName='Tarantino')
|
|
1693
|
+
>>> db.copy_to(MRIs, tarantino)
|
|
1694
|
+
>>> tarantino_MRIs = tarantino.studies()
|
|
1695
|
+
>>> len(tarantino_MRIs)
|
|
1696
|
+
2
|
|
1697
|
+
|
|
1698
|
+
Note that all header information is automatically updated:
|
|
1699
|
+
|
|
1700
|
+
>>> tarantino_MRIs[0].PatientName
|
|
1701
|
+
Tarantino
|
|
1702
|
+
|
|
1703
|
+
Since the studies were copied, the originals remained and the total number of studies in the database has increased:
|
|
1704
|
+
|
|
1705
|
+
>>> MRIs = database.studies(StudyDescription='MRI)
|
|
1706
|
+
>>> len(MRIs)
|
|
1707
|
+
4
|
|
1708
|
+
"""
|
|
1586
1709
|
if not isinstance(records, list):
|
|
1587
|
-
return records.copy_to(
|
|
1710
|
+
return records.copy_to(parent)
|
|
1588
1711
|
copy = []
|
|
1589
|
-
desc =
|
|
1712
|
+
desc = parent.label()
|
|
1590
1713
|
for r, record in enumerate(records):
|
|
1591
|
-
record.
|
|
1592
|
-
copy_record = record.copy_to(
|
|
1714
|
+
record.progress(r+1, len(records), 'Copying ' + desc)
|
|
1715
|
+
copy_record = record.copy_to(parent)
|
|
1593
1716
|
if isinstance(copy_record, list):
|
|
1594
1717
|
copy += copy_record
|
|
1595
1718
|
else:
|
|
@@ -1597,9 +1720,55 @@ def copy_to(records, target):
|
|
|
1597
1720
|
record.status.hide()
|
|
1598
1721
|
return copy
|
|
1599
1722
|
|
|
1600
|
-
def move_to(records, target):
|
|
1601
|
-
|
|
1602
|
-
|
|
1723
|
+
def move_to(records:list, target:Record):
|
|
1724
|
+
"""Move a list of records to a new parent.
|
|
1725
|
+
|
|
1726
|
+
Args:
|
|
1727
|
+
records (list): list of Records of the same type
|
|
1728
|
+
parent (Record): location for the copies.
|
|
1729
|
+
|
|
1730
|
+
See also:
|
|
1731
|
+
`copy`
|
|
1732
|
+
`copy_to`
|
|
1733
|
+
|
|
1734
|
+
Example:
|
|
1735
|
+
|
|
1736
|
+
Consider the hollywood demo database:
|
|
1737
|
+
|
|
1738
|
+
>>> database = db.dro.database_hollywood()
|
|
1739
|
+
|
|
1740
|
+
There are currently two MRI studies in the database:
|
|
1741
|
+
|
|
1742
|
+
>>> MRIs = database.studies(StudyDescription='MRI)
|
|
1743
|
+
>>> len(MRIs)
|
|
1744
|
+
2
|
|
1745
|
+
|
|
1746
|
+
Create a new patient and move the MRI studies there:
|
|
1747
|
+
|
|
1748
|
+
>>> tarantino = database.new_patient(PatientName='Tarantino')
|
|
1749
|
+
>>> db.copy_to(MRIs, tarantino)
|
|
1750
|
+
>>> tarantino_MRIs = tarantino.studies()
|
|
1751
|
+
>>> len(tarantino_MRIs)
|
|
1752
|
+
2
|
|
1753
|
+
|
|
1754
|
+
Note that all header information is automatically updated:
|
|
1755
|
+
|
|
1756
|
+
>>> tarantino_MRIs[0].PatientName
|
|
1757
|
+
Tarantino
|
|
1758
|
+
|
|
1759
|
+
Since the studies were moved, the total number of studies in the database has stayed the same:
|
|
1760
|
+
|
|
1761
|
+
>>> MRIs = database.studies(StudyDescription='MRI)
|
|
1762
|
+
>>> len(MRIs)
|
|
1763
|
+
2
|
|
1764
|
+
|
|
1765
|
+
And the original patients do not have any MRI studies left:
|
|
1766
|
+
|
|
1767
|
+
>>> jb = database.patients(PatientName = 'James Bond')
|
|
1768
|
+
>>> MRIs = jb[0].studies(StudyDescription='MRI')
|
|
1769
|
+
>>> len(MRIs)
|
|
1770
|
+
0
|
|
1771
|
+
"""
|
|
1603
1772
|
if not isinstance(records, list):
|
|
1604
1773
|
records = [records]
|
|
1605
1774
|
mgr = records[0].manager
|
|
@@ -1607,7 +1776,7 @@ def move_to(records, target):
|
|
|
1607
1776
|
mgr.move_to(uids, target.uid, **target.attributes)
|
|
1608
1777
|
return records
|
|
1609
1778
|
|
|
1610
|
-
def group(records, into=None, inplace=False):
|
|
1779
|
+
def group(records:list, into:Record=None, inplace=False)->Record:
|
|
1611
1780
|
if not isinstance(records, list):
|
|
1612
1781
|
records = [records]
|
|
1613
1782
|
if into is None:
|
|
@@ -1618,17 +1787,69 @@ def group(records, into=None, inplace=False):
|
|
|
1618
1787
|
copy_to(records, into)
|
|
1619
1788
|
return into
|
|
1620
1789
|
|
|
1621
|
-
def merge(records, into=None, inplace=False):
|
|
1790
|
+
def merge(records:list, into:Record=None, inplace=False)->Record:
|
|
1791
|
+
"""Merge a list of records into a single new record.
|
|
1792
|
+
|
|
1793
|
+
Args:
|
|
1794
|
+
records (list): list of Records of the same type
|
|
1795
|
+
into (Record, optional): location for the merged series. If None is provided, the merged series is created in the parent of the first record in the list. Defaults to None.
|
|
1796
|
+
inplace (bool, optional): If set to True, the original series will be removed and only the merged series retain. If set to False the original series will contine to exist. Default is False.
|
|
1797
|
+
|
|
1798
|
+
Returns:
|
|
1799
|
+
new_record (Record): the merged record.
|
|
1800
|
+
|
|
1801
|
+
See also:
|
|
1802
|
+
`copy`
|
|
1803
|
+
`copy_to`
|
|
1804
|
+
|
|
1805
|
+
Example:
|
|
1806
|
+
|
|
1807
|
+
The first patient in the hollywood demo database currently has two studies
|
|
1808
|
+
|
|
1809
|
+
>>> database = db.dro.database_hollywood()
|
|
1810
|
+
>>> jb = database.patients(PatientName = 'James Bond')[0]
|
|
1811
|
+
>>> len(jb.studies())
|
|
1812
|
+
2
|
|
1813
|
+
|
|
1814
|
+
If we merge them together, the patient now has three studies, the original MRI and Xray studies, and the new merged study:
|
|
1815
|
+
|
|
1816
|
+
>>> new_study = db.merge(jb.studies())
|
|
1817
|
+
>>> len(jb.studies())
|
|
1818
|
+
3
|
|
1819
|
+
>>> jb.StudyDescription
|
|
1820
|
+
['MRI', 'New Study', 'Xray']
|
|
1821
|
+
|
|
1822
|
+
Since the original MRI and Xray studies had two series each, the new study now has 2+2=4 series:
|
|
1823
|
+
|
|
1824
|
+
>>> len(new_study.series())
|
|
1825
|
+
4
|
|
1826
|
+
|
|
1827
|
+
We have used here the default setting of ``inplace=False``, so the original series are preserved. To see what happens with ``inplace=True``, lets merge all 3 studies of the patient:
|
|
1828
|
+
|
|
1829
|
+
>>> single_jb_study = db.merge(jb.studies(), inplace=True)
|
|
1830
|
+
|
|
1831
|
+
Since we have merged in place, the original 3 studies have been removed and there is now only one study left.
|
|
1832
|
+
|
|
1833
|
+
>>> len(jb.studies())
|
|
1834
|
+
1
|
|
1835
|
+
|
|
1836
|
+
The new study now groups the 8 series that were in the original 3 studies:
|
|
1837
|
+
|
|
1838
|
+
>>> len(single_jb_study.series())
|
|
1839
|
+
8
|
|
1840
|
+
"""
|
|
1622
1841
|
if not isinstance(records, list):
|
|
1623
1842
|
records = [records]
|
|
1624
1843
|
children = []
|
|
1625
1844
|
for record in records:
|
|
1626
1845
|
children += record.children()
|
|
1627
|
-
|
|
1846
|
+
new_record = group(children, into=into, inplace=inplace)
|
|
1628
1847
|
if inplace:
|
|
1629
1848
|
for record in records:
|
|
1630
1849
|
record.remove()
|
|
1631
|
-
return
|
|
1850
|
+
return new_record
|
|
1851
|
+
|
|
1852
|
+
|
|
1632
1853
|
|
|
1633
1854
|
|
|
1634
1855
|
#
|
|
@@ -1637,11 +1858,18 @@ def merge(records, into=None, inplace=False):
|
|
|
1637
1858
|
|
|
1638
1859
|
|
|
1639
1860
|
|
|
1640
|
-
|
|
1641
|
-
def read_dataframe(record, tags):
|
|
1861
|
+
def read_dataframe(record, tags, select={}, **filters):
|
|
1642
1862
|
if set(tags) <= set(record.manager.columns):
|
|
1643
|
-
|
|
1644
|
-
|
|
1863
|
+
df = record.register()[tags]
|
|
1864
|
+
filters = {**select, **filters}
|
|
1865
|
+
for f in filters:
|
|
1866
|
+
if f in df:
|
|
1867
|
+
if isinstance(filters[f], np.ndarray):
|
|
1868
|
+
df = df[df[f].isin(filters[f])]
|
|
1869
|
+
else:
|
|
1870
|
+
df = df[df[f] == filters[f]]
|
|
1871
|
+
return df
|
|
1872
|
+
instances = record.instances(select=select, **filters)
|
|
1645
1873
|
return _read_dataframe_from_instance_array_values(instances, tags)
|
|
1646
1874
|
|
|
1647
1875
|
|
|
@@ -1662,9 +1890,4 @@ def _read_dataframe_from_instance_array_values(instances, tags):
|
|
|
1662
1890
|
indices.append(index)
|
|
1663
1891
|
data.append(values)
|
|
1664
1892
|
instance.progress(i+1, len(instances), 'Reading dataframe..')
|
|
1665
|
-
return pd.DataFrame(data, index=indices, columns=tags)
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1893
|
+
return pd.DataFrame(data, index=indices, columns=tags)
|
dbdicom/types/instance.py
CHANGED
|
@@ -7,6 +7,7 @@ import numpy as np
|
|
|
7
7
|
import nibabel as nib
|
|
8
8
|
import pandas as pd
|
|
9
9
|
import matplotlib.pyplot as plt
|
|
10
|
+
import vreg
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
from dbdicom.record import Record
|
|
@@ -39,28 +40,12 @@ class Instance(Record):
|
|
|
39
40
|
uid = self.manager.copy_instance_to_series(self.key(), series.keys(), series)
|
|
40
41
|
return self.record('Instance', uid)
|
|
41
42
|
|
|
42
|
-
def
|
|
43
|
+
def pixel_values(self):
|
|
43
44
|
return self.get_pixel_array()
|
|
44
45
|
|
|
45
|
-
def
|
|
46
|
-
ds = self.get_dataset()
|
|
47
|
-
return ds.get_pixel_array()
|
|
48
|
-
|
|
49
|
-
def set_array(self, array):
|
|
46
|
+
def set_pixel_values(self, array):
|
|
50
47
|
self.set_pixel_array(array)
|
|
51
48
|
|
|
52
|
-
def set_pixel_array(self, array):
|
|
53
|
-
ds = self.get_dataset()
|
|
54
|
-
if ds is None:
|
|
55
|
-
ds = new_dataset('MRImage')
|
|
56
|
-
ds.set_pixel_array(array)
|
|
57
|
-
in_memory = self.key() in self.manager.dataset
|
|
58
|
-
self.set_dataset(ds)
|
|
59
|
-
# This bit added ad-hoc because set_dataset() places the datset in memory
|
|
60
|
-
# So if the instance is not in memory, it needs to be written and removed again
|
|
61
|
-
if not in_memory:
|
|
62
|
-
self.clear()
|
|
63
|
-
|
|
64
49
|
def set_dataset(self, dataset):
|
|
65
50
|
self._key = self.manager.set_instance_dataset(self.uid, dataset, self.key())
|
|
66
51
|
|
|
@@ -137,6 +122,56 @@ class Instance(Record):
|
|
|
137
122
|
width = self.WindowWidth,
|
|
138
123
|
center = self.WindowCenter,
|
|
139
124
|
)
|
|
125
|
+
|
|
126
|
+
def volume(self):
|
|
127
|
+
return vreg.volume(self.pixel_values(),
|
|
128
|
+
self.affine())
|
|
129
|
+
|
|
130
|
+
def set_volume(self, volume:vreg.Volume3D):
|
|
131
|
+
self.set_pixel_values(np.squeeze(volume.values))
|
|
132
|
+
self.set_affine(volume.affine)
|
|
133
|
+
|
|
134
|
+
def affine(self):
|
|
135
|
+
return image.affine_matrix(self.ImageOrientationPatient,
|
|
136
|
+
self.ImagePositionPatient,
|
|
137
|
+
self.PixelSpacing,
|
|
138
|
+
self.SliceThickness)
|
|
139
|
+
|
|
140
|
+
def set_affine(self, affine):
|
|
141
|
+
p = image.dismantle_affine_matrix(affine)
|
|
142
|
+
self.read()
|
|
143
|
+
#self.SpacingBetweenSlices = p['SpacingBetweenSlices']
|
|
144
|
+
self.SliceThickness = p['SliceThickness']
|
|
145
|
+
self.PixelSpacing = p['PixelSpacing']
|
|
146
|
+
self.ImageOrientationPatient = p['ImageOrientationPatient']
|
|
147
|
+
self.ImagePositionPatient = p['ImagePositionPatient']
|
|
148
|
+
self.SliceLocation = np.dot(p['ImagePositionPatient'], p['slice_cosine'])
|
|
149
|
+
self.clear()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# OBSOLETE API
|
|
153
|
+
|
|
154
|
+
def array(self): # obsolete replace by pixel_values
|
|
155
|
+
return self.get_pixel_array()
|
|
156
|
+
|
|
157
|
+
def get_pixel_array(self): # obsolete replace by pixel_values
|
|
158
|
+
ds = self.get_dataset()
|
|
159
|
+
return ds.get_pixel_array()
|
|
160
|
+
|
|
161
|
+
def set_array(self, array): # obsolete replace by set_pixel_values
|
|
162
|
+
self.set_pixel_array(array)
|
|
163
|
+
|
|
164
|
+
def set_pixel_array(self, array): # obsolete replace by set_pixel_values
|
|
165
|
+
ds = self.get_dataset()
|
|
166
|
+
if ds is None:
|
|
167
|
+
ds = new_dataset('MRImage')
|
|
168
|
+
ds.set_pixel_array(array)
|
|
169
|
+
in_memory = self.key() in self.manager.dataset
|
|
170
|
+
self.set_dataset(ds)
|
|
171
|
+
# This bit added ad-hoc because set_dataset() places the datset in memory
|
|
172
|
+
# So if the instance is not in memory, it needs to be written and removed again
|
|
173
|
+
if not in_memory:
|
|
174
|
+
self.clear()
|
|
140
175
|
|
|
141
176
|
|
|
142
177
|
def map_to(source, target):
|
|
@@ -179,7 +214,7 @@ def map_to(source, target):
|
|
|
179
214
|
|
|
180
215
|
return result
|
|
181
216
|
|
|
182
|
-
|
|
217
|
+
# Obsolete
|
|
183
218
|
def map_mask_to(record, target):
|
|
184
219
|
"""Map non-zero image pixels onto a target image.
|
|
185
220
|
Overwrite pixel values in the target"""
|