Skip to content

Commit 2515e74

Browse files
committed
adding mri
1 parent 6c36f7a commit 2515e74

14 files changed

Lines changed: 467 additions & 539220 deletions

File tree

.github/workflows/CI.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
workflow:
2424
- patient_level_prediction
2525
- geospatial_census
26+
- mri_simulation
2627

2728
steps:
2829
- uses: actions/checkout@v4
@@ -55,3 +56,13 @@ jobs:
5556
println(pkg.name)
5657
end
5758
'
59+
60+
- name: Prepare MRI simulation config
61+
if: matrix.workflow == 'mri_simulation'
62+
run: |
63+
cp src/workflows/mri_simulation/config.toml.example src/workflows/mri_simulation/config.toml
64+
65+
- name: Run MRI simulation workflow
66+
if: matrix.workflow == 'mri_simulation'
67+
run: |
68+
julia --project=src/workflows/mri_simulation src/workflows/mri_simulation/run.jl
56.2 KB
Loading
148 KB
Loading

docs/src/mri-workflow.md

Lines changed: 289 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,290 @@
1-
# End-to-End Workflow
1+
# End-to-End MRI Workflow
22

3-
This page will be populated after the implementation scripts are added.
3+
This page documents the runnable MRI workflow in [src/workflows/mri_simulation](https://github.com/JuliaHealth/JuliaHealthZoo/tree/main/src/workflows/mri_simulation).
4+
It is generated from the documentation source in [docs/make.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/docs/make.jl) and published by the docs CI in [`.github/workflows/docs.yml`](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/.github/workflows/docs.yml).
5+
6+
This page presents the runnable code path, explains each file, and links the documentation directly to the implementation.
7+
8+
## What the workflow does
9+
10+
The runnable pipeline is:
11+
12+
1. Simulate MRI data with KomaMRI.jl.
13+
2. Reconstruct the simulated raw data with MRIReco.jl.
14+
3. Run a minimal BART example through BartIO.jl.
15+
16+
This workflow stops at BART on purpose. PythonCall is documented separately in [Interoperability (PythonCall/C++)](mri-interop.md).
17+
18+
## Source map
19+
20+
| Step | File | Purpose |
21+
| --- | --- | --- |
22+
| Orchestration | [run.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/run.jl) | Reads config, sets shared constants, runs each step |
23+
| Step 1 | [01_simulate_komamri.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/src/01_simulate_komamri.jl) | Generates synthetic raw MRI data |
24+
| Step 2 | [02_komamri_to_mrireco.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/src/02_komamri_to_mrireco.jl) | Reconstructs the simulated raw data |
25+
| Step 3 | [03_bartio_bart.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/src/03_bartio_bart.jl) | Calls BART from Julia through BartIO |
26+
| Config example | [config.toml.example](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/config.toml.example) | Shows the supported local settings |
27+
28+
## How page generation works
29+
30+
DocumenterVitepress turns these markdown pages into the website.
31+
That means this page is not a separate handwritten summary; it is part of the docs source tree and gets rebuilt when the docs workflow runs.
32+
33+
The two CI pieces to know are:
34+
35+
- [`.github/workflows/docs.yml`](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/.github/workflows/docs.yml) builds and deploys the website.
36+
- [`.github/workflows/CI.yml`](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/.github/workflows/CI.yml) runs the workflow itself so failures show up as CI failures, not just as documentation changes.
37+
38+
This keeps the documentation tied to the repository and makes workflow failures visible in CI.
39+
40+
## Orchestrator
41+
42+
The runner in [run.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/run.jl) is intentionally small.
43+
It reads configuration, creates the output directory, and includes each step in order.
44+
45+
The code below is the entry point for the workflow. It shows how shared settings are loaded once, then reused by each included step script.
46+
47+
```julia
48+
config = TOML.parsefile(config_file)
49+
50+
const OUTPUT_DIR = joinpath(@__DIR__, config["output"]["dir"])
51+
const KOMA_GPU = config["koma"]["gpu"]
52+
const BART_ENABLED = get(get(config, "bart", Dict{String, Any}()), "enabled", false)
53+
const BART_PATH = get(get(config, "bart", Dict{String, Any}()), "bart_path", "")
54+
55+
steps = [
56+
("KomaMRI simulation", joinpath("src", "01_simulate_komamri.jl")),
57+
("KomaMRI to MRIReco", joinpath("src", "02_komamri_to_mrireco.jl")),
58+
("BART via BartIO", joinpath("src", "03_bartio_bart.jl")),
59+
]
60+
```
61+
62+
What this means in practice:
63+
64+
- `OUTPUT_DIR` is where every result file is written.
65+
- `KOMA_GPU` controls whether simulation runs on CPU or GPU.
66+
- `BART_ENABLED` and `BART_PATH` keep BART optional.
67+
- The `steps` list makes the workflow easy to read and easy to extend.
68+
69+
This pattern keeps orchestration separate from implementation. Each step file remains focused on one task, while the runner only handles control flow and configuration.
70+
71+
## Step 1: KomaMRI simulation
72+
73+
The simulation step is in [01_simulate_komamri.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/src/01_simulate_komamri.jl).
74+
It loads a built-in phantom, reads an example EPI sequence, simulates the acquisition, and stores the raw output.
75+
76+
This section uses KomaMRI to generate synthetic MRI data from a built-in phantom and an example sequence. The output of this step becomes the input to MRIReco in the next stage.
77+
78+
```julia
79+
scanner = Scanner()
80+
phantom = brain_phantom2D()
81+
82+
seq_path = joinpath(
83+
dirname(pathof(KomaMRI)),
84+
"../examples/5.koma_paper/comparison_accuracy/sequences/EPI/epi_100x100_TE100_FOV230.seq",
85+
)
86+
sequence = read_seq(seq_path)
87+
88+
sim_params = KomaMRICore.default_sim_params()
89+
sim_params["gpu"] = KOMA_GPU
90+
raw_signal = simulate(phantom, sequence, scanner; sim_params = sim_params)
91+
```
92+
93+
The snippet creates the scanner and phantom, loads the sequence file from the package examples, applies simulation settings, and runs the acquisition simulation.
94+
95+
What each part does:
96+
97+
- `Scanner()` creates the scanner/system model.
98+
- `brain_phantom2D()` gives a built-in synthetic anatomy.
99+
- `read_seq(...)` loads the example EPI sequence.
100+
- `default_sim_params()` starts from KomaMRI defaults.
101+
- `sim_params["gpu"] = KOMA_GPU` keeps the CPU/GPU choice in config.
102+
- `simulate(...)` produces the raw data that the next step reconstructs.
103+
104+
The step writes the raw binary output and a small text summary so the acquisition can be checked without opening the binary file.
105+
106+
The step then writes two files:
107+
108+
- `output/komamri_raw.bin`
109+
- `output/komamri_raw_summary.txt`
110+
111+
The summary records the trajectory type, reconstruction size, encoded size, and number of profiles so the output can be checked quickly.
112+
113+
## Step 2: MRIReco reconstruction
114+
115+
The reconstruction step is in [02_komamri_to_mrireco.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/src/02_komamri_to_mrireco.jl).
116+
It reads the serialized raw data, wraps it in MRIReco’s acquisition container, and runs a direct reconstruction.
117+
118+
This step shows the Julia-native reconstruction path. The saved raw acquisition from KomaMRI is deserialized, converted into MRIReco’s input type, and reconstructed with a direct solver.
119+
120+
```julia
121+
raw_path = joinpath(OUTPUT_DIR, "komamri_raw.bin")
122+
if !isfile(raw_path)
123+
error("Missing input raw data: output/komamri_raw.bin. Run Step 1 first.")
124+
end
125+
126+
raw_signal = deserialize(raw_path)
127+
128+
acquisition_data = AcquisitionData(raw_signal)
129+
if !isempty(acquisition_data.traj)
130+
acquisition_data.traj[1].circular = false
131+
end
132+
133+
recon_width, recon_height = raw_signal.params["reconSize"][1:2]
134+
recon_options = Dict{Symbol, Any}(
135+
:reco => "direct",
136+
:reconSize => (recon_width, recon_height),
137+
)
138+
139+
reconstructed_image = reconstruction(acquisition_data, recon_options)
140+
```
141+
142+
The code first checks that the raw file exists, then reconstructs the image using the size stored in the simulation metadata. That keeps the reconstruction aligned with the simulated acquisition.
143+
144+
What this does:
145+
146+
- `deserialize(raw_path)` reloads the simulated raw acquisition.
147+
- `AcquisitionData(raw_signal)` converts the raw data into MRIReco’s input type.
148+
- The trajectory adjustment keeps the reconstruction path explicit.
149+
- `recon_options` tells MRIReco to use direct reconstruction and the target image size.
150+
- `reconstruction(...)` returns the reconstructed image.
151+
152+
The output is saved both as a binary file and as a short summary file. The summary is enough to verify dimensions and reconstruction size at a glance.
153+
154+
This step writes:
155+
156+
- `output/mrireco_reconstruction.bin`
157+
- `output/mrireco_reconstruction_summary.txt`
158+
159+
The summary stores the reconstruction size, number of dimensions, and final array shape.
160+
161+
### MRIReco context
162+
163+
MRIReco is the Julia-native reconstruction layer in this workflow.
164+
It is designed for modular reconstruction experiments and can be extended toward more advanced methods such as compressed sensing and ADMM-based solvers.
165+
That is why it is the natural Julia-side reconstruction checkpoint before comparing against BART.
166+
167+
The next step uses BART to provide a command-line reconstruction comparison through BartIO.
168+
169+
## Step 3: BART via BartIO
170+
171+
The BART step is in [03_bartio_bart.jl](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/src/03_bartio_bart.jl).
172+
It is optional, controlled by the config file, and runs only when BART is installed locally.
173+
174+
This section demonstrates how Julia can orchestrate an external reconstruction tool while keeping the workflow in one place.
175+
176+
```julia
177+
if !BART_ENABLED
178+
println("BART step skipped (bart.enabled = false in config.toml).")
179+
else
180+
if isempty(BART_PATH)
181+
error("bart.enabled=true but bart_path is empty in config.toml")
182+
end
183+
184+
using BartIO
185+
186+
set_bart_path(BART_PATH)
187+
bart(0, "version")
188+
189+
kspace_trajectory = bart(1, "traj -x 128 -y 256 -r")
190+
bart_kspace = bart(1, "phantom -k -t", kspace_trajectory)
191+
bart_reconstruction = bart(1, "nufft -i", kspace_trajectory, bart_kspace)
192+
end
193+
```
194+
195+
The code first checks whether the BART step is enabled, then sets the BART executable path, validates the installation, and runs a minimal phantom-to-reconstruction pipeline.
196+
197+
What this does:
198+
199+
- `set_bart_path(BART_PATH)` tells BartIO where the BART executable lives.
200+
- `bart(0, "version")` checks that BART is reachable.
201+
- `bart(1, "traj -x 128 -y 256 -r")` creates a trajectory.
202+
- `bart(1, "phantom -k -t", ...)` generates k-space phantom data.
203+
- `bart(1, "nufft -i", ...)` performs the reconstruction.
204+
205+
The result is written to BART’s `.cfl/.hdr` format and read back once to confirm the round-trip works. That makes the data exchange explicit and reproducible.
206+
207+
The step then writes and reads back `.cfl/.hdr` files:
208+
209+
- `output/bart_kspace.(cfl/hdr)`
210+
- `output/bart_reconstruction.(cfl/hdr)`
211+
- `output/bartio_summary.txt`
212+
213+
That round-trip matters because it shows the workflow can move BART data through Julia explicitly and reproducibly.
214+
215+
## Configuration
216+
217+
The local config example is [config.toml.example](https://github.com/JuliaHealth/JuliaHealthZoo/blob/main/src/workflows/mri_simulation/config.toml.example).
218+
It keeps BART off by default so the workflow runs on machines that do not have BART installed.
219+
220+
The example config is the file users copy locally before running the workflow.
221+
222+
```toml
223+
[output]
224+
dir = "output"
225+
226+
[koma]
227+
gpu = false
228+
229+
[bart]
230+
enabled = false
231+
bart_path = ""
232+
```
233+
234+
The values are intentionally minimal: a local output directory, CPU simulation by default, and BART disabled unless the user explicitly enables it.
235+
236+
To enable BART locally, set:
237+
238+
- `bart.enabled = true`
239+
- `bart.bart_path = "/path/to/bart"`
240+
241+
## Outputs you should expect
242+
243+
After a successful run, the workflow produces:
244+
245+
These files are the main run artifacts. They cover the simulated raw data, the reconstructed image, and the BART outputs.
246+
247+
- `output/komamri_raw.bin`
248+
- `output/komamri_raw_summary.txt`
249+
- `output/mrireco_reconstruction.bin`
250+
- `output/mrireco_reconstruction_summary.txt`
251+
- `output/bart_kspace.cfl`
252+
- `output/bart_kspace.hdr`
253+
- `output/bart_reconstruction.cfl`
254+
- `output/bart_reconstruction.hdr`
255+
- `output/bartio_summary.txt`
256+
257+
## What the outputs look like
258+
259+
These are the kinds of summaries you should see after a successful local run:
260+
261+
Each summary file is short by design. It gives a quick check that the workflow ran with the expected sizes and output structure.
262+
263+
```text
264+
KomaMRI raw-data summary
265+
trajectory=other
266+
recon_size=[100, 100, 1]
267+
encoded_size=[100, 100, 1]
268+
num_profiles=100
269+
```
270+
271+
```text
272+
MRIReco reconstruction summary
273+
recon_size=(100, 100)
274+
image_ndims=6
275+
image_size=(100, 100, 1, 1, 1, 1)
276+
```
277+
278+
```text
279+
BartIO/BART summary
280+
kspace_size=(1, 128, 256)
281+
reconstruction_size=(128, 128)
282+
```
283+
284+
The workflow also writes PNG reconstructions locally in the output folder:
285+
286+
The images below are copies of the workflow outputs, placed in the docs tree so they can render directly on the page.
287+
288+
![KomaMRI to MRIReco reconstruction](assets/mri-workflow/komamri_mrireco_reconstruction.png)
289+
290+
![MRIReco baseline reconstruction](assets/mri-workflow/mrireco_baseline_reconstruction.png)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
config.toml
2+
output/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name = "MRISimulationReconstructionWorkflow"
2+
authors = ["JuliaHealth contributors"]
3+
4+
[deps]
5+
BartIO = "8b90e6a2-6e4d-4906-89ae-b5f72ca4a65b"
6+
KomaMRI = "6a340f8b-2cdf-4c04-99be-4953d9b66d0a"
7+
MRIReco = "bdf86e05-2d2b-5731-a332-f3fe1f9e047f"
8+
9+
[compat]
10+
julia = "1.10"

src/workflows/mri_simulation/README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[output]
2+
dir = "output"
3+
4+
[koma]
5+
# Use CPU for reproducibility on machines without GPU support.
6+
gpu = false
7+
8+
[bart]
9+
# Optional BART/BartIO execution. Enable only if BART is installed locally.
10+
enabled = false
11+
# Example: "/usr/local/bin/bart" or "C:/tools/bart/bart.exe"
12+
bart_path = ""
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
println("MRI Simulation and Reconstruction Workflow")
2+
3+
using TOML
4+
5+
config_file = joinpath(@__DIR__, "config.toml")
6+
if !isfile(config_file)
7+
error(
8+
"""
9+
config.toml not found.
10+
Copy config.toml.example -> config.toml and adjust settings.
11+
""",
12+
)
13+
end
14+
15+
config = TOML.parsefile(config_file)
16+
17+
const OUTPUT_DIR = joinpath(@__DIR__, config["output"]["dir"])
18+
const KOMA_GPU = config["koma"]["gpu"]
19+
const BART_ENABLED = get(get(config, "bart", Dict{String, Any}()), "enabled", false)
20+
const BART_PATH = get(get(config, "bart", Dict{String, Any}()), "bart_path", "")
21+
22+
mkpath(OUTPUT_DIR)
23+
24+
steps = [
25+
("KomaMRI simulation", joinpath("src", "01_simulate_komamri.jl")),
26+
("KomaMRI to MRIReco", joinpath("src", "02_komamri_to_mrireco.jl")),
27+
("BART via BartIO", joinpath("src", "03_bartio_bart.jl")),
28+
]
29+
30+
for (label, script_path) in steps
31+
println("\n-- $label -------------------------")
32+
include(script_path)
33+
end
34+
35+
println("\nWorkflow complete!")
36+
println("Outputs are in: $OUTPUT_DIR")

0 commit comments

Comments
 (0)