There are 2 registration workflows:
- Registration on the whole slide
- Registration by core
TO DO:
- Add support for Xenium Output V4
- Xenium-CODEX/slide_registration/
- xenium_codex_slide_registration.py
- registered_core_splitter.py
The registration is performed on the whole slide, eventually areas can be cropped with the registered_core_splitter.py script.
If the slide has a TMA or more than one tissue block, one tends to get better results with the TMA workflow.
This script aligns a CODEX image with a Xenium spatial transcriptomics dataset and saves the result as a SpatialData object,
with the registered CODEX image and filtered Xenium transcript table.
To DO: The script needs the CODEX image and Xenium morphology focus image to be in zarr format. It should be updated to work with the original tiffs.
python3 XeniumCodexRegistrator.py \
<xenium_folder> \
<xenium_zarr> \
<codex_zarr> \
<codex_channels.txt> \
<output_path>| Argument | Description |
|---|---|
xenium_folder |
Path to the Xenium experiment folder (from 10x Genomics) |
xenium_zarr |
Path to the Xenium morphology focus DAPI image (OME-Zarr format) |
codex_zarr |
Path to the CODEX image (OME-Zarr format) |
codex_channels.txt |
Text file listing CODEX channel names, one per line |
output_path |
Output path where the SpatialData Zarr will be saved |
-
Image Loading CODEX and Xenium DAPI images are loaded and processed using
dask.array. -
Downscaling Both images are downscaled to fit into memory for SIFT landmark detection (8X).
-
Image Registration
- SIFT keypoints are computed with OpenCV.
- Keypoint matches are filtered using Lowe’s ratio test (0.4).
- An affine transformation is estimated from matched points and rescaled up (8X).
-
SpatialData Integration
- The CODEX image is added to the Xenium
SpatialDataobject. - An affine transformation aligning CODEX to Xenium is added.
- The CODEX image is added to the Xenium
-
Transcript Table Cleanup The following probes/codewords are removed:
- "deprecated_codeword"
- "negative_control_probe"
- "genomic_control_probe"
- "negative_control_codeword"
- "unassigned_codeword"
The following columns are removed:
- "z"
- "fov_name"
- "nucleus_distance"
- "codeword_index"
- "codeword_category"
- "is_gene"
- "transcript_id"
- "overlaps_nucleus"
- Output
The resulting
SpatialDataobject is saved to the specifiedoutput_path.
The output is a single SpatialData object, with associated Zarr store:
├── Images
│ ├── 'codex': DataTree[cyx] (multiple resolutions)
│ └── 'morphology_focus': DataTree[cyx] (multiple resolutions)
├── Labels
│ └── 'cell_labels': DataTree[yx] (multiple resolutions)
├── Points
│ └── 'transcripts': DataFrame with shape: (<Delayed>, 5) (2D points)
├── Shapes
│ └── 'cell_boundaries': GeoDataFrame shape: (N cells, 1) (2D shapes)
└── Tables
└── 'table': AnnData (N cells, N features)
with coordinate systems:
▸ 'codex', with elements:
codex (Images)
▸ 'global', with elements:
codex (Images), morphology_focus (Images), cell_labels (Labels), transcripts (Points), cell_boundaries (Shapes)
The object contains two coordinate spaces:
- "codex": original codex coordinates (you can ignore this)
- "global": Xenium + aligned codex data (this should be used for visualization)
This script crops an aligned Xenium SpatialData object generated with the previous script,
into multiple subregions based on bounding boxes provided in a CSV file. Each cropped region is written as an individual .zarr dataset using the same structure as the original input.
python3 RegisteredCoreSplitter.py \
<input_folder> \
<bounding_boxes> \
<output_folder>| Argument | Description |
|---|---|
input_folder |
Path to the input SpatialData Zarr folder |
bounding_boxes |
Path to a CSV file with bounding box definitions |
output_folder |
Path to the output folder where cropped .zarr files will be written |
The CSV file should have five columns with the following format:
core_id,x_min,x_max,y_min,y_max
1,5000,15000,7000,17000
2,16000,26000,9000,19000
- Pixel coordinates are assumed to be in micrometers and to apply to the Xenium morphology focus DAPI image and will be converted to pixel units (1 px = 212 nm).
- Each row defines one rectangular crop region.
-
Load Data
Read the fullSpatialDataobject from the specifiedinput_folder. -
Read Bounding Boxes
Load the CSV file and convert coordinates from micrometers to pixels. -
Crop and Save
For each bounding box:- Crop the
SpatialDatausingbounding_box_query. - Rechunk the image and label data for efficient storage.
- Save the cropped region to a
.zarrfile namedcore-<core_id>.zarrin theoutput_folder.
- Crop the
Each cropped dataset will be saved in the output_folder as:
core-<core_id>.zarr
These outputs retain the same structure as the original SpatialData object.
The script is at:
- Xenium-CODEXTMA_core_registration/xenium_codex_core_registration.py
Usage of xenium_codex_core_registration.py:
python3 xenium_codex_core_registration.py \
<xenium_folder> \
<codex_tiff> \
<codex_channels.txt> \
<bounding_boxes> \
<output_path>
-
Xenium data loading Load the transcripts, cell mask, feature matrix and cell boundaries. Nuclei are skipped.
-
Transcript Table Cleanup The following probes/codewords are removed:
- "deprecated_codeword"
- "negative_control_probe"
- "genomic_control_probe"
- "negative_control_codeword"
- "unassigned_codeword"
-
Image Loading CODEX and Xenium DAPI images are loaded and processed using
dask.array. -
Downscaling Both images are downscaled to fit into memory for SIFT landmark detection (8X).
-
Image Registration First coarse image registration on the whole slide:
- SIFT keypoints are computed with OpenCV.
- Keypoint matches are filtered using Lowe’s ratio test (0.4).
- An affine transformation is estimated from matched points and rescaled up (8X).
-
Cropping and fine registration The images and xenium data are cropped, then both images are downscaled to fit into memory for SIFT landmark detection (2X).
- SIFT keypoints are computed with OpenCV.
- Keypoint matches are filtered using Lowe’s ratio test (0.5).
- An affine transformation is estimated from matched points and rescaled up (2X).
-
SpatialData Integration The CODEX and Xenium images are added to the Xenium
SpatialDataobject. -
Output The resulting
SpatialDataobject is saved to the specifiedoutput_path.
The output is a single SpatialData object for each core, with its associated Zarr store:
├── Images
│ ├── 'codex': DataTree[cyx] (multiple resolutions)
│ └── 'morphology_focus': DataTree[cyx] (multiple resolutions)
├── Labels
│ └── 'cell_labels': DataTree[yx] (multiple resolutions)
├── Points
│ └── 'transcripts': DataFrame with shape: (<Delayed>, 5) (2D points)
├── Shapes
│ └── 'cell_boundaries': GeoDataFrame shape: (N cells, 1) (2D shapes)
└── Tables
└── 'table': AnnData (N cells, N features)
with coordinate systems:
▸ 'global', with elements:
codex (Images), morphology_focus (Images), cell_labels (Labels), transcripts (Points), cell_boundaries (Shapes)