Skip to content

Commit a255373

Browse files
committed
ENH: RFC 4 implementation
1 parent 326a6c7 commit a255373

File tree

11 files changed

+723
-11
lines changed

11 files changed

+723
-11
lines changed

docs/rfc4.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# RFC 4: Anatomical Orientation Support
2+
3+
RFC 4 adds support for anatomical orientation metadata to OME-NGFF axes,
4+
enabling precise description of spatial axis directions in biological and
5+
medical imaging data.
6+
7+
## Overview
8+
9+
Anatomical orientation refers to the specific arrangement and directional
10+
alignment of anatomical structures within an imaging dataset. This is crucial
11+
for ensuring accurate alignment and comparison of images to anatomical atlases,
12+
facilitating consistent analysis and interpretation of biological data.
13+
14+
## Usage
15+
16+
### Programmatic Usage
17+
18+
#### Enabling RFC 4
19+
20+
To enable RFC 4 support when writing OME-NGFF Zarr data, include `4` in the
21+
`enabled_rfcs` parameter:
22+
23+
```python
24+
import ngff_zarr
25+
26+
# Enable RFC 4 when converting to NGFF Zarr
27+
ngff_zarr.to_ngff_zarr(
28+
store="output.ome.zarr",
29+
multiscales=multiscales,
30+
enabled_rfcs=[4] # Enable RFC 4
31+
)
32+
```
33+
34+
### CLI Usage
35+
36+
RFC 4 can be enabled via the command line using the `--enable-rfc` flag:
37+
38+
```bash
39+
# Convert a medical image with RFC 4 anatomical orientation enabled
40+
ngff-zarr -i image.nii.gz -o output.ome.zarr --enable-rfc 4
41+
42+
# Enable multiple RFCs (when available)
43+
ngff-zarr -i image.nii.gz -o output.ome.zarr --enable-rfc 4 --enable-rfc 5
44+
45+
# Without RFC 4 (default behavior - orientation metadata filtered out)
46+
ngff-zarr -i image.nii.gz -o output.ome.zarr
47+
```
48+
49+
### ITK Integration
50+
51+
When converting ITK images (including from NRRD, NIfTI, or DICOM formats),
52+
anatomical orientation is automatically added based on ITK's LPS coordinate
53+
system:
54+
55+
```python
56+
import ngff_zarr
57+
from itk import imread
58+
59+
# Read a medical image (NRRD, NIfTI, DICOM, etc.)
60+
image = imread("image.nrrd")
61+
62+
# Convert to NGFF image with anatomical orientation
63+
ngff_image = ngff_zarr.itk_image_to_ngff_image(
64+
image,
65+
add_anatomical_orientation=True
66+
)
67+
68+
# Convert to multiscales and write to Zarr with RFC 4 enabled
69+
multiscales = ngff_zarr.to_multiscales(ngff_image)
70+
ngff_zarr.to_ngff_zarr(
71+
store="output.ome.zarr",
72+
multiscales=multiscales,
73+
enabled_rfcs=[4]
74+
)
75+
```
76+
77+
### ITK-Wasm Integration
78+
79+
Similar support is available for ITK-Wasm:
80+
81+
```python
82+
import ngff_zarr
83+
from itkwasm_image_io import imread
84+
85+
# Read with ITK-Wasm
86+
image = imread("image.nii.gz")
87+
88+
# Convert with anatomical orientation
89+
ngff_image = ngff_zarr.itk_image_to_ngff_image(
90+
image,
91+
add_anatomical_orientation=True
92+
)
93+
```
94+
95+
## Anatomical Orientation Values
96+
97+
RFC 4 supports the following anatomical orientation values:
98+
99+
- `left-to-right`: From left side to right lateral side
100+
- `right-to-left`: From right side to left lateral side
101+
- `anterior-to-posterior`: From front (anterior) to back (posterior)
102+
- `posterior-to-anterior`: From back (posterior) to front (anterior)
103+
- `inferior-to-superior`: From below (inferior) to above (superior)
104+
- `superior-to-inferior`: From above (superior) to below (inferior)
105+
- `dorsal-to-ventral`: From top/upper (dorsal) to belly/lower (ventral)
106+
- `ventral-to-dorsal`: From belly/lower (ventral) to top/upper (dorsal)
107+
- `dorsal-to-palmar`: From top/upper (dorsal) to palm of hand (palmar)
108+
- `palmar-to-dorsal`: From palm of hand (palmar) to top/upper (dorsal)
109+
- `dorsal-to-plantar`: From top/upper (dorsal) to sole of foot (plantar)
110+
- `plantar-to-dorsal`: From sole of foot (plantar) to top/upper (dorsal)
111+
112+
## ITK LPS Coordinate System
113+
114+
ITK uses the LPS (Left-to-right, Posterior-to-anterior, Superior-to-inferior)
115+
coordinate system by default. When converting ITK images, the following mappings
116+
are applied:
117+
118+
- **X axis**: `left-to-right`
119+
- **Y axis**: `posterior-to-anterior`
120+
- **Z axis**: `inferior-to-superior`
121+
122+
## Manual Orientation Specification
123+
124+
You can also manually specify orientations:
125+
126+
```python
127+
from ngff_zarr import (
128+
AnatomicalOrientation,
129+
AnatomicalOrientationValues,
130+
NgffImage
131+
)
132+
import dask.array as da
133+
134+
# Create orientation objects
135+
x_orientation = AnatomicalOrientation(
136+
value=AnatomicalOrientationValues.left_to_right
137+
)
138+
y_orientation = AnatomicalOrientation(
139+
value=AnatomicalOrientationValues.anterior_to_posterior
140+
)
141+
z_orientation = AnatomicalOrientation(
142+
value=AnatomicalOrientationValues.inferior_to_superior
143+
)
144+
145+
# Create NGFF image with orientations
146+
data = da.zeros((100, 100, 100))
147+
ngff_image = NgffImage(
148+
data=data,
149+
dims=("z", "y", "x"),
150+
scale={"x": 1.0, "y": 1.0, "z": 1.0},
151+
translation={"x": 0.0, "y": 0.0, "z": 0.0},
152+
axes_orientations={
153+
"x": x_orientation,
154+
"y": y_orientation,
155+
"z": z_orientation
156+
}
157+
)
158+
```
159+
160+
## Metadata Format
161+
162+
When RFC 4 is enabled, spatial axes in the OME-NGFF metadata include an
163+
`orientation` field:
164+
165+
```json
166+
{
167+
"axes": [
168+
{
169+
"name": "z",
170+
"type": "space",
171+
"unit": "micrometer",
172+
"orientation": {
173+
"type": "anatomical",
174+
"value": "inferior-to-superior"
175+
}
176+
},
177+
{
178+
"name": "y",
179+
"type": "space",
180+
"unit": "micrometer",
181+
"orientation": {
182+
"type": "anatomical",
183+
"value": "posterior-to-anterior"
184+
}
185+
},
186+
{
187+
"name": "x",
188+
"type": "space",
189+
"unit": "micrometer",
190+
"orientation": {
191+
"type": "anatomical",
192+
"value": "left-to-right"
193+
}
194+
}
195+
]
196+
}
197+
```
198+
199+
## RFC 4 Functions
200+
201+
### Core Functions
202+
203+
- `itk_lps_to_anatomical_orientation(axis_name)`: Map ITK LPS axes to
204+
orientations
205+
- `add_anatomical_orientation_to_axis(axis_dict, orientation)`: Add orientation
206+
to axis
207+
- `remove_anatomical_orientation_from_axis(axis_dict)`: Remove orientation from
208+
axis
209+
210+
### Data Classes
211+
212+
- `AnatomicalOrientation`: Orientation specification with type and value
213+
- `AnatomicalOrientationValues`: Enum of supported orientation values
214+
215+
## Compatibility
216+
217+
When RFC 4 is not enabled (the default), orientation metadata is automatically
218+
filtered out during Zarr writing to maintain compatibility with standard
219+
OME-NGFF consumers.
220+
221+
## Best Practices
222+
223+
1. **Enable RFC 4** when working with medical/biological images where
224+
orientation matters
225+
2. **Use ITK integration** for automatic LPS-based orientation when converting
226+
from medical image formats
227+
3. **Document orientation assumptions** in your analysis pipelines when working
228+
with oriented data
229+
4. **Validate orientations** match your expectations, especially when combining
230+
data from different sources

py/ngff_zarr/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,21 @@
3636
OmeroChannel,
3737
OmeroWindow,
3838
)
39+
from .rfc4 import (
40+
AnatomicalOrientation,
41+
AnatomicalOrientationValues,
42+
itk_lps_to_anatomical_orientation,
43+
is_rfc4_enabled,
44+
add_anatomical_orientation_to_axis,
45+
remove_anatomical_orientation_from_axis,
46+
)
3947

4048
__all__ = [
4149
"__version__",
4250
"config",
4351
"NgffImage",
4452
"Multiscales",
4553
"to_ngff_image",
46-
"from_ngff_image",
4754
"itk_image_to_ngff_image",
4855
"ngff_image_to_itk_image",
4956
"memory_usage",
@@ -73,4 +80,11 @@
7380
"Omero",
7481
"OmeroChannel",
7582
"OmeroWindow",
83+
# RFC 4 - Anatomical Orientation
84+
"AnatomicalOrientation",
85+
"AnatomicalOrientationValues",
86+
"itk_lps_to_anatomical_orientation",
87+
"is_rfc4_enabled",
88+
"add_anatomical_orientation_to_axis",
89+
"remove_anatomical_orientation_from_axis",
7690
]

py/ngff_zarr/cli.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def _multiscales_to_ngff_zarr(
7878
progress=rich_dask_progress,
7979
use_tensorstore=args.use_tensorstore,
8080
version=args.ome_zarr_version,
81+
enabled_rfcs=args.enable_rfc,
8182
)
8283

8384

@@ -214,6 +215,13 @@ def main():
214215
default="0.4",
215216
choices=["0.4", "0.5"],
216217
)
218+
metadata_group.add_argument(
219+
"--enable-rfc",
220+
action="append",
221+
type=int,
222+
help="Enable specific RFC features. Can be used multiple times. Currently supported: 4 (anatomical orientation)",
223+
metavar="RFC_NUMBER",
224+
)
217225

218226
processing_group = parser.add_argument_group("processing", "Processing options")
219227
processing_group.add_argument(

py/ngff_zarr/itk_image_to_ngff_image.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import dask.array
44

55
from .ngff_image import NgffImage
6+
from .rfc4 import itk_lps_to_anatomical_orientation
67

78

89
def itk_image_to_ngff_image(
910
itk_image,
11+
add_anatomical_orientation: bool = True,
1012
# anatomical_axes: bool = False,
1113
# axis_names: List[str] = None,
1214
# axis_units: List[str] = None,
@@ -72,4 +74,15 @@ def itk_image_to_ngff_image(
7274
origin = image_dict["origin"]
7375
translation = {dim: origin[::-1][idx] for idx, dim in enumerate(spatial_dims)}
7476

75-
return NgffImage(data, dims, scale, translation)
77+
# Add anatomical orientation if requested
78+
axes_orientations = None
79+
if add_anatomical_orientation:
80+
axes_orientations = {}
81+
for dim in spatial_dims:
82+
orientation = itk_lps_to_anatomical_orientation(dim)
83+
if orientation is not None:
84+
axes_orientations[dim] = orientation
85+
86+
return NgffImage(
87+
data, dims, scale, translation, axes_orientations=axes_orientations
88+
)

py/ngff_zarr/ngff_image.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from dask.array.core import Array as DaskArray
55

66
from .v04.zarr_metadata import Units
7+
from .rfc4 import AnatomicalOrientation
78

89
ComputedCallback = Callable[[], None]
910

@@ -16,4 +17,5 @@ class NgffImage:
1617
translation: Dict[str, float]
1718
name: str = "image"
1819
axes_units: Optional[Mapping[str, Units]] = None
20+
axes_orientations: Optional[Mapping[str, AnatomicalOrientation]] = None
1921
computed_callbacks: List[ComputedCallback] = field(default_factory=list)

0 commit comments

Comments
 (0)