Skip to content

Commit 7d40d29

Browse files
v0.5.0 candidate (#29)
* fix: return logging levels to original state * test: add dat.read() test * test: add comment to test_dat_read_both_formats * test: add test_supplements folder * fix: return raw2img log to original state * Bump version: 0.4.0 → 0.5.0
1 parent 966444a commit 7d40d29

File tree

9 files changed

+116
-22
lines changed

9 files changed

+116
-22
lines changed

rawtools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__author__ = """Tim Parker"""
44
__email__ = 'tim.parkerd@gmail.com'
5-
__version__ = '0.4.0'
5+
__version__ = '0.5.0'

rawtools/dat.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,18 @@ def determine_bit_depth(fp, dims):
6161
logging.warning(f"Unable to determine bit-depth of volume '{fp}'. Expected at <{expected_filesize}> bytes but found <{file_size}> bytes. Defaulting to unsigned 16-bit.")
6262
return 'uint16'
6363

64-
def __parse_object_filename(line):
65-
pattern = r"^ObjectFileName\:\s+(?P<filename>.*\.raw)$"
64+
def __parse_object_filename(line, dat_format):
65+
if (dat_format == "Dragonfly"):
66+
pattern = r"<ObjectFileName>(?P<filename>.*\.raw)<\/ObjectFileName>"
67+
elif (dat_format == "NSI"):
68+
pattern = r"^ObjectFileName\:\s+(?P<filename>.*\.raw)$"
6669

6770
match = re.match(pattern, line, flags=re.IGNORECASE)
6871
if match is not None:
6972
logging.debug(f"Match: {match}")
7073
return match.group('filename')
7174

72-
def __parse_resolution(line):
75+
def __parse_resolution(line, dat_format):
7376
"""Get the x, y, z dimensions of a volume.
7477
7578
Args:
@@ -80,13 +83,16 @@ def __parse_resolution(line):
8083
8184
"""
8285
# logging.debug(line.strip())
83-
pattern_old = r'\s+<Resolution X="(?P<x>\d+)"\s+Y="(?P<y>\d+)"\s+Z="(?P<z>\d+)"'
84-
pattern = r'Resolution\:\s+(?P<x>\d+)\s+(?P<y>\d+)\s+(?P<z>\d+)'
86+
if (dat_format == "Dragonfly"):
87+
pattern = r'<Resolution X="(?P<x>\d+)"\s+Y="(?P<y>\d+)"\s+Z="(?P<z>\d+)"'
88+
elif (dat_format == "NSI"):
89+
pattern_old = r'\s+<Resolution X="(?P<x>\d+)"\s+Y="(?P<y>\d+)"\s+Z="(?P<z>\d+)"'
90+
pattern = r'Resolution\:\s+(?P<x>\d+)\s+(?P<y>\d+)\s+(?P<z>\d+)'
8591

8692
# See if the DAT file is the newer version
8793
match = re.match(pattern, line, flags=re.IGNORECASE)
8894
# Otherwise, check the old version (XML)
89-
if match is None:
95+
if match is None and dat_format == "NSI":
9096
match = re.match(pattern_old, line, flags=re.IGNORECASE)
9197
if match is not None:
9298
logging.debug(f"XML format detected for '{line}'")
@@ -103,7 +109,7 @@ def __parse_resolution(line):
103109
raise Exception(f"Unable to extract dimensions from DAT file. Found dimensions: '{dims}'.")
104110
return dims
105111

106-
def __parse_slice_thickness(line):
112+
def __parse_slice_thickness(line, dat_format):
107113
"""Get the x, y, z dimensions of a volume.
108114
109115
Args:
@@ -113,31 +119,56 @@ def __parse_slice_thickness(line):
113119
(float, float, float): x, y, z real-world thickness in mm. Otherwise, returns None.
114120
115121
"""
116-
pattern = r'\w+\:\s+(?P<xth>\d+\.\d+)\s+(?P<yth>\d+\.\d+)\s+(?P<zth>\d+\.\d+)'
122+
if (dat_format == "Dragonfly"):
123+
pattern = r"<Spacing\s+X=\"(?P<xth>\d+\.\d+)\"\s+Y=\"(?P<yth>\d+\.\d+)\"\s+Z=\"(?P<zth>\d+\.\d+)\""
124+
elif (dat_format == "NSI"):
125+
pattern = r'\w+\:\s+(?P<xth>\d+\.\d+)\s+(?P<yth>\d+\.\d+)\s+(?P<zth>\d+\.\d+)'
126+
117127
match = re.match(pattern, line, flags=re.IGNORECASE)
118128
if match is not None:
119129
logging.debug(f"Match: {match}")
120-
df = match.groupdict()
121130
dims = [ match.group('xth'), match.group('yth'), match.group('zth') ]
122-
dims = [ float(s) for s in dims ]
131+
if (dat_format == "Dragonfly"):
132+
# Change Dragonfly thickness units to match NSI format
133+
dims = [ (float(s)*1000) for s in dims ]
134+
elif (dat_format == "NSI"):
135+
dims = [ float(s) for s in dims ]
123136
if not dims or len(dims) != 3:
124137
raise Exception(f"Unable to extract slice thickness from DAT file: '{line}'. Found slice thickness: '{dims}'.")
125138
return dims
126139

127-
def __parse_format(line):
128-
pattern = r"^Format\:\s+(?P<format>\w+)$"
140+
def __parse_format(line, dat_format):
141+
if (dat_format == "Dragonfly"):
142+
pattern = r"<Format>(?P<format>\w+)<\/Format>"
143+
elif (dat_format == "NSI"):
144+
pattern = r"Format\:\s+(?P<format>\w+)$"
145+
129146
match = re.match(pattern, line, flags=re.IGNORECASE)
130147
if match is not None:
131148
logging.debug(f"Match: {match}")
132149
return match.group('format')
133150

134-
def __parse_object_model(line):
135-
pattern = r"^ObjectModel\:\s+(?P<object_model>\w+)$"
151+
def __parse_object_model(line, dat_format):
152+
if (dat_format == "Dragonfly"):
153+
pattern = r"<Unit>(?P<object_model>\w+)<\/Unit>"
154+
elif (dat_format == "NSI"):
155+
pattern = r"^ObjectModel\:\s+(?P<object_model>\w+)$"
156+
136157
match = re.match(pattern, line, flags=re.IGNORECASE)
137158
if match is not None:
138159
logging.debug(f"Match: {match}")
139160
return match.group('object_model')
140161

162+
def __is_dragonfly_dat_format(line):
163+
pattern = r"<\?xml\sversion=\"1\.0\"\?>"
164+
match = re.match(pattern, line, flags=re.IGNORECASE)
165+
if match is not None:
166+
logging.debug(f"Match: {match}")
167+
return True
168+
169+
170+
171+
141172
def read(fp):
142173
"""Read a .DAT file
143174
Args:
@@ -146,24 +177,30 @@ def read(fp):
146177
dict: contents of .DAT file
147178
"""
148179
data = {}
180+
dat_format = "NSI"
149181
with open(fp, 'r') as ifp:
150182
# Parse the individual lines
151183
for line in ifp.readlines():
152184
line = line.strip()
153-
if (object_filename := __parse_object_filename(line)) is not None:
185+
# Determine if format is NSI .dat or Dragonfly .dat
186+
if (__is_dragonfly_dat_format(line)):
187+
dat_format = "Dragonfly"
188+
189+
if (object_filename := __parse_object_filename(line, dat_format)) is not None:
154190
data['ObjectFileName'] = object_filename
191+
155192

156-
if (resolution := __parse_resolution(line)) is not None:
193+
if (resolution := __parse_resolution(line, dat_format)) is not None:
157194
data['xdim'], data['ydim'], data['zdim'] = resolution
158195
data['dimensions'] = resolution
159196

160-
if (thicknesses := __parse_slice_thickness(line)) is not None:
197+
if (thicknesses := __parse_slice_thickness(line, dat_format)) is not None:
161198
data['x_thickness'], data['y_thickness'], data['z_thickness'] = thicknesses
162199

163-
if (file_format := __parse_format(line)) is not None:
200+
if (file_format := __parse_format(line, dat_format)) is not None:
164201
data['Format'] = file_format
165202

166-
if (object_model := __parse_object_model(line)) is not None:
203+
if (object_model := __parse_object_model(line, dat_format)) is not None:
167204
data['model'] = object_model
168205

169206

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.4.0
2+
current_version = 0.5.0
33
commit = True
44
tag = True
55

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@
6666
test_suite='tests',
6767
tests_require=test_requirements,
6868
url='https://github.com/Topp-Roots-Lab/python-rawtools',
69-
version='0.4.0',
69+
version='0.5.0',
7070
zip_safe=False,
7171
)

tests/test_rawtools.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ def slice_uint16_high_variance():
2727
"""Sample uint16 slice with variable values"""
2828
return np.array([-1, 0, 100, 1000, 5000, 14830, 50321, 65535, 65536], dtype=uint16)
2929

30+
@pytest.fixture
31+
def dat_files():
32+
"""Sample .dat files' paths"""
33+
return ['tests/test_supplements/ideal_dragonfly.dat',
34+
'tests/test_supplements/ideal_nsi.dat',
35+
'tests/test_supplements/poor_dragonfly.dat',
36+
'tests/test_supplements/poor_nsi.dat']
37+
38+
3039

3140
def test_scale_uint8(slice_uint8):
3241
"""Test scaling a unsigned 8-bit integer array to own bounds."""
@@ -65,3 +74,16 @@ def test_scale_uint16_to_uint8_large_variance(slice_uint16_high_variance):
6574
scale(slice_uint16_high_variance, lbound, ubound, new_lbound, new_ubound))
6675

6776
np.testing.assert_array_equal(scaled_slice, slice_uint8)
77+
78+
def test_dat_read_both_formats(dat_files):
79+
"""Test dat.read() on 4 example .dat (two acceptable and two unacceptable)
80+
covering both Dragonfly and NSI formats"""
81+
from rawtools.dat import read
82+
# neither of these should raise any errors
83+
read(dat_files[0])
84+
read(dat_files[1])
85+
with pytest.raises(ValueError, match=r"Unable to parse"):
86+
read(dat_files[2])
87+
with pytest.raises(ValueError, match=r"Unable to parse"):
88+
read(dat_files[3])
89+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0"?>
2+
<RAWFileData>
3+
<Version>1</Version>
4+
<ObjectFileName>2020_PlantHaven_IPSRI_133-3_2021-rewash_109um_8Bit.raw</ObjectFileName>
5+
<Format>UCHAR</Format>
6+
<DataSlope>1</DataSlope>
7+
<DataOffset>0</DataOffset>
8+
<Unit>Density</Unit>
9+
<Resolution X="1474" Y="1474" Z="1861" T="1" />
10+
<Spacing X="0.00010852599999999999736115946502579276966571342200040817" Y="0.00010852599999999999736115946502579276966571342200040817" Z="0.00010852599999999999736115946502579276966571342200040817" />
11+
<Orientation X0="1" X1="0" X2="0" Y0="0" Y1="1" Y2="0" Z0="0" Z1="0" Z2="1" />
12+
<Position P1="0" P2="0" P3="0" />
13+
</RAWFileData>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ObjectFileName: 2020_PlantHaven_IPSRI_133-3_2021-rewash_109um.raw
2+
Resolution: 1474 1474 1861
3+
SliceThickness: 0.108526441297249 0.108526441297249 0.108526441297249
4+
Format: USHORT
5+
ObjectModel: DENSITY
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<RAWFileData>
3+
<Version>1</Version>
4+
<ObjectFileName>2020_PlantHaven_IPSRI_133-3_2021-rewash_109um_8Bit.raw</ObjectFileName>
5+
<Format>UCHAR</Format>
6+
<DataSlope>1</DataSlope>
7+
<DataOffset>0</DataOffset>
8+
<Unit>Density</Unit>
9+
<Resolution X="1474" Y="1474" Z="1861" T="1" />
10+
<Orientation X0="1" X1="0" X2="0" Y0="0" Y1="1" Y2="0" Z0="0" Z1="0" Z2="1" />
11+
<Position P1="0" P2="0" P3="0" />
12+
</RAWFileData>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ObjectFileName: 2020_PlantHaven_IPSRI_133-3_2021-rewash_109um.raw
2+
Resolution: 1474 1474 1861
3+
SliceThickness: 0.108526441297249 0.108526441297249 0.108526441297249
4+
Format: USHORT
5+
Object: DENSITY

0 commit comments

Comments
 (0)