Skip to content

Commit f366930

Browse files
committed
Add explicit doc strings and use better internal dimension queries
1 parent 4031915 commit f366930

File tree

5 files changed

+138
-86
lines changed

5 files changed

+138
-86
lines changed

docs/src/index.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@
33
CurrentModule = NIfTI
44
```
55

6-
# NeuroGraphs
6+
# NIfTI
77

88
Documentation for [NIfTI](https://github.com/JuliaNeuroscience/NIfTI.jl).
99

1010
```@docs
11+
NIfTI.freqdim
12+
NIfTI.phasedim
13+
NIfTI.slicedim
14+
NIfTI.slice_start
15+
NIfTI.slice_end
16+
NIfTI.slice_duration
17+
NIfTI.sdims
18+
NIfTI.voxel_size
1119
```

src/NIfTI.jl

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,6 @@ header(x::NIVolume) = getfield(x, :header)
4343

4444
include("coordinates.jl")
4545

46-
47-
"""
48-
voxel_size(header::NIfTI1Header)
49-
50-
Get the voxel size **in mm** from a `NIfTI1Header`.
51-
"""
52-
voxel_size(header::NIfTI1Header) =
53-
[header.pixdim[i] * SPATIAL_UNIT_MULTIPLIERS[header.xyzt_units & Int8(3)] for i = 2:min(header.dim[1], 3)+1]
54-
5546
# Always in ms
5647
"""
5748
time_step(header::NIfTI1Header)
@@ -61,30 +52,6 @@ Get the TR **in ms** from a `NIfTI1Header`.
6152
time_step(header::NIfTI1Header) =
6253
header.pixdim[5] * TIME_UNIT_MULTIPLIERS[header.xyzt_units >> 3]
6354

64-
function to_dim_info(dim_info::Tuple{Integer,Integer,Integer})
65-
if dim_info[1] > 3 || dim_info[1] < 0
66-
error("Invalid frequency dimension $(dim_info[1])")
67-
elseif dim_info[2] > 3 || dim_info[2] < 0
68-
error("Invalid phase dimension $(dim_info[2])")
69-
elseif dim_info[3] > 3 || dim_info[3] < 0
70-
error("Invalid slice dimension $(dim_info[3])")
71-
end
72-
73-
return Int8(dim_info[1] | (dim_info[2] << 2) | (dim_info[3] << 4))
74-
end
75-
76-
# Returns or sets dim_info as a tuple whose values are the frequency, phase, and slice dimensions
77-
function dim_info(header::NIfTI1Header)
78-
return (
79-
header.dim_info & int8(3),
80-
(header.dim_info >> 2) & int8(3),
81-
(header.dim_info >> 4) & int8(3)
82-
)
83-
end
84-
function dim_info(header::NIfTI1Header, dim_info::Tuple{T, T, T}) where {T<:Integer}
85-
header.dim_info = to_dim_info(dim_info)
86-
end
87-
8855
# Gets the size of a type in bits
8956
nibitpix(t::Type) = Int16(sizeof(t)*8)
9057
nibitpix(::Type{Bool}) = Int16(1)
@@ -107,10 +74,10 @@ function NIVolume(
10774
intent_p1::Real=0f0, intent_p2::Real=0f0, intent_p3::Real=0f0,
10875
intent_code::Integer=Int16(0), intent_name::AbstractString="",
10976
# Information about which slices were acquired, in case the volume has been padded
110-
slice_start::Integer=Int16(0), slice_end::Integer=Int16(0), slice_code::Int8=Int8(0),
77+
slice_start::Integer=Int16(0), slice_end::Integer=Int16(0), slice_code=UInt8(0),
11178
# The size of each voxel and the time step. These are formulated in mm unless xyzt_units is
11279
# explicitly specified
113-
voxel_size::NTuple{3, Real}=(1f0, 1f0, 1f0), time_step::Real=0f0, xyzt_units::Int8=Int8(18),
80+
voxel_size::NTuple{3, Real}=(1f0, 1f0, 1f0), time_step::Real=0f0, xyzt_units=UInt8(18),
11481
# Slope and intercept by which volume shoudl be scaled
11582
scl_slope::Real=1f0, scl_inter::Real=0f0,
11683
# These describe how data should be scaled when displayed on the screen. They are probably
@@ -168,8 +135,12 @@ function NIVolume(
168135
regular, to_dim_info(dim_info), to_dim_i16(size(raw)), intent_p1, intent_p2,
169136
intent_p3, intent_code, eltype_to_int16(t), nibitpix(t),
170137
slice_start, (qfac, voxel_size..., time_step, 0, 0, 0), 352,
171-
scl_slope, scl_inter, slice_end, slice_code,
172-
xyzt_units, cal_max, cal_min, slice_duration,
138+
scl_slope,
139+
scl_inter,
140+
slice_end,
141+
UInt8(slice_code),
142+
UInt8(xyzt_units),
143+
cal_max, cal_min, slice_duration,
173144
toffset, glmax, glmin, string_tuple(descrip, 80), string_tuple(aux_file, 24), (method2 || method3),
174145
method3, quatern_b, quatern_c, quatern_d,
175146
qoffset_x, qoffset_y, qoffset_z, (orientation[1, :]...,),

src/headers.jl

Lines changed: 111 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ end
2828
mutable struct NIfTI1Header
2929
sizeof_hdr::Int32
3030

31-
data_type::NTuple{10,UInt8}
32-
db_name::NTuple{18,UInt8}
31+
data_type::NTuple{10, UInt8}
32+
db_name::NTuple{18, UInt8}
3333
extents::Int32
3434
session_error::Int16
3535
regular::Int8
3636

37-
dim_info::Int8
37+
dim_info::UInt8
3838
dim::NTuple{8,Int16}
3939
intent_p1::Float32
4040
intent_p2::Float32
@@ -48,8 +48,8 @@ mutable struct NIfTI1Header
4848
scl_slope::Float32
4949
scl_inter::Float32
5050
slice_end::Int16
51-
slice_code::Int8
52-
xyzt_units::Int8
51+
slice_code::UInt8
52+
xyzt_units::UInt8
5353
cal_max::Float32
5454
cal_min::Float32
5555
slice_duration::Float32
@@ -58,8 +58,8 @@ mutable struct NIfTI1Header
5858
glmax::Int32
5959
glmin::Int32
6060

61-
descrip::NTuple{80,UInt8}
62-
aux_file::NTuple{24,UInt8}
61+
descrip::NTuple{80, UInt8}
62+
aux_file::NTuple{24, UInt8}
6363

6464
qform_code::Int16
6565
sform_code::Int16
@@ -70,13 +70,13 @@ mutable struct NIfTI1Header
7070
qoffset_y::Float32
7171
qoffset_z::Float32
7272

73-
srow_x::NTuple{4,Float32}
74-
srow_y::NTuple{4,Float32}
75-
srow_z::NTuple{4,Float32}
73+
srow_x::NTuple{4, Float32}
74+
srow_y::NTuple{4, Float32}
75+
srow_z::NTuple{4, Float32}
7676

77-
intent_name::NTuple{16,UInt8}
77+
intent_name::NTuple{16, UInt8}
7878

79-
magic::NTuple{4,UInt8}
79+
magic::NTuple{4, UInt8}
8080
end
8181
define_packed(NIfTI1Header)
8282

@@ -95,53 +95,125 @@ function byteswap(hdr::NIfTI1Header)
9595
end
9696

9797
"""
98-
freqdim(img)::Int
98+
NIfTI.slice_start(x)::Int
9999
100-
Returns the frequency dimension associated with with `img`. `img` is an image or a collection of
101-
image related metadata.
100+
Which slice corresponds to the first slice acquired during MRI acquisition (i.e. not padded slices).
102101
"""
103-
freqdim(x::NIfTI1Header) = Int(getfield(x, :dim_info) & 0x03)
104-
freqdim(x) = freqdim(header(x))
102+
slice_start(x::NIfTI1Header) = Int(getfield(x, :slice_start)) + 1
103+
slice_start(x) = slice_start(header(x))
105104

106105
"""
107-
phasedim(img)::Int
106+
NIfTI.slice_end(x)::Int
108107
109-
Returns the phase dimension associated with `img`. `img` is an image or a collection of
110-
image related metadata.
108+
Which slice corresponds to the last slice acquired during MRI acquisition (i.e. not padded slices).
111109
"""
112-
phasedim(x::NIfTI1Header) = Int((getfield(x, :dim_info) >> 2) & 0x03)
113-
phasedim(x) = phasedim(header(x))
110+
slice_end(x::NIfTI1Header) = Int(getfield(x, :slice_end)) + 1
111+
slice_end(x) = slice_end(header(x))
114112

115113
"""
116-
slicedim(img)::Int
114+
NIfTI.slice_duration(x)
117115
118-
Returns the slice dimension associated with `img`. `img` is an image or a collection of
119-
image related metadata.
116+
Time to acquire one slice.
120117
"""
121-
slicedim(x::NIfTI1Header) = Int((getfield(x, :dim_info) >> 4) & 0x03)
122-
slicedim(x) = slicedim(header(x))
118+
slice_duration(x::NIfTI1Header) = getfield(x, :slice_duration)
119+
slice_duration(x) = slice_duration(header(x))
123120

124121
"""
125-
slice_start(x)::Int
122+
NIfTI.sdims(img)
126123
127-
Which slice corresponds to the first slice acquired during MRI acquisition (i.e. not padded slices).
124+
Return the number of spatial dimensions in the image.
128125
"""
129-
slice_start(x::NIfTI1Header) = Int(getfield(x, :slice_start)) + 1
130-
slice_start(x) = slice_start(header(x))
126+
sdims(x::NIfTI1Header) = min(Int(getfield(getfield(x, :dim), 1)), 3)
127+
sdims(x) = sdims(header(x))
128+
129+
# Conversion factors to mm/ms
130+
# http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/xyzt_units.html
131+
# 1 => NIfTI_UNITS_METER
132+
# 2 => NIfTI_UNITS_MM
133+
# 3 => NIfTI_UNITS_MICRON
134+
function to_spatial_multiplier(xyzt_units::UInt8)
135+
i = xyzt_units & 0x03
136+
if i === 0x01
137+
return 1000.0f0
138+
elseif i === 0x02
139+
return 1.0f0
140+
else
141+
return 0.001f0
142+
end
143+
end
131144

132145
"""
133-
slice_end(x)::Int
146+
NIfTI.voxel_size(header::NIfTI1Header)
134147
135-
Which slice corresponds to the last slice acquired during MRI acquisition (i.e. not padded slices).
148+
Get the voxel size **in mm** from a `NIfTI1Header`.
136149
"""
137-
slice_end(x::NIfTI1Header) = Int(getfield(x, :slice_end)) + 1
138-
slice_end(x) = slice_end(header(x))
150+
function voxel_size(x::NIfTI1Header)
151+
sd = sdims(x)
152+
if sd === 0
153+
return ()
154+
else
155+
sconvert = to_spatial_multiplier(getfield(x, :xyzt_units))
156+
if sd === 3
157+
pd = getfield(x, :pixdim)
158+
return (
159+
getfield(pd, 2) * sconvert,
160+
getfield(pd, 3) * sconvert,
161+
getfield(pd, 4) * sconvert
162+
)
163+
elseif sd === 2
164+
pd = getfield(x, :pixdim)
165+
return (getfield(pd, 2) * sconvert, getfield(pd, 3) * sconvert,)
166+
else # sd === 1
167+
pd = getfield(x, :pixdim)
168+
return (getfield(pd, 2) * sconvert,)
169+
end
170+
end
171+
end
172+
173+
function to_dim_info(dim_info::Tuple{Integer,Integer,Integer})
174+
if dim_info[1] > 3 || dim_info[1] < 0
175+
error("Invalid frequency dimension $(dim_info[1])")
176+
elseif dim_info[2] > 3 || dim_info[2] < 0
177+
error("Invalid phase dimension $(dim_info[2])")
178+
elseif dim_info[3] > 3 || dim_info[3] < 0
179+
error("Invalid slice dimension $(dim_info[3])")
180+
end
181+
182+
return UInt8(dim_info[1] | (dim_info[2] << 2) | (dim_info[3] << 4))
183+
end
184+
185+
# Returns or sets dim_info as a tuple whose values are the frequency, phase, and slice dimensions
186+
function dim_info(hdr::NIfTI1Header)
187+
return (hdr.dim_info & 0x03, (hdr.dim_info >> 0x02) & 0x03, (hdr.dim_info >> 0x04) & 0x03)
188+
end
189+
function dim_info(header::NIfTI1Header, dim_info::Tuple{T, T, T}) where {T<:Integer}
190+
header.dim_info = to_dim_info(dim_info)
191+
end
139192

140193
"""
141-
slice_duration(x)
194+
NIfTI.freqdim(img)::Int
142195
143-
Time to acquire one slice.
196+
Returns the frequency dimension associated with with `img`. `img` is an image or a collection of
197+
image related metadata.
144198
"""
145-
slice_duration(x::NIfTI1Header) = getfield(x, :slice_duration)
146-
slice_duration(x) = slice_duration(header(x))
199+
freqdim(x::NIfTI1Header) = Int(getfield(x, :dim_info) & 0x03)
200+
freqdim(x) = freqdim(header(x))
201+
202+
"""
203+
NIfTI.phasedim(img)::Int
204+
205+
Returns the phase dimension associated with `img`. `img` is an image or a collection of
206+
image related metadata.
207+
"""
208+
phasedim(x::NIfTI1Header) = Int((getfield(x, :dim_info) >> 0x02) & 0x03)
209+
phasedim(x) = phasedim(header(x))
210+
211+
"""
212+
NIfTI.slicedim(img)::Int
213+
214+
Returns the slice dimension associated with `img`. `img` is an image or a collection of
215+
image related metadata.
216+
"""
217+
slicedim(x::NIfTI1Header) = Int((getfield(x, :dim_info) >> 0x04) & 0x03)
218+
slicedim(x) = slicedim(header(x))
147219

src/parsers.jl

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
1+
const SPATIAL_UNIT_MULTIPLIERS = [
2+
1000, # 1 => NIfTI_UNITS_METER
3+
1, # 2 => NIfTI_UNITS_MM
4+
0.001 # 3 => NIfTI_UNITS_MICRON
5+
]
26

37
const SIZEOF_HDR1 = Int32(348)
48
const SIZEOF_HDR2 = Int32(540)
@@ -200,13 +204,6 @@ function string_tuple(x::String, n::Int)
200204
end
201205
string_tuple(x::AbstractString) = string_tuple(bytestring(x))
202206

203-
# Conversion factors to mm/ms
204-
# http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/xyzt_units.html
205-
const SPATIAL_UNIT_MULTIPLIERS = [
206-
1000, # 1 => NIfTI_UNITS_METER
207-
1, # 2 => NIfTI_UNITS_MM
208-
0.001 # 3 => NIfTI_UNITS_MICRON
209-
]
210207
const TIME_UNIT_MULTIPLIERS = [
211208
1000, # NIfTI_UNITS_SEC
212209
1, # NIfTI_UNITS_MSEC

test/runtests.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ function image_tests(fname, mmap)
3737

3838
# Header
3939
@test time_step(img.header) == 2000000 # Actually an error in the file AFAIK
40-
@test voxel_size(img.header) Float32[2.0, 2.0, 2.2]
40+
# @test all(isapprox.(voxel_size(img.header), (2.0, 2.0, 2.2)))
41+
vs1, vs2, vs3 = voxel_size(img.header)
42+
@test isapprox(vs1, Float32(2.0))
43+
@test isapprox(vs2, Float32(2.0))
44+
@test isapprox(vs3, Float32(2.2))
4145
@test size(img) == (128, 96, 24, 2)
4246

4347
# Content

0 commit comments

Comments
 (0)