Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:

strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"] # Ensure versions are strings
python-version: ["3.11", "3.12", "3.13"]

steps:
# Set debug environment variable
Expand All @@ -100,4 +100,4 @@ jobs:

# Run Nox sessions
- name: Run Nox
run: nox --python ${{ matrix.python-version }}
run: nox --python ${{ matrix.python-version }}
1 change: 0 additions & 1 deletion docs/advanced/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Pages:
- [Python API](./python-api.md): Use `cardiotensor` as a Python library for
structure tensor analysis, angle computation, tractography, and plotting.
- [Transmural Analysis](./cardio-analysis.md): Extract transmural profiles from angle and FA maps with `cardio-analysis`.
- [Streamlines](./streamlines.md): Generate and visualize 3D streamlines with `cardio-generate-streamlines` and `cardio-visualize-streamlines`.
- [SLURM Launcher](./slurm-launcher.md): Submit chunked `cardio-tensor` jobs on HPC clusters.

See also:
Expand Down
9 changes: 9 additions & 0 deletions docs/advanced/slurm-launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ Other useful flags:
- `--no_monitor`: submit jobs and return immediately (do not watch output progress).
- `--dry_run`: generate the `.slurm` script and print `sbatch` command without submitting.

When `LOW_MEMORY=True`, the launcher estimates the padded tensor, mask, worker,
and post-processing peak from `--mem_gb`. If the requested `--chunk_size` is
unsafe, it is reduced before the array is submitted. Low-memory tensor files
default to `OUTPUT_PATH/.cardiotensor_scratch`, not node `/tmp`. Set
`LOW_MEMORY_DIR` only when another data-processing filesystem should be used.
The generated job prints the selected tensor scratch root and its free capacity.
After an OOM or cancelled job, remove any orphaned `cardiotensor_tensor_*`
directories from that scratch root.

Run help for the full list:

```bash
Expand Down
112 changes: 0 additions & 112 deletions docs/advanced/streamlines.md

This file was deleted.

65 changes: 29 additions & 36 deletions docs/getting-technical/angles.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,53 @@
# Angle Definitions

This page explains how helical and intrusion angles are calculated from the 3D eigenvector field derived by `cardiotensor`.
Cardiotensor computes helical angle (HA) and intrusion angle (IA) from the local myocyte-axis eigenvector.

By default, Cardiotensor reports **unprojected 3D angles**. The primary eigenvector is not first flattened onto a 2D plane before the angle is measured, because projection discards one component of the vector and can bias the resulting angle. Projection-based angles are available only for legacy comparison.
By default, these are **unprojected 3D angles**. The vector is not flattened onto a 2D plane before measuring HA or IA, because projection removes one component of the vector and can bias the result.

## Coordinate System
## Local Components

A transformation to a cylindrical coordinate system is defined for each voxel based on an approximation of the left ventricle (LV) centerline.
Cardiotensor expresses the vector in a centerline-based cylindrical frame for the left ventricle:

- **Radial (r)**: outward from the LV center
- **Circumferential (θ)**: tangential around the ventricle
- **Longitudinal (z)**: base to apex direction
- **R**: radial component, outward from the LV centerline
- **C**: circumferential component, tangent around the ventricle
- **L**: longitudinal component, along the LV axis

To compute local fiber angles consistently, all eigenvectors are first rotated into this cylindrical coordinate frame. This alignment is performed using the Rodrigues rotation formula, which computes the minimal-angle rotation that maps the global reference axis (here the z-axis) onto the local longitudinal axis at each point. This allows a robust comparison of orientations across the myocardium.
This is a practical LV cylindrical approximation. It should not be read as a full reproduction of anatomically corrected methods that explicitly model epicardial curvature.

## Helical Angle (HA)
## Helical Angle

The helical angle is defined as the angle between the primary myocyte-axis eigenvector \\( \vec{v}_1 \\) and the local circumferential plane.
The helical angle measures how far the vector points out of the local horizontal, or short-axis, plane. That plane is spanned by the radial and circumferential directions.

In local cylindrical coordinates, with radial component \\(R\\), circumferential component \\(C\\), and longitudinal component \\(L\\), the unprojected helical angle is:
```text
HA = atan2(L, sqrt(R^2 + C^2))
```

\\[
\mathrm{HA} = \arctan2\left(L, \sqrt{R^2 + C^2}\right)
\\]
Typical values are around -60 degrees at the epicardium, 0 degrees in the mid-wall, and +60 degrees at the endocardium.

It captures the transmural variation of fiber orientation from epicardium to endocardium.
## Intrusion Angle

Typical pattern:
- ~−60° at epicardium
- ~0° in mid-wall
- ~+60° at endocardium
The intrusion angle measures radial deviation from the local tangential plane. That plane is spanned by the circumferential and longitudinal directions.

## Intrusion Angle (IA)
```text
IA = atan2(R, sqrt(C^2 + L^2))
```

The intrusion angle is the angle between the primary myocyte-axis eigenvector \\( \vec{v}_1 \\) and the **tangential plane** (longitudinal + circumferential).
## Projected Legacy Angles

Using the same local components, the unprojected intrusion angle is:
Set `PROJECTED_ANGLES = True` only when comparing with legacy projection-based maps:

\\[
\mathrm{IA} = \arctan2\left(R, \sqrt{C^2 + L^2}\right)
\\]
```text
HA_projected = atan2(L, C)
IA_projected = atan2(R, C)
```

It captures radial deviation of fiber aggregates and can help identify wall thickening or microstructural disruptions.
`IA_projected` corresponds to the projected transverse angle terminology often used in the literature.

## Projection Bias

Conventional projected angles are computed after removing one vector component, for example \\(\arctan2(L, C)\\) for projected helical angle or \\(\arctan2(R, C)\\) for projected intrusion angle. These projected quantities can differ from the true 3D orientation when the discarded component is large.

Set `PROJECTED_ANGLES = True` only when you need legacy projected `HA_projected` and `IA_projected` maps for comparison with literature.

This convention follows the projection-error discussion in Agger et al., "Anatomically correct assessment of the orientation of the cardiomyocytes using diffusion tensor imaging", *NMR in Biomedicine* (2020), https://doi.org/10.1002/nbm.4205.
Projection-based angles can differ from the true 3D orientation when the discarded component is large. This projection bias is discussed in Agger et al., "Anatomically correct assessment of the orientation of the cardiomyocytes using diffusion tensor imaging", *NMR in Biomedicine* (2020), https://doi.org/10.1002/nbm.4205.

## Angle Ranges

Both angles are reported in degrees:
- **HA**: −90° to +90°
- **IA**: −90° to +90°

Angles are defined in a left-handed cylindrical coordinate system aligned to the LV.
- **HA**: -90 to +90 degrees
- **IA**: -90 to +90 degrees
44 changes: 39 additions & 5 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ RHO = 0.6
TRUNCATE = 4

# Padding to avoid border artifacts
# Default value is TRUNCATE * RHO + 0.5
# By default this includes the support of both Gaussian filtering stages.
# VERTICAL_PADDING = 10

# Number of slices to load into memory at a time during processing.
Expand All @@ -46,10 +46,22 @@ N_CHUNK = 20
# Enable GPU computation during the structure tensor calculation (True/False)
USE_GPU = True

# Whether to save the orientation vectors (as .npy) (True/False)
# Store temporary eigenvalue/eigenvector volumes in memory-mapped files.
# Enable this when tensor outputs do not comfortably fit in RAM. Temporary
# files default to OUTPUT_PATH/.cardiotensor_scratch, never the system /tmp.
# Unsafe N_CHUNK values are reduced automatically from the available memory.
LOW_MEMORY = False
# Optional explicit scratch root on your data-processing filesystem.
# LOW_MEMORY_DIR = /path/to/data-proposal/cardiotensor_scratch

# Whether to save the orientation vectors (True/False)
# Use for 3D vector/fiber visualisation
WRITE_VECTORS = True

# Vector storage format: npy or zarr
# Use zarr for compressed, chunked storage of very large volumes.
VECTOR_FORMAT = zarr

# Specify the processing direction:
# - True: Process slices from the beginning (0) to the end.
# - False: Process slices from the end to the beginning.
Expand Down Expand Up @@ -107,6 +119,8 @@ OUTPUT_TYPE = 8bit
!!! note

Modify the configuration file as needed to fit your dataset.
Relative dataset, mask, and output paths are resolved from the directory
containing the configuration file.

---

Expand All @@ -127,14 +141,34 @@ OUTPUT_TYPE = 8bit
- **`SIGMA`**: Noise scale before gradient computation. Helps reduce noise while preserving structures.
- **`RHO`**: Integration scale for smoothing tensor components. Larger values yield smoother orientation fields.
- **`TRUNCATE`**: Multiple of RHO for defining the gradient filter kernel size.
- **`VERTICAL_PADDING (optional)`**: Extra padding (in voxels) to avoid edge artifacts. If not set, defaults to TRUNCATE * RHO + 0.5.
- **`VERTICAL_PADDING (optional)`**: Extra padding (in voxels) to avoid edge
artifacts. If omitted, Cardiotensor includes the support radius of both the
`SIGMA` and `RHO` Gaussian filters.
- **`N_CHUNK`**: Number of slices to process simultaneously.
- **`USE_GPU`**: Enable GPU computation (requires CuPy).
- **`WRITE_VECTORS`**: Save orientation 3rd vector as `.npy` files.
- **`LOW_MEMORY`**: Store temporary tensor outputs on disk using memory maps.
This reduces RAM use but can be slower unless the temporary directory is on
fast node-local storage. Cardiotensor treats `N_CHUNK` as an upper bound and
reduces it when the padded chunk would exceed a conservative cgroup-aware
memory estimate.
- **`LOW_MEMORY_DIR (optional)`**: Scratch root for tensor memory maps. If
omitted, CardioTensor uses `OUTPUT_PATH/.cardiotensor_scratch`; it never
falls back to the system `/tmp`. The required and available scratch
capacities are printed before calculation. Temporary task directories are
removed after normal completion. Because OOM and `SIGKILL` cannot run
cleanup code, inspect this scratch root after failed jobs and remove orphaned
`cardiotensor_tensor_*` directories.
- **`WRITE_VECTORS`**: Save the orientation vector field.
- **`VECTOR_FORMAT`**: Vector storage backend:
- `npy`: one `float32` file per Z slice in `eigen_vec/`.
- `zarr` (default): compressed, chunked and sharded storage in `eigen_vec.zarr/`.
This is recommended for very large volumes and supports safe slice-level
restart tracking.

!!! warning

Orientation vectors are saved in `float32` format and may consume significant disk space.
Orientation vectors are saved in `float32` format. Zarr uses Blosc Zstd
compression with bit-shuffle; zero-filled masked regions generally compress well.

- **`REVERSE`**: Process volume from end to start if set to `True`.

Expand Down
20 changes: 16 additions & 4 deletions examples/parameters_example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ RHO = 0.6
TRUNCATE = 4

# Padding to avoid border artifacts
# Default value is TRUNCATE * RHO + 0.5
# By default this includes the support of both Gaussian filtering stages.
# VERTICAL_PADDING = 10

# Number of slices to load into memory at a time during processing.
Expand All @@ -34,10 +34,22 @@ N_CHUNK = 20
# Enable GPU computation during the structure tensor calculation (True/False)
USE_GPU = True

# Whether to save the orientation vectors (as .npy) (True/False)
# Store temporary tensor outputs as memory-mapped files.
# By default they use OUTPUT_PATH/.cardiotensor_scratch, never system /tmp.
# N_CHUNK is automatically reduced when its conservative peak exceeds the job limit.
LOW_MEMORY = False
# Optional explicit scratch root on your data-processing filesystem.
# LOW_MEMORY_DIR = /path/to/data-proposal/cardiotensor_scratch

# Whether to save the orientation vectors (True/False)
# Use for 3D vector/fiber visualisation
WRITE_VECTORS = True

# Vector storage format: npy or zarr (default)
# Choose npy for one file per slice, or zarr for compressed, chunked storage that
# is better suited to very large volumes and slice-by-slice processing.
VECTOR_FORMAT = zarr

# Specify the processing direction:
# - True: Process slices from the beginning (0) to the end.
# - False: Process slices from the end to the beginning.
Expand All @@ -53,8 +65,8 @@ WRITE_ANGLES = True
# az_el → Azimuth / Elevation (generic vector orientation in 3D)
ANGLE_MODE = ha_ia

# Coordinates of points along the left ventricle axis.
# The first point should be coordinates of the mitral valve point in the volume ([X, Y, Z])
# Coordinates of points along the left ventricle axis.
# The first point should be coordinates of the mitral valve point in the volume ([X, Y, Z])
# The last point should be coordinates of the apex point in the volume ([X, Y, Z])
# Intermediate points will be interpolated to create a curved centre line.
AXIS_POINTS = [104,110,116], [41,87,210], [68,95,162]
Expand Down
1 change: 0 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ nav:
- advanced/index.md
- Python API: advanced/python-api.md
- Transmural Analysis: advanced/cardio-analysis.md
- Streamlines: advanced/streamlines.md
- SLURM Launcher: advanced/slurm-launcher.md
- Reference:
- reference/index.md
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# nox.options.reuse_existing_virtualenvs = True

# Define default Python versions for testing
PYTHON_VERSIONS = ["3.10", "3.11", "3.12"]
PYTHON_VERSIONS = ["3.11", "3.12", "3.13"]

# Locations to check for linting, formatting, and type checking
PACKAGE_LOCATIONS = ["src", "tests", "noxfile.py", "pyproject.toml"]
Expand Down
Loading
Loading