Instruction to Create .nhdr file
Use the following template for your .nhdr file:
NRRD0004
# Complete NRRD file format specification at:
# http://teem.sourceforge.net/nrrd/format.html
type: signed short
dimension: 3
space: left-posterior-superior
sizes: Z Y X
space directions: (0,0,S) (S,0,0) (0,S,0)
kinds: domain domain domain
endian: little
encoding: raw
space origin: (0,0,0)
byte skip: B
data file: CT_0.vol
You need to fill values from your .vol header:
X, Y, Z, S, B
Voxel size (S)
Look for the grid size in the .vol XML header, e.g.:
<tfXGridSize value="0.125"/>
<tfYGridSize value="0.125"/>
<tfZGridSize value="0.125"/>
⇒ S = 0.125
Dimensions (X, Y, Z)
Check the minimum and maximum indices:
<FYI>
<XMin value="-352"/>
<XMax value="352"/>
<YMin value="-352"/>
<YMax value="352"/>
<ZMin value="-162"/>
<ZMax value="161"/>
..
</FYI>
X = XMax - XMin + 1 = 352 - (-352) + 1 = 705
Y = YMax - YMin + 1 = 352 - (-352) + 1 = 705
Z = ZMax - ZMin + 1 = 161 - (-162) + 1 = 324
Byte skip (B)
total voxels = X * Y * Z = 161036100
voxel size = 2 bytes (because data type in .vol is 16-bit integer)
raw data size = total voxels * voxel size = 322072200 (bytes)
Raw data located at the very end of the .vol file, so the byte skip parameter can be calculated as:
B = total .vol file size - raw data size
Adjust axes (if necessary)
If after import the axes look incorrect, modify the space directions line, for example:
space directions: (S,0,0) (0,S,0) (0,0,S)
Test different permutations until the orientation matches your viewer.
Data type consideration
Use signed short data type, not unsigned short, because in your .vol:
0x8000value corresponds to void- and all other values correspond to actual intensities
Python parser for reference
import struct, tempfile, subprocess
import numpy as np
def load_data (path: str, dtype = np.int16, extract_header: bool = False) -> np.ndarray:
with open(path, 'rb') as f:
n = struct.unpack("<I", f.read(4))[0]
data = f.read(n)
assert data.decode('ascii') == "JmVolumeVersion=1"
# XML
n = struct.unpack("<I", f.read(4))[0]
data = f.read(n)
if extract_header:
with tempfile.NamedTemporaryFile() as fht:
fht.write(data)
fht.flush()
subprocess.run(["xmllint", "--format", "--output", path + ".header.xml", fht.name])
n = struct.unpack("<I", f.read(4))[0]
data = f.read(n)
assert data.decode('ascii') == "CArray3D"
# X limits
x_min, x_max = struct.unpack("<ii", f.read(8))
X = x_max - x_min + 1
# Y limits
y_min, y_max = struct.unpack("<ii", f.read(8))
Y = y_max - y_min + 1
# Z limits
z_min, z_max = struct.unpack("<ii", f.read(8))
Z = z_max - z_min + 1
print(f"X={X} Y={Y} Z={Z}")
# voxels
d = np.frombuffer(f.read(), dtype=dtype)
print(len(d))
assert len(d) == X * Y * Z
d = d.reshape((X, Y, Z))
return d