Source code for timagetk.components.tissue_image

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#  Copyright (c) 2022 Univ. Lyon, ENS de Lyon, UCB Lyon 1, CNRS, INRAe, Inria
#  All rights reserved.
#  This file is part of the TimageTK library, and is released under the "GPLv3"
#  license. Please see the LICENSE.md file that should have been included as
#  part of this package.
# ------------------------------------------------------------------------------

"""Biology oriented class to represent cell segmented tissue image.

As opposed to nuclei images, these images are labelled at the cell level and thus have topological information.

"""

import time

import numpy as np
import scipy.ndimage as nd

from timagetk.bin.logger import get_logger
from timagetk.components.labelled_image import LabelledImage
from timagetk.components.labelled_image import _test_structuring_element
from timagetk.components.labelled_image import image_with_labels
from timagetk.components.labelled_image import image_without_labels
from timagetk.components.labelled_image import relabel_from_mapping
from timagetk.components.labelled_image import structuring_element
from timagetk.components.spatial_image import SpatialImage
from timagetk.features.cell_edges import Edges3D
from timagetk.features.cell_vertices import Vertex3D
from timagetk.features.cell_walls import Wall2D
from timagetk.features.cell_walls import Wall3D
from timagetk.features.cells import Cell2D
from timagetk.features.cells import Cell3D
from timagetk.util import elapsed_time
from timagetk.util import get_attributes
from timagetk.util import get_class_name

log = get_logger(__name__)

MISS_CELL = "The following cell{} {} not found in the image: {}"  # ''|'s'; 'is'|'are'; labels


[docs] def voxel_n_layers(image, background, connectivity=None, n_layers=1, **kwargs): """Extract the n-first layer of non-background voxels, *i.e.* those in contact with the background. Parameters ---------- image : numpy.ndarray A labelled array with a background. background : int Id of the background label. connectivity : int, optional Connectivity or neighborhood of the structuring element, default is ``18`` in 3D and ``4`` in 2D. Should be in [4, 6, 8, 18, 26], where 4 and 8 are 2D structuring elements, the rest are 3D structuring elements. n_layers : int, optional Number of layer of voxels to extract, the first one being in contact with the background Returns ------- numpy.ndarray Labelled image made of the selected number of voxel layers """ verbose = kwargs.get('verbose', False) if verbose: log.info("Extracting the first layer of voxels...") t_start = time.time() # - Define default connectivity according to dimensionality: if connectivity is None: if image.ndim == 2: connectivity = 4 else: connectivity = 18 # - Get background position (mask) mask_img_1 = (image == background) # - Dilate it by one voxel using a 18-connexe 3D structuring element: struct = structuring_element(connectivity) assert _test_structuring_element(image, struct) dil_1 = nd.binary_dilation(mask_img_1, structure=struct, iterations=n_layers) # - Difference with background mask gives the first layer of voxels: layer = dil_1 ^ mask_img_1 if verbose: elapsed_time(t_start) return image * layer, mask_img_1
[docs] def cell_layers_from_image(seg_img, layers, labels=None, not_a_label=0, background=1): """Return a layer indexed dictionary of with an array of label per time-point. Parameters ---------- seg_img : str or timagetk.LabelledImage or timagetk.TissueImage3D Segmented image. layers : list or set List of cell layers to return. labels : list or set, optional List of cell labels to keep. not_a_label : int, optional If specified, it defines the "unknown label" (*i.e.* a value that is not a label). Defaults to ``0``. background : int, optional If specified, it defines the "background label" (*i.e.* the space around labels). Defaults to ``1``. Returns ------- dict Dictionary of labels per cell layer. Examples -------- >>> from timagetk.components.tissue_image import cell_layers_from_image >>> from timagetk.synthetic_data.labelled_image import example_layered_sphere_labelled_image >>> from timagetk.io import imread >>> from timagetk import TissueImage3D >>> im = example_layered_sphere_labelled_image(n_points=5, n_layers=2, extent=50., voxelsize=(0.5, 0.5, 0.5)) >>> im = TissueImage3D(im, background=1, not_a_label=0) >>> cell_by_layers = cell_layers_from_image(im, [1, 2, 3]) >>> print(cell_by_layers[2]) # list cells in layer 2 [3, 4, 5, 6, 7] >>> print({layer: len(cids) for layer, cids in cell_by_layers.items()}) {1: 20, 2: 5, 3: 1} """ min_layer = 1 max_layer = max(layers) + 1 # Initialize the dictionary: cell_layer = {l: {} for l in range(min_layer, max_layer)} if isinstance(seg_img, str): from timagetk.io import imread seg_img = TissueImage3D(imread(seg_img), not_a_label=not_a_label, background=background) elif isinstance(seg_img, LabelledImage): seg_img = TissueImage3D(seg_img, not_a_label=not_a_label, background=background) elif isinstance(seg_img, TissueImage3D): seg_img = TissueImage3D(seg_img, not_a_label=not_a_label, background=background) else: raise TypeError("Parameter `seg_img` should either be a filename, a LabelledImage or a TissueImage3D instance!") # Get the list of cell ids: cell_ids = seg_img.cells.ids() # Filter the list of cell ids to use in layer search: if labels is not None: cell_ids &= set(labels) # Get the neighbors of the 'background' as first layer: cell_layer[1] = list(set(seg_img.cells.neighbors([background])[background]) & cell_ids) # Iterate over the successive layers: for layer in range(min_layer + 1, max_layer): seg_img = TissueImage3D(image_without_labels(seg_img, cell_layer[layer - 1], erase_value=background), not_a_label=not_a_label, background=background) cell_layer[layer] = list(set(seg_img.cells.neighbors([background])[background]) & cell_ids) return {k: list(map(int, v)) for k, v in cell_layer.items() if k in layers}
[docs] class AbstractTissueImage(LabelledImage): """Biology oriented class to manipulate dense tissues made of cells and potentially a background. Attributes ---------- _background_id : int or None The id referring to the background, if any. _cells : list of int The list of ids referring to cells. _epidermal_cells : list of int The list of ids referring to epidermal cells, *i.e.* those in contact with the background. _voxel_layer1 : numpy.ndarray The array made of the first layer of voxels in contact with the background. """
[docs] def __new__(cls, image, **kwargs): """Abstract tissue image construction method. Parameters ---------- image : numpy.ndarray or timagetk.components.spatial_image.SpatialImage or timagetk.components.labelled_image.LabelledImage A dense tissue image, *i.e.* a cell-based segmented array with connected labels. Other Parameters ---------------- origin : list, optional Coordinates of the origin in the image, default: [0,0] or [0,0,0] voxelsize : list, optional. Image voxelsize, default: [1.0,1.0] or [1.0,1.0,1.0] dtype : str, optional Image type, default dtype = image.dtype metadata : dict, optional Dictionary of image metadata, default is an empty dict not_a_label : int, optional If given, define the "unknown label" *i.e.* the value that does not refers to a label Example ------- >>> import numpy as np >>> from timagetk import SpatialImage >>> from timagetk import LabelledImage >>> from timagetk.components.tissue_image import AbstractTissueImage >>> test_array = np.random.randint(0, 255, (5, 5)).astype(np.uint8) >>> test_array[0,:] = np.ones((5,), dtype=np.uint8) >>> # - Construct from a NumPy array: >>> tissue = AbstractTissueImage(test_array, voxelsize=[0.5, 0.5], not_a_label=0, background=1) >>> print(tissue) >>> # - Construct from a SpatialImage: >>> image = SpatialImage(test_array, voxelsize=[0.5, 0.5]) >>> tissue = AbstractTissueImage(image, not_a_label=0, background=1) >>> print(tissue) >>> # - Construct from a LabelledImage: >>> lab_image = LabelledImage(test_array, voxelsize=[0.5, 0.5], not_a_label=0) >>> tissue = AbstractTissueImage(lab_image, background=1) >>> print(tissue) """ if isinstance(image, LabelledImage): # -- Can be a LabelledImage or any class inheriting from it (like AbstractTissueImage, TissueImage2D or TissueImage3D): return super(AbstractTissueImage, cls).__new__(cls, image, **kwargs) elif isinstance(image, SpatialImage): # -- Can be a SpatialImage or any class inheriting from it: not_a_label = kwargs.pop('not_a_label', None) return super(AbstractTissueImage, cls).__new__(cls, image, not_a_label=not_a_label, **kwargs) elif isinstance(image, np.ndarray): # -- Case where constructing from a NumPy array: # Attributes are set to None by default to use default values with the `LabelledImage.__new__` method origin = kwargs.pop('origin', None) voxelsize = kwargs.pop('voxelsize', None) dtype = kwargs.pop('dtype', image.dtype) metadata = kwargs.pop('metadata', None) not_a_label = kwargs.pop('not_a_label', None) return super(LabelledImage, cls).__new__(cls, image, origin=origin, voxelsize=voxelsize, dtype=dtype, metadata=metadata, not_a_label=not_a_label, **kwargs) else: msg = "Undefined construction method for type '{}'!" raise NotImplementedError(msg.format(type(image)))
[docs] def __init__(self, image, background=None, **kwargs): """Abstract tissue image initialisation method. Parameters ---------- image : numpy.ndarray or timagetk.SpatialImage or timagetk.LabelledImage A labelled array defining a dense multicellular tissue. background : int, optional If given, define the id of the background, that is the "space" surrounding the tissue. Other Parameters ---------------- origin : list, optional Coordinates of the origin in the image, default: [0,0] or [0,0,0] voxelsize : list, optional. Image voxelsize, default: [1.0,1.0] or [1.0,1.0,1.0] dtype : str, optional Image type, default dtype = image.dtype metadata : dict, optional Dictionary of image metadata, default is an empty dict not_a_label : int, optional If specified, it defines the "unknown label" (*i.e.* a value that is not a label) """ super().__init__(image, **kwargs) # - In case a AbstractTissueImage is constructed from a AbstractTissueImage, get the attributes values: if isinstance(image, AbstractTissueImage): attr_list = ["background"] attr_dict = get_attributes(image, attr_list) class_name = get_class_name(image) msg = "Overriding optional keyword arguments '{}' ({}) with defined attribute ({}) in given '{}'!" # -- Check necessity to override 'origin' with attribute value: if attr_dict['background'] is not None: if background is not None and background != attr_dict['background']: log.info(msg.format('background', background, attr_dict['background'], class_name)) background = attr_dict['background'] # - Initializing EMPTY hidden attributes: # -- Integer defining the background label: self._background_id = None # -- List of cells: self._cells = None # -- List of epidermal cells (L1): self._epidermal_cells = None # -- Array with only the first layer of voxels in contact with the # background label: self._voxel_layer1 = None # - Initialise object property and most used hidden attributes: # -- Define the background value, if any (can be None): self.background = background # -- Get the list of cells found in the image: self.cell_ids()
[docs] def __str__(self): """Method called when printing the object.""" msg = "AbstractTissueImage object with following metadata:\n" md = self.metadata msg += '\n'.join([' - {}: {}'.format(k, v) for k, v in md.items()]) return msg
@property def background(self): """Get the background label, can be ``None``. Returns ------- int The label value for the background """ if self._background_id is None: log.warning("No value defined for the background id!") return self._background_id @background.setter def background(self, label): """Set the background label. Parameters ---------- label : int Integer defining the background id in the image. """ if not isinstance(label, int) and label is not None: log.error("Provided label '{}' is not an integer!".format(label)) return elif label not in self._labels: log.error("Provided label '{}' is not in the image!".format(label)) return else: self._background_id = label self.metadata = {'background': self.background}
[docs] def cell_ids(self, cells=None): """Get the list of cells found in the image, or filter given list of cell ids with those that exists. Parameters ---------- cells : int or list, optional If given, used to filter the returned list of ids. By default, return all cell ids defined in the tissue. Returns ------- list List of cells found in the image, except for *background* (if defined) Notes ----- Value defined for *background* is removed from the returned list of cells as it does not refer to one. Example ------- >>> import numpy as np >>> a = np.array([[1, 2, 7, 7, 1, 1], [1, 6, 5, 7, 3, 3], [2, 2, 1, 7, 3, 3], [1, 1, 1, 4, 1, 1]]) >>> from timagetk.components.tissue_image import AbstractTissueImage >>> im = AbstractTissueImage(a, background=1) >>> im.labels() [1, 2, 3, 4, 5, 6, 7] >>> im.cell_ids() [2, 3, 4, 5, 6, 7] """ return list(set(self.labels(cells)) - {self.background})
[docs] def nb_cells(self): """Return the number of cells found in the image. Example ------- >>> import numpy as np >>> a = np.array([[1, 2, 7, 7, 1, 1], [1, 6, 5, 7, 3, 3], [2, 2, 1, 7, 3, 3], [1, 1, 1, 4, 1, 1]]) >>> from timagetk.components.tissue_image import AbstractTissueImage >>> im = AbstractTissueImage(a, background=None) >>> im.nb_cells() 7 >>> im = AbstractTissueImage(a, background=1) >>> im.nb_cells() 6 """ return len(self.cell_ids())
[docs] def wall_ids(self, walls=None): """Get the list of cell-walls found in the image, or filter given list of cell-wall ids with those that exists. Parameters ---------- walls : tuple of int or list of tuple of int, optional If given, used to filter the returned list of ids. By default, return all wall ids defined in the tissue. Returns ------- list of tuples List of cell-walls found in the image, returned as tuples of cell ids defining them. Example ------- >>> import numpy as np >>> a = np.array([[1, 2, 7, 7, 1, 1], [1, 6, 5, 7, 3, 3], [2, 2, 1, 7, 3, 3], [1, 1, 1, 4, 1, 1]]) >>> from timagetk.components.tissue_image import AbstractTissueImage >>> im = AbstractTissueImage(a, background=1) >>> im.wall_ids() [(1, 2), (3, 7), (1, 3), (5, 7)] """ return self.surfels(surfel_ids=walls)
# -------------------------------------------------------------------------- # # ARRAY edition functions: # # --------------------------------------------------------------------------
[docs] def get_image_with_cells(self, cells, keep_background=True, erase_value=None): """Return an AbstractTissueImage with only the selected *cells*, the rest are replaced by "self._not_a_label". Parameters ---------- cells : int or list of int Cells or list of cells to keep in the tissue image. keep_background : bool, optional Indicate if background label should be kept in the returned image erase_value : int, optional The value to use to erase given `labels`. Default to ``self.not_a_label``. Returns ------- AbstractTissueImage Tissue image with the selected `cells`. """ try: assert self._not_a_label is not None except AssertionError: msg = "Attribute 'not_a_label' is not defined (None)!" raise ValueError(msg) all_cells = self.cell_ids() cells = self.cell_ids(cells) off_cells = list(set(all_cells) - set(cells)) back_id = self.background if keep_background: try: assert back_id is not None except AssertionError: msg = "You asked to keep the background position, but no background label is defined!" raise ValueError(msg) else: cells.append(back_id) else: if back_id: off_cells.append(back_id) if len(off_cells) == 0: log.warning("You selected ALL cells!") return self if len(cells) == 0: log.warning("You selected NO cell!") return None if len(cells) < len(off_cells): template_im = image_with_labels(self, cells, erase_value) else: template_im = image_without_labels(self, off_cells, erase_value) return AbstractTissueImage(template_im, background=back_id)
[docs] def get_image_without_cells(self, cells, keep_background=True): """Return a tissue image without the selected cells. Parameters ---------- cells : int or list Cells or list of cells to remove from the tissue image. keep_background : bool, optional Indicate if background should be kept in the returned tissue image Returns ------- AbstractTissueImage Tissue image without the selected `cells`. """ all_cells = self.cell_ids() cells = self.cell_ids(cells) off_cells = list(set(all_cells) - set(cells)) return self.get_image_with_cells(off_cells, keep_background)
[docs] def fuse_cells_in_image(self, cells, value='min'): """Fuse the provided list of cells to its minimal value. Parameters ---------- cells : list List of cells to fuse value : str, optional Value used to replace the given list of cells, by default use the Min value of the ``cells`` list. Can also be the max value. Returns ------- Nothing, modify the AbstractTissueImage array (re-instantiate the object) """ cells = self.cell_ids(cells) return self.fuse_labels_in_image(cells, value)
[docs] def remove_cells_from_image(self, cells): """Remove 'cells' from self.image using 'erase_value'. Parameters ---------- cells : list or str List of cells to remove from the image Returns ------- Nothing, modify the AbstractTissueImage array (re-instantiate the object) """ cells = self.cell_ids(cells) return self.remove_labels_from_image(cells)
[docs] def relabelling_cells_from_mapping(self, mapping, clear_unmapped=False): """Relabel the image following a given mapping indicating the original cell id as keys and their new id as value. Parameters ---------- mapping : dict A dictionary indicating the original cell id as keys and their new id as value clear_unmapped : bool, optional If ``True`` (default ``False``), only the mapped cells are kept in the returned image, the rest is set to 'not_a_label' Returns ------- Nothing, modify the LabelledImage array (re-instantiate the object) """ return relabel_from_mapping(self, mapping, clear_unmapped)
[docs] def voxel_n_first_layer(self, n_voxel_layer, connectivity, keep_background=True): """Extract the n-first layer of non-background voxels in contact with the background as a TissueImage. Parameters ---------- n_voxel_layer : int Number of layer of voxel from the background to get connectivity : int Connectivity or neighborhood of the structuring element keep_background : bool, optional If ``True`` the image returned contains the background in addition of the first layer of labelled voxels Returns ------- TissueImage Labelled image made of the selected number of voxel layers """ mask_img_1 = None if self._voxel_layer1 is None: self._voxel_layer1, mask_img_1 = voxel_n_layers(self, self.background, connectivity, iter=n_voxel_layer) if keep_background: if mask_img_1 is None: mask_img_1 = (self.get_array() == self.background) return self._voxel_layer1 + mask_img_1 else: return self._voxel_layer1
[docs] def voxel_first_layer(self, connectivity, keep_background=True): """Extract the first layer of non-background voxels in contact with the background as a TissueImage. Parameters ---------- connectivity : int Connectivity or neighborhood of the structuring element keep_background : bool, optional If ``True`` the image returned contains the background in addition of the first layer of labelled voxels Returns ------- TissueImage Labelled image made of the first layer of voxel in contact with the background """ mask_img_1 = None if self._voxel_layer1 is None: self._voxel_layer1, mask_img_1 = voxel_n_layers(self, background=self.background, connectivity=connectivity) if keep_background: if mask_img_1 is None: mask_img_1 = (self.get_array() == self.background) return self._voxel_layer1 + mask_img_1 else: return self._voxel_layer1
[docs] def voxel_first_layer_coordinates(self): """Return an (Nxd) array of coordinates indicating voxels first layer position.""" vfl = self.voxel_first_layer(keep_background=False) return np.array(np.where(vfl != 0)).T
[docs] def epidermal_cell_ids(self): """List epidermal cell, *i.e* cells in contact with the background. Returns ------- list List of epidermal cell, also knwon as L1-cells Example ------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk.components.tissue_image import AbstractTissueImage >>> tissue = AbstractTissueImage(imread(shared_data('sphere_membrane_0.0_seg.inr.gz', "sphere")), background=1, not_a_label=0) >>> tissue.epidermal_cell_ids() """ integers = lambda x: list(map(int, x)) try: assert self.background is not None except AssertionError: raise ValueError("No background id defined for the tissue image!") else: bkgd_id = self.background log.info("Generating list of epidermal cells...") # - Create unfiltered list of ALL neighbors to the background: background_nei = self.neighbors(bkgd_id)[bkgd_id] epidermal_cells = list(set(integers(background_nei))) return epidermal_cells
[docs] class TissueImage2D(AbstractTissueImage): """Class specific to 2D dense multicellular tissues."""
[docs] def __init__(self, image, background=None, **kwargs): """2D dense multicellular tissue constructor. Parameters ---------- image : numpy.ndarray or timagetk.SpatialImage or timagetk.LabelledImage A 2D labelled array defining a dense multicellular tissue. background : int, optional If given, define the id of the background, that is the "space" surrounding the tissue. """ AbstractTissueImage.__init__(self, image, background=background, **kwargs) self.cells = Cell2D(image) self.walls = Wall2D(image)
[docs] def __str__(self): """Method called when printing the object.""" msg = "TissueImage2D object with following attributes:\n" md = self.metadata msg += '\n'.join([' - {}: {}'.format(k, v) for k, v in md.items()]) return msg
[docs] def get_region(self, region): """Extract a region using list of start & stop indices. There should be two values per image dimension in 'indices'. Parameters ---------- region : list Indices as list of integers. Returns ------- TissueImage2D Output image. Raises ------ TypeError If the given `region` is not a list. ValueError If the number of `region` is wrong, should be twice the image dimensionality. If the `region` coordinates are not within the image boundaries. Example ------- >>> from timagetk import TissueImage2D >>> from timagetk.array_util import dummy_labelled_image_2D >>> img = TissueImage2D(dummy_labelled_image_2D([0.5, 0.5]), background=1) >>> region = [1, 5, 1, 5] >>> out_img = img.get_region(region) >>> isinstance(out_img, TissueImage2D) True >>> out_img TissueImage2D([[2, 4, 4, 4], [2, 2, 4, 4], [2, 2, 2, 4], [2, 2, 2, 3]], dtype=uint8) """ from timagetk.components.image import get_image_attributes attrs = get_image_attributes(self) return TissueImage2D(LabelledImage.get_region(self, region), **attrs)
[docs] def voxel_n_first_layer(self, n_voxel_layer, connectivity=4, keep_background=True, **kwargs): """Extract the n-first layer of non-background voxels in contact with the background. Parameters ---------- n_voxel_layer : int Number of layer of voxel from the background to get connectivity : int Connectivity of the 2D structuring element, default 4 keep_background : bool, optional If ``True`` the returned image contains the background in addition of the n-first layers of labelled voxels Returns ------- TissueImage2D Labelled image made of the selected number of voxel layers """ return AbstractTissueImage.voxel_n_first_layer(self, n_voxel_layer, connectivity=connectivity, keep_background=keep_background, **kwargs)
[docs] def voxel_first_layer(self, connectivity=4, keep_background=True, **kwargs): """Extract the first layer of non-background voxels in contact with the background. Parameters ---------- connectivity : int, optional Connectivity of the 2D structuring element, default 4 keep_background : bool, optional If ``True`` the returned image contains the background in addition of the first layers of labelled voxels Returns ------- TissueImage2D Image made of the first layer of voxel in contact with the background """ return AbstractTissueImage.voxel_first_layer(self, connectivity=connectivity, keep_background=keep_background, **kwargs)
[docs] def epidermal_cell_ids(self, min_length=None): """List epidermal cell, *i.e* cells in contact with the background. Parameters ---------- min_area : float, optional The minimum real contact length with the background necessary to be defined as epidermal cell. No minimum by default. TODO: It is possible to provide an epidermal length threshold (minimum length in contact with the background) to consider a cell as in the first layer. Returns ------- list List of epidermal cell, also knwon as L1-cells Example ------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_data >>> from timagetk import TissueImage3D >>> tissue = imread(shared_data('sphere_membrane_0.0_seg.inr.gz', "sphere"), TissueImage3D, background=1, not_a_label=0) >>> tissue.epidermal_cell_ids() """ integers = lambda x: list(map(int, x)) try: assert self.background is not None except AssertionError: raise ValueError("No background id defined for the tissue image!") else: bkgd_id = self.background log.info("Generating list of epidermal cells...") # - Create unfiltered list of ALL neighbors to the background: background_nei = self.neighbors(bkgd_id)[bkgd_id] if min_length is not None: contact_length = self.walls.length([(bkgd_id, bn) for bn in background_nei]) epidermal_cells = list( set(integers([cid for (_, cid), length in contact_length.items() if length >= min_length]))) else: epidermal_cells = list(set(integers(background_nei))) return epidermal_cells
[docs] class TissueImage3D(AbstractTissueImage): """Class specific to 3D dense multicellular tissues. Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk import TissueImage3D >>> tissue = TissueImage3D(imread(shared_dataset("p58", "intensity")[0]), background=1, not_a_label=0) >>> tissue.cell_ids() # Access the list of cells >>> tissue.nb_cells() # Access the number of cells >>> tissue.nb_labels() # Access the number of labels ( = `nb_cell` + 1 if background label is defined) >>> tissue.cells.area() >>> tissue.cells.area(1001) >>> tissue.walls.area() >>> tissue.walls.area((1001, 64)) >>> tissue.walls.area((64, 1001)) """
[docs] def __init__(self, image, background=None, **kwargs): """3D dense multicellular tissue constructor. Parameters ---------- image : numpy.ndarray or timagetk.SpatialImage or timagetk.LabelledImage A 3D labelled array defining a dense multicellular tissue background : int, optional If given, define the id of the background, that is the "space" surrounding the tissue. """ AbstractTissueImage.__init__(self, image, background=background, **kwargs) self.cells = Cell3D(self) self.walls = Wall3D(self) self.edges = Edges3D(self) self.vertices = Vertex3D(self)
[docs] def __str__(self): """Method called when printing the object.""" msg = "TissueImage3D object with following attributes:\n" md = self.metadata msg += '\n'.join([' - {}: {}'.format(k, v) for k, v in md.items()]) return msg
[docs] def get_slice(self, slice_id, axis='z'): """Return a slice of the tissue image. Parameters ---------- slice_id : int Slice to return. axis : int or str in {'x', 'y', 'z'}, optional Axis to use for slicing, default is 'z'. Returns ------- TissueImage2D 2D tissue image with only the required slice. Raises ------ ValueError If the image is not 3D and ``axis='z'``. If ``slice_id`` does not exist, *i.e.* should satisfy: ``0 < slice_id < max(len(axis))``. Examples -------- >>> from timagetk import TissueImage3D >>> from timagetk.array_util import dummy_labelled_image_3D >>> # Initialize a dummy (uint8) TissueImage3D with a ZYX shape of 5x13x12: >>> img = TissueImage3D(dummy_labelled_image_3D([1.0, 0.5, 0.5]), background=1) >>> print(img.background) 1 >>> zsl = img.get_slice(0) >>> print(type(zsl)) <class 'timagetk.components.tissue_image.TissueImage2D'> >>> print(zsl) TissueImage2D object with following attributes: - not_a_label: 0 - background: 1 - shape: (13, 12) - ndim: 2 - dtype: uint8 - origin: [0, 0] - voxelsize: [0.5, 0.5] - unit: 1e-06 - acquisition_date: None - extent: [6.0, 5.5] >>> # Taking an existing x-slice from a 3D image works fine: >>> img_x = img.get_slice(3, 'x') >>> print(img_x.axes_order_dict) {'Z': 0, 'Y': 1} """ from timagetk.components.image import get_image_attributes attrs = get_image_attributes(self) return TissueImage3D(LabelledImage.get_slice(self, slice_id, axis=axis), **attrs)
[docs] def get_region(self, region): """Extract a region using list of start & stop indexes. There should be two values per dimension in `region`, *e.g.* ``region=[5, 8, 5, 8]`` for a 2D image. If the image is 3D and, in one dimension the start and stop indexes only differ by one (one layer of voxels), the returned image will be transformed to 2D! Parameters ---------- region : list Indexes as list of integers, *e.g.* `[y-start, y-stop, x-start, x-stop]` for a 2D image. Returns ------- TissueImage3D Output image. Raises ------ TypeError If the given `region` is not a list. ValueError If the number of indexes in `region` is wrong, should be twice the image dimensionality. If the `region` coordinates are not within the array boundaries. Example ------- >>> from timagetk import TissueImage3D >>> from timagetk.array_util import dummy_labelled_image_3D >>> # Initialize a dummy (uint8) TissueImage3D with a ZYX shape of 5x13x12: >>> img = TissueImage3D(dummy_labelled_image_3D([1.0, 0.5, 0.5]), background=1) >>> region = [0, 2, 1, 5, 1, 5] # z-start, z-stop, y-start, y-stop, x-start, x-stop >>> out_img = img.get_region(region) >>> isinstance(out_img, TissueImage3D) True >>> out_img TissueImage3D([[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], [[2, 4, 4, 4], [2, 2, 4, 4], [2, 2, 2, 4], [2, 2, 2, 3]]], dtype=uint8) """ from timagetk.components.image import get_image_attributes attrs = get_image_attributes(self) img = LabelledImage.get_region(self, region) if img.is2D(): return TissueImage2D(img, **attrs) else: return TissueImage3D(img, **attrs)
[docs] def transpose(self, *axes): """Permute image axes to given order, reverse by default. Parameters ---------- axes : list of int or list of str, optional By default, reverse the dimensions, otherwise permute the axes according to the values given. Returns ------- TissueImage3D The image with permuted axes. Examples -------- >>> from timagetk import TissueImage3D >>> from timagetk.array_util import dummy_labelled_image_3D >>> # -- Transpose works with 3D images: >>> # Initialize a dummy (uint8) TissueImage3D with a ZYX shape of 5x13x12: >>> img = TissueImage3D(dummy_labelled_image_3D([1.0, 0.5, 0.5]), background=1) >>> img_t = img.transpose() >>> # Transpose update the shape attribute of the image (here reversed): >>> print(img_t.shape) (12, 13, 5) >>> # Transpose update the voxelsize attribute of the image (here reversed): >>> print(img_t.voxelsize) [0.5, 0.5, 1.0] >>> # Transpose update the metadata dictionary of the image: >>> print(img_t.metadata) >>> # -- Transpose accept axe names as input: >>> img_t = img.transpose('xyz') >>> print(img_t.shape) (12, 13, 5) >>> img_t = img.transpose('x', 'y', 'z') >>> print(img_t.shape) (12, 13, 5) >>> # -- Transpose works with 2D images: >>> from timagetk.array_util import dummy_labelled_image_2D >>> # Initialize a random (uint8) 2D SpatialImage with a YX shape of 13x12 (rows, columns): >>> img = TissueImage3D(dummy_labelled_image_2D([0.52, 0.53]), background=1) >>> img_t = img.transpose() >>> # Transpose update the shape attribute of the image (here reversed): >>> print(img.shape) (13, 12) >>> print(img_t.shape) (12, 13) """ from timagetk.components.image import get_image_attributes attrs = get_image_attributes(self) return TissueImage3D(LabelledImage.transpose(self, *axes), **attrs)
[docs] def invert_axis(self, axis): """Revert given axis. Parameters ---------- axis : {'x', 'y', 'z'} Axis to invert, can be either 'x', 'y' or 'z' (if 3D). Returns ------- LabelledImage Image with reverted array for selected axis. Raises ------ ValueError If given ``axis`` is not in {'x', 'y', 'z'} for 3D images or not in {'x', 'y'} for 2D images. Example ------- >>> from timagetk import TissueImage3D >>> from timagetk.array_util import dummy_labelled_image_3D >>> # Initialize a dummy (uint8) TissueImage3D with a ZYX shape of 5x13x12: >>> img = TissueImage3D(dummy_labelled_image_3D([1.0, 0.5, 0.5]), background=1) >>> print(img.get_slice(0, "z")) >>> inv_img = img.invert_axis(axis='z') >>> print(inv_img.get_slice(0, "z")) """ from timagetk.components.image import get_image_attributes attrs = get_image_attributes(self) return TissueImage3D(LabelledImage.invert_axis(self, axis), **attrs)
[docs] def voxel_n_first_layer(self, n_voxel_layer, connectivity=18, keep_background=True, **kwargs): """Extract the n-first layer of non-background voxels in contact with the background. Parameters ---------- n_voxel_layer : int Number of layer of voxel from the background to get connectivity : int Connectivity of the 3D structuring element, default 18 keep_background : bool, optional If ``True`` the returned image contains the background in addition of the n-first layers of labelled voxels Returns ------- TissueImage3D Image made of the selected number of voxel layers Example ------- >>> from timagetk import TissueImage3D >>> from timagetk.array_util import dummy_labelled_image_3D >>> # Initialize a dummy (uint8) TissueImage3D with a ZYX shape of 5x13x12: >>> img = TissueImage3D(dummy_labelled_image_3D([1.0, 0.5, 0.5]), background=1) >>> l1_img = img.voxel_n_first_layer(1, keep_background=False) >>> print(l1_img.get_array()) # only second XY-slice should be non-zero """ return AbstractTissueImage.voxel_n_first_layer(self, n_voxel_layer, connectivity=connectivity, keep_background=keep_background, **kwargs)
[docs] def voxel_first_layer(self, connectivity=18, keep_background=True, **kwargs): """Extract the first layer of non-background voxels in contact with the background. Parameters ---------- connectivity : int, optional Connectivity of the 3D structuring element, default 18 keep_background : bool, optional If ``True`` the returned image contains the background in addition of the first layers of labelled voxels Returns ------- TissueImage3D Image made of the first layer of voxel in contact with the background Example ------- >>> from timagetk import TissueImage3D >>> from timagetk.array_util import dummy_labelled_image_3D >>> # Initialize a dummy (uint8) TissueImage3D with a ZYX shape of 5x13x12: >>> img = TissueImage3D(dummy_labelled_image_3D([1.0, 0.5, 0.5]), background=1) >>> l1_img = img.voxel_n_first_layer(keep_background=False) >>> print(l1_img.get_array()) # only second XY-slice should be non-zero """ return AbstractTissueImage.voxel_first_layer(self, connectivity=connectivity, keep_background=keep_background, **kwargs)
[docs] def epidermal_cell_ids(self, min_area=None, real=True): """List epidermal cell, *i.e* cells in contact with the background. Parameters ---------- min_area : float, optional The minimum real contact area with the background necessary to be defined as epidermal cell. No minimum by default. real : bool, optional If ``True`` (default), the `min_area` value is in real world units, else in voxel units. Returns ------- list of int List of epidermal cells, also known as L1-cells Example ------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk import TissueImage3D >>> tissue = TissueImage3D(imread(shared_dataset("p58", "intensity")[0]), background=1, not_a_label=0) >>> ep_cid = tissue.epidermal_cell_ids() >>> # Take a look at the cell wall areas to estimate realistic threshold area: >>> ep_wall_area = tissue.walls.area([(tissue.background, cid) for cid in ep_cid]) >>> from matplotlib import pyplot as plt >>> plt.hist(list(ep_wall_area.values()), bins=100, range=(0, 100)) >>> plt.show() >>> # Filter epidermal cell walls with a threshold area of 5µm²: >>> tissue.epidermal_cell_ids(5.) """ integers = lambda x: list(map(int, x)) try: assert self.background is not None except AssertionError: raise ValueError("No background id defined for the tissue image!") else: bkgd_id = self.background log.info("Generating list of epidermal cells...") # - Create unfiltered list of ALL neighbors to the background: background_nei = self.cells.neighbors(bkgd_id, min_area=min_area, real=real)[bkgd_id] return list(set(integers(background_nei)))
[docs] def tissue_image(image, **kwargs): """Metaclass for 2D or 3D tissue image. Examples -------- >>> from timagetk.io import imread >>> from timagetk.io.util import shared_dataset >>> from timagetk.components.tissue_image import tissue_image >>> tissue = imread(shared_dataset("p58", "intensity")[0], rtype=tissue_image, background=1, not_a_label=0) >>> print(tissue) >>> tissue.is3D() """ # -- Check if the image is 2D if len(image.shape) == 2 or image.shape[2] == 1: return TissueImage2D(image, **kwargs) # -- Else it's considered as a 3D image. else: return TissueImage3D(image, **kwargs)