Image Class Methods#

Overview#

The Image class is the primary interface for image processing, analysis, and manipulation within the PhenoTypic framework. It combines data management, color space handling, object detection, file I/O, and visualization capabilities into a unified interface.

Image data can be provided as NumPy arrays (2-D grayscale or 3-D RGB/RGBA), another Image instance, or loaded from file. The class automatically manages format conversions and maintains internal consistency across multiple data representations.

Key capabilities:

  • Data Management: Grayscale, RGB, enhanced versions, object maps

  • Color Spaces: RGB, grayscale, HSV, CIE XYZ, CIE L*a*b*, xy chromaticity

  • Object Analysis: Detection results, masks, labels, measurements

  • File I/O: Load/save images with metadata management

  • Visualization: Display, overlays, plotting for pipeline development

Initialization & Creation#

Image.__init__(arr: np.ndarray | Image | None = None, name: str | None = None, bit_depth: Literal[8, 16] | None = None, gamma: GAMMA_ENCODINGS | str | None = GAMMA_ENCODINGS.SRGB, illuminant: Literal['D65', 'D50'] | None = 'D65')[source]#

Initialize an Image instance with optional image data and color properties.

Creates a new Image with complete initialization of all data management, color space, I/O, and object handling capabilities. The image can be initialized empty or with data from a NumPy array or another Image instance.

Parameters:
  • arr (np.ndarray | Image | None) –

    Optional image data. Can be:

    • A NumPy array of shape (height, width) for grayscale or (height, width, channels) for RGB/RGBA

    • An existing Image instance to copy from

    • None to create an empty image

    Defaults to None.

  • name (str | None) – Optional human-readable name for the image. If not provided, the image UUID will be used as the name. Defaults to None.

  • bit_depth (Literal[8, 16] | None) – The bit depth of the image data (8 or 16 bits). If not specified and arr is provided, bit depth is automatically inferred from the array dtype. Defaults to None.

  • gamma (GAMMA_ENCODINGS) – The gamma encoding used for color correction. GAMMA_ENCODINGS.SRGB: applies sRGB gamma correction (standard display gamma) GAMMA_ENCODINGS.LINEAR: assumes linear RGB data Defaults to GAMMA_ENCODINGS.SRGB.

  • illuminant (str | None) – The reference illuminant for color calculations. ‘D65’: standard daylight illuminant (recommended) ‘D50’: standard illumination for imaging Defaults to ‘D65’.

Raises:
  • ValueError – If gamma is not a GAMMA_ENCODINGS member.

  • ValueError – If illuminant is not ‘D65’ or ‘D50’.

  • TypeError – If arr is provided but is not a NumPy array or Image instance.

Examples

Create empty image:

>>> img = Image(name='empty_image')

Create from grayscale array:

>>> gray_arr = np.random.randint(0, 256, (480, 640), dtype=np.uint8)
>>> img = Image(gray_arr, name='grayscale_photo')

Create from RGB array:

>>> rgb_arr = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)
>>> from phenotypic.sdk\_.constants_ import GAMMA_ENCODINGS
>>> img = Image(rgb_arr, name='color_photo', gamma=GAMMA_ENCODINGS.SRGB)

Copy another image:

>>> img1 = Image.imread('original.jpg')
>>> img2 = Image(img1, name='copy_of_original')
Image.copy()#

Creates a copy of the current Image instance, excluding the UUID. .. note:

- The new instance is only informationally a copy. The UUID of the new
    instance is different.
Returns:

A copy of the current Image instance.

Return type:

Image

Data Access - Grayscale#

property Image.gray: Grayscale#

The image’s grayscale representation. The array form is converted into a gray form since some algorithm’s only handle 2-D

Note

  • gray elements are not directly mutable in order to preserve image information integrity

  • Change gray elements by changing the image being represented with Image.set_image()

Returns:

An immutable container for the image gray that can be accessed like a numpy array, but has extra methods to streamline development.

Return type:

Grayscale

from phenotypic import Image

image = Image(arr)

# get the gray data
arr = image.gray[:]
print(type(arr))

# set the gray data
# the shape of the new gray must be the same shape as the original gray
image.gray[:] = arr

# without the bracket indexing the accessor is returned instead
print(image.gray[:])

See Also: ImageMatrix

The gray property provides access to the grayscale representation of the image. For RGB images, the grayscale version is automatically computed on first access. For images created from grayscale arrays, this returns the original data.

Grayscale API:

class phenotypic._core._image_parts.accessors._grayscale_accessor.Grayscale(root_image: Image)[source]#

Bases: SingleChannelAccessor

Accessor for managing and visualizing grayscale image data.

This class provides an interface for interacting with the luminance-based grayscale representation of an image. It supports data access, modification, visualization through histograms, and overlay annotations. The accessor maintains immutability of data at the external interface level while allowing controlled modifications through dedicated methods.

The grayscale data is stored as a 2D floating-point array with normalized values in the range [0.0, 1.0].

Parameters:

root_image (Image)

_accessor_property_name#

Property name used to access this accessor from the Image object (set to “gray”).

Type:

str

Examples

Access and display grayscale data:

>>> img = Image("path/to/image.png")
>>> gray_array = img.gray[:]
>>> fig = img.gray.show()

Modify grayscale data with validation:

>>> img.gray[10:20, 10:20] = 0.5  # Set region to mid-gray
>>> img.gray[100, 100] = 0.8      # Set single pixel to light gray

Visualize histogram and overlay:

>>> fig, axes = img.gray.histogram()
>>> fig, ax = img.show(overlay=True, show_labels=True)
classmethod load(filepath: str | Path) numpy.ndarray#

Load an image array from file and verify it was saved from this accessor type.

Checks if the image contains PhenoTypic metadata indicating it was saved from the same accessor type (e.g., Image.gray, Image.rgb). If metadata doesn’t match or is missing, a warning is raised but the array is still loaded.

Parameters:

filepath (str | Path) – Path to the image file to load.

Returns:

The loaded image array.

Return type:

np.ndarray

Warns:

UserWarning – If metadata is missing or indicates the image was saved from a different accessor type.

Examples

Load a grayscale image from file:

>>> from phenotypic import Image
>>> image = Image(arr)
>>> # load an object map you saved or hand-graded
>>> image.objmap.load("path/to/map.png")
__array__(dtype=None, copy=None)#

Implements the array interface for numpy compatibility.

This allows numpy functions to operate directly on accessor objects. For example: np.sum(accessor), np.mean(accessor), etc.

Parameters:
  • dtype – Optional dtype to cast the array to

  • copy – Optional copy parameter for NumPy 2.0+ compatibility

Returns:

The underlying array data

Return type:

np.ndarray

__getitem__(key) numpy.ndarray[source]#

Retrieve a read-only view or slice of the grayscale image data.

Allows array-style indexing and slicing to access subsets of the grayscale data. The returned array is marked read-only to prevent accidental modifications. Use __setitem__ for intentional modifications.

Parameters:

key – Index or slice specification. Can be an integer for single-row access, tuple of slices for multi-dimensional slicing (e.g., [10:20, 5:15]), or boolean arrays for advanced indexing.

Returns:

A read-only view of the requested grayscale data. Values are

normalized floating-point numbers in the range [0.0, 1.0].

Return type:

np.ndarray

Raises:

EmptyImageError – If the underlying image data is empty (shape[0] == 0).

Examples

Access grayscale data with various indexing techniques:

>>> # Access entire grayscale array
>>> full_gray = img.gray[:]
>>> # Slice a region
>>> region = img.gray[10:20, 5:15]
>>> # Access single row
>>> row = img.gray[10]
>>> # Advanced indexing not recommended but supported
>>> mask = img.gray > 0.5
__len__() int#

Returns the length of the subject array.

This method calculates and returns the total number of elements contained in the underlying array.

Returns:

The number of elements in the underlying array attribute.

Return type:

int

__setitem__(key, value) None[source]#

Modify grayscale image data at specified indices with validation.

Allows assignment of new values to grayscale data at specified locations. All values must be normalized to the range [0.0, 1.0]. Modifications trigger automatic reset of dependent data structures (detection matrix and object map) to maintain consistency.

Parameters:
  • key – Index or slice specification. Same indexing schemes as __getitem__ (e.g., [10:20, 5:15], [100, 100], [:]).

  • value (np.ndarray | int | float) – New value(s) to assign. Can be: - A NumPy array with shape matching the indexed region. - A scalar (int or float) to broadcast to the indexed region. Must contain values in the range [0.0, 1.0].

Raises:
Return type:

None

Notes

  • All grayscale values must be normalized to [0.0, 1.0].

  • Modifications automatically reset detect_mat and objmap to prevent stale data.

  • For bulk operations, consider using direct array indexing on a copy.

Examples

Modify grayscale data with different assignment patterns:

>>> # Set a region to a specific value
>>> img.gray[10:20, 5:15] = 0.5
>>> # Set a single pixel
>>> img.gray[100, 100] = 0.8
>>> # Assign from another array
>>> region = np.random.rand(10, 10)
>>> img.gray[10:20, 5:15] = region
copy() numpy.ndarray#
Return type:

numpy.ndarray

dash(figsize: tuple[int, int] | None = None, title: str | None = None, cmap: str | None = 'gray', foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None, plotly_settings: dict | None = None) go.Figure#

Display the single-channel image data using Plotly.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, no title is displayed.

  • cmap (str | None) – Colormap name. Defaults to "gray".

  • foreground_only (bool) – If True, display only foreground elements.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

  • plotly_settings (dict | None) – Additional Plotly layout settings.

Returns:

A plotly.graph_objects.Figure.

Raises:

ImportError – If plotly is not installed.

Return type:

go.Figure

foreground()#

Extracts and returns the foreground of the image by masking out the background.

This method generates a foreground image by applying the object mask stored in the Image to the current array representation. Pixels outside the object mask are set to zero in the resulting foreground image. This is useful in image processing tasks to isolate the region of interest in the image, such as microbe colonies on an agar plate.

Returns:

A numpy array containing only the foreground portion of the image, with all non-foreground pixels set to zero.

Return type:

numpy.ndarray

histogram(figsize: Tuple[int, int] = (10, 5), *, cmap='gray', linewidth=1, channel_names: list | None = None) Tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Plots the histogram(s) of an image along with the image itself. The behavior depends on the dimensionality of the image array (2D or 3D). In the case of 2D, a single image and its histogram are produced. For 3D (multi-channel images), histograms for each channel are created alongside the image. This method supports customization such as figure size, colormap, line width of histograms, and labeling of channels.

Parameters:
  • figsize (Tuple[int, int]) – The size of the figure to create. Default is (10, 5).

  • cmap (str) – Colormap used to render the image when the data is single channel. Default is ‘gray’.

  • linewidth (int) – Line width of the plotted histograms. Default is 1.

  • channel_names (list | None) – Optional names for the channels in 3D data. These are used as titles for channel-specific histograms. If None, channels are instead labeled numerically.

Returns:

The Matplotlib figure and axes objects representing the plotted image and its histograms.

Return type:

Tuple[plt.Figure, plt.Axes]

Raises:

ValueError – If the dimensionality of the input image array is unsupported.

Notes

This method uses skimage.exposure.histogram for computing the histogram data.

imsave(filepath: str | Path | None = None, bit_depth: Literal[8, 16] | None = None) None#

Saves an array representing a microbe colony image to a specified file format while preserving or adjusting metadata and pixel depth as needed. Supports JPEG, PNG, and TIFF formats.

The behavior of the function is context-sensitive based on the file format’s restrictions and array properties. Proper file format selection and bit depth adjustment can have an impact on the accuracy of image analysis and preservation of data integrity.

Parameters:
  • filepath (str | Path | None) –

    The destination file path where the image will be saved. The extension of the file path determines the image format (e.g., .jpeg, .png, .tiff). Changing the file format influences how the image data is handled during saving:

    1. .jpeg: Compression or loss of data may occur. Maximal value limit (255) for uint8 pixel depth affects the fidelity of rich intensity details in microbe colonies.

    2. .png: Retains high-quality output but supports only 8-bit or 16-bit images. Conversions may occur if the array has a different data type, which could result in data loss.

    3. .tiff: Ideal for high-bit-depth precision and analysis preservation; best for maintaining intricate morphological details of microbial colonies.

  • bit_depth (Literal[8, 16] | None, optional) –

    Specifies the bit depth of the saved image (either 8-bit or 16-bit). The provided bit depth must align with the file format’s capabilities. Misalignment could trigger conversion with possible data truncation or rounding. For example:

    • 8-bit: Useful for efficiently representing intensity when detail is moderate, suitable for JPEG or simple PNG outputs.

    • 16-bit: Allows for higher intensity ranges, especially valuable for preserving subtle morphological gradient differentiation when analyzing colonies.

Raises:

ValueError – An error occurs when an unsupported file extension is provided in filepath.

Warns:

UserWarnings – Warnings are issued under the following conditions:

  • Saving a 16-bit or floating-point array as JPEG, as these conversions may cause information loss due to format restrictions.

  • Saving a floating-point array as PNG when conversions to 8-bit or 16-bit integers might lead to truncated or altered pixel intensity values.

Return type:

None

isempty()#
napari(name: str | None = None, reset: bool = False, *, viewer: napari.Viewer | None = None, layer_name: str | None = None) napari.Viewer#

Add image to a persistent global napari viewer for Jupyter workflows.

Creates or reuses a single napari viewer instance that persists across multiple method calls. This is particularly useful in Jupyter notebooks where multiple accessors can contribute layers to the same viewer, enabling interactive comparison of different image transformations (e.g., grayscale, RGB, binary masks) on the same data.

The viewer is automatically displayed in Jupyter environments and recreated if it has been closed externally.

Parameters:
  • name (str | None) – Optional custom name for the image layer. If provided, the layer will be named {accessor}_{name}. If not provided, defaults to using the image’s name attribute.

  • reset (bool) – If True, closes the current napari viewer and creates a fresh one. This is useful for starting a new visualization session without lingering layers from previous calls. Defaults to False.

  • viewer (napari.Viewer | None) – Optional external napari viewer instance to use instead of the global viewer. When provided, global viewer management (creation, reset, smart-grid installation) is bypassed entirely. Defaults to None.

  • layer_name (str | None) – Optional full layer name to use instead of the auto-generated {accessor}_{image_name} pattern. Defaults to None.

Returns:

The global napari viewer instance with the current

image added as a new layer.

Return type:

napari.Viewer

Raises:

ImportError – If napari is not installed. Install with pip install phenotypic[napari].

Examples

View multiple image transformations in one viewer:

>>> from phenotypic import Image
>>> img = Image(arr)
>>> # Add grayscale version to viewer
>>> viewer = img.gray.napari()
>>> # Add RGB version to same viewer
>>> viewer = img.rgb.napari()
>>> # Add binary segmentation with custom name
>>> viewer = img.objmask.napari(name="segmentation_v2")

Using custom names for comparison:

>>> viewer = img.gray.napari(name="raw_grayscale")
>>> viewer = img.objmask.napari(name="segmentation_v2")

Resetting the viewer for a fresh session:

>>> viewer = img.gray.napari()
>>> viewer = img.rgb.napari()  # Same viewer, added layer
>>> viewer = img.gray.napari(reset=True)  # Fresh viewer, old layers gone

Note

Layers are named using the pattern {accessor}_{image_name} to ensure descriptive identification. If a layer with the same name already exists, it is replaced with the new image data. This allows for easy updates and comparison of different processing stages.

save_overlay(filepath: str | Path, overlay_alpha: float = 0.3, bg_label: int = 0, colors: list | None = None, show_grid: bool = True, gridline_color: tuple[int, int, int] = (0, 255, 255), section_box_colors: list[tuple[int, int, int]] | None = None, **label2rgb_kwargs) None#

Save a full-resolution overlay image blending objmap with the subject array.

Creates an RGB overlay by blending the object map labels with the underlying image data and saves it to disk. Unlike show(overlay=True) which produces a matplotlib figure, this method saves the raw pixel data at full resolution, suitable for pixel-level quality validation of detection results.

For GridImage objects, gridlines and section boxes are automatically drawn when show_grid is True. The line widths scale dynamically with image size.

Parameters:
  • filepath (str | Path) – Destination file path. Should have .png or .jpeg extension.

  • overlay_alpha (float) – Alpha value for label overlay (0.0 = transparent, 1.0 = opaque). Defaults to 0.3.

  • bg_label (int) – Label value to treat as background. Defaults to 0.

  • colors (list | None) – List of RGB colors to use for labels. If None, uses default colormap.

  • show_grid (bool) – Whether to draw gridlines and section boxes on overlay for GridImage objects. Ignored for regular Image objects. Defaults to True.

  • gridline_color (tuple[int, int, int]) – RGB color tuple for gridlines. Defaults to cyan (0, 255, 255).

  • section_box_colors (list[tuple[int, int, int]] | None) – List of RGB tuples for cycling through section box colors. Defaults to tab20 colormap colors.

  • **label2rgb_kwargs – Additional keyword arguments for label2rgb.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

Examples

Save full-resolution overlay:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> image.rgb.save_overlay("overlay_rgb.png", overlay_alpha=0.4)
show(figsize: tuple[int, int] | None = None, title: str | None = None, cmap: str | None = 'gray', foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None) tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Display the single-channel image data using matplotlib.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, no title is displayed.

  • cmap (str | None) – Colormap name. Defaults to "gray".

  • foreground_only (bool) – If True, display only foreground elements.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

Returns:

A (plt.Figure, plt.Axes) tuple.

Return type:

tuple[TypeAliasForwardRef(‘matplotlib.figure.Figure’), TypeAliasForwardRef(‘matplotlib.axes.Axes’)]

val_range() pd.Interval#

Return the closed interval [min, max] of the subject array values.

Returns:

A single closed interval including both endpoints.

Return type:

pd.Interval

vmax() float[source]#

Returns the maximum grayscale value in the image. Since the grayscale is normalized to [0.0, 1.0], it returns 1.0.

Return type:

float

vmin() float[source]#

Returns the minimum grayscale value in the image. Since the grayscale is normalized to [0.0, 1.0], it returns 0.0.

Return type:

float

property dtype#
property nbytes: int#
property ndim: int#

Returns the number of dimensions of the underlying array.

The ndim property provides access to the dimensionality of the array being encapsulated in the object. This value corresponds to the number of axes or dimensions the underlying array possesses. It can be useful for understanding the structure of the contained data.

Returns:

The number of dimensions of the underlying array.

Return type:

int

property shape: Tuple[int, ...]#

Returns the shape of the current image data.

This method retrieves the dimensions of the array stored in the _main_arr attribute as a tuple, which indicates its size along each axis.

Returns:

A tuple representing the dimensions of the _main_arr attribute.

Return type:

Tuple[int, …]

property size: int#

Gets the size of the subject array.

This property retrieves the total number of elements in the subject array. It is read-only.

Returns:

The total number of elements in the subject array.

Return type:

int

Data Access - RGB#

property Image.rgb: ImageRGB#

Returns the ImageArray accessor; An image rgb represents the color information with a range either between [0-255] or [0-65536] depending on the bit-depth.

Note

  • rgb/gray element data is synced

  • change image shape by changing the image being represented with Image.set_image()

  • Raises an error if the arr image has no rgb form

Returns:

A class that can be accessed like a numpy rgb, but has extra methods to streamline development, or None if not set

Return type:

ImageRGB

Raises:

NoArrayError – If no multichannel image data is set as arr.

Example

Image.rgb:

>>> from phenotypic import Image
>>> from phenotypic.data import load_colony
>>> image = Image(load_colony())
>>> # get the rgb data
>>> arr = image.rgb[:]
>>> print(type(arr))
>>> # set the rgb data
>>> # the shape of the new rgb must be the same shape as the original rgb
>>> image.rgb[:] = arr
>>> # without the bracket indexing the accessor is returned instead
>>> print(image.rgb[:])

See Also: ImageArray

The rgb property provides access to the RGB/RGBA image data if available. For grayscale-only images, this property may be empty. The RGB accessor supports numpy-like indexing and slicing operations.

ImageRGB API:

class phenotypic._core._image_parts.accessors._rgb_accessor.ImageRGB(root_image: Image)[source]#

Bases: MultiChannelAccessor

Accessor for interacting with RGB multichannel image data.

ImageRGB provides a comprehensive interface for accessing, modifying, visualizing, and analyzing RGB image arrays. It acts as a bridge to the underlying RGB data stored in the parent Image object, exposing the data through intuitive indexing operations and utility methods.

This accessor supports advanced visualization capabilities including display of individual channels or composite images, overlays with segmentation maps, and channel-specific histograms. Users can seamlessly manipulate image arrays, explore geometric and structural attributes, and analyze segmented objects.

The class inherits from MultiChannelAccessor and extends functionality for 3-channel (RGB) images specifically. All visualization and analysis methods from the base class are available and can handle 3-channel color data.

_accessor_property_name#

Property name on Image that returns this accessor. Set to “rgb” to identify this as the RGB accessor.

Type:

str

Raises:
  • EmptyImageError – If attempting to access data when no image is loaded.

  • NoArrayError – If attempting to access RGB data when the image is 2D (no RGB array form exists).

Parameters:

root_image (Image)

Examples

Access RGB data from an Image object:

>>> rgb_accessor = image.rgb
>>> # Get a copy of the full RGB array
>>> rgb_data = rgb_accessor[:]
>>> # Modify a region
>>> rgb_accessor[10:20, 10:20] = [255, 0, 0]
>>> # Display the image
>>> fig = rgb_accessor.show()
classmethod load(filepath: str | Path) numpy.ndarray#

Load an image array from file and verify it was saved from this accessor type.

Checks if the image contains PhenoTypic metadata indicating it was saved from the same accessor type (e.g., Image.gray, Image.rgb). If metadata doesn’t match or is missing, a warning is raised but the array is still loaded.

Parameters:

filepath (str | Path) – Path to the image file to load.

Returns:

The loaded image array.

Return type:

np.ndarray

Warns:

UserWarning – If metadata is missing or indicates the image was saved from a different accessor type.

Examples

Load a grayscale image from file:

>>> from phenotypic import Image
>>> image = Image(arr)
>>> # load an object map you saved or hand-graded
>>> image.objmap.load("path/to/map.png")
__array__(dtype=None, copy=None)#

Implements the array interface for numpy compatibility.

This allows numpy functions to operate directly on accessor objects. For example: np.sum(accessor), np.mean(accessor), etc.

Parameters:
  • dtype – Optional dtype to cast the array to

  • copy – Optional copy parameter for NumPy 2.0+ compatibility

Returns:

The underlying array data

Return type:

np.ndarray

__getitem__(key) ndarray[source]#

Return a read-only view of the RGB subregion specified by the given key.

This method supports NumPy-style indexing and slicing to extract subregions from the RGB image array. The returned array is read-only to prevent accidental modifications outside of the __setitem__ interface.

Parameters:

key – Index or slice specifying the subregion to extract. Supports standard NumPy indexing (e.g., integer indices, slices, boolean masks, advanced indexing). For 3D RGB data, can use notation like [:, :, channel] to select specific channels.

Returns:

A read-only NumPy array containing the extracted subregion

with shape matching the selected region.

Return type:

np.ndarray

Raises:
  • EmptyImageError – If the image contains no RGB data and no grayscale fallback is available.

  • NoArrayError – If the image is 2D (grayscale only) and no RGB array form exists.

Examples

Extract the full RGB array:

>>> full_rgb = image.rgb[:]

Extract a rectangular region:

>>> region = image.rgb[10:50, 20:60, :]

Extract a specific channel:

>>> red_channel = image.rgb[:, :, 0]
__len__() int#

Returns the length of the subject array.

This method calculates and returns the total number of elements contained in the underlying array.

Returns:

The number of elements in the underlying array attribute.

Return type:

int

__setitem__(key, value)[source]#

Modify a subregion of the RGB image array.

This method allows in-place modification of the underlying RGB image data using NumPy-style indexing. The provided value must either be a numeric scalar or a NumPy array with shape matching the indexed subregion.

Parameters:
  • key – Index or slice specifying the subregion to modify. Supports standard NumPy indexing (e.g., integer indices, slices, boolean masks, advanced indexing).

  • value (int | float | np.number | np.ndarray) – The new value(s) to assign. Can be a numeric scalar (int, float, or np.number) which will be broadcast to all elements in the indexed region, or a NumPy array with dtype compatible with the image array. If an array, its shape must exactly match the shape of the indexed subregion.

Raises:
  • TypeError – If value is a scalar that is not numeric (int, float, or np.number).

  • TypeError – If value is an array with non-numeric dtype.

  • TypeError – If value is neither a scalar nor a numpy array.

  • ArrayKeyValueShapeMismatchError – If value is an array and its shape does not match the shape of the indexed subregion.

Note

To replace the entire RGB image data, use Image.set_image() instead of this method. This method only modifies specific regions of the existing image array.

After modification, the parent image is updated to reflect changes made through this accessor.

Examples

Set a region to a solid color:

>>> image.rgb[10:20, 10:20] = [255, 128, 64]

Set a region from another array:

>>> patch = np.ones((10, 10, 3), dtype=np.uint8) * 128
>>> image.rgb[10:20, 10:20] = patch

Modify a single channel:

>>> image.rgb[:, :, 0] = 255  # Set red channel to maximum
copy() numpy.ndarray#
Return type:

numpy.ndarray

dash(figsize: tuple[int, int] | None = None, title: str | None = None, channel: int | None = None, foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None, plotly_settings: dict | None = None) go.Figure#

Display the multichannel image data using Plotly.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, a default title is generated based on the image and channel.

  • channel (int | None) – Specific channel index to plot. If None, all channels are displayed as RGB.

  • foreground_only (bool) – If True, only foreground is displayed.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

  • plotly_settings (dict | None) – Additional Plotly layout settings.

Returns:

A plotly.graph_objects.Figure.

Raises:

ImportError – If plotly is not installed.

Return type:

go.Figure

foreground()#

Extracts and returns the foreground of the image by masking out the background.

This method generates a foreground image by applying the object mask stored in the Image to the current array representation. Pixels outside the object mask are set to zero in the resulting foreground image. This is useful in image processing tasks to isolate the region of interest in the image, such as microbe colonies on an agar plate.

Returns:

A numpy array containing only the foreground portion of the image, with all non-foreground pixels set to zero.

Return type:

numpy.ndarray

histogram(figsize: Tuple[int, int] = (10, 5), *, cmap='gray', linewidth=1, channel_names: list | None = None) Tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Plots the histogram(s) of an image along with the image itself. The behavior depends on the dimensionality of the image array (2D or 3D). In the case of 2D, a single image and its histogram are produced. For 3D (multi-channel images), histograms for each channel are created alongside the image. This method supports customization such as figure size, colormap, line width of histograms, and labeling of channels.

Parameters:
  • figsize (Tuple[int, int]) – The size of the figure to create. Default is (10, 5).

  • cmap (str) – Colormap used to render the image when the data is single channel. Default is ‘gray’.

  • linewidth (int) – Line width of the plotted histograms. Default is 1.

  • channel_names (list | None) – Optional names for the channels in 3D data. These are used as titles for channel-specific histograms. If None, channels are instead labeled numerically.

Returns:

The Matplotlib figure and axes objects representing the plotted image and its histograms.

Return type:

Tuple[plt.Figure, plt.Axes]

Raises:

ValueError – If the dimensionality of the input image array is unsupported.

Notes

This method uses skimage.exposure.histogram for computing the histogram data.

imsave(filepath: str | Path, bit_depth: Literal[8, 16] | None = None) None#

Save the multichannel image array to a file with PhenoTypic metadata embedded.

Metadata is embedded in format-specific locations: - JPEG: EXIF UserComment tag - PNG: tEXt chunk with key ‘phenotypic’ - TIFF: ImageDescription tag (270)

Parameters:
  • filepath (str | Path) – Path to save the image file. Extension determines format.

  • bit_depth (Literal[8, 16] | None) – Target bit depth (8 or 16). If None, uses image’s bit depth.

Raises:

AttributeError – If bit depth is not 8 or 16.

Return type:

None

isempty()#
napari(name: str | None = None, reset: bool = False, *, viewer: napari.Viewer | None = None, layer_name: str | None = None) napari.Viewer#

Add image to a persistent global napari viewer for Jupyter workflows.

Creates or reuses a single napari viewer instance that persists across multiple method calls. This is particularly useful in Jupyter notebooks where multiple accessors can contribute layers to the same viewer, enabling interactive comparison of different image transformations (e.g., grayscale, RGB, binary masks) on the same data.

The viewer is automatically displayed in Jupyter environments and recreated if it has been closed externally.

Parameters:
  • name (str | None) – Optional custom name for the image layer. If provided, the layer will be named {accessor}_{name}. If not provided, defaults to using the image’s name attribute.

  • reset (bool) – If True, closes the current napari viewer and creates a fresh one. This is useful for starting a new visualization session without lingering layers from previous calls. Defaults to False.

  • viewer (napari.Viewer | None) – Optional external napari viewer instance to use instead of the global viewer. When provided, global viewer management (creation, reset, smart-grid installation) is bypassed entirely. Defaults to None.

  • layer_name (str | None) – Optional full layer name to use instead of the auto-generated {accessor}_{image_name} pattern. Defaults to None.

Returns:

The global napari viewer instance with the current

image added as a new layer.

Return type:

napari.Viewer

Raises:

ImportError – If napari is not installed. Install with pip install phenotypic[napari].

Examples

View multiple image transformations in one viewer:

>>> from phenotypic import Image
>>> img = Image(arr)
>>> # Add grayscale version to viewer
>>> viewer = img.gray.napari()
>>> # Add RGB version to same viewer
>>> viewer = img.rgb.napari()
>>> # Add binary segmentation with custom name
>>> viewer = img.objmask.napari(name="segmentation_v2")

Using custom names for comparison:

>>> viewer = img.gray.napari(name="raw_grayscale")
>>> viewer = img.objmask.napari(name="segmentation_v2")

Resetting the viewer for a fresh session:

>>> viewer = img.gray.napari()
>>> viewer = img.rgb.napari()  # Same viewer, added layer
>>> viewer = img.gray.napari(reset=True)  # Fresh viewer, old layers gone

Note

Layers are named using the pattern {accessor}_{image_name} to ensure descriptive identification. If a layer with the same name already exists, it is replaced with the new image data. This allows for easy updates and comparison of different processing stages.

normed() ndarray[source]#

Return a copy of the RGB image array normalized between 0 and 1.

Return type:

ndarray

save_overlay(filepath: str | Path, overlay_alpha: float = 0.3, bg_label: int = 0, colors: list | None = None, show_grid: bool = True, gridline_color: tuple[int, int, int] = (0, 255, 255), section_box_colors: list[tuple[int, int, int]] | None = None, **label2rgb_kwargs) None#

Save a full-resolution overlay image blending objmap with the subject array.

Creates an RGB overlay by blending the object map labels with the underlying image data and saves it to disk. Unlike show(overlay=True) which produces a matplotlib figure, this method saves the raw pixel data at full resolution, suitable for pixel-level quality validation of detection results.

For GridImage objects, gridlines and section boxes are automatically drawn when show_grid is True. The line widths scale dynamically with image size.

Parameters:
  • filepath (str | Path) – Destination file path. Should have .png or .jpeg extension.

  • overlay_alpha (float) – Alpha value for label overlay (0.0 = transparent, 1.0 = opaque). Defaults to 0.3.

  • bg_label (int) – Label value to treat as background. Defaults to 0.

  • colors (list | None) – List of RGB colors to use for labels. If None, uses default colormap.

  • show_grid (bool) – Whether to draw gridlines and section boxes on overlay for GridImage objects. Ignored for regular Image objects. Defaults to True.

  • gridline_color (tuple[int, int, int]) – RGB color tuple for gridlines. Defaults to cyan (0, 255, 255).

  • section_box_colors (list[tuple[int, int, int]] | None) – List of RGB tuples for cycling through section box colors. Defaults to tab20 colormap colors.

  • **label2rgb_kwargs – Additional keyword arguments for label2rgb.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

Examples

Save full-resolution overlay:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> image.rgb.save_overlay("overlay_rgb.png", overlay_alpha=0.4)
show(figsize: tuple[int, int] | None = None, title: str | None = None, channel: int | None = None, foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None) tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Display the multichannel image data using matplotlib.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, a default title is generated based on the image and channel.

  • channel (int | None) – Specific channel index to plot. If None, all channels are displayed as RGB.

  • foreground_only (bool) – If True, only foreground is displayed.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

Returns:

A (plt.Figure, plt.Axes) tuple.

Return type:

tuple[TypeAliasForwardRef(‘matplotlib.figure.Figure’), TypeAliasForwardRef(‘matplotlib.axes.Axes’)]

val_range() pd.Interval#

Return the closed interval [min, max] of the subject array values.

Returns:

A single closed interval including both endpoints.

Return type:

pd.Interval

vmax() int[source]#

Returns the maximum value in the RGB image array. The max value depends on the image bit depth. 8 bit-depth->255 and 16 bit-depth->65536

Return type:

int

vmin() int[source]#

Returns the minimum value in the RGB image array. For RGB arrays, this is 0

Return type:

int

property dtype#
property nbytes: int#
property ndim: int#

Returns the number of dimensions of the underlying array.

The ndim property provides access to the dimensionality of the array being encapsulated in the object. This value corresponds to the number of axes or dimensions the underlying array possesses. It can be useful for understanding the structure of the contained data.

Returns:

The number of dimensions of the underlying array.

Return type:

int

property shape: Tuple[int, ...]#

Returns the shape of the current image data.

This method retrieves the dimensions of the array stored in the _main_arr attribute as a tuple, which indicates its size along each axis.

Returns:

A tuple representing the dimensions of the _main_arr attribute.

Return type:

Tuple[int, …]

property size: int#

Gets the size of the subject array.

This property retrieves the total number of elements in the subject array. It is read-only.

Returns:

The total number of elements in the subject array.

Return type:

int

Data Access - Enhanced#

property Image.detect_mat: DetectMatAccessor#

Returns the image’s detection matrix accessor. Preprocessing steps can be applied to this component to improve detection performance.

The detection matrix is derived from the image’s grayscale (default) or a single RGB channel, controlled by detect_mode. It can be modified and used to improve detection performance while the original gray data is left intact to preserve image information integrity for measurements.

Returns:

A mutable container that stores a copy of the source channel.

Return type:

DetectMatAccessor

from phenotypic import Image
from phenotypic.data import load_colony

image = Image(load_colony())

# get the detect_mat data
arr = image.detect_mat[:]
print(type(arr))

# set the detect_mat data
# the shape must match the original
image.detect_mat[:] = arr

# without the bracket indexing the accessor is returned instead
print(image.detect_mat[:])

The detect_mat property provides access to an enhanceable copy of the grayscale image. This is designed for processing pipelines where enhancement operations should be applied without modifying the original grayscale data. Changes to detection matrix are tracked separately from the base image.

DetectMatAccessor API:

class phenotypic._core._image_parts.accessors._detect_mat_accessor.DetectMatAccessor(root_image: Image)[source]#

Bases: SingleChannelAccessor

Provides the detection matrix channel accessor for image data.

The DetectMatAccessor class represents an accessor for managing and manipulating the detection matrix for an entire image or specific regions. This class extends SingleChannelAccessor to include special behaviors and restrictions when working with a normalized representation of microbial colony images on solid media agar. Detection matrix values are typically normalized to range between 0.0 and 1.0.

The detection matrix source channel is controlled by the image’s detect_mode setting ('gray', 'red', 'green', or 'blue').

This accessor facilitates retrieving and modifying detection matrix values for both visualization and computational purposes while enforcing integrity checks to avoid data inconsistency.

Parameters:

root_image (Image)

classmethod load(filepath: str | Path) numpy.ndarray#

Load an image array from file and verify it was saved from this accessor type.

Checks if the image contains PhenoTypic metadata indicating it was saved from the same accessor type (e.g., Image.gray, Image.rgb). If metadata doesn’t match or is missing, a warning is raised but the array is still loaded.

Parameters:

filepath (str | Path) – Path to the image file to load.

Returns:

The loaded image array.

Return type:

np.ndarray

Warns:

UserWarning – If metadata is missing or indicates the image was saved from a different accessor type.

Examples

Load a grayscale image from file:

>>> from phenotypic import Image
>>> image = Image(arr)
>>> # load an object map you saved or hand-graded
>>> image.objmap.load("path/to/map.png")
__array__(dtype=None, copy=None)#

Implements the array interface for numpy compatibility.

This allows numpy functions to operate directly on accessor objects. For example: np.sum(accessor), np.mean(accessor), etc.

Parameters:
  • dtype – Optional dtype to cast the array to

  • copy – Optional copy parameter for NumPy 2.0+ compatibility

Returns:

The underlying array data

Return type:

np.ndarray

__getitem__(key) numpy.ndarray[source]#

Return a non-writeable view of the detection matrix data for the given index.

Retrieves a portion of the detection matrix array using standard NumPy indexing. The returned view is read-only to prevent unintended modifications outside of the proper __setitem__ interface.

Parameters:

key – Array indexing expression (integer, slice, tuple of indices, or boolean mask). Follows standard NumPy indexing conventions.

Returns:

A non-writeable view of the detection matrix data at the specified

index. The returned array shares memory with the underlying data but cannot be modified.

Return type:

np.ndarray

Raises:

EmptyImageError – If the image has no data loaded (empty shape).

Examples

Retrieve a single pixel value:

>>> pixel_value = detect_mat[100, 200]

Retrieve a rectangular region:

>>> region = detect_mat[100:200, 50:150]
__len__() int#

Returns the length of the subject array.

This method calculates and returns the total number of elements contained in the underlying array.

Returns:

The number of elements in the underlying array attribute.

Return type:

int

__setitem__(key, value)[source]#

Set detection matrix data at the specified index with validation.

Sets data in the detection matrix array at the specified location. The method validates that the input is either a scalar numeric value (int or float) or a NumPy array with a shape matching the indexed region. After successful assignment, the parent image’s object map is reset to maintain consistency with the modified detection matrix data.

Parameters:
  • key – Array indexing expression (integer, slice, tuple of indices, or boolean mask). Follows standard NumPy indexing conventions and must be compatible with the detection matrix array structure.

  • value (int | float | np.ndarray) – The value(s) to assign. Can be: - A scalar (int or float) that will be broadcast to all indexed elements - A NumPy array whose shape must exactly match the indexed region’s shape

Raises:
  • ArrayKeyValueShapeMismatchError – If value is a NumPy array and its shape does not match the shape of the indexed region.

  • TypeError – If value is neither a scalar (int or float) nor a NumPy array.

Examples

Set a single pixel to a scalar value:

>>> detect_mat[100, 200] = 128

Set a rectangular region with an array:

>>> region_data = np.ones((100, 100), dtype=np.uint8) * 150
>>> detect_mat[100:200, 50:150] = region_data

Broadcast a scalar to a region:

>>> detect_mat[0:50, 0:50] = 255  # Set all pixels in region to 255
copy() numpy.ndarray#
Return type:

numpy.ndarray

dash(figsize: tuple[int, int] | None = None, title: str | None = None, cmap: str | None = 'gray', foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None, plotly_settings: dict | None = None) go.Figure#

Display the single-channel image data using Plotly.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, no title is displayed.

  • cmap (str | None) – Colormap name. Defaults to "gray".

  • foreground_only (bool) – If True, display only foreground elements.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

  • plotly_settings (dict | None) – Additional Plotly layout settings.

Returns:

A plotly.graph_objects.Figure.

Raises:

ImportError – If plotly is not installed.

Return type:

go.Figure

foreground()#

Extracts and returns the foreground of the image by masking out the background.

This method generates a foreground image by applying the object mask stored in the Image to the current array representation. Pixels outside the object mask are set to zero in the resulting foreground image. This is useful in image processing tasks to isolate the region of interest in the image, such as microbe colonies on an agar plate.

Returns:

A numpy array containing only the foreground portion of the image, with all non-foreground pixels set to zero.

Return type:

numpy.ndarray

histogram(figsize: Tuple[int, int] = (10, 5), *, cmap='gray', linewidth=1, channel_names: list | None = None) Tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Plots the histogram(s) of an image along with the image itself. The behavior depends on the dimensionality of the image array (2D or 3D). In the case of 2D, a single image and its histogram are produced. For 3D (multi-channel images), histograms for each channel are created alongside the image. This method supports customization such as figure size, colormap, line width of histograms, and labeling of channels.

Parameters:
  • figsize (Tuple[int, int]) – The size of the figure to create. Default is (10, 5).

  • cmap (str) – Colormap used to render the image when the data is single channel. Default is ‘gray’.

  • linewidth (int) – Line width of the plotted histograms. Default is 1.

  • channel_names (list | None) – Optional names for the channels in 3D data. These are used as titles for channel-specific histograms. If None, channels are instead labeled numerically.

Returns:

The Matplotlib figure and axes objects representing the plotted image and its histograms.

Return type:

Tuple[plt.Figure, plt.Axes]

Raises:

ValueError – If the dimensionality of the input image array is unsupported.

Notes

This method uses skimage.exposure.histogram for computing the histogram data.

imsave(filepath: str | Path | None = None, bit_depth: Literal[8, 16] | None = None) None#

Saves an array representing a microbe colony image to a specified file format while preserving or adjusting metadata and pixel depth as needed. Supports JPEG, PNG, and TIFF formats.

The behavior of the function is context-sensitive based on the file format’s restrictions and array properties. Proper file format selection and bit depth adjustment can have an impact on the accuracy of image analysis and preservation of data integrity.

Parameters:
  • filepath (str | Path | None) –

    The destination file path where the image will be saved. The extension of the file path determines the image format (e.g., .jpeg, .png, .tiff). Changing the file format influences how the image data is handled during saving:

    1. .jpeg: Compression or loss of data may occur. Maximal value limit (255) for uint8 pixel depth affects the fidelity of rich intensity details in microbe colonies.

    2. .png: Retains high-quality output but supports only 8-bit or 16-bit images. Conversions may occur if the array has a different data type, which could result in data loss.

    3. .tiff: Ideal for high-bit-depth precision and analysis preservation; best for maintaining intricate morphological details of microbial colonies.

  • bit_depth (Literal[8, 16] | None, optional) –

    Specifies the bit depth of the saved image (either 8-bit or 16-bit). The provided bit depth must align with the file format’s capabilities. Misalignment could trigger conversion with possible data truncation or rounding. For example:

    • 8-bit: Useful for efficiently representing intensity when detail is moderate, suitable for JPEG or simple PNG outputs.

    • 16-bit: Allows for higher intensity ranges, especially valuable for preserving subtle morphological gradient differentiation when analyzing colonies.

Raises:

ValueError – An error occurs when an unsupported file extension is provided in filepath.

Warns:

UserWarnings – Warnings are issued under the following conditions:

  • Saving a 16-bit or floating-point array as JPEG, as these conversions may cause information loss due to format restrictions.

  • Saving a floating-point array as PNG when conversions to 8-bit or 16-bit integers might lead to truncated or altered pixel intensity values.

Return type:

None

isempty()#
napari(name: str | None = None, reset: bool = False, *, viewer: napari.Viewer | None = None, layer_name: str | None = None) napari.Viewer#

Add image to a persistent global napari viewer for Jupyter workflows.

Creates or reuses a single napari viewer instance that persists across multiple method calls. This is particularly useful in Jupyter notebooks where multiple accessors can contribute layers to the same viewer, enabling interactive comparison of different image transformations (e.g., grayscale, RGB, binary masks) on the same data.

The viewer is automatically displayed in Jupyter environments and recreated if it has been closed externally.

Parameters:
  • name (str | None) – Optional custom name for the image layer. If provided, the layer will be named {accessor}_{name}. If not provided, defaults to using the image’s name attribute.

  • reset (bool) – If True, closes the current napari viewer and creates a fresh one. This is useful for starting a new visualization session without lingering layers from previous calls. Defaults to False.

  • viewer (napari.Viewer | None) – Optional external napari viewer instance to use instead of the global viewer. When provided, global viewer management (creation, reset, smart-grid installation) is bypassed entirely. Defaults to None.

  • layer_name (str | None) – Optional full layer name to use instead of the auto-generated {accessor}_{image_name} pattern. Defaults to None.

Returns:

The global napari viewer instance with the current

image added as a new layer.

Return type:

napari.Viewer

Raises:

ImportError – If napari is not installed. Install with pip install phenotypic[napari].

Examples

View multiple image transformations in one viewer:

>>> from phenotypic import Image
>>> img = Image(arr)
>>> # Add grayscale version to viewer
>>> viewer = img.gray.napari()
>>> # Add RGB version to same viewer
>>> viewer = img.rgb.napari()
>>> # Add binary segmentation with custom name
>>> viewer = img.objmask.napari(name="segmentation_v2")

Using custom names for comparison:

>>> viewer = img.gray.napari(name="raw_grayscale")
>>> viewer = img.objmask.napari(name="segmentation_v2")

Resetting the viewer for a fresh session:

>>> viewer = img.gray.napari()
>>> viewer = img.rgb.napari()  # Same viewer, added layer
>>> viewer = img.gray.napari(reset=True)  # Fresh viewer, old layers gone

Note

Layers are named using the pattern {accessor}_{image_name} to ensure descriptive identification. If a layer with the same name already exists, it is replaced with the new image data. This allows for easy updates and comparison of different processing stages.

preview_modes(reset: bool = False) napari.Viewer[source]#

Open a napari viewer with all registered detection mode matrices.

Computes every registered detection mode and adds each as a separate image layer in a dedicated napari viewer. The current (possibly enhanced) detection matrix is also included. Toggle layer visibility in napari’s layer list to compare modes.

This viewer is independent of the main image.gray.napari() viewer.

Parameters:

reset (bool) – If True, closes the existing preview viewer and creates a fresh one. Defaults to False.

Returns:

A viewer with one layer per detection mode plus

the current detection matrix.

Return type:

napari.Viewer

Examples

Compare all detection modes interactively:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> viewer = image.detect_mat.preview_modes()

Reset and re-preview after applying enhancers:

>>> viewer = image.detect_mat.preview_modes(reset=True)
reset()[source]#

Reset the detection matrix to a fresh copy of the current mode’s source channel.

Discards all modifications made to the detection matrix and restores it from the source determined by the image’s detect_mode setting.

Examples

Reset after applying unsuccessful enhancement:

>>> detect_mat.reset()  # Revert to source channel
save_overlay(filepath: str | Path, overlay_alpha: float = 0.3, bg_label: int = 0, colors: list | None = None, show_grid: bool = True, gridline_color: tuple[int, int, int] = (0, 255, 255), section_box_colors: list[tuple[int, int, int]] | None = None, **label2rgb_kwargs) None#

Save a full-resolution overlay image blending objmap with the subject array.

Creates an RGB overlay by blending the object map labels with the underlying image data and saves it to disk. Unlike show(overlay=True) which produces a matplotlib figure, this method saves the raw pixel data at full resolution, suitable for pixel-level quality validation of detection results.

For GridImage objects, gridlines and section boxes are automatically drawn when show_grid is True. The line widths scale dynamically with image size.

Parameters:
  • filepath (str | Path) – Destination file path. Should have .png or .jpeg extension.

  • overlay_alpha (float) – Alpha value for label overlay (0.0 = transparent, 1.0 = opaque). Defaults to 0.3.

  • bg_label (int) – Label value to treat as background. Defaults to 0.

  • colors (list | None) – List of RGB colors to use for labels. If None, uses default colormap.

  • show_grid (bool) – Whether to draw gridlines and section boxes on overlay for GridImage objects. Ignored for regular Image objects. Defaults to True.

  • gridline_color (tuple[int, int, int]) – RGB color tuple for gridlines. Defaults to cyan (0, 255, 255).

  • section_box_colors (list[tuple[int, int, int]] | None) – List of RGB tuples for cycling through section box colors. Defaults to tab20 colormap colors.

  • **label2rgb_kwargs – Additional keyword arguments for label2rgb.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

Examples

Save full-resolution overlay:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> image.rgb.save_overlay("overlay_rgb.png", overlay_alpha=0.4)
show(figsize: tuple[int, int] | None = None, title: str | None = None, cmap: str | None = 'gray', foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None) tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Display the single-channel image data using matplotlib.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, no title is displayed.

  • cmap (str | None) – Colormap name. Defaults to "gray".

  • foreground_only (bool) – If True, display only foreground elements.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

Returns:

A (plt.Figure, plt.Axes) tuple.

Return type:

tuple[TypeAliasForwardRef(‘matplotlib.figure.Figure’), TypeAliasForwardRef(‘matplotlib.axes.Axes’)]

val_range() pd.Interval#

Return the closed interval [min, max] of the subject array values.

Returns:

A single closed interval including both endpoints.

Return type:

pd.Interval

vmax() float[source]#

Returns the maximum value in the detection matrix. Since it is normalized to [0.0, 1.0], it returns 1.0.

Return type:

float

vmin() float[source]#

Returns the minimum value in the detection matrix. Since it is normalized to [0.0, 1.0], it returns 0.0.

Return type:

float

property dtype#
property nbytes: int#
property ndim: int#

Returns the number of dimensions of the underlying array.

The ndim property provides access to the dimensionality of the array being encapsulated in the object. This value corresponds to the number of axes or dimensions the underlying array possesses. It can be useful for understanding the structure of the contained data.

Returns:

The number of dimensions of the underlying array.

Return type:

int

property shape: Tuple[int, ...]#

Returns the shape of the current image data.

This method retrieves the dimensions of the array stored in the _main_arr attribute as a tuple, which indicates its size along each axis.

Returns:

A tuple representing the dimensions of the _main_arr attribute.

Return type:

Tuple[int, …]

property size: int#

Gets the size of the subject array.

This property retrieves the total number of elements in the subject array. It is read-only.

Returns:

The total number of elements in the subject array.

Return type:

int

Data Access - Objects (Detection Results)#

property Image.objects: ObjectsAccessor#

Accessor for performing operations on detected objects in the image.

Provides access to individual or grouped objects detected in the image, enabling measurement calculations, filtering, and object-specific analyses. Objects are identified through the object map (objmap) component which stores integer labels for each detected object.

Returns:

An accessor instance that manages object-specific operations

and measurements.

Return type:

ObjectsAccessor

Raises:

NoObjectsError – If no objects are present in the image. This occurs when num_objects == 0, indicating that either no object detection has been performed yet, or the detection found no objects. Apply an ObjectDetector first to identify and label objects.

Examples

Measure object properties:

>>> img = Image.imread('sample.jpg')
>>> detector = ObjectDetector()
>>> detector.detect(img)
>>> obj_accessor = img.objects
>>> measurements = img.objects.measure.area()

The objects property provides a high-level interface to detected objects, including object properties, statistics, and analysis capabilities. This requires that object detection has been performed on the image (e.g., via an ObjectDetector).

ObjectsAccessor API:

class phenotypic._core._image_parts.accessors._objects_accessor.ObjectsAccessor(root_image: Image)[source]#

Bases: object

Provide access to detected microbial colonies and their properties in agar plate images.

This accessor enables researchers to analyze individual colonies in arrayed microbial cultures after colony detection has been performed. It provides methods for accessing colony labels, retrieving colony properties (area, intensity, position), extracting individual colony crops, and organizing colony data for high-throughput phenotypic screening workflows.

The accessor operates on labeled object maps where each pixel value indicates which colony it belongs to (0 for background, 1+ for individual colonies). Properties are computed using scikit-image’s regionprops functionality, providing standardized morphological and intensity measurements for each colony.

Note

This accessor can only be used after an ObjectDetector has been applied to the Image to identify and label individual colonies. Attempting to access before detection raises NoObjectsError.

Parameters:

root_image (Image)

_root_image#

The parent Image containing the labeled colony map (objmap).

Type:

Image

Examples

Access detected colonies and measure their properties:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> # Load plate image and detect colonies
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Access colony properties
>>> print(f"Detected {len(plate.objects)} colonies")
>>> # Iterate over all colonies
>>> for colony in plate.objects:
...     print(f"Colony area: {colony.gray.sum()}")
>>> # Get information for all colonies
>>> colony_info = plate.objects.info()
>>> print(colony_info[["Object_Label", "Bbox_CenterRR", "Bbox_CenterCC"]])
__getitem__(index: int) Image[source]#

Extract a specific colony by its position index.

This enables bracket notation (e.g., plate.objects[0]) to access individual colonies by their sequential position. The returned Image is cropped to the colony’s bounding box and contains only that colony in its object map.

This is useful when you need to access a specific colony by position, such as the first or last detected colony, or when iterating with enumerate() to track both index and colony.

Parameters:

index (int) – Zero-based position index of the desired colony. Must be in the range [0, len(objects)-1]. Negative indexing is not supported.

Returns:

A cropped Image containing only the specified colony. The Image is cropped to

the colony’s bounding box, has metadata updated to ImageType=’Object’, and has an objmap where pixels belonging to the colony retain their label value while all other pixels are set to 0.

Return type:

Image

Raises:

IndexError – If index is negative or >= the total number of colonies.

Examples

Extract the first detected colony:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_plate.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Get first colony
>>> first_colony = plate.objects[0]
>>> print(f"First colony label: {first_colony.objmap.max()}")
>>> print(f"Colony size: {(first_colony.objmap > 0).sum()} pixels")

Access specific colonies for comparison:

>>> # Compare first and last colonies
>>> first = plate.objects[0]
>>> last = plate.objects[len(plate.objects) - 1]
>>> print(f"First colony mean intensity: {first.gray.mean()}")
>>> print(f"Last colony mean intensity: {last.gray.mean()}")

Use with enumerate for indexed processing:

>>> for idx, colony in enumerate(plate.objects):
...     if idx % 10 == 0:
...         # Process every 10th colony
...         colony.show()
__init__(root_image: Image)[source]#

Initialize the ObjectsAccessor with a parent Image.

This method is called automatically when accessing the objects property of an Image. Users should not instantiate this class directly; instead, access it through the Image.objects property after applying an ObjectDetector.

Parameters:

root_image (Image) – The parent Image containing detected colonies. Must have an objmap (object map) populated by an ObjectDetector that has been applied to identify and label individual colonies.

Examples

Accessor is created automatically when accessing colonies:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("plate.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # ObjectsAccessor is automatically initialized
>>> accessor = plate.objects  # Uses __init__ internally
>>> print(f"Found {len(accessor)} colonies")
__iter__() Generator[Image, Any, None][source]#

Iterate over all detected colonies, yielding each as a cropped Image.

This enables using the accessor in for loops and comprehensions to process each colony individually. Each yielded Image is cropped to the colony’s bounding box and contains only that colony’s pixels in its object map (all other pixels set to 0).

This is particularly useful for batch processing colonies, computing per-colony metrics, or performing quality control by visually inspecting individual colonies.

Yields:

Image

A cropped Image for each colony, in order by position index (0 to N-1).

Each cropped Image has its metadata updated with ImageType=’Object’ and contains only the pixels within that colony’s bounding box. The objmap of each yielded Image contains only the current colony’s label, with all other pixels set to 0.

Return type:

Generator[Image, Any, None]

Examples

Compute average intensity for each colony:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("fluorescent_colonies.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Calculate mean fluorescence for each colony
>>> intensities = []
>>> for colony in plate.objects:
...     mean_intensity = colony.gray.mean()
...     intensities.append(mean_intensity)
>>> print(f"Average colony intensity: {sum(intensities) / len(intensities)}")

Filter colonies by size:

>>> # Get all colonies larger than 500 pixels
>>> large_colonies = [
...     colony for colony in plate.objects
...     if (colony.objmap > 0).sum() > 500
... ]
>>> print(f"Found {len(large_colonies)} large colonies")

Extract colony names for tracking:

>>> colony_names = [
...     obj.metadata[METADATA.IMAGE_NAME]
...     for obj in plate.objects
... ]
__len__() int[source]#

Return the number of detected colonies in the plate image.

This enables using Python’s built-in len() function to quickly check how many colonies were detected by the object detector. Useful for quality control and validating that colony detection worked as expected.

Returns:

The total number of labeled colonies in the object map. Returns 0 if no

colonies have been detected.

Return type:

int

Examples

Check colony count after detection:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("96well_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Check if expected number of colonies detected
>>> colony_count = len(plate.objects)
>>> expected_count = 96
>>> if colony_count != expected_count:
...     print(f"Warning: Expected {expected_count} colonies, found {colony_count}")

Use in conditional logic:

>>> if len(plate.objects) == 0:
...     raise RuntimeError("No colonies detected. Check detector parameters.")
get_label_idx(object_label: int) int[source]#

Convert a colony label to its position index in the labels list.

This method maps from label identifiers (which may have gaps or start from values other than 0) to position indices (which are always contiguous from 0 to N-1). This is useful when you know a colony’s label but need its position for indexing into lists or arrays organized by position.

Parameters:

object_label (int) – The label identifier to look up. This is the value stored in the objmap for pixels belonging to the desired colony, typically a positive integer starting from 1.

Returns:

The zero-based position index where this label appears in the labels list.

This index can be used with methods like iloc(), slices[], or props[].

Return type:

int

Raises:

IndexError – If object_label is not present in the current labels list. This occurs when the label doesn’t exist, was filtered out, or does not match any detected colony.

Examples

Map label to index for accessing properties:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Find the position index for colony label 5
>>> idx = plate.objects.get_label_idx(5)
>>> print(f"Colony with label 5 is at position {idx}")
>>> # Use the index to access properties
>>> colony_slice = plate.objects.slices[idx]
>>> colony_props = plate.objects.props[idx]
>>> print(f"Colony 5 area: {colony_props.area}")

Use with non-contiguous labels:

>>> # After filtering, labels may not be contiguous
>>> labels = plate.objects.labels  # e.g., [1, 2, 5, 8, 10]
>>> # Get position index for label 8 (which is at position 3)
>>> idx = plate.objects.get_label_idx(8)  # Returns 3
>>> colony_8 = plate.objects.iloc(idx)

Verify label exists before accessing:

>>> desired_label = 42
>>> if desired_label in plate.objects.labels:
...     idx = plate.objects.get_label_idx(desired_label)
...     print(f"Found colony {desired_label} at index {idx}")
... else:
...     print(f"Colony {desired_label} not found")
iloc(index: int) Image[source]#

Access a colony by position index using pandas-style iloc syntax.

This method provides a pandas-like interface for accessing colonies by their position index. Unlike __getitem__ (which also uses position), iloc() returns a crop that preserves all labels in the objmap, while __getitem__ zeros out non-matching labels for isolation.

Use iloc() when you need the minimal bounding box crop but want to preserve the original label structure and neighboring colonies within the bounding box, which can be useful for context-aware analysis workflows.

Parameters:

index (int) – Zero-based position index of the colony to extract. Must be in the range [0, num_objects-1].

Returns:

A cropped Image containing the bounding box region of the specified colony.

Unlike __getitem__, this crop preserves all label values in the objmap without zeroing non-matching pixels. The metadata is inherited from the parent Image, and ImageType is not changed to ‘Object’.

Return type:

Image

Raises:

IndexError – If index is negative or >= num_objects.

Examples

Access first colony with pandas-style syntax:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Access first colony
>>> first = plate.objects.iloc(0)
>>> print(f"Bounding box shape: {first.shape}")

Compare iloc() vs __getitem__:

>>> # iloc preserves all labels in the crop
>>> crop_iloc = plate.objects.iloc(5)
>>> print(f"All labels in crop (iloc): {set(crop_iloc.objmap.flatten())}")
>>> # May show {0, 5} if other colonies are nearby
>>> # __getitem__ zeros out non-matching labels for isolation
>>> crop_getitem = plate.objects[5]
>>> print(f"Labels in isolated crop ([5]): {crop_getitem.objmap.max()}")
>>> # Shows only label 5 (others zeroed)

Extract multiple specific colonies:

>>> # Get colonies at positions 0, 5, and 10 with preserved context
>>> selected_indices = [0, 5, 10]
>>> selected_colonies = [plate.objects.iloc(i) for i in selected_indices]
info(include_metadata: bool = True) pandas.DataFrame[source]#

Generate a summary table of colony positions and bounding boxes.

This method creates a pandas DataFrame containing key positional information for all detected colonies, including their labels, centroid coordinates, and bounding box coordinates. This is particularly useful for organizing colony data for downstream analysis, quality control, or exporting to other tools.

The table includes one row per colony with columns for the colony label, centroid position (row and column), and bounding box coordinates (min/max row and column). Optionally, image metadata can be prepended to provide experimental context.

Parameters:

include_metadata (bool, optional) – If True, prepend image metadata columns (such as ImageName, UUID, ImageType) to the DataFrame using the Image’s metadata.insert_metadata() method. This adds experimental context to the colony information. Defaults to True.

Returns:

A DataFrame with one row per colony containing:
  • Object_Label: The colony’s label identifier

  • Bbox_CenterRR: Row coordinate of the colony centroid

  • Bbox_CenterCC: Column coordinate of the colony centroid

  • Bbox_MinRR: Minimum row coordinate of the bounding box

  • Bbox_MinCC: Minimum column coordinate of the bounding box

  • Bbox_MaxRR: Maximum row coordinate of the bounding box

  • Bbox_MaxCC: Maximum column coordinate of the bounding box

If include_metadata=True, additional metadata columns are prepended.

Return type:

pd.DataFrame

Examples

Get basic colony information:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Get colony information
>>> colony_info = plate.objects.info()
>>> print(colony_info.head())
>>> print(f"Columns: {colony_info.columns.tolist()}")

Access specific columns:

>>> # Get just labels and centroids
>>> labels_centroids = colony_info[["Object_Label", "Bbox_CenterRR", "Bbox_CenterCC"]]
>>> print(labels_centroids)

Export colony positions for other tools:

>>> # Get info without metadata for cleaner export
>>> colony_positions = plate.objects.info(include_metadata=False)
>>> colony_positions.to_csv("colony_positions.csv", index=False)

Filter colonies by position:

>>> info = plate.objects.info(include_metadata=False)
>>> # Find colonies in the upper half of the image
>>> upper_colonies = info[info["Bbox_CenterRR"] < 500]
>>> print(f"Found {len(upper_colonies)} colonies in upper half")

Calculate bounding box dimensions:

>>> info = plate.objects.info(include_metadata=False)
>>> # Calculate width and height of each colony's bounding box
>>> info["BBox_Width"] = info["Bbox_MaxCC"] - info["Bbox_MinCC"]
>>> info["BBox_Height"] = info["Bbox_MaxRR"] - info["Bbox_MinRR"]
>>> print(f"Average colony width: {info['BBox_Width'].mean():.1f} pixels")
>>> print(f"Average colony height: {info['BBox_Height'].mean():.1f} pixels")
labels2series() pandas.Series[source]#

Convert colony labels to a pandas Series for joining with measurement DataFrames.

This method creates a pandas Series containing all colony labels with a properly named column (‘Object_Label’) and indexed by position. This is specifically designed to facilitate joining labels with measurement DataFrames that are indexed by position, which is a common pattern in high-throughput phenotypic analysis workflows.

The returned Series can be easily joined or merged with other pandas DataFrames containing per-colony measurements to add label information.

Returns:

A pandas Series with:
  • data: The colony labels (integers)

  • index: Position indices (0 to N-1)

  • name: ‘Object_Label’ for proper DataFrame column naming

Return type:

pd.Series

Examples

Join labels with measurement data:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> from phenotypic.measure import AreaMeasurer
>>> import pandas as pd
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Calculate measurements (indexed by position)
>>> measurer = AreaMeasurer()
>>> measurements = measurer.measure(plate)
>>> # Add labels to measurements
>>> labels = plate.objects.labels2series()
>>> measurements_with_labels = measurements.join(labels)
>>> print(measurements_with_labels.head())

Merge with custom analysis results:

>>> import numpy as np
>>> import pandas as pd
>>> # Custom analysis results indexed by position
>>> custom_analysis = pd.DataFrame({
...     'mean_intensity': [125.3, 142.7, 98.1],
...     'max_intensity': [255, 243, 189]
... }, index=range(len(plate.objects)))
>>> # Add colony labels
>>> labels_series = plate.objects.labels2series()
>>> analysis_with_labels = custom_analysis.join(labels_series)
>>> # Now can group or filter by label
>>> print(analysis_with_labels)

Use as a lookup table:

>>> labels_series = plate.objects.labels2series()
>>> # Get label for position 5
>>> label_at_pos_5 = labels_series.iloc[5]
>>> print(f"Colony at position 5 has label {label_at_pos_5}")

Handle potential label suffix conflicts:

>>> # If DataFrame already has an 'Object_Label' column
>>> labels = plate.objects.labels2series()
>>> measurements_with_labels = measurements.join(labels, rsuffix="_new")
>>> # Creates 'Object_Label' and 'Object_Label_new' columns
loc(label_number: int) Image[source]#

Access a colony by its label identifier using pandas-style loc syntax.

This method provides a pandas-like interface for accessing colonies by their label value (rather than position index). This is useful when you know the specific label ID of a colony from the objmap and want to extract just that colony’s bounding box region.

Unlike iloc() which uses position, loc() uses the actual label value assigned in the object map. This is particularly helpful when labels are non-contiguous or when working with specific colonies identified by their label in previous analysis steps.

Parameters:

label_number (int) – The label identifier assigned to the colony in the objmap. This is typically a positive integer (1, 2, 3, …) but may have gaps if colonies were filtered or removed.

Returns:

A cropped Image containing the bounding box region of the colony with the

specified label. The crop preserves all label values in the objmap without zeroing non-matching pixels.

Return type:

Image

Raises:

IndexError – If label_number does not exist in the current labels list.

Examples

Access colony by its label:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Access colony with label 5
>>> colony_5 = plate.objects.loc(5)
>>> print(f"Colony 5 bounding box: {colony_5.shape}")

Access first colony by its label (not position):

>>> # Get the label of the first colony
>>> first_label = plate.objects.labels[0]  # e.g., could be 1
>>> # Access by that label
>>> first_colony = plate.objects.loc(first_label)

Use with non-contiguous labels:

>>> # Labels might be: [1, 2, 5, 8, 10] after filtering
>>> labels = plate.objects.labels
>>> # Access colony with label 8 (not position 8)
>>> colony_8 = plate.objects.loc(8)
>>> # Compare with position-based access
>>> colony_at_pos_3 = plate.objects.iloc(3)  # Also gets label 8

Safe access with label validation:

>>> desired_label = 42
>>> if desired_label in plate.objects.labels:
...     colony = plate.objects.loc(desired_label)
...     print(f"Found colony {desired_label}")
... else:
...     print(f"Colony {desired_label} not found")
relabel() None[source]#

Renumber colony labels to be sequential and contiguous.

This method relabels all colonies in the object map so that labels are sequential integers starting from 1 (i.e., 1, 2, 3, …, N for N colonies). This is useful after filtering or removing colonies, which can leave gaps in the label sequence, or when you need consistent, predictable label numbering for downstream analysis.

The relabeling preserves the spatial relationships between colonies but assigns new labels in a consistent order. Connected components (colonies) are assigned sequential IDs based on their position in the label image.

Returns:

This method modifies the parent Image’s objmap in-place. Labels are

renumbered directly in the object map, and no value is returned.

Return type:

None

Examples

Relabel after filtering to remove gaps:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Before relabeling (may have gaps)
>>> print(f"Labels before: {plate.objects.labels}")
>>> # e.g., [1, 2, 5, 7, 10, 15]
>>> # Relabel to make sequential
>>> plate.objects.relabel()
>>> # After relabeling (sequential)
>>> print(f"Labels after: {plate.objects.labels}")
>>> # e.g., [1, 2, 3, 4, 5, 6]

Ensure consistent labeling for reproducibility:

>>> # After any colony filtering or modification
>>> plate.objects.relabel()
>>> # Now labels are guaranteed to be 1, 2, 3, ..., N
>>> assert plate.objects.labels == list(range(1, len(plate.objects) + 1))

Relabel after manual objmap modifications:

>>> # After custom filtering or editing the objmap
>>> import numpy as np
>>> # Remove small colonies (custom filtering)
>>> for prop in plate.objects.props:
...     if prop.area < 100:
...         mask = plate.objmap[:] == prop.label
...         plate.objmap[:][mask] = 0
>>> # Relabel to clean up the label sequence
>>> plate.objects.relabel()
>>> print(f"Remaining colonies: {len(plate.objects)}")
>>> print(f"New labels: {plate.objects.labels}")

Compare before and after relabeling:

>>> labels_before = plate.objects.labels.copy()
>>> plate.objects.relabel()
>>> labels_after = plate.objects.labels
>>> print(f"Before: {labels_before}")
>>> print(f"After: {labels_after}")
>>> print(f"Same count: {len(labels_before) == len(labels_after)}")
reset() None[source]#

Clear all colony labels from the object map.

This method resets the object map to its initial empty state by setting all pixels to 0, effectively removing all colony detections. After calling reset(), the image reverts to having no detected colonies (num_objects = 0) and the entire image becomes the analysis target again.

This is useful when you want to re-run colony detection with different parameters or when you need to clear previous detection results before applying a different detection method.

Returns:

This method modifies the parent Image in-place by clearing its objmap. No value

is returned.

Return type:

None

Examples

Clear detections to re-run with different parameters:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> # First detection attempt
>>> detector1 = RoundPeaksDetector(thresh_method="otsu")
>>> detector1.apply(plate)
>>> print(f"First attempt: {plate.objects.num_objects} colonies")
>>> # Clear and try with different parameters
>>> plate.objects.reset()
>>> print(f"After reset: {plate.objects.num_objects} colonies")  # 0
>>> detector2 = RoundPeaksDetector(thresh_method="mean")
>>> detector2.apply(plate)
>>> print(f"Second attempt: {plate.objects.num_objects} colonies")

Verify reset clears all colonies:

>>> plate.objects.reset()
>>> assert plate.num_objects == 0
>>> assert len(plate.objects.labels) == 0
>>> assert plate.objmap.max() == 0
property labels: List[int]#

Get the unique label identifiers for all detected colonies.

Each colony in the object map is assigned a unique positive integer label (typically starting from 1). This property returns all such labels, which can be used to select specific colonies with the loc() method or to verify which colonies are present.

Labels are extracted from scikit-image’s regionprops to ensure consistency with all property calculations, rather than using numpy.unique() on the object map directly.

Returns:

A list of unique colony labels in ascending order. Returns an empty list

if no colonies have been detected. Background pixels (labeled 0) are excluded.

Return type:

list[int]

Examples

Check which colonies are present:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> labels = plate.objects.labels
>>> print(f"Detected colonies with labels: {labels}")
>>> print(f"Labels range from {min(labels)} to {max(labels)}")

Check if a specific colony exists:

>>> if 5 in plate.objects.labels:
...     colony_5 = plate.objects.loc(5)
...     print(f"Colony 5 area: {colony_5.gray.sum()}")
... else:
...     print("Colony 5 not found")

Verify contiguous labeling:

>>> labels = plate.objects.labels
>>> expected_labels = list(range(1, len(labels) + 1))
>>> if labels == expected_labels:
...     print("Labels are contiguous")
... else:
...     print("Labels have gaps - consider using relabel()")
property num_objects: int#

Get the total number of detected colonies.

This property provides the count of unique colony labels in the object map. It is equivalent to len(image.objects) but provides a more explicit property-based interface. The count excludes background pixels (label 0).

Returns:

The number of unique colony labels currently tracked in the objmap. Returns 0

if no colonies have been detected.

Return type:

int

Examples

Verify expected colony count:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("96well_plate.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Check if detection found the expected number of colonies
>>> expected = 96
>>> actual = plate.objects.num_objects
>>> if actual != expected:
...     print(f"Warning: Expected {expected} colonies, found {actual}")

Verify consistency with len():

>>> assert plate.objects.num_objects == len(plate.objects)
property props: list#

Compute region properties for all detected colonies using scikit-image.

This property provides access to scikit-image’s RegionProperties objects, which contain detailed morphological and intensity measurements for each colony. Each RegionProperties object provides lazy-evaluated properties like area, perimeter, centroid, bounding box coordinates, mean intensity, and many morphological descriptors.

The properties are computed dynamically each time this property is accessed (cache=False), ensuring measurements always reflect the current state of the object map and grayscale intensity image.

Returns:

A list of RegionProperties objects, one per

detected colony, sorted by label. Each object provides lazy access to properties:

  • area: Number of pixels in the colony

  • centroid: (row, col) coordinates of the colony center

  • bbox: Bounding box as (min_row, min_col, max_row, max_col)

  • mean_intensity: Mean pixel intensity within the colony region

  • perimeter, eccentricity, solidity, major_axis_length, minor_axis_length, moments, moments_central, moments_hu, and many more standard morphological measurements from scikit-image.measure.regionprops

Refer to scikit-image documentation for the complete list of available properties.

Return type:

list[skimage.measure.RegionProperties]

Examples

Extract colony areas:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Get areas of all colonies
>>> areas = [prop.area for prop in plate.objects.props]
>>> print(f"Colony sizes: {areas}")
>>> print(f"Mean colony size: {sum(areas) / len(areas):.1f} pixels")

Access multiple properties per colony:

>>> for prop in plate.objects.props:
...     print(f"Colony {prop.label}:")
...     print(f"  Area: {prop.area} pixels")
...     print(f"  Centroid: ({prop.centroid[0]:.1f}, {prop.centroid[1]:.1f})")
...     print(f"  Mean intensity: {prop.mean_intensity:.2f}")
...     print(f"  Eccentricity: {prop.eccentricity:.3f}")

Filter colonies by morphology:

>>> # Find circular colonies (low eccentricity)
>>> circular_colonies = [
...     prop for prop in plate.objects.props
...     if prop.eccentricity < 0.5
... ]
>>> print(f"Found {len(circular_colonies)} circular colonies")
property slices: list#

Get bounding box slices for efficiently cropping to each colony region.

This property returns NumPy-compatible slice objects that define the minimal rectangular bounding box around each colony. These slices can be used directly with NumPy array indexing to extract bounding box regions, which is much more efficient than processing the full plate image when analyzing individual colonies.

Each slice is a tuple of (row_slice, col_slice) that can be used with NumPy standard indexing syntax: array[row_slice, col_slice] to extract the bounding box region from any array with the same dimensions.

Returns:

A list of slice tuples, one per colony, in the same order

as the labels. Each tuple contains (row_slice, col_slice) where: - row_slice: slice(min_row, max_row) - includes rows from min_row to max_row-1 - col_slice: slice(min_col, max_col) - includes cols from min_col to max_col-1

Return type:

list[tuple[slice, slice]]

Examples

Extract bounding box regions from the grayscale image:

>>> from phenotypic import Image
>>> from phenotypic.detect import RoundPeaksDetector
>>> plate = Image.from_file("colony_array.png")
>>> detector = RoundPeaksDetector()
>>> detector.apply(plate)
>>> # Get the bounding box region for the first colony
>>> first_slice = plate.objects.slices[0]
>>> first_colony_region = plate.gray[first_slice]
>>> print(f"First colony bounding box shape: {first_colony_region.shape}")

Process all colonies using their bounding boxes:

>>> for idx, bbox_slice in enumerate(plate.objects.slices):
...     colony_region = plate.gray[bbox_slice]
...     mean_intensity = colony_region.mean()
...     print(f"Colony {idx}: mean intensity = {mean_intensity:.2f}")

Extract bounding boxes from multiple image channels:

>>> # Process RGB channels for each colony
>>> for bbox_slice in plate.objects.slices:
...     r_channel = plate.rgb[bbox_slice][..., 0]
...     g_channel = plate.rgb[bbox_slice][..., 1]
...     b_channel = plate.rgb[bbox_slice][..., 2]
...     print(f"R: {r_channel.mean():.1f}, G: {g_channel.mean():.1f}, "
...           f"B: {b_channel.mean():.1f}")
property Image.objmap: ObjectMap#

Returns the ObjectMap accessor; The object map is a mutable integer gray that identifies the different objects in an image to be analyzed. Changes to elements of the object_map sync to the object_mask.

The object_map is stored as a compressed sparse column gray in the backend. This is to save on memory consumption at the cost of adding increased computational overhead between converting between sparse and dense matrices.

Note

  • Has accessor methods to get sparse representations of the object map that can streamline measurement calculations.

Returns:

A mutable integer gray that identifies the different objects in an image to be analyzed.

Return type:

ObjectMap

from phenotypic import Image
from phenotypic.data import load_colony

image = Image(load_colony())

# get the objmap data
arr = image.objmap[:]
print(type(arr))

# set the objmap data
# the shape of the new objmap must be the same shape as the original objmap
image.objmap[:] = arr

# without the bracket indexing the accessor is returned instead
print(image.objmap[:])

See Also: ObjectMap

The objmap property provides access to the labeled object map, where each pixel contains an integer label corresponding to the detected object it belongs to (0 for background). This is the result of instance segmentation operations.

ObjectMap API:

class phenotypic._core._image_parts.accessors._objmap_accessor.ObjectMap(root_image: Image)[source]#

Bases: NapariLabelsMixin, SingleChannelAccessor

Manages an object map for labeled regions in an image.

This class provides a mechanism to manipulate and access labeled object maps within a given image. It is tightly coupled with the parent image object and provides methods for accessing sparse and dense representations, relabeling, resetting, and visualization.

The object map is stored internally as a compressed sparse column (CSC) matrix for memory efficiency. All public methods expose the data through dense array interfaces for ease of use while maintaining the sparse representation backend. Changes to the object map shapes are automatically reflected in the object mask.

The class supports array-like indexing and slicing operations, sparse format conversion, and visualization with matplotlib.

Parameters:

root_image (Image)

_root_image#

The parent Image object that this accessor is associated with.

Note

Changes to the object map shapes will be automatically reflected in the object mask.

classmethod load(filepath: str | Path) numpy.ndarray#

Load an image array from file and verify it was saved from this accessor type.

Checks if the image contains PhenoTypic metadata indicating it was saved from the same accessor type (e.g., Image.gray, Image.rgb). If metadata doesn’t match or is missing, a warning is raised but the array is still loaded.

Parameters:

filepath (str | Path) – Path to the image file to load.

Returns:

The loaded image array.

Return type:

np.ndarray

Warns:

UserWarning – If metadata is missing or indicates the image was saved from a different accessor type.

Examples

Load a grayscale image from file:

>>> from phenotypic import Image
>>> image = Image(arr)
>>> # load an object map you saved or hand-graded
>>> image.objmap.load("path/to/map.png")
__array__(dtype=None, copy=None)[source]#

Implement the array interface for NumPy compatibility.

This method enables NumPy functions to operate directly on ObjectMap instances by converting the sparse representation to a dense array. It allows usage patterns such as np.sum(objmap), np.max(objmap), and array unpacking operations.

Parameters:
  • dtype (type, optional) – Optional NumPy dtype to cast the array to. If None, the array maintains its native dtype. Defaults to None.

  • copy (bool, optional) – Control whether the underlying data is copied. If True, a copy is guaranteed. If False (NumPy 2.0+), no copy is made. If None, a copy is made only if necessary for dtype conversion. Defaults to None.

Returns:

A dense NumPy array representation of the object map

with shape matching the image dimensions (height, width).

Return type:

np.ndarray

Examples

Using ObjectMap with NumPy functions:

>>> import numpy as np
>>> objmap = image.objmap
>>> max_label = np.max(objmap)
>>> unique_labels = np.unique(objmap)
>>> arr = np.asarray(objmap, dtype=np.uint32)
__getitem__(key)[source]#

Return a slice of the object map as if it were a dense array.

This method implements NumPy-style slicing and indexing for the ObjectMap. The sparse representation is converted to dense for the indexing operation, allowing all standard NumPy slicing patterns including integer indexing, boolean masking, and multi-dimensional slicing.

Parameters:

key – Index or slice specification. Can be an integer, tuple of integers, slice object, boolean array, or integer array for advanced indexing. All NumPy indexing patterns are supported.

Returns:

The sliced portion of the object map as a dense array.

The returned shape depends on the indexing pattern. Returns a scalar if a single element is indexed, otherwise returns an ndarray.

Return type:

np.ndarray

Examples

NumPy-style indexing and slicing:

>>> objmap = image.objmap
>>> row = objmap[5]  # Get row 5
>>> region = objmap[10:20, 30:40]  # Get a rectangular region
>>> pixel = objmap[5, 10]  # Get a single pixel
>>> mask = objmap[objmap > 0]  # Boolean indexing
__len__() int#

Returns the length of the subject array.

This method calculates and returns the total number of elements contained in the underlying array.

Returns:

The number of elements in the underlying array attribute.

Return type:

int

__setitem__(key, value)[source]#

Set values in the object map as if it were a dense array.

This method implements NumPy-style assignment for the ObjectMap. It accepts both array and scalar values, validates shapes and types, and atomically updates the internal sparse representation. The operation is atomic with respect to the backend reference.

Parameters:
  • key – Index or slice specification for the location to update. Can be an integer, tuple of integers, slice object, boolean array, or integer array. All NumPy indexing patterns are supported.

  • value – The value(s) to assign. Can be: - A NumPy array matching the shape of the key selection - A scalar integer, bool, or float (converted to int)

Raises:
  • ArrayKeyValueShapeMismatchError – If the shape or dtype of a supplied array does not match the shape of the key selection, or if the dtypes are incompatible.

  • InvalidMapValueError – If value is not a supported type (not an array, int, bool, or float).

Notes

  • After assignment, the sparse matrix is compressed and zeros are eliminated to maintain memory efficiency.

  • All values are internally cast to uint16 to match the sparse matrix dtype and represent object labels.

  • The operation is atomic: if an exception occurs, the map is not modified.

Examples

Assigning values to the object map:

>>> objmap = image.objmap
>>> # Set a single pixel
>>> objmap[5, 10] = 3
>>> # Set a region with a scalar value
>>> objmap[10:20, 30:40] = 0
>>> # Set a region with an array
>>> region_labels = np.array([[1, 1], [1, 1]])
>>> objmap[10:12, 30:32] = region_labels
as_coo() coo_matrix[source]#

Return the object map in COOrdinate (ijv) sparse format.

Converts the internal object map representation to COO (Coordinate or ijv) format, which stores non-zero elements as (row, column, value) tuples. This format is useful for constructing sparse matrices or analyzing individual non-zero entries.

Returns:

A copy of the object map in COO sparse format.

Return type:

scipy.sparse.coo_matrix

Examples

Converting to COO sparse format and accessing entries:

>>> sparse_coo = image.objmap.as_coo()
>>> # Access non-zero entries directly
>>> for i, j, v in zip(sparse_coo.row, sparse_coo.col, sparse_coo.data):
...     print(f"Object label {v} at position ({i}, {j})")
as_csc() csc_matrix[source]#

Return the object map as a compressed sparse column matrix.

Converts the internal object map representation to CSC (Compressed Sparse Column) format. CSC format is optimized for column slicing and matrix operations.

Returns:

A copy of the object map in CSC sparse format.

Return type:

scipy.sparse.csc_matrix

Examples

Converting to CSC sparse format:

>>> sparse_csc = image.objmap.as_csc()
>>> # Efficient column operations
>>> first_column = sparse_csc[:, 0].toarray()
copy() numpy.ndarray[source]#

Return a dense copy of the object map.

Creates and returns a dense NumPy array copy of the object map. The returned array is independent and modifications to it will not affect the original object map.

Returns:

A dense copy of the object map with shape (height, width)

and dtype uint16.

Return type:

np.ndarray

Examples

Creating an independent copy of the object map:

>>> objmap_copy = image.objmap.copy()
>>> objmap_copy[0, 0] = 999  # Modifications don't affect the original
>>> assert image.objmap[0, 0] != 999
dash(figsize: tuple[int, int] | None = None, title: str | None = None, cmap: str | None = 'gray', foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None, plotly_settings: dict | None = None) go.Figure#

Display the single-channel image data using Plotly.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, no title is displayed.

  • cmap (str | None) – Colormap name. Defaults to "gray".

  • foreground_only (bool) – If True, display only foreground elements.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

  • plotly_settings (dict | None) – Additional Plotly layout settings.

Returns:

A plotly.graph_objects.Figure.

Raises:

ImportError – If plotly is not installed.

Return type:

go.Figure

draw(*, viewer: NapariViewer | None = None) Image#

Open a napari editor to hand-edit these labels and save them back.

Launches a blocking PyQt napari viewer showing rgb (when present), gray, and detect_mat as image layers plus this accessor’s data as an editable labels layer. Use napari’s built-in paintbrush, fill, and eraser tools to correct the segmentation, then click Save to Image in the dock panel to commit the edits to the parent image (or Discard & Close to abandon them).

Editing objmap preserves the original integer label IDs. Editing objmask is strictly binary and relabels the object map on save (skimage.measure.label), so original IDs are not retained — call image.objmap.draw() instead when stable IDs matter. Preview the existing detections with show() or napari() before editing.

Parameters:

viewer (NapariViewer | None) – Optional existing napari viewer to reuse instead of opening a fresh one. Defaults to None.

Returns:

The parent image, mutated in place if edits were saved.

Return type:

Image

Raises:

ImportError – If napari is not installed. Install with pip install phenotypic[napari].

Examples

Hand-correct an auto-detected object map:

>>> from phenotypic.data import load_synth_yeast_plate
>>> from phenotypic.detect import OtsuDetector
>>> image = OtsuDetector().apply(load_synth_yeast_plate())
>>> image = image.objmap.draw()
foreground()#

Extracts and returns the foreground of the image by masking out the background.

This method generates a foreground image by applying the object mask stored in the Image to the current array representation. Pixels outside the object mask are set to zero in the resulting foreground image. This is useful in image processing tasks to isolate the region of interest in the image, such as microbe colonies on an agar plate.

Returns:

A numpy array containing only the foreground portion of the image, with all non-foreground pixels set to zero.

Return type:

numpy.ndarray

histogram(figsize: Tuple[int, int] = (10, 5), *, cmap='gray', linewidth=1, channel_names: list | None = None) Tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Plots the histogram(s) of an image along with the image itself. The behavior depends on the dimensionality of the image array (2D or 3D). In the case of 2D, a single image and its histogram are produced. For 3D (multi-channel images), histograms for each channel are created alongside the image. This method supports customization such as figure size, colormap, line width of histograms, and labeling of channels.

Parameters:
  • figsize (Tuple[int, int]) – The size of the figure to create. Default is (10, 5).

  • cmap (str) – Colormap used to render the image when the data is single channel. Default is ‘gray’.

  • linewidth (int) – Line width of the plotted histograms. Default is 1.

  • channel_names (list | None) – Optional names for the channels in 3D data. These are used as titles for channel-specific histograms. If None, channels are instead labeled numerically.

Returns:

The Matplotlib figure and axes objects representing the plotted image and its histograms.

Return type:

Tuple[plt.Figure, plt.Axes]

Raises:

ValueError – If the dimensionality of the input image array is unsupported.

Notes

This method uses skimage.exposure.histogram for computing the histogram data.

imsave(filepath: str | Path | None = None, bit_depth: Literal[8, 16] | None = None, use_label2rgb: bool = False) None[source]#

Save the object map to a file.

Saves the object map array to a specified file format. The raw object map contains integer labels (0 = background, 1+ = object IDs). When use_label2rgb is True, labels are colorized for visualization but NOT blended with the original image.

Parameters:
  • filepath (str | Path | None) – Destination file path (.png, .jpeg, or .tiff).

  • bit_depth (Literal[8, 16] | None) – Target bit depth (8 or 16). If None, uses image’s bit depth.

  • use_label2rgb (bool) – If True, colorize labels using skimage.color.label2rgb for visual inspection. Note: This does NOT blend with the original image - it only colorizes the labels against a black background. For overlays blended with the original image at full resolution, use Image.rgb.save_overlay() or Image.gray.save_overlay() instead. Defaults to False.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

See also

ImageAccessorBase.save_overlay: For saving overlays that blend the

object labels with the original image at full resolution.

Examples

Save raw object map vs colorized:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> # Save raw labeled map (integer values)
>>> image.objmap.imsave("objmap_raw.png")
>>> # Save colorized labels (no background image)
>>> image.objmap.imsave("objmap_colored.png", use_label2rgb=True)
>>> # For overlay with original image, use save_overlay:
>>> image.rgb.save_overlay("overlay.png", overlay_alpha=0.3)
isempty()#
napari(name: str | None = None, reset: bool = False, colormap: dict | None = None, opacity: float = 0.7, contour: int = 0, *, viewer: napari.Viewer | None = None, layer_name: str | None = None) napari.Viewer#

Add labeled regions to a persistent global napari viewer.

Creates or reuses a single napari viewer instance with a labels layer for visualizing discrete labeled regions. Unlike the base class image layer implementation, this uses napari’s labels API which assigns distinct colors to each unique label value, making it ideal for visualizing segmented colonies, object maps, and binary masks.

The viewer persists across multiple method calls, allowing comparison of different processing stages and data types in the same visualization window.

Parameters:
  • name (str | None) – Optional custom name for the labels layer. If provided, the layer will be named {accessor}_{name}. If not provided, defaults to using the image’s name attribute. Defaults to None.

  • reset (bool) – If True, closes the current napari viewer and creates a fresh one. This is useful for starting a new visualization session without lingering layers from previous calls. Defaults to False.

  • colormap (dict | None) – Optional dictionary mapping label values to RGB colors. Keys are integer label IDs, values are RGB tuples/lists with values in [0, 1] range. If None, napari uses its default label colormap which assigns distinct colors automatically. Example: {1: [1.0, 0, 0], 2: [0, 1.0, 0]} for red/green colonies. Defaults to None.

  • opacity (float) – Layer opacity from 0.0 (fully transparent) to 1.0 (fully opaque). This controls the visibility of the labels layer. Values outside this range will raise ValueError. Defaults to 0.7.

  • contour (int) – Contour thickness in pixels. When > 0, renders only the outline of each labeled region rather than filled regions. Useful for overlaying object boundaries on other layers. Must be >= 0. Defaults to 0 (filled).

  • viewer (napari.Viewer | None) – Optional external napari viewer instance to use instead of the global viewer. When provided, global viewer management (creation, reset, smart-grid installation) is bypassed entirely. Defaults to None.

  • layer_name (str | None) – Optional full layer name to use instead of the auto-generated {accessor}_{image_name} pattern. Defaults to None.

Returns:

The global napari viewer instance with the labeled regions

added as a labels layer.

Return type:

napari.Viewer

Raises:

ValueError – If opacity is not in [0.0, 1.0] range or contour is negative.

Examples

Basic labels visualization:

>>> from phenotypic import Image
>>> from phenotypic.detect import OtsuDetector
>>> img = Image.imread("colonies.jpg")
>>> detector = OtsuDetector()
>>> img = detector.apply(img)
>>>
>>> # View detected colonies as labels
>>> viewer = img.objmap.napari()

Customizing labels appearance:

>>> # Semi-transparent labels with contours
>>> viewer = img.objmap.napari(opacity=0.5, contour=2)
>>>
>>> # Custom color mapping for specific colonies
>>> cmap = {
...     1: [1.0, 0, 0],      # Red for colony 1
...     2: [0, 1.0, 0],      # Green for colony 2
...     3: [0, 0, 1.0],      # Blue for colony 3
... }
>>> viewer = img.objmap.napari(colormap=cmap)

Comparing masks and maps in same viewer:

>>> # Add grayscale base image
>>> viewer = img.gray.napari()
>>>
>>> # Overlay binary mask with transparency
>>> viewer = img.objmask.napari(opacity=0.4)
>>>
>>> # Add full object map with contours only
>>> viewer = img.objmap.napari(name="boundaries", contour=1)

Using reset for fresh visualization sessions:

>>> viewer = img.objmap.napari()
>>> # ... do some analysis ...
>>> # Start fresh without old layers
>>> viewer = img.objmap.napari(reset=True)

Note

Labels layers are specifically designed for discrete/categorical data where each unique integer value represents a distinct object or region. This is fundamentally different from image layers which handle continuous intensity data. Labels layers provide:

  • Automatic distinct coloring per label

  • Contour visualization mode

  • Optimized rendering for segmentation data

  • Better interactivity for object selection

For colony analysis, this means each detected colony gets a unique color, making visual QC and counting much easier than grayscale intensity display.

relabel(connectivity: int = 2)[source]#

Relabel all connected components in the object map.

This method reassigns labels to all connected components in the current object map based on the specified connectivity criterion. It uses scikit-image’s label function to identify connected components and assigns them new sequential labels starting from 1. This is useful when:

  • Object labels are non-sequential or have gaps

  • The labeling needs to be reset to sequential labels

  • Connectivity needs to be changed (e.g., from 8-connectivity to 4-connectivity)

The operation treats the current object map as a binary mask (foreground vs. background) and relabels the foreground regions.

Parameters:

connectivity (int, optional) – Maximum number of orthogonal hops to consider a pixel as a neighbor of another. For 2D images: - 1: 4-connectivity (horizontal and vertical neighbors only) - 2: 8-connectivity (including diagonal neighbors) Higher values are supported for higher dimensional data. Defaults to 1.

Returns:

The operation modifies the object map in-place.

Return type:

None

Notes

  • Background (label 0) is preserved in its original location.

  • After relabeling, object labels will be sequential (1, 2, 3, …).

  • The relabeling process may change existing label values.

Examples

Relabeling connected components:

>>> # Relabel with 4-connectivity
>>> image.objmap.relabel(connectivity=1)
>>> # Relabel with 8-connectivity for diagonal neighbors
>>> image.objmap.relabel(connectivity=2)

See also

  • scikit-image.measure.label(): The underlying function used for connected component labeling.

reset() None[source]#

Reset the object map to an empty state with no labeled objects.

Clears all object labels from the object map, effectively removing all detected or manually-set objects. The resulting map contains only zeros (background). The shape of the map is preserved to match the parent image’s dimensions.

This method is useful when you want to clear the current segmentation and prepare for a new detection or labeling operation.

Examples

Clearing all objects from the object map:

>>> image.objmap.reset()
>>> # Now image.objmap contains no objects
>>> assert image.objmap[:].max() == 0
Return type:

None

save_overlay(filepath: str | Path, overlay_alpha: float = 0.3, bg_label: int = 0, colors: list | None = None, show_grid: bool = True, gridline_color: tuple[int, int, int] = (0, 255, 255), section_box_colors: list[tuple[int, int, int]] | None = None, **label2rgb_kwargs) None#

Save a full-resolution overlay image blending objmap with the subject array.

Creates an RGB overlay by blending the object map labels with the underlying image data and saves it to disk. Unlike show(overlay=True) which produces a matplotlib figure, this method saves the raw pixel data at full resolution, suitable for pixel-level quality validation of detection results.

For GridImage objects, gridlines and section boxes are automatically drawn when show_grid is True. The line widths scale dynamically with image size.

Parameters:
  • filepath (str | Path) – Destination file path. Should have .png or .jpeg extension.

  • overlay_alpha (float) – Alpha value for label overlay (0.0 = transparent, 1.0 = opaque). Defaults to 0.3.

  • bg_label (int) – Label value to treat as background. Defaults to 0.

  • colors (list | None) – List of RGB colors to use for labels. If None, uses default colormap.

  • show_grid (bool) – Whether to draw gridlines and section boxes on overlay for GridImage objects. Ignored for regular Image objects. Defaults to True.

  • gridline_color (tuple[int, int, int]) – RGB color tuple for gridlines. Defaults to cyan (0, 255, 255).

  • section_box_colors (list[tuple[int, int, int]] | None) – List of RGB tuples for cycling through section box colors. Defaults to tab20 colormap colors.

  • **label2rgb_kwargs – Additional keyword arguments for label2rgb.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

Examples

Save full-resolution overlay:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> image.rgb.save_overlay("overlay_rgb.png", overlay_alpha=0.4)
show(figsize=None, title=None, cmap: str = 'tab10', ax: None | plt.Axes = None, mpl_params: None | dict = None) tuple[plt.Figure, plt.Axes][source]#

Display the object map using matplotlib’s imshow.

This method visualizes the labeled object map using matplotlib. Each unique label is assigned a distinct color from the specified colormap, allowing visual distinction between labeled objects. The background (label 0) is typically shown in a neutral color.

Parameters:
  • figsize (tuple[int, int], optional) – Tuple specifying the figure size in inches (width, height). If None, uses the default matplotlib figure size. Defaults to None.

  • title (str, optional) – Title text for the plot. If None, no title is displayed. Defaults to None.

  • cmap (str, optional) – The colormap name used for rendering the labeled object map. A diverse colormap like ‘nipy_spectral’ is recommended for clearly distinguishing between many objects. Defaults to ‘nipy_spectral’.

  • ax (plt.Axes, optional) – Existing Matplotlib Axes object where the object map will be plotted. If None, a new figure and axes are created. Defaults to None.

  • mpl_params (dict, optional) – Additional parameters passed to matplotlib’s imshow function for plot customization (e.g., interpolation, normalization). If None, no extra parameters are applied. Defaults to None.

Returns:

A tuple containing the matplotlib Figure

and Axes objects where the object map is rendered. If an existing axes was provided, its parent figure is returned.

Return type:

tuple[plt.Figure, plt.Axes]

Examples

Displaying the object map with various options:

>>> # Basic visualization
>>> fig = image.objmap.show()
>>> # Custom figure size and title
>>> fig = image.objmap.show(figsize=(10, 8), title='Labeled Objects')
>>> # Use a different colormap
>>> fig = image.objmap.show(cmap='tab20')
val_range() pd.Interval#

Return the closed interval [min, max] of the subject array values.

Returns:

A single closed interval including both endpoints.

Return type:

pd.Interval

vmax() int[source]#

Returns the maximum value for the object map data type.

Object maps are stored as uint16 arrays where values represent object labels. This returns the theoretical maximum value that the uint16 dtype can represent.

Return type:

int

vmin() int[source]#

Returns the minimum value for the object map data type.

Object maps are stored as uint16 arrays where 0 represents background. This returns the theoretical minimum value that the uint16 dtype can represent.

Return type:

int

property dtype#
property nbytes: int#
property ndim: int#

Returns the number of dimensions of the underlying array.

The ndim property provides access to the dimensionality of the array being encapsulated in the object. This value corresponds to the number of axes or dimensions the underlying array possesses. It can be useful for understanding the structure of the contained data.

Returns:

The number of dimensions of the underlying array.

Return type:

int

property shape: tuple[int, int]#

Return the shape of the object map.

Returns the dimensions of the object map as a tuple of (height, width). This matches the shape of the parent image’s grayscale representation.

Returns:

A tuple (height, width) representing the spatial

dimensions of the object map.

Return type:

tuple[int, int]

Examples

Getting object map dimensions:

>>> height, width = image.objmap.shape
>>> print(f"Object map dimensions: {width}x{height}")
property size: int#

Gets the size of the subject array.

This property retrieves the total number of elements in the subject array. It is read-only.

Returns:

The total number of elements in the subject array.

Return type:

int

property Image.objmask: ObjectMask#

Returns the ObjectMask Accessor; The object mask is a mutable binary representation of the objects in an image to be analyzed. Changing elements of the mask will reset object_map labeling.

Note

  • If the image has not been processed by a detector, the target for analysis is the entire image itself. Accessing the object_mask in this case

    will return a 2-D array entirely with other_image 1 that is the same shape as the gray

  • Changing elements of the mask will relabel of objects in the object_map

Returns:

A mutable binary representation of the objects in an image to be analyzed.

Return type:

ObjectMaskErrors

from phenotypic import Image
from phenotypic.data import load_colony

image = Image(load_colony())

# get the objmask data
arr = image.objmask[:]
print(type(arr))

# set the objmask data
# the shape of the new objmask must be the same shape as the original objmask
image.objmask[:] = arr

# without the bracket indexing the accessor is returned instead
print(image.objmask[:])

See Also: ObjectMask

The objmask property provides access to the binary mask of all detected objects, where pixels belonging to any object are marked as True/1 and background pixels are marked as False/0.

ObjectMask API:

class phenotypic._core._image_parts.accessors._objmask_accessor.ObjectMask(root_image: Image)[source]#

Bases: NapariLabelsMixin, SingleChannelAccessor

Manages a binary object mask linked to a parent image.

ObjectMask provides array-like access and manipulation of a binary mask indicating object locations in the parent image. It supports slicing, item assignment, copying, and visualization. The mask is backed by a sparse representation stored in the parent image’s sparse_object_map, with automatic relabeling via scikit-image’s label() function to maintain consistent object IDs after modifications.

The object mask distinguishes between foreground (object) pixels (True) and background pixels (False). Any modification to the mask via __setitem__ triggers automatic relabeling to ensure object label consistency across the parent image’s object map.

Parameters:

root_image (Image)

_root_image#

The parent Image object containing this mask.

Type:

Image

Note

Changes to the object mask will trigger automatic relabeling of the object map to maintain consistent object IDs. This ensures data integrity when mask regions are directly modified.

Examples

Basic access and slicing:

>>> # Access the mask as a dense array
>>> mask_array = np.array(objmask)
>>> # Slice operations
>>> region = objmask[10:50, 20:60]
>>> # Modify mask regions
>>> objmask[10:50, 20:60] = np.zeros((40, 40))

Creating foreground images:

>>> # Get foreground of a grayscale channel
>>> foreground = image.gray.foreground()
classmethod load(filepath: str | Path) numpy.ndarray#

Load an image array from file and verify it was saved from this accessor type.

Checks if the image contains PhenoTypic metadata indicating it was saved from the same accessor type (e.g., Image.gray, Image.rgb). If metadata doesn’t match or is missing, a warning is raised but the array is still loaded.

Parameters:

filepath (str | Path) – Path to the image file to load.

Returns:

The loaded image array.

Return type:

np.ndarray

Warns:

UserWarning – If metadata is missing or indicates the image was saved from a different accessor type.

Examples

Load a grayscale image from file:

>>> from phenotypic import Image
>>> image = Image(arr)
>>> # load an object map you saved or hand-graded
>>> image.objmap.load("path/to/map.png")
__and__(other) numpy.ndarray[source]#

Perform element-wise logical AND with the object mask.

Returns the element-wise logical AND between the mask and another operand. This is useful for intersecting masks or filtering foreground regions based on additional criteria.

Parameters:

other – Operand to AND with. Can be: - ObjectMask: Uses its binary mask values - np.ndarray or array-like: Must match mask shape - bool: Applied to all mask elements

Returns:

Bool array with AND results (True/False).

Return type:

np.ndarray

Raises:

ValueError – If array operand shape doesn’t match mask shape.

Examples

Intersect two masks to find overlapping regions:

>>> mask1 = image.objmask
>>> mask2 = other_image.objmask
>>> intersection = mask1 & mask2
>>> print(intersection.shape)
(1024, 1024)

Filter mask using a region of interest:

>>> roi = np.zeros(image.shape, dtype=bool)
>>> roi[100:500, 100:500] = True
>>> filtered_mask = image.objmask & roi
__array__(dtype=None, copy=None)[source]#

Implement the NumPy array interface for seamless integration with NumPy.

Converts the sparse binary mask to a dense NumPy array, enabling direct use of NumPy functions and operations on the ObjectMask. The mask is returned as bool (False for background, True for foreground).

Parameters:
  • dtype (type, optional) – Target NumPy dtype for the returned array. If None, defaults to bool. Defaults to None.

  • copy (bool, optional) – If True, ensures the returned array is a copy. Ignored if dtype is specified. For NumPy 2.0+ compatibility. Defaults to None.

Returns:

A dense bool array of shape matching the parent image

(True/False values), or the specified dtype.

Return type:

np.ndarray

Examples

Using with NumPy functions and type conversion:

>>> # Use with NumPy functions
>>> num_foreground_pixels = np.count_nonzero(objmask)
>>> total_pixels = np.sum(objmask)
>>> # Explicit type conversion
>>> mask_float = np.array(objmask, dtype=np.float32)
__getitem__(key)[source]#

Return a slice of the binary object mask with NumPy-compatible indexing.

Supports all standard NumPy slicing operations (integer indexing, slice objects, boolean masks, fancy indexing). The sparse mask is converted to dense form and bool values (True/False) are returned.

Parameters:

key (int, slice, tuple, np.ndarray) – Index or slice to retrieve. Follows NumPy indexing conventions for 2D arrays.

Returns:

Bool array (True/False values) representing the sliced region of

the mask, with the same shape as the indexed region.

Return type:

np.ndarray

Examples

NumPy slicing and indexing operations:

>>> # Single row
>>> row = objmask[10]
>>> # Rectangular region
>>> region = objmask[10:50, 20:60]
>>> # Column
>>> col = objmask[:, 5]
>>> # Boolean indexing
>>> foreground_indices = objmask > 0
__iand__(other) ObjectMask[source]#

Perform in-place logical AND with the object mask.

Modifies the mask by performing element-wise logical AND with another operand. After modification, automatically relabels the mask to maintain consistent object IDs.

Parameters:

other – Operand to AND with. Can be: - ObjectMask: Uses its binary mask values - np.ndarray or array-like: Must match mask shape - bool: Applied to all mask elements

Returns:

Returns self for method chaining.

Return type:

ObjectMask

Raises:

ValueError – If array operand shape doesn’t match mask shape.

Examples

Remove objects outside a region of interest:

>>> roi = np.zeros(image.shape, dtype=bool)
>>> roi[100:900, 100:900] = True
>>> image.objmask &= roi  # Keep only objects in ROI

Intersect with another detection result:

>>> from phenotypic.detect import OtsuDetector
>>> detector = OtsuDetector()
>>> refined_result = detector.apply(image)
>>> image.objmask &= refined_result.objmask

Note

This operation triggers automatic relabeling of the object map to maintain consistent object IDs. Existing object labels may change.

__invert__() numpy.ndarray[source]#

Perform logical NOT on the object mask.

Returns the logical negation of the mask, inverting foreground and background pixels. Useful for selecting background regions or creating inverted masks.

Returns:

Bool array with inverted mask values.

Return type:

np.ndarray

Examples

Get background pixels for analysis:

>>> background_mask = ~image.objmask
>>> background_intensity = image.gray[:][background_mask].mean()

Combine with other operations to exclude detected regions:

>>> from phenotypic.enhance import GaussianBlur
>>> # Apply blur only to background
>>> blurred = GaussianBlur(sigma=2.0).operate(image)
>>> combined = image.gray[:] * image.objmask[:] + blurred.gray[:] * ~image.objmask
__ior__(other) ObjectMask[source]#

Perform in-place logical OR with the object mask.

Modifies the mask by performing element-wise logical OR with another operand. Useful for progressively adding detections or manually expanding mask regions. Automatically relabels after modification.

Parameters:

other – Operand to OR with. Can be: - ObjectMask: Uses its binary mask values - np.ndarray or array-like: Must match mask shape - bool: Applied to all mask elements

Returns:

Returns self for method chaining.

Return type:

ObjectMask

Raises:

ValueError – If array operand shape doesn’t match mask shape.

Examples

Add manually drawn colonies to existing detection:

>>> manual_additions = np.zeros(image.shape, dtype=bool)
>>> manual_additions[250:300, 250:300] = True
>>> image.objmask |= manual_additions

Combine multiple detection passes:

>>> for detector in [OtsuDetector(), CannyDetector(), WatershedDetector()]:
>>>     result = detector.operate(image)
>>>     image.objmask |= result.objmask

Note

This operation triggers automatic relabeling, which may change existing object IDs. Use with caution in measurement workflows.

__ixor__(other) ObjectMask[source]#

Perform in-place logical XOR with the object mask.

Modifies the mask by performing element-wise logical XOR with another operand. Useful for selectively toggling mask regions or removing specific patterns. Automatically relabels after modification.

Parameters:

other – Operand to XOR with. Can be: - ObjectMask: Uses its binary mask values - np.ndarray or array-like: Must match mask shape - bool: Applied to all mask elements

Returns:

Returns self for method chaining.

Return type:

ObjectMask

Raises:

ValueError – If array operand shape doesn’t match mask shape.

Examples

Toggle mask values in a specific region:

>>> toggle_region = np.zeros(image.shape, dtype=bool)
>>> toggle_region[300:400, 300:400] = True
>>> image.objmask ^= toggle_region  # Flip mask in region

Remove overlapping regions between two masks:

>>> artifact_mask = artifact_detector.operate(image).objmask
>>> image.objmask ^= artifact_mask & image.objmask

Note

This operation triggers automatic relabeling. XOR with True will invert the entire mask.

__len__() int#

Returns the length of the subject array.

This method calculates and returns the total number of elements contained in the underlying array.

Returns:

The number of elements in the underlying array attribute.

Return type:

int

__or__(other) numpy.ndarray[source]#

Perform element-wise logical OR with the object mask.

Returns the element-wise logical OR between the mask and another operand. Useful for combining masks from different detections or merging foreground regions.

Parameters:

other – Operand to OR with. Can be: - ObjectMask: Uses its binary mask values - np.ndarray or array-like: Must match mask shape - bool: Applied to all mask elements

Returns:

Bool array with OR results (True/False).

Return type:

np.ndarray

Raises:

ValueError – If array operand shape doesn’t match mask shape.

Examples

Combine detections from two different methods:

>>> from phenotypic.detect import OtsuDetector, CannyDetector
>>> otsu_result = OtsuDetector().operate(image)
>>> canny_result = CannyDetector().operate(image)
>>> combined_mask = otsu_result.objmask | canny_result.objmask

Add a manually drawn region to existing detections:

>>> manual_region = np.zeros(image.shape, dtype=bool)
>>> manual_region[50:150, 50:150] = True
>>> expanded_mask = image.objmask | manual_region
__setitem__(key, value: numpy.ndarray)[source]#

Update mask values at specified locations with automatic relabeling.

Sets mask values at the specified indices or slices, then automatically relabels the entire mask using scikit-image’s label() function to ensure object IDs remain consistent across the parent image. This maintains data integrity when mask regions are directly modified.

The operation accepts scalar values (0, 1, bool) or arrays. Non-zero values are converted to 1 (foreground), zero values to 0 (background). The updated mask is then relabeled atomically to avoid inconsistent states.

Parameters:
  • key (int, slice, tuple) – Index or slice indicating which mask elements to update. Follows NumPy indexing conventions.

  • value (int, bool, np.ndarray) – Value(s) to set. Scalars are normalized to binary (0 or 1). Arrays are converted to binary and must match the shape of mask[key].

Raises:

Examples

Setting mask regions with scalars and arrays:

>>> # Set a rectangular region to background (0)
>>> objmask[10:50, 20:60] = 0
>>> # Set with a matching array
>>> region_mask = np.ones((40, 40))
>>> objmask[10:50, 20:60] = region_mask
>>> # Set single pixel
>>> objmask[5, 10] = True

Note

The entire mask is relabeled after any modification. This ensures that object IDs in the parent image remain consistent, but may change existing object labels if the relabeling alters connectivity.

__xor__(other) numpy.ndarray[source]#

Perform element-wise logical XOR with the object mask.

Returns the element-wise logical XOR between the mask and another operand. Useful for finding symmetric differences between masks or detecting regions unique to each mask.

Parameters:

other – Operand to XOR with. Can be: - ObjectMask: Uses its binary mask values - np.ndarray or array-like: Must match mask shape - bool: Applied to all mask elements

Returns:

Bool array with XOR results (True/False).

Return type:

np.ndarray

Raises:

ValueError – If array operand shape doesn’t match mask shape.

Examples

Find regions detected by one method but not the other:

>>> detector1_mask = detector1.operate(image).objmask
>>> detector2_mask = detector2.operate(image).objmask
>>> unique_regions = detector1_mask ^ detector2_mask

Invert mask in a specific ROI:

>>> roi = np.zeros(image.shape, dtype=bool)
>>> roi[200:400, 200:400] = True
>>> modified = image.objmask ^ roi  # Flip bits in ROI
copy() numpy.ndarray[source]#

Return an independent copy of the binary object mask.

Creates a new bool array containing the same values (True/False) as the current mask. Modifications to the returned array do not affect the original mask or the parent image.

Returns:

A dense copy of the binary mask with bool dtype, independent

of the original sparse representation.

Return type:

np.ndarray

Examples

Creating an independent mask copy:

>>> # Create a modifiable copy for processing
>>> mask_copy = objmask.copy()
>>> mask_copy[10:50, 20:60] = 0  # Doesn't affect objmask
dash(figsize: tuple[int, int] | None = None, title: str | None = None, cmap: str | None = 'gray', foreground_only: bool = False, overlay: bool = True, *, object_label: int | None = None, show_labels: bool = False, show_grid: bool = True, label_settings: dict | None = None, overlay_settings: dict | None = None, plotly_settings: dict | None = None) go.Figure#

Display the single-channel image data using Plotly.

Parameters:
  • figsize (tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • title (str | None) – Title of the plot. If None, no title is displayed.

  • cmap (str | None) – Colormap name. Defaults to "gray".

  • foreground_only (bool) – If True, display only foreground elements.

  • overlay (bool) – If True, overlay the object map on the image. Falls back to plain image when no objects are detected.

  • object_label (int | None) – Specific object label to highlight. If None, shows all detected objects. Only used when overlay is True.

  • show_labels (bool) – If True, displays numeric labels at object centroids. Only used when overlay is True.

  • show_grid (bool) – For GridImage only. If True, draws gridlines and colored section boxes. Only used when overlay is True. Ignored for regular Image.

  • label_settings (dict | None) – Dict passed to text label rendering.

  • overlay_settings (dict | None) – Dict passed to overlay composition.

  • plotly_settings (dict | None) – Additional Plotly layout settings.

Returns:

A plotly.graph_objects.Figure.

Raises:

ImportError – If plotly is not installed.

Return type:

go.Figure

draw(*, viewer: NapariViewer | None = None) Image#

Open a napari editor to hand-edit these labels and save them back.

Launches a blocking PyQt napari viewer showing rgb (when present), gray, and detect_mat as image layers plus this accessor’s data as an editable labels layer. Use napari’s built-in paintbrush, fill, and eraser tools to correct the segmentation, then click Save to Image in the dock panel to commit the edits to the parent image (or Discard & Close to abandon them).

Editing objmap preserves the original integer label IDs. Editing objmask is strictly binary and relabels the object map on save (skimage.measure.label), so original IDs are not retained — call image.objmap.draw() instead when stable IDs matter. Preview the existing detections with show() or napari() before editing.

Parameters:

viewer (NapariViewer | None) – Optional existing napari viewer to reuse instead of opening a fresh one. Defaults to None.

Returns:

The parent image, mutated in place if edits were saved.

Return type:

Image

Raises:

ImportError – If napari is not installed. Install with pip install phenotypic[napari].

Examples

Hand-correct an auto-detected object map:

>>> from phenotypic.data import load_synth_yeast_plate
>>> from phenotypic.detect import OtsuDetector
>>> image = OtsuDetector().apply(load_synth_yeast_plate())
>>> image = image.objmap.draw()
foreground()#

Extracts and returns the foreground of the image by masking out the background.

This method generates a foreground image by applying the object mask stored in the Image to the current array representation. Pixels outside the object mask are set to zero in the resulting foreground image. This is useful in image processing tasks to isolate the region of interest in the image, such as microbe colonies on an agar plate.

Returns:

A numpy array containing only the foreground portion of the image, with all non-foreground pixels set to zero.

Return type:

numpy.ndarray

histogram(figsize: Tuple[int, int] = (10, 5), *, cmap='gray', linewidth=1, channel_names: list | None = None) Tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Plots the histogram(s) of an image along with the image itself. The behavior depends on the dimensionality of the image array (2D or 3D). In the case of 2D, a single image and its histogram are produced. For 3D (multi-channel images), histograms for each channel are created alongside the image. This method supports customization such as figure size, colormap, line width of histograms, and labeling of channels.

Parameters:
  • figsize (Tuple[int, int]) – The size of the figure to create. Default is (10, 5).

  • cmap (str) – Colormap used to render the image when the data is single channel. Default is ‘gray’.

  • linewidth (int) – Line width of the plotted histograms. Default is 1.

  • channel_names (list | None) – Optional names for the channels in 3D data. These are used as titles for channel-specific histograms. If None, channels are instead labeled numerically.

Returns:

The Matplotlib figure and axes objects representing the plotted image and its histograms.

Return type:

Tuple[plt.Figure, plt.Axes]

Raises:

ValueError – If the dimensionality of the input image array is unsupported.

Notes

This method uses skimage.exposure.histogram for computing the histogram data.

imsave(filepath: str | Path | None = None, bit_depth: Literal[8, 16] | None = None) None#

Saves an array representing a microbe colony image to a specified file format while preserving or adjusting metadata and pixel depth as needed. Supports JPEG, PNG, and TIFF formats.

The behavior of the function is context-sensitive based on the file format’s restrictions and array properties. Proper file format selection and bit depth adjustment can have an impact on the accuracy of image analysis and preservation of data integrity.

Parameters:
  • filepath (str | Path | None) –

    The destination file path where the image will be saved. The extension of the file path determines the image format (e.g., .jpeg, .png, .tiff). Changing the file format influences how the image data is handled during saving:

    1. .jpeg: Compression or loss of data may occur. Maximal value limit (255) for uint8 pixel depth affects the fidelity of rich intensity details in microbe colonies.

    2. .png: Retains high-quality output but supports only 8-bit or 16-bit images. Conversions may occur if the array has a different data type, which could result in data loss.

    3. .tiff: Ideal for high-bit-depth precision and analysis preservation; best for maintaining intricate morphological details of microbial colonies.

  • bit_depth (Literal[8, 16] | None, optional) –

    Specifies the bit depth of the saved image (either 8-bit or 16-bit). The provided bit depth must align with the file format’s capabilities. Misalignment could trigger conversion with possible data truncation or rounding. For example:

    • 8-bit: Useful for efficiently representing intensity when detail is moderate, suitable for JPEG or simple PNG outputs.

    • 16-bit: Allows for higher intensity ranges, especially valuable for preserving subtle morphological gradient differentiation when analyzing colonies.

Raises:

ValueError – An error occurs when an unsupported file extension is provided in filepath.

Warns:

UserWarnings – Warnings are issued under the following conditions:

  • Saving a 16-bit or floating-point array as JPEG, as these conversions may cause information loss due to format restrictions.

  • Saving a floating-point array as PNG when conversions to 8-bit or 16-bit integers might lead to truncated or altered pixel intensity values.

Return type:

None

isempty()#
napari(name: str | None = None, reset: bool = False, colormap: dict | None = None, opacity: float = 0.7, contour: int = 0, *, viewer: napari.Viewer | None = None, layer_name: str | None = None) napari.Viewer#

Add labeled regions to a persistent global napari viewer.

Creates or reuses a single napari viewer instance with a labels layer for visualizing discrete labeled regions. Unlike the base class image layer implementation, this uses napari’s labels API which assigns distinct colors to each unique label value, making it ideal for visualizing segmented colonies, object maps, and binary masks.

The viewer persists across multiple method calls, allowing comparison of different processing stages and data types in the same visualization window.

Parameters:
  • name (str | None) – Optional custom name for the labels layer. If provided, the layer will be named {accessor}_{name}. If not provided, defaults to using the image’s name attribute. Defaults to None.

  • reset (bool) – If True, closes the current napari viewer and creates a fresh one. This is useful for starting a new visualization session without lingering layers from previous calls. Defaults to False.

  • colormap (dict | None) – Optional dictionary mapping label values to RGB colors. Keys are integer label IDs, values are RGB tuples/lists with values in [0, 1] range. If None, napari uses its default label colormap which assigns distinct colors automatically. Example: {1: [1.0, 0, 0], 2: [0, 1.0, 0]} for red/green colonies. Defaults to None.

  • opacity (float) – Layer opacity from 0.0 (fully transparent) to 1.0 (fully opaque). This controls the visibility of the labels layer. Values outside this range will raise ValueError. Defaults to 0.7.

  • contour (int) – Contour thickness in pixels. When > 0, renders only the outline of each labeled region rather than filled regions. Useful for overlaying object boundaries on other layers. Must be >= 0. Defaults to 0 (filled).

  • viewer (napari.Viewer | None) – Optional external napari viewer instance to use instead of the global viewer. When provided, global viewer management (creation, reset, smart-grid installation) is bypassed entirely. Defaults to None.

  • layer_name (str | None) – Optional full layer name to use instead of the auto-generated {accessor}_{image_name} pattern. Defaults to None.

Returns:

The global napari viewer instance with the labeled regions

added as a labels layer.

Return type:

napari.Viewer

Raises:

ValueError – If opacity is not in [0.0, 1.0] range or contour is negative.

Examples

Basic labels visualization:

>>> from phenotypic import Image
>>> from phenotypic.detect import OtsuDetector
>>> img = Image.imread("colonies.jpg")
>>> detector = OtsuDetector()
>>> img = detector.apply(img)
>>>
>>> # View detected colonies as labels
>>> viewer = img.objmap.napari()

Customizing labels appearance:

>>> # Semi-transparent labels with contours
>>> viewer = img.objmap.napari(opacity=0.5, contour=2)
>>>
>>> # Custom color mapping for specific colonies
>>> cmap = {
...     1: [1.0, 0, 0],      # Red for colony 1
...     2: [0, 1.0, 0],      # Green for colony 2
...     3: [0, 0, 1.0],      # Blue for colony 3
... }
>>> viewer = img.objmap.napari(colormap=cmap)

Comparing masks and maps in same viewer:

>>> # Add grayscale base image
>>> viewer = img.gray.napari()
>>>
>>> # Overlay binary mask with transparency
>>> viewer = img.objmask.napari(opacity=0.4)
>>>
>>> # Add full object map with contours only
>>> viewer = img.objmap.napari(name="boundaries", contour=1)

Using reset for fresh visualization sessions:

>>> viewer = img.objmap.napari()
>>> # ... do some analysis ...
>>> # Start fresh without old layers
>>> viewer = img.objmap.napari(reset=True)

Note

Labels layers are specifically designed for discrete/categorical data where each unique integer value represents a distinct object or region. This is fundamentally different from image layers which handle continuous intensity data. Labels layers provide:

  • Automatic distinct coloring per label

  • Contour visualization mode

  • Optimized rendering for segmentation data

  • Better interactivity for object selection

For colony analysis, this means each detected colony gets a unique color, making visual QC and counting much easier than grayscale intensity display.

reset()[source]#

Reset the object mask and linked object map to a cleared state.

Delegates to the parent image’s object map’s reset method, which clears the mask and resets all object labels and properties. This is useful when re-segmenting the image or clearing previous detection results.

Examples

Resetting the mask to cleared state:

>>> # Clear the mask and object map
>>> objmask.reset()
>>> # Now objmask contains only background (0s)

Note

This operation affects both the object mask and the parent image’s object map and properties. Use with caution as it discards all segmentation data.

save_overlay(filepath: str | Path, overlay_alpha: float = 0.3, bg_label: int = 0, colors: list | None = None, show_grid: bool = True, gridline_color: tuple[int, int, int] = (0, 255, 255), section_box_colors: list[tuple[int, int, int]] | None = None, **label2rgb_kwargs) None#

Save a full-resolution overlay image blending objmap with the subject array.

Creates an RGB overlay by blending the object map labels with the underlying image data and saves it to disk. Unlike show(overlay=True) which produces a matplotlib figure, this method saves the raw pixel data at full resolution, suitable for pixel-level quality validation of detection results.

For GridImage objects, gridlines and section boxes are automatically drawn when show_grid is True. The line widths scale dynamically with image size.

Parameters:
  • filepath (str | Path) – Destination file path. Should have .png or .jpeg extension.

  • overlay_alpha (float) – Alpha value for label overlay (0.0 = transparent, 1.0 = opaque). Defaults to 0.3.

  • bg_label (int) – Label value to treat as background. Defaults to 0.

  • colors (list | None) – List of RGB colors to use for labels. If None, uses default colormap.

  • show_grid (bool) – Whether to draw gridlines and section boxes on overlay for GridImage objects. Ignored for regular Image objects. Defaults to True.

  • gridline_color (tuple[int, int, int]) – RGB color tuple for gridlines. Defaults to cyan (0, 255, 255).

  • section_box_colors (list[tuple[int, int, int]] | None) – List of RGB tuples for cycling through section box colors. Defaults to tab20 colormap colors.

  • **label2rgb_kwargs – Additional keyword arguments for label2rgb.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

Examples

Save full-resolution overlay:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> image.rgb.save_overlay("overlay_rgb.png", overlay_alpha=0.4)
show(ax: plt.Axes | None = None, figsize: tuple[int, int] | None = None, cmap: str = 'gray', title: str | None = None) tuple[plt.Figure, plt.Axes][source]#

Display the binary object mask as a Matplotlib image.

Renders the object mask using Matplotlib’s imshow with customizable appearance. The mask is shown as a grayscale image where white represents foreground (objects) and black represents background.

Parameters:
  • ax (plt.Axes, optional) – An existing Matplotlib Axes object to plot on. If None, a new figure and axes are created. Defaults to None.

  • figsize (tuple[int, int], optional) – Size of the figure in inches as (width, height). Only used if ax is None. Defaults to None (uses default size).

  • cmap (str, optional) – Colormap to apply. Defaults to ‘gray’, which shows foreground pixels in white and background in black.

  • title (str, optional) – Title for the plot. If None, no title is displayed. Defaults to None.

Returns:

Matplotlib Figure and Axes objects containing

the rendered mask.

Return type:

tuple[plt.Figure, plt.Axes]

Examples

Displaying the mask with various options:

>>> # Display with default settings
>>> fig = objmask.show()
>>> # Display with custom size and title
>>> fig = objmask.show(figsize=(8, 8), title='Object Mask')
val_range() pd.Interval#

Return the closed interval [min, max] of the subject array values.

Returns:

A single closed interval including both endpoints.

Return type:

pd.Interval

vmax() bool[source]#

Returns the maximum value for the object mask (True for foreground).

Return type:

bool

vmin() bool[source]#

Returns the minimum value for the object mask (False for background).

Return type:

bool

property dtype#
property nbytes: int#
property ndim: int#

Returns the number of dimensions of the underlying array.

The ndim property provides access to the dimensionality of the array being encapsulated in the object. This value corresponds to the number of axes or dimensions the underlying array possesses. It can be useful for understanding the structure of the contained data.

Returns:

The number of dimensions of the underlying array.

Return type:

int

property shape#

Return the shape of the object mask.

The shape is always identical to the parent image’s shape, since the mask covers the entire image extent.

Returns:

Shape of the mask as (height, width), matching the

parent image dimensions.

Return type:

tuple[int, …]

Examples

Accessing mask shape:

>>> height, width = objmask.shape
>>> assert objmask.shape == image.gray.shape
property size: int#

Gets the size of the subject array.

This property retrieves the total number of elements in the subject array. It is read-only.

Returns:

The total number of elements in the subject array.

Return type:

int

Color Space Operations#

property Image.color: ColorAccessor

Access all color space representations through a unified interface.

This property provides access to the ColorAccessor object, which groups all color space transformations and representations including:

  • XYZ: CIE XYZ color space

  • XYZ_D65: CIE XYZ under D65 illuminant

  • Lab: CIE L*a*b* perceptually uniform color space

  • xy: CIE xy chromaticity coordinates

  • hsv: HSV (Hue, Saturation, Value) color space

Returns:

Unified accessor for all color space representations.

Return type:

ColorAccessor

Examples

Access color spaces:

>>> img = Image.imread('sample.jpg')
>>> xyz_data = img.color.XYZ[:]
>>> lab_data = img.color.Lab[:]
>>> hue = img.color.hsv[..., 0] # hue is the first matrix in the array

The color property provides unified access to multiple color space representations through the ColorAccessor interface. This groups together device-dependent (HSV) and CIE standard color spaces (XYZ, XYZ-D65, L*a*b*, xy chromaticity).

All color space conversions are computed on-demand and cached to avoid redundant computations. The parent Image configuration (illuminant, observer, gamma) is used consistently across all transformations.

ColorAccessor API:

class phenotypic._core._image_parts.accessors._color_accessor.ColorAccessor(root_image: Image)[source]#

Bases: object

Provides unified access to all color space representations of an image.

This accessor class serves as a facade to multiple specialized color space accessors, grouping together various color space transformations and representations. It provides convenient access to both device-dependent color spaces (HSV) and CIE standard color spaces (XYZ, XYZ-D65, L*a*b*, and xy chromaticity).

All color space conversions are computed on-demand and returned as read-only numpy arrays. Individual accessor objects maintain caching to avoid redundant computations when the same color space is accessed multiple times.

The parent Image object configuration (illuminant, _observer model, gamma encoding) is used consistently across all color space transformations to ensure coherent color space analysis.

Parameters:

root_image (Image)

_root_image#

The parent Image object that this accessor is bound to. Used to access raw RGB/grayscale data and color properties (illuminant, _observer).

Type:

Image

_xyz#

Accessor for CIE XYZ color space representation.

Type:

XyzAccessor

_xyz_d65#

Accessor for CIE XYZ under D65 illuminant.

Type:

XyzD65Accessor

_cielab#

Accessor for perceptually uniform L*a*b* color space.

Type:

CieLabAccessor

_xy#

Accessor for CIE xy chromaticity coordinates.

Type:

xyChromaticityAccessor

_hsv#

Accessor for HSV color space representation.

Type:

HsvAccessor

Examples

Access different color spaces from an image:

>>> from phenotypic import Image
>>> img = Image.imread('sample.jpg')
>>> # Access CIE XYZ color space
>>> xyz_data = img.color.XYZ[:]
>>> print(xyz_data.shape)  # (height, width, 3)
>>> # Access perceptually uniform Lab color space
>>> lab_data = img.color.Lab[:]
>>> # Access HSV components (hue is first channel)
>>> hue_channel = img.color.hsv[..., 0]
>>> saturation_channel = img.color.hsv[..., 1]
>>> brightness_channel = img.color.hsv[..., 2]
>>> # Access chromaticity coordinates
>>> xy_coords = img.color.xy[:]

Use color spaces for analysis:

>>> import numpy as np
>>> # Calculate color differences using Lab space
>>> lab_data = img.color.Lab[:]
>>> differences = np.sqrt(
...     (lab_data[..., 0] - 50)**2 +
...     (lab_data[..., 1] - 25)**2 +
...     (lab_data[..., 2] - 10)**2
... )
>>> # Extract hue for color classification
>>> hue = img.color.hsv[..., 0] * 360  # Convert to degrees
>>> red_mask = (hue < 30) | (hue > 330)
__init__(root_image: Image)[source]#

Initialize the ColorAccessor with a reference to the parent image.

Creates all subordinate color space accessor objects, each providing specialized access to a specific color space representation. All accessors share the same parent Image reference to ensure consistent color properties (illuminant, _observer) are used across all color space transformations.

Parameters:

root_image (Image) – The Image object that this accessor is bound to. Used to access RGB/grayscale data and color configuration properties (illuminant, _observer, gamma encoding).

Examples

Access ColorAccessor through Image.color property:

>>> from phenotypic import Image
>>> img = Image.imread('photo.jpg')
>>> # Direct initialization is not typically needed - use img.color instead
>>> accessor = img.color  # Returns ColorAccessor instance
property Lab: CieLabAccessor#

Access the CIE L*a*b* color space representation.

Provides access to the perceptually uniform CIE L*a*b* color space, derived from the image’s XYZ representation. The Lab color space is designed to approximate human visual perception, making it ideal for color analysis, color correction, and calculating perceptually meaningful color differences.

The three channels represent: - L* (lightness): Ranges from 0 (black) to 100 (white), representing perceptual brightness - a* (green-red opponent): Negative values indicate green, positive indicate red - b* (blue-yellow opponent): Negative values indicate blue, positive indicate yellow

Because Lab is perceptually uniform, Euclidean distances in Lab space correspond to perceptual color differences as seen by human observers. This makes it superior to RGB for color analysis and comparison tasks.

Returns:

Accessor providing numpy-like interface to Lab data.

Supports array indexing and slicing. Shape is (height, width, 3) where the three channels correspond to L*, a*, and b* values.

Return type:

CieLabAccessor

See also

XYZ: Device-independent color space basis for Lab calculation. XYZ_D65: XYZ under standard D65 illuminant (Lab typically computed from this).

Examples

Access Lab color space and calculate color differences:

>>> from phenotypic import Image
>>> import numpy as np
>>> img = Image.imread('photo.jpg')
>>> # Access Lab color space
>>> lab = img.color.Lab[:]
>>> L = img.color.Lab[..., 0]  # Lightness (0-100)
>>> a = img.color.Lab[..., 1]  # Green (-) to Red (+)
>>> b = img.color.Lab[..., 2]  # Blue (-) to Yellow (+)
>>> # Calculate perceptually meaningful color difference
>>> reference_lab = np.array([50, 0, 0])  # Mid-gray
>>> color_difference = np.sqrt(
...     (lab[..., 0] - reference_lab[0])**2 +
...     (lab[..., 1] - reference_lab[1])**2 +
...     (lab[..., 2] - reference_lab[2])**2
... )
>>> # Find pixels close to a reference color (within deltaE of 5)
>>> similar_mask = color_difference < 5
>>> # Adjust lightness across entire image
>>> brightened = lab.copy()
>>> brightened[..., 0] += 10  # Increase L* by 10
property XYZ: XyzAccessor#

Access the CIE XYZ color space representation.

Provides access to the CIE XYZ color space representation of the image, computed under the parent Image’s configured illuminant. XYZ is a device-independent color space that forms the basis for many other color space transformations.

The XYZ color space separates color information into lightness-related luminance (Y) and chromaticity values (X, Z). It is particularly useful as an intermediate representation for converting between different color spaces.

Returns:

Accessor providing numpy-like interface to XYZ data.

Supports array indexing and slicing. Shape is (height, width, 3) where the three channels correspond to X, Y, and Z values.

Return type:

XyzAccessor

See also

XYZ_D65: XYZ representation specifically under D65 illuminant conditions. xy: Normalized chromaticity coordinates derived from XYZ.

Examples

Access and work with XYZ color space:

>>> from phenotypic import Image
>>> img = Image.imread('photo.jpg')
>>> # Get full XYZ array
>>> xyz_array = img.color.XYZ[:]
>>> print(xyz_array.shape)  # (height, width, 3)
>>> # Extract individual channels
>>> X = img.color.XYZ[..., 0]
>>> Y = img.color.XYZ[..., 1]  # Luminance
>>> Z = img.color.XYZ[..., 2]
>>> # Slice specific region
>>> roi_xyz = img.color.XYZ[100:200, 100:200, :]
property XYZ_D65: XyzD65Accessor#

Access the CIE XYZ color space under D65 illuminant.

Provides XYZ representation specifically under D65 (standard daylight) illuminant viewing conditions. If the parent Image uses a different illuminant (e.g., D50), chromatic adaptation is automatically applied to transform the data to D65 conditions.

D65 is the CIE standard daylight illuminant with a color temperature of approximately 6504 K. It is the most commonly used illuminant in color science, photography, and display technology. Using D65 as a reference standard enables comparison of color data across different imaging systems.

Returns:

Accessor providing numpy-like interface to XYZ D65 data.

Supports array indexing and slicing. Shape is (height, width, 3) where the three channels correspond to X, Y, and Z values under D65 conditions.

Return type:

XyzD65Accessor

See also

XYZ: XYZ representation under the image’s configured illuminant. Lab: Perceptually uniform color space (typically uses D65).

Examples

Access XYZ color space under D65 illuminant:

>>> from phenotypic import Image
>>> img = Image.imread('photo.jpg')
>>> # Get XYZ D65 representation
>>> xyz_d65 = img.color.XYZ_D65[:]
>>> # Use D65 for standardized color comparison
>>> luminance_d65 = img.color.XYZ_D65[..., 1]
>>> # If original illuminant differs from D65, chromatic adaptation is applied
>>> # For images originally in D65, this is equivalent to XYZ
property hsv: HsvAccessor#

Access the HSV (Hue, Saturation, Value) color space representation.

Provides access to the device-dependent HSV color space, which represents colors in a way that is intuitive for human color selection and manipulation. HSV is particularly useful for color-based filtering, hue-specific analysis, and applications where color properties need to be adjusted independently.

The three channels represent (all normalized to range [0, 1]): - H (hue): Color type, ranges from 0 to 1 (corresponds to 0 to 360 degrees) - S (saturation): Color intensity/purity, 0 (grayscale) to 1 (pure color) - V (value): Brightness/luminosity, 0 (black) to 1 (brightest)

HSV is computed from RGB and is device-dependent (unlike CIE color spaces). However, it is more intuitive for color-based operations like selecting all red pixels or adjusting hue.

The HsvAccessor includes additional analysis methods such as histograms and visualization of HSV components. Note: HSV is only available for RGB images; grayscale images do not have HSV representation.

Returns:

Accessor providing numpy-like interface to HSV data.

Supports array indexing and slicing. Shape is (height, width, 3) where the three channels correspond to H, S, V values (each in range [0, 1]).

Return type:

HsvAccessor

Raises:

AttributeError – If called on a grayscale image without RGB data.

See also

XYZ: Device-independent color space alternative. Lab: Perceptually uniform color space alternative.

Examples

Access HSV components and perform color-based filtering:

>>> from phenotypic import Image
>>> img = Image.imread('photo.jpg')
>>> # Access HSV components
>>> hsv = img.color.hsv[:]
>>> hue = img.color.hsv[..., 0]  # 0 to 1
>>> saturation = img.color.hsv[..., 1]  # 0 to 1
>>> brightness = img.color.hsv[..., 2]  # 0 to 1
>>> # Convert hue to degrees (0-360)
>>> hue_degrees = hue * 360
>>> # Extract red pixels (hue near 0 or near 360)
>>> red_hue = hue_degrees
>>> red_mask = (red_hue < 30) | (red_hue > 330)
>>> # Extract highly saturated colors
>>> saturated_mask = saturation > 0.5
>>> # Find bright colors
>>> bright_mask = brightness > 0.7
>>> # Visualize HSV components
>>> fig, axes = img.color.hsv.show()
property xy: xyChromaticityAccessor#

Access the CIE xy chromaticity coordinates.

Provides 2D chromaticity representation derived from the CIE XYZ color space. Chromaticity coordinates express color independently of luminance, isolating the hue and saturation information. This is particularly useful for color analysis, gamut visualization, and studying color without brightness variation.

The xy coordinates are derived from XYZ using the formulas: x = X / (X + Y + Z), y = Y / (X + Y + Z)

This normalized representation is device-independent and widely used in color science for visualizing color spaces on the CIE 1931 chromaticity diagram.

Returns:

Accessor providing numpy-like interface to xy data.

Supports array indexing and slicing. Shape is (height, width, 2) where the two channels correspond to x and y chromaticity coordinates (both in range [0, 1]).

Return type:

xyChromaticityAccessor

See also

XYZ: Full 3D color space representation including luminance. Lab: Perceptually uniform color space incorporating both chromaticity and lightness.

Examples

Access and visualize xy chromaticity coordinates:

>>> from phenotypic import Image
>>> import matplotlib.pyplot as plt
>>> img = Image.imread('photo.jpg')
>>> # Get chromaticity coordinates
>>> xy_coords = img.color.xy[:]
>>> x = img.color.xy[..., 0]
>>> y = img.color.xy[..., 1]
>>> # Plot color on CIE 1931 chromaticity diagram
>>> plt.scatter(x.flatten(), y.flatten(), c=img.rgb[:].reshape(-1, 3)/255)
>>> plt.xlabel('x')
>>> plt.ylabel('y')
>>> plt.show()
>>> # Analyze color composition without luminance effects
>>> roi_xy = img.color.xy[100:200, 100:200, :]

Detailed documentation for individual color space operations is available in a dedicated section.

Visualization & Plotting#

property Image.plot: PlotAccessor

The plot property provides quality-of-life visualizations for pipeline development, specifically designed for colony detection parameter tuning on arrayed microbe cultures on agar plates. These methods help understand how morphological operations, size filtering, and spatial patterns affect detection results.

All methods support flexible data requirements, automatically detecting whether labeled objects (objmap) or binary masks (objmask) are available.

PlotAccessor API:

class phenotypic._core._image_parts.accessors._plot_accessor.PlotAccessor(root_image: Image)[source]#

Bases: ImageAccessorBase

Provides quality-of-life plots for developing image processing pipelines.

This accessor offers sophisticated visualization methods to help understand how morphological operations, size filtering, and spatial patterns affect colony detection in arrayed microbial cultures on solid agar media. These plots are designed for pipeline development and parameter tuning rather than publication.

All methods support flexible data requirements, automatically detecting whether labeled objects (objmap) or binary masks (objmask) are available, and adapting their analysis accordingly.

Built-in plot methods (all, morph_progression, etc.) are defined as explicit methods for IDE autocomplete. User-registered plotters added via @register_plotter are still accessible through dynamic dispatch.

For overlay visualization, use image.show(overlay=True) or image.dash(overlay=True) instead.

Note

For large images (>3000x3000 pixels), memory usage can be significant. Caller is responsible for closing returned figures with plt.close(fig) after saving to free memory and prevent accumulation of matplotlib figure objects in memory.

Examples

Access plot methods through an Image instance:

>>> from phenotypic import Image
>>> from phenotypic.detect import OtsuDetector
>>> # Load and detect colonies
>>> image = Image.imread('plate.jpg')
>>> detector = OtsuDetector()
>>> detected = detector.apply(image)
>>> # Access plot methods
>>> fig, axes = detected.plot.morph_progression()
>>> plt.savefig('morph.png')
>>> plt.close(fig)  # Important: free memory
>>> fig, ax = detected.plot.size_distribution()
>>> plt.savefig('size.png')
>>> plt.close(fig)

List available plotters:

>>> from phenotypic._core._image_parts.plot_accessor import available_plotters
>>> print(available_plotters())
('all', 'diagnostics', 'morph_progression', ...)
Parameters:

root_image (Image)

classmethod load(filepath: str | Path) numpy.ndarray#

Load an image array from file and verify it was saved from this accessor type.

Checks if the image contains PhenoTypic metadata indicating it was saved from the same accessor type (e.g., Image.gray, Image.rgb). If metadata doesn’t match or is missing, a warning is raised but the array is still loaded.

Parameters:

filepath (str | Path) – Path to the image file to load.

Returns:

The loaded image array.

Return type:

np.ndarray

Warns:

UserWarning – If metadata is missing or indicates the image was saved from a different accessor type.

Examples

Load a grayscale image from file:

>>> from phenotypic import Image
>>> image = Image(arr)
>>> # load an object map you saved or hand-graded
>>> image.objmap.load("path/to/map.png")
__array__(dtype=None, copy=None)#

Implements the array interface for numpy compatibility.

This allows numpy functions to operate directly on accessor objects. For example: np.sum(accessor), np.mean(accessor), etc.

Parameters:
  • dtype – Optional dtype to cast the array to

  • copy – Optional copy parameter for NumPy 2.0+ compatibility

Returns:

The underlying array data

Return type:

np.ndarray

__dir__() list[str][source]#

Return list of available attributes including all plotter methods.

Return type:

list[str]

__getattr__(name: str) Any[source]#

Dispatch attribute access to user-registered plotter methods.

Built-in plotters are resolved as explicit methods above. This fallback handles plotters added at runtime via @register_plotter.

Parameters:

name (str) – Name of the plotter method to access.

Returns:

The bound method from the registered plotter instance.

Raises:

AttributeError – If name is not found on any registered plotter.

Return type:

Any

__init__(root_image: Image) None[source]#

Initialize PlotAccessor with a reference to the parent Image.

Parameters:

root_image (Image) – The parent Image instance containing detection results and image data.

Return type:

None

__len__() int#

Returns the length of the subject array.

This method calculates and returns the total number of elements contained in the underlying array.

Returns:

The number of elements in the underlying array attribute.

Return type:

int

all(*args: Any, **kwargs: Any) Any[source]#

Plot all available image data layers side by side.

See all().

Parameters:
Return type:

Any

boundary_displacement(*args: Any, **kwargs: Any) Any[source]#

Visualize boundary displacement from morphological operations.

See boundary_displacement().

Parameters:
Return type:

Any

copy() numpy.ndarray#
Return type:

numpy.ndarray

detect_modes(*args: Any, **kwargs: Any) Any[source]#

Faceted comparison of every registered detection mode.

See detect_modes().

Parameters:
Return type:

Any

diagnostics(*args: Any, **kwargs: Any) Any[source]#

Comprehensive image quality diagnostics dashboard.

See diagnostics().

Parameters:
Return type:

Any

foreground()#

Extracts and returns the foreground of the image by masking out the background.

This method generates a foreground image by applying the object mask stored in the Image to the current array representation. Pixels outside the object mask are set to zero in the resulting foreground image. This is useful in image processing tasks to isolate the region of interest in the image, such as microbe colonies on an agar plate.

Returns:

A numpy array containing only the foreground portion of the image, with all non-foreground pixels set to zero.

Return type:

numpy.ndarray

histogram(figsize: Tuple[int, int] = (10, 5), *, cmap='gray', linewidth=1, channel_names: list | None = None) Tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Plots the histogram(s) of an image along with the image itself. The behavior depends on the dimensionality of the image array (2D or 3D). In the case of 2D, a single image and its histogram are produced. For 3D (multi-channel images), histograms for each channel are created alongside the image. This method supports customization such as figure size, colormap, line width of histograms, and labeling of channels.

Parameters:
  • figsize (Tuple[int, int]) – The size of the figure to create. Default is (10, 5).

  • cmap (str) – Colormap used to render the image when the data is single channel. Default is ‘gray’.

  • linewidth (int) – Line width of the plotted histograms. Default is 1.

  • channel_names (list | None) – Optional names for the channels in 3D data. These are used as titles for channel-specific histograms. If None, channels are instead labeled numerically.

Returns:

The Matplotlib figure and axes objects representing the plotted image and its histograms.

Return type:

Tuple[plt.Figure, plt.Axes]

Raises:

ValueError – If the dimensionality of the input image array is unsupported.

Notes

This method uses skimage.exposure.histogram for computing the histogram data.

imsave(filepath: str | Path | None = None, bit_depth: Literal[8, 16] | None = None) None#

Saves an array representing a microbe colony image to a specified file format while preserving or adjusting metadata and pixel depth as needed. Supports JPEG, PNG, and TIFF formats.

The behavior of the function is context-sensitive based on the file format’s restrictions and array properties. Proper file format selection and bit depth adjustment can have an impact on the accuracy of image analysis and preservation of data integrity.

Parameters:
  • filepath (str | Path | None) –

    The destination file path where the image will be saved. The extension of the file path determines the image format (e.g., .jpeg, .png, .tiff). Changing the file format influences how the image data is handled during saving:

    1. .jpeg: Compression or loss of data may occur. Maximal value limit (255) for uint8 pixel depth affects the fidelity of rich intensity details in microbe colonies.

    2. .png: Retains high-quality output but supports only 8-bit or 16-bit images. Conversions may occur if the array has a different data type, which could result in data loss.

    3. .tiff: Ideal for high-bit-depth precision and analysis preservation; best for maintaining intricate morphological details of microbial colonies.

  • bit_depth (Literal[8, 16] | None, optional) –

    Specifies the bit depth of the saved image (either 8-bit or 16-bit). The provided bit depth must align with the file format’s capabilities. Misalignment could trigger conversion with possible data truncation or rounding. For example:

    • 8-bit: Useful for efficiently representing intensity when detail is moderate, suitable for JPEG or simple PNG outputs.

    • 16-bit: Allows for higher intensity ranges, especially valuable for preserving subtle morphological gradient differentiation when analyzing colonies.

Raises:

ValueError – An error occurs when an unsupported file extension is provided in filepath.

Warns:

UserWarnings – Warnings are issued under the following conditions:

  • Saving a 16-bit or floating-point array as JPEG, as these conversions may cause information loss due to format restrictions.

  • Saving a floating-point array as PNG when conversions to 8-bit or 16-bit integers might lead to truncated or altered pixel intensity values.

Return type:

None

isempty()#
morph_progression(*args: Any, **kwargs: Any) Any[source]#

Show effects of morphological operations at increasing kernel sizes.

See morph_progression().

Parameters:
Return type:

Any

napari(name: str | None = None, reset: bool = False, *, viewer: napari.Viewer | None = None, layer_name: str | None = None) napari.Viewer#

Add image to a persistent global napari viewer for Jupyter workflows.

Creates or reuses a single napari viewer instance that persists across multiple method calls. This is particularly useful in Jupyter notebooks where multiple accessors can contribute layers to the same viewer, enabling interactive comparison of different image transformations (e.g., grayscale, RGB, binary masks) on the same data.

The viewer is automatically displayed in Jupyter environments and recreated if it has been closed externally.

Parameters:
  • name (str | None) – Optional custom name for the image layer. If provided, the layer will be named {accessor}_{name}. If not provided, defaults to using the image’s name attribute.

  • reset (bool) – If True, closes the current napari viewer and creates a fresh one. This is useful for starting a new visualization session without lingering layers from previous calls. Defaults to False.

  • viewer (napari.Viewer | None) – Optional external napari viewer instance to use instead of the global viewer. When provided, global viewer management (creation, reset, smart-grid installation) is bypassed entirely. Defaults to None.

  • layer_name (str | None) – Optional full layer name to use instead of the auto-generated {accessor}_{image_name} pattern. Defaults to None.

Returns:

The global napari viewer instance with the current

image added as a new layer.

Return type:

napari.Viewer

Raises:

ImportError – If napari is not installed. Install with pip install phenotypic[napari].

Examples

View multiple image transformations in one viewer:

>>> from phenotypic import Image
>>> img = Image(arr)
>>> # Add grayscale version to viewer
>>> viewer = img.gray.napari()
>>> # Add RGB version to same viewer
>>> viewer = img.rgb.napari()
>>> # Add binary segmentation with custom name
>>> viewer = img.objmask.napari(name="segmentation_v2")

Using custom names for comparison:

>>> viewer = img.gray.napari(name="raw_grayscale")
>>> viewer = img.objmask.napari(name="segmentation_v2")

Resetting the viewer for a fresh session:

>>> viewer = img.gray.napari()
>>> viewer = img.rgb.napari()  # Same viewer, added layer
>>> viewer = img.gray.napari(reset=True)  # Fresh viewer, old layers gone

Note

Layers are named using the pattern {accessor}_{image_name} to ensure descriptive identification. If a layer with the same name already exists, it is replaced with the new image data. This allows for easy updates and comparison of different processing stages.

save_overlay(filepath: str | Path, overlay_alpha: float = 0.3, bg_label: int = 0, colors: list | None = None, show_grid: bool = True, gridline_color: tuple[int, int, int] = (0, 255, 255), section_box_colors: list[tuple[int, int, int]] | None = None, **label2rgb_kwargs) None#

Save a full-resolution overlay image blending objmap with the subject array.

Creates an RGB overlay by blending the object map labels with the underlying image data and saves it to disk. Unlike show(overlay=True) which produces a matplotlib figure, this method saves the raw pixel data at full resolution, suitable for pixel-level quality validation of detection results.

For GridImage objects, gridlines and section boxes are automatically drawn when show_grid is True. The line widths scale dynamically with image size.

Parameters:
  • filepath (str | Path) – Destination file path. Should have .png or .jpeg extension.

  • overlay_alpha (float) – Alpha value for label overlay (0.0 = transparent, 1.0 = opaque). Defaults to 0.3.

  • bg_label (int) – Label value to treat as background. Defaults to 0.

  • colors (list | None) – List of RGB colors to use for labels. If None, uses default colormap.

  • show_grid (bool) – Whether to draw gridlines and section boxes on overlay for GridImage objects. Ignored for regular Image objects. Defaults to True.

  • gridline_color (tuple[int, int, int]) – RGB color tuple for gridlines. Defaults to cyan (0, 255, 255).

  • section_box_colors (list[tuple[int, int, int]] | None) – List of RGB tuples for cycling through section box colors. Defaults to tab20 colormap colors.

  • **label2rgb_kwargs – Additional keyword arguments for label2rgb.

Raises:

ValueError – If the file extension is not supported.

Return type:

None

Examples

Save full-resolution overlay:

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> image.rgb.save_overlay("overlay_rgb.png", overlay_alpha=0.4)
size_distribution(*args: Any, **kwargs: Any) Any[source]#

Plot object size distribution with optional threshold lines.

See size_distribution().

Parameters:
Return type:

Any

size_scatter(*args: Any, **kwargs: Any) Any[source]#

Scatter plot of object sizes colored by a secondary metric.

See size_scatter().

Parameters:
Return type:

Any

size_viewer(*args: Any, **kwargs: Any) Any[source]#

Interactive size distribution viewer with threshold selection.

See size_viewer().

Parameters:
Return type:

Any

spatial_size_map(*args: Any, **kwargs: Any) Any[source]#

Heatmap of object sizes across spatial positions.

See spatial_size_map().

Parameters:
Return type:

Any

structural_response_curve(*args: Any, **kwargs: Any) Any[source]#

Plot structural response metrics across kernel sizes.

See structural_response_curve().

Parameters:
Return type:

Any

try_thresh(*args: Any, **kwargs: Any) Any[source]#

Compare multiple thresholding techniques side by side.

See try_thresh().

Parameters:
Return type:

Any

val_range() pd.Interval#

Return the closed interval [min, max] of the subject array values.

Returns:

A single closed interval including both endpoints.

Return type:

pd.Interval

property dash: Any#

image.plot.dash.<name>().

Dispatches to a registered FigureProvider plotter’s .dash() — a composed go.Figure for control-free providers, or an ipywidgets dashboard when the figures declare Control``s. Mirrors the ``@register_plotter registry used by image.plot.<name>().

Examples

>>> from phenotypic.data import load_synth_yeast_plate
>>> image = load_synth_yeast_plate()
>>> dashboard = image.plot.dash.diagnostics()  # ipywidgets dashboard
Type:

Interactive Plotly/ipywidgets views

property dtype#
property nbytes: int#
property ndim: int#

Returns the number of dimensions of the underlying array.

The ndim property provides access to the dimensionality of the array being encapsulated in the object. This value corresponds to the number of axes or dimensions the underlying array possesses. It can be useful for understanding the structure of the contained data.

Returns:

The number of dimensions of the underlying array.

Return type:

int

property shape: Tuple[int, ...]#

Returns the shape of the current image data.

This method retrieves the dimensions of the array stored in the _main_arr attribute as a tuple, which indicates its size along each axis.

Returns:

A tuple representing the dimensions of the _main_arr attribute.

Return type:

Tuple[int, …]

property size: int#

Gets the size of the subject array.

This property retrieves the total number of elements in the subject array. It is read-only.

Returns:

The total number of elements in the subject array.

Return type:

int

Detailed documentation for individual plotting methods is available in a dedicated section.

File I/O#

classmethod Image.imread(filepath: PathLike, rawpy_params: dict | None = None, **kwargs) Image#

imread is a class method responsible for reading an image file from the specified path and performing necessary preprocessing based on the file format and additional parameters. The method supports a variety of image file types including common formats (e.g., JPEG, PNG) as well as raw sensor data. It uses the scikit-image library for loading standard images and rawpy for processing raw image files. This method also handles additional configurations for raw image preprocessing via rawpy parameters, such as white balance, gamma correction, and demosaic algorithm.

Parameters:
  • filepath (PathLike) – Path to the image file to be read. It can be any valid file path-like object (e.g., str, pathlib.Path).

  • rawpy_params (dict | None) – Optional dictionary of parameters for processing raw image files when using rawpy. Supports options like white balance settings, demosaic algorithm, gamma correction, and others. Defaults to None.

  • **kwargs – Arbitrary keyword arguments to be passed for additional configurations specific to the Image instantiation.

Returns:

An instance of the Image class containing the processed image array and any

additional metadata.

Return type:

Image

Raises:

UnsupportedFileTypeError – If the file type of the provided filepath is not supported by the method, either due to its extension not being recognized or due to the absence of required libraries like rawpy.

Load an image from various file formats including JPEG, PNG, TIFF, and RAW camera files. Automatically extracts metadata when available.

Image.save2pickle(filename: str) None#

Save the image to a pickle file for fast serialization and deserialization.

Stores all image data components and metadata in Python’s pickle format, which preserves data types and structure exactly. This is the fastest serialization method but produces larger files than HDF5 and is not suitable for inter-language data exchange.

Parameters:

filename (str | PathLike) – Path to the pickle file to write (.pkl or .pickle extension recommended).

Return type:

None

Notes

  • Pickle format is Python-specific and cannot be read by other languages.

  • File size is typically larger than HDF5 compressed files.

  • Load/save is faster than HDF5 for small to medium images.

  • Pickle files may not be compatible across Python versions.

Examples

Save to pickle:

>>> img = Image.imread('photo.jpg')
>>> img.save2pickle('image.pkl')
>>> loaded = Image.load_pickle('image.pkl')

Save the complete Image state (data, metadata, detection results) to a pickle file for later restoration.

Image.save2hdf5(filename, compression='gzip', compression_opts=4)#

Save the image to an HDF5 file with all data and metadata.

Stores the complete image data (RGB, gray, detection matrix, object map) and metadata (protected and public) directly at the HDF5 file’s root group. The file is always overwritten if it already exists.

Parameters:
  • filename – Path to the HDF5 file (.h5 extension recommended). Created or overwritten on each call.

  • compression – Compression filter to apply to datasets. Options: ‘gzip’ (recommended), ‘szip’, or None. Defaults to ‘gzip’.

  • compression_opts – Compression level for ‘gzip’ (1-9, where 1=fastest, 9=best). Ignored for ‘szip’ and None. Defaults to 4 (balanced).

Examples

Save to HDF5:

>>> img = Image.imread('photo.jpg')
>>> img.save2hdf5('output.h5')
>>> img.save2hdf5('output.h5', compression='szip')

Save the image to HDF5 format with metadata. HDF5 provides efficient storage and supports compression.

classmethod Image.load_hdf5(filename, **kwargs) Image#

Loads an image object from an HDF5 file.

Parameters:
  • filename (str) – The path to the HDF5 file containing the image data. Providing an incorrect or improperly structured file may result in incomplete or corrupted image data being loaded.

  • **kwargs – Additional keyword arguments that may alter the image import and processing behavior. These settings may include options for subsampling, resizing, or preprocessing specific to the characteristics of the microbe images. Proper configurations can optimize the processing performance while preserving essential colony features critical to analysis.

Returns:

An image object constructed from the data stored in the HDF5 file. The fidelity of this object depends on both the input file’s quality and the parameters provided through kwargs, as they affect the image’s suitability for detailed microbe colony studies.

Return type:

Image

Load an Image from HDF5 file created with save2hdf5.

Image Manipulation#

Image.rotate(angle_of_rotation: int, mode: str = 'constant', cval=0, order=0, preserve_range=True) None#

Rotates various data attributes of the object by a specified angle.

The method applies rotation transformations image data. It data that falls outside the border is clipped.

Parameters:
  • angle_of_rotation (int) – The angle, in degrees, by which to rotate the data attributes. Positive values indicate counterclockwise rotation.

  • mode (str) – Mode parameter determining how borders are handled during the rotation. Default is ‘constant’.

  • cval – Constant value to fill edges in ‘constant’ mode. Default is 0.

  • order (int) – The order of the spline interpolation for rotating images. Must be an integer in the range [0, 5]. Default is 0 for nearest-neighbor interpolation.

  • preserve_range (bool) – Whether to keep the original input range of values after performing the rotation. Default is True.

Returns:

None

Return type:

None

Rotate the image by a specified angle with optional centering and resampling control.

Image.__getitem__(key) Image#

Returns a new subimage from the current object based on the provided key. The subimage is initialized as a new instance of the same class, maintaining the schema and format consistency as the original image object. This method supports 2-dimensional slicing and indexing.

Note

  • The subimage arrays are copied from the original image object. This means that any changes made to the subimage will not affect the original image.

  • We may add this functionality in future updates if there is demand for it.

Parameters:

key – A slicing key or index used to extract a subset or part of the image object.

Returns:

An instance of the Image representing the subimage corresponding to the provided key.

Return type:

Image

Raises:

KeyError – If the provided key does not match the expected slicing format or dimensions.

Support for numpy-like slicing to extract spatial regions or specific channels. Enables intuitive image manipulation with bracket notation.

Example:

img = Image.imread('sample.jpg')

# Extract region of interest
roi = img[100:300, 100:300]

# Extract specific channel (RGB)
red_channel = img[..., 0]

Visualization (Basic Display)#

Image.show(figsize: Tuple[int, int] | None = None, **kwargs) tuple[TypeAliasForwardRef('matplotlib.figure.Figure'), TypeAliasForwardRef('matplotlib.axes.Axes')]#

Display the image using matplotlib.

Delegates to self.rgb.show() for color images or self.gray.show() for grayscale images.

Parameters:
  • figsize (Tuple[int, int] | None) – Figure size in inches (width, height). If None, auto-calculated from array aspect ratio.

  • **kwargs – Additional keyword arguments passed to the accessor’s show() method.

Returns:

A (matplotlib.figure.Figure, matplotlib.axes.Axes) tuple.

Return type:

tuple[TypeAliasForwardRef(‘matplotlib.figure.Figure’), TypeAliasForwardRef(‘matplotlib.axes.Axes’)]

Display the image using matplotlib. Supports grayscale and RGB images with automatic colormap selection.

Display the image with detection results overlaid. Shows object boundaries, labels, or other annotations on top of the original image.

Display detected objects with object-specific visualization, including individual object highlighting and labeling.

Metadata Management#

property Image.metadata: MetadataAccessor#

Access the metadata accessor for reading and writing image metadata. Metadata is categorized into private, protected, public, and imported categories for organized management.

MetadataAccessor API:

class phenotypic._core._image_parts.accessors._metadata_accessor.MetadataAccessor(image: Image)[source]#

Bases: object

Accessor for managing image metadata with hierarchical read/write permissions.

This class provides dictionary-like access to image metadata with three permission levels: private (read-only), protected (read/write), and public (read/write/delete). All metadata is combined using ChainMap for unified access while preserving permission constraints.

Private metadata is typically reserved for internal use (e.g., UUID), protected metadata contains system properties (e.g., image name, type), and public metadata contains user-defined or imported metadata that can be freely modified.

Parameters:

image (Image)

_parent_image#

The parent Image instance containing the metadata storage.

Type:

Image

Examples

Access metadata like a dictionary:

>>> img = Image(arr, name='sample')
>>> # Get metadata value
>>> image_name = img.metadata['ImageName']
>>> # Set public metadata
>>> img.metadata['user_notes'] = 'A sample image'
>>> # Check if key exists
>>> if 'user_notes' in img.metadata:
...     print(img.metadata['user_notes'])

Iterate through metadata:

>>> for key, value in img.metadata.items():
...     print(f'{key}: {value}')
__contains__(key)[source]#

Check if a metadata key exists at any permission level.

Parameters:

key – The metadata key to check.

Returns:

True if the key exists in private, protected, or public metadata.

Return type:

bool

__delitem__(key)[source]#

Delete a metadata entry with permission checking.

Only public metadata can be deleted. Private and protected metadata cannot be removed.

Parameters:

key – The metadata key to delete.

Raises:
  • PermissionError – If attempting to delete private or protected metadata.

  • KeyError – If the key does not exist in public metadata.

Examples

Delete public metadata entries:

>>> del img.metadata['user_notes']  # Deletes public metadata
__getitem__(key)[source]#

Retrieve a metadata value by key with hierarchical search.

Searches in order: private -> protected -> public. Returns the first match found.

Parameters:

key – The metadata key to retrieve.

Returns:

The metadata value associated with the key.

Return type:

Any

Raises:

KeyError – If the key does not exist in any metadata level.

__init__(image: Image) None[source]#

Initialize the metadata accessor.

Parameters:

image (Image) – The parent Image instance containing the metadata storage.

Return type:

None

__setitem__(key, value)[source]#

Set a metadata value with validation and permission checking.

Only scalar types (str, int, float, bool) or None are allowed as values. If the key exists in protected metadata, updates the protected value. Otherwise, creates or updates a public metadata entry. Private metadata cannot be modified.

Parameters:
  • key – The metadata key to set.

  • value – The metadata value (must be str, int, float, bool, or None).

Raises:

Examples

Set metadata values with permission checking:

>>> img.metadata['resolution'] = 300  # Creates public metadata
>>> img.metadata['ImageName'] = 'updated_name'  # Updates protected metadata
by_module(module) dict[source]#

Group metadata keys/values by REMBI module (read-only view).

Framework private/protected keys (e.g. ImageName, UUID) map to IMAGE_DATA; public tags resolve via the schema reverse index; unrecognized keys fall to UNCATEGORIZED.

Parameters:

module – A REMBI_MODULE or its string value (e.g. "ImageData").

Returns:

{key: value} for every metadata entry resolving to module, in combined-metadata iteration order.

Return type:

dict

get(key, default=None)[source]#

Retrieve a metadata value with a default fallback.

Searches across all permission levels (private -> protected -> public) and returns the first match found.

Parameters:
  • key – The metadata key to retrieve.

  • default – The value to return if the key is not found. Defaults to None.

Returns:

The metadata value if found, otherwise the default value.

Return type:

Any

Examples

Retrieve metadata with default fallback:

>>> resolution = img.metadata.get('resolution', 100)
>>> name = img.metadata.get('ImageName')  # Returns None if not found
insert_metadata(df: pandas.DataFrame, inplace=False, allow_duplicates=False) pandas.DataFrame[source]#

Insert metadata as columns into a DataFrame.

Adds public and protected metadata as new columns at the beginning of the DataFrame. Column names are prefixed with Metadata_ if not already present. Image name is retrieved from the parent image instance rather than metadata storage.

Parameters:
  • df (pd.DataFrame) – The DataFrame to insert metadata columns into.

  • inplace (bool, optional) – If True, modifies the input DataFrame in place. If False, creates a copy before modification. Defaults to False.

  • allow_duplicates (bool, optional) – If True, allows duplicate column names to be inserted. If False, skips insertion for columns that already exist. Defaults to False.

Returns:

The DataFrame with metadata columns inserted at the beginning

(position 0). If inplace=True, returns the same object as input.

Return type:

pd.DataFrame

Notes

  • Only public and protected metadata are included (private metadata is excluded)

  • IMAGE_NAME metadata is populated from parent_image.name instead of the metadata dict

  • Columns are inserted from right to left at position 0, so iteration order determines final order; iteration follows the bio-semantic cluster order (Identity -> Strain -> Condition -> Design, via canonical_metadata_order), not REMBI module order

  • Metadata columns without a Metadata<Topic>_ prefix are automatically prefixed via the schema (e.g. Strain -> MetadataGenetic_Strain; unknown labels fall back to a generic Metadata_ prefix)

Examples

Insert metadata as DataFrame columns:

>>> import pandas as pd
>>> df = pd.DataFrame({'data': [1, 2, 3]})
>>> img = Image(arr, name='sample')
>>> img.metadata['resolution'] = 300
>>> result_df = img.metadata.insert_metadata(df)
>>> # result_df now has MetadataImage_ImageName and Metadata_resolution columns at position 0
items()[source]#

Get all metadata key-value pairs across all permission levels.

Returns:

A view of all key-value pairs from combined metadata (private,

protected, public). Items from private metadata take precedence.

Return type:

ItemsView

keys()[source]#

Get all metadata keys across all permission levels.

Returns:

A view of all keys from combined metadata (private, protected, public).

Keys from private metadata take precedence in the view.

Return type:

KeysView

pop(key)[source]#

Remove and return a metadata value.

Only public metadata can be popped. Private and protected metadata cannot be removed.

Parameters:

key – The metadata key to remove.

Returns:

The value associated with the key before removal.

Return type:

Any

Raises:
  • PermissionError – If attempting to pop private or protected metadata.

  • KeyError – If the key does not exist in public metadata.

Examples

Remove and return a public metadata value:

>>> old_value = img.metadata.pop('user_notes')
table() pandas.Series[source]#

Convert metadata to a pandas Series.

Creates a Series containing all metadata (private, protected, and public) with the parent image name as the Series name.

Returns:

A Series where the index is metadata keys and values are

metadata values. The Series name is the parent image name.

Return type:

pd.Series

Examples

Convert metadata to pandas Series:

>>> img = Image(arr, name='sample_image')
>>> img.metadata['resolution'] = 300
>>> series = img.metadata.table()
>>> print(series.name)  # 'sample_image'
>>> print(series['ImageName'])  # 'sample_image'
values()[source]#

Get all metadata values across all permission levels.

Returns:

A view of all values from combined metadata (private, protected, public).

Values from private metadata take precedence.

Return type:

ValuesView

Comparison & Utility#

Image.__eq__(other: Image) bool#

Compares the current object with another object for equality.

This method checks if the current object’s attributes are equal to another object’s attributes. Equality is determined by verifying that the numerical arrays (rgb, gray, detect_mat, objmap) are element-wise identical.

Note

  • Only checks image data, and not any other attributes such as metadata.

Parameters:

other (Image) – The object to compare with the current instance.

Returns:

True if all the attributes of the current object are identical to those of the other object. Returns False otherwise.

Return type:

bool

Compare two Image instances for equality based on their data content.

Image.__hash__()#

Return hash(self).

Compute a hash value for the Image based on its unique identifier.

Image.__repr__()#

Return repr(self).

Return a string representation of the Image with key information (shape, type, name).

Image.__str__()#

Return str(self).

Return a human-readable string description of the Image.