72 lines
2.7 KiB
Python
Executable File
72 lines
2.7 KiB
Python
Executable File
"""Resume planning: turn a file's frontier into a set of angles to re-acquire.
|
|
|
|
Pure logic, no Qt and no file I/O beyond what SrasFile already parsed, so
|
|
the non-obvious contiguity rule is testable on its own.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from core.scan_engine import ResumeState, ResumeTarget
|
|
from core.sras_format import WRITABLE_VERSIONS, SrasFile
|
|
|
|
|
|
@dataclass
|
|
class ResumePlan:
|
|
targets: list[ResumeTarget]
|
|
auto_added: list[int] = field(default_factory=list) # indices forced in
|
|
frontier_idx: int = 0
|
|
|
|
@property
|
|
def total_rows(self) -> int:
|
|
return sum(t.n_rows for t in self.targets)
|
|
|
|
def to_state(self, sras: SrasFile) -> ResumeState:
|
|
return ResumeState(path=sras.path, targets=self.targets,
|
|
samples_per_frame=sras.header.samples_per_frame)
|
|
|
|
|
|
def plan_resume(statuses, selected: set[int]) -> ResumePlan:
|
|
"""Expand an operator's angle selection into a runnable resume plan.
|
|
|
|
Waveform data is one contiguous append-only stream, so nothing can be
|
|
written past a gap: if the operator picks an angle at or beyond the
|
|
frontier (the first incomplete angle), every angle from the frontier up
|
|
to it must be re-acquired too. Those extras are reported in
|
|
``auto_added`` so the UI can say so.
|
|
"""
|
|
frontier_idx = next((s.index for s in statuses if not s.complete), len(statuses))
|
|
|
|
at_or_past = {i for i in selected if i >= frontier_idx}
|
|
if at_or_past:
|
|
final = selected | set(range(frontier_idx, max(at_or_past) + 1))
|
|
else:
|
|
final = set(selected)
|
|
|
|
targets = [
|
|
ResumeTarget(angle_idx=s.index, bg_offset=s.bg_offset,
|
|
data_offset=s.data_offset, n_rows=s.n_rows,
|
|
angle_deg=s.angle_deg)
|
|
for s in statuses if s.index in final
|
|
]
|
|
return ResumePlan(targets=targets,
|
|
auto_added=sorted(final - set(selected)),
|
|
frontier_idx=frontier_idx)
|
|
|
|
|
|
def is_compatible(sras: SrasFile, *, velocity: float, laser_freq: float,
|
|
sample_rate: float, n_channels: int) -> bool:
|
|
"""Whether appending to this file with the current settings is safe.
|
|
|
|
A legacy v6/v10 file is not: it has one background for the whole scan,
|
|
and every angle this engine acquires writes a background block of its
|
|
own, which the older layout has no room for.
|
|
"""
|
|
h = sras.header
|
|
return (sras.version in WRITABLE_VERSIONS
|
|
and h.bytes_per_sample == 1
|
|
and h.n_channels == n_channels
|
|
and abs(h.velocity - velocity) <= 1e-3
|
|
and abs(h.laser_freq - laser_freq) <= 1e-3
|
|
and abs(h.sample_rate - sample_rate) <= 1.0)
|