-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from tdrose/dev
Conversion of AnnData to ion image array.
- Loading branch information
Showing
14 changed files
with
220 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,5 @@ plots/* | |
*.html | ||
analysis_config.json | ||
.Rhistory | ||
|
||
.vscode/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from metaspace_converter.anndata_to_array import anndata_to_image_array | ||
from metaspace_converter.to_anndata import metaspace_to_anndata | ||
from metaspace_converter.to_spatialdata import metaspace_to_spatialdata | ||
|
||
__all__ = ["metaspace_to_anndata", "metaspace_to_spatialdata"] | ||
__all__ = ["metaspace_to_anndata", "metaspace_to_spatialdata", "anndata_to_image_array"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import numpy as np | ||
from anndata import AnnData | ||
|
||
from metaspace_converter.constants import COL, METASPACE_KEY, X, Y | ||
from metaspace_converter.to_anndata import all_image_pixel_coordinates | ||
|
||
|
||
def _check_pixel_coordinates(adata: AnnData) -> bool: | ||
sorted_obs = adata.obs.sort_values([COL.ion_image_pixel_y, COL.ion_image_pixel_x]) | ||
|
||
pixel_list = sorted_obs[[COL.ion_image_pixel_y, COL.ion_image_pixel_x]].values | ||
|
||
img_size = adata.uns[METASPACE_KEY]["image_size"] | ||
required_pixels = all_image_pixel_coordinates((img_size[Y], img_size[X])) | ||
|
||
return np.all(np.equal(pixel_list, required_pixels)) | ||
|
||
|
||
def anndata_to_image_array(adata: AnnData) -> np.ndarray: | ||
""" | ||
Extracts an array of ion images from an AnnData object | ||
(that has been generated through the ``metaspace_to_anndata`` function). | ||
Args: | ||
adata: An AnnData object. | ||
Returns: | ||
A three-dimensional Numpy array in the following shape | ||
* Dimension 0: Number of ion images in the order of ``adata.var_names`` | ||
* Dimension 1: Image height ``adata.uns["metaspace"]["image_size"]["y"]`` | ||
* Dimension 2: Image width ``adata.uns["metaspace"]["image_size"]["x"]`` | ||
Raises: | ||
ValueError: If the AnnData object has been modified. | ||
E.g. Pixel have been removed/added and the number of pixels | ||
and their coordinates do not match the original image dimensions. | ||
""" | ||
|
||
pixel_array = adata.X.transpose().copy() | ||
img_size = adata.uns[METASPACE_KEY]["image_size"] | ||
|
||
# Check if image dimensions are okay | ||
if img_size[X] * img_size[Y] != pixel_array.shape[1]: | ||
raise ValueError("Number of observations does not match the original image dimensions") | ||
|
||
# Check if all pixels are available | ||
if not _check_pixel_coordinates(adata): | ||
raise ValueError("Not all pixels for ion images are available") | ||
|
||
# Sort indices, in case of modified order of pixels (obs) | ||
image_sorting = adata.obs.sort_values( | ||
[COL.ion_image_pixel_y, COL.ion_image_pixel_x] | ||
).index.values.astype(int) | ||
pixel_array = pixel_array[:, image_sorting] | ||
|
||
image_array = pixel_array.reshape( | ||
( | ||
pixel_array.shape[0], | ||
adata.obs[COL.ion_image_pixel_y].max() + 1, | ||
adata.obs[COL.ion_image_pixel_x].max() + 1, | ||
) | ||
) | ||
|
||
return image_array |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
from dataclasses import dataclass | ||
from typing import Final | ||
|
||
SPATIAL_KEY: Final = "spatial" | ||
METASPACE_KEY: Final = "metaspace" | ||
OBS_INDEX_NAME: Final = "ion_image_pixel" | ||
VAR_INDEX_NAME: Final = "formula_adduct" | ||
Shape2d = tuple[int, int] | ||
|
||
|
||
@dataclass | ||
class COL: | ||
ion_image_shape_y: Final = "ion_image_shape_y" | ||
ion_image_shape_x: Final = "ion_image_shape_x" | ||
ion_image_pixel_y: Final = "ion_image_pixel_y" | ||
ion_image_pixel_x: Final = "ion_image_pixel_x" | ||
|
||
|
||
COORD_SYS_GLOBAL: Final = "global" | ||
MICROMETER: Final = "micrometer" | ||
OPTICAL_IMAGE_KEY: Final = "optical_image" | ||
POINTS_KEY: Final = "maldi_points" | ||
REGION_KEY: Final = "region" | ||
INSTANCE_KEY: Final = "instance_id" | ||
X: Final = "x" | ||
Y: Final = "y" | ||
C: Final = "c" | ||
XY: Final = (X, Y) | ||
YX: Final = (Y, X) | ||
YXC: Final = (Y, X, C) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import pytest | ||
from anndata import AnnData | ||
|
||
if TYPE_CHECKING: | ||
import _pytest.fixtures | ||
|
||
from metaspace_converter import anndata_to_image_array | ||
from metaspace_converter.constants import COL, METASPACE_KEY, X, Y | ||
from metaspace_converter.to_anndata import all_image_pixel_coordinates | ||
|
||
|
||
@pytest.fixture | ||
def adata_with_coordinates_and_image_size( | ||
request: "_pytest.fixtures.SubRequest", | ||
) -> tuple[AnnData, np.ndarray]: | ||
""" | ||
Create an AnnData object filled with parametrized shape | ||
Args: | ||
request: Request passed by Pytest, can be parametrized with a dictionary containing | ||
``num_ions``, ``height``, ``width`` | ||
Returns: | ||
A tuple of an AnnData object and the expected array of stacked ion images | ||
""" | ||
num_ions = request.param.get("num_ions", 0) | ||
height = request.param.get("height", 0) | ||
width = request.param.get("width", 0) | ||
shape = (num_ions, height, width) | ||
# Create a stack of ion images where every pixel has a different value | ||
ion_images_stack = np.arange(np.prod(shape)).reshape(shape) | ||
coordinates = all_image_pixel_coordinates(shape[1:]) | ||
# Create an AnnData with pixel coordinates | ||
obs = pd.DataFrame( | ||
{COL.ion_image_pixel_y: coordinates[:, 0], COL.ion_image_pixel_x: coordinates[:, 1]} | ||
) | ||
adata = AnnData( | ||
X=ion_images_stack.reshape((height * width, num_ions)), | ||
obs=obs, | ||
uns={METASPACE_KEY: {"image_size": {X: width, Y: height}}}, | ||
) | ||
expected = ion_images_stack | ||
return adata, expected | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"adata_with_coordinates_and_image_size", | ||
[ | ||
# Non-square ion images | ||
dict(num_ions=4, height=2, width=3), | ||
# Edge case: No annotations found | ||
dict(num_ions=0, height=2, width=3), | ||
], | ||
indirect=["adata_with_coordinates_and_image_size"], | ||
) | ||
def test_anndata_to_image_array(adata_with_coordinates_and_image_size: AnnData): | ||
adata, expected = adata_with_coordinates_and_image_size | ||
|
||
actual = anndata_to_image_array(adata) | ||
|
||
assert actual.shape == ( | ||
adata.shape[1], | ||
adata.obs[COL.ion_image_pixel_y].max() + 1, | ||
adata.obs[COL.ion_image_pixel_x].max() + 1, | ||
) | ||
|
||
assert actual.shape == ( | ||
adata.shape[1], | ||
adata.uns[METASPACE_KEY]["image_size"][Y], | ||
adata.uns[METASPACE_KEY]["image_size"][X], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters