Visualization tab¶
This guide covers architecture and extension points for the Visualization tab.
Module layout¶
Visualization code lives in:
src/senoquant/tabs/visualization/frontend.pysrc/senoquant/tabs/visualization/backend.pysrc/senoquant/tabs/visualization/plots/
Key responsibilities:
frontend.py:- Builds the Qt UI (
VisualizationTab). - Collects marker selections and thresholds.
- Validates selected-marker thresholds before processing and save reruns.
- Runs preview generation and save actions.
backend.py:- Orchestrates plot handler execution.
- Routes temporary outputs into final files.
plots/:- Contains plot handler implementations.
- Handles dynamic plot discovery and registration.
Plot discovery and state¶
Plot classes are discovered dynamically by get_plot_registry() in
src/senoquant/tabs/visualization/plots/__init__.py.
Discovery behavior:
- Imports all modules under
plots/. - Collects subclasses of
SenoQuantPlot. - Uses each class
plot_typeas the dropdown key. - Sorts by class attribute
order.
Plot runtime state is stored in PlotConfig:
plot_id: stable identifier for the configured row.type_name: selected plot type.data: plot-specific payload (PlotDatasubclass).
PLOT_DATA_FACTORY maps plot type names to typed PlotData classes.
Runtime flow¶
Single run flow:
VisualizationTab._process_plots()validates selected-marker thresholds, then gathers markers/thresholds.- It calls
VisualizationBackend.process(..., save=False, cleanup=False)if validation passes. - Backend calls each handler's
plot(temp_dir, input_path, export_format, markers, thresholds). - Returned paths are stored in
VisualizationResult.plot_outputs. - Frontend renders preview files from those output paths.
Save flow:
VisualizationTab._save_plots()callsVisualizationBackend.save_result(...).- Backend routes/copies files to the chosen output directory.
- Output paths in
PlotExportResult.outputsare updated to final destinations. - If no routed files exist and save triggers a rerun, threshold validation is applied before rerunning.
Current built-in handlers¶
Spatial Plot(spatialplot.py): categorical marker assignment using thresholded first-match precedence.UMAP(umap.py): UMAP embedding from marker intensity features (threshold clipping supported).Double Expression(double_expression.py): two-marker overlap map with threshold gating.Neighborhood Enrichment(neighborhood.py): k-NN graph-based enrichment heatmap with permutation z-scores.
Plot handler contract¶
Plot handlers subclass SenoQuantPlot from
src/senoquant/tabs/visualization/plots/base.py.
Required class attributes:
plot_type: user-facing plot name in the dropdown.order: integer sort key in the registry.
Required methods:
build(self): build plot-specific controls (optional if no custom UI).plot(self, temp_dir, input_path, export_format, markers=None, thresholds=None): generate outputs and return an iterable ofPath.
Behavior notes:
- Handlers may return explicit output paths, or return
[]and write files intotemp_dir. - Backend will fallback to routing all files in
temp_dirwhen explicit paths are not returned.
Threshold contract (frontend):
- Selected markers must have parseable numeric threshold text before Process is allowed.
- Thresholds can be auto-populated from a JSON file in the input folder, but this is best-effort key matching.
Adding a new visualization plot¶
- Add a module under
src/senoquant/tabs/visualization/plots/. - Define a
PlotDatasubclass if typed state is needed. - Implement a
SenoQuantPlotsubclass withplot_typeandorder. - Implement
plot(...)to produce outputs in the providedtemp_dir. - Register typed data in
PLOT_DATA_FACTORYwhen applicable. - Add tests under
tests/senoquant/tabs/visualization/.
Minimal skeleton:
from pathlib import Path
from typing import Iterable
from senoquant.tabs.visualization.plots.base import PlotData, SenoQuantPlot
class MyPlotData(PlotData):
pass
class MyPlot(SenoQuantPlot):
plot_type = "My Plot"
order = 30
def build(self) -> None:
pass
def plot(
self,
temp_dir: Path,
input_path: Path,
export_format: str,
markers: list[str] | None = None,
thresholds: dict[str, float] | None = None,
) -> Iterable[Path]:
output_file = temp_dir / f"my_plot.{export_format}"
# write output_file
return [output_file]
Dependency notes¶
Dependency loading is currently mixed:
SpatialPlotandDoubleExpressionPlotimportpandas/matplotlibinsideplot().UMAPPlotimportspandas,matplotlib, andumap-learnat module load time (src/senoquant/tabs/visualization/plots/umap.py).NeighborhoodEnrichmentPlotimportsnumpy,pandas,matplotlib, andscipy(cKDTree) at module load time (src/senoquant/tabs/visualization/plots/neighborhood.py).
When adding new dependencies:
- Prefer local imports inside handler methods.
- Gracefully return
[]with a clear message when dependency import fails.