Load vol file into 3D Slicer from Morita machine

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:

  • 0x8000 value 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
1 Like