Skip to content

Python API ​

Signatures and docstrings of the public functions and classes in qoradfxm at main (aee2d33), read from the source without importing it. The guides on this site say how the pieces fit together; this page is for looking up an argument.

qoradfxm.cli ​

QoraDFXM command line — the headless front-end over the Core engine.

This layer never imports Qt. It runs in a plain (GUI-less) install:

    pip install qoradfxm            # engine + CLI
    pip install qoradfxm[gui]       # + the PySide6 desktop app

Commands::

    qoradfxm fit shot.h5 --dataset /run/scan00001/det/d/data         --op sub_bg:/dark --op log --points pts.json --out results.csv
    qoradfxm convert dat/ --out-dir tif/          # h5 → tif export
    qoradfxm info shot.h5                         # frames / structure
    qoradfxm gui shot.h5                          # launch the desktop app

Ops run in the order given. See :mod:`qoradfxm.cli.spec` for the ``KIND[:SRC]``
grammar; the GUI reuses it to submit its own recipes as CLI jobs.

build_parser ​

python
def build_parser() -> argparse.ArgumentParser

main ​

python
def main(argv=None) -> int

qoradfxm.cli.spec ​

Recipe ⇄ command-line translation. Pure: no Qt, no argparse, no I/O.

One definition of what ``--op sub_bg:/dark`` means, shared by

* the CLI parser (string → :class:`~qoradfxm.core.ops.Operation`), and
* the GUI, which turns the recipe it built interactively back into an argv and
  hands it to the CLI as a batch job (:mod:`qoradfxm.gui.cli_bridge`).

Keeping both directions here is what makes "the GUI runs the same thing you
could have typed" true by construction instead of by convention.

SpecError ​

python
class SpecError(ValueError):
    ...
A malformed ``KIND[:SRC]`` op spec.

parse_op ​

python
def parse_op(spec: str) -> Operation
``"sub_bg:/dark"`` → ``Operation("sub_bg", {"dataset_path": "/dark"})``.

format_op ​

python
def format_op(op: Operation) -> str
Inverse of :func:`parse_op` — ``Operation`` → ``"sub_bg:/dark"``.

parse_ops ​

python
def parse_ops(specs) -> list[Operation]

format_ops ​

python
def format_ops(history) -> list[str]

argv_for_fit ​

python
def argv_for_fit(
    file,
    *,
    dataset_path: str | None = None,
    ops=(),
    points_file=None,
    out=None,
) -> list[str]
Build the argv of a ``qoradfxm fit`` run (without the ``qoradfxm`` prog name).

argv_for_dataset ​

python
def argv_for_dataset(ds, *, points_file=None, out=None) -> list[str]
Turn a live :class:`~qoradfxm.core.dataset.QoraDFXMDataset` into a ``fit`` argv.

This is how the GUI replays its own recipe through the CLI. Requires the
dataset to know where it came from (``source_path``).

qoradfxm.core.dataset ​

QoraDFXMDataset — the immutable, fluent domain object.

Every preprocessing method returns a NEW dataset that shares the (never
mutated) ``raw`` array and carries a grown :class:`History`. Because the recipe
is data, not code, a dataset supports Replay (rebuild from history), Undo
(``.undo()`` → parent recipe) and full serialization for SQLite sessions.

    ds = (QoraDFXMDataset.from_h5(path, "/run/s1/det/d/data")
            .sub_bg(dataset_path="/dark")
            .divide(file_path="flat.tif")
            .apply_log()
            .fit_ellipse(points))
    img  = ds.image        # raw with history replayed (cached)
    row  = ds.to_record()  # one Master-table row

QoraDFXMDataset ​

python
@dataclass
class QoraDFXMDataset:
    raw: np.ndarray
    source_path: Path | None = None
    meta: dict = field(default_factory=dict)
    history: History = field(default_factory=History)
    fit: FitResult | None = None

QoraDFXMDataset.from_array ​

python
@classmethod
def from_array(cls, arr, source_path=None, meta=None) -> QoraDFXMDataset

QoraDFXMDataset.from_h5 ​

python
@classmethod
def from_h5(cls, path, dataset_path, meta=None) -> QoraDFXMDataset

QoraDFXMDataset.from_frame ​

python
@classmethod
def from_frame(cls, path, frame: _io.FramePath | None=None) -> QoraDFXMDataset

QoraDFXMDataset.from_image_file ​

python
@classmethod
def from_image_file(cls, path) -> QoraDFXMDataset

QoraDFXMDataset.sub_bg ​

python
def sub_bg(self, **source) -> QoraDFXMDataset
Dark subtract. Source: dataset_path=... or file_path=...

QoraDFXMDataset.divide ​

python
def divide(self, **source) -> QoraDFXMDataset
Flat-field divide. Source: dataset_path=... or file_path=...

QoraDFXMDataset.apply_log ​

python
def apply_log(self) -> QoraDFXMDataset

QoraDFXMDataset.pure_log ​

python
def pure_log(self) -> QoraDFXMDataset

QoraDFXMDataset.sqrt ​

python
def sqrt(self) -> QoraDFXMDataset

QoraDFXMDataset.gamma ​

python
def gamma(self, g: float=0.5) -> QoraDFXMDataset

QoraDFXMDataset.normalize ​

python
def normalize(self) -> QoraDFXMDataset

QoraDFXMDataset.scale ​

python
def scale(self, sx: float, sy: float | None=None, interp='auto') -> QoraDFXMDataset
Resize by factors; ``sx != sy`` changes the aspect ratio.

QoraDFXMDataset.rotate ​

python
def rotate(self, angle_deg: float, expand: bool=True) -> QoraDFXMDataset
Rotate about the centre, positive = counter-clockwise.

QoraDFXMDataset.flip ​

python
def flip(self, axis: str='h') -> QoraDFXMDataset
Mirror: ``h`` (left↔right), ``v`` (top↔bottom) or ``both``.

QoraDFXMDataset.add_op ​

python
def add_op(self, kind: str, **params) -> QoraDFXMDataset
Generic append (used when replaying a GUI/serialized recipe).

QoraDFXMDataset.fit_ellipse ​

python
def fit_ellipse(self, points) -> QoraDFXMDataset
Fit an ellipse to picked points (on the PROCESSED image).

QoraDFXMDataset.linear_view ​

python
def linear_view(self) -> QoraDFXMDataset
Same recipe with the intensity-bending ops (log/sqrt/gamma) dropped.

Geometry and background correction are kept, so coordinates picked on
the displayed image still line up — but the values are linear again,
which is what any quantitative measurement needs.

QoraDFXMDataset.ring_profile ​

python
def ring_profile(self, **kw)
Brightness along the fitted ellipse vs. its scale (see core.profile).

Always measured on :meth:`linear_view`; requires a fit.

QoraDFXMDataset.undo ​

python
def undo(self) -> QoraDFXMDataset

QoraDFXMDataset.set_history ​

python
def set_history(self, history: History) -> QoraDFXMDataset

QoraDFXMDataset.replay ​

python
def replay(self, history: History) -> QoraDFXMDataset
New dataset from the same raw with a given recipe (Replay/Macro).

QoraDFXMDataset.image ​

python
@property
def image(self) -> np.ndarray
raw with the full history applied, in order (memoized).

QoraDFXMDataset.bg_applied ​

python
@property
def bg_applied(self) -> bool

QoraDFXMDataset.log_scale ​

python
@property
def log_scale(self) -> bool

QoraDFXMDataset.to_record ​

python
def to_record(self, status: str='OK') -> dict | None
One Master-table row, or None if not yet fitted.

QoraDFXMDataset.to_dict ​

python
def to_dict(self) -> dict
Serialize recipe + result + identity (arrays excluded → SQLite).

QoraDFXMDataset.from_dict ​

python
@classmethod
def from_dict(cls, d: dict, raw=None) -> QoraDFXMDataset
Rebuild from a serialized session. If ``raw`` is None it is reloaded
from ``source_path`` (needs meta['dataset_path'] for h5).

qoradfxm.core.fitting ​

Direct least-squares ellipse fitting from picked points. Pure NumPy.

Coordinate convention: pixel coordinates, x right, y DOWNWARD. Moved out of the
old interactive ``ellipse_fit.py`` so the math has zero matplotlib / GUI deps.

fit_ellipse ​

python
def fit_ellipse(x, y)
Direct least-squares ellipse fit (Halir & Flusser 1998), a numerically
stable reformulation of Fitzgibbon-Pilu-Fisher.

Returns
-------
coeffs : ndarray, shape (6,)
    (a, b, c, d, e, f) for  a x^2 + b x y + c y^2 + d x + e y + f = 0
    in the ORIGINAL pixel coordinates.

conic_to_geometry ​

python
def conic_to_geometry(coeffs)
Convert conic coefficients into geometric ellipse parameters.

sampson_residuals ​

python
def sampson_residuals(coeffs, x, y)
First-order (Sampson) approx of perpendicular distance, in pixels.

ellipse_polyline ​

python
def ellipse_polyline(geom, n=400)
Sample points along the fitted ellipse for plotting.

rms_error ​

python
def rms_error(coeffs, x, y) -> float
RMS Sampson residual — the single-number fit-quality score.

format_equation ​

python
def format_equation(coeffs)

format_report ​

python
def format_report(coeffs, geom, pts, resid)

qoradfxm.core.history ​

Immutable ordered sequence of :class:`Operation` — the processing recipe.

Being immutable (a tuple under the hood), every edit returns a new History,
which is exactly what the immutable :class:`~qoradfxm.core.dataset.QoraDFXMDataset`
needs. Serializes to / from a plain list for SQLite session storage and Replay.

History ​

python
@dataclass(frozen=True)
class History:
    ops: tuple = ()

History.add ​

python
def add(self, op: Operation) -> History

History.pop ​

python
def pop(self) -> History
Drop the last op (Undo).

History.replace_all ​

python
def replace_all(self, ops) -> History

History.to_list ​

python
def to_list(self) -> list[dict]

History.from_list ​

python
@classmethod
def from_list(cls, items) -> History

qoradfxm.core.io ​

HDF5 / image loading helpers for QoraDFXM detector images.

Pure I/O layer — no Qt, no GUI. The files produced by the beamline store one
or more scans under ``run/``, each holding a ``det`` group with one or more
detectors.  A single detector frame lives at::

    run/<scan>/det/<detector>/data

and, after squeezing singleton dimensions, is a 2-D image.  These helpers
enumerate the available scans / detectors and load frames as float32 arrays.

FramePath ​

python
@dataclass(frozen=True)
class FramePath:
    scan: str
    detector: str
Identifies a single 2-D frame inside an HDF5 file.

FramePath.dataset_path ​

python
@property
def dataset_path(self) -> str

list_frames ​

python
def list_frames(h5_file: Path | str) -> list[FramePath]
Return every ``(scan, detector)`` pair that holds a ``data`` dataset.

A file without a ``run`` group, or without any detector ``data`` dataset,
yields an empty list so the caller can report "nothing to show". A file
that is not readable HDF5 raises ``OSError`` (h5py's behaviour); callers
decide whether to skip it.

H5Node ​

python
@dataclass
class H5Node:
    name: str
    path: str
    is_group: bool
    shape: tuple | None = None
    dtype: str | None = None
    attrs: dict | None = None
    children: list[H5Node] | None = None
One entry in an HDF5 tree (group or dataset).

read_structure ​

python
def read_structure(h5_file: Path | str) -> H5Node
Walk the whole file into a nested :class:`H5Node` tree for display.

load_dataset ​

python
def load_dataset(h5_file: Path | str, dataset_path: str) -> np.ndarray
Load an arbitrary dataset by its full path, squeezed to 2-D float32.

dataset_shape ​

python
def dataset_shape(h5_file: Path | str, dataset_path: str) -> tuple
Shape of a dataset without loading it (squeezed, as `load_*` returns).

load_image_file ​

python
def load_image_file(path: Path | str) -> np.ndarray
Load a plain image file (tif/png/jpg/...) as a 2-D float32 array.

Colour images are converted to grayscale luminance so the analysis tools
(levels, profile, fitting) apply uniformly.

save_tif ​

python
def save_tif(img: np.ndarray, path: Path | str) -> Path
Write a 2-D array as an ImageJ-compatible float32 TIFF.

iter_h5_files ​

python
def iter_h5_files(root: Path | str, recursive: bool=True) -> list[Path]
Every HDF5 file at ``root`` (a file yields itself).

h5_to_tif ​

python
def h5_to_tif(
    h5_file: Path | str,
    out_tif: Path | str,
    dataset_path: str | None = None,
    frame: FramePath | None = None,
) -> Path
Export one detector frame of an HDF5 file to TIFF.

Without ``dataset_path`` / ``frame`` the first available frame is used.

load_frame ​

python
def load_frame(h5_file: Path | str, frame: FramePath | None=None) -> np.ndarray
Load a 2-D detector frame as float32.

If ``frame`` is None the first available frame is used, mirroring the
original ``h5totif`` behaviour (first scan, first detector).

qoradfxm.core.ops ​

Preprocessing operations as pure, serializable records.

An :class:`Operation` stores a *kind* plus *params*. Crucially, reference data
(dark / flat frames) is stored as a **location** (``dataset_path`` inside the
dataset's own h5, or an absolute ``file_path``), NOT as a raw array. That keeps
the whole history JSON-serializable — the foundation for Replay, Undo/Redo and
SQLite session storage. Arrays are resolved lazily at apply time.

Operation ​

python
@dataclass(frozen=True)
class Operation:
    kind: str
    params: dict = field(default_factory=dict)

Operation.to_dict ​

python
def to_dict(self) -> dict

Operation.from_dict ​

python
@classmethod
def from_dict(cls, d: dict) -> Operation

Operation.source ​

python
@property
def source(self) -> str | None

Operation.label ​

python
def label(self) -> str

CorrectionError ​

python
class CorrectionError(ValueError):
    ...
A dark/flat correction could not be applied as recorded.

apply_op ​

python
def apply_op(op: Operation, img: np.ndarray, base_path=None) -> np.ndarray

qoradfxm.core.profile ​

Ellipse ring profile — brightness along a fitted ellipse as it is scaled.

Given a fitted ellipse (centre, semi-axes a/b, tilt θ) the contour is scaled by
a factor ``k`` about its centre and the image is sampled along it::

    x(t, k) = cx + k·a·cos t·cosθ − k·b·sin t·sinθ
    y(t, k) = cy + k·a·cos t·sinθ + k·b·sin t·cosθ

Sweeping ``k`` gives ``I(k)`` — the mean brightness per unit contour length,
i.e. the *line density* of intensity, which is what a Debye–Scherrer-like ring
lets you compare across shots.

Two things this module is careful about:

* **Equal-angle sampling is biased.** Steps in ``t`` are not steps in arc
  length, so the high-curvature ends of the major axis would be over-weighted.
  Every sample is therefore weighted by its arc-length element ``ds/dt``; the
  weighted mean is exactly the intensity per unit length.
* **Measure on linear data.** ``mean(log I) ≠ log(mean I)``. Strip the display
  transforms first — :meth:`qoradfxm.core.dataset.QoraDFXMDataset.linear_view` does
  that while keeping geometry and background correction.

Pure NumPy + OpenCV (sampling only), no Qt.

k_axis ​

python
def k_axis(start: float, stop: float, step: float) -> np.ndarray
Inclusive scale axis, e.g. ``k_axis(0.2, 2.0, 0.01)``.

RingProfile ​

python
@dataclass
class RingProfile:
    k: np.ndarray
    mean: np.ndarray
    std: np.ndarray
    total: np.ndarray
    perimeter: np.ndarray
    valid_frac: np.ndarray
    semi_major: np.ndarray
    theta: np.ndarray | None = None
    map: np.ndarray | None = None
    params: dict = field(default_factory=dict)
``I(k)`` plus everything needed to interpret it.

RingProfile.to_dataframe ​

python
def to_dataframe(self) -> pd.DataFrame

RingProfile.to_csv ​

python
def to_csv(self, path) -> None

RingProfile.peak ​

python
def peak(self) -> tuple[float, float]
(k, intensity) at the maximum, parabola-refined between samples.

RingProfile.fwhm ​

python
def fwhm(self) -> float
Full width (in k) at half maximum above the curve's own baseline.

ring_profile ​

python
def ring_profile(
    img,
    fit,
    *,
    k=(0.2, 2.0, 0.01),
    n_theta: int = 720,
    width: float = 0.0,
    width_unit: str = 'px',
    n_sub: int = 3,
    keep_map: bool = False,
) -> RingProfile
Sample mean brightness along the fitted ellipse over a range of scales.

Parameters
----------
img : 2-D array (use LINEAR intensity — see the module docstring)
fit : FitResult, its ``geom`` dict, or a Master row dict
k : ``(start, stop, step)`` or an explicit array of scale factors
n_theta : samples around the contour (arc-length weighted)
width, width_unit : ring thickness, in ``px`` (converted via the mean
    radius) or in ``k`` units. 0 = a single one-pixel-wide contour.
n_sub : sub-rings averaged across that thickness (ignored if width == 0)
keep_map : also return the full ``I(k, θ)`` array (unrolled ring image)

qoradfxm.core.results ​

Fit results + the Master results frame (pandas). No Qt here.

``ResultsFrame`` is the single source of truth for fit results (spec req. 4).
The GUI's QTableView is a *view* onto it via a thin Qt adapter in
``qoradfxm/gui/models.py``; the CLI writes it straight to CSV.

UnknownColumnsWarning ​

python
class UnknownColumnsWarning(UserWarning):
    def __init__(self, message: str, columns: list[str]) -> None
A CSV had columns that are not results columns; ``columns`` lists them.

FitResult ​

python
@dataclass
class FitResult:
    center_x: float
    center_y: float
    major_axis: float
    minor_axis: float
    angle_deg: float
    fit_error: float
    points: list = field(default_factory=list)
    geom: dict = field(default_factory=dict)
One ellipse fit: geometry + quality + the points it came from.

FitResult.from_points ​

python
@classmethod
def from_points(cls, points) -> FitResult
Fit an ellipse to picked (x, y) points and build a result.

FitResult.to_row ​

python
def to_row(self, shot_id='', status='OK', bg_applied=False, log_scale=False) -> dict

FitResult.to_dict ​

python
def to_dict(self) -> dict

FitResult.from_dict ​

python
@classmethod
def from_dict(cls, d: dict) -> FitResult

empty_frame ​

python
def empty_frame() -> pd.DataFrame

ResultsFrame ​

python
class ResultsFrame:
    def __init__(self, df: pd.DataFrame | None=None) -> None
Pure pandas wrapper around the Master DataFrame. No Qt.

ResultsFrame.df ​

python
@property
def df(self) -> pd.DataFrame

ResultsFrame.add_row ​

python
def add_row(self, row: dict) -> int

ResultsFrame.index_of ​

python
def index_of(self, shot_id: str) -> int | None
Row index for a shot_id (Phase 2: 1:1 shot ↔ row), or None.

ResultsFrame.upsert_row ​

python
def upsert_row(self, row: dict) -> tuple[int, bool]
Update the row with this shot_id in place, or append it.

Returns (row_index, created). Preserves an existing 'status' unless the
incoming row carries one (so an EXCLUDE flag survives a re-fit).

ResultsFrame.update_cell ​

python
def update_cell(self, r: int, col: str, value) -> None

ResultsFrame.get_cell ​

python
def get_cell(self, r: int, col: str)

ResultsFrame.remove_row ​

python
def remove_row(self, r: int) -> None

ResultsFrame.set_status ​

python
def set_status(self, r: int, status: str) -> None

ResultsFrame.clear ​

python
def clear(self) -> None

ResultsFrame.to_csv ​

python
def to_csv(self, path: str) -> None

ResultsFrame.from_csv ​

python
@classmethod
def from_csv(cls, path: str) -> ResultsFrame

qoradfxm.core.transform ​

Pixel-domain transforms. Pure NumPy, no Qt.

adaptive_log ​

python
def adaptive_log(img) -> np.ndarray
Normalized log1p — compresses dynamic range to [0, 1].

NaN-tolerant: geometric ops can leave NaN in the corners, and a single NaN
in the max would otherwise blank the whole image.

qoradfxm.core.warp ​

Geometric transforms — ops that resample the pixel grid.

Unlike the intensity ops in :mod:`qoradfxm.core.transform` these change *where* a
pixel is (and usually the image shape), so they are not commutative with
anything that references another frame by shape (``sub_bg`` / ``divide``) and
they invalidate coordinates picked on an earlier version of the image. Order in
the history therefore matters — put geometry first if a fit is to follow.

Pure NumPy + OpenCV, no Qt. OpenCV is imported lazily so that merely importing
the engine stays cheap.

scale ​

python
def scale(img, sx: float=1.0, sy: float | None=None, interp='auto') -> np.ndarray
Resample by independent x / y factors (``sy`` defaults to ``sx``).

``sx != sy`` is the aspect-ratio change: 1.0 keeps the axis as is, 0.5
halves it, 2.0 doubles it. Output shape is the input shape times the
factors, rounded, floored at 1 pixel.

rotate ​

python
def rotate(
    img,
    angle_deg: float,
    expand: bool = True,
    interp='linear',
    fill: float = 0.0,
) -> np.ndarray
Rotate about the image centre. Positive angle = counter-clockwise.

With ``expand`` the canvas grows so nothing is cut off (corners are filled
with ``fill``); without it the original shape is kept and content leaves
the frame.

flip ​

python
def flip(img, axis: str='h') -> np.ndarray
Mirror the image: ``h`` = left↔right, ``v`` = top↔bottom, ``both``.

qoradfxm.gui.cli_bridge ​

Run CLI jobs from the GUI.

The GUI builds a recipe interactively; batch work then goes back out through
the *same* command line a user could have typed (:mod:`qoradfxm.cli`), spawned as a
child process. Reasons for a subprocess rather than an in-process call:

* a long batch never blocks the event loop, and can be cancelled;
* a crash in a job (bad file, out-of-memory) does not take the app down;
* the exact command is printable — reproducible outside the GUI.

    job = CliJob(self)
    job.line.connect(self.log)
    job.finished_ok.connect(lambda code: ...)
    job.start(argv_for_dataset(doc.ds, points_file=pts, out="results.csv"))

command_line ​

python
def command_line(argv: list[str]) -> str
The job as a copy-pasteable one-liner (for logs / 'show command').

CliJob ​

python
class CliJob(QtCore.QObject):
    def __init__(self, parent: QtCore.QObject | None=None) -> None
One ``qoradfxm ...`` child process, streamed back as signals.

CliJob.start ​

python
def start(self, argv: list[str], cwd: Path | str | None=None) -> None

CliJob.cancel ​

python
def cancel(self) -> None

CliJob.is_running ​

python
def is_running(self) -> bool

qoradfxm.gui.control_dock ​

Control dock, analysis dock and the ring profile panel.

ControlDockMixin ​

python
class ControlDockMixin:
    ...
Control dock, analysis dock and the ring profile panel.

qoradfxm.gui.document ​

Per-file document session.

DocumentSession ​

python
@dataclass
class DocumentSession:
    file_path: Path
    kind: str
    view: ImageView | None = None
    ds: object = None
    frames: list = field(default_factory=list)
    frame: object = None
    structure: object = None
    preview: bool = False
    info: str = ''
    session_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
    logs: list = field(default_factory=list)
    roi_objects: dict = field(default_factory=dict)
    prefs: ViewPrefs = field(default_factory=ViewPrefs)
Independent per-file session: image, ROI objects, logs, view settings.

DocumentSession.add_log ​

python
def add_log(self, message: str) -> None

DocumentSession.add_roi ​

python
def add_roi(self, roi) -> None

DocumentSession.remove_roi ​

python
def remove_roi(self, roi_id: str)

DocumentSession.get_roi ​

python
def get_roi(self, roi_id: str)

DocumentSession.rois_of_type ​

python
def rois_of_type(self, roi_type: str) -> list

qoradfxm.gui.documents ​

Document and tab system, file logic, fit logic and cursor readout.

DocumentsMixin ​

python
class DocumentsMixin:
    ...
Document and tab system, file logic, fit logic and cursor readout.

DocumentsMixin.open_path ​

python
def open_path(self, path: Path) -> None
Public entry used by the launcher (``qoradfxm gui FILE ...``).

qoradfxm.gui.file_dock ​

File explorer dock: tree, name filter and context menu.

FileDockMixin ​

python
class FileDockMixin:
    ...
File explorer dock: tree, name filter and context menu.

FileDockMixin.eventFilter ​

python
def eventFilter(self, obj, event) -> bool

qoradfxm.gui.icons ​

logo_icon ​

python
def logo_icon() -> QtGui.QIcon
Window / taskbar icon: the square mark, never the wordmark.

logo_pixmap ​

python
def logo_pixmap(size: int=120) -> QtGui.QPixmap
The square mark at `size` px — legible down to toolbar height.

wordmark_pixmap ​

python
def wordmark_pixmap(height: int=72, on_dark: bool=True) -> QtGui.QPixmap
The full lockup — only where there is room to read it (About/settings).

AppIcons ​

python
class AppIcons:
    ...

AppIcons.get ​

python
@staticmethod
def get(name_or_qta: str, color: str='#d4d4d8') -> QtGui.QIcon

AppIcons.get_pixmap ​

python
@staticmethod
def get_pixmap(name_or_qta: str, size: int=14, color: str='#ffffff') -> QtGui.QPixmap
Pixmap of the icon, for QSS or QPainter.

qoradfxm.gui.image_view ​

Interactive image view: display, log scaling, histogram/levels,
over-max highlighting and interactive shape fitting.

Built on pyqtgraph.  A :class:`HistogramLUTItem` gives the histogram plus
draggable min/max level bars (requirement 3).  Pixels above the chosen max
are painted red by a separate RGBA overlay so the highlight is independent
of the chosen colormap (requirement 4).  A log-scale toggle applies
:func:`qoradfxm.transform.adaptive_log` (requirement 2).  Ellipse fitting reuses
the least-squares fitter in :mod:`qoradfxm.ellipse_fit` (requirement 6).

DrawViewBox ​

python
class DrawViewBox(pg.ViewBox):
    def __init__(self, *a, **k)
ViewBox that, in ``draw_mode``, turns a left-drag into a rubber-band
rectangle instead of a pan (used by the region tool).

DrawViewBox.mouseDragEvent ​

python
def mouseDragEvent(self, ev, axis=None)

ImageView ​

python
class ImageView(QtWidgets.QWidget):
    def __init__(self, parent: QtWidgets.QWidget | None=None) -> None
Image display with histogram, level control and ellipse fitting.

ImageView.set_image ​

python
def set_image(self, img: np.ndarray) -> None
Load a new raw frame, resetting fit state and autoscaling levels.

ImageView.has_image ​

python
def has_image(self) -> bool

ImageView.image_shape ​

python
def image_shape(self) -> tuple[int, int]

ImageView.reset_zoom ​

python
def reset_zoom(self) -> None
Fit the whole image (or error marker) back into the view.

ImageView.current_zoom ​

python
def current_zoom(self) -> float

ImageView.apply_theme ​

python
def apply_theme(self, dark: bool) -> None

ImageView.set_grid ​

python
def set_grid(self, on: bool) -> None

ImageView.zoom_in ​

python
def zoom_in(self) -> None

ImageView.zoom_out ​

python
def zoom_out(self) -> None

ImageView.set_zoom ​

python
def set_zoom(self, percent: float) -> None

ImageView.show_error ​

python
def show_error(self, message: str) -> None
Blank the image and show the empty-image placeholder + message.

ImageView.set_scale ​

python
def set_scale(self, px_size: float, unit: str) -> None
Calibrate: each pixel spans ``px_size`` of the given real unit.

ImageView.set_scale_pixels ​

python
def set_scale_pixels(self) -> None
Revert the scale bar to raw pixels.

ImageView.update_processed ​

python
def update_processed(self, img: np.ndarray) -> None
Swap the displayed (already-processed) image, keeping ROIs and zoom.

Used when the Core pipeline changes (preproc / log toggled). Unlike
:meth:`set_image` it does NOT clear fits or reset the view range.

ImageView.set_colormap ​

python
def set_colormap(self, name: str) -> None

ImageView.set_overmax ​

python
def set_overmax(self, enabled: bool) -> None

ImageView.set_overmax_color ​

python
def set_overmax_color(self, color: tuple[int, int, int] | QtGui.QColor) -> None

ImageView.get_levels ​

python
def get_levels(self) -> tuple[float, float]

ImageView.set_levels ​

python
def set_levels(self, lo: float, hi: float) -> None

ImageView.autoscale_levels ​

python
def autoscale_levels(self) -> None

ImageView.display_array ​

python
def display_array(self) -> np.ndarray | None
The processed image exactly as displayed (float, pre-colormap).

ImageView.save_display ​

python
def save_display(self, path: str) -> None
Write the colormapped, level-clipped image at native resolution.

ImageView.histogram_visible ​

python
def histogram_visible(self) -> bool

ImageView.is_histogram_detached ​

python
def is_histogram_detached(self) -> bool

ImageView.set_histogram_visible ​

python
def set_histogram_visible(self, vis: bool) -> None
Collapse (hide) or restore the embedded histogram column.

ImageView.detach_histogram ​

python
def detach_histogram(self) -> None
Pop the histogram out into its own floating window.

ImageView.set_tool ​

python
def set_tool(self, name: str) -> None
Switch the active interaction tool (state machine).

ImageView.current_tool ​

python
def current_tool(self) -> str

ImageView.point_count ​

python
def point_count(self) -> int

ImageView.clear_fit ​

python
def clear_fit(self) -> None

ImageView.add_roi_item ​

python
def add_roi_item(self, roi) -> None
Draw a managed ROI on the canvas and emit signals on edits.

ImageView.remove_roi_item ​

python
def remove_roi_item(self, roi) -> None

ImageView.select_roi ​

python
def select_roi(self, roi) -> None
Highlight a ROI (cyan, thicker); deselect the previous one.

ImageView.selected_roi ​

python
def selected_roi(self)

ImageView.set_active_line ​

python
def set_active_line(self, roi) -> None

ImageView.clear_distance ​

python
def clear_distance(self) -> None

ImageView.distance_count ​

python
def distance_count(self) -> int

ImageView.fit_ellipse ​

python
def fit_ellipse(self) -> dict | None
Fit an ellipse to the picked points and draw it. Returns geometry.

ImageView.last_geometry ​

python
def last_geometry(self) -> dict | None

ImageView.picked_points ​

python
def picked_points(self) -> np.ndarray

qoradfxm.gui.main_window ​

Main application window: folder browser, image view, controls.

MainWindow ​

python
class MainWindow(DocumentsMixin, ResultsTableMixin, ControlDockMixin, FileDockMixin, RibbonMixin, PipelineMixin, QtWidgets.QMainWindow):
    def __init__(self) -> None

MainWindow.closeEvent ​

python
def closeEvent(self, event) -> None

MainWindow.dragEnterEvent ​

python
def dragEnterEvent(self, e) -> None

MainWindow.dragMoveEvent ​

python
def dragMoveEvent(self, e) -> None

MainWindow.dragLeaveEvent ​

python
def dragLeaveEvent(self, e) -> None

MainWindow.dropEvent ​

python
def dropEvent(self, e) -> None

MainWindow.resizeEvent ​

python
def resizeEvent(self, e) -> None

MainWindow.apply_theme ​

python
def apply_theme(self, theme: str) -> None

qoradfxm.gui.models ​

Qt adapters that expose Core data objects to the UI. View-layer only.

``MasterTableModel`` is a QAbstractTableModel backed by a Core
:class:`~qoradfxm.core.results.ResultsFrame` (the real data). The QTableView renders
this; all data lives in Core.

MasterTableModel ​

python
class MasterTableModel(QtCore.QAbstractTableModel):
    def __init__(self, results: ResultsFrame | None=None, parent=None) -> None
Qt view-model over a Core ResultsFrame.

MasterTableModel.results ​

python
@property
def results(self) -> ResultsFrame

MasterTableModel.df ​

python
@property
def df(self)

MasterTableModel.set_results ​

python
def set_results(self, results: ResultsFrame) -> None

MasterTableModel.rowCount ​

python
def rowCount(self, parent=QtCore.QModelIndex()) -> int

MasterTableModel.columnCount ​

python
def columnCount(self, parent=QtCore.QModelIndex()) -> int

MasterTableModel.headerData ​

python
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole)

MasterTableModel.data ​

python
def data(self, index, role=QtCore.Qt.DisplayRole)

MasterTableModel.flags ​

python
def flags(self, index)

MasterTableModel.setData ​

python
def setData(self, index, value, role=QtCore.Qt.EditRole) -> bool

MasterTableModel.data_for_edit ​

python
def data_for_edit(self, index)

MasterTableModel.add_row ​

python
def add_row(self, row: dict) -> int

MasterTableModel.upsert_row ​

python
def upsert_row(self, row: dict) -> tuple[int, bool]
Update the shot_id's row in place (dataChanged) or append it.

MasterTableModel.index_of ​

python
def index_of(self, shot_id: str) -> int | None

MasterTableModel.shot_id_at ​

python
def shot_id_at(self, r: int)

MasterTableModel.status_at ​

python
def status_at(self, r: int)

MasterTableModel.remove_row ​

python
def remove_row(self, r: int) -> None

MasterTableModel.set_status ​

python
def set_status(self, r: int, status: str) -> None

MasterTableModel.clear ​

python
def clear(self) -> None

MasterTableModel.to_csv ​

python
def to_csv(self, path: str) -> None

qoradfxm.gui.pipeline ​

Preprocessing chain, ROI handling and the pipeline object tree.

PipelineMixin ​

python
class PipelineMixin:
    ...
Preprocessing chain, ROI handling and the pipeline object tree.

PipelineMixin.add_roi ​

python
def add_roi(self, roi) -> None
Add an ROI to the active canvas and register it in the session.

PipelineMixin.remove_roi ​

python
def remove_roi(self, roi_id: str) -> None

PipelineMixin.add_line_roi ​

python
def add_line_roi(self, positions=None) -> LineProfileROI
Create + register a line-profile ROI (used by later phases/tools).

qoradfxm.gui.prefs ​

View preferences — one owner for display state.

Before this existed the same setting lived in up to four places (the widget's
own checked state, a QSettings key, a free-form ``view_settings`` dict on the
document, and sometimes the Core recipe), and they disagreed. The rules now:

* **Document-scoped, Core-owned** — anything that changes pixel VALUES is an op
  in ``doc.ds.history`` (log, gamma, sub_bg, geometry). Never duplicated here.
  The Log checkbox is a *view* of ``ds.log_scale``, not a place state lives.
* **Document-scoped, view-owned** — how one image is drawn right now:
  :class:`ViewPrefs` on the document.
* **App-scoped** — the same object held by the window as "what a newly opened
  document starts with", persisted to QSettings.

So a panel widget never stores state: it edits the app prefs and the current
document's prefs, and both are re-read when a tab is activated.

ViewPrefs ​

python
@dataclass
class ViewPrefs:
    colormap: str = 'gray'
    overmax: bool = True
    overmax_color: str = DEFAULT_OVERMAX_COLOR
    scale_on: bool = False
    px_size: float = 1.0
    unit: str = 'µm'
    tool: str = 'select'
How one image is displayed. No intensity ops — those live in the recipe.

ViewPrefs.load ​

python
@classmethod
def load(cls, settings) -> ViewPrefs
Read the app-scoped defaults (``tool`` is deliberately not stored).

ViewPrefs.save ​

python
def save(self, settings) -> None

ViewPrefs.copy ​

python
def copy(self, **changes) -> ViewPrefs
A new instance — documents must never share one prefs object.

ViewPrefs.apply_to ​

python
def apply_to(self, view) -> None
The single path from prefs to an ImageView.

ViewPrefs.to_dict ​

python
def to_dict(self) -> dict

qoradfxm.gui.results_table ​

Results table, shot inspector and ribbon-driven data table.

ResultsTableMixin ​

python
class ResultsTableMixin:
    ...
Results table, shot inspector and ribbon-driven data table.

qoradfxm.gui.results_window ​

Standalone, resizable window for the Master results table.

The table is large, so it lives in its own top-level window rather than a
cramped bottom dock. It renders the SAME MasterTableModel as everywhere else,
so edits / fits stay in sync live. Row-click still switches the active shot.

DropTableView ​

python
class DropTableView(QtWidgets.QTableView):
    def __init__(self, parent=None) -> None
QTableView that accepts file drops (from the sidebar or the OS).

Emits :attr:`filesDropped` with the dropped local paths so the host can
auto-create rows with basic info (filename, etc.).

DropTableView.dragEnterEvent ​

python
def dragEnterEvent(self, e) -> None

DropTableView.dragMoveEvent ​

python
def dragMoveEvent(self, e) -> None

DropTableView.dropEvent ​

python
def dropEvent(self, e) -> None

ResultsWindow ​

python
class ResultsWindow(QtWidgets.QMainWindow):
    def __init__(self, table: QtWidgets.QTableView, actions, settings, parent=None) -> None

ResultsWindow.show_raise ​

python
def show_raise(self) -> None

ResultsWindow.closeEvent ​

python
def closeEvent(self, e: QtGui.QCloseEvent) -> None

qoradfxm.gui.ribbon ​

Ribbon UI, keyboard shortcuts and the tab bar commands.

RibbonMixin ​

python
class RibbonMixin:
    SHORTCUTS: ClassVar[tuple[tuple[str, str, str], ...]] = (('Ctrl+O', '_open_file', '파일 열기'), ('Ctrl+Shift+O', '_open_folder', '폴더 열기'), ('Ctrl+W', '_close_current_tab', '탭 닫기'), ('Ctrl+S', '_export_processed', '처리된 이미지 저장'), ('Ctrl+Shift+S', '_export_csv', '결과 표 CSV 저장'), ('Ctrl+Tab', '_next_tab', '다음 탭'), ('Ctrl+Shift+Tab', '_prev_tab', '이전 탭'), ('Ctrl+0', '_do_reset', '화면에 맞춤'), ('Ctrl+1', '_do_zoom100', '100% 줌'), ('Ctrl++', '_zoom_in', '확대'), ('Ctrl+=', '_zoom_in', '확대 (= 키)'), ('Ctrl+-', '_zoom_out', '축소'))
Ribbon UI, keyboard shortcuts and the tab bar commands.

qoradfxm.gui.ring_panel ​

Ring profile panel: live plot of the brightness I(k) around the ellipse.

The panel owns no pixel math: it asks its *source* callback for the current
(linear) image plus the live ellipse geometry, hands both to
:func:`qoradfxm.core.profile.ring_profile`, and draws what comes back. Dragging the
ellipse ROI therefore re-measures instead of re-implementing anything.

Updates are coalesced through a short timer so a drag produces a handful of
recomputes, not one per mouse move.

RingProfilePanel ​

python
class RingProfilePanel(QtWidgets.QWidget):
    def __init__(self, source=None, parent: QtWidgets.QWidget | None=None) -> None
Plot of arc-length-weighted mean brightness vs. the ellipse scale k.

RingProfilePanel.set_source ​

python
def set_source(self, source) -> None
``source() -> (image, geom) | None`` — linear image + live ellipse.

RingProfilePanel.request_update ​

python
def request_update(self) -> None
Coalesced refresh; ignored while the live checkbox is off (buttons still work).

RingProfilePanel.refresh ​

python
def refresh(self) -> None

RingProfilePanel.profile ​

python
@property
def profile(self)
The last computed :class:`~qoradfxm.core.profile.RingProfile`, or None.

qoradfxm.gui.roi ​

Analysis ROI objects (Phase B-1).

A small class hierarchy that turns the previously single-instance ellipse / line
overlays into uniquely-identified, managed objects — the data model the ParaView
style object tree (later phases) will bind to.

``AnalysisROI`` is a metadata mixin (uuid, name, type, visible, locked) that is
combined with a concrete pyqtgraph ROI (``EllipseROI`` / ``LineSegmentROI``,
both ``pg.ROI`` subclasses).  Concrete init calls the pyqtgraph base explicitly
(pyqtgraph ROIs do not use cooperative ``super().__init__``), then attaches the
analysis metadata and wires ``sigRegionChanged`` so the object keeps its own
geometry in sync while the user drags it.

AnalysisROI ​

python
class AnalysisROI:
    ...
Metadata + shared behaviour mixed into a concrete pyqtgraph ROI.

AnalysisROI.visible ​

python
@property
def visible(self) -> bool

AnalysisROI.set_visible ​

python
def set_visible(self, on: bool) -> None

AnalysisROI.locked ​

python
@property
def locked(self) -> bool

AnalysisROI.set_locked ​

python
def set_locked(self, on: bool) -> None
Locked ROIs cannot be moved / resized and hide their handles.

AnalysisROI.update_data ​

python
def update_data(self) -> None

AnalysisROI.rename ​

python
def rename(self, name: str) -> None

AnalysisROI.to_dict ​

python
def to_dict(self) -> dict

EllipseFitROI ​

python
class EllipseFitROI(pg.EllipseROI, AnalysisROI):
    def __init__(self, geometry: dict, points=None, name: str='Ellipse', **kw)
Editable ellipse initialised from a least-squares fit result.

EllipseFitROI.update_data ​

python
def update_data(self) -> None

LineProfileROI ​

python
class LineProfileROI(pg.LineSegmentROI, AnalysisROI):
    def __init__(self, positions, name: str='Line', **kw)
Two-point line used to sample an intensity profile.

LineProfileROI.update_data ​

python
def update_data(self) -> None

LineProfileROI.length ​

python
def length(self) -> float

RectRegionROI ​

python
class RectRegionROI(pg.RectROI, AnalysisROI):
    def __init__(self, pos, size, name: str='Region', **kw)
Rectangular region-of-interest (crop / analysis area).

RectRegionROI.update_data ​

python
def update_data(self) -> None

RectRegionROI.region_slice ​

python
def region_slice(self)
(row_slice, col_slice) for indexing the image array, clamped >=0.

qoradfxm.gui.settings_dialog ​

Preferences dialog.

SettingsDialog ​

python
class SettingsDialog(QtWidgets.QDialog):
    def __init__(self, parent, settings: QtCore.QSettings)
Sidebar-tabbed preferences: General / Display / Shortcuts.

SettingsDialog.result_values ​

python
def result_values(self) -> dict

qoradfxm.gui.theme ​

Qt style sheet built from the light and dark palettes.

build_style ​

python
def build_style(theme: str) -> str

qoradfxm.gui.widgets ​

Small reusable widgets of the main window.

CollapsibleSection ​

python
class CollapsibleSection(QtWidgets.QWidget):
    def __init__(self, title: str, body: QtWidgets.QWidget, expanded: bool=True)
A titled section whose body folds away when the header is clicked.

DropOverlay ​

python
class DropOverlay(QtWidgets.QWidget):
    def __init__(self, parent: QtWidgets.QWidget)
Full-window translucent overlay shown while dragging files in.

DropOverlay.set_ok ​

python
def set_ok(self, ok: bool) -> None

DropOverlay.cover ​

python
def cover(self) -> None

FilterEdit ​

python
class FilterEdit(QtWidgets.QLineEdit):
    ...
Explorer filter box: Escape clears it and hands focus back to the tree.

The window binds Escape to "select tool", and a window-level shortcut wins
over the focused widget — so the box has to claim the key first by
accepting the ShortcutOverride, which is Qt's way of saying "this is mine
while I have focus".

FilterEdit.event ​

python
def event(self, ev) -> bool

FilterEdit.keyPressEvent ​

python
def keyPressEvent(self, event) -> None

Each project section is built from that project’s docs/ directory.