You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add support for Space Ranger cell segmentation data #1057
is there any improvement about this ,thank a lot, does squidpy support cell segmentaion, because spatialdata is not compatible with some downstream software, @selmanozleyen
Hey @asmlgkj, we're in the processing of phasing out the support for directly reading spatial formats into AnnData and then operating on them, f.e. your Visium HD data.
We recommend you use the Visium HD reader from spatialdata-io to convert your raw data (including segmentations) into a SpatialData object, then you can use spatialdata-plot to visualize your data.
spatialdata is not compatible with some downstream software
What parts of it are incompatible? It is unlikely that we'll continue to support the other plotting case. SpatialData objects contain AnnData tables that you can apply most normal processing workflows on directly.
@timtreis Thank you so much for your response and all the incredible work you've put into developing these tools! I really appreciate the time and effort that has gone into building such a powerful framework.
To clarify what I meant by "incompatibility" - I was primarily referring to the visualization workflow. Currently, many users (including myself) are accustomed to using functions like sc.pl.spatial() directly from scanpy for spatial visualization. With the transition to SpatialData objects, while I understand the AnnData tables are still accessible, the direct integration with scanpy's plotting functions becomes less straightforward.
I completely understand that SpatialData represents a more robust and future-oriented architecture, and that we can extract the AnnData tables for downstream analysis. My concern is more about the practical workflow - right now, a significant portion of the Python spatial transcriptomics community still primarily works within the scanpy ecosystem, and the direct compatibility with sc.pl.* functions is something many people rely on.
That said, I recognize this is part of the evolution toward better data structures, and I'm willing to adapt my workflows. I just wanted to provide this feedback about where current users might encounter friction during the transition.
Thanks again for your continued development and for considering user feedback!
Hey @asmlgkj, thank you for your perspective! Yeah, fully agree that people are very much used to the pl.spatial() or pl.spatial_scatter() syntax. We're currently implementing functions over at spatialdata-plot (such as scverse/spatialdata-plot#474) which will enable us to swap what happens under the hood in pl.spatial_scatter() for a call to spatialdata-plot.
Currently, both squidpy and spatialdata-plot contain related but separate plotting code, which is not good for maintainability. In fact, spatialdata-plot was originally mostly based on the functions from squidpy but has since then evolved (and diverged) quite a bit.
Our medium-term vision is to fully remove all plotting code from squidpy and purely delegate to spatialdata-plot. In this process, the function signatures and behaviour of squidpy will ideally be untouched. Since this change is upcoming, we're no longer fixing/improving the plotting code in Squidpy.
However, this also comes with the caveat that we're gradually decreasing support for AnnData in Squidpy in favor of SpatialData and will remove the ImageContainer entirely soon. There will be a converter but sooner or later users will either have to adapt to the new, more powerful, SpatialData based ecosystem or work with older versions of Squidpy.
@timtreis Thank you so much for the detailed explanation! This really helps me understand the vision and roadmap for the plotting ecosystem. The plan to unify the plotting code through spatialdata-plot while maintaining the familiar squidpy function signatures sounds like a great approach for both maintainability and user experience.
I really appreciate all the work that's going into building this more robust and unified framework. I'm definitely interested in transitioning to the SpatialData-based ecosystem - it clearly offers more power and flexibility for spatial data analysis.
I have a quick question, and I hope this doesn't come across as rushing you at all - I'm just trying to plan my analysis pipelines accordingly. Do you have a rough timeline for when the full support for high-resolution platforms like Visium HD and Stereo-seq will be available in the SpatialData ecosystem? I'm asking purely for planning purposes so I can decide whether to adapt my current workflows now or wait for the transition to be more complete.
Again, thank you for all your efforts and for being so responsive to the community. The work you're doing is really valuable!
Do you have a rough timeline for when the full support for high-resolution platforms like Visium HD and Stereo-seq will be available in the SpatialData ecosystem
@timtreis Thank you for the clarification! I can see that loading and visualization are already supported, which is great.
I think I need to clarify my understanding, as there might be some confusion on my end about how SpatialData handles spatial coordinates. I have a few questions:
Stereo-seq with cell segmentation: It seems that the current sdata-io.stereoseq reader might not support loading cell segmentation data (i.e., segmented cell polygons). Is that correct, or am I missing something?
Coordinate representation: This is actually my main point of confusion that's been blocking me from fully adopting SpatialData. When working in bin/spot mode (not cell segmentation), each spatial unit traditionally has a single XY coordinate point. However, from what I understand, SpatialData seems to represent spatial features as polygons with multiple coordinate points.
My concern is: after loading data with SpatialData, does each spot/bin still have a unique single XY coordinate position, or does it maintain polygon coordinates? Many downstream analyses I work with expect each cell/spot ID to have a single corresponding XY coordinate pair, not a polygon. This perceived incompatibility has been my main hesitation in switching to SpatialData. Am I misunderstanding how the coordinate system works?
Stereo-seq image support: Stereo-seq data also comes with ssDNA or H&E staining images as background. Does the current sdata-io.stereoseq reader support loading these images?
I really appreciate your patience in helping me understand these details. Thank you again for all your work on these tools!
Stereo-seq with cell segmentation: It seems that the current sdata-io.stereoseq reader might not support loading cell segmentation data (i.e., segmented cell polygons). Is that correct, or am I missing something?
I'm personally not working with StereoSeq, so I'm just going by the codebase here. From the reader it seems we're not yet reading in the segmentation, that's true. While we are in constant contact with other companies regarding updates to their readers, that isn't really the case for STOmics. Therefore it's easy for us to miss such updates to their pipeline. It shouldn't be very difficult to add, though. If you could provide us with example output of a pipeline that includes these segmentations, we can adjust the reader.
However, from what I understand, SpatialData seems to represent spatial features as polygons with multiple coordinate points.
Within SpatialData, cells can either be represented as polygons (in the Shapes layer) or as pixel-mask (in the Labels layer). However, on both of those you can very easily calcualte their centroids to end up with one (x/y) pair per cell, for example using this method: https://spatialdata.scverse.org/en/stable/api/operations.html#spatialdata.get_centroids
Stereo-seq image support: Stereo-seq data also comes with ssDNA or H&E staining images as background. Does the current sdata-io.stereoseq reader support loading these images?
Based on the code, the reader should automatically read these files, yes. However, there might've been changes to their pipeline since we last wrote that reader. However, we can also adjust that of course. In the meantime, here is a tutorial on how you can also easily create SpatialData objects from whichever images, polygons etc you actually have on disk
@timtreis Thank you so much for the detailed explanation!
I have two follow-up questions regarding cell segmentation support:
Visium HD Cell Segmentation (SpaceRanger v4)
For Visium HD, I'm trying to load the cell segmentation data from SpaceRanger v4 outputs. As you mentioned, cells can be represented as polygons in the Shapes layer or as pixel-masks in the Labels layer. However, I'm not sure how to achieve this with the current spatialdata_io.visium_hd reader - apart from first converting to Zarr format.
SpaceRanger v4 (released June 2025) now automatically outputs cell-level segmentation alongside the traditional binned_outputs (2/8/16 µm). The output structure looks something like:
outs/
├── binned_outputs/
│ ├── square_002um/
│ ├── square_008um/
│ └── square_016um/
└── cell_outputs/ # <-- new in v4, contains segmented cell data
├── filtered_feature_bc_matrix.h5
├── spatial/
└── ...
Demo data: 10x provides a Human CRC dataset processed with SpaceRanger v4:
Could you provide example code showing how to load these cell segmentation outputs into SpatialData with proper Shapes (cell polygons) and Labels layers using the method you mentioned? The current bin_size parameter only seems to work for the traditional square bins.
2. Stereo-seq Cellbin.gef
For Stereo-seq, I'd like to load cellbin.gef files (cell-segmented data). Here's an example dataset:
Currently I can read this with stereopy:
pythonimport stereo as st
data = st.io.read_gef(file_path='C03937C4.cellbin.gef', bin_type='cell_bins')
But I'm not sure how to convert this to a proper SpatialData structure that includes:
Cell polygons/boundaries in Shapes layer
Expression matrix linked to cells
Background image if available
The tutorial you linked shows how to manually create SpatialData objects, but I'm struggling to achieve a structure similar to what spatialdata_io.visium_hd produces for bin data - where shapes and tables are properly linked with coordinate systems.
Could you provide a code example for converting Stereo-seq cellbin data to SpatialData format with cell boundaries?
Thank you again for your help! I really appreciate the work on spatialdata-io.
@timtreis Thanks a lot
For Visium HD segmentation-only loading with spatialdata_io.visium_hd:
For cell positions, is it preferred to use nucleus segmentations (nucleus_segmentations.geojson) as centroid coordinates instead of cell membrane segmentations?
Should path point to outs/ or outs/segmented_outputs/ when only segmentation outputs are present?
Which counts file should be used with segmentations: filtered_feature_cell_matrix.h5 under segmented_outputs, or another file under outs/?
Would appreciate guidance on the recommended inputs/settings for Visium HD segmentation-only usage.
I load Visium HD segmentation data via spatialdata_io.visium_hd (cell/nucleus), but the resulting AnnData for Scanpy doesn’t include .uns["spatial"] with scalefactors. When I convert to h5ad, Scanpy’s sc.pl.spatial needs scalefactors; without them I can only plot points using spot_size.
Questions:
Is there a recommended way to add minimal uns["spatial"] (images + a reasonable spot_diameter_fullres) from the Visium HD outs, so Scanpy can overlay clusters on tissue images?
For segmentations, is it better to use spatialdata-plot directly (polygon overlays with HE/hires images) and do centroid-based analysis separately in Scanpy? How to keep the coordinate frames consistent between the polygon view (spatialdata-plot) and the centroid-based AnnData?
Seurat seems able to plot clusters with images without explicit scalefactors—any guidance on the equivalent approach here?
Goal: overlay segmentation polygons on tissue images (spatialdata-plot), and also analyze/plot clusters using centroids in Scanpy, with consistent alignment, even when scalefactors are missing.
For cell positions, is it preferred to use nucleus segmentations (nucleus_segmentations.geojson) as centroid coordinates instead of cell membrane segmentations?
Really depends on your question, f.e. for generating crops around a giuven cell, one would typically use the cytoplasm centroids.
Should path point to outs/ or outs/segmented_outputs/ when only segmentation outputs are present?
Please either check the documentation or just try it out.
Which counts file should be used with segmentations: filtered_feature_cell_matrix.h5 under segmented_outputs, or another file under outs/?
The reader should automatically pick the correct one
Is there a recommended way to add minimal uns["spatial"] (images + a reasonable spot_diameter_fullres) from the Visium HD outs, so Scanpy can overlay clusters on tissue images?
No, because plotting with scanpy is not the suggested way to plot that, but you can just use spatialdata-plot
For segmentations, is it better to use spatialdata-plot directly (polygon overlays with HE/hires images) and do centroid-based analysis separately in Scanpy? How to keep the coordinate frames consistent between the polygon view (spatialdata-plot) and the centroid-based AnnData?
They should exist in the same coordinate system in the SpatialData objet and are therefore aligned.
Seurat seems able to plot clusters with images without explicit scalefactors—any guidance on the equivalent approach here?
Really not sure what you mean here, the scalefactors are for translating shapes into euqivalent size, f.e. the image dimensions or spots.
Input path: In my tests, spatialdata_io.visium_hd only works if I point to sample/outs (it needs feature_slice.h5 there). However, outs/segmented_outputs already contains segmentation GeoJSONs and counts (filtered_feature_cell_matrix.h5). Why can’t we just point the reader to outs/segmented_outputs and skip feature_slice.h5?
Centroids + Scanpy: visium_hd(..., load_segmentations_only=True, load_nucleus_segmentations=True, load_all_images=True) gives a SpatialData object. I don’t see a ready-made AnnData with both polygons and centroid coordinates that could be passed to Scanpy/scanpy.pl.spatial. I can compute centroids myself, but is there a recommended way to export an AnnData with centroid coordinates + images (or a minimal uns["spatial"]) directly from visium_hd?
Plotting with images: I understand spatialdata-plot is the suggested way to overlay polygons on HE/hires. If I still want to use Scanpy for centroid-based analysis/plots, what’s the best practice to keep coordinates aligned between the SpatialData polygons and the centroid AnnData? Is there any helper to inject images/scale factors into AnnData, or should I always use spatialdata-plot for image overlays?
File ~/scanpy/plotting/_tools/scatterplots.py:1305, in _check_spot_size(spatial_data, spot_size) 1300 if spatial_data is None and spot_size is None:
1301 msg = (
1302 "When .uns['spatial'][library_id] does not exist, spot_size must be "
1303 "provided directly."
...
-> 1305 raise ValueError(msg)
1306 elif spot_size is None:
1307 return spatial_data["scalefactors"]["spot_diameter_fullres"]
ValueError: When .uns['spatial'][library_id] does not exist, spot_size must be provided directly.
Input path: In my tests, spatialdata_io.visium_hd only works if I point to sample/outs (it needs feature_slice.h5 there). However, outs/segmented_outputs already contains segmentation GeoJSONs and counts (filtered_feature_cell_matrix.h5). Why can’t we just point the reader to outs/segmented_outputs and skip feature_slice.h5?
is there a recommended way to export an AnnData with centroid coordinates + images
No, because we don't recommend doing that. Nonetheless, here's an example snippet on how to extract and overlay the centroids. And this function here should help you with the general conversion and images.
If I still want to use Scanpy for centroid-based analysis/plots, what’s the best practice to keep coordinates aligned between the SpatialData polygons and the centroid AnnData? Is there any helper to inject images/scale factors into AnnData, or should I always use spatialdata-plot for image overlays?
Ideally you wouldn't use transformations and keep everything in the same coordiante system, then these coordinates would be equivalent.
@timtreis
Thank you so much for your continued professional support. I’m truly grateful for the time and effort you’ve dedicated to helping me.
Your demo code is very helpful and successfully produces the plot as you described, which has given me a lot of insight.
However, when I tried to reproduce it with my own data (sdata), I encountered an issue: my sdata object doesn’t contain a "labels" attribute, so I wasn’t able to generate the same plot.
from spatialdata_io import visium_hd
from spatialdata_io.experimental import to_legacy_anndata
import numpy as np
import scanpy as sc
sdata = visium_hd(
path="/bhpublic/datas02/litj/project/hd_test/Visium_HD_Mouse_Brain/outs",
load_segmentations_only=True,
load_nucleus_segmentations=True,
load_all_images=True,
dataset_id="Visium_HD_Mouse_Brain",
)
Additionally, I used the following code and obtained the plot below. It seems that due to incorrect scaling, the H&E background image isn’t displayed properly.
from spatialdata_io import visium_hd
from spatialdata_io.experimental import to_legacy_anndata
import numpy as np
import scanpy as sc
sdata = visium_hd(
path="./outs",
load_segmentations_only=True,
load_nucleus_segmentations=True,
load_all_images=True,
dataset_id="Visium_HD_Mouse_Brain",
)
table = next((k for k in sdata.tables if "nucleus" in k.lower()), None) or next(iter(sdata.tables))
coord_sys = next(iter(sdata.coordinate_systems))
adata = to_legacy_anndata(
sdata,
coordinate_system=coord_sys,
table_name=table,
include_images=True,
)
shape_key = table if table in sdata.shapes else next(iter(sdata.shapes))
gdf = sdata.shapes[shape_key].copy()
if "cell_id" in gdf.columns:
gdf.index = gdf["cell_id"].astype(str)
gdf.index = gdf.index.astype(str)
adata.obs.index = adata.obs.index.astype(str)
common = adata.obs.index.intersection(gdf.index)
gdf = gdf.loc[common]
cent = gdf.geometry.centroid
coords = np.column_stack([cent.x.values, cent.y.values])
adata = adata[common].copy()
adata.obsm["spatial"] = coords
adata.obs["x"] = coords[:, 0]
adata.obs["y"] = coords[:, 1]
sc.pp.filter_cells(adata, min_genes=100)
sc.pp.filter_genes(adata, min_cells=10)
adata.layers["counts"] = adata.X.copy()
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000, flavor="seurat_v3", layer="counts")
sc.tl.pca(adata, n_comps=30, use_highly_variable=True)
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
sc.tl.leiden(adata, resolution=0.5)
spatial = adata.uns["spatial"]
lib_id = [k for k in spatial if "hires" in k][0]
img_key = next(iter(spatial[lib_id]["images"]))
sc.pl.spatial(adata, color="leiden", library_id=lib_id, img_key=img_key, spot_size=80)
the resullt is (You can see the H&E background image is in the top left corner.)
Also, I would like to ask, could you please demonstrate how to visualize the results after using sc.tl.leidenand create a categorical plot overlaid on the H&E background image, using the current code approach rather than the zarr method?
Could you please take a look and provide some further guidance?
However, when I tried to reproduce it with my own data (sdata), I encountered an issue: my sdata object doesn’t contain a "labels" attribute, so I wasn’t able to generate the same plot.
sc.pl.spatial(adata, color="leiden", library_id=lib_id, img_key=img_key, spot_size=80)
Also, I would like to ask, could you please demonstrate how to visualize the results after using sc.tl.leiden and create a categorical plot overlaid on the H&E background image, using the current code approach rather than the zarr method?
You're using scanpy again, sorry, as a package developer I don't really have the time to provide this level of personalised support for out-of-scope functionality. I suggest discussing this more with your favourite LLM by pasting the relevant parts of the documentation or code directly.
@timtreis First of all, I want to express my deep gratitude for the significant personal time you've invested in maintaining these foundational modules. I fully understand this isn't an obligation you owe anyone — it's work you do voluntarily — and I've always been genuinely thankful for your dedication.
Regarding the conversion tutorial you mentioned (labels_shapes_interchangeability), it's indeed helpful for understanding the interconversion between labels and shapes. However, it doesn't address the core issue I'm facing: none of these conversions currently provide a proper way to correctly overlay the H&E image onto points plots with the appropriate coordinate transformations and scale factors. For workflows that need to visualize both tissue morphology and spatial gene expression (especially for Space Ranger cell segmentation data), this remains a critical gap.
For workflows that need to visualize both tissue morphology and spatial gene expression (especially for Space Ranger cell segmentation data), this remains a critical gap.
That workflow is solved by SpatialData but you don't want to use it
Activity
selmanozleyen commentedon Sep 18, 2025
sorry I don't get it. Which data do you use? And could you provide with a minimal reproducable example?
asmlgkj commentedon Sep 18, 2025
thanks a lot , I am using the visium hd cell segmentataion outputs, which do not have spot_diameter_fullres parameter n the json file.
it will fail when using sq.pl.spatial_scatter(adata, color="cluster") , give the error like above
the data download as following
asmlgkj commentedon Oct 24, 2025
do you have some time to look at this, thanks a lot @selmanozleyen
asmlgkj commentedon Nov 24, 2025
is there any improvement about this ,thank a lot, does squidpy support cell segmentaion, because spatialdata is not compatible with some downstream software, @selmanozleyen
timtreis commentedon Nov 24, 2025
Hey @asmlgkj, we're in the processing of phasing out the support for directly reading spatial formats into AnnData and then operating on them, f.e. your Visium HD data.
We recommend you use the Visium HD reader from
spatialdata-ioto convert your raw data (including segmentations) into aSpatialDataobject, then you can usespatialdata-plotto visualize your data.What parts of it are incompatible? It is unlikely that we'll continue to support the other plotting case. SpatialData objects contain AnnData tables that you can apply most normal processing workflows on directly.
asmlgkj commentedon Nov 24, 2025
@timtreis Thank you so much for your response and all the incredible work you've put into developing these tools! I really appreciate the time and effort that has gone into building such a powerful framework.
To clarify what I meant by "incompatibility" - I was primarily referring to the visualization workflow. Currently, many users (including myself) are accustomed to using functions like sc.pl.spatial() directly from scanpy for spatial visualization. With the transition to SpatialData objects, while I understand the AnnData tables are still accessible, the direct integration with scanpy's plotting functions becomes less straightforward.
I completely understand that SpatialData represents a more robust and future-oriented architecture, and that we can extract the AnnData tables for downstream analysis. My concern is more about the practical workflow - right now, a significant portion of the Python spatial transcriptomics community still primarily works within the scanpy ecosystem, and the direct compatibility with sc.pl.* functions is something many people rely on.
That said, I recognize this is part of the evolution toward better data structures, and I'm willing to adapt my workflows. I just wanted to provide this feedback about where current users might encounter friction during the transition.
Thanks again for your continued development and for considering user feedback!
timtreis commentedon Nov 24, 2025
Hey @asmlgkj, thank you for your perspective! Yeah, fully agree that people are very much used to the
pl.spatial()orpl.spatial_scatter()syntax. We're currently implementing functions over atspatialdata-plot(such as scverse/spatialdata-plot#474) which will enable us to swap what happens under the hood inpl.spatial_scatter()for a call tospatialdata-plot.Currently, both
squidpyandspatialdata-plotcontain related but separate plotting code, which is not good for maintainability. In fact,spatialdata-plotwas originally mostly based on the functions fromsquidpybut has since then evolved (and diverged) quite a bit.Our medium-term vision is to fully remove all plotting code from
squidpyand purely delegate tospatialdata-plot. In this process, the function signatures and behaviour ofsquidpywill ideally be untouched. Since this change is upcoming, we're no longer fixing/improving the plotting code in Squidpy.However, this also comes with the caveat that we're gradually decreasing support for
AnnDatainSquidpyin favor ofSpatialDataand will remove theImageContainerentirely soon. There will be a converter but sooner or later users will either have to adapt to the new, more powerful,SpatialDatabased ecosystem or work with older versions of Squidpy.asmlgkj commentedon Nov 24, 2025
@timtreis Thank you so much for the detailed explanation! This really helps me understand the vision and roadmap for the plotting ecosystem. The plan to unify the plotting code through spatialdata-plot while maintaining the familiar squidpy function signatures sounds like a great approach for both maintainability and user experience.
I really appreciate all the work that's going into building this more robust and unified framework. I'm definitely interested in transitioning to the SpatialData-based ecosystem - it clearly offers more power and flexibility for spatial data analysis.
I have a quick question, and I hope this doesn't come across as rushing you at all - I'm just trying to plan my analysis pipelines accordingly. Do you have a rough timeline for when the full support for high-resolution platforms like Visium HD and Stereo-seq will be available in the SpatialData ecosystem? I'm asking purely for planning purposes so I can decide whether to adapt my current workflows now or wait for the transition to be more complete.
Again, thank you for all your efforts and for being so responsive to the community. The work you're doing is really valuable!
timtreis commentedon Nov 24, 2025
In what sense? You can already load (
sdata-io.stereoseq,sdata-io.visium_hd) and visualize them.asmlgkj commentedon Nov 24, 2025
@timtreis Thank you for the clarification! I can see that loading and visualization are already supported, which is great.
I think I need to clarify my understanding, as there might be some confusion on my end about how SpatialData handles spatial coordinates. I have a few questions:
Stereo-seq with cell segmentation: It seems that the current sdata-io.stereoseq reader might not support loading cell segmentation data (i.e., segmented cell polygons). Is that correct, or am I missing something?
Coordinate representation: This is actually my main point of confusion that's been blocking me from fully adopting SpatialData. When working in bin/spot mode (not cell segmentation), each spatial unit traditionally has a single XY coordinate point. However, from what I understand, SpatialData seems to represent spatial features as polygons with multiple coordinate points.
My concern is: after loading data with SpatialData, does each spot/bin still have a unique single XY coordinate position, or does it maintain polygon coordinates? Many downstream analyses I work with expect each cell/spot ID to have a single corresponding XY coordinate pair, not a polygon. This perceived incompatibility has been my main hesitation in switching to SpatialData. Am I misunderstanding how the coordinate system works?
Stereo-seq image support: Stereo-seq data also comes with ssDNA or H&E staining images as background. Does the current sdata-io.stereoseq reader support loading these images?
I really appreciate your patience in helping me understand these details. Thank you again for all your work on these tools!
timtreis commentedon Nov 24, 2025
I'm personally not working with StereoSeq, so I'm just going by the codebase here. From the reader it seems we're not yet reading in the segmentation, that's true. While we are in constant contact with other companies regarding updates to their readers, that isn't really the case for STOmics. Therefore it's easy for us to miss such updates to their pipeline. It shouldn't be very difficult to add, though. If you could provide us with example output of a pipeline that includes these segmentations, we can adjust the reader.
Within
SpatialData, cells can either be represented as polygons (in theShapeslayer) or as pixel-mask (in theLabelslayer). However, on both of those you can very easily calcualte their centroids to end up with one (x/y) pair per cell, for example using this method: https://spatialdata.scverse.org/en/stable/api/operations.html#spatialdata.get_centroidsBased on the code, the reader should automatically read these files, yes. However, there might've been changes to their pipeline since we last wrote that reader. However, we can also adjust that of course. In the meantime, here is a tutorial on how you can also easily create
SpatialDataobjects from whichever images, polygons etc you actually have on diskasmlgkj commentedon Nov 25, 2025
@timtreis Thank you so much for the detailed explanation!
I have two follow-up questions regarding cell segmentation support:
For Visium HD, I'm trying to load the cell segmentation data from SpaceRanger v4 outputs. As you mentioned, cells can be represented as polygons in the Shapes layer or as pixel-masks in the Labels layer. However, I'm not sure how to achieve this with the current spatialdata_io.visium_hd reader - apart from first converting to Zarr format.
SpaceRanger v4 (released June 2025) now automatically outputs cell-level segmentation alongside the traditional binned_outputs (2/8/16 µm). The output structure looks something like:
outs/
├── binned_outputs/
│ ├── square_002um/
│ ├── square_008um/
│ └── square_016um/
└── cell_outputs/ # <-- new in v4, contains segmented cell data
├── filtered_feature_bc_matrix.h5
├── spatial/
└── ...
Demo data: 10x provides a Human CRC dataset processed with SpaceRanger v4:
https://www.10xgenomics.com/datasets/visium-hd-cytassist-gene-expression-libraries-of-human-crc
Could you provide example code showing how to load these cell segmentation outputs into SpatialData with proper Shapes (cell polygons) and Labels layers using the method you mentioned? The current bin_size parameter only seems to work for the traditional square bins.
2. Stereo-seq Cellbin.gef
For Stereo-seq, I'd like to load cellbin.gef files (cell-segmented data). Here's an example dataset:
https://www.stomics.tech/col1357 (contains C03937C4.cellbin.gef)
Currently I can read this with stereopy:
pythonimport stereo as st
data = st.io.read_gef(file_path='C03937C4.cellbin.gef', bin_type='cell_bins')
But I'm not sure how to convert this to a proper SpatialData structure that includes:
Cell polygons/boundaries in Shapes layer
Expression matrix linked to cells
Background image if available
The tutorial you linked shows how to manually create SpatialData objects, but I'm struggling to achieve a structure similar to what spatialdata_io.visium_hd produces for bin data - where shapes and tables are properly linked with coordinate systems.
Could you provide a code example for converting Stereo-seq cellbin data to SpatialData format with cell boundaries?
Thank you again for your help! I really appreciate the work on spatialdata-io.
asmlgkj commentedon Nov 26, 2025
@timtreis Thanks a lot
For Visium HD segmentation-only loading with
spatialdata_io.visium_hd:nucleus_segmentations.geojson) as centroid coordinates instead of cell membrane segmentations?pathpoint toouts/orouts/segmented_outputs/when only segmentation outputs are present?filtered_feature_cell_matrix.h5undersegmented_outputs, or another file underouts/?Would appreciate guidance on the recommended inputs/settings for Visium HD segmentation-only usage.
asmlgkj commentedon Nov 26, 2025
@timtreis .Thanks
I load Visium HD segmentation data via spatialdata_io.visium_hd (cell/nucleus), but the resulting AnnData for Scanpy doesn’t include
.uns["spatial"]with scalefactors. When I convert to h5ad, Scanpy’ssc.pl.spatialneeds scalefactors; without them I can only plot points usingspot_size.Questions:
uns["spatial"](images + a reasonablespot_diameter_fullres) from the Visium HD outs, so Scanpy can overlay clusters on tissue images?Goal: overlay segmentation polygons on tissue images (spatialdata-plot), and also analyze/plot clusters using centroids in Scanpy, with consistent alignment, even when scalefactors are missing.
Thanks for any advice!
timtreis commentedon Nov 26, 2025
Really depends on your question, f.e. for generating crops around a giuven cell, one would typically use the cytoplasm centroids.
Please either check the documentation or just try it out.
The reader should automatically pick the correct one
No, because plotting with scanpy is not the suggested way to plot that, but you can just use spatialdata-plot
They should exist in the same coordinate system in the SpatialData objet and are therefore aligned.
Really not sure what you mean here, the scalefactors are for translating shapes into euqivalent size, f.e. the image dimensions or spots.
asmlgkj commentedon Nov 27, 2025
@timtreis Thanks a lot.
I’m working with Visium HD segmentation outputs. A few questions:
here is my test code and outputs
output is
SpatialData object
├── Images
│ ├── 'Visium_HD_Mouse_Brain_cytassist_image': DataArray[cyx] (3, 3000, 3200)
│ ├── 'Visium_HD_Mouse_Brain_hires_image': DataArray[cyx] (3, 6000, 4729)
│ └── 'Visium_HD_Mouse_Brain_lowres_image': DataArray[cyx] (3, 600, 473)
├── Shapes
│ ├── 'Visium_HD_Mouse_Brain_cell_segmentations': GeoDataFrame shape: (40222, 2) (2D shapes)
│ └── 'Visium_HD_Mouse_Brain_nucleus_segmentations': GeoDataFrame shape: (40095, 2) (2D shapes)
└── Tables
├── 'cell_segmentations': AnnData (40222, 19059)
└── 'nucleus_segmentations': AnnData (40095, 19059)
with coordinate systems:
▸ 'Visium_HD_Mouse_Brain', with elements:
Visium_HD_Mouse_Brain_cytassist_image (Images), Visium_HD_Mouse_Brain_hires_image (Images), Visium_HD_Mouse_Brain_lowres_image (Images), Visium_HD_Mouse_Brain_cell_segmentations (Shapes), Visium_HD_Mouse_Brain_nucleus_segmentations (Shapes)
▸ 'Visium_HD_Mouse_Brain_downscaled_hires', with elements:
Visium_HD_Mouse_Brain_hires_image (Images), Visium_HD_Mouse_Brain_cell_segmentations (Shapes), Visium_HD_Mouse_Brain_nucleus_segmentations (Shapes)
▸ 'Visium_HD_Mouse_Brain_downscaled_lowres', with elements:
Visium_HD_Mouse_Brain_lowres_image (Images), Visium_HD_Mouse_Brain_cell_segmentations (Shapes), Visium_HD_Mouse_Brain_nucleus_segmentations (Shapes)
Input path: In my tests,
spatialdata_io.visium_hdonly works if I point tosample/outs(it needsfeature_slice.h5there). However,outs/segmented_outputsalready contains segmentation GeoJSONs and counts (filtered_feature_cell_matrix.h5). Why can’t we just point the reader toouts/segmented_outputsand skipfeature_slice.h5?Centroids + Scanpy:
visium_hd(..., load_segmentations_only=True, load_nucleus_segmentations=True, load_all_images=True)gives a SpatialData object. I don’t see a ready-made AnnData with both polygons and centroid coordinates that could be passed to Scanpy/scanpy.pl.spatial. I can compute centroids myself, but is there a recommended way to export an AnnData with centroid coordinates + images (or a minimaluns["spatial"]) directly from visium_hd?Plotting with images: I understand spatialdata-plot is the suggested way to overlay polygons on HE/hires. If I still want to use Scanpy for centroid-based analysis/plots, what’s the best practice to keep coordinates aligned between the SpatialData polygons and the centroid AnnData? Is there any helper to inject images/scale factors into AnnData, or should I always use spatialdata-plot for image overlays?
sc.pl.spatial(adata, color='leiden')
ValueError Traceback (most recent call last)
Cell In[3], line 1
----> 1 sc.pl.spatial(adata, color='leiden')
File ~/typing_extensions.py:2956, in deprecated.call..wrapper(*args, **kwargs)
2953 @functools.wraps(arg)
2954 def wrapper(*args, **kwargs):
2955 warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
-> 2956 return arg(*args, **kwargs)
File ~/scanpy/plotting/_tools/scatterplots.py:1008, in spatial(adata, basis, img, img_key, library_id, crop_coord, alpha_img, bw, size, scale_factor, spot_size, na_color, show, return_fig, save, **kwargs)
1006 library_id, spatial_data = _check_spatial_data(adata.uns, library_id)
1007 img, img_key = _check_img(spatial_data, img, img_key, bw=bw)
-> 1008 spot_size = _check_spot_size(spatial_data, spot_size)
1009 scale_factor = _check_scale_factor(
1010 spatial_data, img_key=img_key, scale_factor=scale_factor
1011 )
1012 crop_coord = _check_crop_coord(crop_coord, scale_factor)
File ~/scanpy/plotting/_tools/scatterplots.py:1305, in _check_spot_size(spatial_data, spot_size)
1300 if spatial_data is None and spot_size is None:
1301 msg = (
1302 "When .uns['spatial'][library_id] does not exist, spot_size must be "
1303 "provided directly."
...
-> 1305 raise ValueError(msg)
1306 elif spot_size is None:
1307 return spatial_data["scalefactors"]["spot_diameter_fullres"]
ValueError: When .uns['spatial'][library_id] does not exist, spot_size must be provided directly.
Thanks a lot, and apologies if I missed something obvious.
timtreis commentedon Nov 27, 2025
You might find more info here scverse/spatialdata-io#310 and your other issue here scverse/spatialdata-io#323
No, because we don't recommend doing that. Nonetheless, here's an example snippet on how to extract and overlay the centroids. And this function here should help you with the general conversion and images.
Ideally you wouldn't use transformations and keep everything in the same coordiante system, then these coordinates would be equivalent.
asmlgkj commentedon Nov 28, 2025
@timtreis
Thank you so much for your continued professional support. I’m truly grateful for the time and effort you’ve dedicated to helping me.
Your demo code is very helpful and successfully produces the plot as you described, which has given me a lot of insight.
However, when I tried to reproduce it with my own data (sdata), I encountered an issue: my sdata object doesn’t contain a "labels" attribute, so I wasn’t able to generate the same plot.
the sdata is
SpatialData object
├── Images
│ ├── 'Visium_HD_Mouse_Brain_cytassist_image': DataArray[cyx] (3, 3000, 3200)
│ ├── 'Visium_HD_Mouse_Brain_hires_image': DataArray[cyx] (3, 6000, 4729)
│ └── 'Visium_HD_Mouse_Brain_lowres_image': DataArray[cyx] (3, 600, 473)
├── Shapes
│ ├── 'Visium_HD_Mouse_Brain_cell_segmentations': GeoDataFrame shape: (40222, 2) (2D shapes)
│ └── 'Visium_HD_Mouse_Brain_nucleus_segmentations': GeoDataFrame shape: (40095, 2) (2D shapes)
└── Tables
├── 'cell_segmentations': AnnData (40222, 19059)
└── 'nucleus_segmentations': AnnData (40095, 19059)
with coordinate systems:
▸ 'Visium_HD_Mouse_Brain', with elements:
Visium_HD_Mouse_Brain_cytassist_image (Images), Visium_HD_Mouse_Brain_hires_image (Images), Visium_HD_Mouse_Brain_lowres_image (Images), Visium_HD_Mouse_Brain_cell_segmentations (Shapes), Visium_HD_Mouse_Brain_nucleus_segmentations (Shapes)
▸ 'Visium_HD_Mouse_Brain_downscaled_hires', with elements:
Visium_HD_Mouse_Brain_hires_image (Images), Visium_HD_Mouse_Brain_cell_segmentations (Shapes), Visium_HD_Mouse_Brain_nucleus_segmentations (Shapes)
▸ 'Visium_HD_Mouse_Brain_downscaled_lowres', with elements:
Visium_HD_Mouse_Brain_lowres_image (Images), Visium_HD_Mouse_Brain_cell_segmentations (Shapes), Visium_HD_Mouse_Brain_nucleus_segmentations (Shapes)
Additionally, I used the following code and obtained the plot below. It seems that due to incorrect scaling, the H&E background image isn’t displayed properly.
the resullt is (You can see the H&E background image is in the top left corner.)
Also, I would like to ask, could you please demonstrate how to visualize the results after using sc.tl.leidenand create a categorical plot overlaid on the H&E background image, using the current code approach rather than the zarr method?
Could you please take a look and provide some further guidance?
asmlgkj commentedon Dec 3, 2025
@timtreis do you have some time to have a look, thank a lot
timtreis commentedon Dec 3, 2025
You can read up on how to convert these here.
You're using scanpy again, sorry, as a package developer I don't really have the time to provide this level of personalised support for out-of-scope functionality. I suggest discussing this more with your favourite LLM by pasting the relevant parts of the documentation or code directly.
asmlgkj commentedon Dec 3, 2025
@timtreis First of all, I want to express my deep gratitude for the significant personal time you've invested in maintaining these foundational modules. I fully understand this isn't an obligation you owe anyone — it's work you do voluntarily — and I've always been genuinely thankful for your dedication.
Regarding the conversion tutorial you mentioned (labels_shapes_interchangeability), it's indeed helpful for understanding the interconversion between labels and shapes. However, it doesn't address the core issue I'm facing: none of these conversions currently provide a proper way to correctly overlay the H&E image onto points plots with the appropriate coordinate transformations and scale factors. For workflows that need to visualize both tissue morphology and spatial gene expression (especially for Space Ranger cell segmentation data), this remains a critical gap.
timtreis commentedon Dec 3, 2025
That workflow is solved by
SpatialDatabut you don't want to use it