This is the abridged developer documentation for PhenoMe # Getting started > Install PhenoMe and run a first analysis in four steps. import { Tabs, TabItem, Steps, Aside, Card, CardGrid, LinkCard } from '@astrojs/starlight/components'; This page walks you from an empty environment to a first PCA plot of your own images. ## 1. Requirements - **Python** 3.12 or newer. - **Hardware**: any CPU works; an NVIDIA GPU is strongly recommended for speed. See [Best practices - performance](/PhenoMe/guides/best-practices/performance/) for sizing. - **OS**: Linux, macOS, Windows. ## 2. Installation Pip installs the published package. Conda uses the pinned environment files in this repository. 1. **Install the package** ```bash pip install phenome ``` PyTorch is included. An NVIDIA GPU is used when CUDA is available; the same install runs on CPU. For very large GPU datasets, add the optional backends: ```bash pip install "phenome[full]" ``` 2. **Verify the install** ```bash python3 -c ' import torch, phenome print(f"PhenoMe {phenome.__version__} is ready!") print("GPU:", torch.cuda.is_available(), f"({torch.cuda.get_device_name(0)})" if torch.cuda.is_available() else "(CPU-only mode)") ' ``` 1. **Clone the repository** ```bash git clone https://github.com/AAitorG/PhenoMe.git cd PhenoMe ``` 2. **Create an environment** for your hardware. These files pin the scientific stack and install this checkout. ```bash conda env create -f envs/environment-gpu.yml conda activate phenome-gpu ``` ```bash conda env create -f envs/environment-cpu.yml conda activate phenome-cpu ``` 3. **Verify the install** ```bash python3 -c ' import torch, phenome print(f"PhenoMe {phenome.__version__} is ready!") print("GPU:", torch.cuda.is_available(), f"({torch.cuda.get_device_name(0)})" if torch.cuda.is_available() else "(CPU-only mode)") ' ```
Install troubleshooting (click to expand) | Symptom | Suggestion | |---------|------------| | `torch` not found or wrong CUDA | Reinstall torch from [PyTorch get started](https://pytorch.org/get-started/locally/); match driver and wheel. | | `opencv` / `h5py` build errors | Use conda-forge or the full conda env file. | | Import works but GPU not used | `import torch; print(torch.cuda.is_available())`; ensure `device` is passed into `PhenoMe(...)`. | More in the [FAQ - installation](/PhenoMe/faq/#installation-and-setup).
## 3. Run your first analysis The snippet below runs the full pipeline on a folder of images. You can run this in a **Python script** or a **Jupyter Notebook** cell. 1. **Set up and start** Load an AI vision model (DINOv2 by default) and create the pipeline. ```python import torch from phenome import PhenoMe, load_dinov2_model device = torch.device("cuda" if torch.cuda.is_available() else "cpu") wrapper = load_dinov2_model(model_name="dinov2_vitb14_reg", device=device) pheno = PhenoMe(device=device, seed=42) ``` 2. **Find your images** A simple folder is fine. For grouped experiments (conditions, drugs, time points) add labels via [folder patterns or spreadsheets](/PhenoMe/guides/experiment-details/). ```python pheno.find_files("path/to/your/images") ``` 3. **Extract embeddings (AI descriptions) and properties** ```python pheno.process_images(wrapper) pheno.compute_properties(property_preset="intensity") ``` 4. **Visualize your results** ```python pheno.plot_pca(color_by="condition") ``` Replace `condition` with any metadata column in your dataset. Use `pheno.get_available_metadata_keys()` to list them. If you have no labels yet, call `pheno.plot_pca()` with no `color_by`. ## What's next? Pick the card that matches your next goal. Each links to the most relevant guide, reference, or notebook - no duplicates of the quickstart above. # Using PhenoMe with LLMs > How assistants should write PhenoMe analysis code without changing defaults, and which parameters to pass when you want a methods dump or a specific figure format. import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; PhenoMe is a scientific pipeline. **Defaults stay as they are** (logging, plots, HTML reports, `save_results` output). If you are an assistant writing analysis code, use the public API below and only pass extra parameters when the user asked for them. Machine-readable docs (after the site build): - [llms.txt](https://AAitorG.github.io/PhenoMe/llms.txt) — page index - [llms-small.txt](https://AAitorG.github.io/PhenoMe/llms-small.txt) — compact corpus for a context window - [llms-full.txt](https://AAitorG.github.io/PhenoMe/llms-full.txt) — full site text Attach `llms-small.txt` in ChatGPT or Claude when answering “how do I use PhenoMe?”. ## Rules for assistants 1. Import from the public surface: `from phenome import PhenoMe, load_dinov2_model`. Do not invent methods. Look up names in the [function location guide](/PhenoMe/function-location-guide/). 2. Canonical order: `find_files` → `process_images` → optional `compute_properties` → analysis / plots. 3. Do **not** change logging, plot backends, or report sections unless the user asked. Processing steps are already logged at INFO during `process_images` / `compute_properties`. Reprint only if asked: `print_processing_pipeline()`. 4. When the user wants a paper-style methods dump, then call `export_experiment_config` (and optionally `include_run_settings=True` on the report). The pipeline records settings silently; nothing is written until those calls. 5. Pass `device` explicitly so CPU/GPU is obvious in shared scripts. ## Canonical first analysis ```python import torch from phenome import PhenoMe, load_dinov2_model device = torch.device("cuda" if torch.cuda.is_available() else "cpu") wrapper = load_dinov2_model(model_name="dinov2_vitb14_reg", device=device) pheno = PhenoMe(device=device, seed=42) pheno.find_files("path/to/your/images") pheno.process_images(wrapper) pheno.compute_properties(property_preset="intensity") pheno.plot_pca(color_by="condition") ``` `seed=42` is only needed when the user asked for a reproducible run. Omit it to keep the library default (`seed=None`). ## When the user asks to save settings The run log is silent. Export it explicitly: ```python pheno.save_results("results/phenome_results.h5") # HDF5 only; no sibling config file pheno.export_experiment_config("results/config.json") pheno.export_methods_markdown("results/methods.md") # optional paper supplement pheno.generate_report( "results/report.html", include_run_settings=True, # Methods section; default is False ) ``` `export_experiment_config` includes seed, environment, processing params, model name (from `load_dinov2_model`), and recorded steps (`property_preset`, `reference_filters`, clustering, …). You do not need to pass `model_name=` unless you want to override. Inspect without writing a file: `settings = pheno.get_run_settings()`. ## Folder metadata vs spreadsheet ```python from phenome import get_metadata_from_path pheno.find_files( "path/to/images", metadata_fn=get_metadata_from_path("{condition}/{well}/{filename}"), ) ``` See [Using a spreadsheet](/PhenoMe/guides/experiment-details/csv-lookup/). Only rows that exist in **both** the folder and the CSV are processed. ## Property presets Use a preset name, not a guessed feature list. Masks are required for shape-based presets; `intensity` does not need masks. ```python pheno.compute_properties(property_preset="basic") # needs masks ``` Valid presets: `none`, `intensity`, `shape`, `basic`, `standard`, `complete`. Details: [Select properties — Presets](/PhenoMe/guides/select-properties/presets/). ## Batch correction ```python pheno.correct_batches( batch_metadata_key="plate_id", control_filters={"condition": "Control"}, ) ``` ## Checkpoint (large datasets) ```python pheno.process_images(wrapper, checkpoint_path="results/phenome_results.h5") ``` Reload: `find_files` first, then `pheno.load_results("results/phenome_results.h5")`. ## Verbosity and plot format (caller’s choice) Do not change these unless asked. | Goal | Parameter | |------|-----------| | Quieter logs | `logging.getLogger("phenome").setLevel(logging.WARNING)` | | Return a Plotly figure | `plot_pca(..., return_fig=True)` | | Static-ish scatter | `plot_pca(..., render_mode="svg")` | | Report without CDN | `generate_report(..., offline_plotly=True)` (already the default) | | Methods section in the HTML report | `generate_report(..., include_run_settings=True)` | | Reprint the processing pipeline | `pheno.print_processing_pipeline()` | ## MCP (optional) For Cursor or Claude Desktop, install `phenome[mcp]` and run `phenome-mcp`. See [MCP for PhenoMe](/PhenoMe/guides/mcp/). # Common workflows > Simple overview of what you can do with PhenoMe, with links to the best tutorial for each task. Full code for each workflow lives in the Jupyter notebooks under [`Notebooks/tutorials/`](https://github.com/AAitorG/PhenoMe/tree/main/Notebooks/tutorials). This page keeps only a short summary and pointers. ## 1. Basic analysis The standard end-to-end workflow - from loading images and extracting visual fingerprints (AI descriptions) to comparing images and generating reports - is covered in the core tutorial. - **Tutorial:** [03 - Core phenotyping workflow](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/03_core_phenotyping_workflow.ipynb) ## 2. Drug screening and time-course Analyzing the effects of different drug treatments or mapping changes over time requires using experimental info (metadata). Whether your data is organized hierarchically (`{drug}/{concentration}/{image}.tif`) or encoded in filenames (`Treated_24h`), you can handle it through the metadata pipeline. - **Guide:** [Adding experiment info (metadata)](/PhenoMe/guides/experiment-details/) ## 3. Property-based analysis and explaining results To connect abstract AI patterns to real biology, compute classical physical properties (like area, brightness, and texture) and link them to your visual fingerprints. - **Tutorial:** [04 - Exploratory analysis and explainability](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/04_exploratory_analysis_and_explainability.ipynb) - **Guide:** [Understanding properties](/PhenoMe/concepts/property-interpretation/), [Explaining AI results](/PhenoMe/concepts/embeddings-interpretability/) ## 4. Finding groups and outliers Discover hidden groups of images (clusters) within a single condition or find unusual cells (outliers) using clustering and similarity checks. - **Tutorial:** [04 - Exploratory analysis and explainability](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/04_exploratory_analysis_and_explainability.ipynb) ## 5. Temporal images (testing new data quickly) To see how **new** images fit into your existing pattern visualizations (PCA/t-SNE) without starting over, use temporal processing. ```python pheno.process_temporal_images( wrapper, files=["new_data/test1.tif", "new_data/test2.tif"], ) pheno.plot_pca(color_by="condition", hover_features=["source"]) pheno.clear_temporal_data() ``` ## 6. Batch processing large datasets For very large datasets, use "checkpointing" so results are saved to disk as they are calculated. ```python pheno.process_images( wrapper, checkpoint_path="results/my_large_dataset.h5", batch_size=128, ) ``` ## 7. Plate / Batch correction If your data comes from multiple plates or batches, you can correct for technical variation (plate effects) using control wells. ```python # Correct embeddings using 'Control' wells in each 'Plate' pheno.correct_batches( batch_metadata_key="Plate", control_filters={"condition": "Control"}, method="sphering", # or "zscore" source="embeddings", # or "properties" ) ``` See [Best practices - performance](/PhenoMe/guides/best-practices/performance/) for memory sizing. ## See also | Topic | Where | |-------|-------| | Installation and quick start | [Getting started](/PhenoMe/getting-started/) | | Folder structure and metadata | [Preparing your data](/PhenoMe/guides/data-setup/), [Adding experiment info](/PhenoMe/guides/experiment-details/) | | Visual fingerprints, channel modes, and results | [Core concepts](/PhenoMe/concepts/) | | Full API reference | [API & data formats](/PhenoMe/advanced/), [Pipeline API](/PhenoMe/advanced/api/pipeline/) | | Property sets and custom functions | [Select properties](/PhenoMe/guides/select-properties/), [Understanding properties](/PhenoMe/concepts/property-interpretation/) | | Reproducibility and optimization | [Best practices](/PhenoMe/guides/best-practices/) | # FAQ > Grouped answers to the most common PhenoMe questions - install, data, compute, results, and extension. Common questions grouped by topic. --- ## Installation and setup ### Do I need to train a model? No. PhenoMe is **zero-shot**: it uses pre-trained AI models like DINOv2 out-of-the-box. You point the pipeline at your images and run. No training, fine-tuning, or manual annotation is required. ### Do I need a GPU? A GPU is **strongly recommended** for reasonable speed. The pipeline works on CPU, but processing thousands of images will be slow. For small datasets (hundreds of images), CPU is feasible. See [Best practices - performance](/PhenoMe/guides/best-practices/performance/). ### `conda` / `pip` failed or PyTorch is wrong - what should I try? 1. Use the pinned env files under `envs/` (see [Getting started - installation](/PhenoMe/getting-started/#2-installation)). 2. For GPU, ensure your driver matches the CUDA wheel you install; see [PyTorch get started](https://pytorch.org/get-started/locally/). 3. If `pykeops` fails on your OS, omit it and use the default backends (see [Best practices - performance](/PhenoMe/guides/best-practices/performance/)). ### I get a `[KeOps] Warning : CUDA libraries not found or could not be loaded; Switching to CPU only` error, what should I do? This happens if `pykeops` is installed but cannot find your system's CUDA toolkit or compiler. In most cases, you can ignore this: PhenoMe will automatically fall back to standard PyTorch or Scikit-Learn backends for visualizing patterns (using PCA, t-SNE, or UMAP), which is fast enough for most datasets. If you are working with very large datasets (e.g., >100,000 images) and need the memory efficiency of KeOps, ensure you have a functional C++ compiler and that your `CUDA_HOME` environment variable is set. For standard usage, you can safely ignore the warning or simply uninstall `pykeops`. If you have a working CUDA setup but still see the error, try clearing the cache: ```python import pykeops pykeops.clean_pykeops() ``` ### How do I verify the install? See the verification steps in [Getting started — Installation](/PhenoMe/getting-started/#2-installation). ### Do I need masks? - **For embeddings**: No. These are extracted directly from images. - **For classical properties**: Depends on the preset. The `intensity` preset works on images only (no masks). Presets like `basic`, `shape`, `standard`, and `complete` require masks for shape-based features (see [Select properties — Presets](/PhenoMe/guides/select-properties/presets/)). See [Understanding properties](/PhenoMe/concepts/property-interpretation/). ### How long does processing take? Rough order of magnitude (GPU, ViT-B/14, batch_size=32): - ~1–5 seconds per 100 images (embedding extraction) - PCA/t-SNE/UMAP: seconds to minutes depending on sample count - For tens of thousands of images, expect minutes to hours; use checkpointing to resume if interrupted. --- ## Data and metadata ### What folder structure should I use? The pipeline is flexible: - **Flat**: All images in one folder. Use `pheno.find_files("path/to/images")`. - **By condition**: `data/Control/img1.tif`, `data/Drug1/img1.tif`. Use a **path template** (`get_metadata_from_path`) or custom `metadata_fn`. See [Preparing your data](/PhenoMe/guides/data-setup/) and [Adding experiment info](/PhenoMe/guides/experiment-details/). ### I use a CSV for metadata—why don’t my CSV row count and `file_df` row count match? `find_files` first scans your images folder, then matches those images to your CSV. **Only the overlap is used:** image files must exist in **both** the folder and the CSV to be processed. Extra rows in your CSV are ignored, and images without a CSV row are skipped by default. See [Adding experiment info — Using a spreadsheet](/PhenoMe/guides/experiment-details/csv-lookup/). ### My filter returns no images. Why? Check that: 1. The metadata keys exist (`pheno.get_available_metadata_keys()`). 2. The filter values match your data exactly (case-sensitive). 3. The column values are as expected (`df['condition'].unique()`). See [Best practices - data organization](/PhenoMe/guides/best-practices/data-organization/). --- ## Analysis and compute ### I get "CUDA out of memory." What do I do? 1. Reduce `batch_size` (e.g., from 32 to 16 or 8). 2. Use a smaller model (e.g., ViT-S instead of ViT-g). 3. Clear GPU memory between runs: `gc.collect(); torch.cuda.empty_cache()`. See [Best practices - performance](/PhenoMe/guides/best-practices/performance/#gpu-out-of-memory). ### Processing is very slow. How can I speed it up? 1. Ensure you are using a GPU (`wrapper.device` should show `cuda:X`). 2. Increase `batch_size` if GPU memory allows. 3. Increase `num_workers` for data loading. 4. Use the `intensity` preset instead of `complete` if you do not need all properties. ### Results differ between runs. Why? t-SNE and UMAP have random elements (they are stochastic). For reproducibility, set `seed=42` when creating the pipeline: `PhenoMe(seed=42)`. Also use `use_gpu_for_dr=False` if you need exact match with CPU sklearn/umap-learn. For large datasets on GPU, install `pykeops` to avoid OOM. See [Best practices - reproducibility](/PhenoMe/guides/best-practices/reproducibility/) and [Best practices - performance](/PhenoMe/guides/best-practices/performance/). --- ## Results and interpretation ### What is the "correlation engine" (linking AI to biology) and how do I use it? The correlation engine links the abstract AI descriptions to classical physical properties (like size or shape). It answers: "What physical trait does this AI pattern represent?" For example, a certain axis might represent how elongated a cell is. See [Explaining AI results](/PhenoMe/concepts/embeddings-interpretability/). ### How do I choose channel mode (split vs combined)? - **Split**: Fluorescence images with distinct channels (DAPI, GFP, etc.). Each channel is processed separately. - **Combined**: Brightfield, phase contrast, or RGB images. Channels are treated as a single color image. See [Core concepts — channel modes](/PhenoMe/concepts/channel-modes/). ### Which property preset should I use? | You have... | Use preset | |---------------------------------------------------|--------------| | No masks | `intensity` | | Masks, lightweight stats | `basic` | | Masks, shape only | `shape` | | Masks, rings + axis lengths (recommended default) | `standard` | | Masks, texture + QC + extended shape | `complete` | See [Select properties — Presets](/PhenoMe/guides/select-properties/presets/) and [Understanding properties — choosing properties](/PhenoMe/concepts/property-interpretation/#choosing-properties). --- ## Extending PhenoMe ### What is the relationship between PhenoMe and the `phenome` package? **PhenoMe** is the project name. The installable Python package is **`phenome`** (`pip install phenome`; `import phenome`). ### Where do I add custom models, properties, or plugins? See [Extending PhenoMe](/PhenoMe/guides/extending/), [Select properties](/PhenoMe/guides/select-properties/), [Plugins](/PhenoMe/guides/plugins/), and notebook [05 - Extending](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/05_extending_phenome_plugins.ipynb). # Function location guide > Quick lookup of which module exports each PhenoMe function or class, grouped by tier (Public, Advanced, Internal). Auto-generated on each docs build. :::note[Auto-generated] This page is rebuilt from live ``phenome`` exports and API routing in ``docs/scripts/api_docs/function_index.py`` (invoked by ``docs/scripts/generate_api_docs.py``). **Do not edit by hand** — change code or docstrings and rebuild. ::: ## Audience | | Meaning | | --- | --- | | ✅ **Public** | Part of the main workflow. Use via `PhenoMe` or top-level imports. | | 🔧 **Advanced** | For power users extending the pipeline (custom models, metadata, properties, plugins). | | 🔒 **Internal** | Used by the pipeline internally. Rarely needed directly. | --- ## Index | Section | Module | | --- | --- | | [Top-level](#top-level-phenome) | `phenome` | | [Pipeline methods](#pipeline-methods-on-phenome) | `PhenoMe` | | [Metadata](#metadata-phenomemetadata) | `phenome.metadata` | | [Utilities](#utilities-phenomeutils) | `phenome.utils` | | [I/O](#io-phenomeio) | `phenome.io` | | [Plugins](#plugins-phenomeplugins) | `phenome.plugins` | | [Core](#core-phenomecore) | `phenome.core` | | [Report](#report-phenomereportgenerator) | `phenome.report.generator` | | [Mixins](#mixins-phenomemixins) | `phenome.mixins` | ### Module one-liners | Module | One-line role | | --- | --- | | `phenome` | Stable imports: `PhenoMe`, model loader, metadata helpers, property factories. | | `phenome.pipeline` | `PhenoMe` orchestrator wiring mixins into one user class. | | `phenome.metadata` | OOP metadata extractors (`MetadataBase`, path / DataFrame sources). | | `phenome.utils` | Model wrappers, transforms, device helpers, metadata + property utilities. | | `phenome.io` | `read_image`, `FileDiscovery`, `CheckpointManager`, array layout helpers. | | `phenome.plugins` | Registries for properties, metadata extractors, report sections. | | `phenome.core` | Results container, DR, correlation, interpretability, export, validation. | | `phenome.report.generator` | `ReportConfig` and standalone HTML report generation. | | `phenome.mixins` | Implementation pieces behind `PhenoMe` (dataset, viz, distances, …). | ### Typical import blocks ```python from phenome import PhenoMe, load_dinov2_model from phenome import get_metadata_from_path, make_dataframe_metadata_fn from phenome.metadata import MetadataBase, PathTemplateMetadata, DataFrameMetadata from phenome.io import read_image, FileDiscovery, CheckpointManager ``` --- ## Top-level (`phenome`) | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `create_blur_effect_function` | Create property function that computes focus sharpness. | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_concentric_ring_function` | Create property function that computes stats per concentric ring (edge to core). | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_entropy_function` | Create property function that computes Shannon entropy of intensity distribution. | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_intensity_function` | Create intensity property function over entire image (no mask). | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_masked_intensity_function` | Create intensity property function over masked pixels only (mask > 0.5). | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_regionprops_function` | Create property function that extracts skimage regionprops from a mask. | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_texture_function` | Create property function that computes GLCM texture features within the mask. | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `DataFrameMetadata` | Metadata lookup from DataFrame. Supports single and multi-channel modes. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#dataframemetadata) | | `default_metadata_from_path` | Simple, dataset-agnostic metadata extractor used by default. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#default_metadata_from_path) · [Experiment Details](/PhenoMe/guides/experiment-details/) | | `DefaultMetadata` | Minimal metadata extractor: file_path and filename from path. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#defaultmetadata) | | `get_metadata_from_path` | Create metadata extractor function from a path template. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#get_metadata_from_path) · [Experiment Details](/PhenoMe/guides/experiment-details/) | | `get_preset_property_functions` | Return a preset dict of property functions for use with compute_properties. | ✅ | [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `load_dinov2_model` | Load a DINOv2 model and return a DinoV2ModelWrapper ready for processing. | ✅ | [Model Wrappers](/PhenoMe/advanced/api/model-wrapper/#load_dinov2_model) | | `make_dataframe_metadata_fn` | Build metadata function using a dataframe for metadata lookup. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#make_dataframe_metadata_fn) · [Experiment Details](/PhenoMe/guides/experiment-details/) | | `MetadataBase` | Base class for metadata extraction with configurable columns and ID handling. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#metadatabase) | | `PathTemplateMetadata` | Metadata extractor from path template with capture groups. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#pathtemplatemetadata) | | `PhenoMe` | Main class for phenotyping analysis using deep learning embeddings. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#class-phenome) | | `PhenoMeResults` | Typed container for per-image phenotyping data (``PhenoMe.results``). | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#class-phenomeresults) | --- ## Pipeline methods (on `PhenoMe`) *All public — this is the main API surface on `PhenoMe`.* | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `analyze_group_enrichment` | Compute z-score enrichment of properties per group (e.g. cluster labels). | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-analyze_group_enrichment) | | `checkpoint_context` | Context manager that loads a checkpoint and guarantees it is closed on exit. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-checkpoint_context) | | `clear_temporal_data` | Remove all temporal images (metadata['source'] == 'NEW') from the session. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-clear_temporal_data) | | `compute_clustering` | Perform clustering and store labels in ``metadata[i]['cluster']`` for each image. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-compute_clustering) | | `compute_component_correlation` | Correlate dim-reduction components with phenotypic properties. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-compute_component_correlation) | | `compute_embedding_property_correlations` | Compute correlations between embedding dimensions and phenotypic scalar properties. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-compute_embedding_property_correlations) | | `compute_multivariate_interpretability` | Explain a dimensionality reduction component using LASSO or Random Forest. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-compute_multivariate_interpretability) | | `compute_properties` | Compute per-image properties using presets and/or custom functions. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-compute_properties) | | `compute_reference_distances` | Compute distances from all images to reference group. | ✅ | [Distances](/PhenoMe/advanced/api/distances/#api-phenomedistances-compute_reference_distances) | | `correct_batches` | Correct plate-to-plate variation using control wells per batch. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomebatchcorrection-correct_batches) | | `create_colab_interactive_explorer` | Launch the short Colab explorer for this pipeline's results. | ✅ | [Interactive Explorer](/PhenoMe/advanced/api/interactive/#create_colab_interactive_explorer) | | `create_interactive_explorer` | Launch the interactive explorer for this pipeline's results. | ✅ | [Interactive Explorer](/PhenoMe/advanced/api/interactive/#create_interactive_explorer) | | `detect_outliers` | Detect outliers based on distance to centroid. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-detect_outliers) | | `embedding_dim` | Return the embedding dimensionality, or 0 if unavailable. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-embedding_dim) | | `export_dataset_table` | Export the dataset as a table (CSV, Parquet, or Excel). | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-export_dataset_table) | | `export_experiment_config` | Export experiment configuration for reproducibility. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-export_experiment_config) | | `export_methods_markdown` | Write a markdown methods summary from the silent run log. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-export_methods_markdown) | | `find_files` | Discover image and mask files from directories, extract metadata, and cache internally. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files) | | `find_prototypes` | Find images closest to each group centroid. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-find_prototypes) | | `generate_report` | Generate a comprehensive standalone HTML report from phenotyping results. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-generate_report) | | `get_available_metadata_keys` | Return sorted metadata keys. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-get_available_metadata_keys) | | `get_available_property_keys` | Return sorted list of property keys stored in results. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-get_available_property_keys) | | `get_embeddings` | Return embeddings for the given row indices. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-get_embeddings) | | `get_image_info` | Return metadata, properties, and optional distance for image idx. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-get_image_info) | | `get_processing_pipeline` | Return the session processing pipeline, or None if none was recorded. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-get_processing_pipeline) | | `get_run_settings` | Return the silent run log as a JSON-serializable dict. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-get_run_settings) | | `has_embeddings` | Return True if embedding data is available (lazy or eager). | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-has_embeddings) | | `image_preview_png_bytes` | Rasterize the same view as ``plot_image_by_index`` to PNG bytes. | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_imagedisplaymixin-image_preview_png_bytes) | | `inspect_data` | Inspect image and mask dimensions, shapes, dtypes, and data ranges. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-inspect_data) | | `load_embeddings` | Load an embeddings checkpoint into the pipeline. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-load_embeddings) | | `load_properties` | Load a properties checkpoint into the pipeline. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-load_properties) | | `load_results` | Load and use an existing results/checkpoint file. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-load_results) | | `plot_centroids` | Plot centroids of groups in reduced embedding space. | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_drplotsmixin-plot_centroids) | | `plot_counts` | Plot count of images grouped by metadata using Plotly. | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-phenomevisualization-plot_counts) | | `plot_image_by_index` | Plot a specific image by its index. | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_imagedisplaymixin-plot_image_by_index) | | `plot_pca` | Plot PCA of embeddings, properties, or combined features (Plotly, WebGL by default). | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_drplotsmixin-plot_pca) | | `plot_tsne` | Plot t-SNE of embeddings, properties, or combined features (Plotly, WebGL by default). | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_drplotsmixin-plot_tsne) | | `plot_umap` | Plot UMAP of embeddings, properties, or combined features (Plotly, WebGL by default). | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_drplotsmixin-plot_umap) | | `print_distance_summary` | Print distance summary statistics without plotting. Safe to use when enable_plots=False. | ✅ | [Visualization](/PhenoMe/advanced/api/visualization/#api-_distanceplotsmixin-print_distance_summary) | | `print_processing_pipeline` | Print the ordered processing pipeline for this session. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-print_processing_pipeline) | | `process_images` | Extract embeddings from images using a pretrained or custom model. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-process_images) | | `process_temporal_images` | Process new images in-memory (temporary) and append to the current session. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-process_temporal_images) | | `property_stats_by_group` | Group properties, compute per-group statistics, and optionally print a table. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-property_stats_by_group) | | `reset` | Reset all stored data to a clean state. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-reset) | | `reset_properties` | Reset only the computed properties. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-reset_properties) | | `save_results` | Save results to HDF5 (atomic write or in-place flush). | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-save_results) | | `set_file_df` | Set the internal file DataFrame used for processing. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenome-set_file_df) | | `summarize_embedding_property_correlations` | Summarize embedding-property correlations, optionally plot, and/or return a Plotly figure. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-summarize_embedding_property_correlations) | | `top_properties_different_from_reference` | For each non-reference group, return the top k properties that most differentiate it from the reference. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-top_properties_different_from_reference) | | `transfer_metadata_to_properties` | Transfer specified metadata columns to properties. | ✅ | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-transfer_metadata_to_properties) | --- ## Metadata (`phenome.metadata`) | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `DataFrameMetadata` | Metadata lookup from DataFrame. Supports single and multi-channel modes. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#dataframemetadata) | | `DefaultMetadata` | Minimal metadata extractor: file_path and filename from path. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#defaultmetadata) | | `MetadataBase` | Base class for metadata extraction with configurable columns and ID handling. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#metadatabase) | | `PathTemplateMetadata` | Metadata extractor from path template with capture groups. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#pathtemplatemetadata) | --- ## Utilities (`phenome.utils`) | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `create_blur_effect_function` | Create property function that computes focus sharpness. | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_blur_effect_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_concentric_ring_function` | Create property function that computes stats per concentric ring (edge to core). | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_concentric_ring_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_entropy_function` | Create property function that computes Shannon entropy of intensity distribution. | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_entropy_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_intensity_function` | Create intensity property function over entire image (no mask). | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_intensity_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_masked_intensity_function` | Create intensity property function over masked pixels only (mask > 0.5). | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_masked_intensity_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_regionprops_function` | Create property function that extracts skimage regionprops from a mask. | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_regionprops_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `create_texture_function` | Create property function that computes GLCM texture features within the mask. | ✅ | [Properties](/PhenoMe/advanced/api/properties/#create_texture_function) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `DataFrameMetadata` | Metadata lookup from DataFrame. Supports single and multi-channel modes. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#dataframemetadata) | | `default_metadata_from_path` | Simple, dataset-agnostic metadata extractor used by default. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#default_metadata_from_path) · [Experiment Details](/PhenoMe/guides/experiment-details/) · [Metadata](/PhenoMe/advanced/api/metadata/) | | `DefaultMetadata` | Minimal metadata extractor: file_path and filename from path. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#defaultmetadata) | | `DinoV2ModelWrapper` | Heritage wrapper for DINOv2 models from Meta AI. | 🔧 | [Model Wrappers](/PhenoMe/advanced/api/model-wrapper/#class-dinov2modelwrapper) | | `get_default_device` | Return a default device for phenotyping operations. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/#get_default_device) | | `get_metadata_from_path` | Create metadata extractor function from a path template. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#get_metadata_from_path) · [Experiment Details](/PhenoMe/guides/experiment-details/) · [Metadata](/PhenoMe/advanced/api/metadata/) | | `get_preset_property_functions` | Return a preset dict of property functions for use with compute_properties. | ✅ | [Properties](/PhenoMe/advanced/api/properties/#get_preset_property_functions) · [Select properties](/PhenoMe/guides/select-properties/) · [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | `load_dinov2_model` | Load a DINOv2 model and return a DinoV2ModelWrapper ready for processing. | ✅ | [Model-Wrapper](/PhenoMe/advanced/api/model-wrapper/#load_dinov2_model) · [Model Wrappers](/PhenoMe/advanced/api/model-wrapper/#load_dinov2_model) | | `log_computing` | Log a one-line status when a step cannot report a progress bar. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#log_computing) | | `make_dataframe_metadata_fn` | Build metadata function using a dataframe for metadata lookup. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#make_dataframe_metadata_fn) · [Experiment Details](/PhenoMe/guides/experiment-details/) · [Metadata](/PhenoMe/advanced/api/metadata/) | | `MetadataBase` | Base class for metadata extraction with configurable columns and ID handling. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#metadatabase) | | `ModelWrapper` | Base wrapper for vision model embedding extraction. | 🔧 | [Model Wrappers](/PhenoMe/advanced/api/model-wrapper/#class-modelwrapper) | | `normalize_by_dtype_max` | Normalize an image to [0, 1] using effective intensity scale inference. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `PadToSize` | Pad image (C, H, W) equally on all sides to pad_size. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `PathTemplateMetadata` | Metadata extractor from path template with capture groups. | ✅ | [Metadata Classes](/PhenoMe/advanced/api/metadata/#pathtemplatemetadata) | | `ProgressCallback` | Type alias: ``(current, total, desc) -> None`` GUI progress hook. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#report_progress) | | `quantile_normalize` | Apply quantile normalization to each channel independently. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#quantile_normalize) | | `report_progress` | Invoke *callback* if provided; no-op when ``None``. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#report_progress) | | `resolve_intensity_scale` | Infer how to normalize image intensities to [0, 1]. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#resolve_intensity_scale) | | `scale_minmax` | Apply min-max scaling to each channel independently. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `set_determinism` | Set random seeds for reproducibility across all frameworks. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/#set_determinism) | | `track` | Wrap *iterable* in a ``tqdm`` progress bar. | ✅ | [Utilities](/PhenoMe/advanced/api/utilities/#track) | | `TransformBuilder` | Builds torchvision transform pipelines for image preprocessing. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/#transforms--transformbuilder) | | `TypeMaxNorm` | Normalize image by effective intensity scale (shared with normalize_by_dtype_max). | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | --- ## I/O (`phenome.io`) | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `align_by_metadata` | Compute metadata-based alignment for checkpoint resume. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `align_by_paths` | Compute path-based alignment for properties checkpoint resume. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `CheckpointManager` | Crash-safe incremental checkpoint backed by a single HDF5 file. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/#checkpointmanager) · [HDF5 Protocol](/PhenoMe/advanced/database_protocol/) | | `ensure_hwc` | Ensure image is in (H, W, C) format. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/#ensure_hwc) | | `FileDiscovery` | Finds image files and extracts metadata from directories. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/#file-discovery--filediscovery) · [HDF5 Protocol](/PhenoMe/advanced/database_protocol/) | | `read_image` | Read an image and return as a numpy array. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/#read_image) | --- ## Plugins (`phenome.plugins`) *All advanced — for extending the pipeline.* | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `get_blob_properties` | Extract blob properties using Difference of Gaussians (DoG) blob detection. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#get_blob_properties) · [Extending](/PhenoMe/guides/extending/) · [Select properties](/PhenoMe/guides/select-properties/) | | `get_metadata_extractor` | Get a registered metadata extractor by name. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#get_metadata_extractor) · [Extending](/PhenoMe/guides/extending/) | | `get_property` | Get a registered property function by name. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#get_property) · [Extending](/PhenoMe/guides/extending/) · [Plugins](/PhenoMe/guides/plugins/) | | `get_report_sections` | Return all registered report section generators. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#get_report_sections) · [Extending](/PhenoMe/guides/extending/) | | `list_properties` | Return names of all registered property functions. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#list_properties) · [Extending](/PhenoMe/guides/extending/) · [Plugins](/PhenoMe/guides/plugins/) | | `my_custom_max_intensity` | Use as a template for creating custom property functions. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#my_custom_max_intensity) · [Extending](/PhenoMe/guides/extending/) | | `register_metadata_extractor` | Register a custom metadata extractor. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#register_metadata_extractor) · [Extending](/PhenoMe/guides/extending/) · [Plugins](/PhenoMe/guides/plugins/) | | `register_property` | Register a custom property function. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#register_property) · [Extending](/PhenoMe/guides/extending/) · [Plugins](/PhenoMe/guides/plugins/) | | `register_report_section` | Register a custom report section generator. | 🔧 | [Plugins](/PhenoMe/advanced/api/plugins/#register_report_section) · [Extending](/PhenoMe/guides/extending/) · [Plugins](/PhenoMe/guides/plugins/) | --- ## Core (`phenome.core`) *Advanced helpers for custom scripts extending or bypassing the pipeline.* | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `append_property_columns` | Add property columns to a Tier-A DataFrame. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `build_combined_features` | Build combined embedding + property matrix with balanced normalization. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `build_export_dataframe` | Build a DataFrame with the complete dataset for export. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `build_metadata_columns` | Build dict of metadata columns suitable for DataFrame creation. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `clean_correlation_inputs` | Clean correlation inputs by removing NaN/inf values. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_distance_correlation` | Compute distance correlation between x and y. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_entropy` | Compute entropy of data using histogram (PyTorch on GPU/CPU). | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_lasso_interpretability` | Explain a target variable y using a LASSO model on features x. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_mutual_info` | Compute MI-derived correlation coefficient between x and y. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_pearson_correlation` | Compute Pearson correlation between x and y using torch.corrcoef on GPU/CPU. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_rf_interpretability` | Explain a target variable y using a Random Forest model on features x. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `compute_spearman_correlation` | Compute Spearman rank correlation between x and y using scipy. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `filter_indices` | Return indices that satisfy provided metadata filters and exclusions. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `get_all_metadata_keys` | Get sorted unique metadata keys present in results. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `get_metadata_value` | Safely fetch a metadata value for an image index. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `get_metadata_value_from_dict` | Get value from a single metadata dict with case-insensitive key matching. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `IMAGE_INDEX` | str(object='') -> str | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `IMAGE_NAME` | str(object='') -> str | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `IMAGE_PATH` | str(object='') -> str | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `is_per_image_df` | Return whether ``df`` satisfies the per-image DataFrame contract (Tier A). | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `metadata_to_stable_key` | Build a deterministic, portable key from metadata for matching across devices. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `optimize_property_types` | Reduce memory usage of property values. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `per_image_dataframe` | Build a Tier-A per-image DataFrame. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `PhenoMeProtocol` | Protocol describing attributes and methods mixins expect from PhenoMe. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `PhenoMeResults` | Typed container for per-image phenotyping data (``PhenoMe.results``). | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `prepare_embedding_dataframe` | Create a DataFrame with embedding/reduction coordinates, metadata, and properties. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `reorder_tier_a_columns` | Place Tier-A id columns first, then metadata, then remaining columns. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `run_dimensionality_reduction` | Run dimensionality reduction using pipeline data and return DataFrame. | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `run_dimensionality_reduction_matrix` | Run dimensionality reduction on a raw matrix (pure, no pipeline). | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `TIER_A_EXCLUDE_FROM_PROPERTIES` | frozenset() -> empty frozenset object | 🔧 | [Utilities](/PhenoMe/advanced/api/utilities/) | | `validate_results` | Check that pipeline results have consistent lengths across all arrays. | 🔒 | [Utilities](/PhenoMe/advanced/api/utilities/) | --- ## Report (`phenome.report.generator`) | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `ReportConfig` | Configuration for report generation. Pass to generate_report via config=. | ✅ | [Report](/PhenoMe/advanced/api/report/#reportconfig) | | `generate_report` | Generate comprehensive standalone HTML report from phenotyping results. | ✅ | [Report](/PhenoMe/advanced/api/report/#generate_report) | --- ## Mixins (`phenome.mixins`) *Prefer using `PhenoMe`; these classes implement its methods.* | Import | Description | Tier | Extended docs | | --- | --- | --- | --- | | `collate_fn` | Custom collate function that handles failed loads and split/combined channels. | 🔒 | [Pipeline](/PhenoMe/advanced/api/pipeline/) | | `create_colab_interactive_explorer` | Launch the interactive explorer built only for Google Colab. | ✅ | [Interactive Explorer](/PhenoMe/advanced/api/interactive/#create_colab_interactive_explorer) | | `create_interactive_explorer` | Launch an interactive explorer for phenotyping results in Jupyter. | ✅ | [Interactive Explorer](/PhenoMe/advanced/api/interactive/#create_interactive_explorer) | | `EmbeddingExtractor` | Extracts embeddings from images using a ModelWrapper and DataLoader. | 🔒 | [Pipeline](/PhenoMe/advanced/api/pipeline/) | | `PhenoMeAnalysis` | Pipeline providing analysis methods for PhenoMe. | 🔒 | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-compute_clustering) | | `PhenoMeBatchCorrection` | Mixin: ``correct_batches()`` for plate / batch effects. | 🔒 | [Pipeline](/PhenoMe/advanced/api/pipeline/) | | `PhenoMeDataset` | Dataset for loading and preprocessing images for phenotyping analysis. | 🔒 | [Pipeline](/PhenoMe/advanced/api/pipeline/) | | `PhenoMeDistances` | Provides distance computation methods for PhenoMe. | 🔒 | [Distances](/PhenoMe/advanced/api/distances/#api-phenomedistances-compute_reference_distances) | | `PhenoMeInteractive` | High-performance interactive explorer for PhenoMe results. | ✅ | [Interactive Explorer](/PhenoMe/advanced/api/interactive/#class-phenomeinteractive) | | `PhenoMeProperties` | Pipeline providing property computation and reporting for PhenoMe. | 🔒 | [Pipeline](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-compute_properties) · [Properties](/PhenoMe/advanced/api/properties/) | | `PhenoMeVisualization` | Pipeline class providing visualization methods for PhenoMe. | 🔒 | [Visualization](/PhenoMe/advanced/api/visualization/#api-_drplotsmixin-plot_pca) | --- # PhenoMe API Reference > Main orchestrator — data, properties, analysis, reporting.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.pipeline`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/pipeline.py), the mixins under [`phenome.mixins`](https://github.com/AAitorG/PhenoMe/tree/main/phenome/mixins), and [`phenome.core.pipeline_results`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/core/pipeline_results.py). ::: **See also:** [Getting started](/PhenoMe/getting-started/) · [Concepts](/PhenoMe/concepts/) · [Visualization](/PhenoMe/advanced/api/visualization/) · [Distances](/PhenoMe/advanced/api/distances/) · [Model wrappers](/PhenoMe/advanced/api/model-wrapper/) ## Class `PhenoMe` Main class for phenotyping analysis using deep learning embeddings. Provides: compute_properties, property_stats_by_group; compute_clustering, detect_outliers, find_prototypes; compute_reference_distances; correct_batches; plot_pca, plot_tsne, plot_umap, and related methods. **Embedding lifecycle:** - **Eager**: When no checkpoint is used, embeddings live in ``results.embeddings`` (np.ndarray). - **Lazy**: When a checkpoint is active (``self._db``), ``results.embeddings`` is ``None``; embeddings are read on demand via ``get_embeddings()`` from the HDF5 file. - **Temporal**: Rows from ``process_temporal_images()`` use ``self._temporal_embeddings`` while ``_db`` is open; ``get_embeddings()`` merges them with checkpoint rows by index. After ``checkpoint_context()`` exits with temporal data present, the merged array is copied to ``results.embeddings`` and the temporal buffer is cleared. **Args:** - **`device`**: Optional torch.device for GPU-accelerated analysis operations. Recommended for faster computation. If None, operations run on CPU. - **`seed`**: Optional random seed for reproducibility (default: None). When set, used for all random operations (determinism, clustering, t-SNE, UMAP, mutual information, random sampling in reports). If None, no seeds are set anywhere. - **`use_gpu_for_dr`**: If True and device is CUDA, uses TorchDR for GPU-accelerated dimensionality reduction (PCA, t-SNE, UMAP). If False (default), always use sklearn/umap-learn on CPU. ```python PhenoMe( device: torch.device | str | None = None, seed: int | None = None, use_gpu_for_dr: bool = False ) ``` ### Data ingestion and embedding extraction
Method

find_files

```python PhenoMe.find_files( self, image_dir: str, mask_dir: str | None = None, extensions: list[str] | None = None, metadata_fn: collections.abc.Callable[[str], dict[str, typing.Any]] | phenome.metadata.base.MetadataBase | None = None, on_missing_metadata: str = 'drop', mask_filename_column: str | None = None, mask_extensions: list[str] | None = None, channel_names: list[str] | None = None ) -> DataFrame ```
Discover image and mask files from directories, extract metadata, and cache internally. Recursively scans `image_dir` for image files and optionally aligns them with masks from `mask_dir`. Extracts per-file metadata using `metadata_fn` and stores the result as the internal file DataFrame used by `process_images()`, `compute_properties()`, and `inspect_data()`. **Args:** - **`image_dir`** (`str`): Root directory or glob pattern for image files. Scanned recursively. Example: "path/to/images/" or "path/to/**/*.tif". - **`mask_dir`** (`str or None`): Root directory for corresponding mask files. If provided, adds a 'mask_path' column to the output DataFrame. Default: None (no masks). - **`extensions`** (`list of str or None`): File extensions to include (e.g., [".tif", ".png", ".jpg"]). If None, defaults to common image formats. Default: None. - **`metadata_fn`** (`callable, MetadataBase, or None`): Function or object to extract metadata from file paths. Signature: `fn(file_path: str) -> dict[str, Any]`. Examples: - `DefaultMetadata()`: Simple metadata. - `PathTemplateMetadata(template="...")`: Extract from path pattern. - `lambda p: {'batch': p.split('/')[-2]}`: Custom function. If None, no metadata extracted. Default: None. - **`on_missing_metadata`** (`str`): How to handle files with missing metadata. One of: - 'drop': Remove rows with incomplete metadata. - 'keep': Include rows with NaN metadata. Default: 'drop'. - **`mask_filename_column`** (`str or None`): Metadata column name used to locate mask files. If set, masks are located using this column value. Useful for custom mask naming. Default: None (standard alignment logic). - **`mask_extensions`** (`list of str or None`): Extensions to try when exact mask filename fails (e.g., [".png", ".tif"]). Default: None (exact filename required). - **`channel_names`** (`list of str or None`): Optional labels for image channels, in channel-axis order (same order as list entries in ``file_path`` for multi-file samples). When set, multi-channel property columns use these labels as suffixes (e.g. ``intensity_entropy_DAPI`` instead of ``intensity_entropy_ch0``). Names must be non-empty, unique (case-insensitive), and contain no whitespace. Default: None (integer ``_ch{idx}`` suffixes). **Returns:** pd.DataFrame: File discovery DataFrame with columns: - 'file_path': (str or list of str) Image file path(s). - 'mask_path': (str, if mask_dir set) Corresponding mask path. - Metadata columns: Custom columns from `metadata_fn` if provided. Shape: (N, 2+M) where N is number of images, M is metadata columns. **Raises:** - **`FileNotFoundError`**: If `image_dir` does not exist. - **`ValueError`**: If all files are dropped due to `on_missing_metadata='drop'`, or if `channel_names` is invalid. - **`RuntimeError`**: If metadata extraction fails or masks cannot be resolved. **Example:** ```python Discover images with metadata: >>> pm = PhenoMe() >>> df = pm.find_files( ... "data/images/", ... metadata_fn=PathTemplateMetadata(template="batch_{batch}/sample_{id}"), ... extensions=[".tif", ".png"] ... ) >>> print(df.shape) (150, 4) # 150 images, 4 columns: file_path, batch, id, [others] With masks: >>> df = pm.find_files( ... image_dir="data/images/", ... mask_dir="data/masks/", ... extensions=[".tif"] ... ) >>> print(df.columns) Index(['file_path', 'mask_path'], dtype='object') Named channels for property suffixes: >>> df = pm.find_files( ... "data/images/", ... channel_names=["DAPI", "GFP"], ... ) ``` **See Also:** - `set_file_df`: Manually provide a file DataFrame. - `process_images`: Process discovered images.
Method

set_file_df

```python PhenoMe.set_file_df( self, file_df: pandas.DataFrame, channel_names: list[str] | None = None ) -> DataFrame ```
Set the internal file DataFrame used for processing. Validates that file_df has a 'file_path' column and optionally 'mask_path'. Stores a normalized copy for use by process_images, compute_properties, inspect_data, and related methods. **Args:** - **`file_df`**: DataFrame with 'file_path' column (str or list of str per row). May include 'mask_path' and metadata columns. - **`channel_names`**: Optional labels for image channels, in channel-axis order (same order as list entries in ``file_path`` for multi-file samples). When set, multi-channel property columns use these labels as suffixes (e.g. ``intensity_entropy_DAPI`` instead of ``intensity_entropy_ch0``). Default: None (integer ``_ch{idx}`` suffixes). **Returns:** The normalized DataFrame that was stored.
Method

process_images

```python PhenoMe.process_images( self, model_wrapper: phenome.utils.model_wrapper.ModelWrapper, batch_size: int = 32, num_workers: int = 4, filters: dict[str, list[typing.Any]] | None = None, exclude: dict[str, list[typing.Any]] | None = None, channel_mode: Literal['split', 'combined'] = 'split', channels: list[int] | None = None, preprocessing_fn: collections.abc.Callable[[numpy.ndarray], numpy.ndarray] | None = None, custom_transformations: typing.Any | None = None, preprocessing_description: str | None = None, transformations_description: str | None = None, append: bool = False, resize_size: int | None = 224, pad_size: int | None = None, checkpoint_path: str | None = None, force_rgb: bool = True, l2_normalize_channels: bool = True, save_every: int = 5, lazy_checkpoint: bool = True, force_reprocess: bool = False, progress_callback: collections.abc.Callable[[int, int, str], None] | None = None ) -> None ```
Extract embeddings from images using a pretrained or custom model. Reads images from the file DataFrame (set via `find_files()` or `set_file_df()`), processes them through the model in batches, and stores embeddings and metadata in `self.results`. Supports filtering, channel selection, custom preprocessing, and checkpointing for resumable processing. **Embedding storage:** - Eager (default): Embeddings stored in `results.embeddings` (ndarray). - Lazy (with checkpoint): Embeddings stored in HDF5; `results.embeddings` is None and embeddings are loaded on-demand via `get_embeddings()`. - Temporal (incremental): New images added to existing checkpoint via `process_temporal_images()`. **Default processing order** (when ``custom_transformations`` is None): 1. Load image (native dtype) 2. Optional ``preprocessing_fn`` 3. Channel selection / ``channel_mode`` / ``force_rgb`` 4. TypeMaxNorm intensity scaling (identity if max <= 1) 5. Optional pad, then resize (default 224) 6. ImageNet mean/std normalize 7. Vision model -> embedding 8. Split mode: L2-normalize each channel embedding (default True) A concise processing pipeline is logged at INFO. Inspect ``get_processing_pipeline()`` or ``print_processing_pipeline()``. Custom ``custom_transformations`` replace steps 4-6. **Args:** - **`model_wrapper`** (`ModelWrapper`): Model instance (e.g., from `load_dinov2_model()`). Must implement `_get_embeddings(tensor: torch.Tensor) -> torch.Tensor`. For non-PyTorch models (Keras, TensorFlow, etc.), see the [Custom Model Wrapper guide](https://AAitorG.github.io/PhenoMe/guides/custom-model-wrapper/). - **`batch_size`** (`int`): Batch size for processing. Default: 32. Larger batches are faster but use more GPU memory. - **`num_workers`** (`int`): Number of loader processes. In Jupyter, automatically reduced to 0 to avoid multiprocessing issues. Default: 4. - **`filters`** (`dict or None`): Include only rows matching criteria. Format: `{column: [value1, value2, ...]}`. Default: None (no filtering). - **`exclude`** (`dict or None`): Exclude rows matching *any* criterion (OR across fields). Same value format as `filters`. Default: None. - **`channel_mode`** (`str`): How to handle multi-channel images. One of: - 'split': Process each channel separately (default). - 'combined': Process all channels as RGB or grayscale. - **`channels`** (`list of int or None`): Specific channel indices to use. If None, uses all available. Default: None. - **`preprocessing_fn`** (`callable or None`): Custom preprocessing function applied to each image before the model. Signature: `fn(image: ndarray) -> ndarray`. Example: normalization, contrast adjustment, etc. Default: None. - **`custom_transformations`** (`transforms object or None`): PyTorch transforms to apply before the model (e.g., resize, normalize). If None, the default order is TypeMaxNorm, optional pad, optional resize, then ImageNet Normalize. Default: None. - **`preprocessing_description`** (`str or None`): Optional plain-language note for `preprocessing_fn`. Shown in the INFO processing pipeline log and on `get_processing_pipeline()`. Internals are never inspected. Recommended when using custom preprocessing so the log describes what the function does. Default: None. - **`transformations_description`** (`str or None`): Optional plain-language note for `custom_transformations`. Shown in the INFO processing pipeline log and on `get_processing_pipeline()`. Internals are never inspected. Default: None. - **`append`** (`bool`): If True, append to existing embeddings in `results.embeddings`. If False (default), replace. Default: False. - **`resize_size`** (`int or None`): Image resize dimension (square). Default: 224. Set to None to skip resizing. - **`pad_size`** (`int or None`): Padding size (square). Default: None (no padding). - **`checkpoint_path`** (`str or None`): HDF5 file path for lazy storage and resumable processing. If file exists, processing resumes from last checkpoint. If None, embeddings stored in-memory. Default: None. - **`force_rgb`** (`bool`): If True, convert grayscale to RGB before model. Default: True. - **`l2_normalize_channels`** (`bool`): In ``channel_mode='split'``, L2-normalize each channel embedding before concatenation so channels contribute equally. Set False to preserve raw channel magnitudes (one channel may dominate). Ignored in combined mode. Default: True. - **`save_every`** (`int`): Save checkpoint every N batches (when using checkpoint_path). Default: 5. - **`lazy_checkpoint`** (`bool`): If True and checkpoint_path is set, enable lazy loading (embeddings not kept in-memory). If False, load all embeddings after processing. Default: True. - **`force_reprocess`** (`bool`): If True and ``checkpoint_path`` already exists, delete that file and process all requested images from scratch (no resume). Default: False. - **`progress_callback`** (`callable or None`): Optional ``(current, total, desc) -> None`` hook for GUI/notebook progress bars. Invoked after each batch. When set, the default ``tqdm`` bar is disabled. Default: None. **Returns:** None. Modifies `self.results` in-place with embeddings, metadata, and paths. **Raises:** - **`FileNotFoundError`**: If file_df not set (call `find_files()` or `set_file_df()` first). - **`ValueError`**: If filters/exclude reference non-existent columns or produce no data. - **`RuntimeError`**: If model inference fails or checkpoint is corrupted. - **`IOError`**: If checkpoint file cannot be created or read. **Example:** ```python Process images with DINOv2 model: >>> from phenome import PhenoMe, load_dinov2_model >>> pm = PhenoMe(device="cuda") >>> pm.find_files("images/") >>> model = load_dinov2_model("dinov2_vitb14") >>> pm.process_images(model, batch_size=64, resize_size=224) >>> print(pm.results.embeddings.shape) (1000, 768) Optional description for custom preprocessing (appears in the INFO processing pipeline log even though the callable itself is opaque): >>> def enhance_contrast(image): ... p1, p99 = np.percentile(image, (1, 99)) ... return np.clip(image, p1, p99) >>> pm.process_images( ... model, ... preprocessing_fn=enhance_contrast, ... preprocessing_description="clip 1st/99th percentiles", ... ) With filtering and checkpoint: >>> pm.process_images( ... model, ... filters={"batch": ["batch_1", "batch_2"]}, ... checkpoint_path="embeddings.h5", ... lazy_checkpoint=True ... ) With a Jupyter progress bar: >>> from ipywidgets import IntProgress, Label, VBox >>> from IPython.display import display >>> bar, lab = IntProgress(max=1), Label() >>> display(VBox([lab, bar])) >>> def on_progress(current, total, desc): ... bar.max = max(total, 1) ... bar.value = current ... lab.value = f"{desc}: {current}/{total}" >>> pm.process_images(model, progress_callback=on_progress) ``` **See Also:** - `find_files`: Discover and organize image files. - `process_temporal_images`: Incrementally add images to existing checkpoint. - `get_embeddings`: Retrieve embeddings (handles lazy loading). - `compute_properties`: Extract morphological properties from embeddings. - `get_processing_pipeline`: Structured ordered processing pipeline for this run. - `print_processing_pipeline`: Print the same text as the INFO log.
Method

process_temporal_images

```python PhenoMe.process_temporal_images( self, model_wrapper: phenome.utils.model_wrapper.ModelWrapper, files: str | list[str] | pandas.DataFrame, batch_size: int = 32, num_workers: int = 4, channel_mode: Optional[Literal['split', 'combined']] = None, channels: list[int] | None = None, preprocessing_fn: collections.abc.Callable[[numpy.ndarray], numpy.ndarray] | None = None, custom_transformations: typing.Any | None = None, preprocessing_description: str | None = None, transformations_description: str | None = None, resize_size: int | None = None, pad_size: int | None = None, force_rgb: bool | None = None, l2_normalize_channels: bool | None = None, extensions: list[str] | None = None ) -> None ```
Process new images in-memory (temporary) and append to the current session. Use this to explore additional images (e.g., from a new condition or replicate) without re-running process_images. Temporal images are not persisted to checkpoint; visualize or export before calling clear_temporal_data() or reset(). Requires process_images() to have been run first. Processing parameters (channel_mode, channels, resize_size, etc.) are inherited from the base run when not specified. Temporal rows get metadata['source'] = 'NEW'. Optional ``preprocessing_description`` / ``transformations_description`` are recorded on the session-only temporal pipeline (same optional notes as ``process_images``; they appear in the INFO processing pipeline log). Default processing order matches ``process_images`` (load, optional preprocess, channels/RGB, TypeMaxNorm, pad/resize, ImageNet, model, split L2). A concise processing pipeline is logged at INFO.
Method

inspect_data

```python PhenoMe.inspect_data( self ) -> DataFrame ```
Inspect image and mask dimensions, shapes, dtypes, and data ranges. Delegates to FileDiscovery. Uses stored dtypes (not the float32 cast from ``read_image``). Requires file_df from ``set_file_df`` or ``find_files``.
Method

clear_temporal_data

```python PhenoMe.clear_temporal_data( self ) -> int ```
Remove all temporal images (metadata['source'] == 'NEW') from the session. **Returns:** - **`int`**: Number of temporal rows removed.
### Accessors and inspection
Method

get_embeddings

```python PhenoMe.get_embeddings( self, indices: int | list[int] | numpy.ndarray | None = None ) -> numpy.ndarray | None ```
Return embeddings for the given row indices. When a checkpoint / HDF5 database is active (``self._db`` is not None), only the requested rows are read from disk — the core of the lazy-loading strategy. When temporal embeddings exist (from ``process_temporal_images``), rows with index ``>= self._temporal_start_idx`` are read from ``self._temporal_embeddings``; earlier rows come from the checkpoint. When no database is active, returns ``results.embeddings`` (optionally sliced). If that array holds only checkpoint rows (length ``_temporal_start_idx``) and temporal embeddings are still in memory, they are appended to form a full ``(n_images, D)`` array. If ``results.embeddings`` already has ``n_images`` rows (e.g. materialized on ``checkpoint_context`` exit), temporal data is not appended again. **Args:** - **`indices`**: Optional row index or sequence of row indices (0-based). A single ``int`` returns shape ``(1, D)``. If None, returns all embeddings. **Returns:** np.ndarray shape ``(len(indices), D)`` float32, or None if no embeddings are available.
Method

get_image_info

```python PhenoMe.get_image_info( self, idx: int, distance_results: pandas.DataFrame | None = None ) -> dict ```
Return metadata, properties, and optional distance for image idx. **Args:** - **`idx`**: Image index (0 to n_images-1). - **`distance_results`**: Optional DataFrame from compute_reference_distances. **Returns:** - **`dict`**: Keys image_index, image_name, image_path, metadata keys, property keys, distance (if distance_results provided), is_reference (if applicable).
Method

get_available_metadata_keys

```python PhenoMe.get_available_metadata_keys( self ) -> list ```
Return sorted metadata keys.
Method

get_available_property_keys

```python PhenoMeProperties.get_available_property_keys( self ) -> list ```
Return sorted list of property keys stored in results.
Property

has_embeddings

Property on PhenoMe

Return True if embedding data is available (lazy or eager). A properties-only checkpoint has ``n_committed > 0`` but no embedding dataset; we additionally require ``embedding_dim`` to be a positive int.
Property

embedding_dim

Property on PhenoMe

Return the embedding dimensionality, or 0 if unavailable.
Method

get_processing_pipeline

```python PhenoMe.get_processing_pipeline( self ) -> dict[str, Any] | None ```
Return the session processing pipeline, or None if none was recorded. The dict has ``version``, ``base``, and optional ``temporal`` and ``properties``. After ``load_results``, embedding and property steps come from ``/processing_pipeline``; temporal exists only in the current session. **See Also:** - `print_processing_pipeline`: Print the concise pipeline text.
Method

print_processing_pipeline

```python PhenoMe.print_processing_pipeline( self ) -> None ```
Print the ordered processing pipeline for this session. Uses the same text as the INFO log during ``process_images`` and ``compute_properties``. After ``load_results``, prints the steps stored in ``/processing_pipeline`` (embeddings and properties). **See Also:** - `get_processing_pipeline`: Structured dict form of the same record.
### Data lifecycle and export
Method

reset

```python PhenoMe.reset( self, verbose: bool = False, clear_file_df: bool = False ) -> None ```
Reset all stored data to a clean state. Closes any open HDF5 database handle before clearing results. By default preserves _file_df so compute_properties and inspect_data continue to work after process_images. Set clear_file_df=True for a full reset (e.g. when switching to a completely new dataset).
Method

reset_properties

```python PhenoMeProperties.reset_properties( self, verbose: bool = False ) -> None ```
Reset only the computed properties. **Args:** - **`verbose`**: If True, logs a confirmation message.
Method

transfer_metadata_to_properties

```python PhenoMeProperties.transfer_metadata_to_properties( self, columns: str | list[str] ) -> None ```
Transfer specified metadata columns to properties. **Args:** - **`columns`**: A single metadata column name or a list of names to transfer.
Method

save_results

```python PhenoMe.save_results( self, path: str | None = None, compression: str = 'gzip' ) -> None ```
Save results to HDF5 (atomic write or in-place flush). When a checkpoint database is already active (``self._db`` is set), the data is already on disk; this method flushes any uncommitted buffers and logs the existing path. When no database is active, writes ``self.results`` to a new HDF5 file using a temporary-file + atomic rename for crash safety. For full reproducibility, also call [export_experiment_config](/PhenoMe/advanced/api/pipeline/#api-phenome-export_experiment_config) to save seed, use_gpu_for_dr, reference_filters, and model name (not stored in the checkpoint). **Args:** - **`path`**: Explicit output path. - **`compression`**: HDF5 compression algorithm (used only for new files).
Method

load_results

```python PhenoMe.load_results( self, path: str | None = None, lazy_checkpoint: bool = True ) -> None ```
Load and use an existing results/checkpoint file. Metadata and properties are loaded immediately. Embeddings are either loaded into RAM (lazy_checkpoint=False) or accessed on-demand from disk (lazy_checkpoint=True). Uses the internally stored file_df (from [find_files](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files) or [set_file_df](/PhenoMe/advanced/api/pipeline/#api-phenome-set_file_df)) to resolve paths so the checkpoint works on this machine. Call find_files or set_file_df first. For separate embeddings and properties checkpoints, prefer [load_embeddings](/PhenoMe/advanced/api/pipeline/#api-phenome-load_embeddings) then [load_properties](/PhenoMe/advanced/api/pipeline/#api-phenome-load_properties). **Args:** - **`path`**: Path to .h5 or .hdf5 file. - **`lazy_checkpoint`**: If True (default), keep the checkpoint file open. If False, load all data into RAM and close the file. **Raises:** - **`FileNotFoundError`**: If file does not exist. - **`ValueError`**: If file format is not recognised as HDF5. - **`RuntimeError`**: If file_df is not available (call find_files or set_file_df first). - **`ConcurrentCheckpointAccessError`**: If the checkpoint is already open in another notebook or process (only one instance can access it at a time).
Method

checkpoint_context

```python PhenoMe.checkpoint_context( self, path: str | None = None, lazy_checkpoint: bool = True ) -> Generator ```
Context manager that loads a checkpoint and guarantees it is closed on exit. More reliable than relying on __del__ for cleanup. Use when you need to ensure the HDF5 file handle is released (e.g. before moving or deleting the file, or when opening multiple checkpoints in sequence). **Embeddings:** While the context is open, lazy embeddings are read from the HDF5 file via ``get_embeddings()``. On exit, if temporal embeddings were loaded in memory (``process_temporal_images`` while the checkpoint was open), the merged full embedding matrix is copied to ``results.embeddings`` and ``self._temporal_embeddings`` is cleared. Otherwise ``results.embeddings`` may stay ``None``; call ``load_results(..., lazy_checkpoint=True)`` or enter another ``checkpoint_context`` before using ``get_embeddings()`` again. **Args:** - **`path`**: Checkpoint HDF5 path. If None, uses the path from the last ``load_results`` / ``process_images`` session when available. - **`lazy_checkpoint`**: If True, keep the HDF5 file open for on-demand reads inside the context. Default: True. **Example:** ```python pheno.find_files("path/to/images") with pheno.checkpoint_context("results.h5") as p: p.plot_pca(color_by="condition") # Checkpoint closed here ```
Method

export_experiment_config

```python PhenoMe.export_experiment_config( self, path: str, reference_filters: dict[str, typing.Any] | None = None, model_name: str | None = None, checkpoint_path: str | None = None, **extra: Any ) -> None ```
Export experiment configuration for reproducibility. Writes a JSON file with init parameters, environment, processing params, and silently recorded method steps. Call after the analysis you want to document. Optional ``model_name`` / ``reference_filters`` override values already captured from ``process_images`` / ``compute_reference_distances``. **Args:** - **`path`**: Output path for config.json. - **`reference_filters`**: Optional dict used for compute_reference_distances (e.g. {'condition': 'Control'}). Include for full traceability. - **`model_name`**: Optional model identifier (e.g. 'dinov2_vitb14_reg'). - **`checkpoint_path`**: Optional checkpoint path used during processing (if not provided and a checkpoint is open, uses self._db.path). **extra: Additional key-value pairs to include in the config.
Method

export_methods_markdown

```python PhenoMe.export_methods_markdown( self, path: str ) -> None ```
Write a markdown methods summary from the silent run log. Opt-in companion to [export_experiment_config](/PhenoMe/advanced/api/pipeline/#api-phenome-export_experiment_config) for paper supplements. Does nothing unless this method is called.
Method

export_dataset_table

```python PhenoMe.export_dataset_table( self, output_path: str | None = None, dist_results: pandas.DataFrame | None = None, include_embeddings: Union[bool, Literal['separate']] = False, export_format: Literal['csv', 'parquet', 'excel'] = 'csv' ) -> DataFrame ```
Export the dataset as a table (CSV, Parquet, or Excel). **Args:** - **`output_path`**: Path to save the export. If None, only returns the DataFrame. - **`dist_results`**: Optional DataFrame from compute_reference_distances. - **`include_embeddings`**: If True, adds embedding columns; if 'separate', saves embeddings to a companion .npy file. - **`export_format`**: Output format: 'csv', 'parquet', or 'excel'. **Returns:** DataFrame with image_index, image_path, metadata, properties, distances, and embeddings (if requested).
### Properties
Method

compute_properties

```python PhenoMeProperties.compute_properties( self, metadata_config: typing.Any | None = None, property_preset: str | None = None, additional_property_functions: dict[str, collections.abc.Callable | list[collections.abc.Callable]] | None = None, checkpoint_path: str | None = None, save_every: int = 50, n_jobs: int = 1, lazy_checkpoint: bool = True, force_update: bool = False, include_metadata: bool = False, progress_callback: collections.abc.Callable[[int, int, str], None] | None = None ) -> DataFrame ```
Compute per-image properties using presets and/or custom functions. Each function receives a 2D image slice and/or mask slice and returns a dict[str, float]. When multiple channels exist, properties are suffixed with ``_ch{idx}``, or with labels from ``find_files`` / ``set_file_df`` ``channel_names`` when provided (e.g. ``_DAPI``). Use ``property_preset`` for built-in property sets and ``additional_property_functions`` to add custom functions on top (optional). This method now relies solely on ``file_df`` (typically produced by [PhenoMe.find_files](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files)) for resolving image and mask paths. Legacy ``image_dir`` / ``mask_dir`` parameters are no longer supported. Images used for properties are loaded, laid out as HxWxC, then scaled with ``normalize_by_dtype_max`` (same 0-1 rule as TypeMaxNorm). Masks are loaded and laid out the same way, then binarized (foreground = pixels ``> 0.5``; no intensity scaling). A concise processing pipeline of those applied steps is logged at INFO. **Args:** - **`metadata_config`**: Optional MetadataBase instance (e.g. from `phenome.metadata`). When it has ``mask_dir`` and ``mask_filename_column``, uses explicit mask lookup. - **`property_preset`**: Preset name. Use ``"none"`` or ``None`` for no preset. Valid: ``"none"``, ``"basic"``, ``"shape"``, ``"intensity"``, ``"standard"``, ``"complete"``. - **`additional_property_functions`**: Extra property functions to add on top of preset. Dict of {requirement: fn_or_list} where requirement is ``"image"``, ``"mask"``, ``"both"``, or ``"any"``. Merged with preset when both are used; used alone when ``property_preset`` is ``"none"`` or ``None``. - **`checkpoint_path`**: HDF5 checkpoint for incremental persistence. - **`save_every`**: Commit frequency (images) when checkpointing. - **`n_jobs`**: Number of parallel jobs for property computation. ``1`` (default) = sequential. Use ``-1`` to use all available CPU cores. - **`lazy_checkpoint`**: If True (default), keep the checkpoint file open. If False, load all data into RAM and close the file. - **`force_update`**: If True and ``checkpoint_path`` points to an existing file, clears stored properties in that file, writes current ``_processing_params`` into the checkpoint config, and recomputes properties for every image (no resume). Use this after changing ``property_preset`` or custom functions, or after log warnings about missing checkpoint property keys. If ``checkpoint_path`` is set but the file does not exist yet, only an info log is emitted; computation proceeds as for a new checkpoint. - **`include_metadata`**: If True, add metadata columns to the returned DataFrame. - **`progress_callback`**: Optional ``(current, total, desc) -> None`` hook for GUI/notebook progress bars. Invoked after each image (sequential) or after each parallel batch. When set, the default ``tqdm`` bar is disabled. Default: None. **Returns:** DataFrame with ``image_index``, ``image_path``, ``image_name``, property values, and optional metadata columns. Also populates ``results.properties`` as ``List[dict]`` (one dict per image). **Raises:** - **`ValueError`**: If no images processed or invalid preset/requirement keys are used. - **`TypeError`**: If ``additional_property_functions`` values are not callable. **Example:** ```python Drive a napari/Qt progress bar from a worker thread: >>> def on_progress(current, total, desc): ... # update Qt/napari progress on the main thread ... progress.setValue(int(100 * current / total) if total else 0) ... progress.setLabelText(desc) >>> pm.compute_properties( ... property_preset="basic", ... progress_callback=on_progress, ... ) ```
Method

property_stats_by_group

```python PhenoMeProperties.property_stats_by_group( self, group_by: list[str] | None = None, properties: list[str] | None = None, print_table: bool = True, print_properties: list[str] | None = None, group_column_width_max: int = 25, content_col_width_max: int = 25 ) -> DataFrame ```
Group properties, compute per-group statistics, and optionally print a table. Uses `_build_properties_dataframe` from computed ``results.properties`` and metadata (same source as [compute_properties](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-compute_properties)). **Args:** - **`group_by`**: Metadata columns to group by. If None, auto-selects first 2. - **`properties`**: Property columns to include in aggregation. If None, auto-detects numeric. Matching is case-insensitive; names must be unique ignoring case. - **`print_table`**: If True, log a formatted mean±std table. - **`print_properties`**: Subset of properties to show in the table. If None, shows all. Matching is case-insensitive; names must be unique ignoring case. - **`group_column_width_max`**: Maximum width for each grouping column when printing. - **`content_col_width_max`**: Maximum width for each property statistic column when printing. **Returns:** Aggregated DataFrame with mean/std/min/max per property per group. **Raises:** - **`TypeError`**: If group_by/properties are invalid types. - **`ValueError`**: If group_by keys are not in DataFrame columns.
Method

top_properties_different_from_reference

```python PhenoMeProperties.top_properties_different_from_reference( self, df: pandas.DataFrame, reference_group: dict[str, typing.Any], k: int = 5, properties: list[str] | None = None, metric: Literal['cohens_d', 'mean_diff'] = 'cohens_d', print_output: bool = True, group_column_width_max: int = 25, top_property_col_width_max: int = 25, top_effect_col_width_min: int = 12 ) -> DataFrame ```
For each non-reference group, return the top k properties that most differentiate it from the reference. Uses Cohen's d (effect size) by default to rank properties by how different each group is from the reference. Both higher and lower values count as "different" (uses |d|). Cohen's d formula (pooled): d = (mean_group - mean_ref) / s_pooled, where s_pooled = sqrt([(n_ref-1)*std_ref² + (n_other-1)*std_other²] / (n_ref + n_other - 2)). Requires sample std (ddof=1) from property_stats_by_group. **Args:** - **`df`**: Aggregated DataFrame from property_stats_by_group(). - **`reference_group`**: Dict mapping grouping column names to values (e.g. {"drug": "Control", "time": "60_min"}). - **`k`**: Number of top properties per group. - **`properties`**: Property names to consider. If None, uses all in DataFrame. Matching is case-insensitive; names must be unique ignoring case. - **`metric`**: 'cohens_d' (effect size) or 'mean_diff' (absolute mean difference). - **`print_output`**: If True, pretty-print the results. - **`group_column_width_max`**: Maximum width for each grouping column in the printed table. - **`top_property_col_width_max`**: Max width for the property name column in the printed table. - **`top_effect_col_width_min`**: Min width for Cohen's d and mean diff columns when printing. **Returns:** DataFrame with columns: grouping cols, property, effect_size (Cohen's d when ``metric='cohens_d'``), mean_diff, ref_mean, group_mean, rank. One row per (group, property) for top-k only.
### Advanced analysis
Method

correct_batches

```python PhenoMeBatchCorrection.correct_batches( self, batch_metadata_key: 'str', method: 'MethodName' = 'sphering', source: 'SourceName' = 'embeddings', property_keys: 'list[str] | None' = None, control_filters: 'dict[str, Any] | None' = None, inplace: 'bool' = True, checkpoint_path: 'str | None' = None, chunk_size: 'int' = 4096, force: 'bool' = False, ridge_multiplier: 'float' = 0.001, min_controls: 'int' = 2 ) -> dict[str, Any] ```
Correct plate-to-plate variation using control wells per batch. Call after :meth:`process_images` (and optionally after ``compute_properties`` when ``source='properties'``). For lazy checkpoints, embeddings are read and rewritten in chunks of *chunk_size* rows. **Args:** - **`batch_metadata_key`**: Metadata column for batch / plate id (case-insensitive). - **`method`**: ``"sphering"`` (default) or ``"zscore"``. - **`source`**: ``"embeddings"`` or ``"properties"``. - **`property_keys`**: Property names to correct when ``source='properties'``. If None, uses all keys from :meth:`get_available_property_keys`. - **`control_filters`**: Same semantics as ``filter_indices`` filters (required). - **`inplace`**: If True, updates ``results.embeddings`` or ``results.properties`` in place. - **`checkpoint_path`**: If set, persist batch-correction metadata and/or export results. - If ``None`` (default): Corrections still apply (RAM or an already-open lazy checkpoint), but nothing new is written for persistence—no ``/batch_correction`` stats on disk and no new HDF5 via :meth:`save_results`. - If ``str``: With an open checkpoint, it is cloned to this path first; stats are saved under ``/batch_correction`` when a DB is active; in eager mode (no DB), results are saved to this path after correction. - **`chunk_size`**: Rows per chunk for embedding I/O and property scatter. - **`force`**: If True, allow re-applying correction after a previous run in this session. - **`ridge_multiplier`**: Covariance ridge strength for sphering (see :func:`compute_batch_stats`). - **`min_controls`**: Minimum control wells required per batch. **Returns:** Dict with batch stats summary (``batches``, ``controls_per_batch``, ``method``, …).
Method

compute_clustering

```python PhenoMeAnalysis.compute_clustering( self, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', n_clusters: int = 5, clustering_method: Literal['kmeans', 'dbscan', 'gmm'] = 'kmeans', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, random_state: int | None = None, normalize: bool = True, reduce_dim: int | None = 100, reduce_method: Literal['pca', 'tsne', 'umap'] = 'pca', return_silhouette: bool = False, dbscan_eps: float | None = None, dbscan_min_samples: int | None = None, include_metadata: bool = False ) -> pandas.DataFrame | tuple[pandas.DataFrame, float | None] ```
Perform clustering and store labels in ``metadata[i]['cluster']`` for each image. **Args:** - **`source`**: ``'embeddings'``, ``'properties'``, or ``'combined'``. - **`n_clusters`**: Number of clusters (k-means, GMM). Ignored for ``clustering_method='dbscan'``. - **`clustering_method`**: ``'kmeans'`` (default), ``'dbscan'``, or ``'gmm'``. - **`property_keys`**: Property subset when *source='properties'*. - **`filters`**: Optional metadata filters. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`random_state`**: Seed for k-means/GMM. If None, uses pipeline seed when available. - **`normalize`**: Whether to normalize data before clustering (default: True). For embeddings, uses L2 normalization. For properties, uses StandardScaler. - **`reduce_dim`**: If set, apply dimensionality reduction before clustering (default: 100). Only applied when data has more features than *reduce_dim*. Set to None to disable dimensionality reduction. - **`reduce_method`**: Method for dimensionality reduction when *reduce_dim* is set: ``'pca'`` (fast, linear), ``'tsne'`` (non-linear, slower), ``'umap'`` (non-linear, preserves structure). Default: ``'pca'``. - **`return_silhouette`**: If True, compute and return the silhouette score (default: False). Returns ``(df, score)``; score is None if it could not be computed. Silhouette requires at least 2 clusters and 2 samples per cluster. - **`dbscan_eps`**: Maximum distance between two samples for DBSCAN (only when clustering_method='dbscan'). If None, uses 0.5. - **`dbscan_min_samples`**: Minimum samples in a neighborhood for DBSCAN (only when clustering_method='dbscan'). If None, uses max(2, n_samples // 20). - **`include_metadata`**: If True, add metadata columns to the returned DataFrame. **Returns:** If return_silhouette=False: Tier-A DataFrame with image_index, image_path, image_name, cluster, and optional metadata. If return_silhouette=True: Tuple of (df, silhouette_score). Score is None if it could not be computed. Cluster labels are 0..K-1; None for excluded/noise.
Method

detect_outliers

```python PhenoMeAnalysis.detect_outliers( self, method: Literal['z-score', 'iqr'] = 'z-score', threshold: float = 3.0, group_by: str | list[str] | None = None, filters: dict[str, typing.Any] | None = None, exclude: dict[str, typing.Any] | None = None, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, normalize: bool = True, drop_outliers: bool = False, plot: bool = True ) -> DataFrame ```
Detect outliers based on distance to centroid. When *source* is ``'embeddings'`` and *normalize* is True (default), embeddings are L2-normalized and group centroids use the **arithmetic mean** (not necessarily a unit vector). Euclidean distance to that centroid is not the same as angular distance to the mean direction; consider property-based or combined sources when a different geometry is needed. **Args:** - **`method`**: ``'z-score'`` or ``'iqr'``. - **`threshold`**: Threshold multiplier. - **`group_by`**: Property/metadata key(s) for grouping. Can be a single string or a list of strings. If a list is provided, groups are formed by combining values from all specified columns (e.g., "drug1-10uM"). If *None*, all images are treated as one group. - **`filters`**: Optional metadata filters. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`source`**: Feature space for outlier detection: - ``'embeddings'``: model embeddings - ``'properties'``: scalar phenotypic properties - ``'combined'``: normalized concatenation of embeddings and properties. - **`property_keys`**: Property subset when *source* is ``'properties'`` or ``'combined'``. - **`normalize`**: Whether to normalize data before outlier detection (default: True). For embeddings, uses L2 normalization. For properties, uses StandardScaler. - **`drop_outliers`**: If True, remove detected outliers from self.results (default: False). - **`plot`**: If True (default), shows one matplotlib figure per group: subplots for detected outliers and a figure title naming the group (``Group: …``). Images are shown without pipeline ``image_transforms`` (same as ``apply_transforms=False`` for display). **Returns:** pd.DataFrame: Tier-A DataFrame containing detected outliers. Columns include: - ``image_index``: Global image index. - ``image_path``: Path to the image file. - ``image_name``: Basename of the image file. - Additional columns for each grouping key (if *group_by* was set). - ``threshold``: The threshold value used for the image's group. - ``distance_to_centroid``: Distance to the group centroid. If no outliers are detected, returns an empty DataFrame. The index is a range index from 0 to N-1.
Method

find_prototypes

```python PhenoMeAnalysis.find_prototypes( self, cluster_col: str | list[str] | None = 'cluster', n_prototypes: int = 5, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any] | None = None, exclude: dict[str, typing.Any] | None = None, metric: str = 'euclidean', normalize: bool = True, plot: bool = True ) -> DataFrame ```
Find images closest to each group centroid. **Args:** - **`cluster_col`**: Property/metadata key(s) for grouping. Can be a single string or a list of strings. If a list is provided, groups are formed by combining values from all specified columns (e.g., "drug1-10uM"). If *None*, all images are treated as one group. - **`n_prototypes`**: How many prototypes per group. - **`source`**: ``'embeddings'``, ``'properties'``, or ``'combined'``. - **`property_keys`**: Property subset when *source='properties'*. - **`filters`**: Optional metadata filters. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`metric`**: ``'euclidean'`` or ``'cosine'``. With ``metric='euclidean'``, the group centroid is the arithmetic mean (even when embeddings are L2-normalized). With ``metric='cosine'``, the centroid direction is L2-normalized before ranking by similarity. - **`normalize`**: Whether to normalize data before finding prototypes (default: True). For embeddings, uses L2 normalization. For properties, uses StandardScaler. - **`plot`**: If True (default), shows one matplotlib figure per group: subplots for that group's prototypes and a figure title naming the group (``Group: …``). Images are shown without pipeline ``image_transforms`` (same as ``apply_transforms=False`` for display). **Returns:** pd.DataFrame: DataFrame with one row per prototype. Columns include grouping keys (split into individual columns if multiple), ``image_index``, ``image_path``, ``image_name``, and ``rank_in_group``.
Method

analyze_group_enrichment

```python PhenoMeAnalysis.analyze_group_enrichment( self, group_by: str | list[str] = 'cluster', property_keys: list[str] | None = None, filters: dict[str, typing.Any] | None = None, exclude: dict[str, typing.Any] | None = None, plot: bool = True, return_fig: bool = False, top_k: int | None = None, figsize: tuple[int, int] = (10, 6), title: str | None = None, correct_multiple_testing: bool = True ) -> pandas.DataFrame | tuple[pandas.DataFrame, Any] ```
Compute z-score enrichment of properties per group (e.g. cluster labels). For each group, z-scores are computed against a **leave-group-out** population (all samples *except* the current group). When *correct_multiple_testing* is True (default), Welch two-sample t-tests (group vs leave-one-out rest) are run per property and Benjamini-Hochberg FDR correction is applied across all (group, property) pairs; a ``significant`` column is added to the output DataFrame. **Args:** - **`group_by`**: Property/metadata key(s) holding group labels (e.g. ``"cluster"``). Can be a single string or a list of strings for composite grouping. - **`property_keys`**: Properties to analyse (all numeric if *None*). - **`filters`**: Optional metadata filters. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`plot`**: If True (default), show an interactive Plotly faceted bar chart (one row per group). If False, log a plain-text summary via the package logger instead (unless *return_fig* requests a figure). - **`return_fig`**: If True, return a tuple ``(df, fig)`` where ``fig`` is the Plotly figure. When *return_fig* is True, ``fig.show()`` is not called; use ``plot=True`` with ``return_fig=False`` for the default interactive display. - **`top_k`**: Max properties per group in the figure and in the text summary (``None`` = all). - **`figsize`**: Figure size ``(width, height)`` in inches for the Plotly layout. - **`title`**: Optional figure title. - **`correct_multiple_testing`** (`bool`): If True (default), run Welch t-tests per (group, property), apply Benjamini-Hochberg FDR, and add ``p_value`` / ``significant`` columns (alpha = 0.05). **Returns:** pd.DataFrame or tuple[pd.DataFrame, Any]: - If *return_fig* is False (default): pd.DataFrame with columns: [group_by columns], property, score, mean_group, mean_pop, p_value, abs_score, and optionally significant when *correct_multiple_testing* is True. - If *return_fig* is True: A tuple (enrichment_df, fig).
Method

compute_component_correlation

```python PhenoMeAnalysis.compute_component_correlation( self, method: Literal['pca', 'tsne', 'umap'] = 'pca', n_components: int = 2, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any] | None = None, exclude: dict[str, typing.Any] | None = None, top_k: int | None = None, normalize: bool = True, correlation_method: Literal['pearson', 'spearman', 'distance_correlation', 'mutual_info'] = 'pearson', plot: bool = True, return_fig: bool = False, figsize: tuple[int, int] = (10, 6) ) -> pandas.DataFrame | tuple[pandas.DataFrame, Any] ```
Correlate dim-reduction components with phenotypic properties. **Args:** - **`method`**: Dimensionality reduction method. - **`n_components`**: Number of components to compute. - **`source`**: ``'embeddings'``, ``'properties'``, or ``'combined'``. - **`property_keys`**: Property subset when *source* is ``'properties'`` or ``'combined'``. - **`filters`**: Optional metadata filters. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`top_k`**: Number of top correlations to return in summary and per facet in the plot. - **`normalize`**: Whether to normalize data before computing correlations (default: True). - Source data (embeddings/properties): Normalized before dimensionality reduction (L2 normalization for embeddings, StandardScaler for properties) - Properties: Normalized using StandardScaler before correlation computation - **`correlation_method`**: Correlation method to use. Options: - ``'pearson'``: Pearson correlation coefficient (default, linear relationships) - ``'spearman'``: Spearman rank correlation (monotonic relationships, robust to outliers) - ``'distance_correlation'``: Distance correlation (detects non-linear relationships, requires ``dcor`` library: ``pip install dcor``) - ``'mutual_info'``: MI-derived correlation coefficient (detects any dependency, mapped to [0, 1] via the Gaussian bivariate transform, **not** standard NMI) - **`plot`**: If True (default), show an interactive Plotly faceted bar chart (one row per component). If False, log a plain-text summary via the package logger instead (unless *return_fig* requests a figure). - **`return_fig`**: If True, return a tuple of ``(correlation_df, figure)``. When ``return_fig`` is True, ``fig.show()`` is not called. - **`figsize`**: Figure size ``(width, height)`` in inches for the Plotly layout. **Returns:** pd.DataFrame | tuple[pd.DataFrame, Any]: - If ``return_fig`` is False (default): Correlation DataFrame with a ``property`` column and ``component_1``, ``component_2``, … columns. - If ``return_fig`` is True: Returns a tuple of ``(correlation_df, figure)``. **Raises:** - **`ValueError`**: If dimensionality reduction fails, no components are found, no numeric properties are available, or correlation computation fails. **Examples:** ```python Get correlations directly:: corr = pheno.compute_component_correlation(plot=False) Get correlations and save the figure:: corr, fig = pheno.compute_component_correlation(return_fig=True) fig.write_html("component_corr.html") ```
Method

compute_embedding_property_correlations

```python PhenoMeAnalysis.compute_embedding_property_correlations( self, property_keys: list[str] | None = None, normalize: bool = True, method: Literal['pearson', 'spearman', 'distance_correlation', 'mutual_info'] = 'pearson', n_jobs: int = 1 ) -> dict ```
Compute correlations between embedding dimensions and phenotypic scalar properties. This is the time-consuming step that computes raw correlations for each property. **Args:** - **`property_keys`**: Property names (all if *None*). - **`normalize`**: Whether to normalize data before computing correlations (default: True). - Embeddings: L2 normalization (unit norm) - Properties: StandardScaler normalization (zero mean, unit variance) - **`method`**: Correlation method to use. Options: - ``'pearson'``: Pearson correlation coefficient (default, linear relationships) - ``'spearman'``: Spearman rank correlation (monotonic relationships, robust to outliers) - ``'distance_correlation'``: Distance correlation (detects non-linear relationships, requires ``dcor`` library: ``pip install dcor``) - ``'mutual_info'``: MI-derived correlation coefficient (detects any dependency, mapped to [0, 1] via the Gaussian bivariate transform, **not** standard NMI) **Returns:** Dict mapping property names to correlation arrays (n_dims,)
Method

summarize_embedding_property_correlations

```python PhenoMeAnalysis.summarize_embedding_property_correlations( self, correlations: dict[str, numpy.ndarray], order_by: str = 'mean_abs', top_k: int | None = 20, plot: bool = True, return_fig: bool = False, figsize: tuple[int, int] = (10, 8), title: str = 'Property Correlations with Embeddings', embedding_shape: tuple[int, int] | None = None, n_properties: int | None = None, correlation_method: str = 'pearson' ) -> pandas.DataFrame | tuple[pandas.DataFrame, Any | None] ```
Summarize embedding-property correlations, optionally plot, and/or return a Plotly figure. Computes per-property ``mean_abs``, ``std`` (across dimensions), ``max_abs``, ``min_abs``, and ``mean``, orders rows by ``order_by``, and either shows a horizontal violin plot of the distribution of |r| across dimensions per property or logs a plain-text table when ``plot`` is *False*. **Args:** - **`correlations`**: Output from :meth:`compute_embedding_property_correlations` (dict mapping property names to correlation arrays). - **`order_by`**: Metric used to sort properties (descending): ``mean_abs`` | ``max_abs`` | ``mean`` | ``std``. - **`top_k`**: Number of top properties shown in the plot and listed in ``top_properties``; full sorted table is always in ``summary`` (*None* = all). - **`plot`**: If *True* (default), show a Plotly violin plot of |r| per dimension for the top ``top_k`` properties by ``order_by`` (*None* = all). Highest metric at the **top** of the y-axis. - **`return_fig`**: If *True*, return both the summary DataFrame and the Plotly figure. - **`figsize`**: Figure size in inches, converted to pixels for Plotly layout. - **`title`**: Chart title. - **`embedding_shape`**: Shape of embeddings (n_samples, n_dims) for metadata display. - **`n_properties`**: Number of properties processed for metadata display. - **`correlation_method`**: Method used for correlation computation for metadata display. **Returns:** Sorted DataFrame with ``property``, ``mean_abs``, ``std``, ``max_abs``, ``min_abs``, and ``mean``. If ``return_fig`` is *True*, returns ``(summary, figure)``.
Method

compute_multivariate_interpretability

```python PhenoMeAnalysis.compute_multivariate_interpretability( self, method: Literal['pca', 'tsne', 'umap'] = 'tsne', component: int = 1, model_type: Literal['lasso', 'random_forest'] = 'lasso', property_keys: list[str] | None = None, filters: dict[str, typing.Any] | None = None, exclude: dict[str, typing.Any] | None = None, normalize: bool = True, cv: int = 5, stratify_by: str | None = None, rf_n_estimators: int = 100, seed: int | None = None, plot: bool = True, return_fig: bool = False, return_meta: bool = False, top_k: int = 10, figsize: tuple[int, int] = (10, 8) ) -> pandas.DataFrame | tuple[pandas.DataFrame, dict[str, Any]] | tuple[pandas.DataFrame, Any] | tuple[pandas.DataFrame, dict[str, Any], Any] ```
Explain a dimensionality reduction component using LASSO or Random Forest. Calculates which phenotypic properties (features) best explain the variability seen in a deep learning embedding dimension (the target, usually t-SNE 1 or 2). Workflow: dimensionality reduction is run on the full filtered embedding set first; regression then relates classical properties to the resulting axis coordinates. For transductive methods (t-SNE, UMAP), axis coordinates depend on all samples, so the returned ``r2`` (LASSO) describes descriptive on-axis alignment on the same dataset—not independent generalization to new plates or refit embeddings. Prefer ``drivers`` for hypothesis generation; interpret ``r2`` as an effect-size-style summary unless DR and evaluation are nested with proper holdouts. **Args:** - **`method`**: Dimensionality reduction method ('pca', 'tsne', or 'umap'). - **`component`**: Which component to explain (1, 2, ...). - **`model_type`**: The regression model to use ('lasso' or 'random_forest'). 'lasso' uses L1 regularization for linear, sparse explanations. 'random_forest' captures non-linear relationships. - **`property_keys`**: Subset of properties to use as features. - **`filters`**: Optional metadata filters. - **`exclude`**: Optional metadata exclusions. - **`normalize`**: Whether to normalize embeddings before dimensionality reduction (default: True). Property features are scaled fold-wise inside the LASSO pipeline (not globally) to avoid cross-validation leakage. - **`cv`**: Number of cross-validation folds (only for 'lasso'). - **`stratify_by`**: Optional metadata key (e.g. ``"Treatment"``, ``"Batch"``) to stratify LASSO folds by class balance. Requires at least ``cv`` samples per class; otherwise falls back to unstratified KFold. Balances folds for ``lambda`` selection but does not correct global t-SNE/UMAP target leakage. - **`rf_n_estimators`**: Number of trees (only for 'random_forest'). - **`seed`**: Random seed for reproducibility. If None, uses the pipeline's ``seed`` when set. - **`plot`**: If True (default), show an interactive Plotly bar chart of top drivers. If False, log a plain-text summary via the package logger instead. - **`return_fig`**: If True, include the Plotly figure in the return value (see Returns). When ``return_fig`` is True, ``fig.show()`` is not called; use ``plot=True`` with ``return_fig=False`` for interactive display. - **`return_meta`**: If True, include a run-parameters dict in the return value. - **`top_k`**: Number of top drivers to show in the plot or text summary. - **`figsize`**: Figure size ``(width, height)`` passed through to Plotly layout; each value is multiplied by 100 to set width and height in layout pixels. **Returns:** pd.DataFrame or tuple, depending on *return_meta* and *return_fig*: - Default: ``drivers_df`` with columns ``feature``, ``weight``. - ``return_meta=True``: ``(drivers_df, meta)``; *meta* includes r2 (descriptive on-axis fit for globally fit DR targets), n_samples, n_features, method, model_type, target_component. - ``return_fig=True``: ``(drivers_df, fig)`` or ``(drivers_df, meta, fig)`` when *return_meta* is also True.
### Reporting
Method

generate_report

```python PhenoMe.generate_report( self, output_path: str = 'pheno_report.html', title: str = 'PhenoMe Analysis Report', config: typing.Any | None = None, **overrides: Any ) -> str ```
Generate a comprehensive standalone HTML report from phenotyping results. Uses ReportConfig as the primary source of options. Pass config= for full control, or use **overrides to tweak individual settings (e.g. include_plots=False). **Args:** - **`output_path`**: Path to save the HTML file. - **`title`**: Report title displayed at the top. - **`config`**: Optional ReportConfig for defaults. If None, uses ReportConfig(). **overrides: Any ReportConfig field to override (e.g. include_plots=False, outlier_threshold=2.5, n_clusters=10). **Returns:** Path to the generated HTML file.
### Other
Method

get_run_settings

```python PhenoMe.get_run_settings( self ) -> dict ```
Return the silent run log as a JSON-serializable dict. Includes init parameters, environment, recorded method steps, processing_params, and the processing pipeline when available. Does not print or write files.
Method

image_preview_png_bytes

```python _ImageDisplayMixin.image_preview_png_bytes( self, idx: int, distance_results: pandas.DataFrame | None = None, channels: int | list | None = None, title_fields: list[str] | None = None, show_extra_info: bool = False, apply_transforms: bool = True, downsample: int | None = 720 ) -> tuple ```
Rasterize the same view as ``plot_image_by_index`` to PNG bytes. Used by the interactive explorer with ``ipywidgets.Image`` because matplotlib ``display()`` / ``plt.show()`` from Plotly click callbacks does not reliably target a nested ``Output`` widget (output goes to the cell or nowhere). When ``show_extra_info`` is True, also emits the same details as ``plot_image_by_index`` via logging and returns their text for UI display. **Returns:** ``(png_bytes, details_text_or_none)`` — ``details_text_or_none`` is the formatted extra-info block when ``show_extra_info`` is True and details exist.
Method

load_embeddings

```python PhenoMe.load_embeddings( self, path: str | None = None, lazy_checkpoint: bool = True ) -> None ```
Load an embeddings checkpoint into the pipeline. Same path/file_df rebase flow as [load_results](/PhenoMe/advanced/api/pipeline/#api-phenome-load_results), but requires the file to contain embeddings. Sets ``self._db`` for lazy ``get_embeddings()`` access when ``lazy_checkpoint=True``. Typical two-file workflow:: pheno.find_files(data_dir) pheno.load_embeddings("run_embeddings.h5") pheno.load_properties("run_properties.h5") **Args:** - **`path`**: Path to .h5 or .hdf5 embeddings checkpoint. - **`lazy_checkpoint`**: If True (default), keep the checkpoint file open. If False, load all data into RAM and close the file. **Raises:** - **`FileNotFoundError`**: If file does not exist. - **`ValueError`**: If file format is not recognised as HDF5, or the checkpoint has no embeddings. - **`RuntimeError`**: If file_df is not available (call find_files or set_file_df first). - **`ConcurrentCheckpointAccessError`**: If the checkpoint is already open in another notebook or process (only one instance can access it at a time).
Method

load_properties

```python PhenoMe.load_properties( self, path: str | None = None, lazy_checkpoint: bool = True ) -> None ```
Load a properties checkpoint into the pipeline. When results already contain images (e.g. after [load_embeddings](/PhenoMe/advanced/api/pipeline/#api-phenome-load_embeddings) or ``process_images``), properties are aligned by metadata key then path and overlaid onto the existing rows without replacing an embeddings ``self._db`` from a different file. When no results are loaded yet, performs a full checkpoint setup like [load_results](/PhenoMe/advanced/api/pipeline/#api-phenome-load_results). For a properties-only file, the HDF5 handle is closed after load (properties are eager); ``self._db`` is kept only if that file also has embeddings. **Args:** - **`path`**: Path to .h5 or .hdf5 properties checkpoint. - **`lazy_checkpoint`**: Used on cold start only. If True (default), keep the file open when it also stores embeddings. Ignored when overlaying onto existing results. **Raises:** - **`FileNotFoundError`**: If file does not exist. - **`ValueError`**: If file format is not recognised as HDF5, or the checkpoint has no property content, or overlay matches no rows. - **`RuntimeError`**: On cold start, if file_df is not available (call find_files or set_file_df first). - **`ConcurrentCheckpointAccessError`**: If the checkpoint is already open in another notebook or process (only one instance can access it at a time).
Method

plot_centroids

```python _DRPlotsMixin.plot_centroids( self, group_by: str | list[str], method: Literal['pca', 'tsne', 'umap'] = 'pca', n_components: int = 2, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, show_points: bool = True, show_centroids: bool = True, figsize: tuple[int, int] = (10, 8), return_fig: bool = False, normalize: bool = True, **dr_kwargs: Any ) -> Any ```
Plot centroids of groups in reduced embedding space.
Method

plot_image_by_index

```python _ImageDisplayMixin.plot_image_by_index( self, idx: int, distance_results: pandas.DataFrame | None = None, channels: int | list | None = None, figsize: tuple = (6, 6), title_fields: list[str] | None = None, show_extra_info: bool = True, apply_transforms: bool = False, downsample: int | None = 720, ax: Any = None, return_fig: bool = False, show_mask_overlay: bool = False ) -> Any ```
Plot a specific image by its index. **Args:** - **`idx`**: Image index in results - **`distance_results`**: Optional distance computation results DataFrame - **`channels`**: specific channels to plot - **`figsize`**: Figure size - **`title_fields`**: Optional list of field names to display in title - **`show_extra_info`**: If True, prints detailed information after plotting - **`apply_transforms`**: If True, apply pipeline image_transforms - **`downsample`**: If not None, approximate desired size (in pixels) for the longest image edge when downsampling. The final image size may differ slightly due to integer stepping. If None, no downsampling. - **`ax`**: Optional matplotlib axes to plot on. If provided, a new figure is not created. - **`return_fig`**: If True, return the displayed image as a NumPy array and do not open a matplotlib figure. Mask overlay, when requested, is composited onto that array. - **`show_mask_overlay`**: If True, draws the segmentation mask as a semi-transparent yellow overlay with a crisp contour. Requires masks to have been discovered via ``mask_dir`` in ``find_files``. Useful for verifying that masks load correctly and spatially align with their images. **Returns:** The image as a NumPy array when ``return_fig`` is True; otherwise ``None``.
Method

plot_pca

```python _DRPlotsMixin.plot_pca( self, n_components: int = 2, color_by: str | None = None, figsize: tuple[int, int] = (10, 8), source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, hover_features: list[str] | None = None, return_fig: bool = False, render_mode: Literal['auto', 'svg', 'webgl'] = 'webgl', normalize: bool = True, sample_size: int | None = None, **dr_kwargs: Any ) -> Any ```
Plot PCA of embeddings, properties, or combined features (Plotly, WebGL by default).
Method

plot_tsne

```python _DRPlotsMixin.plot_tsne( self, n_components: int = 2, perplexity: float = 30.0, color_by: str | None = None, figsize: tuple[int, int] = (10, 8), source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, hover_features: list[str] | None = None, return_fig: bool = False, render_mode: Literal['auto', 'svg', 'webgl'] = 'webgl', normalize: bool = True, sample_size: int | None = None, **dr_kwargs: Any ) -> Any ```
Plot t-SNE of embeddings, properties, or combined features (Plotly, WebGL by default).
Method

plot_umap

```python _DRPlotsMixin.plot_umap( self, n_components: int = 2, n_neighbors: int = 15, min_dist: float = 0.1, color_by: str | None = None, figsize: tuple[int, int] = (10, 8), source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, hover_features: list[str] | None = None, return_fig: bool = False, render_mode: Literal['auto', 'svg', 'webgl'] = 'webgl', normalize: bool = True, sample_size: int | None = None, **dr_kwargs: Any ) -> Any ```
Plot UMAP of embeddings, properties, or combined features (Plotly, WebGL by default).
Method

print_distance_summary

```python _DistancePlotsMixin.print_distance_summary( self, distance_results: pandas.DataFrame, group_by: str | None = None, dist_range: tuple = (0, 100) ) -> None ```
Print distance summary statistics without plotting. Safe to use when enable_plots=False. **Args:** - **`distance_results`**: DataFrame containing 'distance' column - **`group_by`**: Metadata key to group by - **`dist_range`**: Tuple of (min, max) distances to include
## Class `PhenoMeResults` Typed container for per-image phenotyping data (``PhenoMe.results``). Access fields via attributes: ``results.img_path``, ``results.metadata``, ``results.properties``, and ``results.embeddings``. **Embeddings lifecycle** ``embeddings`` tracks the eager copy held by the pipeline: * **Not yet computed** — ``embeddings is None``, ``pipeline._db is None`` * **Lazy-backed (HDF5)** — ``embeddings is None``, ``pipeline._db is not None`` (rows are fetched from disk on demand) * **Eagerly computed** — ``embeddings`` is an ``np.ndarray`` of shape ``(N, D)`` The former ``[]``-sentinel that mixed "uninitialised" with "lazy" with "being filled during extraction" has been removed. In-progress embeddings during extraction live in ``pipeline._emb_buffer`` (a plain ``List[np.ndarray]``) and never touch this container until they are finalised. **Parameters:** - **`img_path`** (`list of str or list of list[str]`): Per-image file path(s). Single-channel images use a plain ``str``; multi-channel images use a ``List[str]`` with one path per channel. - **`metadata`** (`list of dict`): Per-image metadata dicts (e.g. ``{"drug": "DMSO", "time": "24h"}``). - **`properties`** (`list of dict`): Per-image computed scalar properties (e.g. ``{"intensity_mean": 0.42, "area": 1024}``). - **`embeddings`** (`np.ndarray or None`): Shape ``(N, D)`` float32 array, or ``None`` when not available / when embeddings are stored lazily in an HDF5 file. ### Accessors and inspection
Property

has_embeddings

Property on PhenoMeResults

True if embeddings are stored eagerly (not lazy or absent).
Property

embedding_dim

Property on PhenoMeResults

Embedding dimensionality, or 0 if no embeddings are stored.
### Other
Method

clear

```python PhenoMeResults.clear( self ) -> None ```
Reset all fields to empty state.
Property

has_properties

Property on PhenoMeResults

True if at least one image has computed properties.
Method

image_name

```python PhenoMeResults.image_name( self, idx: 'int' ) -> str ```
Return the basename of the primary path for image *idx*.
Property

metadata_keys

Property on PhenoMeResults

Sorted unique metadata keys across all images (case-preserving).
Method

metadata_value

```python PhenoMeResults.metadata_value( self, idx: 'int', key: 'str' ) -> Any ```
Case-insensitive metadata lookup for image *idx*.
Property

n_images

Property on PhenoMeResults

Number of images stored.
Method

primary_path

```python PhenoMeResults.primary_path( self, idx: 'int' ) -> str ```
Return the primary (or only) file path for image *idx*.
Property

property_keys

Property on PhenoMeResults

Sorted list of property names from the first non-empty properties dict.
Method

property_value

```python PhenoMeResults.property_value( self, idx: 'int', key: 'str' ) -> float | None ```
Return a scalar property for image *idx*, or None if absent.
Method

rebase_paths

```python PhenoMeResults.rebase_paths( self, data_dir: 'str', old_data_dir: 'str | None' = None ) -> None ```
Update all image paths by replacing *old_data_dir* with *data_dir*. This is useful when moving a checkpoint and its dataset to a different machine or directory. **Parameters:** - **`data_dir`** (`str`): The new base directory where the images are located. - **`old_data_dir`** (`str, optional`): The old base directory to be replaced. If not provided, it is automatically detected by finding the common prefix of all stored paths. Special value "relative" indicates that stored paths are already relative and just need to be joined with *data_dir*.
# MCP for PhenoMe > Optional Model Context Protocol server so assistants can search docs, fetch recipes, inspect a folder, and format exported run settings. The MCP server is **optional**. It does not change PhenoMe defaults and does not run embedding extraction. Install it only if you want Cursor, Claude Desktop, or similar tools to call PhenoMe helpers. ## Install ```bash pip install "phenome[mcp]" ``` From a clone (editable): ```bash pip install -e ".[mcp]" ``` Then run: ```bash phenome-mcp ``` ## Cursor example In MCP settings, point stdio at the `phenome-mcp` executable from the same environment where PhenoMe is installed: ```json { "mcpServers": { "phenome": { "command": "phenome-mcp" } } } ``` ## Tools - **search_docs** — keyword search over local docs (if you have a clone), bundled recipes, or the published [llms-small.txt](https://AAitorG.github.io/PhenoMe/llms-small.txt) - **get_recipe** — named snippets (`first-analysis`, `export-settings`, `path-metadata`, `property-presets`, `batch-correction`, `checkpoint`) - **list_public_api** — top-level imports and `PhenoMe` methods - **inspect_dataset** — `find_files` + `inspect_data` on a folder (no GPU / no embeddings) - **format_run_settings** — pretty-print a JSON file from `export_experiment_config` Assistants should still only pass extra PhenoMe parameters (seed, `include_run_settings`, log level, plot kwargs) when the user asked. See [Using with LLMs](/PhenoMe/guides/using-with-llms/). # API & data formats > Hub for auto-generated API docs, the function-location index, and the HDF5 checkpoint layout. ## Available tools For a scannable capability map (formats, embeddings, DR, clustering, properties, distances, and more), see [Available tools](/PhenoMe/available-tools/). ## API reference The pages under [`advanced/api/`](/PhenoMe/advanced/api/pipeline/) are **auto-generated** from Python docstrings on every docs build (`docs/scripts/generate_api_docs.py`). **Edit docstrings in `phenome/` and rebuild** — there is no hand-maintained copy. | Document | Description | |----------|-------------| | [PhenoMe pipeline](/PhenoMe/advanced/api/pipeline/) | Main orchestrator class for the complete workflow. | | [Vision model wrappers](/PhenoMe/advanced/api/model-wrapper/) | `ModelWrapper` (base), `DinoV2ModelWrapper`. | | [Visualization](/PhenoMe/advanced/api/visualization/) | PCA, t-SNE, UMAP, centroid plots, distance distributions, correlations. | | [Distance computation](/PhenoMe/advanced/api/distances/) | Computing distances to reference groups. | | [Property computation](/PhenoMe/advanced/api/properties/) | Custom property extraction from images and masks. | | [Report generation](/PhenoMe/advanced/api/report/) | `ReportConfig` and standalone HTML report generation. | | [Interactive explorer](/PhenoMe/advanced/api/interactive/) | Widget-based dashboard for exploring embeddings in Jupyter. | | [Utilities](/PhenoMe/advanced/api/utilities/) | Device helpers, transforms, image I/O, checkpoints, file discovery. | | [Metadata classes](/PhenoMe/advanced/api/metadata/) | `MetadataBase`, `DefaultMetadata`, `PathTemplateMetadata`, `DataFrameMetadata`. | | [Plugins](/PhenoMe/advanced/api/plugins/) | Property and metadata registries, report sections. | ## Function location guide The [Function location guide](/PhenoMe/function-location-guide/) is **auto-generated** in the same build step from live `phenome` exports (`docs/scripts/api_docs/function_index.py`). It lists top-level imports, `PhenoMe` methods, and submodule exports with tier badges (public / advanced / internal) and deep links into the API pages above. ## Getting started For a walkthrough of the complete pipeline, installation, and a first analysis, see the [Getting started guide](/PhenoMe/getting-started/). For an overview of the core concepts, see [Core concepts](/PhenoMe/concepts/). ## Extending the pipeline - [Extending PhenoMe](/PhenoMe/guides/extending/) - overview of extension points. - [Experiment details](/PhenoMe/guides/experiment-details/) - metadata extraction. - [Select properties](/PhenoMe/guides/select-properties/) - property presets, requirement types, factories. - [Plugins](/PhenoMe/guides/plugins/) - plugin registry, custom presets. ## Technical reference | Document | Description | |----------|-------------| | [HDF5 database protocol](/PhenoMe/advanced/database_protocol/) | Internal checkpoint and result file structure. | # Distance computation > Reference-group distances (embeddings, properties, combined).

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.mixins.distances`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/mixins/distances.py) (class `PhenoMeDistances`). ::: **See also:** [Visualization](/PhenoMe/advanced/api/visualization/) · [Pipeline](/PhenoMe/advanced/api/pipeline/) Provides distance computation methods for PhenoMe. Expects the following attributes from the pipeline: - self.results: PhenoMeResults containing processed results - self.device: torch.device for GPU computation ## Reference distances
Method

compute_reference_distances

```python PhenoMeDistances.compute_reference_distances( self, reference_filters: dict[str, typing.Any], filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', mode: Literal['centroid', 'all_to_all'] = 'centroid', distance_type: Literal['euclidean', 'cosine'] = 'euclidean', property_keys: list[str] | None = None, group_by: str | list[str] | None = None, dist_range: tuple[float, float] = (0.0, 100.0), figsize: tuple[int, int] = (10, 6), plot: bool = True, return_fig: bool = False, return_meta: bool = False, include_metadata: bool = False, points: Optional[Literal['all', 'outliers', False]] = None ) -> pandas.DataFrame | tuple[pandas.DataFrame, dict[str, Any]] | tuple[pandas.DataFrame, Any] | tuple[pandas.DataFrame, dict[str, Any], Any] ```
Compute distances from all images to reference group. **Args:** - **`reference_filters`**: Dict mapping metadata keys to values for reference group. Example: {'drug': 'Control', 'time': '60_min'} If a value is a list, matches any value in the list. - **`filters`**: Optional generic filters applied before computing distances. Reference selection still uses reference_filters. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`source`**: 'embeddings' for model embeddings, 'properties' for scalar properties, or 'combined' for the normalized concatenation of both. - **`mode`**: 'centroid' computes distance to mean embedding of reference, 'all_to_all' computes min distance to any reference image. When *source* is ``'embeddings'`` (L2-normalized by default), the centroid is the **arithmetic mean** of reference vectors (not necessarily unit length). Euclidean distance to that centroid differs from angular distance to the mean direction; use ``distance_type='cosine'`` for angular comparisons on normalized embeddings. - **`distance_type`**: 'euclidean' (default) or 'cosine'. Cosine distance is ``1 - cosine_similarity``, range [0, 2]. Zero-norm vectors produce a distance of 1. - **`property_keys`**: Optional subset of property names when *source* is ``'properties'`` or ``'combined'``. - **`group_by`**: If set, optionally visualize or print grouped statistics after computing distances. Pass ``None`` to skip post-processing. - **`dist_range`**: When using *group_by*, keep distances in ``[min, max]`` for display and summary. - **`figsize`**: Figure size in inches (width, height) when a plot is built. - **`plot`**: If True and *group_by* is set, show or return a violin plot only (no per-group text summary to the logger). If False, log per-group summary statistics where applicable (e.g. text-only mode, or with *return_fig*). - **`return_fig`**: If True and *group_by* is set, include the Plotly figure in the return value (see Returns). When ``return_fig`` is True, ``fig.show()`` is not called. - **`return_meta`**: If True, include a run-parameters dict in the return value. - **`include_metadata`**: If True, add metadata columns to the returned DataFrame. - **`points`**: Violin plot point overlay: ``'all'``, ``'outliers'``, or ``False``; ``None`` auto-selects by data size. **Returns:** pd.DataFrame or tuple, depending on *return_meta* and *return_fig*: - Default: ``distance_df`` (Tier A) with ``image_index``, ``image_path``, ``image_name``, ``distance``, ``is_reference``. - ``return_meta=True``: ``(distance_df, meta)``; *meta* holds reference_filters, filters, mode, source, distance_type. - ``return_fig=True`` (with *group_by*): ``(distance_df, fig)`` or ``(distance_df, meta, fig)`` when *return_meta* is also True. **Example:** ```python >>> dist_df = pipeline.compute_reference_distances( ... reference_filters={'condition': 'Control'}, ... source='embeddings', ... mode='centroid', ... distance_type='euclidean', ... ) >>> distances = dist_df['distance'] ```
# Interactive explorer > Jupyter widget explorer for embeddings.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.mixins.interactive`](https://github.com/AAitorG/PhenoMe/tree/main/phenome/mixins/interactive). ::: **See also:** [Visualization](/PhenoMe/advanced/api/visualization/) · [Pipeline](/PhenoMe/advanced/api/pipeline/) ## Factory ### `create_interactive_explorer` ```python create_interactive_explorer( pheno_me: '_InteractiveExplorerProtocol', filters: 'dict[str, Any] | None' = None, exclude: 'dict[str, Any] | None' = None, hover_features: 'list[str] | None' = None ) -> PhenoMeInteractive ``` Launch an interactive explorer for phenotyping results in Jupyter. Creates a :class:`PhenoMeInteractive` instance and displays it. Use in Jupyter notebooks to explore embeddings via PCA/t-SNE/UMAP with instant color switching, highlight mode, box/lasso multi-selection, and click-to-inspect image viewing. **Args:** - **`pheno_me`**: Processed PhenoMe instance with embeddings and optional properties. Must have run ``process_images()`` first. - **`filters`**: Optional metadata filters to restrict which images are shown. Dict mapping metadata keys to allowed values or lists of values. Example: ``{'condition': 'Control', 'time': ['24h', '48h']}``. - **`exclude`**: Optional metadata exclusions (same structure as filters). - **`hover_features`**: Optional list of metadata or property keys to show in hover tooltips. If ``None``, uses metadata keys from the pipeline. For Google Colab, use ``create_colab_interactive_explorer``. **Returns:** - **`PhenoMeInteractive`**: The explorer instance. Call ``.show()`` again to re-display. **Example:** ```python >>> from phenome import PhenoMe, load_dinov2_model >>> wrapper = load_dinov2_model() >>> pheno = PhenoMe(seed=42) >>> pheno.find_files("data/") >>> pheno.process_images(wrapper) >>> explorer = pheno.create_interactive_explorer( ... filters={'condition': 'Treatment'}, ... hover_features=['drug', 'area', 'eccentricity'], ... ) ``` ## Colab ### `create_colab_interactive_explorer` ```python create_colab_interactive_explorer( pheno_me: '_InteractiveExplorerProtocol', filters: 'dict[str, Any] | None' = None, exclude: 'dict[str, Any] | None' = None ) -> ColabInteractiveExplorer ``` Launch the interactive explorer built only for Google Colab. Shows Embedding and Appearance controls, an SVG scatter of the embedding, and the image for a clicked point. Highlight, box/lasso selection, and filter editors are not included. ``filters`` and ``exclude`` still restrict which images are reduced when Compute is pressed. Local Jupyter notebooks should use :func:`create_interactive_explorer`. That explorer is not adapted for Colab. Widget updates run inside the button and click callbacks. Colab does not show updates made from a background thread. **Args:** - **`pheno_me`**: Processed PhenoMe instance with embeddings. Must have run ``process_images()`` first. - **`filters`**: Optional metadata filters. Dict mapping metadata keys to allowed values or lists of values. - **`exclude`**: Optional metadata exclusions (same structure as filters). **Returns:** The explorer. Call ``.show()`` again to re-display it. **Example:** ```python >>> explorer = pheno.create_colab_interactive_explorer() ``` ## Class `PhenoMeInteractive` High-performance interactive explorer for PhenoMe results. Optimized for large datasets using WebGL and efficient data handling. Provides a unified interface for 2D/3D exploration with: - Instant colour switching (no recomputation). - Highlight mode (shows all points, emphasises a subset). - Multi-select categorical highlight and live stats bar. - Click-to-inspect image viewer. - 2D box/lasso selection with CSV export of selected rows in the Selection section (Plotly modebar: pan, zoom, box/lasso, PNG). Key design: - **Color changes** are instant (no recomputation, only visual update). - **Filter** and **Exclude** (separate collapsible sections under Embedding) restrict data before recomputation; same field + search + multi-select pattern as Highlight. - **Method/Source/Dim** or metadata filter changes trigger dimensionality reduction (expensive). - **Highlight mode** shows ALL data points but visually emphasises a matching subset (translucent halo + dimmed non-matching points) instead of hiding the rest. This lets the user "find" a group in context. - **Multi-value highlight**: categorical fields use filter + multi-select (OR). - **Live stats bar** (total / highlighted / selected / lasso-box). - **Selected point**: clicking a point shows a black ring (hollow marker) for inspection. - **Multi-selection**: box / lasso on the 2D plot stores a persistent set of image indices; the side panel shows a thumbnail grid (single-click traceback + image id, double-click full view with back to grid). ### Interactive exploration
Method

set_filters

```python PhenoMeInteractive.set_filters( self, filters: 'dict | None' = None, exclude: 'dict | None' = None ) -> None ```
Programmatically update the metadata filters and exclusions and recompute. Since filters/exclude change the data subset, this triggers a full recomputation.
Method

close

```python PhenoMeInteractive.close( self ) -> None ```
Cancel pending debounce timers (call before discarding the explorer).
Method

show

```python PhenoMeInteractive.show( self ) -> None ```
Display the interactive dashboard. Only one widget tree stays visible. Jupyter clears the cell output before the new dashboard is displayed.
# Metadata classes > MetadataBase, DefaultMetadata, PathTemplateMetadata, DataFrameMetadata.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.metadata`](https://github.com/AAitorG/PhenoMe/tree/main/phenome/metadata) (multiple classes). ::: **See also:** [Pipeline](/PhenoMe/advanced/api/pipeline/) @section Metadata Metadata handling for extensible and configurable file discovery. This package provides an object-oriented API for extracting and managing metadata from file paths, DataFrames, and custom sources. Metadata is extracted alongside images during file discovery and used to organize results, group analysis, and annotate visualizations. **Metadata extractors (classes):** - `MetadataBase`: Abstract base class for custom metadata extractors. - `DefaultMetadata`: Minimal extractor (file path, filename, auto-generated ID). - `PathTemplateMetadata`: Extract metadata by matching file paths against regex patterns. - `DataFrameMetadata`: Look up metadata from a DataFrame indexed by file path. **Quick start:** ```python from phenome import PhenoMe, PathTemplateMetadata # Extract batch and sample ID from paths like: .../batch_1/sample_42.tif # Use (field) placeholders — not raw regex named groups. metadata_fn = PathTemplateMetadata(template=r".../batch_(batch)/sample_(sample_id).*") pm = PhenoMe() df = pm.find_files("images/", metadata_fn=metadata_fn) print(df.columns) # includes file_path, batch, sample_id, ... ``` **Functional helpers:** For functional metadata extraction, see `phenome.utils.metadata`: - `default_metadata_from_path`: Simple functional wrapper. - `get_metadata_from_path`: Advanced functional wrapper. - `make_dataframe_metadata_fn`: Create extractor from DataFrame. **See Also:** For factory functions and presets, see `phenome.utils.property_factories`. For metadata utilities in math operations, see `phenome.core.results_metadata`. ## `MetadataBase` Base class for metadata extraction with configurable columns and ID handling. Subclasses implement `_extract` to produce raw metadata; the base class ensures a unique ``id`` is always present and provides stable key generation for checkpoint matching. **Parameters:** - **`filename_columns`** (`str or list of str`): Column(s) for image filenames. List for multi-channel. - **`unique_id_column`** (`str`): Column name for unique sample ID (default ``"id"``). - **`mask_filename_column`** (`str, optional`): Column name for mask filename when metadata has explicit mask. - **`mask_dir`** (`str, optional`): Root directory for mask resolution. - **`data_dir`** (`str, optional`): Base directory for relative path ID generation. ### Construction
Method

__init__

```python MetadataBase( filename_columns: 'str | list[str]' = 'filename', unique_id_column: 'str' = 'id', mask_filename_column: 'str | None' = None, mask_dir: 'str | None' = None, data_dir: 'str | None' = None ) ```
### Identifiers
Method

get_id

```python MetadataBase.get_id( self, meta: 'dict[str, Any]' ) -> str ```
Return the unique ID from metadata. **Raises:** - **`KeyError`**: If the unique ID column is missing from meta.
Method

ensure_id

```python MetadataBase.ensure_id( self, meta: 'dict[str, Any]', paths: 'str | list[str]', data_dir: 'str | None' = None ) -> str ```
Inject or return unique ID in meta. Auto-generate if not present. **Parameters:** - **`meta`** (`dict`): Metadata dict (modified in place). - **`paths`** (`str or list of str`): File path(s) for this sample. - **`data_dir`** (`str, optional`): Base directory for ID generation. Uses instance data_dir if None. **Returns:** - **`str`**: The unique ID (existing or newly generated).
Method

to_stable_key

```python MetadataBase.to_stable_key( self, meta: 'dict[str, Any]' ) -> str ```
Build stable key for checkpoint matching. Uses the unique ID when present and non-path-like; otherwise delegates to metadata_to_stable_key for a JSON-based stable key.
### Extraction
Method

metadata_fn

```python MetadataBase.metadata_fn( self, path: 'str', data_dir: 'str | None' = None ) -> dict[str, Any] ```
Extract metadata for a path. Guarantees 'id' is present. **Parameters:** - **`path`** (`str`): File path. - **`data_dir`** (`str, optional`): Base directory for ID generation. **Returns:** - **`dict`**: Metadata with file_path, metadata keys, and guaranteed id.
### Mask resolution
Method

get_mask_path

```python MetadataBase.get_mask_path( self, meta: 'dict[str, Any]' ) -> str | None ```
Resolve mask path from metadata when mask_filename_column and mask_dir are set. **Returns:** - **`str or None`**: Full path to mask file, or None if not resolvable.
Property

mask_dir

Property on MetadataBase

Root directory for mask files.
Property

mask_filename_column

Property on MetadataBase

Column containing mask filename when using explicit mask lookup.
### Grouping
Property

group_by

Property on MetadataBase

Column used for multi-channel grouping in find_files.
## `DefaultMetadata` Minimal metadata extractor: file_path and filename from path. Single filename column. Auto-generates ID from path when not provided. ## `PathTemplateMetadata` Metadata extractor from path template with capture groups. Uses parentheses for capture groups, e.g. ``.../(drug)/(time)/(crop_name).*``. group_by is the last capture group that is not ``channel_index``. ### Construction
Method

__init__

```python PathTemplateMetadata( template: 'str', filename_columns: 'str | list[str]' = 'filename', unique_id_column: 'str' = 'id', mask_filename_column: 'str | None' = None, mask_dir: 'str | None' = None, data_dir: 'str | None' = None ) ```
### Grouping
Property

group_by

Property on PathTemplateMetadata

Return a sample-level capture group, never ``channel_index``.
## `DataFrameMetadata` Metadata lookup from DataFrame. Supports single and multi-channel modes. ### Construction
Method

__init__

```python DataFrameMetadata( metadata_df: 'pd.DataFrame', filename_columns: 'str | list[str]' = 'filename', unique_id_column: 'str' = 'id', mask_filename_column: 'str | None' = None, mask_dir: 'str | None' = None, data_dir: 'str | None' = None ) ```
### Extraction
Method

metadata_fn

```python DataFrameMetadata.metadata_fn( self, path: 'str', data_dir: 'str | None' = None ) -> dict[str, Any] ```
Extract metadata with data_dir-aware partial path lookup.
# Vision model wrappers > ModelWrapper, DinoV2ModelWrapper, load_dinov2_model.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.utils.model_wrapper`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/utils/model_wrapper.py) (module). ::: **See also:** [Pipeline](/PhenoMe/advanced/api/pipeline/) Model wrappers for vision model embedding extraction. ## Model wrappers ### `load_dinov2_model` ```python load_dinov2_model( model_name: str = 'dinov2_vitb14_reg', device: torch.device | None = None ) -> DinoV2ModelWrapper ``` Load a DINOv2 model and return a DinoV2ModelWrapper ready for processing. Convenience helper for DINOv2 users. Uses get_default_device() when device is not specified. Returns the heritage DinoV2ModelWrapper, which subclasses ModelWrapper and is fully compatible with the pipeline. **Args:** - **`model_name`**: DINOv2 model identifier for torch.hub. Common: 'dinov2_vitb14_reg', 'dinov2_vitl14_reg', 'dinov2_vitg14_reg' - **`device`**: Optional torch.device. If None, uses get_default_device(). **Returns:** DinoV2ModelWrapper. Use with process_images(). The underlying ``torch.nn.Module`` is ``wrapper.model``. **Example:** ```python >>> wrapper = load_dinov2_model() >>> pheno.find_files("path/to/images") >>> pheno.process_images(wrapper) ``` ### `class ModelWrapper` Base wrapper for vision model embedding extraction. While PhenoMe is primarily built on PyTorch, this interface is designed to be framework-agnostic. Users can wrap models from any framework (TensorFlow, Keras, ONNX, etc.) by implementing a custom `ModelWrapper` subclass. **Contract for custom wrappers:** 1. The `_get_embeddings(tensor)` method must be implemented. 2. It receives a `torch.Tensor` (B, C, H, W) as input. 3. It MUST return a `torch.Tensor` (B, D) as output. **Args:** - **`model`**: Vision model (PyTorch module, or any other framework model). - **`device`**: Optional torch.device. If None, uses get_default_device(). **Example (Keras):** ```python >>> import tensorflow as tf >>> import numpy as np >>> class KerasWrapper(ModelWrapper): ... def _get_embeddings(self, tensor): ... x = np.transpose(tensor.cpu().numpy(), (0, 2, 3, 1)) ... out = self.model.predict(x, verbose=0) ... return torch.from_numpy(out).to(self.device) >>> model = tf.keras.applications.MobileNetV2(pooling='avg') >>> wrapper = KerasWrapper(model) >>> pheno.process_images(wrapper) ```
Method

__init__

```python ModelWrapper( model: Any, device: torch.device | None = None ) ```
Method

extract_embeddings

```python ModelWrapper.extract_embeddings( self, tensor: torch.Tensor ) -> Tensor ```
Extract per-image embeddings. Delegates to _get_embeddings (heritage class) and validates output shape (B, D). Even for non-PyTorch models, this method ensures the final output is a PyTorch tensor compatible with the rest of the pipeline. **Args:** - **`tensor`**: Input tensor (B, C, H, W) on device. **Returns:** Embedding tensor (B, D).
Method

sync_device

```python ModelWrapper.sync_device( self, device: torch.device | None = None ) -> None ```
Move the wrapped model to *device* and set eval mode when supported. Called from ``__init__`` and again by ``EmbeddingExtractor`` before each batch so the model stays on the pipeline device after user changes. **Args:** - **`device`**: Target device. If None, uses the wrapper's current ``self.device``. **Raises:** - **`RuntimeError`**: If ``model.to(device)`` or ``model.eval()`` fails.
### `class DinoV2ModelWrapper` Heritage wrapper for DINOv2 models from Meta AI. Handles dict output with 'x_norm_clstoken' (default key) and squeeze (B,1,D) -> (B,D). **Args:** - **`model`**: DINOv2 model (from torch.hub.load('facebookresearch/dinov2', ...)). - **`device`**: Optional torch.device. If None, uses get_default_device(). - **`embedding_output_key`**: Key for global embedding in forward_features output. Default 'x_norm_clstoken' (required for DINOv2). - **`model_name`**: Optional identifier stored for experiment export (for example ``dinov2_vitb14_reg``).
Method

__init__

```python DinoV2ModelWrapper( model: Any, device: torch.device | None = None, embedding_output_key: str = 'x_norm_clstoken', model_name: str | None = None ) ```
Method

extract_embeddings

```python DinoV2ModelWrapper.extract_embeddings( self, tensor: torch.Tensor ) -> Tensor ```
Extract per-image embeddings. Delegates to _get_embeddings (heritage class) and validates output shape (B, D). Even for non-PyTorch models, this method ensures the final output is a PyTorch tensor compatible with the rest of the pipeline. **Args:** - **`tensor`**: Input tensor (B, C, H, W) on device. **Returns:** Embedding tensor (B, D).
Method

sync_device

```python DinoV2ModelWrapper.sync_device( self, device: torch.device | None = None ) -> None ```
Move the wrapped model to *device* and set eval mode when supported. Called from ``__init__`` and again by ``EmbeddingExtractor`` before each batch so the model stays on the pipeline device after user changes. **Args:** - **`device`**: Target device. If None, uses the wrapper's current ``self.device``. **Raises:** - **`RuntimeError`**: If ``model.to(device)`` or ``model.eval()`` fails.
# Plugins and registry > register_property, metadata extractors, report sections.

Tier: Internal API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.plugins`](https://github.com/AAitorG/PhenoMe/tree/main/phenome/plugins) (module). ::: **See also:** [Plugins guide](/PhenoMe/guides/plugins/) · [Properties](/PhenoMe/advanced/api/properties/) @section Plugins Plugin registry and builtin extensions for phenome. This package provides extensibility hooks for registering custom properties, metadata extractors, and report sections. It also exports builtin property functions. **Property registry:** - `register_property`: Register a custom property function. - `get_property`: Retrieve a registered property function by name. - `list_properties`: List all registered property functions. **Metadata registry:** - `register_metadata_extractor`: Register a custom metadata extractor. - `get_metadata_extractor`: Retrieve a registered metadata extractor. **Report sections:** - `register_report_section`: Register a custom report section. - `get_report_sections`: Retrieve all registered report sections. **Builtin properties:** - `get_blob_properties`: Difference of Gaussians blob detection via scikit-image `blob_dog`. - `my_custom_max_intensity`: Example custom property function. **Example: Register custom property** ```python from phenome import PhenoMe from phenome.plugins import register_property, get_property def my_property(image, mask=None): '''Custom property: average brightness.''' if image is None: return {"avg_brightness": float("nan")} if mask is not None: image = image[mask] return {"avg_brightness": float(image.mean())} register_property("avg_brightness", my_property) # Later, use in pipeline: pm = PhenoMe() prop_fn = get_property("avg_brightness") if prop_fn is not None: pm.compute_properties( property_preset="none", additional_property_functions={"image": [prop_fn]}, ) ``` **See Also:** For property factories and presets, see `phenome.utils.property_factories`. For built-in metadata classes, see `phenome.metadata`. ## Plugin registry ### `register_property` ```python register_property( name: str, fn: collections.abc.Callable[[typing.Any | None, typing.Any | None], dict[str, float | int]] ) -> None ``` Register a custom property function. The function should have signature (image2d, mask2d) -> Dict[str, float]. Use with compute_properties via get_property(name) or pass the function directly. **Args:** - **`name`**: Property identifier (e.g. "blob", "my_custom"). - **`fn`**: Callable(image2d, mask2d) -> dict of property name -> value. ### `get_property` ```python get_property( name: str ) -> collections.abc.Callable[[Any | None, Any | None], dict[str, float | int]] | None ``` Get a registered property function by name. ### `list_properties` ```python list_properties() -> list ``` Return names of all registered property functions. ### `register_metadata_extractor` ```python register_metadata_extractor( name: str, fn: collections.abc.Callable[[str], dict[str, typing.Any]] ) -> None ``` Register a custom metadata extractor. The function should accept a file path and return a dict with at least 'file_path' (full path). Other keys become metadata columns. **Args:** - **`name`**: Extractor identifier. - **`fn`**: Callable(path) -> metadata dict. ### `get_metadata_extractor` ```python get_metadata_extractor( name: str ) -> collections.abc.Callable[[str], dict[str, Any]] | None ``` Get a registered metadata extractor by name. ### `register_report_section` ```python register_report_section( name: str, fn: collections.abc.Callable[..., str] ) -> None ``` Register a custom report section generator. The function will be called during report generation. Signature should accept (pipeline, ...) and return HTML string for the section. **Args:** - **`name`**: Section identifier (used for nav and ordering). - **`fn`**: Callable that generates the section HTML. ### `get_report_sections` ```python get_report_sections() -> dict ``` Return all registered report section generators. ### `get_blob_properties` ```python get_blob_properties( image2d: numpy.ndarray | None, mask2d: numpy.ndarray | None ) -> dict ``` Extract blob properties using Difference of Gaussians (DoG) blob detection. Detects blobs in the image and computes: - Number of blobs - Mean and standard deviation of blob areas (in pixels²) - Mean and standard deviation of blob intensities at blob centers Blob area uses the skimage convention: radius ≈ sqrt(2)*sigma for 2D images, hence area = 2*pi*sigma² (not pi*sigma²). **Args:** - **`image2d`**: 2D grayscale image array (H, W). Blobs assumed light on dark. - **`mask2d`**: Not used. **Returns:** Dictionary with keys: - 'blob_count': Number of detected blobs - 'blob_area_mean': Mean blob area (in pixels²) - 'blob_area_std': Standard deviation of blob areas - 'blob_intensity_mean': Mean intensity at blob centers (subpixel interpolated) - 'blob_intensity_std': Standard deviation of blob intensities All values are np.nan if image is None or invalid. ### `my_custom_max_intensity` ```python my_custom_max_intensity( image2d: numpy.ndarray | None, mask2d: numpy.ndarray | None ) -> dict ``` Example: return the max intensity of the image (no mask). Use as a template for creating custom property functions. **Args:** - **`image2d`**: 2D image array (H, W). - **`mask2d`**: Not used. **Returns:** Dictionary with 'my_max_intensity' key. # Property computation > Custom property extraction — factory helpers and presets.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.utils.property_factories`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/utils/property_factories.py) (module). ::: **See also:** [Pipeline compute_properties](/PhenoMe/advanced/api/pipeline/) · [Select properties guide](/PhenoMe/guides/select-properties/) Factory functions for property computation in phenotyping analysis. Each factory creates a callable that computes properties from 2D image and/or mask arrays. The returned functions are designed for use with the compute_properties method of PhenoMe. Function signature: Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]] Input arrays (when provided): - image2d: np.ndarray, shape (H, W) or (H, W, C). Intensity image. - mask2d: np.ndarray, shape (H, W). Binary mask; values > 0.5 are foreground. Output: Dict[str, float]: Property names mapped to computed values. NaN for invalid/missing inputs. Note: Functions that work without masks (mask-free intensity properties) can use requirement type "image" and simply ignore the mask2d parameter. ## Property factory functions ### `create_regionprops_function` ```python create_regionprops_function( property_names: list[str], derived_properties: dict[str, collections.abc.Callable] | None = None ) -> Callable ``` Create property function that extracts skimage regionprops from a mask. Uses [skimage.measure.regionprops](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.regionprops) to compute geometric and shape descriptors from the largest connected component. Available direct properties: - area: Foreground pixel count. - perimeter: Boundary length. - major_axis_length: Length of the major axis of the fitted ellipse. - minor_axis_length: Length of the minor axis of the fitted ellipse. - equivalent_diameter: Diameter of a circle with the same area. - convex_hull_area: Number of pixels in the convex hull. - eccentricity: Elongation of the fitted ellipse (0 for circle, 1 for line). - solidity: Ratio of area to convex hull area (measures "compactness" or "roughness"). - orientation: Angle of the major axis (in radians). - euler_number: Number of objects minus number of holes. - extent: Ratio of area to bounding box area. Available derived properties (computed from regionprops): - circularity: 4*pi*area / perimeter^2 (1 for perfect circle). - aspect_ratio: major_axis_length / minor_axis_length. - roundness: 4*area / (pi * major_axis_length^2). See [Property interpretation](/PhenoMe/concepts/property-interpretation/) for detailed descriptions of each property from a biological perspective. **Args:** - **`property_names`**: Names to extract (e.g. 'area', 'perimeter', 'major_axis_length', 'solidity', 'circularity'). - **`derived_properties`**: Optional dict mapping names to (region) -> float functions. **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. ### `create_masked_intensity_function` ```python create_masked_intensity_function( stat_name: str, stat_func: collections.abc.Callable[[numpy.ndarray], float] ) -> Callable ``` Create intensity property function over masked pixels only (mask > 0.5). Extracts pixel values where the mask is foreground and applies a statistical aggregation. Useful for measuring fluorescence or marker expression restricted to the segmented object. **Args:** - **`stat_name`**: Statistic name (e.g. 'mean', 'max', 'std'). - **`stat_func`**: Function that computes the statistic from a 1D array (e.g. [np.mean](https://numpy.org/doc/stable/reference/generated/numpy.mean.html), [np.std](https://numpy.org/doc/stable/reference/generated/numpy.std.html)). **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. Output key: intensity_{stat_name}_masked. ### `create_intensity_function` ```python create_intensity_function( stat_name: str, stat_func: collections.abc.Callable[[numpy.ndarray], float] ) -> Callable ``` Create intensity property function over entire image (no mask). Computes a statistic over all pixels regardless of segmentation. Useful for background characterization, illumination uniformity, or global quality metrics. **Args:** - **`stat_name`**: Statistic name (e.g. 'mean', 'max', 'std'). - **`stat_func`**: Function that computes the statistic from a 2D array (e.g. [np.mean](https://numpy.org/doc/stable/reference/generated/numpy.mean.html), [np.std](https://numpy.org/doc/stable/reference/generated/numpy.std.html)). **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. Output key: intensity_{stat_name}. ### `create_blur_effect_function` ```python create_blur_effect_function() -> Callable ``` Create property function that computes focus sharpness. Uses [skimage.measure.blur_effect](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.blur_effect) which estimates blur by comparing re-blurred versions at multiple scales. Values range from 0 (sharp) to 1 (heavily blurred). Useful for QC filtering of out-of-focus acquisitions. **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. Output key: sharpness_metric. ### `create_entropy_function` ```python create_entropy_function() -> Callable ``` Create property function that computes Shannon entropy of intensity distribution. Uses [skimage.measure.shannon_entropy](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.shannon_entropy). Low entropy = uniform/constant intensity; high entropy = diverse gray levels (complex structures). Defined as H = -sum(p_i * log2(p_i)). In biological contexts, this is often used as a measure of texture complexity within the image or object. **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. Output key: intensity_entropy. ### `compute_concentric_ring_mask` ```python compute_concentric_ring_mask( mask2d: numpy.ndarray, num_rings: int ) -> ndarray ``` Compute a mask of concentric rings based on distance from object boundary. Rings are numbered from 1 (outermost) to num_rings (innermost). Background is 0. Uses [scipy.ndimage.distance_transform_edt](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.distance_transform_edt.html) to compute Euclidean distances from the boundary, then splits the range into equal-width bins. Captures spatial gradients (e.g. membrane vs cytoplasm vs nucleus intensity distribution). **Args:** - **`mask2d`**: np.ndarray, shape (H, W). Binary mask; values > 0.5 are foreground. - **`num_rings`**: Number of concentric rings. **Returns:** np.ndarray, shape (H, W), dtype int. Pixel values 0 (background) to num_rings (innermost). ### `create_concentric_ring_function` ```python create_concentric_ring_function( num_rings: int, stats: list[str] | None = None ) -> Callable ``` Create property function that computes stats per concentric ring (edge to core). Combines `compute_concentric_ring_mask` with statistical aggregation per ring, producing a radial intensity profile from membrane to core. Useful for translocation assays or protein localisation studies. **Args:** - **`num_rings`**: Number of rings. - **`stats`**: Statistics per ring: 'mean', 'std', 'max', 'min', 'median'. Default: ['mean', 'std']. **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. Output keys: ring_{1..N}_{stat}. ### `create_texture_function` ```python create_texture_function( properties: list[str] | None = None ) -> Callable ``` Create property function that computes GLCM texture features within the mask. Uses [skimage.feature.graycomatrix](https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.graycomatrix) and [skimage.feature.graycoprops](https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.graycoprops) to extract Gray-Level Co-occurrence Matrix features at 4 angles (0, 45, 90, 135 deg) with distance=1, averaged across angles. See the [scikit-image GLCM tutorial](https://scikit-image.org/docs/stable/auto_examples/features_detection/plot_glcm.html) for background on texture analysis. **Args:** - **`properties`**: GLCM properties. Default: contrast, dissimilarity, homogeneity, energy, correlation. Valid: 'contrast', 'dissimilarity', 'homogeneity', 'energy', 'correlation', 'asm'. **Returns:** Callable[[Optional[np.ndarray], Optional[np.ndarray]], Dict[str, float]]. Output keys: texture_{prop}. ### `get_preset_property_functions` ```python get_preset_property_functions( preset: str ) -> dict ``` Return a preset dict of property functions for use with compute_properties. **Args:** - **`preset`**: "basic", "shape", "intensity", "standard", or "complete". **Returns:** Dict with keys 'image', 'mask', 'both' mapping to lists of property functions. # Report generation > ReportConfig and generate_report standalone API.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.report.generator`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/report/generator.py) (module). ::: **See also:** [Pipeline generate_report](/PhenoMe/advanced/api/pipeline/) Main report generator module. Assembles the complete HTML report from individual section generators. Author: Aitor González-Marfil (@AAitorG) ## Report configuration and generation ## `ReportConfig` Configuration for report generation. Pass to generate_report via config=. Use ReportConfig for full control, or pass individual overrides to generate_report(..., **overrides). **Attributes:** - **`include_plots`**: Include interactive PCA, t-SNE, and UMAP plots. - **`include_distance_analysis`**: Run and visualize distances to the reference group. - **`include_outliers`**: Detect outliers via Z-score and list them. - **`include_correlations`**: List properties correlated with embeddings. - **`include_property_stats`**: Export aggregated property statistics tables. - **`include_clustering`**: Show GMM clusters and prototype images. - **`include_image_gallery`**: Render the base64 thumbnails gallery. - **`reference_filters`**: Dict mapping metadata keys to values for the reference group in distance analysis. Example: {'condition': 'Control'}. - **`top_k_features`**: Number of top correlated features to show. - **`overview_metadata_keys`**: Metadata keys for the overview section. - **`metadata_keys`**: Metadata keys for plots and sections. If None, uses all. - **`color_by`**: Metadata key for coloring points in plots. If None, uses first. - **`outlier_threshold`**: Z-score threshold for outlier detection. - **`outlier_group_by`**: Metadata key to group by when detecting outliers. - **`n_clusters`**: Number of clusters for GMM/kmeans clustering. - **`clustering_method`**: 'kmeans', 'dbscan', or 'gmm'. - **`n_cluster_prototypes`**: Number of prototype images per cluster. - **`clustering_reduce_dim`**: Max PCA dim before clustering. None = no reduction. - **`clustering_reduce_method`**: 'pca', 'tsne', or 'umap' for pre-clustering. - **`n_gallery_images`**: Number of images per gallery category. - **`image_size_px`**: Thumbnail size in pixels for gallery and prototypes. - **`property_group_by`**: Metadata key for grouping property statistics. - **`property_table_max_rows`**: Max rows in property stats table. - **`lite_mode`**: If True, optimizes for large datasets (sampling, WebGL). - **`description`**: Optional custom description for the report. - **`filters`**: Pre-report metadata filters applied to all results. - **`exclude`**: Pre-report metadata exclusions (same structure as filters). - **`offline_plotly`**: If True (default), inline the Plotly JS library so the report renders without internet access. Adds ~5 MB to the file but makes it truly standalone. Set False to load Plotly from a CDN. - **`theme`**: UI theme, ``"dark"`` (default) or ``"light"``, set on the generated HTML ```` attribute. - **`include_run_settings`**: If True, add a Methods / settings section from the silent run log. Default False so existing reports are unchanged. **Fields:** - **`clustering_method`** (`Literal['kmeans', 'dbscan', 'gmm']`): - **`clustering_reduce_dim`** (`int | None`): - **`clustering_reduce_method`** (`Literal['pca', 'tsne', 'umap']`): - **`color_by`** (`str | None`): - **`description`** (`str | None`): - **`exclude`** (`dict[str, Any] | None`): - **`filters`** (`dict[str, Any] | None`): - **`image_size_px`** (`int`): - **`include_clustering`** (`bool`): - **`include_correlations`** (`bool`): - **`include_distance_analysis`** (`bool`): - **`include_image_gallery`** (`bool`): - **`include_interpretability`** (`bool`): - **`include_outliers`** (`bool`): - **`include_plots`** (`bool`): - **`include_property_stats`** (`bool`): - **`include_run_settings`** (`bool`): - **`interpretability_model_type`** (`Literal['lasso', 'random_forest']`): - **`lite_mode`** (`bool`): - **`metadata_keys`** (`list[str] | None`): - **`n_cluster_prototypes`** (`int`): - **`n_clusters`** (`int`): - **`n_gallery_images`** (`int`): - **`offline_plotly`** (`bool`): - **`outlier_group_by`** (`str | None`): - **`outlier_threshold`** (`float`): - **`overview_metadata_keys`** (`list[str] | None`): - **`property_group_by`** (`str | None`): - **`property_table_max_rows`** (`int`): - **`reference_filters`** (`dict[str, Any] | None`): - **`theme`** (`Literal['dark', 'light']`): - **`top_k_features`** (`int`): ### `generate_report` ```python generate_report( pipeline: 'PhenoMe', output_path: str = 'pheno_report.html', title: str = 'PhenoMe Analysis Report', config: phenome.report.generator.ReportConfig | None = None, **overrides: Any ) -> str ``` Generate comprehensive standalone HTML report from phenotyping results. Uses [ReportConfig](/PhenoMe/advanced/api/report/#reportconfig) as the primary source of options. Pass ``config=`` for full control, or use ``**overrides`` to tweak individual settings. **Args:** - **`pipeline`**: Processed PhenoMe instance with results. - **`output_path`**: Path to save the HTML file. - **`title`**: Report title displayed at the top. - **`config`**: Optional ReportConfig for defaults. If None, uses ReportConfig(). **overrides: Any ReportConfig field to override. Examples: include_plots=False, outlier_threshold=2.5, n_clusters=10, reference_filters={'condition': 'Control'}, lite_mode=True. **Returns:** - **`str`**: Path to the generated HTML file (output_path). File is written to disk. **Example:** ```python >>> pheno.generate_report("report.html") >>> pheno.generate_report("report.html", include_clustering=False, n_clusters=8) >>> from phenome.report import generate_report >>> from phenome.report.generator import ReportConfig >>> cfg = ReportConfig(outlier_threshold=2.5, lite_mode=True) >>> generate_report(pheno, config=cfg, title="My Report") ``` # Results DataFrames > Per-image, grouped, and model result table contracts for PhenoMe API returns # Results DataFrames PhenoMe analysis methods return standard `pandas.DataFrame` objects. To maintain consistency across different analysis types (clustering, outlier detection, property computation, etc.), PhenoMe uses a tiered system of "contracts" for these return shapes. ## Result Tiers | Tier | Type | Purpose | Primary Join Key | | :--- | :--- | :--- | :--- | | **Tier A** | **Per-image** | One row per image in the dataset. | `image_index` | | **Tier B** | **Grouped** | Aggregated statistics by metadata groups. | Grouping columns | | **Tier C** | **Model/Summary** | Feature-level or global analysis summaries. | `property` or `feature` | :::note **Stable Imports vs. Return Shapes**: Tier A/B/C describe the **shape** of the returned data. This is distinct from the [Function Location Guide](/PhenoMe/function-location-guide/), which describes which **symbols** are stable for import. ::: --- ## Tier A: Per-image Tables Tier A is the most common return shape. It ensures that results from different analysis methods can be easily joined together. ### The Contract A Tier A DataFrame is strictly enforced to have: 1. **`image_index`**: A global integer index (`0..N-1`) as the first column. 2. **`image_path`**: The full path to the image as the second column. 3. **`RangeIndex`**: The pandas index is always a simple `0..N-1` range. ### Example: Joining Results Because Tier A tables share the same `image_index`, you can merge them to create a master analysis table. ```python # 1. Compute properties props = pheno.compute_properties() # 2. Run clustering clusters = pheno.compute_clustering() # 3. Join on image_index # We only take the columns we need from the second table full_results = props.merge( clusters[["image_index", "cluster"]], on="image_index" ) ``` ### Slim vs full tables By default, `compute_properties`, `compute_clustering`, and `compute_reference_distances` return **slim** Tier-A tables: identifier columns plus method-specific result columns (no metadata keys as columns). Metadata remains in `pheno.results.metadata` and in grouped/export paths. | Need | Approach | | :--- | :--- | | Join analysis outputs | `merge` on `image_index` (see example below) | | Metadata columns in a DataFrame | `compute_* (..., include_metadata=True)` | | Full export table | `export_dataset_table()` / `build_export_dataframe()` | ### Common Columns | Column | Description | | :--- | :--- | | `image_index` | Unique identifier for the image in the current pipeline. | | `image_path` | Absolute path to the source image. | | `image_name` | Basename of the image file (optional). | | `[Metadata]` | Optional; present when `include_metadata=True` or in export tables. | | `[Results]` | Method-specific columns like `cluster`, `distance`, or `outlier_score`. | --- ## Tier B: Grouped Tables Tier B tables are returned by methods that aggregate data across images, such as `property_stats_by_group` or `analyze_group_enrichment`. ### Structure Rows represent groups defined by your metadata (e.g., one row per "Treatment"). | Column | Description | | :--- | :--- | | `[Grouping Columns]` | The metadata keys used to group the data. | | `property` | The name of the phenotypic property being analyzed. | | `mean_group` / `mean_pop` | Statistics for the group vs. the total population. | | `score` / `p_value` | Statistical significance or enrichment scores. | --- ## Tier C: Model & Summary Tables Tier C tables provide high-level insights into the model or the dataset as a whole. They are typically returned by interpretability and correlation methods. ### Common Methods - **`compute_multivariate_interpretability`**: Returns drivers of embedding dimensions (columns: `feature`, `weight`). - **`compute_component_correlation`**: Returns correlations between properties and components (columns: `property`, `component_1`, etc.). --- ## Optional `return_meta` and `include_metadata` Some methods can return an extra **run-parameters** dict (filters used, distance mode, model R², etc.) via `return_meta=True`: ```python dist_df = pheno.compute_reference_distances( reference_filters={"Treatment": "Control"}, ) dist_df, meta = pheno.compute_reference_distances( reference_filters={"Treatment": "Control"}, return_meta=True, ) print(meta["mode"], meta["source"]) df, meta = pheno.compute_multivariate_interpretability(return_meta=True) print(meta["r2"]) ``` With `return_fig=True` (when a plot is built), the default is `(df, fig)`; use `return_meta=True` as well for `(df, meta, fig)`. Add experiment metadata columns to the DataFrame with `include_metadata=True` on `compute_properties`, `compute_clustering`, or `compute_reference_distances`. --- ## Internal & Visualization DataFrames Some internal methods, such as those used for interactive plots, return DataFrames that do not follow the Tier A contract. For example, `prepare_embedding_dataframe` uses legacy capitalized column names (`Image`, `Index`) and capitalized metadata keys. **Recommendation**: Do not rely on the shape of these DataFrames for downstream analysis. Always use Tier A tables or `export_dataset_table()` for stable integration. --- ## API Helpers If you are building custom analysis tools, use these helpers from `phenome.core`: - `is_per_image_df(df)`: Validates if a DataFrame follows the Tier A contract. - `per_image_dataframe(...)`: Factory for creating Tier A tables from pipeline results. - `IMAGE_INDEX`: Constant for the string `"image_index"`. # Utility functions > Device helpers, transforms, image I/O, checkpoints, file discovery.

Tier: Internal API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.utils.device`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/utils/device.py), [`phenome.utils.metadata`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/utils/metadata.py), [`phenome.io`](https://github.com/AAitorG/PhenoMe/tree/main/phenome/io), [`phenome.utils.transforms`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/utils/transforms.py), and [`phenome.utils.progress`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/utils/progress.py). ::: **See also:** [Model wrappers](/PhenoMe/advanced/api/model-wrapper/) · [HDF5 protocol](/PhenoMe/advanced/database_protocol/) · [Best practices](/PhenoMe/guides/best-practices/) ## Device and reproducibility ### `set_default_device` ```python set_default_device( device: torch.device | str | None ) -> None ``` Set the default device for phenotyping operations. **Args:** - **`device`**: Target device (torch.device, string like 'cuda:0', or None to reset). ### `get_default_device` ```python get_default_device() -> device ``` Return a default device for phenotyping operations. If set_default_device() was called, returns that device. Otherwise, uses a single GPU (cuda:0) when available, else CPU. When CUDA_VISIBLE_DEVICES is set, cuda:0 refers to the first visible GPU. **Returns:** torch.device: cuda:0 if CUDA is available, else cpu. ### `set_determinism` ```python set_determinism( seed: int ) -> None ``` Set random seeds for reproducibility across all frameworks. **Args:** - **`seed`**: Random seed value ## Progress reporting ### `report_progress` ```python report_progress( callback: collections.abc.Callable[[int, int, str], None] | None, current: int, total: int, desc: str = '' ) -> None ``` Invoke *callback* if provided; no-op when ``None``. ``ProgressCallback`` is ``Callable[[int, int, str], None]`` — ``(current, total, desc)``. Pass the same callable to ``PhenoMe.process_images`` / ``compute_properties`` via ``progress_callback=...``. When a callback is set, those methods disable ``tqdm`` so GUIs are not fighting a second progress UI. **Args:** - **`callback`**: Optional progress hook, or ``None``. - **`current`**: Units completed so far (batches or images). - **`total`**: Expected total units; may be ``0`` if unknown. - **`desc`**: Short stage label (e.g. ``"Processing batches"``). ### `track` ```python track( iterable: collections.abc.Iterable[T], desc: str, total: int | None = None, leave: bool = False ) -> Iterable ``` Wrap *iterable* in a ``tqdm`` progress bar. **Args:** - **`iterable`**: Sequence of work units whose length is known, or can be passed as *total*. - **`desc`**: Short label shown beside the bar. - **`total`**: Number of units. Inferred for sized sequences when omitted. - **`leave`**: If False (default), remove the bar when iteration finishes. **Returns:** A ``tqdm`` iterator over *iterable*. ### `log_computing` ```python log_computing( message: str ) -> None ``` Log a one-line status when a step cannot report a progress bar. Notebook output uses the package logger (INFO, message only), same as other PhenoMe progress lines. **Args:** - **`message`**: Status sentence, for example ``"Computing Pearson correlations..."``. ## Metadata helpers ### `default_metadata_from_path` ```python default_metadata_from_path( path: 'str' ) -> dict[str, Any] ``` Simple, dataset-agnostic metadata extractor used by default. Returns 'file_path' (full file path) and 'filename' (basename stem, no extension). For richer metadata (e.g., 'drug', 'time', 'plate', 'well'), define a dataset-specific function and pass it as the metadata_fn argument to PhenoMe. Helpers for path templates and dataframe lookup are available in phenome.utils.metadata. The default column name for file identifiers is ``"filename"`` across all metadata helpers. **Args:** - **`path`**: File path. **Returns:** Dictionary with 'file_path', 'filename', and 'id' keys. **Example:** ```python >>> meta = default_metadata_from_path('/data/image.tif') >>> meta['file_path'], meta['filename'] ('/data/image.tif', 'image') ``` ### `get_metadata_from_path` ```python get_metadata_from_path( template: 'str' ) -> Callable[[str], dict[str, Any]] ``` Create metadata extractor function from a path template. Features: - Use '...' at the start to indicate the template matches a suffix of the path. - Use parentheses for capture groups: (field_name). - Use '.*' for matching any file extension: (filename).* - Supports both Unix (/) and Windows (\) separators in the template. **Args:** - **`template`**: Path template with capture groups in parentheses. **Returns:** Callable[[str], Dict[str, Any]]: Function (path) -> dict of captured group names to values. Returns {} if no match. Keys from template placeholders plus ``filename`` (the default column name for file identifiers). The returned function has a ``group_by`` attribute (last capture group, or ``"filename"``) for [find_files](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files) multi-channel grouping. **Example:** ```python >>> extractor = get_metadata_from_path(".../(drug)/(time)/(crop_name).tif") >>> meta = extractor("/data/project/DrugA/24h/crop_01.tif") >>> meta['drug'], meta['crop_name'], meta['filename'] ('DrugA', 'crop_01', 'crop_01') >>> # Match any extension >>> extractor = get_metadata_from_path(".../(plate)/(well).*") >>> extractor("/data/P1/A01.tif")['filename'] 'A01' ``` ### `make_dataframe_metadata_fn` ```python make_dataframe_metadata_fn( metadata_df: 'pd.DataFrame', filename_column: 'str | list[str]' = 'filename' ) -> Callable[[str], dict[str, Any]] ``` Build metadata function using a dataframe for metadata lookup. Supports two modes: 1. **Single file per sample** (default): Pass a single column name as `filename_column`. The dataframe must contain that column with image filenames; both stems (e.g. ``'my_image'``, ``'my.image'``) and full names with extension (e.g. ``'my_image.tif'``) are accepted. Each row is one image. 2. **Multiple channels in separate files**: Pass a list of column names in channel order, e.g. ``['ch0', 'ch1', 'ch2']``. Each column holds the filename (with or without extension) for that channel. Each row is one sample; the same row is matched when any of its channel filenames is seen. The returned metadata includes ``channel_index`` (0, 1, …). [find_files](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files) groups paths by the first filename column (e.g. ``ch0``) into one row per sample with ``file_path`` as a list of paths in channel order. All other columns are returned with their column names as keys. The function adds ``file_path`` (current path) and, in multi-channel mode, ``channel_index``. No separate ``img_name`` column is added when the filename column already identifies the image. **Args:** - **`metadata_df`**: DataFrame with at least the column(s) specified by filename_column. - **`filename_column`**: Column name(s) for filenames. Single string (default ``"filename"``) for one image per row, or list of column names for multi-channel. Accepts filenames with or without extension. Only known image/data extensions (e.g. ``.tif``, ``.png``) are stripped; names that contain dots but no extension (e.g. ``'my.image'``, ``'plate.A01.well'``) are preserved as-is. **Returns:** Callable[[str], Dict[str, Any]]: Function (path) -> dict. Adds ``file_path`` and, in multi-channel mode, ``channel_index``. All dataframe columns are included (filename column identifies the image). The returned function has a ``group_by`` attribute (first filename column) for [find_files](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files) multi-channel grouping. **Raises:** - **`ValueError`**: If filename_column (or any of its elements) is not in metadata_df. **Example (single file):** ```python >>> df = pd.DataFrame({ ... 'filename': ['img1', 'img2'], ... 'condition': ['Control', 'Treatment'], ... 'time': ['24h', '48h'] ... }) >>> metadata_fn = make_dataframe_metadata_fn(df, filename_column='filename') >>> meta = metadata_fn('/data/img1.tif') >>> sorted(meta.items()) # doctest: +NORMALIZE_WHITESPACE [('condition', 'Control'), ('file_path', '/data/img1.tif'), ('filename', 'img1'), ('id', 'img1'), ('time', '24h')] ``` **Example (single file, df with extension - both formats accepted):** ```python >>> df_ext = pd.DataFrame({'filename': ['img1.tif', 'img2.tif'], 'cond': ['A', 'B']}) >>> fn_ext = make_dataframe_metadata_fn(df_ext, filename_column='filename') >>> fn_ext('/data/img1.tif')['cond'] 'A' ``` **Example (filename with dots, no extension):** ```python >>> df_dots = pd.DataFrame({'filename': ['my.image'], 'cond': ['A']}) >>> fn_dots = make_dataframe_metadata_fn(df_dots) >>> fn_dots('/data/my.image.tif')['filename'] 'my.image' ``` **Example (multi-channel, one column per channel):** ```python >>> df = pd.DataFrame({ ... 'ch0': ['sample1_c0', 'sample2_c0'], ... 'ch1': ['sample1_c1', 'sample2_c1'], ... 'ch2': ['sample1_c2', 'sample2_c2'], ... 'condition': ['Control', 'Treatment'], ... }) >>> metadata_fn = make_dataframe_metadata_fn(df, filename_column=['ch0', 'ch1', 'ch2']) >>> meta = metadata_fn('/data/sample1_c1.tif') >>> meta['condition'], meta['channel_index'], meta['ch0'] ('Control', 1, 'sample1_c0') ``` ## Image I/O and checkpoints ### `read_image` ```python read_image( path: str | list[str] ) -> ndarray ``` Read an image and return as a numpy array. Supports TIFF (via tifffile), NIfTI (via nibabel), Numpy arrays (.npy, .npz), and common formats (PNG, JPEG, BMP, etc. via OpenCV). When ``path`` is a list of paths (e.g. one per channel), each file is read and concatenated along the channel axis, yielding shape (H, W, C_total). **Args:** - **`path`**: Path to the image file, or list of paths (one per channel) for multi-channel datasets where each channel is in a separate file. **Returns:** np.ndarray: Shape (H, W) or (H, W, C), dtype float32. All channels returned. ### `ensure_hwc` ```python ensure_hwc( img: numpy.ndarray ) -> ndarray ``` Ensure image is in (H, W, C) format. **Args:** - **`img`**: np.ndarray. Supported shapes: (H, W), (C, H, W), (H, W, C). For (C, H, W), transposes to (H, W, C) using smallest dim as C. **Returns:** np.ndarray: Shape (H, W, C). Single channel gets (H, W, 1). ## `CheckpointManager` Crash-safe incremental checkpoint backed by a single HDF5 file. Provides crash-safe, incremental persistence for embeddings, metadata, and properties using HDF5. The core safety invariant is a **committed count** attribute that is updated *after* all data for a batch has been flushed to disk. On reload, only the first ``n_committed`` rows are trusted; any trailing rows left behind by an interrupted write are silently discarded. When used as a context manager (``with CheckpointManager(...) as ckpt:``), an exception during the block **discards** uncommitted in-memory buffers without writing them to disk; see ``__exit__`` for the explicit error log. All compression is **lossless** (gzip). Float32 embedding data is stored bit-for-bit exactly; gzip only removes redundancy without altering values. Paths are always stored as POSIX relative (forward slashes) regardless of OS. Version configuration (Constants section): CHECKPOINT_FORMAT_VERSION: version written to new checkpoints CHECKPOINT_SUPPORTED_VERSIONS: set of versions that can be loaded **HDF5 schema** /embeddings (N, D) float32, chunked, lossless gzip /img_path (N,) vlen UTF-8 — primary/only path per image /img_path_channels (N, C) vlen UTF-8 — present only for multi-channel images; each row lists the C channel paths /metadata/ {key} (N,) vlen UTF-8 or float32 — one dataset per metadata key /properties/ {name} (N,) float32 — one dataset per property name /internal/ {name} (N,) float32 — checkpoint control (not phenotypic), same N as /properties /batch_correction/ — optional; batch / plate correction stats (attrs) method, batch_metadata_key, source, written_at batch_{hash}/ group per batch_id (hash of UTF-8 batch_id) (attrs) batch_id, n_controls mean (D,) float32 whiten_mat (D, D) float32 — sphering std (D,) float32 — z-score /config/ — processing parameters (typed attributes) channel_mode str attribute resize_size str attribute ("none" when absent) pad_size str attribute ("none" when absent) force_rgb bool attribute l2_normalize_channels bool attribute — default True when absent (legacy) channels (C,) int8 dataset — absent when all channels used /processing_pipeline/ — optional; ordered printed steps only /embeddings/step_01 title, detail attributes (embedding pipeline) /properties/step_01 title, detail, kind=image|mask|compute Root attributes version str — CHECKPOINT_FORMAT_VERSION n_committed int — rows committed for paths + embeddings + metadata n_committed_props int — rows committed for properties (may lag n_committed) embedding_dim int — 0 when no embeddings is_multichannel bool — True when /img_path_channels is present **External access (no custom code required)** import h5py, numpy as np with h5py.File("results.h5", "r") as f: paths = f["img_path"][:] drug = f["metadata/drug"][:] # vlen UTF-8 array intensity = f["properties/intensity_mean_ch0"][:] # float32 array embeddings = f["embeddings"][:] # (N, D) float32 channel_mode = f["config"].attrs["channel_mode"] l2_ch = f["config"].attrs.get("l2_normalize_channels", True) **Parameters:** - **`path`** (`str`): Filesystem path for the checkpoint file. If it already exists it is opened in append mode and validated; otherwise a new file is created. - **`embedding_dim`** (`int or None`): Dimensionality of the embedding vectors. Required when *creating* a new file. Ignored when opening an existing one. - **`processing_params`** (`dict or None`): Pipeline parameters used to produce embeddings (``channel_mode``, ``channels``, ``resize_size``, ``pad_size``, ``force_rgb``, ``l2_normalize_channels``). Stored in ``/config`` so that a resumed run can verify the same settings are being used. ### Other
Method

buffer_embeddings

```python CheckpointManager.buffer_embeddings( self, embeddings: 'np.ndarray', img_paths: 'list[Any]', metadata_dicts: 'list[dict[str, Any]]' ) -> None ```
Add a batch of embeddings to the in-memory buffer. **Parameters:** - **`embeddings`** (`np.ndarray`): Shape ``(B, D)`` float32. - **`img_paths`** (`list`): Length B. Each element is a ``str`` (single-channel) or a ``List[str]`` (multi-channel). - **`metadata_dicts`** (`list of dict`): Length B.
Method

buffer_internal

```python CheckpointManager.buffer_internal( self, internal_dicts: 'list[dict[str, Any]]' ) -> None ```
Buffer per-row internal dicts; must match the next :meth:`buffer_properties` batch size.
Method

buffer_paths_and_metadata

```python CheckpointManager.buffer_paths_and_metadata( self, img_paths: 'list[Any]', metadata_dicts: 'list[dict[str, Any]]' ) -> None ```
Buffer paths and metadata without embeddings (property-only run).
Method

buffer_properties

```python CheckpointManager.buffer_properties( self, properties_dicts: 'list[dict[str, Any]]' ) -> None ```
Add property dicts to the in-memory buffer.
Method

clear_properties

```python CheckpointManager.clear_properties( self ) -> None ```
Truncate all property datasets to 0 and reset n_committed_props.
Method

clone_to_new_file

```python CheckpointManager.clone_to_new_file( self, new_path: 'str' ) -> CheckpointManager ```
Create a full copy of the current checkpoint at *new_path* and return a new manager. If the current manager is lazy, the file is copied on disk. If it is in RAM, the RAM data is written to the new path. **Parameters:** - **`new_path`** (`str`): Path for the new HDF5 file. **Returns:** - **`CheckpointManager`**: A new manager instance pointing to the cloned file.
Method

close

```python CheckpointManager.close( self ) -> None ```
Flush any remaining buffers and close the HDF5 file.
Method

commit_embeddings

```python CheckpointManager.commit_embeddings( self ) -> int ```
Flush embedding + path + metadata buffer to HDF5 atomically. **Returns:** - **`int`**: Total n_committed after this commit.
Method

commit_internal

```python CheckpointManager.commit_internal( self ) -> int ```
Internal rows are written by :meth:`commit_properties` together with properties. Returns current ``n_committed_props`` (internal uses the same row count).
Method

commit_properties

```python CheckpointManager.commit_properties( self ) -> int ```
Flush property buffer to HDF5 atomically. **Returns:** - **`int`**: Total n_committed_props after this commit.
Static method

detect_format

```python CheckpointManager.detect_format( path: 'str' ) -> str ```
Detect whether *path* is an HDF5 file. Returns ``'hdf5'`` or ``'unknown'``.
Property

embedding_dim

Property on CheckpointManager

Embedding width *D* from file attrs, or ``None`` when not set.
Property

embeddings_buffered

Property on CheckpointManager

Number of paths/embeddings currently in the write buffer.
Method

get_committed_metadata_list

```python CheckpointManager.get_committed_metadata_list( self ) -> list[dict[str, Any]] ```
Return committed metadata dicts in order.
Method

get_committed_paths

```python CheckpointManager.get_committed_paths( self ) -> set[str] ```
Return the set of committed primary ``img_path`` values (resolved to absolute when possible).
Method

get_committed_paths_list

```python CheckpointManager.get_committed_paths_list( self ) -> list[str] ```
Return committed primary paths in order (resolved to absolute when storage root available).
Method

get_processing_params

```python CheckpointManager.get_processing_params( self ) -> dict[str, Any] | None ```
Return processing parameters from ``/config`` group, or None.
Method

get_processing_pipeline

```python CheckpointManager.get_processing_pipeline( self ) -> dict[str, Any] | None ```
Return the stored processing-pipeline steps, or None if absent. Stored as ``/processing_pipeline`` (embeddings and properties step lists).
Method

has_property_content

```python CheckpointManager.has_property_content( self ) -> bool ```
Return True if this file stores real property columns or values. ``n_committed_props > 0`` alone is insufficient: writing a list of empty ``{}`` rows can set that counter and create an empty ``/properties`` group.
Property

is_multichannel

Property on CheckpointManager

Whether the checkpoint stores separate paths per channel.
Method

load_batch_correction_state

```python CheckpointManager.load_batch_correction_state( self ) -> tuple[dict[str, Any], dict[str, Any]] | None ```
Load ``/batch_correction`` if present; otherwise ``None``.
Method

load_committed_properties

```python CheckpointManager.load_committed_properties( self ) -> list[dict[str, Any]] ```
Load only the committed properties (avoids loading embeddings/paths). Used when merging partial property results.
Method

load_committed_results

```python CheckpointManager.load_committed_results( self ) -> PhenoMeResults ```
Read all committed data and return a [PhenoMeResults](/PhenoMe/advanced/api/pipeline/#class-phenomeresults). Paths are resolved to absolute when _storage_root is available. **Returns:** - **`PhenoMeResults`**: Embeddings are loaded eagerly into the ``embeddings`` field. Returns an empty ``PhenoMeResults`` when no data is committed.
Method

load_embeddings_by_indices

```python CheckpointManager.load_embeddings_by_indices( self, indices: 'np.ndarray' ) -> np.ndarray ```
Load only the specified rows of the embedding dataset. Sorts indices before reading for chunk efficiency, then restores original order. **Parameters:** - **`indices`** (`np.ndarray`): 1-D integer array of row indices (0-based, within committed range). **Returns:** - **`np.ndarray`**: Shape ``(len(indices), D)`` float32.
Static method

load_hdf5_results

```python CheckpointManager.load_hdf5_results( path: 'str' ) -> PhenoMeResults ```
Load and validate an HDF5 results/checkpoint file. Paths are resolved to absolute when _storage_root is stored in the file. **Parameters:** - **`path`** (`str`): Path to the HDF5 file. **Returns:** PhenoMeResults **Raises:** - **`ValueError`**: If the file format is invalid or corrupted.
Method

load_internal_all

```python CheckpointManager.load_internal_all( self ) -> list[dict[str, Any]] ```
Load per-row internal checkpoint state (e.g. property-attempt flags). Same length as properties.
Method

load_metadata_and_paths

```python CheckpointManager.load_metadata_and_paths( self ) -> tuple[list[Any], list[dict[str, Any]]] ```
Load all committed paths and metadata dicts. Paths are resolved to absolute when _storage_root is available (from file or inference). **Returns:** (paths, metadata_dicts) ``paths`` is a list of ``str`` (single-channel) or ``List[str]`` (multi-channel). ``metadata_dicts`` is a list of dicts.
Method

load_properties_all

```python CheckpointManager.load_properties_all( self ) -> list[dict[str, Any]] ```
Load all committed property dicts. **Returns:** list of dict Length n_committed_props. Empty list when no properties stored.
Property

n_committed

Property on CheckpointManager

Rows committed for paths + embeddings + metadata.
Property

n_committed_internal

Property on CheckpointManager

Rows with internal tracking data; same as ``n_committed_props`` when using /internal.
Property

n_committed_props

Property on CheckpointManager

Rows committed for properties (may lag n_committed).
Property

properties_buffered

Property on CheckpointManager

Number of property dicts in the write buffer.
Method

property_column_names

```python CheckpointManager.property_column_names( self ) -> list[str] ```
Names of stored property columns, or empty if none were written.
Method

reset_empty_property_counter

```python CheckpointManager.reset_empty_property_counter( self ) -> None ```
Set ``n_committed_props`` to 0 when ``/properties`` has no columns. Saving embeddings with a list of empty ``{}`` dicts can stamp ``n_committed_props = n_images`` without writing columns. Later ``commit_properties`` then refuses to append because the counter already equals ``n_committed``.
Method

save_batch_correction_state

```python CheckpointManager.save_batch_correction_state( self, stats_by_batch: 'dict[str, BatchCorrectionStats]', method: 'MethodName', batch_metadata_key: 'str', source: 'str' ) -> None ```
Persist batch-correction statistics under ``/batch_correction``.
Method

set_processing_params

```python CheckpointManager.set_processing_params( self, params: 'dict[str, Any]' ) -> None ```
Write processing params to ``/config`` group (creates if needed).
Method

set_processing_pipeline

```python CheckpointManager.set_processing_pipeline( self, payload: 'dict[str, Any] | None' ) -> None ```
Write ``/processing_pipeline`` as ``step_01``, ``step_02``, … groups. Stores embeddings and property steps when present. A payload that has only one segment keeps the other segment already on disk. Property steps store ``kind=image``, ``kind=mask``, or ``kind=compute``. Removes the group if *payload* is None.
Method

validate_processing_params

```python CheckpointManager.validate_processing_params( self, current_params: 'dict[str, Any]' ) -> list[tuple[str, Any, Any]] ```
Compare *current_params* against stored params. Returns list of ``(key, stored_value, current_value)`` for mismatches.
Method

write_embeddings_rows

```python CheckpointManager.write_embeddings_rows( self, h5_row_indices: 'np.ndarray', data: 'np.ndarray' ) -> None ```
In-place write of embedding rows identified by HDF5 row indices. Only rows within the committed range may be written. Flushes the file after updating. For non-lazy checkpoints, updates the in-RAM embedding array instead. **Parameters:** - **`h5_row_indices`** (`np.ndarray`): 1-D int64 indices into ``/embeddings`` (0-based, ``< n_committed``). - **`data`** (`np.ndarray`): ``float32`` array of shape ``(len(h5_row_indices), D)`` aligned with *h5_row_indices*.
Static method

write_results_to_hdf5

```python CheckpointManager.write_results_to_hdf5( results: 'PhenoMeResults', path: 'str', compression: 'str' = 'gzip', compression_level: 'int' = 4, processing_params: 'dict[str, Any] | None' = None, internal: 'list[dict[str, Any]] | None' = None, processing_pipeline: 'dict[str, Any] | None' = None ) -> None ```
Atomically write a full results object to an HDF5 file. Uses a temporary file + ``os.replace`` for crash safety. **Parameters:** - **`results`** (`PhenoMeResults`): Source data (embeddings, img_path, metadata, properties). - **`path`** (`str`): Output file path. compression, compression_level Passed to h5py for the embeddings dataset. - **`processing_params`** (`dict or None`): Written to the ``/config`` group when provided. - **`internal`** (`list of dict, optional`): Per-row internal checkpoint state (e.g. attempted flags), written to ``/internal`` when non-empty. - **`processing_pipeline`** (`dict or None`): Optional processing pipeline written to ``/processing_pipeline`` (ordered title/detail steps for embeddings and properties; property steps store ``kind=image``, ``kind=mask``, or ``kind=compute``).
## File discovery — `FileDiscovery` Finds image files and extracts metadata from directories. ### Other
Method

inspect_data

```python FileDiscovery.inspect_data( self, file_df: pandas.DataFrame ) -> DataFrame ```
Inspect image and mask dimensions, shapes, dtypes, and data ranges. Reads each image (and mask when present) in the stored dtype, without the float32 cast used by ``read_image``. Reports shape statistics, image-mask shape matching, and dtype/range. Masks are expected in the ``mask_path`` column of file_df when present. **Args:** - **`file_df`**: pd.DataFrame from find_files(). Must have 'file_path' column. When masks exist, includes 'mask_path' column. **Returns:** pd.DataFrame with columns: file_path, height, width, channels, dtype, min, max, error, shape_match (True/False/None), mask_height, mask_width, mask_dtype, mask_min, mask_max, mask_error.
## NumPy image utilities ### `scale_minmax` ```python scale_minmax( img: numpy.ndarray ) -> ndarray ``` Apply min-max scaling to each channel independently. **Args:** - **`img`**: np.ndarray, shape (H, W) or (..., C) with channels as last dimension. Any dtype. **Returns:** np.ndarray: Same shape as input, dtype float. Values in [0, 1] per channel. ### `quantile_normalize` ```python quantile_normalize( img: numpy.ndarray, quantile: float = 0.99 ) -> ndarray ``` Apply quantile normalization to each channel independently. Clips values at the specified quantile threshold, then rescales to [0, 1]. **Args:** - **`img`**: np.ndarray, shape (H, W) or (..., C) with channels as last dimension. Any dtype. - **`quantile`**: float, quantile threshold for clipping (default: 0.99). **Returns:** np.ndarray: Same shape as input, dtype float64. Values in [0, 1] per channel. ### `normalize_by_dtype_max` ```python normalize_by_dtype_max( img: numpy.ndarray ) -> ndarray ``` Normalize an image to [0, 1] using effective intensity scale inference. Images already in [0, 1] (max <= 1.0) are returned unchanged. Non-negative images use the smallest standard bit-depth scale (255, 65535, …) that fits the data max, so misplaced dtypes (e.g. uint8 stored as int16) scale correctly. Signed images with negative values use min-max scaling. **Args:** - **`img`**: np.ndarray, any shape. **Returns:** np.ndarray: Same shape, dtype float64. Values in [0, 1]. ### `resolve_intensity_scale` ```python resolve_intensity_scale( data_max: float, data_min: float, dtype: numpy.dtype | torch.dtype ) -> tuple ``` Infer how to normalize image intensities to [0, 1]. Uses observed value range first (handles misplaced dtypes and float32 loads), then falls back to nominal dtype scale or data max. **Args:** - **`data_max`**: Maximum pixel value in the image. - **`data_min`**: Minimum pixel value in the image. - **`dtype`**: Stored array or tensor dtype. **Returns:** Tuple of (mode, scale) where mode is ``identity``, ``minmax``, or ``divide``. For ``minmax``, scale is unused; for ``divide``, scale is the divisor. ## Transforms — `TransformBuilder` Builds torchvision transform pipelines for image preprocessing. Uses ImageNet normalization by default. **Args:** - **`mean`**: Normalization mean per channel (default: ImageNet). - **`std`**: Normalization std per channel (default: ImageNet). ### Other
Method

build

```python TransformBuilder.build( self, resize_size: int | None = None, pad_size: int | None = None, n_channels: int = 3 ) -> Compose ```
Build torchvision transform pipeline for image preprocessing. Pipeline steps: ToTensor -> TypeMaxNorm -> [PadToSize] -> [Resize] -> Normalize. When both pad_size and resize_size are given, padding is applied first, then resize (even if resize_size is smaller than pad_size). **Args:** - **`resize_size`**: Target spatial size (H, W) for final resize. If None, no resize. - **`pad_size`**: Minimum size for H and W before resize. If None, no padding. - **`n_channels`**: Number of input channels for Normalize mean/std (default 3). **Returns:** transforms.Compose: Pipeline for (H, W, C) numpy image -> (C, H', W') tensor.
# Visualization > PCA, t-SNE, UMAP, plots, distance distributions, correlations.

Tier: Public API

:::note[Auto-generated] This page is rebuilt from docstrings in [`phenome.mixins.visualization._core`](https://github.com/AAitorG/PhenoMe/blob/main/phenome/mixins/visualization/_core.py) (class `PhenoMeVisualization`). ::: **See also:** [Pipeline](/PhenoMe/advanced/api/pipeline/) · [Distances](/PhenoMe/advanced/api/distances/) · [Concepts](/PhenoMe/concepts/) Pipeline class providing visualization methods for PhenoMe. **Module Breakdown:** - `_dr_plots.py`: Dimensionality reduction visualizations (PCA, t-SNE, UMAP) and centroid plots. - `_distance_plots.py`: Distance distributions, reference comparison plots, and property correlations. - `_image_display.py`: Raw and transformed image visualization with channel-wise controls. - `_interpretability_plots.py`: Visualizing feature importance and drivers for embedding axes. - `_component_correlation_plots.py`: Faceted plots of correlations between embeddings and properties. - `_group_enrichment_plots.py`: Faceted Z-score enrichment plots for metadata groups. - `_helpers.py`: Shared utilities for Plotly and Matplotlib layout/styling. **Interdependencies:** The following private plot functions are intended for use by `PhenoMeAnalysis`: - `_plot_property_correlations_plotly` (from `_distance_plots.py`): Used for embedding-property correlation analysis. - `_display_multivariate_interpretability` (from `_interpretability_plots.py`): Used for explaining embedding axes. ## Visualization
Method

plot_pca

```python _DRPlotsMixin.plot_pca( self, n_components: int = 2, color_by: str | None = None, figsize: tuple[int, int] = (10, 8), source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, hover_features: list[str] | None = None, return_fig: bool = False, render_mode: Literal['auto', 'svg', 'webgl'] = 'webgl', normalize: bool = True, sample_size: int | None = None, **dr_kwargs: Any ) -> Any ```
Plot PCA of embeddings, properties, or combined features (Plotly, WebGL by default).
Method

plot_tsne

```python _DRPlotsMixin.plot_tsne( self, n_components: int = 2, perplexity: float = 30.0, color_by: str | None = None, figsize: tuple[int, int] = (10, 8), source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, hover_features: list[str] | None = None, return_fig: bool = False, render_mode: Literal['auto', 'svg', 'webgl'] = 'webgl', normalize: bool = True, sample_size: int | None = None, **dr_kwargs: Any ) -> Any ```
Plot t-SNE of embeddings, properties, or combined features (Plotly, WebGL by default).
Method

plot_umap

```python _DRPlotsMixin.plot_umap( self, n_components: int = 2, n_neighbors: int = 15, min_dist: float = 0.1, color_by: str | None = None, figsize: tuple[int, int] = (10, 8), source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, hover_features: list[str] | None = None, return_fig: bool = False, render_mode: Literal['auto', 'svg', 'webgl'] = 'webgl', normalize: bool = True, sample_size: int | None = None, **dr_kwargs: Any ) -> Any ```
Plot UMAP of embeddings, properties, or combined features (Plotly, WebGL by default).
Method

plot_counts

```python PhenoMeVisualization.plot_counts( self, group_by: list[str] | None = None, return_fig: bool = False, plot: bool = True ) -> Any ```
Plot count of images grouped by metadata using Plotly. **Args:** - **`group_by`**: List of metadata keys to group by. - **`return_fig`**: If True, return the Plotly figure instead of displaying it. - **`plot`**: If True (default), create and display a Plotly plot. If False, print results as text.
Method

plot_centroids

```python _DRPlotsMixin.plot_centroids( self, group_by: str | list[str], method: Literal['pca', 'tsne', 'umap'] = 'pca', n_components: int = 2, filters: dict[str, typing.Any | list[typing.Any]] | None = None, exclude: dict[str, typing.Any | list[typing.Any]] | None = None, source: Literal['embeddings', 'properties', 'combined'] = 'embeddings', property_keys: list[str] | None = None, show_points: bool = True, show_centroids: bool = True, figsize: tuple[int, int] = (10, 8), return_fig: bool = False, normalize: bool = True, **dr_kwargs: Any ) -> Any ```
Plot centroids of groups in reduced embedding space.
Method

plot_image_by_index

```python _ImageDisplayMixin.plot_image_by_index( self, idx: int, distance_results: pandas.DataFrame | None = None, channels: int | list | None = None, figsize: tuple = (6, 6), title_fields: list[str] | None = None, show_extra_info: bool = True, apply_transforms: bool = False, downsample: int | None = 720, ax: Any = None, return_fig: bool = False, show_mask_overlay: bool = False ) -> Any ```
Plot a specific image by its index. **Args:** - **`idx`**: Image index in results - **`distance_results`**: Optional distance computation results DataFrame - **`channels`**: specific channels to plot - **`figsize`**: Figure size - **`title_fields`**: Optional list of field names to display in title - **`show_extra_info`**: If True, prints detailed information after plotting - **`apply_transforms`**: If True, apply pipeline image_transforms - **`downsample`**: If not None, approximate desired size (in pixels) for the longest image edge when downsampling. The final image size may differ slightly due to integer stepping. If None, no downsampling. - **`ax`**: Optional matplotlib axes to plot on. If provided, a new figure is not created. - **`return_fig`**: If True, return the displayed image as a NumPy array and do not open a matplotlib figure. Mask overlay, when requested, is composited onto that array. - **`show_mask_overlay`**: If True, draws the segmentation mask as a semi-transparent yellow overlay with a crisp contour. Requires masks to have been discovered via ``mask_dir`` in ``find_files``. Useful for verifying that masks load correctly and spatially align with their images. **Returns:** The image as a NumPy array when ``return_fig`` is True; otherwise ``None``.
Method

image_preview_png_bytes

```python _ImageDisplayMixin.image_preview_png_bytes( self, idx: int, distance_results: pandas.DataFrame | None = None, channels: int | list | None = None, title_fields: list[str] | None = None, show_extra_info: bool = False, apply_transforms: bool = True, downsample: int | None = 720 ) -> tuple ```
Rasterize the same view as ``plot_image_by_index`` to PNG bytes. Used by the interactive explorer with ``ipywidgets.Image`` because matplotlib ``display()`` / ``plt.show()`` from Plotly click callbacks does not reliably target a nested ``Output`` widget (output goes to the cell or nowhere). When ``show_extra_info`` is True, also emits the same details as ``plot_image_by_index`` via logging and returns their text for UI display. **Returns:** ``(png_bytes, details_text_or_none)`` — ``details_text_or_none`` is the formatted extra-info block when ``show_extra_info`` is True and details exist.
Method

print_distance_summary

```python _DistancePlotsMixin.print_distance_summary( self, distance_results: pandas.DataFrame, group_by: str | None = None, dist_range: tuple = (0, 100) ) -> None ```
Print distance summary statistics without plotting. Safe to use when enable_plots=False. **Args:** - **`distance_results`**: DataFrame containing 'distance' column - **`group_by`**: Metadata key to group by - **`dist_range`**: Tuple of (min, max) distances to include
# Available tools > Single-table summary of PhenoMe capabilities — formats, embeddings, analysis, and visualization. Capability map of what PhenoMe can do. For method names and signatures, see the [Function location guide](/PhenoMe/function-location-guide/) and the [API reference](/PhenoMe/advanced/). Most analysis methods accept a feature `source`: `"embeddings"`, `"properties"`, or `"combined"`. | Category | Tools / options | Notes | |----------|-----------------|-------| | **Image I/O** | TIFF, NIfTI, NumPy (`.npy`/`.npz`), PNG/JPEG/BMP | `tifffile`, `nibabel`, `numpy`, OpenCV; discovery also matches many microscopy extensions (e.g. `.czi`, `.nd2`) without dedicated readers | | **Embeddings** | DINOv2 (built-in); custom via `ModelWrapper` | e.g. `dinov2_vitb14_reg`; channel modes `"split"` / `"combined"` | | **Dimensionality reduction** | PCA, t-SNE, UMAP | CPU: sklearn + umap-learn; optional GPU: TorchDR (`use_gpu_for_dr=True`) | | **Clustering** | k-means, GMM, DBSCAN | Optional normalize, silhouette, and PCA/t-SNE/UMAP pre-reduce | | **Distances & similarity** | Euclidean, cosine; prototypes; outliers | Reference modes `centroid` / `all_to_all`; outliers via z-score or IQR | | **Group enrichment** | Leave-one-out z-scores; Welch + BH-FDR; property stats | Also top properties vs reference (`cohens_d` / `mean_diff`) | | **Properties** | Presets: none, intensity, shape, basic, standard, complete | Backends: scikit-image, scipy, numpy; factories for regionprops, intensity, texture, rings, blur, entropy; blob plugin | | **Batch correction** | Sphering, z-score | Control-based per batch/plate (`correct_batches`) | | **Interpretability** | Pearson, Spearman, dCor, MI; lasso, random forest | Correlate or explain DR/embedding axes with properties | | **Visualization & reporting** | Plotly plots; matplotlib image grids; Jupyter explorer; HTML report | DR scatters, enrichment, correlations, gallery, etc. | | **Preprocessing & I/O** | Transforms; L2 / StandardScaler; HDF5 checkpoints; CSV/Parquet/Excel export | Orchestrated on [`PhenoMe`](/PhenoMe/advanced/api/pipeline/) | **See also:** [Embeddings](/PhenoMe/concepts/embeddings/), [Distances](/PhenoMe/concepts/distances/), [Select properties](/PhenoMe/guides/select-properties/), [Visualization & results](/PhenoMe/concepts/visualization-and-results/), [Embeddings interpretability](/PhenoMe/concepts/embeddings-interpretability/). # Core concepts > How PhenoMe represents images, distances, metadata, and results—not a prerequisite to install or run your first analysis. import { CardGrid, LinkCard } from '@astrojs/starlight/components'; Use this section when you want background on **how PhenoMe represents** phenotyping: embeddings, channel handling, distances, metadata, and what gets stored in results. You can install, run the [Getting started](/PhenoMe/getting-started/) workflow, and read the [Learning paths](/PhenoMe/user-paths/) without it; come back here when terminology in other pages needs a fuller explanation. ## What is phenotyping? **Phenotyping** characterizes the observable traits of biological samples. In microscopy this means analyzing cell shape (morphology), where proteins are located (fluorescence patterns), or how structures change under treatment. PhenoMe automates this with deep learning. It is **model-agnostic** (it works with almost any vision model) and **dataset-agnostic** (it does not care how your files are organized). ## Processing flow ![Pipeline Overview](../../../assets/pipeline_overview.png) PhenoMe processes data through a bifurcated 4-step pipeline: 1. **Discovery (Input)** — Discover images and extract experimental metadata. 2. **Embeddings (A)** — Load images, apply transforms, and extract deep learning embeddings. 3. **Properties (B)** — Compute interpretable classical features (shape, texture, intensity) in parallel. 4. **Analysis & Visualization (C)** — Integrate both sources to quantify phenotypic distances and produce interactive plots. ## Explore concepts # Channel Modes > Split vs. combined processing for multi-channel images. Microscopy images often have multiple channels (e.g. DAPI for the nucleus, GFP for a specific protein). PhenoMe offers two ways to handle this. ## Split mode (recommended for fluorescence) Each channel is processed **separately**, then results are combined. This ensures the model captures channel-specific information (e.g. if only one protein changes localisation). When using split mode, PhenoMe **L2-normalizes each channel vector** before concatenation by default. This ensures that channels with different dynamic ranges or feature densities contribute equally to the final embedding. To preserve raw channel magnitudes (for models where scale itself is meaningful), pass `l2_normalize_channels=False`. In that case one channel may dominate Euclidean geometry. ## Combined mode (recommended for brightfield / RGB) All channels are treated as a single colour image and processed together. ## Choosing a mode ```python pheno.process_images(wrapper, channel_mode="split") # fluorescence pheno.process_images(wrapper, channel_mode="combined") # brightfield / RGB # Keep raw channel magnitudes in split mode pheno.process_images(wrapper, channel_mode="split", l2_normalize_channels=False) ``` See [FAQ — How do I choose channel mode](/PhenoMe/faq/#how-do-i-choose-channel-mode-split-vs-combined) for more details. # Distance Metrics > Quantifying phenotypic change using mathematical distance. Once images are converted to embeddings, we measure mathematical distance between them to quantify phenotypic change. - **Low distance** = similar to the reference (e.g. no treatment effect). - **High distance** = different from the reference (e.g. strong phenotypic change). ## Common methods - **Euclidean vs cosine**: Euclidean measures absolute straight-line distance; cosine measures the angle between vectors (often better for high-dimensional embeddings because it ignores magnitude). - **Centroid vs all-to-all**: compare each image to the **average** (centroid) of the reference group (fast and stable), or to **every individual** reference image (more sensitive but slower). For a complete guide, see [Common workflows](/PhenoMe/workflows/). # Embeddings & Models > Visual fingerprints, supported models, and the embedding lifecycle. An **embedding** is a digital "fingerprint" of an image: a fixed-size vector of numbers that captures the most important visual features—textures, shapes, patterns—without requiring a human to define them. ```text Image -> Vision model (the "brain") -> Visual fingerprint (the numbers) ``` ## Supported models Common choices: - **DINOv2** (default): self-supervised vision transformer by Meta AI. - **CLIP**: vision-language models. - **Custom models**: any model can be used with the [Vision model wrappers](/PhenoMe/advanced/api/model-wrapper/). DINOv2 transfers well to diverse microscopy images without training or fine-tuning, which is why it is the default. ### Embedding dimensions | Model | Embedding dimension | |-------|---------------------| | ViT-S/14 (Small) | 384 | | ViT-B/14 (Base) | 768 | | ViT-L/14 (Large) | 1024 | | ViT-g/14 (Giant) | 1536 | For custom models see [Vision model wrappers](/PhenoMe/advanced/api/model-wrapper/). ## Embedding lifecycle: eager vs lazy | Mode | When | Where | Access | |------|------|-------|--------| | **Eager** | No checkpoint used during `process_images()` | `results.embeddings` (in-memory `np.ndarray`) | Direct slice | | **Lazy** | Checkpoint path provided or `load_results()` used | On disk (HDF5); `results.embeddings` is `None` | `get_embeddings()` loads rows on demand | Use `get_embeddings()` for lazy-safe access. For full HDF5 storage details, see the [Database protocol](/PhenoMe/advanced/database_protocol/). ## Distances and centroids Analysis methods (`compute_reference_distances`, `detect_outliers`, `find_prototypes`) L2-normalize embeddings by default. Centroid distances use the **arithmetic mean** of vectors in the group; that mean is not generally a unit vector, so Euclidean distance to the centroid is not the same as angular (cosine) distance to the mean direction. For angular comparisons on normalized embeddings, use `distance_type='cosine'` in reference-distance calls or `metric='cosine'` in prototype search. ## Temporal images (in-memory) Use `process_temporal_images()` to add new images **temporarily** to an existing analysis without re-running `process_images`. Temporal images are kept in memory only (not persisted to checkpoint) and are tagged with `metadata['source'] = 'NEW'`. Call `clear_temporal_data()` to remove them. See [Temporal images](/PhenoMe/workflows/#5-temporal-images-testing-new-data-quickly). # Embeddings interpretability > Turn correlation engine output into biological hypotheses - from component correlations to LASSO-based multivariate explanations. import { Card, Aside } from '@astrojs/starlight/components'; PhenoMe ships a **correlation engine** that bridges abstract deep-learning embeddings and interpretable morphological features. When PCA, t-SNE, or UMAP shows interesting clusters, the engine helps answer: > "What morphological property drives this separation?" ## Two correlation modes
Correlates **principal components** (or t-SNE / UMAP coordinates) with classical properties. **Use when** you want to explain the main axes of variation. **Example**: "PC1 correlates with eccentricity (r = 0.88)" → the main separation in PCA is driven by cell elongation. ```python pheno.compute_component_correlation(method="pca") ``` Correlates **raw embedding dimensions** with classical properties. **Use when** you want fine-grained insight into which embedding dimensions encode specific traits. **Example**: "Embedding dim 142 correlates with nuclear intensity." ```python corrs = pheno.compute_embedding_property_correlations() summary_df = pheno.summarize_embedding_property_correlations(corrs) ```
For most users, **component correlation** is the most intuitive starting point. ## Multivariate interpretability Univariate correlation (Pearson / Spearman) looks at one classical property at a time. Deep learning models often rely on **combinations** of features. Multivariate interpretability trains a secondary model (regression or random forest) to predict the embedding axis from **all** classical features simultaneously. ### Why it matters Seeing two conditions form distinct clusters only tells you half the story; the critical question is *"what geometric or morphological rules is the black-box model using to separate them?"* Multivariate forward probing lets you translate an abstract deep learning axis (for example t-SNE axis 1) into a concrete, readable formula of classical biology. ### Reading the output You get two critical pieces of information per axis:
*How well do classical properties track this axis on the same dataset?* - **High R² (e.g. 0.85)** — variation along the axis aligns strongly with combinations of standard features (size, shape, texture). Useful for hypothesis generation about what the layout may reflect. - **Low R² (e.g. 0.15)** — classical features weakly track the axis; the embedding may encode patterns outside the current property vocabulary. Unlike univariate metrics, this shows the **combination** of features acting together. *Example:* if t-SNE axis 1 separates healthy and diseased cells, the model might return `[+0.45] Eccentricity + [-0.30] Intensity Entropy` - proving both elongation and nuclear texture matter.
### LASSO regression (sparse linear) **LASSO** (L1-regularised regression) identifies a small subset of properties that linearly combine to explain the axis. It shrinks irrelevant properties to exactly zero weight. - **Best for**: primary drivers in a human-readable formula. - **Output**: descriptive on-axis R² and a list of weights. - **Example**: "t-SNE axis 1 is associated (on-axis R² = 0.85) with `[+0.65] Area, [-0.42] Circularity, [+0.15] Texture Entropy`." ```python results = pheno.compute_multivariate_interpretability(model_type="lasso") # Optional: balance LASSO folds by treatment or batch (when each class has >= cv samples) # results = pheno.compute_multivariate_interpretability(model_type="lasso", stratify_by="Treatment") # Default: shows an interactive driver bar chart. Use plot=False for a logger text summary only. ``` ### Random forest (non-linear) **Random forest** captures non-linear relationships and interactions linear models miss. - **Best for**: when LASSO gives a low on-axis R² but you still suspect classical properties hold the answer. - **Output**: OOB R² (same global-DR caveat for the target axis) plus non-negative feature importances. ```python results = pheno.compute_multivariate_interpretability(model_type="random_forest") ``` ## Reading univariate correlation coefficients For a quick reference on how to interpret correlation coefficients ($r$), see the [Glossary — Correlation coefficient (r)](/PhenoMe/glossary/#correlation-coefficient-r). - **r = 0.88 between PC1 and eccentricity** → moving along PC1 (left-to-right) corresponds to increasingly elongated cells. - **r = -0.75 between PC2 and area** → PC2 encodes cell size. Strong correlations (`|r| > 0.7`) suggest a clear biological story. Weaker correlations (`|r| < 0.5`) may indicate noise or multi-factor effects - that is often when multivariate interpretability becomes useful. ## From numbers to hypotheses 1. **Compute** properties and a dimensionality reduction. 2. **Run component correlation** for each component (PC1, PC2, ...); the `top_k` properties with highest `|r|` are the main drivers. 3. **Formulate a hypothesis**: "PC1 represents cell elongation; clusters along PC1 likely differ in aspect ratio." 4. **Validate**: colour the same space by the top property to confirm - `pheno.plot_pca(color_by="eccentricity")` (if eccentricity is a column) or overlay property values. 5. **Report**: include correlations in the HTML report via `pheno.generate_report(include_correlations=True, top_k_features=...)`. ## Choosing a correlation method | Method | Use when | |--------|----------| | **Pearson** (default) | Roughly linear relationships (size, intensity). | | **Spearman** | Monotonic but non-linear; robust to outliers. | | **Distance correlation** | Suspected non-linear dependency; Pearson is low. | | **Mutual information** | Any dependency, including non-monotonic. | For most morphological traits, **Pearson** is sufficient and GPU-accelerated. Switch to Spearman for outliers or dose-response curves; switch to distance correlation or mutual information when linear metrics miss obvious structure. Pass the correlation type via `correlation_method` (not `method`, which selects the dimensionality reduction: `pca`, `tsne`, or `umap`): ```python pheno.compute_component_correlation( method="pca", correlation_method="spearman", plot=False, ) ``` Valid values: `"pearson"` (default), `"spearman"`, `"distance_correlation"`, `"mutual_info"`. Distance correlation requires the optional `dcor` package (`pip install dcor`). See the [`compute_component_correlation` API](/PhenoMe/advanced/api/pipeline/#api-phenomeanalysis-compute_component_correlation) for the full parameter list. - **Tutorial**: [04 - Exploratory analysis and explainability](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/04_exploratory_analysis_and_explainability.ipynb). ## See also | Topic | Document | |-------|----------| | Built-in property names | [Property interpretation](/PhenoMe/concepts/property-interpretation/) | | Select properties | [Select properties](/PhenoMe/guides/select-properties/) | | Report with correlations | [Report API](/PhenoMe/advanced/api/report/) | | Workflow examples | [Common workflows](/PhenoMe/workflows/#3-property-based-analysis-and-explaining-results) | # Metadata & Properties > Contextual metadata and classical image features. ## Metadata **Metadata** links an image to its experimental context: - Which drug was applied? - At what concentration? - Which plate well is this from? - What timepoint was this taken at? By organising images with metadata, PhenoMe can colour plots by drug concentration, calculate distances relative to controls, and compare across plates. See [Experiment details](/PhenoMe/guides/experiment-details/). ## Properties Where **embeddings** are abstract vectors from a deep learning model, **properties** are classical, explainable image measurements (cell count, average intensity, nuclear area, ...). PhenoMe computes and stores properties alongside embeddings, so you can ask whether simple properties (like nucleus size) already explain the grouping a deep-learning model sees. See [Select properties](/PhenoMe/guides/select-properties/) for user-defined metrics and [Property interpretation](/PhenoMe/concepts/property-interpretation/) for the built-ins. # Property interpretation > What each built-in classical property measures (shape, intensity, texture, QC), how to read it, and how to avoid redundant features when comparing groups. :::tip This page explains **what each built-in property means**. **Preset bundles** (`basic`, `standard`, `complete`, …) and choosing `property_preset` are covered under [Select properties — Presets](/PhenoMe/guides/select-properties/presets/). ::: ## Overview Properties are numerical features extracted from images and/or masks. They quantify morphology, intensity, texture, and image quality. Use them for: - **Distance computation** (`source='properties'`) - **Dimensionality reduction** (PCA, t-SNE, UMAP colored by properties) - **Correlation analysis** with embeddings - **Statistical comparison** between experimental groups Properties are computed on the **largest connected component** of the mask when a mask is used. Multi-channel images produce per-channel properties (e.g., `intensity_mean_ch0`, `intensity_mean_ch1`). Pass `channel_names=["DAPI", "GFP"]` to `find_files` to get names like `intensity_mean_DAPI` instead. --- ## Shape Properties (from mask) These require a binary mask. They describe object size and shape. Computed via [skimage.measure.regionprops](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.regionprops). | Property | Measures | Range / Formula | Interpretation | | --- | --- | --- | --- | | **area** | Foreground pixel count | ≥ 0 | Larger = bigger object | | **perimeter** | Boundary length (4-connected contour) | ≥ 0 | Rough edges → higher perimeter | | **major_axis_length** | Major axis of fitted ellipse | ≥ 0 | Main elongation direction | | **minor_axis_length** | Minor axis of fitted ellipse | ≥ 0 | Width perpendicular to major | | **eccentricity** | Elongation of fitted ellipse | 0 (circle) – 1 (line) | How elongated vs round | | **solidity** | Area / convex hull area | 0 – 1 | Low = concave/indented shape | | **orientation** | Angle of major axis vs horizontal | [−π/2, π/2] rad | Cell alignment direction | | **extent** | Area / bounding box area | 0 – 1 | How much of box is filled | | **equivalent_diameter** | Diameter of equal-area circle | 2√(area/π) | Size in interpretable units | | **euler_number** | Objects − holes | integer | 1 = solid, 0 = has a hole | | **convex_hull_area** | Convex hull pixel count | ≥ area | Used internally for solidity | **Derived properties** (computed from the above): | Property | Formula | Interpretation | | --- | --- | --- | | **circularity** | 4π · area / perimeter² | 1 = perfect circle, < 1 = irregular boundary | | **aspect_ratio** | major_axis / minor_axis | 1 = round, > 1 = elongated | | **roundness** | 4 · area / (π · major²) | 1 = circle, < 1 = elongated | :::note `compactness` (= 1/circularity) is omitted to avoid redundancy. Use `circularity`. ::: **Learn more:** [scikit-image regionprops reference](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.regionprops) --- ## Intensity Properties ### Whole-image (no mask needed) Computed over all pixels. Useful for illumination/background characterization. | Property | Measures | Interpretation | | --- | --- | --- | | **intensity_mean** | Average pixel intensity | Overall brightness | | **intensity_std** | Standard deviation | Spread / contrast | | **intensity_min** | Minimum intensity | Darkest pixel (baseline) | | **intensity_max** | Maximum intensity | Brightest pixel (saturation) | ### Masked (inside object only) Computed only on foreground pixels (mask > 0.5). Useful for fluorescence/staining quantification. | Property | Measures | Interpretation | | --- | --- | --- | | **intensity_mean_masked** | Mean inside object | Signal level in segmented region | | **intensity_std_masked** | Std inside object | Intracellular heterogeneity | | **intensity_min_masked** | Min inside object | Darkest point in object | | **intensity_max_masked** | Max inside object | Peak signal | **Learn more:** [NumPy statistical functions](https://numpy.org/doc/stable/reference/routines.statistics.html) --- ## Concentric Ring Properties The object is divided into concentric rings by Euclidean distance from the boundary (via [`scipy.ndimage.distance_transform_edt`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.distance_transform_edt.html)). Ring 1 = outermost (membrane), higher = closer to center (core). | Property | Measures | Interpretation | | --- | --- | --- | | **ring_N_mean** | Mean intensity in ring N | Radial intensity profile | | **ring_N_std** | Std of intensity in ring N | Local heterogeneity per ring | Captures **spatial gradients** — e.g. whether a marker is concentrated at the membrane (ring 1) vs the nucleus (ring N). Useful for translocation assays and protein localisation. Use `create_concentric_ring_function(num_rings=3, stats=['mean','std'])` → `ring_1_mean`, `ring_2_mean`, `ring_3_mean`, etc. --- ## Texture Properties (GLCM) Gray-Level Co-occurrence Matrix features computed within the masked region using [`skimage.feature.graycomatrix`](https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.graycomatrix) at 4 angles, distance=1, averaged. | Property | Measures | Range | Interpretation | | --- | --- | --- | --- | | **texture_contrast** | Squared intensity differences between neighbours | [0, (levels−1)²] | High = grainy/rough | | **texture_dissimilarity** | Absolute intensity differences | [0, levels−1] | High = heterogeneous | | **texture_homogeneity** | Closeness to GLCM diagonal | [0, 1] | High = smooth/uniform | | **texture_energy** | Sum of squared GLCM elements | [0, 1] | High = repetitive/ordered | | **texture_correlation** | Linear dependency between pixel pairs | [−1, 1] | High = predictable pattern | | **texture_asm** | Angular Second Moment (= energy²) | [0, 1] | Orderliness measure | **Learn more:** [scikit-image GLCM tutorial](https://scikit-image.org/docs/stable/auto_examples/features_detection/plot_glcm.html) · [graycoprops reference](https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.graycoprops) --- ## Image Quality Properties | Property | Measures | Range | Interpretation | | --- | --- | --- | --- | | **sharpness_metric** | Focus quality (multi-scale blur estimation) | 0 (sharp) – 1 (blurry) | QC: filter out-of-focus images | | **intensity_entropy** | Shannon entropy of intensity histogram | ≥ 0 | Low = uniform; high = complex texture | **Learn more:** [`skimage.measure.blur_effect`](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.blur_effect) · [`skimage.measure.shannon_entropy`](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.shannon_entropy) --- ## Choosing properties - **Avoid redundancy:** `circularity` and `compactness` are inverses — use `circularity`. `aspect_ratio` and `roundness` both capture elongation; pick one unless both add value. - **Match your question:** Morphology → area, perimeter, eccentricity, solidity. Intensity → masked mean/std. Texture → GLCM. QC → sharpness_metric. - **Presets:** For which features are included in `property_preset="basic"` / `"standard"` / `"complete"` (and other valid presets), see [Select properties — Presets](/PhenoMe/guides/select-properties/presets/). --- ## See also | Topic | Document | |-------|----------| | Preset bundles | [Select properties — Presets](/PhenoMe/guides/select-properties/presets/) | | Writing your own functions | [Select properties](/PhenoMe/guides/select-properties/) | | Extension points | [Extending PhenoMe](/PhenoMe/guides/extending/) | | API reference | [`compute_properties`](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-compute_properties) | | Concepts overview | [Metadata and properties](/PhenoMe/concepts/metadata-and-properties/) | | Workflow examples | [Common workflows](/PhenoMe/workflows/#3-property-based-analysis-and-explaining-results) | # Visualization & Results > Dimensionality reduction, results structure, and portability. ## Dimensionality reduction High-dimensional embeddings (768+ dimensions) are reduced to 2D/3D for plotting: :::note GPU-accelerated DR (`torchdr`) may differ slightly from CPU due to numerical precision. For strict reproducibility use `use_gpu_for_dr=False`. See [Best practices - reproducibility](/PhenoMe/guides/best-practices/reproducibility/). ::: | Method | Description | Best for | |--------|-------------|----------| | **PCA** | Linear projection preserving variance | Quick overview, global structure | | **t-SNE** | Non-linear, preserves local structure | Clustering, local relationships | | **UMAP** | Non-linear, preserves global + local | General purpose, faster than t-SNE | ```python pheno.plot_pca(n_components=2, color_by="condition") pheno.plot_tsne(n_components=3, color_by="condition") pheno.plot_umap(n_neighbors=15, min_dist=0.1, color_by="drug") ``` ## Data sources | `source` | Uses | |----------|------| | `"embeddings"` | Embedding vectors (default) | | `"properties"` | Computed classical properties | | `"combined"` | Concatenation of both | ```python pheno.plot_pca(source="properties", property_keys=["area", "intensity"]) ``` ## Results structure Results live in `PhenoMeResults` (`pheno.results`): `img_path`, `metadata`, `properties`, and `embeddings`. Use **attribute access** (not dict-style `results["key"]` or `results.get()`): ```python paths = pheno.results.img_path meta = pheno.results.metadata props = pheno.results.properties emb = pheno.results.embeddings # np.ndarray when eager; None if absent or lazy (HDF5) ``` ### Accessing results | Access | When to use | |--------|-------------| | `pheno.get_embeddings()` | Preferred. Loads from disk when using checkpoint/lazy; returns `None` if no embeddings. | | `pheno.results.embeddings` | Same underlying data when stored eagerly in memory; `None` when not computed or lazy-backed. | | `pheno.export_dataset_table()` | Metadata, properties, and distances as a DataFrame. Use for filtering, grouping, external analysis. | | `pheno.get_image_info(idx)` | Full metadata, properties, and optional distance for one image. | Analysis methods return [per-image DataFrames](/PhenoMe/advanced/api/results-dataframes/) (Tier A) with `image_index`, `image_path`, and `image_name` so tables join on one schema. Results are saved and loaded as HDF5 via `save_results()` and `load_results()`. ## Portability and data management Sharing and moving analysis results across environments is a core design goal. Checkpoints store **relative** POSIX paths so they travel between machines. **Moving a dataset:** 1. Copy the HDF5 checkpoint and image files to the new location. 2. Call `find_files` at the new location, then `load_results`: ```python pheno.find_files("/new/path/to/images") pheno.load_results("my_analysis.h5") ``` 3. The pipeline resolves paths automatically. # Glossary > Plain-language definitions of the terms used across PhenoMe. Definitions of key terms used in the PhenoMe documentation, in plain language. ## A–D ### Centroid The average (mean) position of a group of points in embedding space. When computing "distance to centroid," we measure how far each image's embedding is from the mean embedding of a reference group (e.g., controls). ### Channel A single color or wavelength layer in a microscopy image. A 3-channel fluorescence image might have DAPI (nucleus), GFP (protein), and mCherry (membrane). The pipeline can process each channel separately (split mode) or combine them as RGB (combined mode). ### Channel mode How multi-channel images are handled. **Split mode**: each channel is processed independently; embeddings are concatenated. **Combined mode**: channels are stacked as RGB and processed together. Use split for fluorescence; use combined for brightfield or natural color. ### Checkpoint A saved state of your analysis (embeddings, metadata, properties) stored in HDF5 format. Enables resuming after interruption and sharing results. ### Classical properties Hand-crafted numerical features (area, intensity, eccentricity, etc.) computed from images and masks. Interpretable and used as the "vocabulary" to explain what embeddings encode. ### CLS token In Vision Transformers (ViTs), a special token whose output represents a summary of the entire image. Used as the embedding vector. ### Correlation coefficient (r) A number between -1 and 1 measuring how two variables move together. Positive r: both increase together. Negative r: one increases as the other decreases. Near 0: little linear relationship. Used to link embedding dimensions to classical properties for interpretability. ### Dimensionality reduction (visualizing patterns) Reducing complex high-dimensional information (like 768 numbers describing an image) into simpler representations (2 or 3 numbers) so we can plot them on a map. Methods: PCA, t-SNE, UMAP. ### DINOv2 A self-supervised vision model by Meta AI. Learns visual features without labels. Default example model in the pipeline. ### Distance metric How "far apart" two embeddings are. **Euclidean**: straight-line distance. **Cosine**: angle between vectors (scale-invariant). --- ## E–M ### Embedding A fixed-size numerical vector that represents an image. Produced by a deep learning model; captures visual characteristics in a compact form. ### Metadata Information about each image: file path, condition, drug, timepoint, replicate, etc. Extracted from file paths or CSV. ### Model-agnostic The pipeline works with any vision model that produces embeddings (DINOv2, CLIP, BioCLIP, custom models). No lock-in to a single architecture. ### Morphometry Quantitative measurement of shape and form. Classical morphometry uses hand-crafted features (area, perimeter, eccentricity); the pipeline also uses deep embeddings. --- ## P–R ### PCA (Principal Component Analysis) Linear dimensionality reduction. Finds directions of maximal variance. Fast, deterministic. Good for a quick overview. ### Phenotypic distance A scalar value indicating how different an image is from a reference group (e.g., controls). Higher distance = more phenotypically different. ### Phenotyping Characterizing observable traits (phenotypes) of biological samples, e.g., cell morphology, fluorescence patterns, response to treatments. ### Property preset A predefined set of classical properties: `none`, `intensity`, `shape`, `basic`, `standard`, or `complete`. See [Select properties — Presets](/PhenoMe/guides/select-properties/presets/) and [Property interpretation](/PhenoMe/concepts/property-interpretation/). ### Reference group The set of images used as baseline (e.g., untreated controls). Defined by `reference_filters` when computing phenotypic distances. ### Report A standalone HTML file with interactive plots, distance analysis, clustering, correlations, and image galleries. Shareable without re-running the pipeline. --- ## S–Z ### Split mode See [Channel mode](#channel-mode). Each channel processed independently. ### t-SNE Non-linear dimensionality reduction. Preserves local structure; good for clustering visualization. Can be stochastic (results vary with seed). ### UMAP Non-linear dimensionality reduction. Balances global and local structure; often faster than t-SNE. Default for many exploratory workflows. ### Zero-shot Using a pre-trained model without training or fine-tuning on your data. DINOv2 and similar models work out-of-the-box for phenotyping. --- ## See also - **Visual fingerprints and labels** - [Core concepts](/PhenoMe/concepts/), [Preparing your data](/PhenoMe/guides/data-setup/) - **Channel / split mode** - [Concepts — channel modes](/PhenoMe/concepts/channel-modes/) - **Checkpoints and HDF5** - [HDF5 protocol](/PhenoMe/advanced/database_protocol/), [Best practices](/PhenoMe/guides/best-practices/) - **Linking AI to biology** - [Explaining AI results](/PhenoMe/concepts/embeddings-interpretability/) # Architecture > High-level layout of the phenome package, data flow, and extension points. ## Package layout | Area | Path | Role | |------|------|------| | Orchestrator | `phenome.pipeline` | `PhenoMe` class; user-facing workflow. | | Mixins | `phenome.mixins` | Behaviour split into dataset, embeddings, properties, distances, analysis, visualization, interactive. | | Core algorithms | `phenome.core` | DR, correlation, interpretability, results container (`PhenoMeResults`), export, validation. | | I/O | `phenome.io` | Images, checkpoints, file discovery. | | Metadata | `phenome.metadata` | `MetadataBase` and concrete extractors. | | Utilities | `phenome.utils` | Model wrappers, transforms, metadata helpers, property factories. | | Plugins | `phenome.plugins` | Registries for properties, metadata extractors, report sections. | | Report | `phenome.report` | HTML report generation. | Imports for users are mostly from **`phenome`** (re-exports). See the [Function location guide](/PhenoMe/function-location-guide/) for a full symbol index. The `PhenoMe` orchestrator composes the mixins in `phenome.mixins` and delegates to **`phenome.io`** (discovery, HDF5), **`phenome.metadata`** (`MetadataBase` and helpers), **`phenome.core`** (DR, correlations, interpretability, `PhenoMeResults`), **`phenome.utils`** (model wrappers, property factories), **`phenome.plugins`** (user registries), and **`phenome.report`** (HTML output). ## Data flow (typical run) ![Pipeline Overview](../../../assets/pipeline_overview.png) The typical data flow maps directly to the pipeline overview components: 1. **Discovery (Input)** — Gather raw image paths and experimental metadata. 2. **Embeddings (A)** — Batch inference through a `ModelWrapper` to extract visual fingerprints. 3. **Properties (B)** — Parallel computation of classical features from images/masks. 4. **Analysis & Visualization (C)** — Combine embeddings and properties for quantification and plotting. ## Extension points | Goal | Where to start | |------|----------------| | Custom metadata from paths or tables | [Experiment details](/PhenoMe/guides/experiment-details/), `MetadataBase`, `get_metadata_from_path`, `make_dataframe_metadata_fn`. | | Custom image features | [Select properties](/PhenoMe/guides/select-properties/), `phenome.plugins.register_property`. | | Custom vision backbone | [Model wrapper API](/PhenoMe/advanced/api/model-wrapper/); subclass `ModelWrapper`. | | Report sections | [Extending PhenoMe](/PhenoMe/guides/extending/), `register_report_section`. | **Rule of thumb:** prefer hooks (`metadata_fn`, property factories, plugins) before forking `PhenoMe` internals. ## Where should I add code? | Adding... | Prefer | |-----------|--------| | New user-facing workflow step | `phenome.pipeline` or relevant `phenome.mixins`. | | Reusable algorithm (DR, stats, export) | `phenome.core`. | | File formats, HDF5, discovery | `phenome.io`. | | User-registerable extension | `phenome.plugins`. | ## Further reading - [HDF5 protocol](/PhenoMe/advanced/database_protocol/). - [API & data formats](/PhenoMe/advanced/). - [Developer guide](/PhenoMe/guides/developer-guide/). # Best practices > Reproducibility, performance, data organization, and collaboration patterns for PhenoMe. This section collects habits that keep PhenoMe analyses **reproducible**, **fast enough for the hardware you have**, and **easy to share**. Each topic is a short focused page: | Topic | What you'll find | |-------|------------------| | [Reproducibility](/PhenoMe/guides/best-practices/reproducibility/) | Seeds, deterministic mode, saving configurations, DR backend choice. | | [Performance](/PhenoMe/guides/best-practices/performance/) | GPU sizing, batch size, memory management, troubleshooting OOM and slow runs. | | [Data organization](/PhenoMe/guides/best-practices/data-organization/) | Directory layout, naming conventions, inspection, filtering. | | [Batch correction](/PhenoMe/workflows/#7-plate--batch-correction) | Correcting for technical variation (plate effects) using controls. | | [Collaboration](/PhenoMe/guides/best-practices/collaboration/) | Sharing experiments, DVC, environment specs. | ## Quick reference | Topic | Key tip | |-------|---------| | Reproducibility | `PhenoMe(seed=42)`; use `export_experiment_config()` after `save_results()`. | | DR backends | `use_gpu_for_dr=False` for strict reproducibility (sklearn / umap-learn). | | DR GPU OOM | Install `pykeops` for memory-efficient t-SNE / UMAP; PCA uses batched `ExactIncrementalPCA`. | | GPU OOM | Reduce `batch_size`, pick a smaller model, process in chunks. | | Portability | HDF5 stores relative paths - call `find_files` first, then `load_results`. | | Batch correction | Use `pheno.correct_batches(batch_metadata_key="Plate", control_filters={"condition": "Control"})`. | | Data inspection | Run `pheno.inspect_data()` before processing (after `find_files`). | # Collaboration > Share PhenoMe experiments with collaborators, version data with DVC, and keep environments aligned. ## Sharing experiments Generate a self-contained bundle: ```python # Directory form writes phenome_results.h5 pheno.save_results("phenome_results.h5") pheno.generate_report( output_path="experiment_2026_01/report.html", reference_filters={"condition": "Control"}, ) pheno.export_experiment_config( "experiment_2026_01/config.json", ) ``` A collaborator reloads it with: ```python pheno = PhenoMe() pheno.find_files("/path/to/experiment_2026_01/images") pheno.load_results("phenome_results.h5") ``` :::tip Have colleagues install the same release with `pip install phenome`. Check the version you used when the analysis must match exactly. Check it with `import phenome; print(phenome.__version__)`. ::: ## Recommended shared project layout See [Data organization - recommended project layout](/PhenoMe/guides/best-practices/data-organization/#recommended-project-layout). For collaboration, add `configs/` (template configs) and environment specs under `envs/` (conda YAML and pip requirements); the repo root keeps a short [`requirements.txt`](https://github.com/AAitorG/PhenoMe/blob/main/requirements.txt) that defaults to GPU. ## Data versioning with DVC (optional) **When to use DVC.** Large raw image datasets, checkpoint / HDF5 result files, and reproducibility of data lineage across machines. **What the checkpoint already provides.** The HDF5 from `save_results()` or `checkpoint_path` stores: - `processing_params` (`channel_mode`, `resize_size`, `pad_size`, `force_rgb`, `l2_normalize_channels`). - `version` (format version). - Embeddings, metadata, properties. You do **not** need to duplicate this configuration elsewhere. The checkpoint is self-contained for processing state. **How DVC complements the checkpoint.** DVC handles **data and assets** (versioning, storage, sharing). The checkpoint handles **processing state** (what was computed, with which params). They work together: ```bash dvc add data/ dvc add results/phenome_results.h5 git add data.dvc results/phenome_results.h5.dvc git commit -m "Add data and results" ``` **Summary.** DVC does not replace the checkpoint. Use the checkpoint for crash-safe processing and parameter traceability; use DVC for data versioning and large-file storage. ## Naming conventions - Use consistent metadata keys across experiments (for example `condition`, `replicate`, `timepoint`). - Name config files by experiment: `drug_screen_2026.json`. - Commit configs alongside scripts, not checkpoints - checkpoints are regenerable. ## See also - [Reproducibility](/PhenoMe/guides/best-practices/reproducibility/) - seed and DR-backend choice. - [External checkpoints](/PhenoMe/guides/external-checkpoints/) - exchange embeddings with non-PhenoMe tooling. - [Report API](/PhenoMe/advanced/api/report/) - `generate_report()` and the primary HTML artefact shared with non-technical reviewers. # Data organization > Folder layout, naming conventions, inspection, and filtering patterns. import { FileTree, Aside } from '@astrojs/starlight/components'; ## Recommended project layout - project/ - data/ - images/ raw images - masks/ segmentation masks (optional) - metadata.csv experimental metadata - results/ - phenome_results.h5 checkpoint / results - distances.pkl - properties.csv - config.json - figures/ - pca_by_condition.html - distance_distribution.html - scripts/ - process_data.py - analyze_results.py ## Naming conventions Use descriptive, consistent filenames. ```text # Good images/Control_30min_rep1_001.tif images/DrugA_10uM_30min_rep1_001.tif # Avoid images/img1.tif images/experiment_final_v2_new.tif ``` Consistent names let you reuse the same path templates and CSV lookups across experiments - see [Experiment details](/PhenoMe/guides/experiment-details/). ## Inspect before processing Always run `inspect_data()` between `find_files` and `process_images` when you want a full per-image table of shapes, stored dtypes, and ranges. `process_images` also samples up to 32 images, logs a short processing pipeline at INFO, and warns on suspicious but processable inputs (for example under-used bit depth). Those warnings do not change the data. Known-unprocessable combinations (unsupported dimensions, invalid channel selection, mixed split-channel counts, mixed sizes with no resize and `batch_size>1`) fail before the model runs. ```python inspect_df = pheno.inspect_data() print(inspect_df.head()) shape_groups = inspect_df.groupby(["height", "width", "channels"]).ngroups if shape_groups > 1: print("Warning: multiple image shapes detected - consider resizing.") ``` ## Validate inputs ```python import os def validate_file_df(file_df) -> bool: assert "file_path" in file_df.columns, "Missing 'file_path' column" missing = [p for p in file_df["file_path"] if not os.path.exists(p)] if missing: print(f"Warning: {len(missing)} files not found") print(f"Examples: {missing[:3]}") return not missing file_df = pheno.find_files("path/to/images") if validate_file_df(file_df): pheno.process_images(wrapper) ``` ## Filtering patterns ### Filter returns empty results **Symptom**: no images after filtering. ```python print("Available keys:", pheno.get_available_metadata_keys()) df = pheno.export_dataset_table() print("Unique conditions:", df["condition"].unique()) dist_df = pheno.compute_reference_distances( reference_filters={"condition": "Control"}, ) ``` Common causes: - Case sensitivity - `"control"` vs `"Control"`. - Whitespace in CSV values. - Column absent from the final `file_df` (check your metadata function). ### Reduce compute by filtering ```python pheno.process_images( wrapper, filters={"condition": ["Control", "Drug1"]}, batch_size=32, ) ``` ## Missing properties **Symptom**: some images have `NaN` properties (the framework warns automatically when NaNs are present). Diagnose: ```python df = pheno.compute_properties(property_preset="basic") nan_counts = df.select_dtypes(include=["number"]).isna().sum() print("Properties with NaNs:", nan_counts[nan_counts > 0].to_dict()) for idx in df[df["area"].isna()].index[:5]: print(pheno.get_image_info(idx)["image_path"]) ``` Fixes: - Confirm masks exist for all images (or switch preset to `intensity`). - Verify property `requirements` match available data; see [Select properties](/PhenoMe/guides/select-properties/). ## See also - [Data setup](/PhenoMe/guides/data-setup/) - folder → metadata mapping. - [Experiment details](/PhenoMe/guides/experiment-details/) - path templates, CSV lookup, mask discovery. - [Property interpretation](/PhenoMe/concepts/property-interpretation/) - which preset requires which inputs. # Performance > GPU sizing, batch size, memory management, and fixes for slow or out-of-memory runs. ## GPU sizing ### Adjust batch size ```python pheno.process_images(wrapper, batch_size=16) pheno.process_images(wrapper, batch_size=64) ``` Higher is faster but uses more memory. Drop on OOM. ### Check GPU availability ```python import torch print(f"CUDA available: {torch.cuda.is_available()}") print(f"CUDA device: {torch.cuda.get_device_name(0)}") total = torch.cuda.get_device_properties(0).total_memory / 1e9 print(f"GPU memory: {total:.1f} GB") device = torch.device("cuda" if torch.cuda.is_available() else "cpu") ``` ### Multi-GPU selection ```python device = torch.device("cuda:0") device = torch.device("cuda:1") ``` Pass the same `device` to `load_dinov2_model(...)` and `PhenoMe(...)`. ## Memory management ### Clear GPU memory ```python import gc import torch def clear_gpu_memory() -> None: """Free GPU memory between stages.""" gc.collect() torch.cuda.empty_cache() clear_gpu_memory() ``` ### Process in chunks For very large datasets: ```python file_df = pheno.find_files("path/to/images") chunk_size = 1000 chunks = [file_df[i:i + chunk_size] for i in range(0, len(file_df), chunk_size)] for i, chunk_df in enumerate(chunks): print(f"Processing chunk {i + 1}/{len(chunks)}") pheno.set_file_df(chunk_df) pheno.process_images( wrapper, batch_size=32, append=(i > 0), ) clear_gpu_memory() ``` ### Monitor memory usage ```python import psutil import torch def print_memory_status() -> None: process = psutil.Process() cpu_mem = process.memory_info().rss / 1e9 print(f"CPU memory: {cpu_mem:.2f} GB") if torch.cuda.is_available(): gpu_mem = torch.cuda.memory_allocated() / 1e9 gpu_max = torch.cuda.max_memory_allocated() / 1e9 print(f"GPU memory: {gpu_mem:.2f} GB (max {gpu_max:.2f} GB)") ``` ## Throughput tips ### Choose `num_workers` ```python import os num_workers = min(4, (os.cpu_count() or 2) - 1) pheno.process_images(wrapper, num_workers=num_workers) ``` ### Filter early Do not process what you will not use later. ```python pheno.process_images( wrapper, filters={"condition": ["Control", "Drug1"]}, batch_size=32, ) ``` ### Checkpoint and reload ```python pheno.process_images(wrapper, checkpoint_path="results/run.h5") pheno.save_results("results.h5") pheno.find_files("path/to/images") pheno.load_results("results.h5") ``` ## Troubleshooting ### GPU out of memory **Symptom**: `RuntimeError: CUDA out of memory`. **Fixes (in order):** 1. Reduce `batch_size`. 2. Pick a smaller model (for example `vits14`). 3. `gc.collect(); torch.cuda.empty_cache()` between stages. 4. Process in chunks (see above). ```python pheno.process_images(wrapper, batch_size=8) ``` ### Slow processing **Symptom**: jobs take much longer than expected. **Fixes:** 1. Increase `batch_size` if GPU has headroom. 2. Increase `num_workers`. 3. Use SSD storage. 4. Pre-filter to the subset you actually need. ```python print(f"Device: {wrapper.device}") # should say cuda:X pheno.process_images(wrapper, batch_size=64, num_workers=8) ``` ### DR on GPU runs out of memory Install `pykeops` so t-SNE / UMAP use memory-efficient GPU kernels: ```bash pip install pykeops ``` PCA is already batched through `ExactIncrementalPCA`. ### Wrap processing with fallback ```python try: pheno.process_images(wrapper, batch_size=32) except RuntimeError as e: if "out of memory" in str(e): clear_gpu_memory() pheno.process_images(wrapper, batch_size=16) else: raise ``` ## Logging for long runs ```python import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[logging.FileHandler("processing.log"), logging.StreamHandler()], ) logger = logging.getLogger(__name__) logger.info("Starting processing") try: pheno.process_images(wrapper) logger.info("Processing complete") except Exception: logger.exception("Processing failed") raise ``` ## See also - [Reproducibility](/PhenoMe/guides/best-practices/reproducibility/) - trade-offs with CPU DR backends. - [Data organization](/PhenoMe/guides/best-practices/data-organization/) - avoid re-processing what you do not need. - [External checkpoints](/PhenoMe/guides/external-checkpoints/) - load embeddings computed elsewhere. # Reproducibility > Seeds, deterministic mode, configuration export, and choice of dimensionality-reduction backend. ## Random seeds Pass a seed to the framework: ```python pheno = PhenoMe(seed=42) ``` When a seed is provided, it is used for **all** random operations: - Determinism (PyTorch, NumPy, Python `random`) - Clustering (GMM) - Dimensionality reduction (t-SNE, UMAP) - Mutual information computation - Random sampling in reports When `seed=None` (default), no seeds are set anywhere - runs are non-deterministic. For manual control (for example before data loading), call: ```python from phenome.utils import set_determinism set_determinism(42) ``` This ensures: - Same PCA / t-SNE / UMAP projections (within the same backend). - Reproducible clustering. - Consistent train/test splits. ## Save your configuration The checkpoint (HDF5) already stores configuration and results. When `save_results()` or `checkpoint_path` are used during `process_images()` / `compute_properties()`, the file contains: - **Results**: embeddings, `img_path`, metadata, properties. - **Processing params**: `channel_mode`, `resize_size`, `pad_size`, `force_rgb`, `l2_normalize_channels` (and `batch_size` when set). - **Processing pipeline** (optional `/processing_pipeline`): the ordered printed steps (title and detail) for embeddings and, when run, properties. Stored as `step_01`, `step_02`, … HDF5 groups with attributes — not JSON. Property steps also store `kind=image`, `kind=mask`, or `kind=compute`. Temporal processing is session-only. The HDF5 file is self-contained for processing parameters and data. For full reproducibility, also call `export_experiment_config()` after the analysis you want to document. The pipeline records method parameters silently (model name from `load_dinov2_model`, property presets, clustering, `reference_filters` from `compute_reference_distances`, and so on). That call writes JSON; `save_results()` still writes only the HDF5 file. ```python pheno.save_results("results.h5") pheno.export_experiment_config("results/config.json") ``` Optional overrides (`model_name=`, `reference_filters=`) still work if you want to set values that were not captured from a method call. To add a Methods / settings section to the HTML report (off by default): ```python pheno.generate_report("results/report.html", include_run_settings=True) ``` When loading: call `find_files` first, then `pheno.load_results("results.h5")`. The framework restores configuration, the processing pipeline (base and properties), and resolves paths automatically. Inspect it with `pheno.print_processing_pipeline()` or `pheno.get_processing_pipeline()`, or watch INFO logs during `process_images` / `load_results`. Warnings mean “suspicious but processable”; they never change pixels or parameters. Custom `preprocessing_fn` / `custom_transformations` appear by name (and optional description) only — PhenoMe does not inspect their source. Pass `preprocessing_description` / `transformations_description` when you want that optional note in the INFO processing pipeline log: ```python pheno.process_images( wrapper, preprocessing_fn=enhance_contrast, preprocessing_description="clip 1st/99th percentiles, then rescale to 0-1", ) ``` If embeddings and properties are in separate files, use `pheno.load_embeddings(...)` then `pheno.load_properties(...)` instead. ## Version control Track your analysis code: - Use git for scripts. - Record library versions. - Save the environment specification. ```bash pip freeze > requirements-frozen.txt ``` ## Save intermediate results The HDF5 file from `save_results()` or checkpointing stores embeddings, metadata, properties, processing params, and the base processing pipeline in one place - no sibling config or pipeline files. ```python pheno.save_results("results.h5") df = pheno.compute_properties(property_preset="basic") df.to_csv("results/properties.csv", index=False) dist_df = pheno.compute_reference_distances( reference_filters={"condition": "Control"} ) dist_df.to_csv("results/distances.csv") ``` ## Dimensionality-reduction backends :::caution PCA, t-SNE, and UMAP results may differ slightly between the **TorchDR + GPU** backend and the **sklearn / umap-learn + CPU** backend. For strict reproducibility across machines or over time, use one backend consistently. ::: ### Why results can differ | Reason | Explanation | |--------|-------------| | **Different implementations** | TorchDR and sklearn / umap-learn use different algorithms and optimisations. | | **Numerical precision** | GPU floating-point accumulation order and parallel reductions can diverge slightly from CPU. | | **Random initialisation** | Even with the same seed, libraries may apply it differently during optimisation. | | **Approximations** | GPU backends may use approximate k-NN (e.g. FAISS, KeOps); CPU backends may use exact computations. | ### Recommendations - **Reproducibility**: `use_gpu_for_dr=False` to always use sklearn / umap-learn; document the chosen backend and seed. - **Speed**: `use_gpu_for_dr=True` when available - qualitative structure (clusters, trends) is typically preserved even when coordinates differ slightly. - **Large datasets**: install `pykeops` to avoid GPU OOM with t-SNE and UMAP; PCA uses a batched `ExactIncrementalPCA` by default. ### `n_components` support | Method | `n_components` | Notes | |--------|----------------|-------| | **PCA** | Any | No restrictions. | | **UMAP** | Any | No restrictions. | | **t-SNE** | Any | sklearn (CPU): `n_components >= 4` falls back to `method='exact'` (slower, O(n^2)); otherwise Barnes-Hut. TorchDR (GPU) supports any `n_components` natively. | ## Handling non-determinism that remains Some non-determinism is intrinsic to the hardware stack (CUDA atomic additions, concurrent reductions). If a bit-exact match matters: 1. Pin PyTorch, CUDA, and the DR libraries to explicit versions. 2. Set `torch.use_deterministic_algorithms(True)` (done by `set_determinism`) and verify no op raises. 3. Prefer the CPU DR backend. 4. Run on the same machine you report from. ## See also - [Performance](/PhenoMe/guides/best-practices/performance/) - when to trade a little reproducibility for speed. - [Collaboration](/PhenoMe/guides/best-practices/collaboration/) - sharing a fully reproducible bundle. - [HDF5 protocol](../../../advanced/database_protocol/) - what the checkpoint contains. # Custom Model Wrappers > Learn how to use non-PyTorch models (TensorFlow, Keras, ONNX) with PhenoMe. While PhenoMe is built on PyTorch, its modular architecture allows you to use models from any machine learning framework. This is achieved through the `ModelWrapper` interface. :::tip[Tutorial Reference] For a hands-on walkthrough of custom models and advanced preprocessing, see **[Tutorial 06: Custom Models and Preprocessing](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/06_custom_model_and_preprocessing.ipynb)**. ::: --- ## The ModelWrapper Contract To use a custom model, you must create a subclass of `ModelWrapper` and implement the `_get_embeddings` method. The contract is simple: 1. **Input**: A `torch.Tensor` of shape `(B, C, H, W)` on the target device. 2. **Output**: A `torch.Tensor` of shape `(B, D)` where `D` is the embedding dimension. ### Preprocessing with PyTorch Even if you are using a non-PyTorch model (like Keras), you can still leverage PyTorch's high-performance transformations. You can provide a `custom_transformations` object (e.g., `torchvision.transforms.Compose`) to `process_images()`. These transformations run on the GPU (if available) before the tensor is passed to your wrapper's `_get_embeddings` method. For non-PyTorch models, you will typically use **numpy** as a bridge between frameworks inside the wrapper. --- ## Example: Keras MobileNetV2 This example shows a fully functional implementation of a `ModelWrapper` for a Keras model, including the necessary data transformations and pipeline integration. ```python import numpy as np import torch import tensorflow as tf from phenome import PhenoMe from phenome.utils.model_wrapper import ModelWrapper class KerasModelWrapper(ModelWrapper): """Wrapper for Keras/TensorFlow models.""" def __init__(self, model, device=None): # Initialize the base class super().__init__(model, device=device) # Why pass 'device'? # 1. Base class stores it in self.device. # 2. Even if Keras runs on GPU, the final output must be # a PyTorch tensor on the device PhenoMe expects. # 3. We use self.device in _get_embeddings to move the # result back to the correct PyTorch device. def _get_embeddings(self, tensor: torch.Tensor) -> torch.Tensor: # 1. Convert PyTorch tensor (B, C, H, W) to NumPy x_np = tensor.detach().cpu().numpy() # 2. Transpose to Keras format (B, H, W, C) x_np = np.transpose(x_np, (0, 2, 3, 1)) # 3. Run Keras inference # .predict() returns a NumPy array of shape (B, D) embeddings_np = self.model.predict(x_np, verbose=0) # 4. Convert back to PyTorch tensor on the correct device return torch.from_numpy(embeddings_np).to(self.device) # --- Usage in Pipeline --- # 1. Load a pre-trained Keras model keras_model = tf.keras.applications.MobileNetV2( input_shape=(224, 224, 3), include_top=False, pooling='avg' ) # 2. Wrap the model wrapper = KerasModelWrapper(keras_model) # 3. Initialize PhenoMe and process images pm = PhenoMe() pm.find_files("path/to/images") # The resize_size should match the model's expected input pm.process_images(wrapper, resize_size=224) # 4. Access results print(f"Extracted {len(pm.results.embeddings)} embeddings") ``` --- ## Common Pitfalls ### Channel Ordering PyTorch uses `(Channels, Height, Width)`, while TensorFlow/Keras typically uses `(Height, Width, Channels)`. Always remember to transpose your arrays using `np.transpose(arr, (0, 2, 3, 1))` before passing them to a non-PyTorch model. ### Device Management Non-PyTorch frameworks handle GPUs differently. - **TensorFlow/Keras**: Usually manages devices globally via `tf.config.list_physical_devices()`. - **ONNX Runtime**: Uses "Execution Providers" (e.g., `CUDAExecutionProvider`). Ensure your framework is using the same physical device as PhenoMe to avoid unnecessary data transfers or memory conflicts. ### Normalization PhenoMe's default transformations might not match what your model expects. You can provide a custom `preprocessing_fn` to `process_images()` or handle normalization inside your wrapper's `_get_embeddings` method. Custom callables are treated as opaque: the processing pipeline log records their name. PhenoMe does not read their source. Pass an optional `preprocessing_description` / `transformations_description` so the INFO log (and `get_processing_pipeline()`) can say what the callable does: ```python def enhance_contrast(image): p1, p99 = np.percentile(image, (1, 99)) return np.clip(image, p1, p99) pm.process_images( wrapper, resize_size=224, preprocessing_fn=enhance_contrast, preprocessing_description="clip 1st/99th percentiles", # custom_transformations=my_transforms, # optional # transformations_description="Resize 224 then ImageNet normalize", ) ``` The description is optional. If you omit it, the log still shows the function name with internals unknown. Check INFO logs, `print_processing_pipeline()`, or `get_processing_pipeline()` if you need to confirm that default ImageNet normalization is still applied after your code. --- ## See Also | Topic | Document | |-------|----------| | ModelWrapper API | [API Reference](/PhenoMe/advanced/api/model-wrapper/) | | Image Processing | [Pipeline API](/PhenoMe/advanced/api/pipeline/) | | Custom Plugins | [Plugins Guide](/PhenoMe/guides/plugins/) | # Preparing your data > Organize images, masks, and labels so plots and reports are meaningful. import { FileTree, Aside, LinkCard, CardGrid } from '@astrojs/starlight/components'; A short guide on how PhenoMe expects images, masks, and **metadata** (experiment labels) to be organized. ## 1. Do I need to add labels (metadata)? | You need... | How to set up | |-------------|---------------| | Just visual fingerprints / properties per file | A simple folder is enough. | | Plots or reports split by biology (drug, dose, time, etc.) | Add a **folder pattern**, **spreadsheet**, or **custom script**. | ## 2. Common folder layouts ### Single folder - images/ - img001.tif - img002.tif ```python pheno.find_files("images/") ``` Default metadata includes path and filename. Add a template or CSV when you need `condition`, `drug`, etc. ### Grouped by folder - data/ - Control/ - a.tif - Treated/ - b.tif Use a **folder pattern** (see [Adding experiment info — folder patterns](/PhenoMe/guides/experiment-details/path-templates/)): ```python from phenome import get_metadata_from_path metadata_fn = get_metadata_from_path(".../(condition)/(filename).*") pheno.find_files("data", metadata_fn=metadata_fn) ``` ## 3. Using a spreadsheet (CSV) Use this when your labels are in a table. 1. PhenoMe scans your **image files**. 2. Each file is matched to a spreadsheet row (usually by **filename**). 3. **Only the overlap** is kept - files not in the spreadsheet are skipped. Details and options: [Adding experiment info — Using a spreadsheet](/PhenoMe/guides/experiment-details/csv-lookup/). ```python import pandas as pd from phenome import make_dataframe_metadata_fn df = pd.read_csv("metadata.csv") metadata_fn = make_dataframe_metadata_fn(df) pheno.find_files("images/", metadata_fn=metadata_fn) ``` ## 4. Adding masks If you need to measure physical traits like cell shape, add **masks** when calling `find_files` (see [Adding experiment info — Finding masks](/PhenoMe/guides/experiment-details/mask-discovery/)). A typical layout is a parallel mask tree: - project/ - images/ - plate1/ - A01.tif - A02.tif - masks/ - plate1/ - A01.tif - A02.tif ```python pheno.find_files("images", mask_dir="masks") ``` ## 5. Common study designs | Design | Suggested approach | See also | |--------|------------------------------|----------| | Drug screening (plate + compound + dose) | Spreadsheet with `filename`, `compound`, `dose`, etc. | [Example notebooks](https://github.com/AAitorG/PhenoMe/tree/main/Notebooks/examples) | | Time course (images over time) | Folder pattern `(timepoint)/(filename).*` or spreadsheet. | [Workflows](/PhenoMe/workflows/) | | Multiple repeats | Include `replicate` in spreadsheet or folder name. | [Best practices - data organization](/PhenoMe/guides/best-practices/data-organization/) | ## 6. Troubleshooting | Problem | What to check | |---------|---------------| | `plot_pca(color_by='condition')` fails | That name must be a metadata key — run `pheno.get_available_metadata_keys()`. | | Spreadsheet row count != image count | How overlap works - [FAQ](/PhenoMe/faq/#data-and-metadata). | | Filter returns no images | Exact string match and keys - [Best practices - data organization](/PhenoMe/guides/best-practices/data-organization/). | | Masks not found | Check mask folder and naming - [Adding experiment info — Finding masks](/PhenoMe/guides/experiment-details/mask-discovery/). | ## Learn more # Adding experiment info (metadata) > How to describe the context of each image so PhenoMe can group, filter, and compare results. **Metadata** describes the context of your images (drug, concentration, time point, plate, well, replicate). Without metadata, PhenoMe only sees pixels; with metadata, it can group, filter, and compare. PhenoMe can extract metadata automatically from **folder paths** or **CSV files**, or you can write a **custom function**. This section covers each option. ## Quick reference | You have... | Read next | |-------------|-----------| | Folders that tell the story (`drug/time/img.tif`) | [Using folder names](/PhenoMe/guides/experiment-details/path-templates/) | | A CSV or spreadsheet of labels | [Using a CSV or spreadsheet](/PhenoMe/guides/experiment-details/csv-lookup/) | | Masks in a separate folder | [Finding masks](/PhenoMe/guides/experiment-details/mask-discovery/) | | Regex, nested directories, or object-oriented configuration | [Advanced patterns](/PhenoMe/guides/experiment-details/advanced-patterns/) | ## Why this info matters - **Filtering**: "show me only images treated with Drug A". - **Grouping**: "colour the plot by drug concentration". - **Comparing**: "measure how far the Drug A group is from the Control group". ## Quick example ```python from phenome import PhenoMe, get_metadata_from_path pheno = PhenoMe() metadata_fn = get_metadata_from_path(".../(condition)/(filename).*") pheno.find_files("data", metadata_fn=metadata_fn) print(pheno.get_available_metadata_keys()) ``` ## Best practices (applies to every pattern) ### Always include `file_path` Every metadata function must return a dict containing at least `file_path`: ```python return {"file_path": path, "condition": "Control"} ``` ### Handle messy filenames Be defensive when parsing messy filenames: ```python def robust_metadata_fn(path: str) -> dict: try: parts = path.split("/") return {"file_path": path, "condition": parts[-2] if len(parts) >= 2 else "Unknown"} except Exception: return {"file_path": path, "condition": "ParseError"} ``` ### Use consistent key names Choose `condition` or `treatment`, `concentration` or `conc`, and stick with it across all your experiments. Downstream filtering is **case-sensitive**. ### Check before starting Test the function on a handful of paths first: ```python for path in ["/data/Control/image_001.tif", "/data/Drug1_10uM/image_001.tif"]: print(path, "->", my_metadata_fn(path)) ``` ## Troubleshooting | Symptom | Fix | |---------|-----| | Some images have empty metadata fields | Add logging in your function; confirm filenames match the pattern. | | `filters={'drug': ['Control']}` returns nothing | Check `pheno.get_available_metadata_keys()` and ensure case matches. | | Grouping shows unexpected duplicates | Strip / lowercase string values in your function. | ## See also | Topic | Where | |-------|-------| | Extension points overview | [Extending PhenoMe](/PhenoMe/guides/extending/) | | `find_files` API | [Pipeline - find_files](/PhenoMe/advanced/api/pipeline/#api-phenome-find_files) | | Metadata classes (OOP) | [Metadata classes](/PhenoMe/advanced/api/metadata/) | | Properties and grouping | [Select properties](/PhenoMe/guides/select-properties/) | | End-to-end examples | [Workflows](/PhenoMe/workflows/) | | Plugins and discovery | [Plugins](/PhenoMe/guides/plugins/) | | Reproducibility and data organisation | [Best practices](/PhenoMe/guides/best-practices/) | # Advanced metadata patterns > Regex parsers, nested directory trees, flexible fallbacks, computed fields, and MetadataBase classes. import { FileTree } from '@astrojs/starlight/components'; When [path templates](/PhenoMe/guides/experiment-details/path-templates/) and [CSV lookup](/PhenoMe/guides/experiment-details/csv-lookup/) are not enough, write a plain Python function (or a `MetadataBase` subclass) that returns a dict per image. ## Encoding in the filename - images/ - Control_30min_rep1.tif - Control_30min_rep2.tif - Drug1_30min_rep1.tif - Drug1_60min_rep1.tif ```python import os def metadata_from_filename(path: str) -> dict: filename = os.path.basename(path).replace(".tif", "") condition, time, replicate = filename.split("_") return { "file_path": path, "condition": condition, "time": time, "replicate": replicate, } ``` ## Nested directory structure - experiment/ - Plate1/ - A01/ - Control_001.tif - Control_002.tif - A02/ - Drug1_10uM_001.tif - Drug1_10uM_002.tif - Plate2/ - ... ```python import os def metadata_from_nested(path: str) -> dict: parts = path.split(os.sep) filename = parts[-1].replace(".tif", "") name_parts = filename.split("_") return { "file_path": path, "plate": parts[-3], "well": parts[-2], "condition": name_parts[0], "concentration": name_parts[1] if len(name_parts) > 2 else "N/A", "image_num": name_parts[-1], } ``` ## Regular expressions For complex naming patterns: ```python import os import re def metadata_regex(path: str) -> dict: filename = os.path.basename(path) match = re.match(r"(\w+)_(\d+)uM_(\d+)min_rep(\d+)\.tif", filename) if match: drug, conc, time, rep = match.groups() return { "file_path": path, "drug": drug, "concentration_uM": int(conc), "time_min": int(time), "replicate": int(rep), } return { "file_path": path, "drug": "Unknown", "concentration_uM": 0, "time_min": 0, "replicate": 0, } ``` ## Flexible fallback for inconsistent naming ```python def metadata_flexible(path: str) -> dict: filename = path.rsplit("/", 1)[-1] base_meta: dict = {"file_path": path} if "_" in filename: parts = filename.replace(".tif", "").split("_") if len(parts) >= 3: base_meta.update(condition=parts[0], time=parts[1], replicate=parts[2]) elif len(parts) == 2: base_meta.update(condition=parts[0], replicate=parts[1]) else: base_meta["condition"] = path.split("/")[-2] return base_meta ``` ## Computed fields Derive extra metadata from parsed values (for sorting, filtering, or plotting): ```python def metadata_with_computed(path: str) -> dict: parts = path.split("/") filename = parts[-1] name_parts = filename.replace(".tif", "").split("_") condition = name_parts[0] time_str = name_parts[1] if len(name_parts) > 1 else "0min" time_numeric = int(time_str.replace("min", "").replace("h", "")) return { "file_path": path, "condition": condition, "time": time_str, "time_numeric": time_numeric, "is_control": condition.lower() == "control", "source_folder": parts[-2], } ``` ## Object-oriented metadata (`MetadataBase`) For advanced use (unique IDs for checkpoint matching, centralised configuration, automatic mask resolution), the framework provides `MetadataBase` classes: `DefaultMetadata`, `PathTemplateMetadata`, and `DataFrameMetadata`. These wrap the helpers above. Example: `DataFrameMetadata` configures filename columns, mask directory, and mask filename column in one place. Pass an instance to `find_files(metadata_fn=...)`. For full class documentation, parameters, and examples, see [Metadata classes API](../../../advanced/api/metadata/). ## See also - [Path templates](/PhenoMe/guides/experiment-details/path-templates/) and [CSV lookup](/PhenoMe/guides/experiment-details/csv-lookup/) - the simpler cases. - [Mask discovery](/PhenoMe/guides/experiment-details/mask-discovery/) - masks and `mask_filename_column`. - [Plugins](/PhenoMe/guides/plugins/) - register a custom metadata extractor under a name. # Using a CSV or spreadsheet > Connect image files to labels stored in a CSV or spreadsheet. ## How it works `pheno.find_files` first scans your image folder, then looks each file up in your CSV. **Only the overlap is used:** - Files must appear **both** in the folder scan and in the CSV to be processed. - CSV rows without a matching image are ignored. - Images without a matching CSV row are skipped (`on_missing_metadata="drop"`). To keep images that fail lookup (with empty labels), pass `on_missing_metadata="keep"` to `find_files`. ## Example CSV format ```csv filename,drug,concentration_uM,time img_01,Control,0,24h img_02,DrugA,10,24h ``` Values with or without extension are both accepted; only the **last** dot is treated as the extension, so names like `plate.A01.well.tif` are matched correctly. ## Recommended: `make_dataframe_metadata_fn` ```python from phenome import make_dataframe_metadata_fn import pandas as pd df = pd.read_csv("metadata.csv") metadata_fn = make_dataframe_metadata_fn( df, filename_column="filename", # default ) file_df = pheno.find_files("images/", metadata_fn=metadata_fn) ``` ## Multiple files per image (multi-channel) When each channel is stored in a different file, pass a **list** of column names in channel order. The dataframe must have one row per sample and one column per channel with the filename (with or without extension) for that channel. ```csv ch0,ch1,ch2,condition,time sample1_c0,sample1_c1,sample1_c2,Control,24h sample2_c0,sample2_c1,sample2_c2,Treatment,48h ``` ```python from phenome import make_dataframe_metadata_fn meta_df = pd.read_csv("metadata_channels.csv") metadata_fn = make_dataframe_metadata_fn( meta_df, filename_column=["ch0", "ch1", "ch2"], ) file_df = pheno.find_files("images/", metadata_fn=metadata_fn) ``` `find_files` groups discovered files by sample and sets `file_path` to a list of paths in channel order. ## Custom lookup logic If you need different keys or custom logic (for example `image_id` instead of `filename`), write your own lookup: ```python import os import pandas as pd METADATA_CSV = pd.read_csv("experiment_metadata.csv") METADATA_LOOKUP = METADATA_CSV.set_index("filename").to_dict("index") def metadata_from_csv(path: str) -> dict: filename = os.path.basename(path) if filename in METADATA_LOOKUP: meta = METADATA_LOOKUP[filename].copy() meta["file_path"] = path return meta return { "file_path": path, "drug": "Unknown", "concentration_uM": 0, "time": "Unknown", "replicate": 0, } ``` ## See also - [Notebook 02: Loading & Inspection](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/02_inspect_your_images.ipynb) - runnable example of CSV lookup. - [Using folder names](/PhenoMe/guides/experiment-details/path-templates/) - skip the CSV if folders already encode the info. - [Finding masks](/PhenoMe/guides/experiment-details/mask-discovery/) - attach segmentation masks via the same CSV. - [Advanced patterns](/PhenoMe/guides/experiment-details/advanced-patterns/) - `MetadataBase` objects that centralise filename, mask, and key configuration. # Finding masks > Automatically match masks to your images to measure physical traits like shape and size. When you have segmentation masks, `pheno.find_files` can discover and attach mask paths automatically via the `mask_dir` parameter. The returned DataFrame includes a `mask_path` column used by `compute_properties` for mask-based features. ## Default behaviour (mirrored folder structure) Masks are matched by the same relative path and filename under `mask_dir`. For example: | Image | Auto-matched mask | |-------|-------------------| | `images/plate1/A01.tif` | `masks/plate1/A01.tif` | | `images/plate1/A01.tif` | `masks/plate1/A01.png` | ```python df = pheno.find_files("/data/images", mask_dir="/data/masks") ``` Restrict the extensions tried: ```python df = pheno.find_files( "/data/images", mask_dir="/data/masks", mask_extensions=[".png", ".tif", ".tiff"], ) ``` ## Custom mask filename in metadata When your metadata (for example from a CSV) contains a column with the mask filename, point to it: ```python from phenome import make_dataframe_metadata_fn metadata_fn = make_dataframe_metadata_fn(meta_df, filename_column="filename") df = pheno.find_files( "/data/images", mask_dir="/data/masks", mask_filename_column="mask_file", metadata_fn=metadata_fn, ) ``` ## Centralising mask configuration When you use `MetadataBase` (for example `DataFrameMetadata`) with `mask_dir` and `mask_filename_column` set, those values are used automatically if you omit them from `pheno.find_files` - see [Advanced patterns](/PhenoMe/guides/experiment-details/advanced-patterns/#object-oriented-metadata-metadatabase). ## Verify mask alignment visually After calling `find_files` with `mask_dir`, use `plot_image_by_index` with `show_mask_overlay=True` to confirm that each mask loads correctly and overlaps the expected region of its image: ```python pheno.plot_image_by_index(0, show_mask_overlay=True) ``` A semi-transparent yellow overlay with a crisp contour appears on top of the image wherever the mask is non-zero. If the overlay is missing, shifted, or covers the wrong area, check your folder layout and mask filenames against the [default behaviour](#default-behaviour-mirrored-folder-structure) rules above. :::tip This is the fastest way to catch alignment problems before running `compute_properties`, where a misaligned mask would silently produce incorrect shape and intensity measurements. ::: ## See also - [Understanding properties](/PhenoMe/concepts/property-interpretation/) - which physical traits depend on masks. - [Preparing your data](/PhenoMe/guides/data-setup/) - file-layout guidance. # Using folder names > Identify images and add labels based on your folder structure. import { FileTree } from '@astrojs/starlight/components'; When folders encode your experiment (for example `/data/DrugA/24h/img01.tif`), you can use a **folder pattern** to tell PhenoMe what each folder represents. ## Simplest case: folders by treatment - data/ - Control/ - img_01.tif - img_02.tif - Drug1/ - img_01.tif - img_02.tif ```python from phenome import get_metadata_from_path metadata_fn = get_metadata_from_path(".../(condition)/(filename).*") pheno.find_files("data", metadata_fn=metadata_fn) ``` ## Pattern rules A pattern matches a file path from right to left. You choose the names of the tokens in parentheses—they become the column names in your results. | Token | Meaning | |-------|---------| | `...` | Matches any parent folders you do not care about. | | `(name)` | Captures a path segment into the `name` metadata field. | | `.*` | Matches any file extension. | For example, `(drug)` or `(condition)` are common choices. Avoid spaces inside the parentheses and keep names consistent across your experiments, as downstream filters are case-sensitive. ## Multiple levels For a folder like `/data/Plate1/Drug1/24h/img_001.tif`: ```python metadata_fn = get_metadata_from_path( ".../(plate)/(drug)/(time)/(filename).*" ) ``` ## Common mistakes | Mistake | Why it fails | Correct version | |---------|--------------|-----------------| | `(drug)/(filename)` | Missing `.../` to match parents. | `.../(drug)/(filename).*` | | `.../(drug)/img.tif` | Hardcoded filename; matches one file. | `.../(drug)/(filename).*` | | `(drug)/(drug)/(filename)` | Duplicate key `drug`. | `(plate)/(drug)/(filename).*` | ## When folder patterns are not enough Switch to a custom function (see [Advanced patterns](/PhenoMe/guides/experiment-details/advanced-patterns/)) when: - Paths mix encodings (some fields in folders, others in filenames). - You need to cast values (`int`, `float`, stripping units). - You need derived fields (for example `is_control`). ## See also - [Notebook 02: Loading & Inspection](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/02_inspect_your_images.ipynb) - runnable example of using folder patterns. - [Using a CSV or spreadsheet](/PhenoMe/guides/experiment-details/csv-lookup/) - link labels stored in a spreadsheet. - [Advanced patterns](/PhenoMe/guides/experiment-details/advanced-patterns/) - regex, nested structures, `MetadataBase`. # Extending PhenoMe > The four places to plug in - metadata, properties, vision models, and plugin registries. import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components'; Most users start by extracting metadata from paths or CSVs, or by adding a domain-specific property function. Pick the extension point that matches what you need. ## Extension map Your file paths or layout differ from the defaults; extract `condition`, `drug`, `timepoint` from paths or a CSV. - [Experiment details](/PhenoMe/guides/experiment-details/) - overview. - [Path templates](/PhenoMe/guides/experiment-details/path-templates/) - folder-based parsing. - [CSV lookup](/PhenoMe/guides/experiment-details/csv-lookup/). - [Advanced patterns](/PhenoMe/guides/experiment-details/advanced-patterns/) - regex, `MetadataBase`. Add domain-specific features (morphology, intensity, texture) from images and masks. - [Select properties — Presets](/PhenoMe/guides/select-properties/presets/) - built-in factories. - [Writing property functions](/PhenoMe/guides/select-properties/writing-functions/). - [Advanced recipes](/PhenoMe/guides/select-properties/advanced-recipes/) - GLCM, OpenCV, multi-channel. Use a different architecture (for example ResNet or custom transformers) or load ONNX / TensorFlow models natively. - [Vision model wrappers](/PhenoMe/advanced/api/model-wrapper/) - subclass `ModelWrapper`. - [`load_dinov2_model`](/PhenoMe/advanced/api/pipeline/) - reference implementation. Register named property functions, metadata extractors, or report sections so other code can discover them. - [Plugins](/PhenoMe/guides/plugins/). - `register_property`, `register_metadata_extractor`, `register_report_section`. ## Rule of thumb Prefer **hooks** before forking internals: 1. A `metadata_fn` if you need different metadata. 2. A property function if you need different features. 3. A `ModelWrapper` subclass if you need a different backbone. 4. A plugin registration when you want the extension to be discoverable across projects. Only modify `phenome.pipeline` or `phenome.mixins` when none of the above is enough; see [Architecture](/PhenoMe/guides/architecture/) for boundaries. ## See also # External checkpoints > Create and load PhenoMe-compatible HDF5 checkpoints from embeddings or properties computed by an external tool. ## 1. Core requirement: alignment When you call `load_results()`, PhenoMe needs to match each row in your HDF5 file to the image files currently on disk. There are two ways. ### Option A: ID-based (recommended) You provide a unique identifier per image. - **In HDF5**: include `/metadata/id` (one unique string per row). - **In code**: call `pipeline.find_files(image_dir)` first. - **Matching**: the pipeline matches HDF5 IDs to the IDs generated by `find_files`. By default `find_files` uses the filename stem (for example `cell_01` for `cell_01.png`). ### Option B: path-based You provide the literal file path. - **In HDF5**: store absolute paths in `/img_path`, or relative paths plus `_storage_root` root attribute. - **In code**: call `pipeline.set_file_df(df)` with a DataFrame that has a `file_path` column. - **Matching**: the pipeline matches rows where the path strings are identical. :::caution If you use Option A (IDs) and your IDs do not match exactly, the alignment fails silently - you will see 0 images loaded. ::: ## 2. HDF5 file schema Attributes are metadata attached to the root `/` of the file. ### Root attributes :::caution[Mandatory attributes] Every PhenoMe checkpoint must define these four attributes at the root level: | Attribute | Type | Description | |-----------|------|-------------| | `version` | str | Must be `"2.0"`. | | `n_committed` | int | Total number of images. | | `embedding_dim` | int | Dimension of embedding vectors (set to `0` if none). | | `n_committed_props` | int | Number of rows with properties (set to `0` if none). | ::: :::note[Optional attributes] | Attribute | Type | Description | |-----------|------|-------------| | `_storage_root` | str | Base directory for resolving relative paths. | | `is_multichannel` | bool | Set to `True` if using `/img_path_channels`. | ::: ### Datasets and groups - `/img_path` (Dataset, `N` strings) - **mandatory**. The primary image paths. - `/embeddings` (Dataset, `N × D` float32) - optional feature vectors. - `/metadata/` (Group) - optional. - `id` (strings) - **required for ID-based alignment**. - `[any]` (strings / floats) - drug, dose, condition, ... - `/properties/` (Group) - optional. - `[any]` (float32) - cell area, intensity, texture, ... - `/config/` (Group) - optional but recommended when embeddings were produced with PhenoMe-compatible settings. Attributes only (no full paths): | Attribute / dataset | Type | Description | |--------------------|------|-------------| | `channel_mode` | str | `'split'` or `'combined'`. | | `resize_size` | str | Square resize size, or `"none"`. | | `pad_size` | str | Pad size, or `"none"`. | | `force_rgb` | bool | Grayscale expanded to RGB. | | `l2_normalize_channels` | bool | Split mode: L2 each channel before concat. Absent → treated as `True`. | | `channels` | int dataset `(C,)` | Optional channel subset; omit if all channels. | See [HDF5 database protocol](/PhenoMe/advanced/database_protocol/) for the full `/config` contract. ## 3. Full example ```python import os import h5py import numpy as np DATA_DIR = "/path/to/images" N, D = 5, 128 img_paths = [f"image_{i}.png" for i in range(N)] img_ids = [f"image_{i}" for i in range(N)] embeddings = np.random.randn(N, D).astype("float32") drug_names = np.array(["DMSO", "DMSO", "DrugA", "DrugB", "Control"], dtype=object) cell_area = (np.random.rand(N) * 1000).astype("float32") with h5py.File("external_results.h5", "w") as f: f.attrs["version"] = "2.0" f.attrs["n_committed"] = N f.attrs["embedding_dim"] = D f.attrs["n_committed_props"] = N f.attrs["_storage_root"] = DATA_DIR f.attrs["is_multichannel"] = False dt_str = h5py.special_dtype(vlen=str) f.create_dataset("img_path", data=np.array(img_paths, dtype=object), dtype=dt_str) if D > 0: f.create_dataset( "embeddings", data=embeddings, compression="gzip", compression_opts=4, ) meta = f.create_group("metadata") meta.create_dataset("id", data=np.array(img_ids, dtype=object), dtype=dt_str) meta.create_dataset("drug", data=np.array(drug_names, dtype=object), dtype=dt_str) props = f.create_group("properties") props.create_dataset("cell_area", data=cell_area) # Optional: record how embeddings were produced (PhenoMe resume / reproducibility) cfg = f.create_group("config") cfg.attrs["channel_mode"] = "split" cfg.attrs["resize_size"] = "224" cfg.attrs["pad_size"] = "none" cfg.attrs["force_rgb"] = True cfg.attrs["l2_normalize_channels"] = True from phenome.pipeline import PhenoMe pipeline = PhenoMe() pipeline.find_files(DATA_DIR) pipeline.load_results("external_results.h5") print(f"Loaded {pipeline.results.n_images} images.") pipeline.plot_tsne(color_by="drug") ``` ### Separate embeddings and properties files When embeddings and properties live in two checkpoints (common after `process_images(..., checkpoint_path="..._embeddings.h5")` and `compute_properties(..., checkpoint_path="..._properties.h5")`), load them in sequence: ```python pipeline = PhenoMe() pipeline.find_files(DATA_DIR) pipeline.load_embeddings("run_embeddings.h5") pipeline.load_properties("run_properties.h5") ``` `load_properties` overlays properties onto the rows established by `load_embeddings` and keeps the embeddings file open for lazy `get_embeddings()` access. ### Verify the file Quick sanity check before you open it in PhenoMe: ```python import h5py with h5py.File("external_results.h5", "r") as f: print("version :", f.attrs["version"]) print("n_committed :", f.attrs["n_committed"]) print("embedding_dim :", f.attrs["embedding_dim"]) print("n_committed_props :", f.attrs["n_committed_props"]) print("keys :", list(f.keys())) if "config" in f: print("config attrs :", dict(f["config"].attrs)) ``` ## 4. Common variants ### Properties only (no embeddings) Use this pattern if you only want PhenoMe to visualise properties (for example from CellProfiler): ```python with h5py.File("props_only.h5", "w") as f: f.attrs["version"] = "2.0" f.attrs["n_committed"] = N f.attrs["embedding_dim"] = 0 f.attrs["n_committed_props"] = N # ... create img_path and /metadata/id ... # ... create /properties/ datasets ... ``` ### Path-only alignment (no IDs) If you already have absolute paths: ```python import pandas as pd file_df = pd.DataFrame( {"file_path": ["/abs/path/img1.png", "/abs/path/img2.png"]} ) pipeline.set_file_df(file_df) pipeline.load_results("abs_paths.h5") ``` ## 5. Troubleshooting | Symptom | Probable cause | Solution | |---------|----------------|----------| | 0 images loaded | IDs in HDF5 do not match `find_files` IDs. | Check `/metadata/id` - by default, filename without extension. | | Images not found | `_storage_root` + relative paths are incorrect. | Print absolute paths in Python and compare to HDF5 `/img_path`. | | No PCA / UMAP plots | `embedding_dim` is 0 or dataset missing. | Ensure `embedding_dim > 0` and the `/embeddings` dataset exists. | | Runtime / type error | Incorrect dtype. | Paths and string metadata must be `vlen str`; properties must be `float32`. | ## See also - [HDF5 protocol](/PhenoMe/advanced/database_protocol/) - full structure and version 2.0 guarantees. - [Architecture](/PhenoMe/guides/architecture/) - where checkpoint I/O lives in the code. - [Best practices - reproducibility](/PhenoMe/guides/best-practices/reproducibility/) - what the checkpoint stores. # Plugins and Extensibility The phenotyping pipeline supports optional plugin registries for metadata extractors, report sections, and property functions. --- ## Table of Contents 1. [Property Functions](#property-functions) 2. [Metadata Extractors](#metadata-extractors) 3. [Report Sections](#report-sections) --- ## Property Functions Register custom property functions for use with `compute_properties`. Property functions have signature `(image2d, mask2d) -> Dict[str, float]`. ### Registering a Property ```python from phenome.plugins import register_property def my_blob_counter(image2d, mask2d): """Count blobs in image.""" if image2d is None: return {"my_blob_count": float("nan")} # ... your logic ... return {"my_blob_count": 42.0} register_property("my_blobs", my_blob_counter) ``` ### Using a Registered Property ```python from phenome.plugins import get_property, get_blob_properties df = pheno.compute_properties( property_preset="none", additional_property_functions={"image": [get_blob_properties]} ) blob_fn = get_property("blob") if blob_fn: df = pheno.compute_properties( property_preset="none", additional_property_functions={"image": [blob_fn]} ) ``` ### Built-in Properties | Name | Function | Description | |------|----------|-------------| | `blob` | `get_blob_properties` | DoG blob detection (count, area, intensity stats) | --- ## Metadata Extractors Register metadata extractors for use with `find_files(metadata_fn=...)` or for discovery. ### Registering an Extractor ```python from phenome.plugins import register_metadata_extractor import re def extract_from_path_template(path: str): """Extract metadata from path like /data/plate1/well_A01.tif""" match = re.search(r"plate(\d+)/well_([A-Z]\d+)", path) if match: return {"file_path": path, "plate": match.group(1), "well": match.group(2)} return {"file_path": path} register_metadata_extractor("plate_well", extract_from_path_template) ``` ### Using a Registered Extractor ```python from phenome.plugins import get_metadata_extractor extractor = get_metadata_extractor("plate_well") if extractor: df = pheno.find_files("/data/images", metadata_fn=extractor) ``` --- ## Report Sections Register custom HTML section generators for inclusion in `generate_report()`. ### Registering a Section ```python from phenome.plugins import register_report_section def my_custom_section(pipeline, title: str = "Custom Analysis") -> str: """Generate HTML for a custom report section.""" n_images = pipeline.results.n_images return f"""

{title}

Processed {n_images} images.

""" register_report_section("custom_analysis", my_custom_section) ``` **Report integration:** Custom report sections are registered in the plugin registry but are **not** automatically included in `generate_report()` by default. To add them to your reports, you need to extend `generate_report` in your project to iterate over `get_report_sections()` and append the generated HTML to the report body. The registry provides the discovery mechanism; the main report generator does not call it by default. --- ## See Also | Topic | Document | |-------|----------| | Extension points overview | [Extending the Pipeline](/PhenoMe/guides/extending/) | | Metadata extractors | [Experiment Details](/PhenoMe/guides/experiment-details/) | | Property functions | [Select properties](/PhenoMe/guides/select-properties/) | | Report API | [Report Generation](/PhenoMe/advanced/api/report/) | # Select properties > Choose property_preset bundles, combine built-in factories, or add your own property functions. Property functions extract numerical features from images and masks. For what each built-in column *means*, see [Property interpretation](/PhenoMe/concepts/property-interpretation/). For **preset bundles** (`basic`, `standard`, …) and mixing in custom code, stay in this section. Select properties covers: - Quantifying phenotypes (cell size, intensity, shape) via presets or your own functions. - Using classical features as distance sources alongside embeddings. - Domain-specific metrics and correlation with embedding space. ## Decision tree | Situation | Read next | |-----------|-----------| | You want a ready set of features with one parameter | [Presets](/PhenoMe/guides/select-properties/presets/) | | You need a domain-specific function | [Writing functions](/PhenoMe/guides/select-properties/writing-functions/) | | You need GLCM, OpenCV, multi-channel tricks, or troubleshooting | [Advanced recipes](/PhenoMe/guides/select-properties/advanced-recipes/) | ## Minimal example ```python import numpy as np def mean_intensity(image2d, mask2d): if image2d is None: return {} return {"mean_intensity": float(np.mean(image2d))} df = pheno.compute_properties( property_preset="basic", additional_property_functions={"image": [mean_intensity]}, ) ``` ## See also - [Property interpretation](/PhenoMe/concepts/property-interpretation/) - what each built-in property measures. - [Embeddings interpretability](/PhenoMe/concepts/embeddings-interpretability/) - correlate properties with embeddings. - [`compute_properties` API](/PhenoMe/advanced/api/pipeline/#api-phenomeproperties-compute_properties). - [Plugins](/PhenoMe/guides/plugins/) - register a function as a named property. # Advanced recipes > GLCM textures, skimage regionprops, OpenCV, multi-channel handling, troubleshooting. ## Texture (GLCM) ```python import numpy as np def texture_features(image2d, mask2d): if image2d is None: return {} from skimage.feature import graycomatrix, graycoprops img_uint8 = (image2d * 255).astype(np.uint8) glcm = graycomatrix( img_uint8, distances=[1], angles=[0, np.pi / 4, np.pi / 2, 3 * np.pi / 4], levels=256, symmetric=True, normed=True, ) return { "contrast": float(np.mean(graycoprops(glcm, "contrast"))), "dissimilarity": float(np.mean(graycoprops(glcm, "dissimilarity"))), "homogeneity": float(np.mean(graycoprops(glcm, "homogeneity"))), "energy": float(np.mean(graycoprops(glcm, "energy"))), "correlation": float(np.mean(graycoprops(glcm, "correlation"))), } ``` For the built-in factory, see [`create_texture_function`](/PhenoMe/guides/select-properties/presets/#built-in-helper-factories). ## Region properties with scikit-image ```python import numpy as np def regionprops_features(image2d, mask2d): if mask2d is None: return {} from skimage.measure import label, regionprops labelled = label(mask2d > 0) if labelled.max() == 0: return {"n_objects": 0.0, "total_area": 0.0, "mean_area": np.nan} props = regionprops(labelled, intensity_image=image2d) areas = [p.area for p in props] result = { "n_objects": float(len(props)), "total_area": float(sum(areas)), "mean_area": float(np.mean(areas)), "std_area": float(np.std(areas)) if len(areas) > 1 else 0.0, } if image2d is not None: intensities = [p.mean_intensity for p in props] result["mean_object_intensity"] = float(np.mean(intensities)) return result ``` ## Edge energy ```python import numpy as np def edge_features(image2d, mask2d): if image2d is None: return {} from scipy import ndimage sx = ndimage.sobel(image2d, axis=0) sy = ndimage.sobel(image2d, axis=1) edge = np.sqrt(sx ** 2 + sy ** 2) return { "edge_mean": float(np.mean(edge)), "edge_max": float(np.max(edge)), "edge_std": float(np.std(edge)), "edge_energy": float(np.sum(edge ** 2)), } ``` ## OpenCV contours ```python def contour_features(image2d, mask2d): if mask2d is None: return {} import cv2 mask_uint8 = (mask2d > 0).astype("uint8") * 255 contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return {"n_contours": 0.0, "max_contour_area": 0.0} areas = [cv2.contourArea(c) for c in contours] return { "n_contours": float(len(contours)), "max_contour_area": float(max(areas)), "total_contour_area": float(sum(areas)), } ``` ## Multi-channel handling For multi-channel images, property functions are called **once per channel**. The pipeline automatically suffixes property names with `_ch{idx}`. Pass `channel_names` to `find_files` or `set_file_df` to use semantic labels instead (e.g. `_DAPI`, `_GFP`). Single-channel images keep names unsuffixed. ```python def avg_intensity(image2d, mask2d): if image2d is None: return {} return {"avg_intensity": float(np.mean(image2d))} ``` ```python pheno.find_files("images/", channel_names=["DAPI", "GFP"]) # → avg_intensity_DAPI, avg_intensity_GFP after compute_properties ``` ### Normalise per channel Different channels can have very different intensity ranges; normalise when that matters for comparison: ```python def normalised_intensity(image2d, mask2d): if image2d is None: return {} import numpy as np p5, p95 = np.percentile(image2d, [5, 95]) normalised = (image2d - p5) / (p95 - p5 + 1e-8) return { "normalized_mean": float(np.mean(normalised)), "dynamic_range": float(p95 - p5), } ``` ## Troubleshooting ### NaN values in results ```python def safe_mean(arr): if arr.size == 0: return float("nan") import numpy as np return float(np.mean(arr)) def safe_std(arr): import numpy as np if arr.size == 0 or len(arr) < 2: return float("nan") return float(np.std(arr)) ``` ### Function never called Symptom: no properties computed for some images. - Check the requirement type (`image`, `mask`, `both`, `any`) matches the data actually present. - Confirm `mask_dir` is set and masks were discovered (see [Mask discovery](/PhenoMe/guides/experiment-details/mask-discovery/)). ### Memory errors on large images Downsample before expensive operations: ```python def memory_efficient_fn(image2d, mask2d): if image2d is None: return {} from skimage.transform import resize small = resize(image2d, (image2d.shape[0] // 4, image2d.shape[1] // 4)) return {"texture": compute_texture(small)} ``` ## See also - [Writing property functions](/PhenoMe/guides/select-properties/writing-functions/) - signature, testing, best practices. - [Presets](/PhenoMe/guides/select-properties/presets/) - swap code for a one-line preset when possible. - [Plugins](/PhenoMe/guides/plugins/) - ship your function as an importable plugin. # Property presets > Built-in property sets (basic, intensity, standard, ...) and how to extend them without writing code. Presets are named bundles of property functions that cover the most common phenotyping needs. Pass a preset name to `compute_properties` and PhenoMe will compute the corresponding features for every image in the dataset. Start with a preset and add custom functions only when you need something the preset does not cover. ## Choosing a preset | Preset | Requires | Best for | |--------|----------|----------| | `"none"` | — | Custom-only pipelines; pair with `additional_property_functions`. | | `"intensity"` | Image only | No segmentation masks; global illumination/signal QC. | | `"shape"` | Mask only | Shape analysis without intensity information. | | `"basic"` | Image + mask | General-purpose starting point; covers shape and signal in one pass. | | `"standard"` | Image + mask | More complete description; adds axis lengths and radial intensity rings. | | `"complete"` | Image + mask | Maximum feature set; adds texture, QC metrics, and extended shape. | The "Requires" column indicates what the preset needs to run. Presets that require a mask return `NaN` for mask-dependent features if no mask is found for an image. ## Properties per preset ### `"none"` No properties are computed. Use this when you want to supply all functions yourself via `additional_property_functions`. --- ### `"intensity"` Image-level statistics over all pixels (no mask required). | Column | Description | |--------|-------------| | `intensity_mean` | Mean pixel intensity across the whole image. | | `intensity_std` | Standard deviation of pixel intensity. | | `intensity_min` | Minimum pixel intensity. | | `intensity_max` | Maximum pixel intensity. | --- ### `"shape"` Shape descriptors of the segmented object (mask required). | Column | Description | |--------|-------------| | `area` | Foreground pixel count. | | `major_axis_length` | Length of the major axis of the fitted ellipse. | | `minor_axis_length` | Length of the minor axis of the fitted ellipse. | | `perimeter` | Boundary length of the object. | | `eccentricity` | Elongation of the fitted ellipse (0 = circle, 1 = line). | | `solidity` | Ratio of area to convex hull area (1 = fully convex). | --- ### `"basic"` A lightweight but well-rounded set of intensity and shape features. **Image-level** (image required): | Column | Description | |--------|-------------| | `intensity_mean` | Mean pixel intensity across the whole image. | | `intensity_std` | Standard deviation of pixel intensity. | **Shape** (mask required): | Column | Description | |--------|-------------| | `area` | Foreground pixel count. | | `perimeter` | Boundary length of the object. | | `eccentricity` | Elongation of the fitted ellipse (0 = circle, 1 = line). | | `solidity` | Ratio of area to convex hull area (1 = fully convex). | **Masked intensity** (image + mask required): | Column | Description | |--------|-------------| | `intensity_mean_masked` | Mean intensity inside the segmented object. | | `intensity_std_masked` | Intensity standard deviation inside the object. | --- ### `"standard"` Extends `"basic"` with axis lengths, full intensity range, and a 3-ring radial intensity profile. Recommended default for most analyses. **Image-level** (image required): | Column | Description | |--------|-------------| | `intensity_mean` | Mean pixel intensity across the whole image. | | `intensity_std` | Standard deviation of pixel intensity. | | `intensity_min` | Minimum pixel intensity. | | `intensity_max` | Maximum pixel intensity. | **Shape** (mask required): | Column | Description | |--------|-------------| | `area` | Foreground pixel count. | | `major_axis_length` | Length of the major axis of the fitted ellipse. | | `minor_axis_length` | Length of the minor axis of the fitted ellipse. | | `perimeter` | Boundary length of the object. | | `eccentricity` | Elongation of the fitted ellipse (0 = circle, 1 = line). | | `solidity` | Ratio of area to convex hull area (1 = fully convex). | **Masked intensity + radial profile** (image + mask required): | Column | Description | |--------|-------------| | `intensity_mean_masked` | Mean intensity inside the segmented object. | | `intensity_std_masked` | Intensity standard deviation inside the object. | | `ring_1_mean` | Mean intensity in the outermost ring (membrane region). | | `ring_1_std` | Intensity std in the outermost ring. | | `ring_2_mean` | Mean intensity in the middle ring (cytoplasm region). | | `ring_2_std` | Intensity std in the middle ring. | | `ring_3_mean` | Mean intensity in the innermost ring (core/nucleus region). | | `ring_3_std` | Intensity std in the innermost ring. | --- ### `"complete"` The most comprehensive preset. Adds texture (GLCM), QC metrics, extended shape descriptors, and masked min/max on top of `"standard"`. **Image-level** (image required): | Column | Description | |--------|-------------| | `intensity_mean` | Mean pixel intensity across the whole image. | | `intensity_std` | Standard deviation of pixel intensity. | | `intensity_min` | Minimum pixel intensity. | | `intensity_max` | Maximum pixel intensity. | | `sharpness_metric` | Focus quality estimate (0 = sharp, 1 = blurry). | | `intensity_entropy` | Shannon entropy of the intensity distribution. | **Shape** (mask required): | Column | Description | |--------|-------------| | `area` | Foreground pixel count. | | `major_axis_length` | Length of the major axis of the fitted ellipse. | | `minor_axis_length` | Length of the minor axis of the fitted ellipse. | | `perimeter` | Boundary length of the object. | | `eccentricity` | Elongation of the fitted ellipse (0 = circle, 1 = line). | | `solidity` | Ratio of area to convex hull area (1 = fully convex). | | `orientation` | Angle of the major axis in radians. | | `extent` | Ratio of object area to bounding-box area. | | `equivalent_diameter` | Diameter of a circle with the same area as the object. | | `euler_number` | Number of objects minus number of holes (topology). | | `circularity` | 4π · area / perimeter² (1 = perfect circle). | **Masked intensity + radial profile** (image + mask required): | Column | Description | |--------|-------------| | `intensity_mean_masked` | Mean intensity inside the segmented object. | | `intensity_std_masked` | Intensity standard deviation inside the object. | | `intensity_min_masked` | Minimum intensity inside the object. | | `intensity_max_masked` | Maximum intensity inside the object. | | `ring_1_mean` | Mean intensity in the outermost ring (membrane region). | | `ring_1_std` | Intensity std in the outermost ring. | | `ring_2_mean` | Mean intensity in the middle ring (cytoplasm region). | | `ring_2_std` | Intensity std in the middle ring. | | `ring_3_mean` | Mean intensity in the innermost ring (core/nucleus region). | | `ring_3_std` | Intensity std in the innermost ring. | | `texture_contrast` | GLCM contrast (local intensity variation). | | `texture_dissimilarity` | GLCM dissimilarity (weighted distance between intensity pairs). | | `texture_homogeneity` | GLCM homogeneity (closeness of distribution to diagonal). | | `texture_energy` | GLCM energy (uniformity of texture). | | `texture_correlation` | GLCM correlation (linear dependency of grey levels). | :::note For multi-channel images, all per-channel properties are suffixed with `_ch0`, `_ch1`, etc. Pass `channel_names=["DAPI", "GFP"]` to `find_files` (or `set_file_df`) to use those labels instead (e.g. `intensity_mean_DAPI`). Metadata and mask-derived shape columns are not duplicated per channel. ::: ## Usage ```python file_df = pheno.find_files("path/to/images") pheno.process_images(wrapper) # Use a preset df = pheno.compute_properties(property_preset="basic") # Custom-only (no preset) df = pheno.compute_properties( property_preset="none", additional_property_functions={"image": [my_intensity_fn]}, ) # Extend a preset with extra functions df = pheno.compute_properties( property_preset="standard", additional_property_functions={ "both": [create_concentric_ring_function(5, ["mean"])] }, ) ``` ## Extending a preset Inspect the functions a preset would produce, then add to them: ```python from phenome import ( get_preset_property_functions, create_regionprops_function, create_concentric_ring_function, ) # Inspect what "basic" includes print(get_preset_property_functions("basic")) # {'image': [...], 'mask': [...], 'both': [...]} # Add a custom shape property on top of "basic" extra_shape = create_regionprops_function(["aspect_ratio"]) df = pheno.compute_properties( property_preset="basic", additional_property_functions={"mask": [extra_shape]}, ) ``` ## Built-in helper factories These factories create property functions that can be passed directly to `additional_property_functions`. All presets are built from them. | Factory | Output columns | |---------|----------------| | `create_regionprops_function(names)` | One column per name in `names`. Available: `area`, `perimeter`, `major_axis_length`, `minor_axis_length`, `equivalent_diameter`, `eccentricity`, `solidity`, `orientation`, `euler_number`, `extent`, `circularity`, `roundness`, `aspect_ratio`. | | `create_intensity_function(stat, fn)` | `intensity_{stat}` — whole-image statistic. | | `create_masked_intensity_function(stat, fn)` | `intensity_{stat}_masked` — statistic inside the object. | | `create_texture_function()` | `texture_contrast`, `texture_dissimilarity`, `texture_homogeneity`, `texture_energy`, `texture_correlation`. | | `create_blur_effect_function()` | `sharpness_metric` — focus quality (0 = sharp, 1 = blurry). | | `create_entropy_function()` | `intensity_entropy` — Shannon entropy of the intensity distribution. | | `create_concentric_ring_function(N, stats)` | `ring_1_{stat}` … `ring_N_{stat}` — radial intensity profile from boundary to core. | | `phenome.plugins.get_blob_properties` | Blob/cell-level properties from a labelled mask. | Pass the returned callable via `additional_property_functions`, for example: ```python {"mask": [create_regionprops_function(["area", "circularity"])]} ``` ## See also - [Writing property functions](/PhenoMe/guides/select-properties/writing-functions/) — define your own. - [Advanced recipes](/PhenoMe/guides/select-properties/advanced-recipes/) — external libraries, GLCM, multi-channel. - [Property interpretation](/PhenoMe/concepts/property-interpretation/) — what each built-in property measures biologically. # Writing property functions > Signature, requirement types, and basic examples for property functions. ## Function signature Every property function follows this signature: ```python from typing import Optional import numpy as np def my_property_fn( image2d: Optional[np.ndarray], mask2d: Optional[np.ndarray], ) -> dict[str, float]: """Compute properties from image and/or mask. Parameters ---------- image2d : (H, W) float32 array, normalised to [0, 1]. May be None. mask2d : (H, W) array. Typically binary or labelled. May be None. Returns ------- dict[str, float] Named float values. """ return {} ``` ## Requirement types When you pass a function to `compute_properties`, you specify what it needs. The pipeline only calls it when its needs are satisfied: | Type | Image | Mask | When called | |------|-------|------|-------------| | `"image"` | required | ignored | image available | | `"mask"` | ignored | required | mask available | | `"both"` | required | required | both available | | `"any"` | optional | optional | either available | ```python file_df = pheno.find_files("images/", mask_dir="masks/") pheno.process_images(wrapper) df = pheno.compute_properties( property_preset="basic", additional_property_functions={ "image": [intensity_fn], "both": [masked_intensity_fn], }, ) df = pheno.compute_properties( property_preset="none", additional_property_functions={ "image": [intensity_fn], "mask": [shape_fn], "both": [masked_intensity_fn], "any": [flexible_fn], }, ) ``` ## Basic examples ### Intensity stats (image only) ```python def intensity_stats(image2d, mask2d): if image2d is None: return {} return { "mean_intensity": float(np.mean(image2d)), "std_intensity": float(np.std(image2d)), "min_intensity": float(np.min(image2d)), "max_intensity": float(np.max(image2d)), "median_intensity": float(np.median(image2d)), } ``` ### Shape properties (mask only) ```python def shape_props(image2d, mask2d): if mask2d is None: return {} binary = mask2d > 0 area = np.sum(binary) if area == 0: return {"area": 0.0, "perimeter": 0.0} from scipy import ndimage eroded = ndimage.binary_erosion(binary) perimeter = np.sum(binary) - np.sum(eroded) return { "area": float(area), "perimeter": float(perimeter), "circularity": float(4 * np.pi * area / (perimeter ** 2)) if perimeter > 0 else 0.0, } ``` ### Masked intensity (both required) ```python def masked_intensity(image2d, mask2d): if image2d is None or mask2d is None: return {} masked_pixels = image2d[mask2d > 0] if len(masked_pixels) == 0: return {"masked_mean": np.nan, "masked_std": np.nan} return { "masked_mean": float(np.mean(masked_pixels)), "masked_std": float(np.std(masked_pixels)), "masked_total": float(np.sum(masked_pixels)), } ``` ## Best practices ### 1. Always return float values ```python return {"area": float(np.sum(mask))} ``` ### 2. Handle empty / missing data ```python def robust_fn(image2d, mask2d): if image2d is None: return {} if image2d.size == 0: return {"mean": np.nan} if np.std(image2d) == 0: return {"mean": float(np.mean(image2d)), "std": 0.0} return {"mean": float(np.mean(image2d)), "std": float(np.std(image2d))} ``` ### 3. Use descriptive names ```python return { "cell_area_pixels": float(area), "nucleus_mean_intensity": float(nuc_intensity), "cytoplasm_texture_contrast": float(contrast), } ``` ### 4. Document your function ```python def cell_morphology(image2d, mask2d): """Compute cell morphology features. Features -------- area : cell area in pixels perimeter : cell perimeter in pixels circularity : 4*pi*area / perimeter^2 (1.0 for a perfect circle) aspect_ratio : major axis / minor axis of the fitted ellipse Parameters ---------- image2d : Not used (pass None). mask2d : Binary cell mask. Returns ------- dict[str, float] """ ... ``` ### 5. Test locally ```python test_image = np.random.rand(100, 100).astype(np.float32) test_mask = np.zeros((100, 100)) test_mask[30:70, 30:70] = 1 result = my_property_fn(test_image, test_mask) for name, value in result.items(): assert isinstance(value, float), f"{name} is not float" ``` ## See also - [Presets](/PhenoMe/guides/select-properties/presets/) - prebuilt factories you can use instead of rolling your own. - [Advanced recipes](/PhenoMe/guides/select-properties/advanced-recipes/) - GLCM, OpenCV, multi-channel. - [Plugins](/PhenoMe/guides/plugins/) - register a named property function. # PhenoMe > Bring your images, pick any vision model, and turn embeddings into interpretable biology with no training or labels. import { Card, CardGrid, LinkCard, Badge } from '@astrojs/starlight/components'; ## What is PhenoMe? **PhenoMe** is a reproducible phenotyping pipeline built around three ideas: - **Embeddings as the common representation.** Any vision model that can describe images can be plugged in; all downstream analyses work on the resulting digital descriptions (vectors). - **Metadata as experimental context.** Label images with conditions, doses, time points, plates, and wells. - **Classical properties to keep results easy to understand.** Shape, intensity, texture, and quality metrics are computed alongside the embeddings and can be linked back to them to explain what the AI sees. ## How it fits together ![Pipeline Overview](../../assets/pipeline_overview.png) PhenoMe follows a modular 4-step workflow: 1. **Discover** — Find your raw microscopy images and masks. 2. **Embeddings** (A) — Preprocess images and run the vision model to extract **visual fingerprints** (digital descriptions of each image). 3. **Properties** (B) — Extract explainable **classical features** (size, brightness, and texture). 4. **Analyze** (C) — Merge both branches to compare images, visualize patterns (PCA/t-SNE/UMAP), explore interactively, and report. The same four steps underpin every workflow. Everything else (interactive exploration, extension hooks, etc.) plugs into that backbone. ## Choose your path Pick the card that matches you best. Each path ends at the same core idea: install, point at data, run notebooks or code, and understand your results. Learn the basics with almost no code. - [Getting started](/PhenoMe/getting-started/) (install + first script) - [01 Interactive quickstart](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/01_interactive_quickstart.ipynb) - [Learning paths](/PhenoMe/user-paths/) Clean and organize data, then plot results. - [Preparing your data](/PhenoMe/guides/data-setup/) - [02 Load and inspect images](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/02_inspect_your_images.ipynb) - [03 Core phenotyping workflow](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/03_core_phenotyping_workflow.ipynb) Extend or embed PhenoMe into your pipeline. - [Architecture](/PhenoMe/guides/architecture/) - [API & data formats](/PhenoMe/advanced/) - [Developer guide](/PhenoMe/guides/developer-guide/) Deepen your understanding of embeddings and how to explain AI results. - [Core concepts](/PhenoMe/concepts/) - [04 Exploratory analysis and explainability](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/04_exploratory_analysis_and_explainability.ipynb) ## Essential guides and references # Learning paths > Curated routes through the docs and notebooks by audience and goal. import { Steps, Aside, Card, CardGrid } from '@astrojs/starlight/components'; Curated routes through the PhenoMe documentation and notebooks. **Pick one path and follow it in order** - jumping around tends to leave gaps. ## Path A - First day :::note[Goal] Start using PhenoMe today and understand the basic concepts. Target audience: **newcomers** with no prior PhenoMe experience. ::: 1. [Getting started](/PhenoMe/getting-started/) - install, verify `import phenome`, CPU/GPU note. 2. [Notebook 01 - Interactive quickstart](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/01_interactive_quickstart.ipynb) - widgets, toy or your data. 3. [FAQ - installation](/PhenoMe/faq/#installation-and-setup) - common blockers. 4. Optional: [Core concepts](/PhenoMe/concepts/) - understand terms like visual fingerprints, metadata, and results. ## Path B - Your own dataset :::note[Goal] Run a reliable analysis on your own images using experimental info (metadata). Target audience: **scientists / analysts** moving from a toy run to their own data. ::: 1. [Getting started](/PhenoMe/getting-started/) - environment locked in. 2. [Preparing your data](/PhenoMe/guides/data-setup/) - decide if you need labels; folder patterns vs spreadsheets. 3. [Notebook 02 - Load and inspect images](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/02_inspect_your_images.ipynb) - quality checks. 4. [Notebook 03 - Core phenotyping workflow](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/03_core_phenotyping_workflow.ipynb) - embeddings, properties, plots. 5. [Select properties — Presets](/PhenoMe/guides/select-properties/presets/) — choose a `property_preset` (column meanings: [Understanding properties](/PhenoMe/concepts/property-interpretation/)). 6. [Workflows](/PhenoMe/workflows/) or [Example notebooks](https://github.com/AAitorG/PhenoMe/tree/main/Notebooks/examples) - match your study design. ## Path C - Reporting and communication :::note[Goal] Communicate and share your results with collaborators. Target audience: **PIs, reviewers, authors** of a paper or slide deck. ::: 1. Complete **Path B** through notebook 03 (or load saved results). 2. [`PhenoMe.generate_report`](/PhenoMe/advanced/api/report/) - parameters, sections, and output. 3. [FAQ - results](/PhenoMe/faq/#results-and-interpretation). ## Path D - Developer / extension author :::note[Goal] Make safe changes to the codebase and keep API docs up to date. Target audience: **library developers** and people embedding PhenoMe in a larger pipeline. ::: 1. [Architecture](/PhenoMe/guides/architecture/) - packages, data flow, extension points. 2. [API & data formats](/PhenoMe/advanced/). 3. [Developer guide](/PhenoMe/guides/developer-guide/) - docstrings, Astro build, CI. 4. [Testing](/PhenoMe/guides/testing/) - ruff, mypy, interrogate. 5. [Extending PhenoMe](/PhenoMe/guides/extending/), [Plugins](/PhenoMe/guides/plugins/), [Select properties](/PhenoMe/guides/select-properties/). 6. [Notebook 05 - Plugins](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/05_extending_phenome_plugins.ipynb). 7. [Notebook 06 - Custom model and preprocessing](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/06_custom_model_and_preprocessing.ipynb). ## Path E - Interpretability and exploration :::note[Goal] Visualize patterns, find links between features, and explain AI results. Target audience: **scientists looking for biological patterns in their data.** ::: 1. [Notebook 04 - Exploratory analysis](https://github.com/AAitorG/PhenoMe/blob/main/Notebooks/tutorials/04_exploratory_analysis_and_explainability.ipynb). 2. [Explaining AI results](/PhenoMe/concepts/embeddings-interpretability/). 3. [Glossary](/PhenoMe/glossary/) - terminology overview (visual fingerprints, linking AI to biology, etc.). ## Doc vs notebook: where to look | Topic | Read first | Practice in | |-------|------------|-------------| | Install | [Getting started](/PhenoMe/getting-started/) | Notebook 01 (Colab cell if needed) | | Metadata | [Preparing your data](/PhenoMe/guides/data-setup/) | Notebook 02 | | Full pipeline | [Getting started, step 3](/PhenoMe/getting-started/#3-run-your-first-analysis) | Notebook 03 | | Plugins | [Extending](/PhenoMe/guides/extending/) | Notebook 05 | | Custom Models | [Extending](/PhenoMe/guides/extending/) | Notebook 06 |