spatialdata-io icon indicating copy to clipboard operation
spatialdata-io copied to clipboard

VisiumHD: load scalefactor microns_per_pixel in anndata

Open ConstensouxAlexis opened this issue 10 months ago • 5 comments

Hello, Segmentation of nuclei on VisiumHD H&E image might require (at least for StarDist segmentation model) to have the micron per pixel information (since pre trained models can be trained on H&E images with different micron per pixel ratio, it is sometimes needed to rescale the image). Could it be possibe for the visiumHD reader to parse this info and store it somewhere in the Anndata ? Thank you very much ! Alexis

ConstensouxAlexis avatar Mar 11 '25 13:03 ConstensouxAlexis

Hi @ConstensouxAlexis, would it be an option to retrieve this coefficient from the coordinate transformation metadata?

from spatialdata.transformations import get_transformation

transformation = get_transformation(sdata['my_element'], to_coordinate_system='my_coordinate_system')
# if it's a scale
print(transformation.scale)
# in the general case, to get the `x` scale factor
transformation.to_affine_matrix(input_axes=('x',), output_axes=('x',)).flatten().item()

Adding the metadata would work for VisiumHD, but the approach above (or something adapted from it) would work for any tech and any image. Please let me know what you think.

LucaMarconato avatar Mar 11 '25 16:03 LucaMarconato

Hi, thank you for your answer ! I might be wrong, but I think that if we want to derive the number of micron per pixel from the transformation metadata, we need for at least one image resolution the absolute value of the number of microns per pixel.

I think that this value depends on the microscope used to generate the full resolution image. For instance, on my VisiumHD samples, this value is different accross samples, thus the "micron_per_pixel" key in the scalefactors.json is different. However I think my usecase is marginal and I am fine parsing this value manually !

ConstensouxAlexis avatar Mar 11 '25 18:03 ConstensouxAlexis

You are right, in the sense that the information on how to map pixels to microns should be present somewhere in the transformations, but if this is available there is no need to explicitly pass the metadata. Practically, the general case is that there is a series of transformations to map the image to a physical space (let's call it "micron" coordinate system). Then instead of get_transformation() one would call get_transformation_between_coordinate_systems(), between the image and the "micron" coordinate system.

Also the functions described in this issue would be of help: https://github.com/scverse/spatialdata/issues/436. Regarding timelines, we are currently working on the OME-NGFF side to push for the release of the coordinate transformation specification. When this is available the ergonomics around working with units will improve, by implementing the functions in the issue above above.

For the moment I'd suggest either get_transformation() or get_transformation_between_coordinate_systems(), or manually adding the additional metadata.

  • [ ] Finally we could double check in the VisiumHD reader that the information on pixels to microns is present (ensuring that the coordinate systems express the coordinates in microns).

LucaMarconato avatar Mar 11 '25 18:03 LucaMarconato

Ok, thank you very much for explanations !

ConstensouxAlexis avatar Mar 12 '25 10:03 ConstensouxAlexis

Hi @ConstensouxAlexis,

I was also interested in accessing the microns_to_pixel value, but, since there is no microns coordinate system when reading the Visium HD data, it's not possible to simply use the information from the transformations.

I found a way to access it via the bins surfaces:

for key, geo_df in sdata.shapes.items():
    diameter = int(key[-5:-2]) # bin width in microns
    area = geo_df.geometry.iloc[0].area # bin area based on the shapes, in pixels^2

    # the line below depends on the bins_as_squares argument you used in the reader
    microns_per_pixel = diameter / (np.sqrt(area) if bins_as_squares else geo_df["radius"].iloc[0] * 2)

quentinblampey avatar Aug 29 '25 09:08 quentinblampey