Complete Python workflow¶
Goal: work through every public method on AUCExperiment, understanding
what each returns and why it exists.
Prerequisites: Installation. Commands
run from the repository root, using the repository's own demo data at
examples/data/demo_experiment.
A runnable condensation of this page lives at
examples/02_inspect_summary.py.
1. Import¶
import openauc is deliberately light — it does not pull in matplotlib.
Plotting is imported separately when you need it.
2. Load a directory of CSV/TSV data¶
openauc.load returns an AUCExperiment: the canonical in-memory model. It
accepts an experiment directory, a data file with an adjacent manifest, a
manifest file, or an .aucx archive.
3. Load an AUCX archive¶
Identical call — dispatch is on the suffix:
or force it:
Every checksum is verified before a model is constructed.
4. summary() → str¶
A human-readable structural description: identity, counts, units, ranges, metadata presence, provenance, validation counts. It ends with an explicit statement that no scientific claim is made.
5. summary_data() → ExperimentSummary¶
summary = experiment.summary_data()
summary.n_scans # int
summary.total_valid_observations # int
summary.points_per_scan # tuple[int, ...]
summary.radius.render() # '5.9 to 7.2 cm (observed)'
summary.elapsed_time.minimum # float | None
summary.to_dict() # JSON-friendly dict
A frozen pydantic model. Every collection is a tuple; there are no mutable
fields. summary() is exactly summary_data().to_text().
Metadata absence is counted, never defaulted:
for entry in summary.metadata_presence:
if entry.unrecorded:
print(entry.component, entry.field, entry.unrecorded, "of", entry.total)
6. validate_structure() → ValidationReport¶
report = experiment.validate_structure()
report.is_valid # bool — True when there are no ERROR findings
report.errors # tuple[ValidationIssue, ...]
report.warnings
Returns the ARCHIVAL and STRUCTURAL findings of ERROR or
WARNING severity. It never raises.
7. validate() → ValidationReport¶
full = experiment.validate()
full.counts() # (errors, warnings, infos)
full.codes() # ('cell_absent', 'rotor_speed_absent', ...)
The superset: all four tiers, all severities, including informational findings.
8. Filtering findings¶
from openauc.models import ValidationSeverity, ValidationTier
full.by_code("rotor_speed_absent") # tuple of matching issues
full.for_tiers(ValidationTier.SV_READINESS) # a narrowed report
full.blocking_for(ValidationTier.SV_READINESS) # what prevents that tier
full.for_tiers(
ValidationTier.STRUCTURAL,
severities=(ValidationSeverity.ERROR,),
)
Each ValidationIssue carries code, severity, tiers, blocks,
message, observed, expected, remediation, component, location and
scan_ids. issue.describe() renders all of it:
9. assess_readiness() → ReadinessAssessment¶
assessment = experiment.assess_readiness()
sv = assessment.sedimentation_velocity
sv.status # ReadinessStatus
sv.is_blocked # bool
sv.blocking_issues # findings that prevent this tier
sv.advisory_issues # findings that pertain to it but block nothing
10. Reading the four statuses¶
| Status | Meaning |
|---|---|
POTENTIALLY_READY |
Every metadata prerequisite is present. Nothing is claimed about the data. |
BLOCKED |
A prerequisite is absent, or a structural error prevents assessment. |
NOT_APPLICABLE |
The declared experiment type says this workflow does not apply. |
NOT_ASSESSED |
Not evaluated — and, for scientific suitability, never will be. |
That entry is a constant. It is never derived from any finding.
11. Reading the numbers¶
radius, signal = experiment.observations.scan_vectors("scan_001")
# both numpy float64 arrays, padding excluded, stored order preserved
for scan_id, radius, signal in experiment.observations.iter_scan_vectors():
print(scan_id, len(radius))
In shared-axis mode every scan returns the same radius array; in per-scan mode each returns its own. Nothing is sorted or resampled.
Other accessors:
experiment.observations.mode # RadiusAxisMode.SHARED | PER_SCAN
experiment.observations.scan_ids # tuple[str, ...]
experiment.observations.points_per_scan() # tuple[int, ...]
experiment.observations.radius_range() # (min, max) | None
12. Plotting¶
from openauc.plotting import plot_scan, plot_scans
ax = plot_scans(experiment) # all scans overlaid
ax = plot_scans(experiment, scan_ids=["scan_001", "scan_002"])
ax = plot_scan(experiment, "scan_001") # exactly one
Both return a matplotlib.axes.Axes. matplotlib loads on the first draw.
13. Saving without a display¶
pyplot is never used, so no backend or display is required. To show a window,
create your own axes and pass them in:
14. Export to AUCX¶
path = experiment.export("demo.aucx") # refuses to overwrite
path = experiment.export("demo.aucx", overwrite=True)
Writes atomically: a temporary sibling file is written, verified in full, then
moved into place. Also available as openauc.export_aucx(experiment, path).
15. Reload¶
16. Compare¶
AUCX round-trips the canonical model exactly — both radius modes, masks, dtypes, units, and all four value statuses.
CSV cannot promise this
Generic delimited output cannot express "explicitly unknown" as distinct from "missing", so exact dict equality is an AUCX property only. See Missing and unknown metadata.
17. Handling exceptions¶
Everything derives from OpenAUCError:
from openauc.exceptions import (
OpenAUCError, ManifestError, ParseError, DataConflictError,
ArchiveError, ArchiveIntegrityError, ArchiveVersionError,
PlottingError, StructuralValidationError,
)
try:
experiment = openauc.load("some-directory")
except ManifestError as exc:
print("manifest problem:", exc)
except ParseError as exc:
print("data problem:", exc)
except OpenAUCError as exc:
print("openauc:", exc)
Validation never raises — unless you ask it to: