Source code for phenotypic.tune._tune_cli._auto_space
"""``--auto-space`` — infer a reviewable search space from a pipeline.
``run_auto_space`` mines a configured ``ImagePipeline`` with
:func:`~phenotypic.tune.infer_search_space` and persists the resulting
:class:`~phenotypic.tune.InferredSearchSpace` proposal to
``deliverables/tuning_spec.json`` (the proposal round-trips through JSON). It
runs **no** engine — there is no ``trials.parquet``, no scoring, no winner. The
point is to hand the user a generous, flagged-for-review candidate space they
can edit before launching ``run``.
:func:`_render_review_table` is a **pure** ``InferredSearchSpace -> str``
function (no Dash, no I/O) that produces the non-blocking terminal summary:
trusted knobs (``✓``), knobs needing review (``⚠``), and excluded fields with
their reason.
Example:
>>> from phenotypic import ImagePipeline
>>> from phenotypic.enhance import GaussianBlur
>>> from phenotypic.detect import OtsuDetector
>>> pipe = ImagePipeline(ops=[GaussianBlur(sigma=2.0), OtsuDetector()])
>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from phenotypic.sdk_ import _io_constants as io
>>> with TemporaryDirectory() as d:
... proposal = run_auto_space(pipe, d)
... io.tuning_spec_path(Path(d)).exists()
True
>>> "✓" in _render_review_table(proposal)
True
"""
from __future__ import annotations
from pathlib import Path
from phenotypic.sdk_ import _io_constants as io
from phenotypic.sdk_ import atomic_write_text
from .._search_space import InferredSearchSpace, infer_search_space
#: Marker glyphs for the review table (a closed set; never bare strings inline).
_TRUSTED_MARK = "✓"
_REVIEW_MARK = "⚠"
_EXCLUDED_MARK = "✗"
[docs]
def run_auto_space(pipeline: object, output_dir: str | Path) -> InferredSearchSpace:
"""Infer a search-space proposal from ``pipeline`` and persist it.
Mines ``pipeline`` with :func:`~phenotypic.tune.infer_search_space` (one-level
nested recursion on) and writes the proposal to
``deliverables/tuning_spec.json`` under ``output_dir``. No engine is run, so
no ``trials.parquet`` / ``best_pipeline.json`` / ``param_importance.json`` is
produced — this is the inspect-before-you-tune step.
Args:
pipeline: A live ``ImagePipeline`` whose ops are mined for tunable
fields.
output_dir: The run directory; the proposal lands at
``io.tuning_spec_path(output_dir)``.
Returns:
The :class:`~phenotypic.tune.InferredSearchSpace` proposal (also written
to disk).
"""
output_dir = Path(output_dir)
io.deliverables_dir(output_dir).mkdir(parents=True, exist_ok=True)
proposal = infer_search_space(pipeline)
atomic_write_text(
io.tuning_spec_path(output_dir), proposal.model_dump_json(indent=2)
)
return proposal
def _render_review_table(inferred: InferredSearchSpace) -> str:
"""Render a proposal as a plain-text review table (pure; no I/O).
Each knob renders on one line marked ``✓`` (trusted) or ``⚠`` (needs review)
with its key, domain kind, and provenance source. Each excluded field renders
marked ``✗`` with its key and exclusion reason. A trailing summary line counts
trusted / review / excluded entries.
Args:
inferred: The proposal to render.
Returns:
A newline-joined table string (always non-empty — at least the header
and summary lines are present).
"""
lines: list[str] = ["Inferred search space:"]
for knob in inferred.knobs:
mark = _REVIEW_MARK if knob.needs_review else _TRUSTED_MARK
lines.append(
f" {mark} {knob.key} [{knob.domain.kind}] source={knob.source}"
)
for excl in inferred.excluded:
lines.append(
f" {_EXCLUDED_MARK} {excl.key} excluded: {excl.reason}"
)
n_trusted = inferred.n_knobs - inferred.n_needs_review
lines.append(
f"Summary: {n_trusted} trusted, {inferred.n_needs_review} need review, "
f"{inferred.n_excluded} excluded."
)
return "\n".join(lines)