-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ROIs] Transform physical-unit ROI into pixels, at arbitrary pyramid level #22
Comments
Great! This should then become a library function that different tasks can call. |
This is our prototype: import anndata as ad
import pandas as pd
import numpy as np
import math
import typing
from typing import List, Union, Dict
def convert_ROI_table_to_indices(ROI : ad.AnnData,
level : int = 0,
coarsening_xy : int = 2) -> List[List]:
list_indices = []
for FOV in range(ROI.n_obs):
# Extract data from anndata table
# FIXME: is there a better way to do this??
x_micrometer = ROI[FOV, ROI.var_names == "x_micrometer"].X[0, 0]
y_micrometer = ROI[FOV, ROI.var_names == "y_micrometer"].X[0, 0]
z_micrometer = ROI[FOV, ROI.var_names == "z_micrometer"].X[0, 0]
len_x_micrometer = ROI[FOV, ROI.var_names == "len_x_micrometer"].X[0, 0]
len_y_micrometer = ROI[FOV, ROI.var_names == "len_y_micrometer"].X[0, 0]
len_z_micrometer = ROI[FOV, ROI.var_names == "len_z_micrometer"].X[0, 0]
pixel_size_x = ROI[FOV, ROI.var_names == "pixel_size_x"].X[0, 0]
pixel_size_y = ROI[FOV, ROI.var_names == "pixel_size_y"].X[0, 0]
pixel_size_z = ROI[FOV, ROI.var_names == "pixel_size_z"].X[0, 0]
# Set pyramid-level pixel sizes
prefactor = coarsening_xy ** level
pixel_size_x *= prefactor
pixel_size_y *= prefactor
# Identify indices along the three dimensions
# FIXME: We don't need Z indices for FOVs, but perhaps we will for
# more complex 3D shapes
start_x = x_micrometer / pixel_size_x
end_x = (x_micrometer + len_x_micrometer) / pixel_size_x
start_y = y_micrometer / pixel_size_y
end_y = (y_micrometer + len_y_micrometer) / pixel_size_y
start_z = z_micrometer / pixel_size_z
end_z = (z_micrometer + len_z_micrometer) / pixel_size_z
indices = [start_z, end_z, start_y, end_y, start_x, end_x]
# Round indices to lower integer
# FIXME: to be checked
indices = list(map(math.floor, indices))
list_indices.append(indices)
return list_indices (I'm pushing it now) |
I'm also no AnnData expert. If this is really the best way, we can also convert the AnnData dataframe to pandas and then use Though according to the AnnData documentation, indexing with column names should be possible:
I think it's a good idea to support 3D ROIs from the beginning. Organoid ROIs may not always cover the whole Z span. Especially when we have 80 or 100 Z slices, it will be very valuable to be able to just select e.g. the 30-50 Z slices that an organoid is in for processing, instead of the whole stack |
We need to better define what's the expected output for 2D ROIs, when used to index a 3D array. The obvious use case is illumination correction, which we want to apply to a bunch of images of shape To be more explicit we are now adding this functionality # Default behavior
if num_z_replicas == 1:
list_indices.append(indices)
# Create 3D stack of 2D ROIs
else:
# Check that this ROI is 2D, i.e. it has z indices [0:1]
if start_z != 0 or end_z != 1:
raise Exception(f"ERROR: num_z_replicas={num_z_replicas}, "
f"but [start_z,end_z]={[start_z,end_z]}")
# Loop over Z planes
for z_start in range(num_z_replicas):
indices[0:2] = [z_start, z_start + 1]
list_indices.append(indices[:]) Comments? |
True. I guess it won't be possible to remove the |
Good question. To my mind, ROIs are always 3D representations. If we have sites, it's some x & y location plus all the z. If we have an organoid, it is x & y box plus some to all of the z planes. When a task wants to run per plane, it could lazily process the different planes of a ROI. That would be the more general solution, because then tasks should run on 2D & 3D, just depending on what ROI they get and how they process it. Regarding the |
This seems to match with what we proposed in #22: if ROIs are 2D then we replicate them over all Z planes, while if ROIs are 3D we just use the ones that are defined in the table. |
Ah, now I understand. Yes, that is a good idea. As such, the Z parameter is optional and if it's not provided, it's assumed that the ROI contains all Z planes, right? That actually is a nice generalization! :) |
We should decide the default behavior. The one which is in-place now simply returns the ROIs as they are defined in the table:
We can improve this behavior with a constraint that says: if you provided only ROIs which have a trivial Z extensions (corresponding to indices |
You can use this snippet import numpy as np
import anndata as ad
import zarr
from anndata.experimental import write_elem
adata = ad.AnnData(X=np.arange(6).reshape(2,3), dtype=int)
print(f"First table: {adata}")
adata.obs_names = ["row_A", "row_B"]
adata.var_names = ["col_1", "col_2", "col_3"]
zarr_group = zarr.group("test_anndata.zarr")
write_elem(zarr_group, "table", adata)
adata2 = ad.read_zarr("test_anndata.zarr/table")
print(f"Second table: {adata2}")
single_element = adata2["row_B", "col_2"]
print(f"Single element: {single_element}")
print(f"Single element (data): {single_element.X[0, 0]}") |
Maybe we should briefly discuss this, it's a good question. Can we have a quick call at 9:30? (or later today) |
As of our last call with @jluethi:
|
Can we also extract the Z dimension of a well from the metadata? Or should we parse the filenames? (this goes together with the other request of parsing the image size - see #23) |
Hmm, I think it's feasible to parse this. Will add this to the list of things to add to |
…onversion, if needed) (ref #113)
…add _inspect_ROI_table (ref #112 #113)
* Do not store pixel sizes in the FOV-ROI table; * Always parse pixel sizes from the zattrs file, when needed; * Add level arg to extract_zyx_pixel_sizes_from_zattrs.
I close this as def prepare_FOV_ROI_table(df: pd.DataFrame) -> ad.AnnData:
def convert_ROI_table_to_indices(
ROI: ad.AnnData,
level: int = 0,
coarsening_xy: int = 2,
pixel_sizes_zyx: Union[List[float], Tuple[float]] = None,
) -> List[List[int]]:
def convert_ROI_table_to_indices(
ROI: ad.AnnData,
level: int = 0,
coarsening_xy: int = 2,
pixel_sizes_zyx: Union[List[float], Tuple[float]] = None,
) -> List[List[int]]:
def split_3D_indices_into_z_layers(
list_indices: List[List[int]],
) -> List[List[int]]:
def _inspect_ROI_table(
path: str = None,
level: int = 0,
coarsening_xy: int = 2,
pixel_sizes_zyx=[1.0, 0.1625, 0.1625],
) -> None:
def extract_zyx_pixel_sizes_from_zattrs(zattrs_path: str, level: int = 0): Let's reopen it if something else comes up. |
Starting from discussion in
we are now splitting the work on ROIs into several steps/issues:
Useful resources:
Here is a prototype of the function that returns indices corresponding to a ROI:
Note: this only works for orthogonal ROIs, which are the only ones we plan to support for the moment (AKA: non-orthogonal regions must be placed into a bounding box).
Missing: how to treat pixel rounding for higher levels.
The text was updated successfully, but these errors were encountered: