Contributing Guidelines#

Note

This page is under construction. Check back soon for detailed contributing guidelines.

Getting Started#

This section will provide information on how to get started with contributing to Phenotypic.

Code Standards#

This section will describe the coding standards and style guidelines for Phenotypic.

Documenting measurements (MeasureFeatures vs MeasurementInfo)#

Measurement documentation is split across two places, and the two must not overlap:

  • The operation (a MeasureFeatures subclass) documents what its parameters mean and gives a high-level overview of the measurements it produces — what the operation does and when to reach for it. Keep this at the “overview” altitude; do not restate each output column here.

  • The measurement schema (the MeasurementInfo enum and its members) carries the detailed, per-column explanation of the measurements themselves — what each value is, how it is computed, and how to read the measurement output (the prefixed column header, units, and range). Each member’s desc is the canonical per-column documentation.

Why the split matters: the deliverables README.md generator (src/phenotypic/_cli/_cli_readme_generator.py) maps each configured MeasureFeatures op to its MeasurementInfo enum(s) and emits every member’s desc verbatim as the public column reference. The enum desc is therefore what end users read in the run output — write it for them. (Author only label and desc on a member; never author bio_desc — see the Gotchas in the root CLAUDE.md and the adding-an-operation skill.)

Adding a tuning objective (Scorer)#

A tuning objective is a Scorer. Every value the tuner optimizes is a bounded cost in [0, 1] (0 = perfect, 1 = worst) and the optimizer minimizes it. You emit your metric’s natural value and declare its sense; the framework orients, aggregates, and combines.

The contract (canonical in the Scorer base-class docstring and src/phenotypic/tune/CLAUDE.md — keep all three in sync):

  1. Subclass Scorer and implement _score_terms(image, measurements) -> dict[str, float] returning your natural per-term values. Do not flip or normalize by hand — a divergence stays a divergence, Dice stays Dice.

  2. Declare the sense with the _TERM_SENSE class variable: Sense.LOWER_BETTER (the default) if a larger value is worse; Sense.HIGHER_BETTER if a larger value is better (Dice, IoU, ICC, solidity).

  3. Supply an anchor only for an unbounded term by overriding _term_anchor to return the value at which cost should reach 0.5 (for a QC-backed term, its check’s fail_threshold). Bounded [0, 1] terms need nothing.

  4. Do not add scalarization parameters. The utopia shift ε, the augmentation coefficient ρ, per-axis normalization, and default weights are all framework-derived — a scorer never exposes them.

  5. Register the class: re-export it from phenotypic.tune (its __init__) and the class registry, or the GUI and from_json deserialization cannot discover it.

Minimal example — a reference-free scorer that rewards round colonies (Solidity is already [0, 1] and higher-is-better, so it only declares the sense):

from typing import Any, ClassVar

import pandas as pd

from phenotypic.tune.score import Scorer
from phenotypic.tune.score._orient import Sense


class SolidityScorer(Scorer):
    """Reward compact, non-jagged colonies (mean Solidity, higher = better)."""

    _TERM_SENSE: ClassVar[Sense] = Sense.HIGHER_BETTER

    def _score_terms(
        self, image: Any, measurements: pd.DataFrame
    ) -> dict[str, float]:
        # Solidity is bounded [0, 1]; emit it raw. The base score_image
        # complements it into cost (1 - value) because _TERM_SENSE is
        # HIGHER_BETTER — you write no flip.
        return {"Solidity": float(measurements["Shape_Solidity"].mean())}

The base score_image() template method then turns each natural term into cost via to_cost; the Evaluator robust-aggregates (median + λ·IQR, clamped) and the optimizer minimizes. A CompositeScorer combines several scorers’ per-child cost with an augmented Tchebycheff scalarization (worst-axis-dominant by default) — see the explainer docs/superpowers/explain/tune-with-optuna.md for the math.

Pull Request Workflow#

This section will explain the process for submitting pull requests to the Phenotypic repository.

Issue Reporting#

This section will provide guidelines for reporting issues and bugs in Phenotypic.