---------------------------------------------------------------------- This is the API documentation for the omero_screen library. ---------------------------------------------------------------------- ## Pipeline Running a plate through the analysis pipeline. ## Metadata Plate layout, channel names and channel roles. ## Configuration Environment files and logging setup. set_env_vars() -> None Loads environment variables from configuration files or the environment. If the ENV variable is not set, defaults to 'development'. Loads the first ``.env.{ENV}`` (or ``.env``) found, then fills any variable still unset from the site profile and user config (:mod:`omero_screen.settings`). Nothing is required at import: a missing OMERO login is reported when a connection is made. configure_logging(*, level: str | None = None, console: bool = False, log_file: str | None = None, diagnose: bool = False, plugin: bool = False) -> None Configure the global loguru logger for an application entry point. Call this ONCE, explicitly, from an application's ``main()`` (or, for the napari plugin, from the top-level widget — see ``configure_logging_once``). Libraries must never call it. Because loguru's ``logger`` is a single process-global object, this one call governs logging for every module in every package in the process. Production defaults (zero config required): a rotating **file** sink at INFO, **console off** (the Rich progress display / panels are the user channel — a console log sink would corrupt them). Calling it again simply re-applies the configuration (``logger.remove()`` first), so it is safe to call from an idempotent widget hook. Resolution precedence for ``level``/``log_file`` is **argument > env var > default** (env vars ``OMERO_SCREEN_LOG_LEVEL`` / ``OMERO_SCREEN_LOG_FILE``, read here at call time — never at import). Args: level: Minimum level (e.g. ``"DEBUG"``). Falls back to ``OMERO_SCREEN_LOG_LEVEL`` then ``"INFO"``. console: Add a Rich console sink (sharing ``progress.py``'s ``Console``). Off by default; turn on for ``--verbose`` / interactive debugging. log_file: File-sink path. Falls back to ``OMERO_SCREEN_LOG_FILE`` then ``logs/app.log``. ``"none"``/``"off"``/``""`` disables the file sink. diagnose: loguru variable-value tracebacks. Off in production (can leak values/secrets into the log); on for ``--verbose``. plugin: When embedded in a host that owns the stdlib root logger (napari), skip installing the stdlib InterceptHandler. ---------------------------------------------------------------------- This is the CLI documentation for the package. ---------------------------------------------------------------------- ## CLI: omero-screen ``` Usage: omero-screen [OPTIONS] ID... Run the OMERO-Screen analysis pipeline on one or more plates. ID... are OMERO plate IDs. Each plate is segmented, measured and, where an EdU channel is present, assigned cell-cycle phases; results are attached back to the plate in OMERO. Setting up and checking an install: omero-screen setup configure the server, user and password omero-screen doctor check the installation omero-screen config show show the settings in effect omero-screen models pull SET / models publish MODEL.pt Options: --version Show the version and exit. --env TEXT Environment name (requires configuration file .env.{name}). --config PATH Path to an OMERO_SCREEN_CONFIG JSON (MODEL_DICT / FEATURELIST / CHANNEL_SEG_PROFILES). Overrides the OMERO_SCREEN_CONFIG env var. Errors if the path does not exist (no silent fallback to defaults). --inference MODEL Inference model filename(s). Accepts one or more values. --gallery INTEGER Width N for the inference NxN example gallery. [default: 10] --batch INTEGER Classification batch size. [default: 16] --segmentation / --no-segmentation Only perform image segmentation. [default: no-segmentation] --delete Delete the plate's existing segmentation masks and segment from scratch. Without this, a re-run reuses the stored masks (both stitched and per-field) and only recomputes the measurements. Use it after changing segmentation settings, or to repair a plate whose stored masks are wrong or empty. --cp4 Use Cellpose 4 (cpsam) for segmentation instead of the default Cellpose 3 models. --model MODEL Override all segmentation models with a single model name (e.g. 'cp4:cpsam', 'cp3:cyto3'). Overrides --cp4. --benchmark / --no-benchmark Record per-image timing data and write a JSON benchmark report. [default: no- benchmark] --stitch / --no-stitch Run stitched-well segmentation: assemble all fields per well into one canvas, segment that canvas, and exclude border objects only at the outer edge. [default: no-stitch] --stream-stitch / --no-stream-stitch Stitch one timepoint at a time to bound host RAM on long multi-channel timelapses (peak ~= canvas + one frame, not all fields + canvas; costs n_fields x T OMERO reads). Default: auto-enable when the estimated peak exceeds the host-RAM budget. Use --stream- stitch / --no-stream-stitch to force. Requires --stitch. --stitch-config PATH Path to an OMERO_SCREEN_STITCH_CONFIG JSON configuration. Overrides the OMERO_SCREEN_STITCH_CONFIG env var. Errors if the path does not exist (no silent fallback to defaults). --track MODEL Track nuclei across time with Trackastra. Optional MODEL is a pretrained name or checkpoint path (default when flag given: general_2d). Requires --stitch and a timelapse (T>1); a no-op on single-timepoint plates. --track-mode [greedy|greedy_nodiv|ilp] Trackastra linking mode. [default: greedy] --track-batch-size INTEGER Attention windows Trackastra scores per forward pass. Caps GPU memory during tracking; lower this if tracking hits CUDA OOM, raise it for faster scoring when VRAM allows (Trackastra's own GPU default is 16). [default: 4] --track-device [cpu|cuda] Force the tracking device (default: auto- detect). Use 'cpu' when a dense well exceeds GPU VRAM — runs the identical computation in host RAM (slower, but no 44 GiB ceiling and no loss of accuracy). --track-window INTEGER Override Trackastra's temporal window (frames per attention window). Smaller cuts GPU memory ~quadratically at the cost of temporal context; default keeps the model's trained window. --log-level TEXT Log level (DEBUG/INFO/WARNING/ERROR). Overrides $OMERO_SCREEN_LOG_LEVEL; default INFO. --log-file TEXT Log file path, or 'none' to disable file logging. Overrides $OMERO_SCREEN_LOG_FILE; default logs/app.log. -v, --verbose Verbose debugging: DEBUG level, console logging on, and rich tracebacks. Console is off by default so logs don't clutter the progress display. --help Show this message and exit. ``` ---------------------------------------------------------------------- This is the User Guide documentation for the package. ---------------------------------------------------------------------- ## Start here ### Install & configure OMERO-Screen needs three things: the software, a configuration that names your OMERO server and user, and your password. One command installs the software and walks you through the rest. Budget ten minutes the first time, most of it downloading. ## Quick install (macOS and Linux) Open **Terminal** and paste: ```bash curl -LsSf https://raw.githubusercontent.com/HocheggerLab/omero-screen/main/install.sh | sh ``` The installer: 1. installs [uv](https://docs.astral.sh/uv/), which brings its own Python, if it is missing; 2. downloads the latest release and installs exactly the tested versions of everything, including napari, CellView, cellclass and the plotting library, into `~/.local/share/omero-screen`; 3. puts the commands (`omero-screen`, `cellview`, `cellclass`, `napari`, …) in uv's command directory (normally `~/.local/bin`) and adds that directory to your shell `PATH` automatically if needed; 4. runs `omero-screen setup` and `omero-screen doctor` (below). Run the same line again to update; your settings and data are kept. If the installer says it updated your `PATH`, open a new Terminal window once before running the commands. You do not need to edit `.zshrc` or another shell file manually. ::: {.callout-note} If you work with Claude, continue with [Working with Claude](claude.qmd) once the installer has finished. ::: ## Set up your OMERO login ```bash omero-screen setup ``` It asks for: - the **site profile**: a ready-made description of your server and microscopes. Choose `sussex` for the Hochegger lab OMERO, or `none` to type in a server name; - your **OMERO user name** and **password**. The password is checked against the server, then stored in your computer's keychain (macOS Keychain), never in a file. Your answers are saved in `~/.config/omero-screen/config.toml`. Run `setup` again at any time to change them. ## Check the installation ```bash omero-screen doctor ``` `doctor` checks everything OMERO-Screen needs (the software, your login, the connection to the server, CellView, napari, the GPU and disk space) and says how to fix anything that fails. If something does not work, run it first. A failed connection is usually the VPN. ```bash omero-screen config show # the settings in effect, and where each came from omero-screen --version # the installed version ``` ## Hardware Segmentation and tracking are the expensive steps. Everything runs on a laptop; some combinations need the right hardware. | | Apple silicon (MPS) | NVIDIA (CUDA) | CPU only | |---|---|---|---| | **Default segmentation** | stock Cellpose 3 models (`nuclei`, `cyto3`) | Cellpose 4 (`cpsam`) | stock Cellpose 3, slow | | **Cellpose 4** (`--cp4`) | not in practice: too slow and too large | yes | impractical | | **Tracking** (`--track`) | falls back to CPU | yes | slow | | **Classifier inference** | yes | yes | workable | - **Segmentation models.** With no models configured, OMERO-Screen picks the best default for the machine and says so in the log. The stock Cellpose 3 models segment most cells reasonably; a model trained on your own cells and microscope does better: see [Make a model for your cells](models.qmd). - **The models used are recorded on every plate.** Each run writes the version, settings, device and models onto the plate in OMERO, so you can always tell how a plate was segmented. - **Tracking has no MPS backend.** On Apple silicon, `--track` runs on the CPU: correct, but slow. Device selection is automatic: CUDA if present, then MPS, then CPU. Force CPU with `OMERO_SCREEN_USE_GPU=0`. ## The configuration in detail Settings come from several places. A value set higher up in this list wins: 1. command-line flags (e.g. `--cp4`); 2. environment variables (`HOST`, `USERNAME`, `OMERO_SCREEN_*`); 3. a `.env` file in a source checkout (developers, see below); 4. your **user config**, `~/.config/omero-screen/config.toml`; 5. the **site profile** it names; 6. built-in defaults. A user config written by `setup` looks like this: ```toml site = "sussex" [omero] username = "ab123" # group = "my-lab-group" # default: your OMERO default group [segmentation] # model_set = "hocheggerlab" # the lab's custom models (omero-screen models pull) [paths] # cache = "~/omero-cache" # downloaded images # cellview_db = "~/.cellview/cellview.duckdb" [env] # any OMERO_SCREEN_* variable, e.g. # OMERO_SCREEN_USE_GPU = "0" ``` A **site profile** holds what everyone at a site shares: the OMERO server and port, the stitch calibrations of its microscopes, its model sets. `sussex` is built in. For another site, write one as a TOML file, set `site = "/path/to/your-site.toml"`, and send it to us so it can be included. On a machine without a keychain (an HPC cluster), put the password in a file only you can read and name it in the user config: ```bash umask 077 && echo 'your-password' > ~/.omero-password ``` ```toml [omero] password_file = "~/.omero-password" ``` The `PASSWORD` environment variable works too, and takes precedence. ## Working in another user's screens To analyse plates that belong to someone else, you need at least read access to their OMERO group. Your OMERO administrator sets this. Masks and results are written to your own `Screens` project. ## Developer install To work on the code, clone the repository and install the workspace with the development tools: ```bash git clone https://github.com/HocheggerLab/omero-screen.git cd omero-screen uv sync --dev uv run omero-screen doctor ``` `uv sync` installs all six packages (the core pipeline, `cellview`, `omero-screen-plots`, `omero-screen-napari`, `cellclass` and `omero-utils`). Configure with `uv run omero-screen setup` as above. A `.env.development` (or `.env.production`, selected with `ENV=production`) at the repository root is still read and takes precedence over the user config: ```bash USERNAME="your-omero-login" PASSWORD="your-omero-password" HOST="omero.your-institution.ac.uk" ``` ::: {.callout-warning} `.env.*` files hold your password in plain text. They are gitignored; keep it that way, and never paste one into an issue or a pull request. ::: Run `uv run omero-integration-test e2e_connection` to check the connection to a test server. ## Next You are ready to [analyse your first plate](first-plate.qmd). ### Working with Claude You can run OMERO-Screen by talking to Claude instead of typing commands. Claude runs the tools for you, explains what it is doing, and asks before it changes anything in OMERO. This page sets that up. It takes about ten minutes. ## What you need - The **Claude desktop app** (claude.ai/download), signed in. - **OMERO-Screen installed** with the one-line installer ([Install & configure](installation.qmd)). The installer ends with `omero-screen setup`, which saves your OMERO login. - For OMERO from outside the university, the **VPN**. ## 1. Install the OMERO-Screen plugin The plugin teaches Claude how OMERO-Screen works. 1. Open the Claude desktop app. 2. Go to **Customize → Plugins**. 3. Choose **Add marketplace** and paste: ``` HocheggerLab/omero-screen ``` 4. Find **omero-screen** in the list and click **Install**. The plugin updates itself when we publish improvements. ::: {.callout-note} If your lab uses a Claude Team plan, the administrator can install the plugin for everyone (**Organization settings → Plugins & skills → Sync from GitHub**, repository `HocheggerLab/omero-screen`). You then skip this step. ::: ## 2. Start a session that can use your computer OMERO-Screen runs on your computer: napari opens windows on your screen, and OMERO is reached over your network. Claude therefore has to work on your computer, not only in the cloud. - Start a session in the desktop app with access to a folder on your computer, for example a `Claude` folder in your Documents. This is where Claude saves figures and tables. - **Test it.** Ask: > Check my OMERO-Screen installation. Claude should run `omero-screen doctor` and report a list of checks. If it says it cannot run commands on your computer, use the **Code** tab of the desktop app instead; it looks like a chat too, runs on your computer, and uses the same plugin. ## 3. Things to ask Start with something small, and say what you want in your own words. Claude will ask when it needs a plate number or a channel name. **Check and look** > Check my OMERO-Screen installation. > Which plates do I have in OMERO? Show me plate 1234: wells, channels and cell lines. > Open plate 1234, well B3 in napari. **Train a classifier** > I want to train a classifier that recognises micronuclei. Open plate 1234 in > napari and help me collect example cells with and without micronuclei. > Train a classifier on the cells I collected and show me how well it does. > Publish the best model to OMERO and run it on plates 1234 and 1235. **Analyse and plot** > Import plate 1234 into CellView. > Load plate 1234 from CellView and plot the γH2AX intensity per siRNA, as a PDF. > Make a cell-cycle plot for plates 1234 and 1235, controls first. ## Good to know - **Claude asks before changing OMERO**: running the pipeline on a plate, publishing a classifier, deleting anything. Reading and plotting it does without asking. - **Your password is never in the chat.** If Claude says your login is missing, run `omero-screen setup` in Terminal yourself; it asks for the password there and keeps it in your keychain. - **When something fails**, ask Claude to run the check again. A failed connection is almost always the VPN. - **Segmentation on a Mac** uses the standard Cellpose 3 models, which work for most cells. For better masks, see [Make a model for your cells](models.qmd). ### Your first plate One continuous journey: a plate ID on an OMERO server becomes segmented, measured single cells, then a local database, then a plot. Nothing here requires reading the source code. **You need:** a working install ([previous page](installation.qmd)) and the OMERO ID of a fixed-cell plate you have permission to read. **You get:** per-cell measurements attached to the plate in OMERO, the same data in a local database, and one figure. --- ## 1. Check the plate's metadata The pipeline reads the experimental layout — which well holds which cell line and condition, and what each channel is — from an Excel file attached to the plate object in OMERO. This is the single most common thing to get wrong, so look before you run. The sheet needs one row per well, with columns naming at least the cell line and the condition, plus a channel sheet mapping channel names to indices. Channel names matter: the pipeline recognises nuclear channels (`DAPI`, `Hoechst`), a cytoplasm channel used for cell segmentation, and `EdU` if present. ::: {.callout-tip} If the plate has been analysed before, the Excel file will already have been converted to OMERO map-annotations and removed — that is expected, not a problem. ::: ## 2. Analyse the plate ```bash uv run omero-screen 1234 ``` Replace `1234` with your plate ID. That is the whole command; everything else is an override. What happens, in order: 1. **Metadata parsing** — the layout is read and converted to map-annotations. 2. **Flatfield correction** — per-channel illumination masks are built from a random sample of 100 images and cached in a dedicated OMERO dataset, so later runs skip this step. 3. **Well loop** — for every non-empty well and every image in it: pixels are downloaded and flatfield-corrected, Cellpose segments nuclei and cells, border objects are filtered, cytoplasm is derived as cell minus nucleus, and `regionprops` extracts area, intensity statistics and centroids. Per-well CSVs are attached to OMERO as it goes, so a crash does not cost you the whole run. 4. **Cell-cycle analysis** — if an EdU channel is present, integrated DAPI intensity and EdU signal are normalised per cell line and each nucleus is assigned to G1, S, G2/M, sub-G1 or polyploid. 5. **Save** — plate-level CSVs and QC figures are attached to the plate; intermediate per-well files are deleted. Expect tens of minutes for a full plate on a GPU, considerably longer on CPU. ### Useful variations | You want | Add | |---|---| | Whole-well canvas segmentation instead of field-by-field | `--stitch` | | Segmentation only, no cell-cycle or classification | `--segmentation` | | A specific Cellpose model | `--model cp4:cpsam` | | Live-cell lineages across timepoints | `--stitch --track` | | A different environment file | `--env production` | `--stitch` is worth understanding early. By default each field of view is segmented independently, so a cell straddling two fields is cut in half by the border filter on each side. Stitched mode assembles all fields of a well into one canvas, runs Cellpose once on it, and excludes border objects only at the outer edge. ## 3. Check it worked Look at the plate in OMERO. You should see: - CSV attachments on the plate object holding the final measurements, - QC figures showing segmentation overlays, - map-annotations carrying the parsed metadata, - a mask image per field (or per well, in stitched mode). Open one QC figure and confirm the outlines sit on cells. If the masks look empty or wildly wrong, stop here — importing bad segmentation into the database just moves the problem downstream. For a closer look, the [napari widgets](../napari/) let you browse wells and inspect individual cells interactively. ## 4. Import into CellView The measurements are now in OMERO, but querying CSV attachments is painful. CellView pulls them into a local DuckDB database. ```bash uv run cellview import plate 1234 ``` Then look at what arrived: ```bash uv run cellview plate 1234 # summary, conditions, measurement counts uv run cellview projects # everything in the database ``` If the counts look implausible — a well with far fewer cells than its neighbours — that is worth chasing before you plot. ## 5. Make a figure Load the plate into a DataFrame and plot it. ```python from cellview.api import cellview_load_data from omero_screen_plots import count_plot, cellcycle_plot, save_fig df, conditions = cellview_load_data(1234) fig, ax = count_plot( df, norm_control="control", # the condition everything is normalised to conditions=conditions, ) save_fig(fig, "cell-counts") ``` `cellview_load_data` returns the measurements and the list of conditions found in the plate. Every plot function takes that DataFrame and a `conditions` list, so swapping `count_plot` for `cellcycle_plot`, `feature_plot` or `classification_plot` needs no other change. That is the loop: plate → analysis → database → figure. ## Where to go next - **Plot properly** — the [plots guide](../plots/) covers cell-cycle stacks, feature comparisons, histograms, scatter and synergy plots, with the statistics and styling the lab uses. - **Classify cells** — train a CNN on labelled crops with [CellClass](../cellclass/). - **Query the database** — [CellView](../cellview/) has a schema reference and an interactive notebook explorer (`cellview explore`). - **Track live cells** — add `--stitch --track` for lineages across timepoints. ## When it goes wrong | Symptom | Usually means | |---|---| | Connection refused / authentication error | Credentials or `HOST` in your `.env` file; check with `omero-integration-test e2e_connection` | | "No metadata found" | The plate has no Excel attachment and no map-annotations | | Masks present but empty | A field with no stage position — a failed acquisition. Re-run that plate with `--delete` | | Out of memory on a stitched timelapse | Add `--stream-stitch`, which stitches one timepoint at a time | | Cell counts lower than expected near well edges | Known behaviour — border and foreground filtering removes cells more often than nuclei | ### Try it locally You can run the entire platform against a disposable OMERO server on your own machine — no institutional server, no credentials, no data of your own. This is the fastest way to see what OMERO-Screen actually does. ::: {.callout-note} Allow about 30 minutes end to end, most of which is the first Docker pull and the Cellpose model download. ::: ## What you need - **Docker**, running. The test server is a container pair (OMERO + Postgres). - **Python 3.12–3.14** and [uv](https://docs.astral.sh/uv/), as in [Install & configure](installation.qmd). - **A GPU helps but is not required.** On CPU, segmentation is slow but works — the sample plate is deliberately small. ## 1. Start the test server ```bash ./scripts/manage_test_server.sh start ``` This brings up `docker-compose.test.yml` and waits for OMERO to accept connections on port 4064. It is a throwaway instance: `stop` shuts it down, and the data lives in a Docker volume you can delete. ```bash ./scripts/manage_test_server.sh status # is it up? ./scripts/manage_test_server.sh stop # shut it down ``` Point your environment at it by setting `ENV=e2etest`, which loads `.env.e2etest` — that file already contains the container's credentials (`root` / `omero` on `localhost`), so nothing needs editing. ```bash export ENV=e2etest uv run omero-integration-test e2e_connection ``` A clean run confirms the server is up and the client can authenticate. ## 2. Load the sample plate ::: {.callout-warning} **Sample dataset not yet published.** A small multi-well plate with an EdU channel is being prepared for deposit, so that every step below can be run exactly as written. Until then, use your own Operetta-format plate with `scripts/load_plates.sh` (see step 2b). ::: ### 2b. Using your own data `load_plates.sh` imports Operetta-layout plates — directories containing `*/Images/Index.idx.xml` — into a project named "Screens". ```bash ./scripts/load_plates.sh # show the options ./scripts/load_plates.sh -d /path/to/plates -x # -x actually executes ``` Note the plate ID it reports; every command below takes it. ## 3. Run the pipeline ```bash uv run omero-screen ``` Metadata is parsed, flatfield masks are built, every well is segmented with Cellpose, features are extracted, and — if an EdU channel is present — each cell is assigned a cell-cycle phase. Results are attached back to the plate as CSVs, with QC figures alongside. Add `--stitch` to segment each well as a single canvas instead of field by field. See [Your first plate](first-plate.qmd) for what each stage does. ## 4. Inspect the segmentation ```bash uv run napari ``` Open the **Welldata Widget** from the plugin menu, enter the plate ID, and step through wells to see the segmentation overlaid on the raw channels. This is the quickest way to judge whether the masks are sensible. The [napari guide](../../napari/) covers the other widgets. ## 5. Import into the database ```bash uv run cellview import plate uv run cellview plate # summary, conditions, measurement counts uv run cellview projects # everything in the database ``` ## 6. Make a figure ```python from cellview.api import cellview_load_data from omero_screen_plots import cellcycle_plot, save_fig df, conditions = cellview_load_data() fig, ax = cellcycle_plot(df, conditions=conditions) save_fig(fig, "cell-cycle") ``` The [plots documentation](../../plots/) covers the other plot types and the statistics behind them. ## 7. Optional: train a classifier If you want to exercise the machine-learning path, generate labelled crops from the napari training widget, then: ```bash uv run cellclass dataset --name demo --channels DAPI Tub uv run cellclass train demo.npz --epochs 50 --no-wandb ``` The [CellClass guide](../../cellclass/) explains the architecture sweep and the metrics. ## Cleaning up ```bash ./scripts/manage_test_server.sh stop docker volume prune # removes the throwaway OMERO data ``` ## If something fails | Symptom | Usually means | |---|---| | `Connection refused` on 4064 | The container is still starting; wait and re-run `status` | | Authentication error | `ENV=e2etest` is not exported, so a different `.env` was loaded | | "No metadata found" | The plate has no Excel attachment and no map-annotations | | Segmentation extremely slow | Running on CPU — expected; use a smaller plate | ### How it works What the pipeline actually does to a plate, how the pieces fit together, and which package owns which step. ```{=html} ``` ## What it does Point it at a plate ID. It reads the experimental layout from the plate's metadata, corrects illumination, and segments every nucleus and cell with [Cellpose](https://www.cellpose.org/). It measures them, assigns each cell a cell-cycle phase from DNA content and EdU signal, and attaches the results back to OMERO. A trained CNN classifies cell features, and [Trackastra](https://github.com/weigertlab/trackastra) follows lineages through live-cell timelapses. ::: {.fig-card} ![**The OMERO-Screen platform.** Images and plate metadata enter from the microscope. The core pipeline handles metadata parsing, flatfield correction, segmentation, classification and feature extraction, with a separate cyclic-IF route for registration and feature alignment. omero-utils brokers every exchange with the OMERO server, which stores plates, masks, metadata and feature data. Single-cell measurements land in the local cellview DuckDB database, feeding the napari viewers, cellclass classifier training, and the plotting package.](../assets/figure1-architecture.png){#fig-architecture} ::: ```bash omero-screen 1234 --stitch # segment a plate, write results back to OMERO cellview import plate 1234 # pull the measurements into a local database ``` Two commands: analyse the plate, then pull the measurements into a local database you can query, plot and classify. ## The journey Six steps, each with a guide. You do not need to know which Python package owns which step. | | Step | You run | Guide | |---|---|---|---| | 1 | **Connect** to an OMERO server and check your plate's metadata | `omero-screen --help` | [Install & configure](installation.qmd) | | 2 | **Analyse** a plate — segment, measure, assign cell-cycle phase | `omero-screen ` | [Your first plate](first-plate.qmd) | | 3 | **Inspect** segmentation quality and browse cells interactively | napari widgets | [Napari](../napari/) | | 4 | **Store & query** single-cell measurements locally | `cellview import plate` | [CellView](../cellview/) | | 5 | **Classify** cell features with a trained CNN | `cellclass train` | [CellClass](../cellclass/) | | 6 | **Plot** publication-ready statistics | Python API | [Plots](../plots/) | ## Inside one analysis run ::: {.fig-card} ![Starting from a plate ID, the pipeline parses experimental metadata, builds per-channel flatfield masks, then iterates over every non-empty well. Each image is segmented with Cellpose, features are extracted with `regionprops`, and results are written back to OMERO as CSV attachments. Cell-cycle assignment and optional classification run once all wells are done.](../assets/analysis-loop.svg){#fig-loop width=85%} ::: Two things are worth knowing early: - **Stitched mode** (`--stitch`) segments the whole well as one canvas instead of field by field, so a cell straddling two fields is no longer cut in half by the border filter on each side. - **Live-cell tracking** (`--track`) relabels nuclei with a stable `track_id` across timepoints using [Trackastra](https://github.com/weigertlab/trackastra), giving you lineages rather than independent snapshots. ## Why the results are reproducible Every number in a figure traces back to a plate ID, a segmentation mask stored in OMERO, and a row in a local database. Re-running a plate reproduces it; sharing it means sharing an ID rather than a folder. The analysis stops being something that happened once, on one desktop, in a way nobody can retrace. ## The packages Each has its own reference site with generated API and CLI documentation. | Package | What it is | |---|---| | **omero-screen** | The analysis pipeline: Cellpose segmentation, feature extraction, cell-cycle phases, flatfield correction, stitching, tracking | | **[cellview](../cellview/)** | DuckDB-backed single-cell database with a CLI and a Python API | | **[omero-screen-plots](../plots/)** | Statistical plots built for publication figures — cell cycle, counts, features, classification, synergy | | **[omero-screen-napari](../napari/)** | Napari widgets for browsing plates, inspecting galleries and labelling CNN training data | | **[cellclass](../cellclass/)** | CNN classifier training, sweeps and inference for cell features | | **[omero-utils](../utils/)** | Connection handling, attachments, map annotations and image I/O against the OMERO API | ## Performance and accuracy The pipeline has been benchmarked across CPU, Apple silicon (MPS) and two NVIDIA GPUs, for both Cellpose 3 and Cellpose 4 models. ::: {.fig-card} ![**Benchmarking the pipeline.** (**A**) Pre-processing and the analysis loop around the OMERO server. (**B**, **C**) Per-image processing time and how it divides between download, nucleus segmentation, cell segmentation and feature extraction. (**D**) Total pipeline run time across CPU, MPS, an NVIDIA A40 and an RTX 5090. (**E**) Boundary accuracy against manual annotation, per object, for nuclei and cells. (**F**) Detection accuracy as average precision across IoU matching thresholds.](../assets/figure2-benchmarks.png){#fig-benchmarks} ::: Two practical conclusions: download time dominates on fast GPUs, so the image cache matters more than raw compute once you have one; and Cellpose 4 is markedly more accurate on cell boundaries, which is why it is worth an NVIDIA GPU if you have access to one. ## Running the pipeline ### How the pipeline works `omero-screen ` runs `plate_loop()`, which fetches the plate from OMERO, segments every image, measures every cell and writes the results back to the plate. This page describes each stage and the configuration that controls it. Every command-line option is listed in the [CLI reference](../reference/cli/omero_screen.qmd). ## The stages ::: {.fig-card} ![Starting from a plate ID, the pipeline parses experimental metadata, builds per-channel flatfield masks, then iterates over every non-empty well. Each image is segmented with Cellpose, features are extracted with `regionprops`, and results are written back to OMERO as CSV attachments. Cell-cycle assignment and optional classification run once all wells are done.](../assets/analysis-loop.svg){#fig-loop width=85%} ::: 1. **Metadata parsing.** The experimental layout (well positions, cell lines, conditions, channel names) is read from an Excel file attached to the plate. It is then converted to OMERO map annotations and the Excel file is removed. 2. **Flatfield correction.** Per-channel illumination masks are built from a random sample of 100 images across the plate. The masks are stored in a dedicated OMERO dataset, so later runs skip this step. 3. **Well and image loop.** For each non-empty well and each image in it: - the raw pixels are downloaded and divided by the flatfield mask; - Cellpose segments nuclei (DAPI/Hoechst channel) and cells (cytoplasm channel); - objects touching the border are removed, and the cytoplasm mask is derived as cell minus nucleus; - `skimage.measure.regionprops_table` measures the nucleus, cell and cytoplasm regions (see [Extracted features](#extracted-features)); - the well's results are attached to OMERO as an intermediate CSV, so a crashed run can resume. 4. **Cell-cycle analysis.** If the plate has an EdU channel, integrated DAPI intensity and EdU signal are normalised per cell line and each nucleus is assigned to sub-G1, G1, S, G2/M or polyploid. 5. **Save results.** Plate-level CSVs and QC figures are attached to the plate; the intermediate per-well files are deleted. Tiled plates and timelapses are better served by [stitched mode](stitching.qmd), which segments the whole well as one canvas. ## Re-running a plate A re-run reuses the segmentation masks already stored in OMERO and only recomputes the measurements. Add `--delete` to discard the stored masks and segment from scratch, for example after changing a model or a [segmentation profile](#per-channel-segmentation-profiles). ## Configuration file Models, features and segmentation profiles come from a JSON configuration. The built-in defaults live in `src/data/omero_screen_config.json`. To use your own, point `OMERO_SCREEN_CONFIG` at a JSON file, or pass `--config PATH`, which takes precedence over the variable. A path that does not exist is an error; the pipeline does not fall back to the defaults. The file may contain any of three blocks: `MODEL_DICT`, `FEATURELIST` and `CHANNEL_SEG_PROFILES`. ## Extracted features `FEATURELIST` declares which `regionprops` properties are measured, split into two groups: ```json { "FEATURELIST": { "intensity": ["intensity_mean", "intensity_max", "intensity_min", "intensity_std"], "morphology": ["area", "solidity", "eccentricity", "extent", "perimeter", "area_convex", "axis_major_length", "axis_minor_length", "equivalent_diameter_area"] } } ``` - **Intensity** features read the pixel data, so they are measured per channel and named `{feature}_{channel}_{segment}`. - **Morphology** features read only the label mask, so they are the same on every channel. They are measured once per segment, on the channel that produced the mask, and named `{feature}_{segment}`. - `label` and `centroid` are identity columns and are always added; don't list them. Feature names must be valid `skimage.measure.regionprops` properties. `area` and `intensity_mean` are required, because the integrated-intensity column is computed from them, and their presence is checked when the configuration loads. A flat list (the older format) is still accepted and split into the two groups automatically. The default set is deliberately small. A richer, cell-state-oriented set ships as `src/data/omero_screen_config_full.json`: ```bash uv run omero-screen --config src/data/omero_screen_config_full.json ``` ## Per-channel segmentation profiles Cellpose's default thresholds suit fixed-cell stains such as DAPI and Hoechst, which are bright and even. They miss cells on live-cell markers with variable brightness, such as H2B-RFP, or heterogeneous expression, such as Tubulin-GFP. `CHANNEL_SEG_PROFILES` applies its own preprocessing and Cellpose parameters when a channel of that name is segmented. The built-in profiles are: ```json { "CHANNEL_SEG_PROFILES": { "h2b_rfp": {"gamma": 0.5, "cellprob_threshold": -2.0}, "tub_gfp": {"gamma": 0.5, "cellprob_threshold": -2.0, "flow_threshold": 0.6} } } ``` Channel names are matched case-insensitively. Three keys are supported: | Key | Effect | |---|---| | `gamma` | Dynamic-range compression after percentile rescaling, before Cellpose. Values below 1 lift dim nuclei in frames that also contain bright ones. | | `cellprob_threshold` | Passed to Cellpose's `eval()`. Lower it (e.g. `-2`) to accept less confident detections. | | `flow_threshold` | Passed to Cellpose's `eval()`. Helps cell segmentation when the Tubulin signal is faint at the cell edge. | Profiles in your configuration file are **merged** with the built-in ones: an entry replaces the built-in profile of the same name, and the others stay in place. Channels without a profile use the default Cellpose parameters, so fixed-cell plates are unaffected. ### Make a model for your cells OMERO-Screen segments nuclei and cells with [Cellpose](https://www.cellpose.org). Without further setup it uses a default that suits the machine: | Machine | Default | |---|---| | NVIDIA GPU (Linux workstation, HPC) | Cellpose 4 (`cpsam`), good on most images | | Mac or CPU | the stock Cellpose 3 models: `nuclei` for nuclei, `cyto3` for cells | The stock Cellpose 3 models segment most fixed cells reasonably. On a 10x DAPI plate they found 616 nuclei where a custom lab model found 606 and Cellpose 4 found 621. They do worse on unusual stains, very dense or very sparse cells, other magnifications and live-cell DNA dyes. A model trained on a few of **your** images, with **your** microscope, usually fixes that. Every processed plate records which models were used (map annotation `omero-screen/provenance` on the plate), so you can always check. ## Train a model with the Cellpose GUI Training is done in the Cellpose desktop app, with you correcting its masks ("human in the loop"). Install it once: ```bash uv tool install --python 3.12 "cellpose[gui]<4" ``` 1. **Export a few representative images**, say 5–10 fields covering your conditions, for example from napari (*File → Save Selected Layer*) as TIFF. Use the channel you segment on (DAPI/Hoechst for nuclei). 2. **Open them**: run `cellpose`, then drag in the first image. 3. **Start from the closest model.** Choose `nuclei` (or `cyto3`) under *Model zoo*, run it, then correct the masks: delete wrong ones (Ctrl+click on the mask), draw missing ones (right-click, then trace the outline). 4. **Train**: *Models → Train new model*, start from the same model, keep the defaults. Cellpose saves the new model in a `models` folder next to your images; copy the model file into `~/.cellpose/models/` when you are done. 5. **Repeat on the next image** with your new model; each round needs fewer corrections. Three or four images are often enough. The [Cellpose documentation](https://cellpose.readthedocs.io/en/v3.1.1.1/gui.html) covers the GUI in detail. ## Use it in OMERO-Screen Name your model in the user config (`~/.config/omero-screen/config.toml`), per role or cell line: ```toml [segmentation.models] nuclei = "my_nuclei_model" # file name in ~/.cellpose/models RPE = "my_rpe_cells_model" # cell segmentation for cell lines containing "RPE" ``` Cell lines are matched by name (exact, then contained in the plate's cell line), so `RPE` covers "RPE-1 WT". A cell line without an entry uses the `U2OS` entry, or the device default if none is configured. For a single run you can also override everything: `omero-screen 1234 --model cp3:my_model`. ## Share models in a lab Models a whole lab uses can go into the lab's site profile as a **model set**, published for download (e.g. on Zenodo). Everyone then gets them with: ```bash omero-screen models pull ``` and selects them with `[segmentation] model_set = ""`. The Hochegger lab's models are the `hocheggerlab` set of the `sussex` profile. ### Stitched mode By default the pipeline segments each field of view on its own and merges the results. On tiled plates, where every well is acquired as several positions, `--stitch` switches to a **whole-well canvas**: 1. All fields of the well are downloaded and flatfield-corrected. 2. The fields are stitched into one `(T, Y, X, C)` canvas. Stage positions from OMERO give each field's `(column, row)` in the grid; the spacing between tiles comes from a [per-objective calibration](#stitch-calibration). 3. Cellpose runs **once** on the canvas, tiling the large input internally. A cell straddling two fields is no longer cut in half by the border filter on each side. 4. The whole-well mask is split back into per-field tiles (each label goes to the field holding its centroid) and uploaded to the OMERO segmentation dataset with the suffix `_stitched_segmentation`. 5. Features are measured on the canvas, so each cell appears exactly once in the output. ```bash uv run omero-screen --stitch ``` **Use `--stitch` for:** - multi-position acquisitions with tiled fields per well; - live-cell timelapses, where one global segmentation keeps labels consistent across the canvas. [Tracking](../tracking/index.qmd) requires it; - large cells that often straddle field boundaries. **Don't use it for:** - single-position acquisitions (one image per well). The per-field path is faster and stitching gains nothing; - plates without reliable stage positions. The stitcher needs `PosX`/`PosY` on every well sample; if they are missing or noisy the canvas will not align. The napari widgets recognise stitched plates by their `_stitched_segmentation` images and load the masks without further configuration. Only stitched plates can be [cached as OME-Zarr](caching.qmd) for whole-plate browsing. ## Stitch calibration Tiles are placed by grid arithmetic. A field at grid position `(col, row)` is written at canvas offset `col * (tile_w - overlap_x) + row * translate_x`, and likewise in y. Stage positions fix the grid *indices*, not the spacing, so `overlap_*` and `translate_*` must match the acquisition. These values depend on the objective. The same physical overlap covers twice as many pixels at 20x as at 10x. The 10x values on a 20x plate shear every seam by about 19 px; a nucleus on a seam is then segmented twice, once in each tile, which inflates counts and, on timelapses, breaks tracks. The pipeline therefore picks the calibration from the plate's **pixel size**, read from OMERO by `MetadataParser`, in `STITCH_CALIBRATIONS` (`omero_utils.stitching`): | Objective | Pixel size (µm) | `overlap_x` / `overlap_y` | `translate_x` / `translate_y` | |---|---|---|---| | 10x | 1.2 | 7 / 7 | -3 / 3 | | 20x | 0.6 | 25 / 26 | -5 / 4 | The chosen calibration is logged at INFO on every stitched run. A pixel size that matches no entry falls back to the 10x values **with a warning**; calibrate the objective rather than ignoring it. ### Checking a plate The seam diagnostic is read-only and works on any stitched plate: ```bash uv run python scripts/diagnose_stitch_seams.py ``` It counts how often pairs of detections near a seam are separated by the same small displacement, compared with an interior control. A clean plate shows no peak. A peak at *d* px means `overlap` is short by *d*. Use the same script to calibrate a new objective. ### Another microscope `OMERO_SCREEN_STITCH_CONFIG`, or `--stitch-config PATH`, points at a JSON file with your own values. It replaces the table and switches off selection by pixel size. `src/data/omero_screen_stitch_config.json` shows the format. ## Host memory Flatfield-corrected fields are held as `float32`, which halves the canvas compared with `float64`. Long multi-channel timelapses can still exceed host RAM when all fields and the canvas are in memory together. **Streaming** stitches one timepoint at a time: the peak becomes the canvas plus one frame's fields, at the cost of `n_fields × T` OMERO reads instead of `n_fields`. Streaming switches on automatically when the estimated peak exceeds 70 % of the RAM budget: - The budget is the job's cgroup memory limit when there is one, so a SLURM job's real ceiling is used, and otherwise total physical RAM. If neither can be read, the pipeline does not auto-stream. - The estimated peak is about 2.5 × the float32 canvas (all raw fields, the canvas and a transient copy). The estimate is logged for every stitched well. Force the choice with `--stream-stitch` / `--no-stream-stitch`, or `OMERO_SCREEN_STITCH_STREAMING=1` / `0`. Both require `--stitch`. ::: {.callout-note} Streaming bounds only the stitching stage. Feature extraction still holds the full canvas in memory. ::: ### Running on a SLURM cluster `bin/sbatch_omero_screen.py` writes a SLURM job script that runs `omero-screen` and submits it with `sbatch`. It is set up for the Sussex HPC (job classes, GPU nodes); other clusters will need its template adjusted. ::: {.callout-note} This is a `bin/` utility and still uses argparse, unlike the installed commands. It keeps argparse conventions, including abbreviated options. ::: ```bash python bin/sbatch_omero_screen.py 12345 67890 --env production --stitch --track ``` By default it submits one job per plate ID. ## Job options | Option | Default | Effect | |---|---|---| | `--class CLASS` | `gpu` | SLURM job class | | `-u`, `--username USER` | current user | Cluster username | | `-t`, `--threads N` | 1 | CPU threads; raise it when running without a GPU | | `--hours N` | 24 | Wall-clock limit in hours | | `-m`, `--memory N` | 32 | Memory in GB | | `--gpu` / `--no-gpu` | on | Request a GPU node | | `--exec` / `--no-exec` | on | Execute the generated script statements | | `--submit` / `--no-submit` | on | Submit with `sbatch` | | `--multi-submit` / `--no-multi-submit` | on | One job per plate ID | The memory you request becomes the job's cgroup limit, which the pipeline reads to decide whether to [stream the stitch](stitching.qmd#host-memory). ## Pipeline options These are forwarded to `omero-screen` in every job. They mean the same as in the [CLI reference](../reference/cli/omero_screen.qmd). | Option | Forwarded as | |---|---| | `--env ENV` | `--env` | | `--config PATH` | `--config`, resolved to an absolute path and checked at submission | | `--inference MODEL [MODEL ...]` | `--inference` | | `--segmentation` | `--segmentation` | | `--delete` | `--delete` | | `--cp4`, `--model MODEL` | `--cp4`, `--model` | | `--stitch`, `--stitch-config PATH` | `--stitch`, `--stitch-config` | | `--stream-stitch` / `--no-stream-stitch` | the same | | `--track [MODEL]`, `--track-mode`, `--track-device`, `--track-window`, `--track-batch-size` | the same | ## Viewing results ### Local cache Fetching pixels from OMERO every time you open a well is slow, and a tiled plate would be re-stitched in memory on every view. The napari widgets therefore keep a **local cache**. Once a plate is cached, any well, or the whole plate, loads from disk in a fraction of a second without a server connection. For plates analysed in [stitched mode](stitching.qmd), the cache is an **OME-Zarr (OME-NGFF) store** holding the stitched whole-well canvas. Napari reads only the chunks needed for the current view, so you can pan, zoom and flip through every well of a plate at full resolution in bounded memory. ::: {.callout-note} napari builds the cache on demand; the analysis pipeline does not. `omero-screen --stitch` writes the stitched segmentation to OMERO. The OME-Zarr store is created later, when you click **Cache** in the [Welldata widget](../napari/user-guide/welldata_widget.html). ::: ## Two backends | Backend | Used for | What is stored | |---|---|---| | **Disk cache** (older) | Plates analysed per field (without `--stitch`) | Raw per-image `TCZYX` pixels (uint16). Any stitching happens in memory when you view. | | **OME-Zarr cache** | Stitched plates (`--stitch`) | The stitched whole-well canvas as a multiscale OME-NGFF store, read lazily chunk by chunk. | The widget picks the backend per plate: OME-Zarr when the OMERO dataset holds `_stitched_segmentation` images, the disk cache otherwise. Both can exist side by side. The rest of this page is about the OME-Zarr cache. ## On-disk layout The cache root is `~/omero-cache`. It is a visible folder rather than one hidden under `~/.cache`, so external tools such as Fiji/Mastodon can open the stores directly. Set `OMERO_SCREEN_CACHE_PATH` to move it. There is one store per plate, `plate_.zarr`, with wells as `/` groups, following the OME-NGFF high-content-screening layout: ```text ~/omero-cache/ ├── images/ # disk cache: per-field pixels ├── plates/ # disk cache: plate metadata └── zarr/ # OME-Zarr cache ├── registry.json # per-plate size, last access, pin state └── plate_4155.zarr/ ├── .zattrs # plate metadata, channel names, pixel size └── B/ # row └── 2/ # well B2 └── 0/ # multiscale image group (Mastodon opens this) ├── 0/ # level 0, full resolution ├── 1/ # level 1, 2× downsampled ├── 2/ # level 2, 4× downsampled └── labels/ ├── nuclei/ # nucleus label mask (uint32), same pyramid └── cells/ # cell label mask (uint32), if present ``` Each well image is a `(T, C, Y, X)` array. - **Pyramid:** three levels, each downsampled 2× from the one before, so the viewer reads the level that suits the current zoom. - **Chunks:** `(1, 1, 256, 256)` for images and `(1, 256, 256)` for masks, i.e. one timepoint and one channel per chunk in 256-pixel tiles. Single-cell crops stay cheap to read and each slider step does a bounded amount of work. - **Compression:** Blosc/zstd, the ome-zarr default. - **Crash safety:** each well is written to a `.tmp` directory and renamed when complete, so an interrupted build leaves only whole wells. Clicking **Cache** again resumes where it stopped. ## Browsing a whole plate Because the canvas is stitched on disk and stored as a lazy, chunked pyramid, napari never holds a whole well, let alone a whole plate, in memory. - **Lazy reads.** Wells reach napari as dask arrays backed by the store; only the chunks in view, at the current pyramid level, are decompressed. - **Well slider.** Loading several wells, or *All*, stacks them on a new leading axis. Napari shows it as a slider, with a metadata overlay for each well. - **Warm caches.** Recently decoded chunks stay in memory, so scrubbing the time or well slider and zooming back stay smooth. The [Welldata widget guide](../napari/user-guide/welldata_widget.html) walks through the interface. ## Timelapses A timelapse uses the same per-plate store: its wells simply have a `T` axis longer than one, and the napari time slider steps through frames. To keep long multi-channel timelapses within a laptop's RAM, the build stitches and writes a few timepoints at a time (`OMERO_SCREEN_CACHE_BLOCK`, default 4). On a [tracked](../tracking/index.qmd) plate, the cached nucleus mask stores each cell's `track_id` as its pixel value. The Tracks widget exports [Mastodon bundles](../tracking/mastodon.qmd) from it. ## Configuration Set these in your `.env.{ENV}` file or the shell. | Variable | Default | Effect | |---|---|---| | `OMERO_SCREEN_CACHE_PATH` | `~/omero-cache` | Root for all caches. A fast SSD gives the best interactivity. `~` is expanded. | | `OMERO_SCREEN_ZARR_MAX_GB` | `100` | Size cap of the OME-Zarr cache in GiB (integer, minimum 10) | | `OMERO_SCREEN_IMAGE_CACHE_SIZE_LIMIT` | 20 GiB | Size cap of the disk cache, in bytes | | `OMERO_SCREEN_IMAGE_CACHE_WORKERS` | `3` | Parallel downloads when filling the disk cache | | `OMERO_SCREEN_CACHE_BLOCK` | `4` | Timepoints stitched and written per block. Lower it (e.g. `2`) for long timelapses on a 16 GB machine. | | `OMERO_SCREEN_CACHE_WORKERS` | `2` | Dask workers during an OME-Zarr build | | `OMERO_SCREEN_ZARR_DISPLAY_CACHE_MB` | `1024` | Chunk cache on the store while viewing, in MiB | | `OMERO_SCREEN_DASK_CACHE_MB` | `2048` | Cache of decoded results while viewing, in MiB | | `OMERO_SCREEN_NAPARI_ASYNC` | `1` | Napari asynchronous slicing; `0` turns it off | ## Eviction and pinning The OME-Zarr cache manages its own size. `registry.json` records each plate's size and last access. When a new build would exceed `OMERO_SCREEN_ZARR_MAX_GB`, the least recently used plate is evicted. If a single plate is larger than the whole cap, the build stops instead of evicting everything; raise the cap. Curation can span days in a separate Fiji/Mastodon session, so eviction would pull the data out from under you. **Pin** the plate in the [Tracks widget](../napari/user-guide/tracks_widget.html) to exempt it; the pin survives restarts. **Unpin** it when you are done. Caching a plate does not pin it. ::: {.callout-tip} The cache is always safe to delete (`rm -rf ~/omero-cache`): every plate rebuilds on demand from OMERO. Do so if you suspect orphaned files after a crash. ::: ::: {.callout-note} Older versions cached under `~/.cache/omero_screen`. If that folder exists and `~/omero-cache` does not, the first run logs a one-time notice. Rebuild your plates in the new location and delete the old folder, or set `OMERO_SCREEN_CACHE_PATH` to keep using it. ::: ## Contributing ### Development Set up a development environment, run the tests against a local OMERO server, and preview the documentation. ## Setup ```bash uv sync --dev uv run pre-commit install ``` The pre-commit hooks run ruff (lint and format) and the [ty](https://github.com/astral-sh/ty) type checker. Fix what they report rather than bypassing them with `--no-verify`. CI type-checks the whole workspace: ```bash uv run ty check ``` ty is pinned to an exact version in the dev group, because it is still beta and its diagnostics change between releases; bump it deliberately. It reads `# ty: ignore[]` comments, not mypy's `# type: ignore[]`. Configuration is in `[tool.ty]` in the root `pyproject.toml`. ## Tests Unit tests live in `tests/unit_tests`, one directory per package: ```bash uv run pytest # everything uv run pytest tests/unit_tests/omero_screen_tests # one package ``` End-to-end tests in `tests/e2e_tests` run against an OMERO server under production-like conditions. Run one by name: ```bash uv run omero-integration-test e2e_connection uv run omero-integration-test e2e_excel ``` ## Local OMERO test server A separate OMERO server for development runs in Docker on `127.0.0.2:4064` (user `root`, password `omero`): ```bash ./scripts/manage_test_server.sh start ./scripts/manage_test_server.sh status ./scripts/manage_test_server.sh stop ``` Import Operetta screens into it with `load_plates.sh`. Nothing is imported without `-x`: ```bash ./scripts/load_plates.sh -d /path/to/plates -x ``` [Try it on sample data](try-it.qmd) walks through a full run on this server. ## Documentation The docs are built with Great Docs: six sites, one per package, assembled into one tree. ```bash ./scripts/build_docs.sh # build everything into ./_site ./scripts/build_docs.sh --serve # build, then serve on localhost:8000 ./scripts/dev_docs.py # root site, rebuilt and reloaded on save ./scripts/dev_docs.py --package packages/cellview ``` Use `dev_docs.py` rather than `great-docs build --watch`, which watches copies of the sources and never sees your edits. CLI references are generated from the Click commands, so change the command's help text rather than writing option tables by hand. ## Commits and releases Work goes from an issue to a branch to a pull request (`Closes #N`), and is squash-merged once CI is green. Use conventional commits (`uv run cz commit`). After the merge, the release workflow bumps the version, tags it and updates the changelog. With squash merges, the **PR title** decides the release: | PR title type | Release | |---|---| | `fix(): …` | patch | | `feat(): …` | minor | | `docs`, `ci`, `chore`, `dev` | none | Don't run `cz bump` or create version tags by hand.