Skip to content

Commit d8e94d6

Browse files
Merge pull request #4 from MagneticResonanceImaging/Bugfixes
Subtle Bugfixes / improvements
2 parents 68cc085 + e05e941 commit d8e94d6

4 files changed

Lines changed: 676 additions & 229 deletions

File tree

README.md

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,86 +6,142 @@
66
| [![][docs-img]][docs-url] | [![][gh-actions-img]][gh-actions-url] | [![][codecov-img]][codecov-url] |
77

88

9-
This package aligns a time series of 3D MRI images with similar contrast, following the seminal paper by [Friston et al.](https://doi.org/10.1002/hbm.460030303) It minimizes the squared difference between the images in a given mask. The package was heavily inspired by [SPM's](https://www.fil.ion.ucl.ac.uk/spm/) `spm_realign` function. The principal advantage over `spm_realign` is speed. Additionally, we implemented a *consensus* estimation, which aligns all time frames pairwise and uses [iteratively reweighted least squares](https://en.wikipedia.org/wiki/Iteratively_reweighted_least_squares) to calculate a consensus between all estimates. Compared to a single reference time frame, the consensus approach is less sensitive to the image quality of the reference frame. Compared with the *mean* time frame as a reference, it avoids difficulties in mapping to a blurred reference.
10-
11-
## Quick Tutorial
9+
MRIRealign.jl performs rigid-body (6-DOF) motion correction for 4-D MRI
10+
time-series data. It estimates three rotation angles and three
11+
translations per volume by minimizing the sum of squared intensity
12+
differences, then reslices (resamples) the volumes to undo the estimated
13+
motion.
14+
15+
The algorithm follows the seminal paper by
16+
[Friston et al.](https://doi.org/10.1002/hbm.460030303) and was heavily
17+
inspired by [SPM's](https://www.fil.ion.ucl.ac.uk/spm/) `spm_realign`
18+
function. Key differences from SPM include:
19+
20+
* **Speed** — a Gauss–Newton trust-region optimizer with exact analytic
21+
Jacobians of the rotation matrix converges in fewer iterations than
22+
SPM's re-estimation loop.
23+
* **Consensus estimation** — all time frames are aligned pairwise and a
24+
robust weighted consensus is computed via
25+
[iteratively reweighted least squares](https://en.wikipedia.org/wiki/Iteratively_reweighted_least_squares)
26+
with geodesic rotation distance on SO(3). This is less sensitive to
27+
the image quality of any single reference frame and avoids
28+
difficulties in mapping to a blurred temporal mean.
29+
30+
## Quick Start
1231

1332
On Unix systems, Julia can be installed with
14-
```Bash
33+
```bash
1534
curl -fsSL https://install.julialang.org | sh
1635
```
1736

1837
and on Windows systems with
1938
```
2039
winget install --name Julia --id 9NJNWW8PVKMN -e -s msstore
2140
```
22-
More detailed installation instructions can be found [here](https://julialang.org/install/).
41+
More detailed installation instructions can be found
42+
[here](https://julialang.org/install/).
2343

2444
Thereafter, you can start Julia from the command line with
25-
```Bash
45+
```bash
2646
julia
2747
```
2848

29-
This section assumes that you have a folder at the path `/path_to_files/` with NIfTI files of the format `mask.nii` and `somename_1.nii`, `somename_2.nii`, ... . Our package does not include loading functions, allowing users to load data from [NIfTI](https://github.com/JuliaNeuroscience/NIfTI.jl), [DICOM](https://github.com/JuliaHealth/DICOM.jl), [Matlab](https://github.com/JuliaIO/MAT.jl), [HDF5](https://github.com/JuliaIO/HDF5.jl) files etc.
49+
### Loading data
50+
51+
This tutorial assumes that you have a folder at the path
52+
`/path_to_files/` with NIfTI files of the format `mask.nii` and
53+
`somename_1.nii`, `somename_2.nii`, … . MRIRealign.jl does not include
54+
I/O functions, so you are free to load data from
55+
[NIfTI](https://github.com/JuliaNeuroscience/NIfTI.jl),
56+
[DICOM](https://github.com/JuliaHealth/DICOM.jl),
57+
[MAT](https://github.com/JuliaIO/MAT.jl),
58+
[HDF5](https://github.com/JuliaIO/HDF5.jl) files, etc.
3059

31-
The first time, the packages need to be installed with the package manager:
60+
Install the packages once:
3261

33-
```@Julia
62+
```julia
3463
using Pkg
3564
Pkg.add("MRIRealign")
3665
Pkg.add("NIfTI")
3766
```
3867

39-
Thereafter, we can use them:
68+
Then load them:
4069

41-
```@Julia
70+
```julia
4271
using MRIRealign
4372
using NIfTI
4473
```
4574

46-
We can change the directory
47-
```@Julia
75+
Change to the data directory:
76+
77+
```julia
4878
cd("/path_to_files/")
4979
```
5080

51-
and, optionally, load a mask and convert it to a binary mask
52-
```@Julia
81+
Optionally, load a mask and convert it to a `BitArray`:
82+
83+
```julia
5384
mask = round.(Bool, niread("mask.nii"))
5485
```
55-
Note that the `.` after round indicates a point-wise operation.
5686

57-
We can create a list of file names in the current folder, except for `mask.nii`, and sort them in natural order, i.e., 1, 2, 3, ... instead of the ASCII order 1, 10, 100, 101, ... .
87+
Create a sorted list of volume file names (natural numeric order):
5888

59-
```@Julia
89+
```julia
6090
files = filter(f -> isfile(f) && f != "mask.nii", readdir())
6191
files = sort(files, by = file -> parse(Int, match(r"\d+", file).match))
6292
```
6393

64-
Using the size of the mask, where `size(mask)...` returns the three dimensions separately, we can allocate an array and load all time frames into it:
94+
Allocate a 4-D array and read all volumes into it:
6595

66-
```@Julia
96+
```julia
6797
img = Array{Float64}(undef, size(mask)..., length(files))
6898

6999
for t in eachindex(files)
70100
img[:,:,:,t] .= niread(files[t]).raw
71101
end
72102
```
73103

74-
Now we are all set to call the `realign!` function, which will overwrite `img` with the aligned volumes and return the motion parameters, i.e., 3 rotation and 3 translation parameters in this order:
75-
```@Julia
104+
### Estimating and applying motion correction
105+
106+
Call `realign!`, which overwrites `img` with the aligned volumes and
107+
returns the motion parameters — a `(6, t)` matrix where each column is
108+
`[rx, ry, rz, tx, ty, tz]` (rotations in radians, translations in
109+
voxels):
110+
111+
```julia
76112
params = realign!(img; mask=mask)
77113
```
78114

79-
We can write the aligned images back to the NIfTI files:
80-
```@Julia
115+
Write the aligned images back to NIfTI files:
116+
117+
```julia
81118
for t in eachindex(files)
82119
ni = niread(files[t])
83120
ni.raw .= img[:,:,:,t]
84121
niwrite(files[t], ni)
85122
end
86123
```
87124

88-
Note that this tutorial assumes that the headers of all NIfTI files are identical and replaces the raw data with interpolated data. For changing the NIfTI header instead, we can call `params = realign!(img; mask=mask, realign=false)` and write `params` to the NIfTI header. For more information, refer to [the NIfTI.jl documentation](https://github.com/JuliaNeuroscience/NIfTI.jl).
125+
### Estimate-only workflow
126+
127+
To estimate motion parameters without modifying the images:
128+
129+
```julia
130+
params = realign!(img; mask=mask, realign=false)
131+
```
132+
133+
The returned `params` can later be applied with the two-argument form:
134+
135+
```julia
136+
realign!(img, params)
137+
```
138+
139+
> **Note:** This tutorial assumes that the NIfTI headers of all files are
140+
> identical and replaces the raw data with interpolated data. To update
141+
> the NIfTI header instead (preserving the original voxel data), use
142+
> `realign=false` and write the parameters into the header. See
143+
> [the NIfTI.jl documentation](https://github.com/JuliaNeuroscience/NIfTI.jl)
144+
> for details.
89145
90146

91147
[docs-img]: https://img.shields.io/badge/docs-latest%20release-blue.svg

docs/src/index.md

Lines changed: 115 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,185 @@ CurrentModule = MRIRealign
44

55
# MRIRealign.jl
66

7-
This package aligns a time series of 3D MRI images with similar contrast, following the seminal paper by [Friston et al.](https://doi.org/10.1002/hbm.460030303) It minimizes the squared difference between the images in a given mask. The package was heavily inspired by [SPM's](https://www.fil.ion.ucl.ac.uk/spm/) `spm_realign` function. The principal advantage over `spm_realign` is speed. Additionally, we implemented a *consensus* estimation, which aligns all time frames pairwise and uses [iteratively reweighted least squares](https://en.wikipedia.org/wiki/Iteratively_reweighted_least_squares) to calculate a consensus between all estimates. Compared to a single reference time frame, the consensus approach is less sensitive to the image quality of the reference frame. Compared with the *mean* time frame as a reference, it avoids difficulties in mapping to a blurred reference.
8-
9-
## Quick Tutorial
7+
MRIRealign.jl performs rigid-body (6-DOF) motion correction for 4-D MRI
8+
time-series data. It estimates three rotation angles and three
9+
translations per volume by minimizing the sum of squared intensity
10+
differences, then reslices (resamples) the volumes to undo the estimated
11+
motion.
12+
13+
The algorithm follows the seminal paper by
14+
[Friston et al.](https://doi.org/10.1002/hbm.460030303) and was heavily
15+
inspired by [SPM's](https://www.fil.ion.ucl.ac.uk/spm/) `spm_realign`
16+
function. Key differences from SPM include:
17+
18+
* **Speed** — a Gauss–Newton trust-region optimizer with exact analytic
19+
Jacobians of the rotation matrix converges in fewer iterations than
20+
SPM's re-estimation loop.
21+
* **Consensus estimation** — all time frames are aligned pairwise and a
22+
robust weighted consensus is computed via
23+
[iteratively reweighted least squares](https://en.wikipedia.org/wiki/Iteratively_reweighted_least_squares)
24+
with geodesic rotation distance on SO(3). This is less sensitive to
25+
the image quality of any single reference frame and avoids
26+
difficulties in mapping to a blurred temporal mean.
27+
28+
## Quick Start
1029

1130
On Unix systems, Julia can be installed with
12-
```Bash
31+
```bash
1332
curl -fsSL https://install.julialang.org | sh
1433
```
1534

1635
and on Windows systems with
1736
```
1837
winget install --name Julia --id 9NJNWW8PVKMN -e -s msstore
1938
```
20-
More detailed installation instructions can be found [here](https://julialang.org/install/).
39+
More detailed installation instructions can be found
40+
[here](https://julialang.org/install/).
2141

2242
Thereafter, you can start Julia from the command line with
23-
```Bash
43+
```bash
2444
julia
2545
```
2646

27-
This section assumes that you have a folder at the path `/path_to_files/` with NIfTI files of the format `mask.nii` and `somename_1.nii`, `somename_2.nii`, ... . Our package does not include loading functions, allowing users to load data from [NIfTI](https://github.com/JuliaNeuroscience/NIfTI.jl), [DICOM](https://github.com/JuliaHealth/DICOM.jl), [Matlab](https://github.com/JuliaIO/MAT.jl), [HDF5](https://github.com/JuliaIO/HDF5.jl) files etc.
47+
### Loading data
48+
49+
This tutorial assumes that you have a folder at the path
50+
`/path_to_files/` with NIfTI files of the format `mask.nii` and
51+
`somename_1.nii`, `somename_2.nii`, … . MRIRealign.jl does not include
52+
I/O functions, so you are free to load data from
53+
[NIfTI](https://github.com/JuliaNeuroscience/NIfTI.jl),
54+
[DICOM](https://github.com/JuliaHealth/DICOM.jl),
55+
[MAT](https://github.com/JuliaIO/MAT.jl),
56+
[HDF5](https://github.com/JuliaIO/HDF5.jl) files, etc.
2857

29-
The first time, the packages need to be installed with the package manager:
58+
Install the packages once:
3059

31-
```@Julia
60+
```julia
3261
using Pkg
3362
Pkg.add("MRIRealign")
3463
Pkg.add("NIfTI")
3564
```
3665

37-
Thereafter, we can use them:
66+
Then load them:
3867

39-
```@Julia
68+
```julia
4069
using MRIRealign
4170
using NIfTI
4271
```
4372

44-
We can change the directory
45-
```@Julia
73+
Change to the data directory:
74+
75+
```julia
4676
cd("/path_to_files/")
4777
```
4878

49-
and, optionally, load a mask and convert it to a binary mask
50-
```@Julia
79+
Optionally, load a mask and convert it to a `BitArray`:
80+
81+
```julia
5182
mask = round.(Bool, niread("mask.nii"))
5283
```
53-
Note that the `.` after round indicates a point-wise operation.
5484

55-
We can create a list of file names in the current folder, except for `mask.nii`, and sort them in natural order, i.e., 1, 2, 3, ... instead of the ASCII order 1, 10, 100, 101, ... .
85+
Create a sorted list of volume file names (natural numeric order):
5686

57-
```@Julia
87+
```julia
5888
files = filter(f -> isfile(f) && f != "mask.nii", readdir())
5989
files = sort(files, by = file -> parse(Int, match(r"\d+", file).match))
6090
```
6191

62-
Using the size of the mask, where `size(mask)...` returns the three dimensions separately, we can allocate an array and load all time frames into it:
92+
Allocate a 4-D array and read all volumes into it:
6393

64-
```@Julia
94+
```julia
6595
img = Array{Float64}(undef, size(mask)..., length(files))
6696

6797
for t in eachindex(files)
6898
img[:,:,:,t] .= niread(files[t]).raw
6999
end
70100
```
71101

72-
Now we are all set to call the `realign!` function, which will overwrite `img` with the aligned volumes and return the motion parameters, i.e., 3 rotation and 3 translation parameters in this order:
73-
```@Julia
102+
### Estimating and applying motion correction
103+
104+
Call [`realign!`](@ref), which overwrites `img` with the aligned volumes
105+
and returns the motion parameters — a `(6, t)` matrix where each column
106+
is `[rx, ry, rz, tx, ty, tz]` (rotations in radians, translations in
107+
voxels):
108+
109+
```julia
74110
params = realign!(img; mask=mask)
75111
```
76112

77-
We can write the aligned images back to the NIfTI files:
78-
```@Julia
113+
Write the aligned images back to NIfTI files:
114+
115+
```julia
79116
for t in eachindex(files)
80117
ni = niread(files[t])
81118
ni.raw .= img[:,:,:,t]
82119
niwrite(files[t], ni)
83120
end
84121
```
85122

86-
Note that this tutorial assumes that the headers of all NIfTI files are identical and replaces the raw data with interpolated data. For changing the NIfTI header instead, we can call `params = realign!(img; mask=mask, realign=false)` and write `params` to the NIfTI header. For more information, refer to [the NIfTI.jl documentation](https://github.com/JuliaNeuroscience/NIfTI.jl).
123+
### Estimate-only workflow
124+
125+
To estimate motion parameters without modifying the images:
126+
127+
```julia
128+
params = realign!(img; mask=mask, realign=false)
129+
```
130+
131+
The returned `params` can later be applied with the two-argument form:
132+
133+
```julia
134+
realign!(img, params)
135+
```
136+
137+
### Reference modes
138+
139+
```julia
140+
# Robust consensus across all pairwise alignments (default, slowest)
141+
params = realign!(img; ref_mode=:consensus)
142+
143+
# Align to the temporal mean (fast, may be blurred)
144+
params = realign!(img; ref_mode=:mean)
145+
146+
# Align to a specific time frame (fast, quality depends on that frame)
147+
params = realign!(img; ref_mode=1)
148+
```
149+
150+
### Smoothing
151+
152+
For noisy data, applying Gaussian smoothing before estimation can
153+
improve robustness. The `fwhm` keyword accepts a 3-tuple of
154+
full-width-at-half-maximum values in voxel units:
155+
156+
```julia
157+
params = realign!(img; fwhm=(5.0, 5.0, 5.0))
158+
```
159+
160+
!!! note
161+
The default `fwhm=nothing` (no smoothing) differs from SPM's default
162+
of approximately 5 mm. For noisy data, setting `fwhm` explicitly is
163+
recommended.
164+
165+
!!! note
166+
This tutorial assumes that the NIfTI headers of all files are
167+
identical and replaces the raw data with interpolated data. To
168+
update the NIfTI header instead (preserving the original voxel
169+
data), use `realign=false` and write the parameters into the header.
170+
See [the NIfTI.jl documentation](https://github.com/JuliaNeuroscience/NIfTI.jl)
171+
for details.
172+
87173

174+
## API Reference
88175

89-
## Main Interface
176+
### Main interface
90177

91178
```@docs
92179
MRIRealign.realign!
93180
```
94181

95-
## Helper functions
182+
### Geometric utilities
96183

97184
```@docs
98185
MRIRealign.create_rotation_matrix
99186
MRIRealign.create_affine_matrix
187+
MRIRealign.params_from_rigid_affine
100188
```

0 commit comments

Comments
 (0)