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,321 @@
|
|
|
1
|
+
"""Pure four-state vector builder: tidy data to vector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from .config import FourStateVectorConfig, load_four_state_vector_config
|
|
13
|
+
from .math import compute_four_state_vector
|
|
14
|
+
from .reference import compute_reference_table, resolve_reference_design_id
|
|
15
|
+
from .selection import CornerizeResult, cornerize_and_aggregate
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _assert_same_times(time_a: float | None, time_b: float | None) -> None:
|
|
19
|
+
if time_a is None and time_b is None:
|
|
20
|
+
return
|
|
21
|
+
if time_a is None or time_b is None:
|
|
22
|
+
raise ValueError("four-state vector: logic and intensity selections produced different times.")
|
|
23
|
+
ta = float(time_a)
|
|
24
|
+
tb = float(time_b)
|
|
25
|
+
if not np.isclose(ta, tb, rtol=0, atol=1e-9):
|
|
26
|
+
raise ValueError(f"four-state vector: logic and intensity channels selected different times: {ta} vs {tb}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _attach_sequence(vector: pd.DataFrame, tidy_df: pd.DataFrame, *, design_by: list[str]) -> pd.DataFrame:
|
|
30
|
+
# If vector already has a usable 'sequence' column, keep it.
|
|
31
|
+
if "sequence" in vector.columns and vector["sequence"].notna().any():
|
|
32
|
+
return vector
|
|
33
|
+
|
|
34
|
+
if "sequence" not in tidy_df.columns:
|
|
35
|
+
return vector
|
|
36
|
+
|
|
37
|
+
idx_cols = [c for c in design_by if c in tidy_df.columns]
|
|
38
|
+
if not idx_cols:
|
|
39
|
+
return vector
|
|
40
|
+
|
|
41
|
+
seq_map = (
|
|
42
|
+
tidy_df[idx_cols + ["sequence"]].dropna(subset=["sequence"]).drop_duplicates(subset=idx_cols, keep="first")
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
merged = vector.merge(seq_map, on=idx_cols, how="left", suffixes=("", "_ref"))
|
|
46
|
+
# Prefer existing 'sequence' if present; otherwise use attached value
|
|
47
|
+
if "sequence_ref" in merged.columns:
|
|
48
|
+
if "sequence" not in merged.columns:
|
|
49
|
+
merged["sequence"] = merged["sequence_ref"]
|
|
50
|
+
else:
|
|
51
|
+
merged["sequence"] = merged["sequence"].fillna(merged["sequence_ref"])
|
|
52
|
+
merged = merged.drop(columns=["sequence_ref"])
|
|
53
|
+
|
|
54
|
+
return merged
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _reorder_and_filter(
|
|
58
|
+
vector: pd.DataFrame,
|
|
59
|
+
*,
|
|
60
|
+
design_by: list[str],
|
|
61
|
+
ref_design_id: str | None,
|
|
62
|
+
exclude_reference: bool,
|
|
63
|
+
) -> pd.DataFrame:
|
|
64
|
+
"""
|
|
65
|
+
Apply user-requested:
|
|
66
|
+
- drop REF design_id rows
|
|
67
|
+
- reorder columns to:
|
|
68
|
+
design_id, sequence, time_selected_h, reference_design_id, r_logic, v00, v10, v01, v11,
|
|
69
|
+
y00_star, y10_star, y01_star, y11_star, flat_logic (then keep remaining columns)
|
|
70
|
+
"""
|
|
71
|
+
label_col = design_by[0]
|
|
72
|
+
|
|
73
|
+
df = vector.copy()
|
|
74
|
+
|
|
75
|
+
# Exclude REF (if specified)
|
|
76
|
+
if exclude_reference and ref_design_id is not None and label_col in df.columns:
|
|
77
|
+
df = df[df[label_col].astype(str) != str(ref_design_id)].copy()
|
|
78
|
+
|
|
79
|
+
# Ensure 'sequence' exists even if missing upstream
|
|
80
|
+
if "sequence" not in df.columns:
|
|
81
|
+
df["sequence"] = pd.NA
|
|
82
|
+
|
|
83
|
+
if "reference_sequence" in df.columns:
|
|
84
|
+
df = df.drop(columns=["reference_sequence"])
|
|
85
|
+
|
|
86
|
+
if label_col not in df.columns:
|
|
87
|
+
raise ValueError(f"four-state vector: expected label column '{label_col}' in vector output.")
|
|
88
|
+
|
|
89
|
+
desired = [
|
|
90
|
+
label_col,
|
|
91
|
+
"sequence",
|
|
92
|
+
"time_selected_h",
|
|
93
|
+
"reference_design_id",
|
|
94
|
+
"r_logic",
|
|
95
|
+
"v00",
|
|
96
|
+
"v10",
|
|
97
|
+
"v01",
|
|
98
|
+
"v11",
|
|
99
|
+
"y00_star",
|
|
100
|
+
"y10_star",
|
|
101
|
+
"y01_star",
|
|
102
|
+
"y11_star",
|
|
103
|
+
"flat_logic",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
# Reorder preferred columns first, then keep remaining diagnostics/metadata.
|
|
107
|
+
front = [c for c in desired if c in df.columns]
|
|
108
|
+
rest = [c for c in df.columns if c not in front]
|
|
109
|
+
out = df[front + rest].copy()
|
|
110
|
+
|
|
111
|
+
# Final tidy: drop duplicates just in case and reset index
|
|
112
|
+
out = out.drop_duplicates().reset_index(drop=True)
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _anchors_payload(anchor_table: pd.DataFrame | None) -> dict[str, float] | None:
|
|
117
|
+
if anchor_table is None or anchor_table.empty:
|
|
118
|
+
return None
|
|
119
|
+
row = anchor_table.iloc[0]
|
|
120
|
+
return {
|
|
121
|
+
"00": float(row.get("b00")),
|
|
122
|
+
"10": float(row.get("b10")),
|
|
123
|
+
"01": float(row.get("b01")),
|
|
124
|
+
"11": float(row.get("b11")),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass(frozen=True)
|
|
129
|
+
class FourStateVectorBuildResult:
|
|
130
|
+
vector: pd.DataFrame
|
|
131
|
+
log: dict[str, Any]
|
|
132
|
+
cfg: FourStateVectorConfig
|
|
133
|
+
sel_logic: CornerizeResult
|
|
134
|
+
sel_int: CornerizeResult
|
|
135
|
+
ref_design_id: str | None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def build_four_state_vector_from_tidy(
|
|
139
|
+
tidy_df: pd.DataFrame, xform_cfg: Mapping[str, Any]
|
|
140
|
+
) -> FourStateVectorBuildResult:
|
|
141
|
+
"""
|
|
142
|
+
Shared four-state vector builder for pipeline + notebooks.
|
|
143
|
+
Returns vector + log payload + selection diagnostics without writing to disk.
|
|
144
|
+
"""
|
|
145
|
+
cfg = load_four_state_vector_config(xform_cfg)
|
|
146
|
+
|
|
147
|
+
# 1) Cornerize + aggregate (LOGIC channel)
|
|
148
|
+
sel_logic = cornerize_and_aggregate(
|
|
149
|
+
tidy_df,
|
|
150
|
+
design_by=cfg.design_by,
|
|
151
|
+
treatment_map=cfg.treatment_map,
|
|
152
|
+
case_sensitive=cfg.treatment_case_sensitive,
|
|
153
|
+
preferred_treatment_column=cfg.treatment_column,
|
|
154
|
+
time_column=cfg.time_column,
|
|
155
|
+
channel=cfg.response.logic_channel,
|
|
156
|
+
target_time_h=cfg.target_time_h,
|
|
157
|
+
time_mode=cfg.time_mode,
|
|
158
|
+
time_tolerance_h=cfg.time_tolerance_h,
|
|
159
|
+
require_all_corners_per_design=cfg.require_all_corners_per_design,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# 2) Cornerize + aggregate (INTENSITY channel)
|
|
163
|
+
sel_int = cornerize_and_aggregate(
|
|
164
|
+
tidy_df,
|
|
165
|
+
design_by=cfg.design_by,
|
|
166
|
+
treatment_map=cfg.treatment_map,
|
|
167
|
+
case_sensitive=cfg.treatment_case_sensitive,
|
|
168
|
+
preferred_treatment_column=cfg.treatment_column,
|
|
169
|
+
time_column=cfg.time_column,
|
|
170
|
+
channel=cfg.response.intensity_channel,
|
|
171
|
+
target_time_h=cfg.target_time_h,
|
|
172
|
+
time_mode=cfg.time_mode,
|
|
173
|
+
time_tolerance_h=cfg.time_tolerance_h,
|
|
174
|
+
require_all_corners_per_design=cfg.require_all_corners_per_design,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# 3) Safety: same chosen snapshot times
|
|
178
|
+
_assert_same_times(sel_logic.chosen_time, sel_int.chosen_time)
|
|
179
|
+
|
|
180
|
+
tol_note = sel_logic.time_warning
|
|
181
|
+
|
|
182
|
+
# 4) Compute vector (logic from logic channel, y* from intensity channel + anchors)
|
|
183
|
+
ref_raw = resolve_reference_design_id(tidy_df, design_by=cfg.design_by, ref_label=cfg.reference.design_id)
|
|
184
|
+
vector = compute_four_state_vector(
|
|
185
|
+
points_logic=sel_logic.points,
|
|
186
|
+
points_intensity=sel_int.points,
|
|
187
|
+
per_corner_intensity=sel_int.per_corner,
|
|
188
|
+
design_by=cfg.design_by,
|
|
189
|
+
reference_design_id=ref_raw,
|
|
190
|
+
reference_stat=cfg.reference.observation_stat,
|
|
191
|
+
eps_ratio=cfg.eps_ratio,
|
|
192
|
+
eps_range=cfg.eps_range,
|
|
193
|
+
eps_ref=cfg.eps_ref,
|
|
194
|
+
eps_abs=cfg.eps_abs,
|
|
195
|
+
ref_add_alpha=cfg.ref_add_alpha,
|
|
196
|
+
log2_offset_delta=cfg.log2_offset_delta,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# 4a) Attach selected snapshot time
|
|
200
|
+
if sel_logic.chosen_time is not None:
|
|
201
|
+
vector["time_selected_h"] = float(sel_logic.chosen_time)
|
|
202
|
+
vector["time_selected_h"] = pd.to_numeric(vector["time_selected_h"], errors="coerce").astype(float)
|
|
203
|
+
|
|
204
|
+
# 4b) Attach 'sequence' if available in the tidy inputs
|
|
205
|
+
vector = _attach_sequence(vector, tidy_df, design_by=cfg.design_by)
|
|
206
|
+
# 4b.5) Persist intensity log2 offset delta for downstream enforcement
|
|
207
|
+
if "intensity_log2_offset_delta" not in vector.columns:
|
|
208
|
+
vector["intensity_log2_offset_delta"] = float(cfg.log2_offset_delta)
|
|
209
|
+
# Carry additional metadata columns if requested
|
|
210
|
+
idx_cols = [c for c in cfg.design_by if c in tidy_df.columns]
|
|
211
|
+
for col in cfg.carry_metadata or []:
|
|
212
|
+
if col in tidy_df.columns and col not in vector.columns:
|
|
213
|
+
meta = tidy_df[idx_cols + [col]].dropna(subset=[col]).drop_duplicates(subset=idx_cols, keep="first")
|
|
214
|
+
vector = vector.merge(meta, on=idx_cols, how="left", validate="m:1")
|
|
215
|
+
|
|
216
|
+
# 4c) Reference provenance columns
|
|
217
|
+
if "reference_design_id" not in vector.columns:
|
|
218
|
+
vector["reference_design_id"] = ref_raw
|
|
219
|
+
|
|
220
|
+
# 4d) Apply requested filtering and column order
|
|
221
|
+
vector_out = _reorder_and_filter(
|
|
222
|
+
vector,
|
|
223
|
+
design_by=cfg.design_by,
|
|
224
|
+
ref_design_id=ref_raw,
|
|
225
|
+
exclude_reference=cfg.exclude_reference_from_output,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# 5) Compose a log payload (unchanged)
|
|
229
|
+
# Compute r_logic distribution for quick provenance metrics
|
|
230
|
+
r_desc = vector_out["r_logic"].describe().to_dict() if "r_logic" in vector_out.columns else {}
|
|
231
|
+
flat_count = 0
|
|
232
|
+
flat_fraction = 0.0
|
|
233
|
+
flat_samples: list[str] = []
|
|
234
|
+
if "flat_logic" in vector_out.columns and len(vector_out) > 0:
|
|
235
|
+
flat_mask = vector_out["flat_logic"].astype(bool)
|
|
236
|
+
flat_count = int(flat_mask.sum())
|
|
237
|
+
flat_fraction = float(flat_count) / float(len(vector_out)) if len(vector_out) else 0.0
|
|
238
|
+
label_col = cfg.design_by[0] if cfg.design_by else None
|
|
239
|
+
if label_col and label_col in vector_out.columns and flat_count > 0:
|
|
240
|
+
flat_samples = vector_out.loc[flat_mask, label_col].astype(str).dropna().drop_duplicates().head(5).tolist()
|
|
241
|
+
|
|
242
|
+
log_payload: dict[str, Any] = {
|
|
243
|
+
"operation": "four_state_vector",
|
|
244
|
+
"design_by": cfg.design_by,
|
|
245
|
+
"time_column": cfg.time_column,
|
|
246
|
+
"channels": {
|
|
247
|
+
"logic": cfg.response.logic_channel,
|
|
248
|
+
"intensity": cfg.response.intensity_channel,
|
|
249
|
+
},
|
|
250
|
+
"treatment_map": cfg.treatment_map,
|
|
251
|
+
"treatment_case_sensitive": cfg.treatment_case_sensitive,
|
|
252
|
+
"time": {
|
|
253
|
+
"target_time_h": cfg.target_time_h,
|
|
254
|
+
"mode": cfg.time_mode,
|
|
255
|
+
"tolerance_h": cfg.time_tolerance_h,
|
|
256
|
+
"tolerance_policy": "soft-warning",
|
|
257
|
+
"chosen_time": sel_logic.chosen_time,
|
|
258
|
+
"out_of_tolerance": tol_note,
|
|
259
|
+
},
|
|
260
|
+
"reference": {
|
|
261
|
+
"design_id": cfg.reference.design_id,
|
|
262
|
+
"design_id_resolved": ref_raw,
|
|
263
|
+
"observation_stat": cfg.reference.observation_stat,
|
|
264
|
+
"anchors": _anchors_payload(
|
|
265
|
+
compute_reference_table(
|
|
266
|
+
sel_int.per_corner,
|
|
267
|
+
design_by=cfg.design_by,
|
|
268
|
+
ref_design_id=ref_raw,
|
|
269
|
+
stat=cfg.reference.observation_stat,
|
|
270
|
+
)
|
|
271
|
+
if ref_raw is not None
|
|
272
|
+
else None,
|
|
273
|
+
),
|
|
274
|
+
},
|
|
275
|
+
"eps": {
|
|
276
|
+
"ratio": cfg.eps_ratio,
|
|
277
|
+
"range": cfg.eps_range,
|
|
278
|
+
"eta": cfg.eps_range,
|
|
279
|
+
"ref": cfg.eps_ref,
|
|
280
|
+
"abs": cfg.eps_abs,
|
|
281
|
+
},
|
|
282
|
+
"semantics": {
|
|
283
|
+
"v": {
|
|
284
|
+
"source_channel": cfg.response.logic_channel,
|
|
285
|
+
"transform": "u = log2(L); v = (u - min(u))/((max(u)-min(u)) + eta)",
|
|
286
|
+
"persisted_units": "unit-interval [0,1] (not log)",
|
|
287
|
+
"eta": cfg.eps_range,
|
|
288
|
+
},
|
|
289
|
+
"y_star": {
|
|
290
|
+
"source_channel": cfg.response.intensity_channel,
|
|
291
|
+
"transform": "y* = log2( ((I + eps_abs)/max(A + alpha, eps_ref)) + delta )",
|
|
292
|
+
"persisted_units": "log2",
|
|
293
|
+
"alpha": cfg.ref_add_alpha,
|
|
294
|
+
"delta": cfg.log2_offset_delta,
|
|
295
|
+
},
|
|
296
|
+
"r_logic": {
|
|
297
|
+
"definition": "per design dynamic range on LOGIC (linear, ε-guarded): max(L_i)/min(L_i)",
|
|
298
|
+
"epsilon_guard_ratio": cfg.eps_ratio,
|
|
299
|
+
"stats_over_vector": r_desc,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
"rows": {
|
|
303
|
+
"per_corner_logic": int(len(sel_logic.per_corner)),
|
|
304
|
+
"per_corner_intensity": int(len(sel_int.per_corner)),
|
|
305
|
+
"points_logic": int(len(sel_logic.points)),
|
|
306
|
+
"points_intensity": int(len(sel_int.points)),
|
|
307
|
+
"vector": int(len(vector_out)),
|
|
308
|
+
},
|
|
309
|
+
"flat_logic_count": int(flat_count),
|
|
310
|
+
"flat_logic_fraction": float(flat_fraction),
|
|
311
|
+
"flat_logic_sample_design_ids": flat_samples,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return FourStateVectorBuildResult(
|
|
315
|
+
vector=vector_out,
|
|
316
|
+
log=log_payload,
|
|
317
|
+
cfg=cfg,
|
|
318
|
+
sel_logic=sel_logic,
|
|
319
|
+
sel_int=sel_int,
|
|
320
|
+
ref_design_id=ref_raw,
|
|
321
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .constants import VECTOR_CHANNELS
|
|
4
|
+
from .model import FourStateVectorCollection, FourStateVectorSource
|
|
5
|
+
from .sources import collect_four_state_vector_sources
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def render_four_state_vector_collection_heatmap(*args, **kwargs):
|
|
9
|
+
from .render import render_four_state_vector_collection_heatmap as render # noqa: PLC0415
|
|
10
|
+
|
|
11
|
+
return render(*args, **kwargs)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"FourStateVectorCollection",
|
|
16
|
+
"FourStateVectorSource",
|
|
17
|
+
"VECTOR_CHANNELS",
|
|
18
|
+
"collect_four_state_vector_sources",
|
|
19
|
+
"render_four_state_vector_collection_heatmap",
|
|
20
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from reader_workbench.errors import FourStateVectorError
|
|
8
|
+
|
|
9
|
+
from ..validation import require_intensity_delta_column
|
|
10
|
+
from .constants import REQUIRED_VECTOR_COLUMNS
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def require_vector_columns(frame: pd.DataFrame) -> None:
|
|
14
|
+
require_intensity_delta_column(frame)
|
|
15
|
+
missing = [column for column in REQUIRED_VECTOR_COLUMNS if column not in frame.columns]
|
|
16
|
+
if missing:
|
|
17
|
+
raise FourStateVectorError(f"four-state vector input requires columns: {', '.join(missing)}.")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def require_normalized_frame(frame: pd.DataFrame) -> None:
|
|
21
|
+
require_vector_columns(frame)
|
|
22
|
+
source_columns = {"source_resource_id", "source_experiment_id"} & set(frame.columns)
|
|
23
|
+
missing = ([] if source_columns else ["source_resource_id or source_experiment_id"]) + (
|
|
24
|
+
[] if "row_label" in frame.columns else ["row_label"]
|
|
25
|
+
)
|
|
26
|
+
if missing:
|
|
27
|
+
raise FourStateVectorError(f"four-state vector collection requires normalized columns: {', '.join(missing)}.")
|
|
28
|
+
if frame.empty:
|
|
29
|
+
raise FourStateVectorError("four-state vector collection has no rows to plot.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def finite_numeric_column(
|
|
33
|
+
series: pd.Series,
|
|
34
|
+
*,
|
|
35
|
+
column: str,
|
|
36
|
+
source: str,
|
|
37
|
+
allow_nan: bool = False,
|
|
38
|
+
) -> pd.Series:
|
|
39
|
+
values = pd.to_numeric(series, errors="coerce")
|
|
40
|
+
coerced_to_nan = series.notna() & values.isna()
|
|
41
|
+
nonfinite = values.notna() & ~values.fillna(0.0).map(lambda value: math.isfinite(float(value)))
|
|
42
|
+
invalid = coerced_to_nan | nonfinite
|
|
43
|
+
if not allow_nan:
|
|
44
|
+
invalid |= values.isna()
|
|
45
|
+
if invalid.any():
|
|
46
|
+
raise FourStateVectorError(
|
|
47
|
+
f"four-state vector collection column {column!r} must be finite numeric values in {source}."
|
|
48
|
+
)
|
|
49
|
+
return values.astype(float)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
VECTOR_CHANNELS = ("v00", "v10", "v01", "v11", "y00_star", "y10_star", "y01_star", "y11_star")
|
|
4
|
+
REQUIRED_VECTOR_COLUMNS = (
|
|
5
|
+
"design_id",
|
|
6
|
+
"reference_design_id",
|
|
7
|
+
"intensity_log2_offset_delta",
|
|
8
|
+
"r_logic",
|
|
9
|
+
*VECTOR_CHANNELS,
|
|
10
|
+
"flat_logic",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
METADATA_COLUMNS = (
|
|
14
|
+
"source_index",
|
|
15
|
+
"source_resource_id",
|
|
16
|
+
"source_experiment_id",
|
|
17
|
+
"source_record_id",
|
|
18
|
+
"source_record_revision_digest",
|
|
19
|
+
"source_row_index",
|
|
20
|
+
"row_label",
|
|
21
|
+
"design_id",
|
|
22
|
+
"sequence",
|
|
23
|
+
"id",
|
|
24
|
+
"time_selected_h",
|
|
25
|
+
"reference_design_id",
|
|
26
|
+
"intensity_log2_offset_delta",
|
|
27
|
+
"r_logic",
|
|
28
|
+
"flat_logic",
|
|
29
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class FourStateVectorSource:
|
|
10
|
+
resource_id: str
|
|
11
|
+
experiment_id: str
|
|
12
|
+
record_id: str
|
|
13
|
+
revision_digest: str
|
|
14
|
+
frame: pd.DataFrame
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class FourStateVectorCollection:
|
|
19
|
+
frame: pd.DataFrame
|