Skip to content
This repository was archived by the owner on Aug 27, 2025. It is now read-only.

Commit 626c3d8

Browse files
show right micron scale in the Xenium Explorer
1 parent accf61c commit 626c3d8

6 files changed

Lines changed: 64 additions & 41 deletions

File tree

docs/cli.md

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,73 +41,74 @@ $ spatialdata_xenium_explorer [OPTIONS] COMMAND [ARGS]...
4141
* `update-obs`: Update the cell categories for the Xenium...
4242
* `write`: Convert a spatialdata object to Xenium...
4343

44-
### `write`
44+
### `spatialdata_xenium_explorer add-aligned`
4545

46-
Convert a spatialdata object to Xenium Explorer's inputs
46+
After alignment on the Xenium Explorer, add an image to the SpatialData object
4747

4848
**Usage**:
4949

5050
```console
51-
$ spatialdata_xenium_explorer write [OPTIONS] SDATA_PATH
51+
$ spatialdata_xenium_explorer add-aligned [OPTIONS] SDATA_PATH IMAGE_PATH TRANSFORMATION_MATRIX_PATH
5252
```
5353

5454
**Arguments**:
5555

5656
* `SDATA_PATH`: Path to the SpatialData `.zarr` directory [required]
57+
* `IMAGE_PATH`: Path to the image file to be added (`.ome.tif` used in the explorer during alignment) [required]
58+
* `TRANSFORMATION_MATRIX_PATH`: Path to the `matrix.csv` file returned by the Explorer after alignment [required]
5759

5860
**Options**:
5961

60-
* `--output-path TEXT`: Path to a directory where Xenium Explorer's outputs will be saved. By default, writes to the same path as `sdata_path` but with the `.explorer` suffix
61-
* `--image-key TEXT`: Name of the image of interest (key of `sdata.images`). This argument doesn't need to be provided if there is only one image.
62-
* `--shapes-key TEXT`: Name of the cell shapes (key of `sdata.shapes`). This argument doesn't need to be provided if there is only one shapes key or a table with only one region.
63-
* `--points-key TEXT`: Name of the transcripts (key of `sdata.points`). This argument doesn't need to be provided if there is only one points key.
64-
* `--gene-column TEXT`: Column name of the points dataframe containing the gene names
65-
* `--layer TEXT`: Layer of `sdata.table` where the gene counts are saved. If `None`, uses `sdata.table.X`.
66-
* `--lazy / --no-lazy`: If `True`, will not load the full images in memory (except if the image memory is below `ram_threshold_gb`) [default: lazy]
67-
* `--ram-threshold-gb INTEGER`: Threshold (in gygabytes) from which image can be loaded in memory. If `None`, the image is never loaded in memory [default: 4]
68-
* `--mode TEXT`: string that indicated which files should be created. `'-ib'` means everything except images and boundaries, while `'+tocm'` means only transcripts/observations/counts/metadata (each letter corresponds to one explorer file). By default, keeps everything
62+
* `--original-image-key TEXT`: Optional original-image key (of sdata.images) on which the new image will be aligned. This doesn't need to be provided if there is only one image
63+
* `--overwrite / --no-overwrite`: Whether to overwrite the image if existing [default: no-overwrite]
6964
* `--help`: Show this message and exit.
7065

71-
### `add-aligned`
66+
### `spatialdata_xenium_explorer update-obs`
7267

73-
After alignment on the Xenium Explorer, add an image to the SpatialData object
68+
Update the cell categories for the Xenium Explorer's (i.e. what's in `adata.obs`). This is useful when you perform analysis and update your `AnnData` object
69+
70+
!!! note "Usage"
71+
This command should only be used if you updated `adata.obs`, after creation of the other explorer files.
7472

7573
**Usage**:
7674

7775
```console
78-
$ spatialdata_xenium_explorer add-aligned [OPTIONS] SDATA_PATH IMAGE_PATH TRANSFORMATION_MATRIX_PATH
76+
$ spatialdata_xenium_explorer update-obs [OPTIONS] ADATA_PATH OUTPUT_PATH
7977
```
8078

8179
**Arguments**:
8280

83-
* `SDATA_PATH`: Path to the SpatialData `.zarr` directory [required]
84-
* `IMAGE_PATH`: Path to the image file to be added (`.ome.tif` used in the explorer during alignment) [required]
85-
* `TRANSFORMATION_MATRIX_PATH`: Path to the `matrix.csv` file returned by the Explorer after alignment [required]
81+
* `ADATA_PATH`: Path to the anndata file (`zarr` or `h5ad`) containing the new observations [required]
82+
* `OUTPUT_PATH`: Path to the Xenium Explorer directory (it will update `analysis.zarr.zip`) [required]
8683

8784
**Options**:
8885

89-
* `--original-image-key TEXT`: Optional original-image key on which the new image will be aligned. This doesn't need to be provided if there is only one image
90-
* `--overwrite / --no-overwrite`: Whether to overwrite the image if existing [default: no-overwrite]
9186
* `--help`: Show this message and exit.
9287

93-
### `update-obs`
94-
95-
Update the cell categories for the Xenium Explorer's (i.e. what's in `adata.obs`). This is useful when you perform analysis and update your `AnnData` object
88+
### `spatialdata_xenium_explorer write`
9689

97-
!!! note "Usage"
98-
This command should only be used if you updated `adata.obs`, after creation of the other explorer files.
90+
Convert a spatialdata object to Xenium Explorer's inputs
9991

10092
**Usage**:
10193

10294
```console
103-
$ spatialdata_xenium_explorer update-obs [OPTIONS] ADATA_PATH OUTPUT_PATH
95+
$ spatialdata_xenium_explorer write [OPTIONS] SDATA_PATH
10496
```
10597

10698
**Arguments**:
10799

108-
* `ADATA_PATH`: Path to the anndata file (`zarr` or `h5ad`) containing the new observations [required]
109-
* `OUTPUT_PATH`: Path to the Xenium Explorer directory (it will update `analysis.zarr.zip`) [required]
100+
* `SDATA_PATH`: Path to the SpatialData `.zarr` directory [required]
110101

111102
**Options**:
112103

104+
* `--output-path TEXT`: Path to a directory where Xenium Explorer's outputs will be saved. By default, writes to the same path as `sdata_path` but with the `.explorer` suffix
105+
* `--image-key TEXT`: Name of the image of interest (key of `sdata.images`). This argument doesn't need to be provided if there is only one image.
106+
* `--shapes-key TEXT`: Name of the cell shapes (key of `sdata.shapes`). This argument doesn't need to be provided if there is only one shapes key or a table with only one region.
107+
* `--points-key TEXT`: Name of the transcripts (key of `sdata.points`). This argument doesn't need to be provided if there is only one points key.
108+
* `--gene-column TEXT`: Column name of the points dataframe containing the gene names
109+
* `--pixelsize FLOAT`: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer. [default: 0.2125]
110+
* `--layer TEXT`: Layer of `sdata.table` where the gene counts are saved. If `None`, uses `sdata.table.X`.
111+
* `--lazy / --no-lazy`: If `True`, will not load the full images in memory (except if the image memory is below `ram_threshold_gb`) [default: lazy]
112+
* `--ram-threshold-gb INTEGER`: Threshold (in gygabytes) from which image can be loaded in memory. If `None`, the image is never loaded in memory [default: 4]
113+
* `--mode TEXT`: string that indicated which files should be created. `'-ib'` means everything except images and boundaries, while `'+tocm'` means only transcripts/observations/counts/metadata (each letter corresponds to one explorer file). By default, keeps everything
113114
* `--help`: Show this message and exit.

spatialdata_xenium_explorer/_constants.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ExplorerConstants:
1111
GRID_SIZE = 250
1212
QUALITY_SCORE = 40
1313
MICRONS_TO_PIXELS = 4.705882
14+
PIXELS_TO_MICRONS = 0.2125
1415

1516
COLORS = ["white", 400, 500, 600, 700]
1617
NUCLEUS_COLOR = "white"
@@ -71,7 +72,7 @@ def group_attrs() -> dict:
7172
}
7273

7374

74-
def experiment_dict(run_name: str, region_name: str, num_cells: int) -> dict:
75+
def experiment_dict(run_name: str, region_name: str, num_cells: int, pixelsize: float) -> dict:
7576
return {
7677
"major_version": Versions.EXPERIMENT[0],
7778
"minor_version": Versions.EXPERIMENT[1],
@@ -91,7 +92,7 @@ def experiment_dict(run_name: str, region_name: str, num_cells: int) -> dict:
9192
"panel_organism": "Human",
9293
"panel_num_targets_predesigned": 0,
9394
"panel_num_targets_custom": 0,
94-
"pixel_size": 0.2125,
95+
"pixel_size": pixelsize,
9596
"instrument_sn": "N/A",
9697
"instrument_sw_version": "N/A",
9798
"analysis_sw_version": "xenium-1.3.0.5",

spatialdata_xenium_explorer/cli/app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def write(
2727
gene_column: str = typer.Option(
2828
None, help="Column name of the points dataframe containing the gene names"
2929
),
30+
pixelsize: float = typer.Option(
31+
0.2125,
32+
help="Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.",
33+
),
3034
layer: str = typer.Option(
3135
None,
3236
help="Layer of `sdata.table` where the gene counts are saved. If `None`, uses `sdata.table.X`.",
@@ -63,6 +67,7 @@ def write(
6367
shapes_key=shapes_key,
6468
points_key=points_key,
6569
gene_column=gene_column,
70+
pixelsize=pixelsize,
6671
layer=layer,
6772
lazy=lazy,
6873
ram_threshold_gb=ram_threshold_gb,

spatialdata_xenium_explorer/converter.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def write(
2727
shapes_key: str | None = None,
2828
points_key: str | None = None,
2929
gene_column: str | None = None,
30+
pixelsize: float = 0.2125,
3031
layer: str | None = None,
3132
polygon_max_vertices: int = 13,
3233
lazy: bool = True,
@@ -62,6 +63,7 @@ def write(
6263
shapes_key: Name of the cell shapes (key of `sdata.shapes`). This argument doesn't need to be provided if there is only one shapes key or a table with only one region.
6364
points_key: Name of the transcripts (key of `sdata.points`). This argument doesn't need to be provided if there is only one points key.
6465
gene_column: Column name of the points dataframe containing the gene names.
66+
pixelsize: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
6567
layer: Layer of `sdata.table` where the gene counts are saved. If `None`, uses `sdata.table.X`.
6668
polygon_max_vertices: Maximum number of vertices for the cell polygons. A higher value will display smoother cells.
6769
lazy: If `True`, will not load the full images in memory (except if the image memory is below `ram_threshold_gb`).
@@ -100,25 +102,25 @@ def write(
100102
if sdata.table is not None:
101103
geo_df = geo_df.loc[adata.obs[adata.uns["spatialdata_attrs"]["instance_key"]]]
102104

103-
write_polygons(path, geo_df.geometry, polygon_max_vertices)
105+
write_polygons(path, geo_df.geometry, polygon_max_vertices, pixelsize=pixelsize)
104106

105107
### Saving transcripts
106108
df = get_element(sdata, "points", points_key)
107109

108110
if _should_save(mode, "t") and df is not None:
109111
if gene_column is not None:
110112
df = to_intrinsic(sdata, df, image_key)
111-
write_transcripts(path, df, gene_column)
113+
write_transcripts(path, df, gene_column, pixelsize=pixelsize)
112114
else:
113115
log.warn("The argument 'gene_column' has to be provided to save the transcripts")
114116

115117
### Saving image
116118
if _should_save(mode, "i"):
117-
write_image(path, image, lazy=lazy, ram_threshold_gb=ram_threshold_gb)
119+
write_image(path, image, lazy=lazy, ram_threshold_gb=ram_threshold_gb, pixelsize=pixelsize)
118120

119121
### Saving experiment.xenium file
120122
if _should_save(mode, "m"):
121-
write_metadata(path, image_key, shapes_key, _get_n_obs(sdata, geo_df))
123+
write_metadata(path, image_key, shapes_key, _get_n_obs(sdata, geo_df), pixelsize)
122124

123125
log.info(f"Saved files in the following directory: {path}")
124126
log.info(f"You can open the experiment with 'open {path / FileNames.METADATA}'")
@@ -150,7 +152,12 @@ def _get_n_obs(sdata: SpatialData, geo_df: gpd.GeoDataFrame) -> int:
150152

151153

152154
def write_metadata(
153-
path: str, image_key: str = "NA", shapes_key: str = "NA", n_obs: int = 0, is_dir: bool = True
155+
path: str,
156+
image_key: str = "NA",
157+
shapes_key: str = "NA",
158+
n_obs: int = 0,
159+
is_dir: bool = True,
160+
pixelsize: float = 0.2125,
154161
):
155162
"""Create an `experiment.xenium` file that can be open by the Xenium Explorer.
156163
@@ -163,9 +170,10 @@ def write_metadata(
163170
shapes_key: Key of `SpatialData` object containing the boundaries shown on the explorer.
164171
n_obs: Number of cells
165172
is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
173+
pixelsize: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
166174
"""
167175
path = explorer_file_path(path, FileNames.METADATA, is_dir)
168176

169177
with open(path, "w") as f:
170-
metadata = experiment_dict(image_key, shapes_key, n_obs)
178+
metadata = experiment_dict(image_key, shapes_key, n_obs, pixelsize)
171179
json.dump(metadata, f, indent=4)

spatialdata_xenium_explorer/core/points.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def write_transcripts(
2323
gene: str = "gene",
2424
max_levels: int = 15,
2525
is_dir: bool = True,
26+
pixelsize: float = 0.2125,
2627
):
2728
"""Write a `transcripts.zarr.zip` file containing pyramidal transcript locations
2829
@@ -32,17 +33,19 @@ def write_transcripts(
3233
gene: Column of `df` containing the genes names.
3334
max_levels: Maximum number of levels in the pyramid.
3435
is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
36+
pixelsize: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
3537
"""
3638
path = explorer_file_path(path, FileNames.POINTS, is_dir)
3739

3840
# TODO: make everything using dask instead of pandas
3941
df = df.compute()
4042

4143
num_transcripts = len(df)
44+
grid_size = ExplorerConstants.GRID_SIZE / ExplorerConstants.PIXELS_TO_MICRONS * pixelsize
4245
df[gene] = df[gene].astype("category")
4346

4447
location = df[["x", "y"]]
45-
location /= ExplorerConstants.MICRONS_TO_PIXELS
48+
location *= pixelsize
4649
location = np.concatenate([location, np.zeros((num_transcripts, 1))], axis=1)
4750

4851
if location.min() < 0:
@@ -84,7 +87,7 @@ def write_transcripts(
8487
GRIDS_ATTRS = {
8588
"grid_key_names": ["grid_x_loc", "grid_y_loc"],
8689
"grid_zip": False,
87-
"grid_size": [ExplorerConstants.GRID_SIZE],
90+
"grid_size": [grid_size],
8891
"grid_array_shapes": [],
8992
"grid_number_objects": [],
9093
"grid_keys": [],
@@ -100,7 +103,7 @@ def write_transcripts(
100103
log.info(f" > Level {level}: {len(location)} transcripts")
101104
level_group = grids.create_group(level)
102105

103-
tile_size = ExplorerConstants.GRID_SIZE * 2**level
106+
tile_size = grid_size * 2**level
104107

105108
indices = np.floor(location[:, :2] / tile_size).clip(0).astype(int)
106109
tiles_str_indices = np.array([f"{tx},{ty}" for (tx, ty) in indices])

spatialdata_xenium_explorer/core/shapes.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ def pad_polygon(
4545

4646

4747
def write_polygons(
48-
path: Path, polygons: Iterable[Polygon], max_vertices: int, is_dir: bool = True
48+
path: Path,
49+
polygons: Iterable[Polygon],
50+
max_vertices: int,
51+
is_dir: bool = True,
52+
pixelsize: float = 0.2125,
4953
) -> None:
5054
"""Write a `cells.zarr.zip` file containing the cell polygonal boundaries
5155
@@ -54,12 +58,13 @@ def write_polygons(
5458
polygons: A list of `shapely` polygons to be written
5559
max_vertices: The number of vertices per polygon (they will be transformed to have the right number of vertices)
5660
is_dir: If `False`, then `path` is a path to a single file, not to the Xenium Explorer directory.
61+
pixelsize: Number of microns in a pixel. Invalid value can lead to inconsistent scales in the Explorer.
5762
"""
5863
path = explorer_file_path(path, FileNames.SHAPES, is_dir)
5964

6065
log.info(f"Writing {len(polygons)} cell polygons")
6166
coordinates = np.stack([pad_polygon(p, max_vertices) for p in polygons])
62-
coordinates /= ExplorerConstants.MICRONS_TO_PIXELS
67+
coordinates *= pixelsize
6368

6469
num_cells = len(coordinates)
6570
cells_fourth = ceil(num_cells / 4)

0 commit comments

Comments
 (0)