odx 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {odx-0.3.0 → odx-0.3.2}/Cargo.lock +2 -2
- {odx-0.3.0 → odx-0.3.2}/Cargo.toml +1 -1
- {odx-0.3.0 → odx-0.3.2}/PKG-INFO +3 -1
- {odx-0.3.0 → odx-0.3.2}/README.md +3 -1
- {odx-0.3.0 → odx-0.3.2}/SPECIFICATION.md +6 -4
- {odx-0.3.0 → odx-0.3.2}/docs/cli.md +3 -2
- {odx-0.3.0 → odx-0.3.2}/python/README.md +2 -0
- {odx-0.3.0 → odx-0.3.2}/python/src/lib.rs +23 -6
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_foreign_io.py +37 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/dsistudio.rs +478 -49
- {odx-0.3.0 → odx-0.3.2}/src/header.rs +54 -1
- {odx-0.3.0 → odx-0.3.2}/src/interop.rs +96 -17
- {odx-0.3.0 → odx-0.3.2}/src/io/directory.rs +7 -2
- {odx-0.3.0 → odx-0.3.2}/src/io/zip.rs +5 -2
- {odx-0.3.0 → odx-0.3.2}/tests/cli.rs +60 -0
- {odx-0.3.0 → odx-0.3.2}/tests/pam_io.rs +49 -0
- {odx-0.3.0 → odx-0.3.2}/.github/workflows/ci.yml +0 -0
- {odx-0.3.0 → odx-0.3.2}/.github/workflows/release.yml +0 -0
- {odx-0.3.0 → odx-0.3.2}/.gitignore +0 -0
- {odx-0.3.0 → odx-0.3.2}/LICENSE-APACHE +0 -0
- {odx-0.3.0 → odx-0.3.2}/LICENSE-MIT +0 -0
- {odx-0.3.0 → odx-0.3.2}/LICENSE-MRTRIX +0 -0
- {odx-0.3.0 → odx-0.3.2}/LICENSE-NIBABEL +0 -0
- {odx-0.3.0 → odx-0.3.2}/TODO.md +0 -0
- {odx-0.3.0 → odx-0.3.2}/assets/dsistudio_odf8_faces.txt +0 -0
- {odx-0.3.0 → odx-0.3.2}/assets/dsistudio_odf8_vertices.txt +0 -0
- {odx-0.3.0 → odx-0.3.2}/benches/dsistudio_io.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/benches/sphere_lookup.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/docs/compare.md +0 -0
- {odx-0.3.0 → odx-0.3.2}/docs/dsistudio_mrtrix_conversion_workflows.md +0 -0
- {odx-0.3.0 → odx-0.3.2}/docs/fixel_qc.md +0 -0
- {odx-0.3.0 → odx-0.3.2}/docs/template.md +0 -0
- {odx-0.3.0 → odx-0.3.2}/examples/afd_validate.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/examples/check_rotation.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/examples/convert_dsistudio.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/examples/convert_mrtrix_to_fz.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/examples/inspect_h5.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/odx/__init__.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/odx/adapters/__init__.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/odx/adapters/dipy.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/odx/adapters/nibabel.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/odx/spheres.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/pyproject.toml +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/.cargo/config.toml +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/Cargo.toml +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_basic.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_coherence.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_dipy_adapter.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_graddev.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_nibabel_adapter.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/python/tests/test_peak_finder.py +0 -0
- {odx-0.3.0 → odx-0.3.2}/setting_up_cs_and_benchmarking.md +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/bin/odx.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/cli_support.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/combine.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/compare.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/data_array.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/densify.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/descoteaux_sh.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/dtype.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/error.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/fixel_match.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/fmls.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/dsistudio_odf8.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/mat4.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/mif.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/mod.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/mrtrix.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/pam.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/pam_repulsion724_hemisphere_f32.bin +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/pyafq_aodf.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/formats/tortoise_mapmri.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/graddev.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/icosphere.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/io/archive_edit.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/io/filename.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/io/mod.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/lib.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/mmap_backing.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/mrtrix_sh.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/nifti_canon.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/nifti_export.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/odx_file.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/peak_finder.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/qc.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/reference_affine.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/sh_basis_evaluator.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/sh_basis_evaluator.rs.orig +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/sphere_lookup.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/stream.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/template.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/transform/mod.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/transform/resample.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/transform/sh_apsf.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/transform/source_volume.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/transform/upsample.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/typed_view.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/src/validate.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/archive_mutation.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/common/mod.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/fibgz_round_trip.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/graddev.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/interop.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/load_options.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/mif_reader.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/mrtrix_io.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/qc.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/round_trip.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/transform.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/transform_mrtrix_compat.rs +0 -0
- {odx-0.3.0 → odx-0.3.2}/tests/upsample.rs +0 -0
|
@@ -1042,7 +1042,7 @@ dependencies = [
|
|
|
1042
1042
|
|
|
1043
1043
|
[[package]]
|
|
1044
1044
|
name = "odx-py"
|
|
1045
|
-
version = "0.3.
|
|
1045
|
+
version = "0.3.2"
|
|
1046
1046
|
dependencies = [
|
|
1047
1047
|
"bytemuck",
|
|
1048
1048
|
"ndarray",
|
|
@@ -1054,7 +1054,7 @@ dependencies = [
|
|
|
1054
1054
|
|
|
1055
1055
|
[[package]]
|
|
1056
1056
|
name = "odx-rs"
|
|
1057
|
-
version = "0.3.
|
|
1057
|
+
version = "0.3.2"
|
|
1058
1058
|
dependencies = [
|
|
1059
1059
|
"assert_cmd",
|
|
1060
1060
|
"bytemuck",
|
|
@@ -11,7 +11,7 @@ members = [".", "python"]
|
|
|
11
11
|
# release workflow has a verify-version step that fails if the tag and
|
|
12
12
|
# this value disagree.
|
|
13
13
|
[workspace.package]
|
|
14
|
-
version = "0.3.
|
|
14
|
+
version = "0.3.2"
|
|
15
15
|
edition = "2021"
|
|
16
16
|
# Bulk of the crate is dual MIT OR Apache-2.0 (the typical Rust ecosystem
|
|
17
17
|
# license). Two files (src/peak_finder.rs, src/mrtrix_sh.rs) port code from
|
{odx-0.3.0 → odx-0.3.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: odx
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -69,6 +69,8 @@ peaked.save_mrtrix("mrtrix_out/") # → MRtrix mif (auto-converts basis)
|
|
|
69
69
|
|
|
70
70
|
# Round-trip back to dipy if you want
|
|
71
71
|
pam = odx.to_peaks_and_metrics(peaked)
|
|
72
|
+
|
|
73
|
+
wmfod = odx.from_mrtrix("wmfod.mif") # bare SH image (.mif[.gz] or 4-D .nii[.gz])
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
## Features
|
|
@@ -39,7 +39,9 @@ Some formats are composite:
|
|
|
39
39
|
|
|
40
40
|
- MRtrix fixel directories may be paired with `--sh <path>`
|
|
41
41
|
- MRtrix SH images may be paired with `--fixel-dir <path>`
|
|
42
|
-
- DSI Studio `fib.gz` files may use `--reference-affine <mif-or-nifti
|
|
42
|
+
- DSI Studio `fib.gz` files may use `--reference-affine <mif-or-nifti>`: the
|
|
43
|
+
DWI the file was reconstructed from, whose affine odx reorders to DSI
|
|
44
|
+
Studio's LPS voxel order
|
|
43
45
|
- TORTOISE MAP-MRI input (`--input-format tortoise-mapmri-nifti`) pairs a
|
|
44
46
|
coefficient NIfTI given as `<input>` with `--mapmri-tensor <nifti>` and
|
|
45
47
|
`--mapmri-uvec <nifti>`
|
|
@@ -49,7 +49,7 @@ data.odx/
|
|
|
49
49
|
|
|
50
50
|
sphere/ # ODF sampling sphere (optional)
|
|
51
51
|
vertices.3.{dtype} # unit directions (NB_SPHERE_VERTICES x 3)
|
|
52
|
-
faces.3.uint32 # triangle indices (NB_SPHERE_FACES x 3)
|
|
52
|
+
faces.3.uint32 # triangle indices (NB_SPHERE_FACES x 3, optional)
|
|
53
53
|
|
|
54
54
|
odf/ # ODF amplitudes (optional)
|
|
55
55
|
amplitudes.{ndirs}.{dtype} # (NB_VOXELS x NB_SPHERE_VERTICES)
|
|
@@ -104,7 +104,7 @@ data.odx/
|
|
|
104
104
|
| Field | Present when | Type | Description |
|
|
105
105
|
|-------|-------------|------|-------------|
|
|
106
106
|
| `NB_SPHERE_VERTICES` | `sphere/` exists | `u64` | Number of ODF sampling directions. |
|
|
107
|
-
| `NB_SPHERE_FACES` | `sphere
|
|
107
|
+
| `NB_SPHERE_FACES` | `sphere/faces` exists | `u64` | Number of triangles in the sphere mesh. |
|
|
108
108
|
| `SH_ORDER` | `sh/` exists | `u64` | Maximum spherical harmonic order. |
|
|
109
109
|
| `SH_BASIS` | `sh/` exists | `string` | SH basis convention: `"descoteaux07"` (Dipy) or `"tournier07"` (MRtrix). |
|
|
110
110
|
| `SH_FULL_BASIS` | optional | `bool` | When `true`, the SH array uses the full basis with both even and odd ℓ (ncoeffs = `(SH_ORDER + 1)²`); when `false` or absent, the symmetric even-only basis is used (ncoeffs = `(SH_ORDER + 1)(SH_ORDER + 2)/2`). Full basis is required for asymmetric ODFs (e.g. pyAFQ aodf). |
|
|
@@ -182,7 +182,9 @@ RAS+mm orientation.
|
|
|
182
182
|
### `sphere/faces.3.uint32`
|
|
183
183
|
|
|
184
184
|
Shape `(NB_SPHERE_FACES, 3)`. Triangle indices into the vertex array, for
|
|
185
|
-
mesh visualization and interpolation.
|
|
185
|
+
mesh visualization and interpolation. Optional: a sphere known only by its
|
|
186
|
+
sampling directions (e.g. one imported from a dipy PAM5 file) has vertices but
|
|
187
|
+
no faces, and readers must accept a `sphere/` without `faces`.
|
|
186
188
|
|
|
187
189
|
The sphere directory is required when `odf/` data is present on a noncanonical
|
|
188
190
|
sphere. It may be omitted when `SPHERE_ID` identifies a standard sphere. It is
|
|
@@ -361,7 +363,7 @@ This applies to:
|
|
|
361
363
|
|
|
362
364
|
| Source | Transform |
|
|
363
365
|
|--------|-----------|
|
|
364
|
-
| DSI Studio (LPS
|
|
366
|
+
| DSI Studio (voxel frame of its LPS+-reordered grid) | `d_ras = R · d_dsi`, with `R` the 3×3 linear part of `trans` with its columns normalized; this is `diag(-1, -1, 1)` (negate x and y) only for a non-oblique image. Applies to `odf_vertices`, `dir{p}` and so to `index{p}`. A `.fib.gz` without `trans` uses the reference DWI affine reordered to LPS+ voxel order. |
|
|
365
367
|
| MRtrix (RAS) | No change needed for directions. Affine from NIfTI header. |
|
|
366
368
|
| Dipy (RAS) | No change needed. Affine from NIfTI header. |
|
|
367
369
|
|
|
@@ -474,7 +474,8 @@ can). The loader also reads them back in preference to `index{p}`.
|
|
|
474
474
|
Useful options:
|
|
475
475
|
|
|
476
476
|
- `--reference-affine <nifti>`: required for a native-space `.fib.gz`/`.fz`
|
|
477
|
-
without a `trans` record (e.g. the preprocessed DWI it was built from)
|
|
477
|
+
without a `trans` record (e.g. the preprocessed DWI it was built from); it
|
|
478
|
+
is reordered to DSI Studio's LPS voxel order automatically.
|
|
478
479
|
- `--odf-lmax <even-int>`, `--apsf-dirs <N>`: accuracy knobs for the dense-ODF
|
|
479
480
|
and SH paths.
|
|
480
481
|
- `--json`: machine-readable summary including the correction report
|
|
@@ -504,7 +505,7 @@ Useful options:
|
|
|
504
505
|
Some formats are composite:
|
|
505
506
|
|
|
506
507
|
- MRtrix fixel directories may be accompanied by an SH image.
|
|
507
|
-
- DSI Studio `fib.gz` inputs may need `--reference-affine` when the file does not contain a usable transform.
|
|
508
|
+
- DSI Studio `fib.gz` inputs may need `--reference-affine` when the file does not contain a usable transform (no `trans` record). Pass the DWI NIfTI the file was reconstructed from: DSI Studio reorders voxels to LPS on import, and odx reorders the reference affine the same way (as nibabel's `io_orientation` + `ornt_transform` + `inv_ornt_aff` would), so a LAS or otherwise ordered DWI lands its voxels correctly. An affine that is already LPS-ordered is used as is.
|
|
508
509
|
- ODX may hold sparse fixels, dense ODFs, and SH together in one dataset.
|
|
509
510
|
|
|
510
511
|
The CLI therefore treats `<input>` as the primary object and accepts companion inputs as flags instead of requiring a manifest format.
|
|
@@ -32,6 +32,8 @@ peaked.save_mrtrix("mrtrix_out/") # → MRtrix mif (auto-converts basis)
|
|
|
32
32
|
|
|
33
33
|
# Round-trip back to dipy if you want
|
|
34
34
|
pam = odx.to_peaks_and_metrics(peaked)
|
|
35
|
+
|
|
36
|
+
wmfod = odx.from_mrtrix("wmfod.mif") # bare SH image (.mif[.gz] or 4-D .nii[.gz])
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
## Features
|
|
@@ -1720,6 +1720,11 @@ fn from_fz(path: PathBuf, skip_odf: bool) -> PyResult<PyOdx> {
|
|
|
1720
1720
|
|
|
1721
1721
|
/// Load a DSI Studio `.fib.gz`. `skip_odf=True` discards the dense ODFs
|
|
1722
1722
|
/// while decompressing, for fixel-only work.
|
|
1723
|
+
///
|
|
1724
|
+
/// `affine` is only used when the file has no `trans` record: pass the
|
|
1725
|
+
/// affine of the DWI NIfTI the file was reconstructed from. DSI Studio
|
|
1726
|
+
/// reorders voxels to LPS on import, and the affine is reordered the same
|
|
1727
|
+
/// way (a no-op for an affine already in LPS order).
|
|
1723
1728
|
#[pyfunction]
|
|
1724
1729
|
#[pyo3(signature = (path, affine=None, *, skip_odf=false))]
|
|
1725
1730
|
fn from_fibgz(
|
|
@@ -1733,13 +1738,25 @@ fn from_fibgz(
|
|
|
1733
1738
|
Ok(PyOdx::from_dataset(dataset))
|
|
1734
1739
|
}
|
|
1735
1740
|
|
|
1736
|
-
/// Load
|
|
1737
|
-
///
|
|
1738
|
-
///
|
|
1739
|
-
///
|
|
1741
|
+
/// Load MRtrix data. `path` is either a bare SH image (`.mif`, `.mif.gz`,
|
|
1742
|
+
/// `.nii`, `.nii.gz`; voxel order is canonicalized to RAS+) or a directory
|
|
1743
|
+
/// containing a fixel directory and (optionally) a sibling SH `.mif` file.
|
|
1744
|
+
/// For a directory, if `sh_filename` is provided the SH is loaded from
|
|
1745
|
+
/// `path/sh_filename`; otherwise common defaults are tried (`wmfod.mif`,
|
|
1746
|
+
/// `WM_FODs.mif`). `sh_filename` is not accepted with a bare SH image.
|
|
1740
1747
|
#[pyfunction]
|
|
1741
|
-
#[pyo3(signature = (
|
|
1742
|
-
fn from_mrtrix(
|
|
1748
|
+
#[pyo3(signature = (path, sh_filename=None))]
|
|
1749
|
+
fn from_mrtrix(path: PathBuf, sh_filename: Option<String>) -> PyResult<PyOdx> {
|
|
1750
|
+
if path.is_file() {
|
|
1751
|
+
if sh_filename.is_some() {
|
|
1752
|
+
return Err(PyValueError::new_err(
|
|
1753
|
+
"from_mrtrix: sh_filename is only valid when path is a directory",
|
|
1754
|
+
));
|
|
1755
|
+
}
|
|
1756
|
+
let dataset = odx_rs::mrtrix::load_mrtrix_sh(&path).map_err(map_io)?;
|
|
1757
|
+
return Ok(PyOdx::from_dataset(dataset));
|
|
1758
|
+
}
|
|
1759
|
+
let directory = path;
|
|
1743
1760
|
let candidates: Vec<PathBuf> = match sh_filename {
|
|
1744
1761
|
Some(name) => vec![directory.join(&name), directory.join(format!("{name}.gz"))],
|
|
1745
1762
|
None => vec![
|
|
@@ -42,6 +42,43 @@ def test_save_mif_then_from_mrtrix_roundtrips_sh(tmp_path):
|
|
|
42
42
|
np.testing.assert_allclose(loaded_sh, src_sh, atol=1e-3)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def test_from_mrtrix_accepts_a_bare_sh_image(tmp_path):
|
|
46
|
+
src = _make_dataset()
|
|
47
|
+
# save_mif gzips by default and writes "fod.mif.gz" even when given "fod.mif".
|
|
48
|
+
path = tmp_path / "fod.mif.gz"
|
|
49
|
+
src.save_mif(path, which="sh")
|
|
50
|
+
assert path.is_file()
|
|
51
|
+
loaded = odx.from_mrtrix(path)
|
|
52
|
+
# The bare-image loader derives its own mask from the SH, so compare on
|
|
53
|
+
# the full grid rather than masked rows.
|
|
54
|
+
np.testing.assert_allclose(
|
|
55
|
+
np.asarray(loaded.densify_sh()), np.asarray(src.densify_sh()), atol=1e-3
|
|
56
|
+
)
|
|
57
|
+
np.testing.assert_array_equal(np.asarray(loaded.affine), np.asarray(src.affine))
|
|
58
|
+
assert loaded.dimensions == src.dimensions
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_from_mrtrix_accepts_a_bare_nifti_sh_image(tmp_path):
|
|
62
|
+
nib = pytest.importorskip("nibabel")
|
|
63
|
+
src = _make_dataset()
|
|
64
|
+
dense = np.asarray(src.densify_sh(), dtype=np.float32)
|
|
65
|
+
path = tmp_path / "fod.nii.gz"
|
|
66
|
+
nib.save(nib.Nifti1Image(dense, np.eye(4)), path)
|
|
67
|
+
loaded = odx.from_mrtrix(path)
|
|
68
|
+
np.testing.assert_allclose(np.asarray(loaded.densify_sh()), dense, atol=1e-3)
|
|
69
|
+
np.testing.assert_array_equal(np.asarray(loaded.affine), np.eye(4))
|
|
70
|
+
assert loaded.dimensions == dense.shape[:3]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_from_mrtrix_rejects_sh_filename_with_a_file_path(tmp_path):
|
|
74
|
+
src = _make_dataset()
|
|
75
|
+
path = tmp_path / "fod.mif.gz"
|
|
76
|
+
src.save_mif(path, which="sh")
|
|
77
|
+
assert path.is_file()
|
|
78
|
+
with pytest.raises(ValueError, match="sh_filename"):
|
|
79
|
+
odx.from_mrtrix(path, sh_filename="wmfod.mif")
|
|
80
|
+
|
|
81
|
+
|
|
45
82
|
# ─── DSI Studio FZ ────────────────────────────────────────────────────────────
|
|
46
83
|
|
|
47
84
|
|