|
1 | | -# End-to-End Workflow |
| 1 | +# End-to-End MRI Workflow |
2 | 2 |
|
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 | + |
| 289 | + |
| 290 | + |
0 commit comments