reader-workbench 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reader_workbench/__init__.py +22 -0
- reader_workbench/__main__.py +4 -0
- reader_workbench/_version.py +17 -0
- reader_workbench/api/__init__.py +74 -0
- reader_workbench/api/_record_reads.py +75 -0
- reader_workbench/api/artifacts.py +79 -0
- reader_workbench/api/facade.py +538 -0
- reader_workbench/api/models.py +285 -0
- reader_workbench/api/notebooks.py +63 -0
- reader_workbench/contracts/__init__.py +18 -0
- reader_workbench/contracts/builtins/__init__.py +36 -0
- reader_workbench/contracts/builtins/cytometry.py +140 -0
- reader_workbench/contracts/builtins/four_state_event_window.py +214 -0
- reader_workbench/contracts/builtins/generic.py +21 -0
- reader_workbench/contracts/builtins/logic.py +149 -0
- reader_workbench/contracts/builtins/plate_reader.py +47 -0
- reader_workbench/contracts/catalog.py +257 -0
- reader_workbench/contracts/model.py +109 -0
- reader_workbench/domains/__init__.py +1 -0
- reader_workbench/domains/cytometry/__init__.py +3 -0
- reader_workbench/domains/cytometry/analysis/__init__.py +29 -0
- reader_workbench/domains/cytometry/analysis/events.py +182 -0
- reader_workbench/domains/cytometry/analysis/gating.py +175 -0
- reader_workbench/domains/cytometry/analysis/workflow.py +248 -0
- reader_workbench/domains/cytometry/io/__init__.py +3 -0
- reader_workbench/domains/cytometry/io/fcs.py +135 -0
- reader_workbench/domains/cytometry/plots/__init__.py +5 -0
- reader_workbench/domains/cytometry/plots/diagnostic.py +155 -0
- reader_workbench/domains/logic/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/pairs.py +661 -0
- reader_workbench/domains/logic/four_state_vector/__init__.py +7 -0
- reader_workbench/domains/logic/four_state_vector/builder.py +321 -0
- reader_workbench/domains/logic/four_state_vector/collection/__init__.py +20 -0
- reader_workbench/domains/logic/four_state_vector/collection/checks.py +49 -0
- reader_workbench/domains/logic/four_state_vector/collection/constants.py +29 -0
- reader_workbench/domains/logic/four_state_vector/collection/model.py +19 -0
- reader_workbench/domains/logic/four_state_vector/collection/render.py +383 -0
- reader_workbench/domains/logic/four_state_vector/collection/sources.py +185 -0
- reader_workbench/domains/logic/four_state_vector/config.py +214 -0
- reader_workbench/domains/logic/four_state_vector/diagnostic.py +361 -0
- reader_workbench/domains/logic/four_state_vector/heatmap.py +86 -0
- reader_workbench/domains/logic/four_state_vector/math.py +191 -0
- reader_workbench/domains/logic/four_state_vector/reference.py +85 -0
- reader_workbench/domains/logic/four_state_vector/selection.py +228 -0
- reader_workbench/domains/logic/four_state_vector/treatment_semantics.py +51 -0
- reader_workbench/domains/logic/four_state_vector/validation.py +19 -0
- reader_workbench/domains/logic/logic_symmetry/__init__.py +3 -0
- reader_workbench/domains/logic/logic_symmetry/encodings.py +93 -0
- reader_workbench/domains/logic/logic_symmetry/extract_corners.py +192 -0
- reader_workbench/domains/logic/logic_symmetry/main.py +236 -0
- reader_workbench/domains/logic/logic_symmetry/metrics.py +96 -0
- reader_workbench/domains/logic/logic_symmetry/overlay.py +129 -0
- reader_workbench/domains/logic/logic_symmetry/prep.py +138 -0
- reader_workbench/domains/logic/logic_symmetry/render.py +356 -0
- reader_workbench/domains/logic/treatment_columns.py +42 -0
- reader_workbench/domains/plate_reader/__init__.py +1 -0
- reader_workbench/domains/plate_reader/analysis/__init__.py +14 -0
- reader_workbench/domains/plate_reader/analysis/fold_change.py +474 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/__init__.py +21 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/aggregation.py +191 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contract_fields.py +50 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contracts.py +320 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/design_dispositions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/disposition_records.py +140 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/event_sensitivity.py +27 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/materialize.py +274 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/observation_resampling.py +97 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/reduction.py +62 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/seeds.py +15 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/sources.py +308 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusion_validation.py +94 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/timepoints.py +76 -0
- reader_workbench/domains/plate_reader/io/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/sample_map.py +65 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_kinetic.py +141 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_parser.py +295 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_shared.py +195 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_snapshot.py +130 -0
- reader_workbench/domains/plate_reader/ordering.py +59 -0
- reader_workbench/domains/plate_reader/plots/__init__.py +15 -0
- reader_workbench/domains/plate_reader/plots/_data.py +29 -0
- reader_workbench/domains/plate_reader/plots/common.py +346 -0
- reader_workbench/domains/plate_reader/plots/distributions.py +324 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych.py +525 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych_render.py +195 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/__init__.py +26 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic.py +295 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_components.py +149 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_render.py +308 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_style.py +51 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/schema.py +8 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/summary.py +140 -0
- reader_workbench/domains/plate_reader/plots/grouping.py +53 -0
- reader_workbench/domains/plate_reader/plots/panels/__init__.py +12 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot.py +161 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot_data.py +91 -0
- reader_workbench/domains/plate_reader/plots/panels/time_series.py +296 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic.py +435 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic_render.py +300 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/__init__.py +315 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/planning.py +168 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/__init__.py +205 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/inputs.py +103 -0
- reader_workbench/domains/plate_reader/plots/time_series.py +317 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/__init__.py +479 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/planning.py +283 -0
- reader_workbench/domains/time_series/__init__.py +29 -0
- reader_workbench/domains/time_series/aggregation.py +60 -0
- reader_workbench/domains/time_series/contracts.py +368 -0
- reader_workbench/domains/time_series/reduction.py +395 -0
- reader_workbench/errors.py +55 -0
- reader_workbench/maintenance/__init__.py +6 -0
- reader_workbench/maintenance/docs.py +335 -0
- reader_workbench/maintenance/model.py +28 -0
- reader_workbench/maintenance/release.py +39 -0
- reader_workbench/maintenance/skills.py +124 -0
- reader_workbench/plotting/__init__.py +20 -0
- reader_workbench/plotting/mpl.py +56 -0
- reader_workbench/plotting/sinks.py +69 -0
- reader_workbench/plotting/style.py +175 -0
- reader_workbench/plotting/utils.py +27 -0
- reader_workbench/plugins/__init__.py +1 -0
- reader_workbench/plugins/catalog.py +33 -0
- reader_workbench/plugins/export/__init__.py +0 -0
- reader_workbench/plugins/export/_paths.py +21 -0
- reader_workbench/plugins/export/csv.py +41 -0
- reader_workbench/plugins/export/xlsx.py +44 -0
- reader_workbench/plugins/ingest/__init__.py +0 -0
- reader_workbench/plugins/ingest/_discovery.py +58 -0
- reader_workbench/plugins/ingest/discovery_policy.py +66 -0
- reader_workbench/plugins/ingest/flow_cytometer.py +139 -0
- reader_workbench/plugins/ingest/synergy_h1.py +234 -0
- reader_workbench/plugins/manifests/__init__.py +1 -0
- reader_workbench/plugins/manifests/export.py +29 -0
- reader_workbench/plugins/manifests/ingest.py +29 -0
- reader_workbench/plugins/manifests/plot.py +161 -0
- reader_workbench/plugins/manifests/transform.py +172 -0
- reader_workbench/plugins/manifests/validator.py +18 -0
- reader_workbench/plugins/plot/__init__.py +0 -0
- reader_workbench/plugins/plot/_shared.py +55 -0
- reader_workbench/plugins/plot/cytometry_diagnostic.py +54 -0
- reader_workbench/plugins/plot/distributions.py +58 -0
- reader_workbench/plugins/plot/dual_reporter_triptych.py +224 -0
- reader_workbench/plugins/plot/four_state_event_window_diagnostic.py +133 -0
- reader_workbench/plugins/plot/four_state_event_window_summary.py +53 -0
- reader_workbench/plugins/plot/four_state_vector_collection.py +45 -0
- reader_workbench/plugins/plot/four_state_vector_diagnostic.py +105 -0
- reader_workbench/plugins/plot/four_state_vector_heatmap.py +63 -0
- reader_workbench/plugins/plot/logic_symmetry.py +56 -0
- reader_workbench/plugins/plot/single_reporter_diagnostic.py +279 -0
- reader_workbench/plugins/plot/snapshot_barplot.py +65 -0
- reader_workbench/plugins/plot/snapshot_heatmap.py +104 -0
- reader_workbench/plugins/plot/time_series.py +114 -0
- reader_workbench/plugins/plot/ts_and_snap.py +210 -0
- reader_workbench/plugins/transform/__init__.py +0 -0
- reader_workbench/plugins/transform/_four_state_vector.py +204 -0
- reader_workbench/plugins/transform/_labeling.py +109 -0
- reader_workbench/plugins/transform/alias.py +70 -0
- reader_workbench/plugins/transform/assay_labels.py +62 -0
- reader_workbench/plugins/transform/blank.py +79 -0
- reader_workbench/plugins/transform/crosstalk_pairs.py +180 -0
- reader_workbench/plugins/transform/cytometry_gating.py +120 -0
- reader_workbench/plugins/transform/fold_change.py +79 -0
- reader_workbench/plugins/transform/four_state_event_window.py +93 -0
- reader_workbench/plugins/transform/four_state_vector.py +62 -0
- reader_workbench/plugins/transform/four_state_vector_collection.py +41 -0
- reader_workbench/plugins/transform/logic_symmetry.py +67 -0
- reader_workbench/plugins/transform/outlier_filter.py +60 -0
- reader_workbench/plugins/transform/overflow.py +197 -0
- reader_workbench/plugins/transform/ratio.py +237 -0
- reader_workbench/plugins/transform/sample_map.py +170 -0
- reader_workbench/plugins/transform/sample_metadata.py +94 -0
- reader_workbench/plugins/validator/__init__.py +1 -0
- reader_workbench/plugins/validator/to_tidy_plus_map.py +155 -0
- reader_workbench/protocols/__init__.py +80 -0
- reader_workbench/protocols/_builtins_plate_reader_growth.py +179 -0
- reader_workbench/protocols/_builtins_plate_reader_variants.py +274 -0
- reader_workbench/protocols/builtins.py +1656 -0
- reader_workbench/protocols/compiler.py +22 -0
- reader_workbench/protocols/compilers/__init__.py +1 -0
- reader_workbench/protocols/compilers/common.py +100 -0
- reader_workbench/protocols/compilers/cytometry.py +87 -0
- reader_workbench/protocols/compilers/generic.py +14 -0
- reader_workbench/protocols/compilers/logic.py +245 -0
- reader_workbench/protocols/compilers/plate_reader.py +937 -0
- reader_workbench/protocols/compilers/plate_reader_pipeline.py +197 -0
- reader_workbench/protocols/model.py +1486 -0
- reader_workbench/protocols/semantic_coverage.py +234 -0
- reader_workbench/runtime/__init__.py +12 -0
- reader_workbench/runtime/builtin.py +23 -0
- reader_workbench/runtime/model.py +42 -0
- reader_workbench/workbench/__init__.py +60 -0
- reader_workbench/workbench/assets/__init__.py +22 -0
- reader_workbench/workbench/assets/types.py +118 -0
- reader_workbench/workbench/audit/__init__.py +5 -0
- reader_workbench/workbench/audit/experiments.py +307 -0
- reader_workbench/workbench/audit/staging.py +187 -0
- reader_workbench/workbench/cli/__init__.py +51 -0
- reader_workbench/workbench/cli/_lazy.py +9 -0
- reader_workbench/workbench/cli/_records_view.py +150 -0
- reader_workbench/workbench/cli/_surface_execution.py +443 -0
- reader_workbench/workbench/cli/audit.py +95 -0
- reader_workbench/workbench/cli/automation.py +229 -0
- reader_workbench/workbench/cli/demo.py +46 -0
- reader_workbench/workbench/cli/dop.py +91 -0
- reader_workbench/workbench/cli/experiments.py +635 -0
- reader_workbench/workbench/cli/helpers.py +232 -0
- reader_workbench/workbench/cli/main.py +59 -0
- reader_workbench/workbench/cli/maintenance.py +82 -0
- reader_workbench/workbench/cli/notebooks.py +260 -0
- reader_workbench/workbench/cli/pagination.py +117 -0
- reader_workbench/workbench/cli/protocols.py +336 -0
- reader_workbench/workbench/cli/shared.py +309 -0
- reader_workbench/workbench/cli/surfaces.py +534 -0
- reader_workbench/workbench/cli/verification.py +128 -0
- reader_workbench/workbench/commands.py +10 -0
- reader_workbench/workbench/config/__init__.py +47 -0
- reader_workbench/workbench/config/identity.py +13 -0
- reader_workbench/workbench/config/load.py +405 -0
- reader_workbench/workbench/config/model.py +274 -0
- reader_workbench/workbench/context.py +26 -0
- reader_workbench/workbench/decl/__init__.py +31 -0
- reader_workbench/workbench/decl/build.py +190 -0
- reader_workbench/workbench/decl/model.py +81 -0
- reader_workbench/workbench/dop/__init__.py +12 -0
- reader_workbench/workbench/dop/builtins.py +261 -0
- reader_workbench/workbench/dop/model.py +209 -0
- reader_workbench/workbench/engine/__init__.py +42 -0
- reader_workbench/workbench/engine/_shared.py +76 -0
- reader_workbench/workbench/engine/contracts.py +283 -0
- reader_workbench/workbench/engine/execution.py +326 -0
- reader_workbench/workbench/engine/file_outputs.py +260 -0
- reader_workbench/workbench/engine/inputs.py +161 -0
- reader_workbench/workbench/engine/invocations.py +507 -0
- reader_workbench/workbench/engine/planning.py +72 -0
- reader_workbench/workbench/engine/runtime.py +464 -0
- reader_workbench/workbench/engine/setup.py +149 -0
- reader_workbench/workbench/engine/validation.py +684 -0
- reader_workbench/workbench/experiment/__init__.py +47 -0
- reader_workbench/workbench/experiment/model.py +381 -0
- reader_workbench/workbench/experiments.py +133 -0
- reader_workbench/workbench/graph/__init__.py +47 -0
- reader_workbench/workbench/graph/nodes.py +102 -0
- reader_workbench/workbench/graph/normalize.py +177 -0
- reader_workbench/workbench/graph/refs.py +148 -0
- reader_workbench/workbench/input_discovery.py +19 -0
- reader_workbench/workbench/inspection/__init__.py +3 -0
- reader_workbench/workbench/inspection/catalogs.py +128 -0
- reader_workbench/workbench/inspection/common.py +92 -0
- reader_workbench/workbench/inspection/dop.py +64 -0
- reader_workbench/workbench/inspection/experiments.py +449 -0
- reader_workbench/workbench/inspection/inventory.py +68 -0
- reader_workbench/workbench/inspection/protocols.py +368 -0
- reader_workbench/workbench/inspection/readiness.py +333 -0
- reader_workbench/workbench/inspection/reports.py +367 -0
- reader_workbench/workbench/inspection/results.py +166 -0
- reader_workbench/workbench/inspection/runtime.py +287 -0
- reader_workbench/workbench/inspection/semantics.py +192 -0
- reader_workbench/workbench/inspection/validation.py +30 -0
- reader_workbench/workbench/notebooks/__init__.py +17 -0
- reader_workbench/workbench/notebooks/_launch_registry.py +112 -0
- reader_workbench/workbench/notebooks/_launch_runtime.py +104 -0
- reader_workbench/workbench/notebooks/components/__init__.py +21 -0
- reader_workbench/workbench/notebooks/components/deliverables.py +403 -0
- reader_workbench/workbench/notebooks/components/overview.py +119 -0
- reader_workbench/workbench/notebooks/eda.marimo.py.txt +153 -0
- reader_workbench/workbench/notebooks/launch.py +274 -0
- reader_workbench/workbench/notebooks/presentation.py +136 -0
- reader_workbench/workbench/notebooks/scaffold.py +60 -0
- reader_workbench/workbench/ontology.py +78 -0
- reader_workbench/workbench/paths.py +44 -0
- reader_workbench/workbench/ports/__init__.py +31 -0
- reader_workbench/workbench/ports/model.py +168 -0
- reader_workbench/workbench/records/__init__.py +44 -0
- reader_workbench/workbench/records/epoch.py +329 -0
- reader_workbench/workbench/records/evidence.py +247 -0
- reader_workbench/workbench/records/identity.py +87 -0
- reader_workbench/workbench/records/locking.py +185 -0
- reader_workbench/workbench/records/model.py +711 -0
- reader_workbench/workbench/records/sources.py +73 -0
- reader_workbench/workbench/records/store.py +1022 -0
- reader_workbench/workbench/records/verification.py +998 -0
- reader_workbench/workbench/registry.py +333 -0
- reader_workbench/workbench/spec_overrides.py +215 -0
- reader_workbench-1.0.0.dist-info/METADATA +91 -0
- reader_workbench-1.0.0.dist-info/RECORD +293 -0
- reader_workbench-1.0.0.dist-info/WHEEL +5 -0
- reader_workbench-1.0.0.dist-info/entry_points.txt +2 -0
- reader_workbench-1.0.0.dist-info/licenses/LICENSE +21 -0
- reader_workbench-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Render a provenance-aware four-state event-window diagnostic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from reader_workbench.plotting.style import use_style
|
|
12
|
+
|
|
13
|
+
from .diagnostic import FourStateEventWindowDiagnostic, prepare_four_state_event_window_diagnostic
|
|
14
|
+
from .diagnostic_components import draw_component_panel, has_quality_flags
|
|
15
|
+
from .diagnostic_style import BOUND_MARKERS, STATE_COLORS, STATE_MARKERS, validated_axis_labels, validated_state_labels
|
|
16
|
+
from .schema import STATE_ORDER
|
|
17
|
+
|
|
18
|
+
_REFERENCE_COLOR = "#7C8793"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def render_four_state_event_window_diagnostic(
|
|
22
|
+
traces: pd.DataFrame,
|
|
23
|
+
designs: pd.DataFrame,
|
|
24
|
+
*,
|
|
25
|
+
source_experiment_id: str,
|
|
26
|
+
design_id: str,
|
|
27
|
+
reduction_id: str,
|
|
28
|
+
pre_window_duration_h: float | None,
|
|
29
|
+
axis_labels: Mapping[str, str] | None = None,
|
|
30
|
+
state_labels: Mapping[str, str] | None = None,
|
|
31
|
+
reference_label: str = "reference",
|
|
32
|
+
title: str | None = None,
|
|
33
|
+
) -> Any:
|
|
34
|
+
"""Render observed traces, descriptive dispersion, and event sensitivity in one row."""
|
|
35
|
+
|
|
36
|
+
diagnostic = prepare_four_state_event_window_diagnostic(
|
|
37
|
+
traces,
|
|
38
|
+
designs,
|
|
39
|
+
source_experiment_id=source_experiment_id,
|
|
40
|
+
design_id=design_id,
|
|
41
|
+
reduction_id=reduction_id,
|
|
42
|
+
pre_window_duration_h=pre_window_duration_h,
|
|
43
|
+
)
|
|
44
|
+
with use_style(
|
|
45
|
+
{
|
|
46
|
+
"axes_titleweight": "regular",
|
|
47
|
+
"figure_figsize": (18.5, 6.0),
|
|
48
|
+
"font_size": 13.0,
|
|
49
|
+
"axes_labelsize": 13.0,
|
|
50
|
+
"axes_titlesize": 15.0,
|
|
51
|
+
"xtick_labelsize": 11.5,
|
|
52
|
+
"ytick_labelsize": 11.5,
|
|
53
|
+
"legend_fontsize": 11.5,
|
|
54
|
+
}
|
|
55
|
+
):
|
|
56
|
+
from matplotlib.figure import Figure # noqa: PLC0415
|
|
57
|
+
from matplotlib.lines import Line2D # noqa: PLC0415
|
|
58
|
+
|
|
59
|
+
figure = Figure(
|
|
60
|
+
figsize=(18.5, 6.0),
|
|
61
|
+
constrained_layout=True,
|
|
62
|
+
)
|
|
63
|
+
axes = figure.subplots(
|
|
64
|
+
1,
|
|
65
|
+
4,
|
|
66
|
+
gridspec_kw={"width_ratios": (1.0, 1.0, 1.0, 0.95)},
|
|
67
|
+
)
|
|
68
|
+
figure.set_gid("four-state-event-window-diagnostic")
|
|
69
|
+
labels = validated_axis_labels(axis_labels)
|
|
70
|
+
states = validated_state_labels(state_labels)
|
|
71
|
+
for axis, signal_kind, panel_title in (
|
|
72
|
+
(axes[0], "growth", "Growth"),
|
|
73
|
+
(axes[1], "response", "Response"),
|
|
74
|
+
(axes[2], "magnitude", "Signal vs reference"),
|
|
75
|
+
):
|
|
76
|
+
_draw_trace_panel(axis, diagnostic=diagnostic, signal_kind=signal_kind)
|
|
77
|
+
axis.set_title(panel_title, pad=12)
|
|
78
|
+
axis.set_xlabel("Time from event estimate (h)", labelpad=8)
|
|
79
|
+
axis.set_ylabel(labels[signal_kind], labelpad=8)
|
|
80
|
+
draw_component_panel(
|
|
81
|
+
axes[3],
|
|
82
|
+
diagnostic=diagnostic,
|
|
83
|
+
axis_labels=labels,
|
|
84
|
+
reference_label=reference_label,
|
|
85
|
+
)
|
|
86
|
+
window_start, window_end = diagnostic.window
|
|
87
|
+
axes[3].set_title(f"{window_start:g}–{window_end:g} h mean", pad=12)
|
|
88
|
+
for axis in axes:
|
|
89
|
+
axis.set_box_aspect(1.0)
|
|
90
|
+
|
|
91
|
+
figure.suptitle(
|
|
92
|
+
title or f"{diagnostic.source_experiment_id} :: {diagnostic.design_id}",
|
|
93
|
+
fontsize=19.0,
|
|
94
|
+
fontweight="bold",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
legend = [
|
|
98
|
+
Line2D(
|
|
99
|
+
[0],
|
|
100
|
+
[0],
|
|
101
|
+
color=STATE_COLORS[state],
|
|
102
|
+
marker=STATE_MARKERS[state],
|
|
103
|
+
linewidth=1.8,
|
|
104
|
+
label=states[state],
|
|
105
|
+
)
|
|
106
|
+
for state in STATE_ORDER
|
|
107
|
+
]
|
|
108
|
+
if diagnostic.reference_design_id != diagnostic.design_id:
|
|
109
|
+
legend.append(
|
|
110
|
+
Line2D([0], [0], color=_REFERENCE_COLOR, linestyle="--", linewidth=1.7, label=reference_label)
|
|
111
|
+
)
|
|
112
|
+
legend.extend(
|
|
113
|
+
(
|
|
114
|
+
Line2D(
|
|
115
|
+
[0],
|
|
116
|
+
[0],
|
|
117
|
+
color="#64748b",
|
|
118
|
+
linewidth=5.0,
|
|
119
|
+
alpha=0.28,
|
|
120
|
+
label="event timing sensitivity",
|
|
121
|
+
),
|
|
122
|
+
Line2D(
|
|
123
|
+
[0],
|
|
124
|
+
[0],
|
|
125
|
+
color="#334155",
|
|
126
|
+
linewidth=1.4,
|
|
127
|
+
marker="|",
|
|
128
|
+
markersize=8.0,
|
|
129
|
+
markeredgewidth=1.1,
|
|
130
|
+
label=f"{100.0 * diagnostic.descriptive_interval_mass:g}% resampling interval",
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
if has_quality_flags(diagnostic):
|
|
135
|
+
legend.append(Line2D([0], [0], color="#7c2d12", marker="x", linestyle="none", label="quality/bound flag"))
|
|
136
|
+
figure.legend(
|
|
137
|
+
handles=legend,
|
|
138
|
+
loc="outside lower center",
|
|
139
|
+
ncol=min(len(legend), 8),
|
|
140
|
+
frameon=False,
|
|
141
|
+
columnspacing=1.5,
|
|
142
|
+
handletextpad=0.55,
|
|
143
|
+
)
|
|
144
|
+
return figure
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _draw_trace_panel(axis: Any, *, diagnostic: FourStateEventWindowDiagnostic, signal_kind: str) -> None:
|
|
148
|
+
rows = diagnostic.traces.loc[diagnostic.traces["signal_kind"].astype(str).eq(signal_kind)].copy()
|
|
149
|
+
rows["plot_value"] = rows["value"].astype(float)
|
|
150
|
+
if signal_kind in {"response", "magnitude"}:
|
|
151
|
+
rows["plot_value"] = np.log2(rows["plot_value"])
|
|
152
|
+
_draw_time_context(axis, diagnostic=diagnostic, signal_kind=signal_kind)
|
|
153
|
+
for state in STATE_ORDER:
|
|
154
|
+
selected = rows.loc[
|
|
155
|
+
rows["design_id"].astype(str).eq(diagnostic.design_id) & rows["state"].astype(str).eq(state)
|
|
156
|
+
]
|
|
157
|
+
_draw_observed_traces(
|
|
158
|
+
axis,
|
|
159
|
+
selected,
|
|
160
|
+
color=STATE_COLORS[state],
|
|
161
|
+
marker=STATE_MARKERS[state],
|
|
162
|
+
linestyle="-",
|
|
163
|
+
reference=False,
|
|
164
|
+
observation_stat=diagnostic.observation_stat,
|
|
165
|
+
interval_mass=diagnostic.descriptive_interval_mass,
|
|
166
|
+
gid="four-state-event-window-trace",
|
|
167
|
+
)
|
|
168
|
+
if signal_kind == "magnitude" and diagnostic.reference_design_id != diagnostic.design_id:
|
|
169
|
+
reference = rows.loc[
|
|
170
|
+
rows["design_id"].astype(str).eq(diagnostic.reference_design_id) & rows["state"].astype(str).eq(state)
|
|
171
|
+
]
|
|
172
|
+
_draw_observed_traces(
|
|
173
|
+
axis,
|
|
174
|
+
reference,
|
|
175
|
+
color=_REFERENCE_COLOR,
|
|
176
|
+
marker=STATE_MARKERS[state],
|
|
177
|
+
linestyle="--",
|
|
178
|
+
reference=True,
|
|
179
|
+
observation_stat=diagnostic.observation_stat,
|
|
180
|
+
interval_mass=diagnostic.descriptive_interval_mass,
|
|
181
|
+
gid="four-state-event-window-reference-trace",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _draw_time_context(axis: Any, *, diagnostic: FourStateEventWindowDiagnostic, signal_kind: str) -> None:
|
|
186
|
+
uncertainty = diagnostic.event_time_uncertainty_h
|
|
187
|
+
if uncertainty > 0.0:
|
|
188
|
+
interval = axis.axvspan(-uncertainty, uncertainty, color="#64748b", alpha=0.10, linewidth=0.0)
|
|
189
|
+
interval.set_gid("four-state-event-window-event-interval")
|
|
190
|
+
event = axis.axvline(0.0, color="#64748b", linewidth=0.9)
|
|
191
|
+
event.set_gid("four-state-event-window-event-estimate")
|
|
192
|
+
post = axis.axvspan(*diagnostic.window, color="#f0c36e", alpha=0.18, linewidth=0.0)
|
|
193
|
+
post.set_gid("four-state-event-window-reduction-window")
|
|
194
|
+
if signal_kind == "response" and diagnostic.pre_window is not None:
|
|
195
|
+
pre = axis.axvspan(*diagnostic.pre_window, color="#94a3b8", alpha=0.14, linewidth=0.0)
|
|
196
|
+
pre.set_gid("four-state-event-window-pre-window")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _draw_observed_traces(
|
|
200
|
+
axis: Any,
|
|
201
|
+
rows: pd.DataFrame,
|
|
202
|
+
*,
|
|
203
|
+
color: str,
|
|
204
|
+
marker: str,
|
|
205
|
+
linestyle: str,
|
|
206
|
+
reference: bool,
|
|
207
|
+
observation_stat: str,
|
|
208
|
+
interval_mass: float,
|
|
209
|
+
gid: str,
|
|
210
|
+
) -> None:
|
|
211
|
+
traces: list[pd.DataFrame] = []
|
|
212
|
+
for _, trace in rows.groupby("position", sort=True):
|
|
213
|
+
trace = trace.sort_values("time_from_event_h", kind="stable")
|
|
214
|
+
traces.append(trace)
|
|
215
|
+
flagged = trace.loc[
|
|
216
|
+
trace["value_policy_clipped"].astype(bool)
|
|
217
|
+
| trace["value_instrument_overflow"].astype(bool)
|
|
218
|
+
| trace["value_bound_kind"].astype(str).ne("exact")
|
|
219
|
+
]
|
|
220
|
+
if not flagged.empty:
|
|
221
|
+
axis.scatter(
|
|
222
|
+
flagged["time_from_event_h"],
|
|
223
|
+
flagged["plot_value"],
|
|
224
|
+
marker="x",
|
|
225
|
+
s=18.0,
|
|
226
|
+
color="#7c2d12",
|
|
227
|
+
zorder=4,
|
|
228
|
+
)
|
|
229
|
+
summary = summarize_observed_traces(
|
|
230
|
+
traces,
|
|
231
|
+
observation_stat=observation_stat,
|
|
232
|
+
interval_mass=interval_mass,
|
|
233
|
+
)
|
|
234
|
+
if summary is None:
|
|
235
|
+
raise ValueError("four-state event-window diagnostic requires observed trace points")
|
|
236
|
+
times, values, interval_low, interval_high = summary
|
|
237
|
+
band = axis.fill_between(
|
|
238
|
+
times,
|
|
239
|
+
interval_low,
|
|
240
|
+
interval_high,
|
|
241
|
+
color=color,
|
|
242
|
+
alpha=0.07 if reference else 0.14,
|
|
243
|
+
linewidth=0.0,
|
|
244
|
+
zorder=1,
|
|
245
|
+
)
|
|
246
|
+
band.set_gid("four-state-event-window-observation-interval")
|
|
247
|
+
(line,) = axis.plot(
|
|
248
|
+
times,
|
|
249
|
+
values,
|
|
250
|
+
color=color,
|
|
251
|
+
linestyle=linestyle,
|
|
252
|
+
linewidth=1.9 if not reference else 1.15,
|
|
253
|
+
marker=marker,
|
|
254
|
+
markersize=4.0 if not reference else 2.7,
|
|
255
|
+
markeredgecolor="white",
|
|
256
|
+
markeredgewidth=0.55 if not reference else 0.45,
|
|
257
|
+
alpha=0.78 if reference else 1.0,
|
|
258
|
+
markevery=4 if reference else None,
|
|
259
|
+
zorder=3,
|
|
260
|
+
)
|
|
261
|
+
line.set_gid(gid)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def summarize_observed_traces(
|
|
265
|
+
traces: list[pd.DataFrame],
|
|
266
|
+
*,
|
|
267
|
+
observation_stat: str,
|
|
268
|
+
interval_mass: float,
|
|
269
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray] | None:
|
|
270
|
+
if not traces:
|
|
271
|
+
return None
|
|
272
|
+
observed = pd.concat(
|
|
273
|
+
(
|
|
274
|
+
trace.loc[:, ["time_from_event_h", "plot_value"]].assign(trace_index=index)
|
|
275
|
+
for index, trace in enumerate(traces)
|
|
276
|
+
),
|
|
277
|
+
ignore_index=True,
|
|
278
|
+
)
|
|
279
|
+
observed_by_time = observed.groupby("time_from_event_h", sort=True, observed=True)
|
|
280
|
+
grouped = observed_by_time["plot_value"]
|
|
281
|
+
times = grouped.size().index.to_numpy(dtype=float)
|
|
282
|
+
center = (
|
|
283
|
+
grouped.mean().to_numpy(dtype=float) if observation_stat == "mean" else grouped.median().to_numpy(dtype=float)
|
|
284
|
+
)
|
|
285
|
+
tail = (1.0 - interval_mass) / 2.0
|
|
286
|
+
interval_low = grouped.quantile(tail).to_numpy(dtype=float)
|
|
287
|
+
interval_high = grouped.quantile(1.0 - tail).to_numpy(dtype=float)
|
|
288
|
+
trace_cohorts = observed_by_time["trace_index"].apply(lambda values: frozenset(values.tolist())).tolist()
|
|
289
|
+
break_indices = [
|
|
290
|
+
index
|
|
291
|
+
for index, (previous, current) in enumerate(zip(trace_cohorts, trace_cohorts[1:], strict=False), start=1)
|
|
292
|
+
if current != previous
|
|
293
|
+
]
|
|
294
|
+
if break_indices:
|
|
295
|
+
times = np.insert(times, break_indices, np.nan)
|
|
296
|
+
center = np.insert(center, break_indices, np.nan)
|
|
297
|
+
interval_low = np.insert(interval_low, break_indices, np.nan)
|
|
298
|
+
interval_high = np.insert(interval_high, break_indices, np.nan)
|
|
299
|
+
return times, center, interval_low, interval_high
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
__all__ = [
|
|
303
|
+
"BOUND_MARKERS",
|
|
304
|
+
"STATE_COLORS",
|
|
305
|
+
"STATE_MARKERS",
|
|
306
|
+
"render_four_state_event_window_diagnostic",
|
|
307
|
+
"summarize_observed_traces",
|
|
308
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Shared visual vocabulary for four-state event-window diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
|
|
7
|
+
STATE_COLORS = {
|
|
8
|
+
"00": "#596675",
|
|
9
|
+
"10": "#4F8C83",
|
|
10
|
+
"01": "#607CC8",
|
|
11
|
+
"11": "#C15D6E",
|
|
12
|
+
}
|
|
13
|
+
STATE_MARKERS = {"00": "o", "10": "s", "01": "^", "11": "D"}
|
|
14
|
+
BOUND_MARKERS = {"exact": "o", "lower": ">", "upper": "<", "indeterminate": "X"}
|
|
15
|
+
|
|
16
|
+
_DEFAULT_AXIS_LABELS = {
|
|
17
|
+
"growth": "Signal",
|
|
18
|
+
"response": "log$_2$(response signal)",
|
|
19
|
+
"magnitude": "log$_2$(magnitude signal)",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def validated_axis_labels(axis_labels: Mapping[str, str] | None) -> dict[str, str]:
|
|
24
|
+
"""Validate the three compiler-owned signal labels."""
|
|
25
|
+
|
|
26
|
+
labels = dict(_DEFAULT_AXIS_LABELS if axis_labels is None else axis_labels)
|
|
27
|
+
if set(labels) != set(_DEFAULT_AXIS_LABELS):
|
|
28
|
+
raise ValueError("four-state event-window diagnostic axis labels must define growth, response, and magnitude")
|
|
29
|
+
if any(not isinstance(value, str) or not value.strip() for value in labels.values()):
|
|
30
|
+
raise ValueError("four-state event-window diagnostic axis labels must be non-empty strings")
|
|
31
|
+
return {key: value.strip() for key, value in labels.items()}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validated_state_labels(state_labels: Mapping[str, str] | None) -> dict[str, str]:
|
|
35
|
+
"""Validate one display label for every canonical state."""
|
|
36
|
+
|
|
37
|
+
labels = {state: state for state in STATE_MARKERS} if state_labels is None else dict(state_labels)
|
|
38
|
+
if set(labels) != set(STATE_MARKERS):
|
|
39
|
+
raise ValueError("four-state event-window diagnostic state labels must define 00, 10, 01, and 11")
|
|
40
|
+
if any(not isinstance(value, str) or not value.strip() for value in labels.values()):
|
|
41
|
+
raise ValueError("four-state event-window diagnostic state labels must be non-empty strings")
|
|
42
|
+
return {key: value.strip() for key, value in labels.items()}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"BOUND_MARKERS",
|
|
47
|
+
"STATE_COLORS",
|
|
48
|
+
"STATE_MARKERS",
|
|
49
|
+
"validated_axis_labels",
|
|
50
|
+
"validated_state_labels",
|
|
51
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Shared four-state event-window plot coordinates."""
|
|
2
|
+
|
|
3
|
+
STATE_ORDER = ("00", "10", "01", "11")
|
|
4
|
+
RESPONSE_COLUMNS = tuple(f"r{state}" for state in STATE_ORDER)
|
|
5
|
+
MAGNITUDE_COLUMNS = tuple(f"b{state}" for state in STATE_ORDER)
|
|
6
|
+
COMPONENT_COLUMNS = (*RESPONSE_COLUMNS, *MAGNITUDE_COLUMNS)
|
|
7
|
+
|
|
8
|
+
__all__ = ["COMPONENT_COLUMNS", "MAGNITUDE_COLUMNS", "RESPONSE_COLUMNS", "STATE_ORDER"]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Selection and rendering for the canonical four-state event-window summary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from reader_workbench.plotting.style import use_style
|
|
11
|
+
|
|
12
|
+
from .schema import COMPONENT_COLUMNS, MAGNITUDE_COLUMNS, RESPONSE_COLUMNS, STATE_ORDER
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class FourStateEventWindowSummaryMatrix:
|
|
17
|
+
"""Ordered labels and values consumed by the summary heatmap."""
|
|
18
|
+
|
|
19
|
+
row_labels: tuple[str, ...]
|
|
20
|
+
component_labels: tuple[str, ...]
|
|
21
|
+
values: np.ndarray
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def four_state_event_window_summary_matrix(
|
|
25
|
+
designs: pd.DataFrame,
|
|
26
|
+
*,
|
|
27
|
+
primary_reduction_id: str,
|
|
28
|
+
experiment_ids: list[str] | tuple[str, ...] | None = None,
|
|
29
|
+
design_ids: list[str] | tuple[str, ...] | None = None,
|
|
30
|
+
maximum_rows: int = 64,
|
|
31
|
+
) -> FourStateEventWindowSummaryMatrix:
|
|
32
|
+
"""Select one reduction's non-reference designs and validate plot values."""
|
|
33
|
+
|
|
34
|
+
experiment_selection = _identity_filter(experiment_ids, label="experiment_ids")
|
|
35
|
+
design_selection = _identity_filter(design_ids, label="design_ids")
|
|
36
|
+
if isinstance(maximum_rows, bool) or not isinstance(maximum_rows, int) or maximum_rows < 1:
|
|
37
|
+
raise ValueError("four-state event-window summary maximum_rows must be a positive integer")
|
|
38
|
+
selected = designs.loc[
|
|
39
|
+
designs["reduction_id"].astype(str).eq(primary_reduction_id) & ~designs["is_reference"].astype(bool)
|
|
40
|
+
].copy()
|
|
41
|
+
if experiment_selection is not None:
|
|
42
|
+
selected = selected.loc[selected["experiment_id"].astype(str).isin(experiment_selection)]
|
|
43
|
+
if design_selection is not None:
|
|
44
|
+
selected = selected.loc[selected["design_id"].astype(str).isin(design_selection)]
|
|
45
|
+
if selected.empty:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"four-state event-window plot has no non-reference rows for reduction {primary_reduction_id!r} "
|
|
48
|
+
"under the configured experiment_ids/design_ids filters"
|
|
49
|
+
)
|
|
50
|
+
if len(selected) > maximum_rows:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"four-state event-window summary selected {len(selected)} rows, exceeding maximum_rows={maximum_rows}; "
|
|
53
|
+
"configure experiment_ids/design_ids or raise maximum_rows explicitly"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
selected = selected.sort_values(["experiment_id", "design_id"], kind="stable")
|
|
57
|
+
row_labels = tuple(selected["experiment_id"].astype(str) + " :: " + selected["design_id"].astype(str))
|
|
58
|
+
values = selected.loc[:, COMPONENT_COLUMNS].to_numpy(dtype=float)
|
|
59
|
+
if not np.isfinite(values).all():
|
|
60
|
+
raise ValueError("four-state event-window summary requires finite component values")
|
|
61
|
+
|
|
62
|
+
return FourStateEventWindowSummaryMatrix(
|
|
63
|
+
row_labels=row_labels,
|
|
64
|
+
component_labels=COMPONENT_COLUMNS,
|
|
65
|
+
values=values,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def render_four_state_event_window_summary(
|
|
70
|
+
designs: pd.DataFrame,
|
|
71
|
+
*,
|
|
72
|
+
primary_reduction_id: str,
|
|
73
|
+
title: str,
|
|
74
|
+
experiment_ids: list[str] | tuple[str, ...] | None = None,
|
|
75
|
+
design_ids: list[str] | tuple[str, ...] | None = None,
|
|
76
|
+
maximum_rows: int = 64,
|
|
77
|
+
):
|
|
78
|
+
"""Render the selected response and anchored-magnitude components."""
|
|
79
|
+
|
|
80
|
+
summary = four_state_event_window_summary_matrix(
|
|
81
|
+
designs,
|
|
82
|
+
primary_reduction_id=primary_reduction_id,
|
|
83
|
+
experiment_ids=experiment_ids,
|
|
84
|
+
design_ids=design_ids,
|
|
85
|
+
maximum_rows=maximum_rows,
|
|
86
|
+
)
|
|
87
|
+
with use_style(
|
|
88
|
+
{
|
|
89
|
+
"axes_grid": False,
|
|
90
|
+
"figure_figsize": (11.0, max(4.5, 0.32 * len(summary.row_labels) + 2.0)),
|
|
91
|
+
}
|
|
92
|
+
):
|
|
93
|
+
import matplotlib.pyplot as plt # noqa: PLC0415
|
|
94
|
+
from matplotlib.colors import TwoSlopeNorm # noqa: PLC0415
|
|
95
|
+
|
|
96
|
+
figure, axes = plt.subplots(1, 2, sharey=True, constrained_layout=True)
|
|
97
|
+
response_values = summary.values[:, : len(RESPONSE_COLUMNS)]
|
|
98
|
+
magnitude_values = summary.values[:, len(RESPONSE_COLUMNS) :]
|
|
99
|
+
families = (
|
|
100
|
+
(axes[0], "Response", "response", response_values),
|
|
101
|
+
(axes[1], "Anchored magnitude", "anchored magnitude", magnitude_values),
|
|
102
|
+
)
|
|
103
|
+
for axis, panel_title, colorbar_label, values in families:
|
|
104
|
+
limit = max(float(np.max(np.abs(values))), np.finfo(float).eps)
|
|
105
|
+
image = axis.imshow(
|
|
106
|
+
values,
|
|
107
|
+
aspect="auto",
|
|
108
|
+
cmap="coolwarm",
|
|
109
|
+
norm=TwoSlopeNorm(vmin=-limit, vcenter=0.0, vmax=limit),
|
|
110
|
+
)
|
|
111
|
+
axis.set_xticks(range(len(STATE_ORDER)), labels=STATE_ORDER)
|
|
112
|
+
axis.set_title(panel_title)
|
|
113
|
+
axis.set_xlabel("state")
|
|
114
|
+
figure.colorbar(image, ax=axis, label=colorbar_label, shrink=0.8)
|
|
115
|
+
axes[0].set_yticks(range(len(summary.row_labels)), labels=summary.row_labels)
|
|
116
|
+
axes[0].set_ylabel("source :: design")
|
|
117
|
+
figure.suptitle(title)
|
|
118
|
+
return figure
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _identity_filter(values: list[str] | tuple[str, ...] | None, *, label: str) -> tuple[str, ...] | None:
|
|
122
|
+
if values is None:
|
|
123
|
+
return None
|
|
124
|
+
normalized = tuple(str(value).strip() for value in values)
|
|
125
|
+
if not normalized or any(not value for value in normalized):
|
|
126
|
+
raise ValueError(f"four-state event-window summary {label} must contain non-empty identities")
|
|
127
|
+
if len(normalized) != len(set(normalized)):
|
|
128
|
+
raise ValueError(f"four-state event-window summary {label} must not contain duplicates")
|
|
129
|
+
return normalized
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"COMPONENT_COLUMNS",
|
|
134
|
+
"MAGNITUDE_COLUMNS",
|
|
135
|
+
"RESPONSE_COLUMNS",
|
|
136
|
+
"FourStateEventWindowSummaryMatrix",
|
|
137
|
+
"STATE_ORDER",
|
|
138
|
+
"render_four_state_event_window_summary",
|
|
139
|
+
"four_state_event_window_summary_matrix",
|
|
140
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Plate-reader plot grouping helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from reader_workbench.domains.plate_reader.ordering import order_levels
|
|
10
|
+
|
|
11
|
+
GroupMatch = Literal["exact", "contains", "startswith", "endswith", "regex"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _match_value(val: str, needle: str, mode: GroupMatch) -> bool:
|
|
15
|
+
v = str(val)
|
|
16
|
+
n = str(needle)
|
|
17
|
+
if mode == "exact":
|
|
18
|
+
return v == n
|
|
19
|
+
if mode == "contains":
|
|
20
|
+
return n in v
|
|
21
|
+
if mode == "startswith":
|
|
22
|
+
return v.startswith(n)
|
|
23
|
+
if mode == "endswith":
|
|
24
|
+
return v.endswith(n)
|
|
25
|
+
if mode == "regex":
|
|
26
|
+
return re.search(n, v) is not None
|
|
27
|
+
raise ValueError(f"Unknown group_match: {mode}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_groups(
|
|
31
|
+
universe: Iterable[str],
|
|
32
|
+
groups: list[dict[str, list[str]]] | None,
|
|
33
|
+
*,
|
|
34
|
+
match: GroupMatch,
|
|
35
|
+
) -> list[tuple[str, list[str]]]:
|
|
36
|
+
values = list(map(str, universe))
|
|
37
|
+
if not groups:
|
|
38
|
+
return [("all", values)]
|
|
39
|
+
resolved: list[tuple[str, list[str]]] = []
|
|
40
|
+
for item in groups:
|
|
41
|
+
if not isinstance(item, dict) or len(item) != 1:
|
|
42
|
+
raise ValueError("groups must be list of single-key dicts")
|
|
43
|
+
label, needles = next(iter(item.items()))
|
|
44
|
+
members = [v for v in values if any(_match_value(v, n, match) for n in needles)]
|
|
45
|
+
resolved.append((str(label), members))
|
|
46
|
+
return resolved
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def ordered_groups(universe: Iterable[str]) -> list[str]:
|
|
50
|
+
return order_levels(list(map(str, universe)))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["GroupMatch", "ordered_groups", "resolve_groups"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .snapshot import draw_snapshot_panel
|
|
2
|
+
from .snapshot_data import SnapshotSelection, select_snapshot_rows, summarize_snapshot_values
|
|
3
|
+
from .time_series import draw_time_series_panel, marker_map_for_levels
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"SnapshotSelection",
|
|
7
|
+
"draw_snapshot_panel",
|
|
8
|
+
"draw_time_series_panel",
|
|
9
|
+
"marker_map_for_levels",
|
|
10
|
+
"select_snapshot_rows",
|
|
11
|
+
"summarize_snapshot_values",
|
|
12
|
+
]
|