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.ArgumentParsermain
python
def main(argv=None) -> intqoradfxm.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) -> strInverse 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 rowQoraDFXMDataset
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 = NoneQoraDFXMDataset.from_array
python
@classmethod
def from_array(cls, arr, source_path=None, meta=None) -> QoraDFXMDatasetQoraDFXMDataset.from_h5
python
@classmethod
def from_h5(cls, path, dataset_path, meta=None) -> QoraDFXMDatasetQoraDFXMDataset.from_frame
python
@classmethod
def from_frame(cls, path, frame: _io.FramePath | None=None) -> QoraDFXMDatasetQoraDFXMDataset.from_image_file
python
@classmethod
def from_image_file(cls, path) -> QoraDFXMDatasetQoraDFXMDataset.sub_bg
python
def sub_bg(self, **source) -> QoraDFXMDatasetDark subtract. Source: dataset_path=... or file_path=...
QoraDFXMDataset.divide
python
def divide(self, **source) -> QoraDFXMDatasetFlat-field divide. Source: dataset_path=... or file_path=...
QoraDFXMDataset.apply_log
python
def apply_log(self) -> QoraDFXMDatasetQoraDFXMDataset.pure_log
python
def pure_log(self) -> QoraDFXMDatasetQoraDFXMDataset.sqrt
python
def sqrt(self) -> QoraDFXMDatasetQoraDFXMDataset.gamma
python
def gamma(self, g: float=0.5) -> QoraDFXMDatasetQoraDFXMDataset.normalize
python
def normalize(self) -> QoraDFXMDatasetQoraDFXMDataset.scale
python
def scale(self, sx: float, sy: float | None=None, interp='auto') -> QoraDFXMDatasetResize by factors; ``sx != sy`` changes the aspect ratio.
QoraDFXMDataset.rotate
python
def rotate(self, angle_deg: float, expand: bool=True) -> QoraDFXMDatasetRotate about the centre, positive = counter-clockwise.
QoraDFXMDataset.flip
python
def flip(self, axis: str='h') -> QoraDFXMDatasetMirror: ``h`` (left↔right), ``v`` (top↔bottom) or ``both``.
QoraDFXMDataset.add_op
python
def add_op(self, kind: str, **params) -> QoraDFXMDatasetGeneric append (used when replaying a GUI/serialized recipe).
QoraDFXMDataset.fit_ellipse
python
def fit_ellipse(self, points) -> QoraDFXMDatasetFit an ellipse to picked points (on the PROCESSED image).
QoraDFXMDataset.linear_view
python
def linear_view(self) -> QoraDFXMDatasetSame 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) -> QoraDFXMDatasetQoraDFXMDataset.set_history
python
def set_history(self, history: History) -> QoraDFXMDatasetQoraDFXMDataset.replay
python
def replay(self, history: History) -> QoraDFXMDatasetNew dataset from the same raw with a given recipe (Replay/Macro).
QoraDFXMDataset.image
python
@property
def image(self) -> np.ndarrayraw with the full history applied, in order (memoized).
QoraDFXMDataset.bg_applied
python
@property
def bg_applied(self) -> boolQoraDFXMDataset.log_scale
python
@property
def log_scale(self) -> boolQoraDFXMDataset.to_record
python
def to_record(self, status: str='OK') -> dict | NoneOne Master-table row, or None if not yet fitted.
QoraDFXMDataset.to_dict
python
def to_dict(self) -> dictSerialize recipe + result + identity (arrays excluded → SQLite).
QoraDFXMDataset.from_dict
python
@classmethod
def from_dict(cls, d: dict, raw=None) -> QoraDFXMDatasetRebuild 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) -> floatRMS 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) -> HistoryHistory.pop
python
def pop(self) -> HistoryDrop the last op (Undo).
History.replace_all
python
def replace_all(self, ops) -> HistoryHistory.to_list
python
def to_list(self) -> list[dict]History.from_list
python
@classmethod
def from_list(cls, items) -> Historyqoradfxm.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: strIdentifies a single 2-D frame inside an HDF5 file.
FramePath.dataset_path
python
@property
def dataset_path(self) -> strlist_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 = NoneOne entry in an HDF5 tree (group or dataset).
read_structure
python
def read_structure(h5_file: Path | str) -> H5NodeWalk 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.ndarrayLoad 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) -> tupleShape of a dataset without loading it (squeezed, as `load_*` returns).
load_image_file
python
def load_image_file(path: Path | str) -> np.ndarrayLoad 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) -> PathWrite 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,
) -> PathExport 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.ndarrayLoad 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) -> dictOperation.from_dict
python
@classmethod
def from_dict(cls, d: dict) -> OperationOperation.source
python
@property
def source(self) -> str | NoneOperation.label
python
def label(self) -> strCorrectionError
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.ndarrayqoradfxm.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.ndarrayInclusive 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.DataFrameRingProfile.to_csv
python
def to_csv(self, path) -> NoneRingProfile.peak
python
def peak(self) -> tuple[float, float](k, intensity) at the maximum, parabola-refined between samples.
RingProfile.fwhm
python
def fwhm(self) -> floatFull 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,
) -> RingProfileSample 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]) -> NoneA 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) -> FitResultFit 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) -> dictFitResult.to_dict
python
def to_dict(self) -> dictFitResult.from_dict
python
@classmethod
def from_dict(cls, d: dict) -> FitResultempty_frame
python
def empty_frame() -> pd.DataFrameResultsFrame
python
class ResultsFrame:
def __init__(self, df: pd.DataFrame | None=None) -> NonePure pandas wrapper around the Master DataFrame. No Qt.
ResultsFrame.df
python
@property
def df(self) -> pd.DataFrameResultsFrame.add_row
python
def add_row(self, row: dict) -> intResultsFrame.index_of
python
def index_of(self, shot_id: str) -> int | NoneRow 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) -> NoneResultsFrame.get_cell
python
def get_cell(self, r: int, col: str)ResultsFrame.remove_row
python
def remove_row(self, r: int) -> NoneResultsFrame.set_status
python
def set_status(self, r: int, status: str) -> NoneResultsFrame.clear
python
def clear(self) -> NoneResultsFrame.to_csv
python
def to_csv(self, path: str) -> NoneResultsFrame.from_csv
python
@classmethod
def from_csv(cls, path: str) -> ResultsFrameqoradfxm.core.transform
Pixel-domain transforms. Pure NumPy, no Qt.
adaptive_log
python
def adaptive_log(img) -> np.ndarrayNormalized 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.ndarrayResample 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.ndarrayRotate 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.ndarrayMirror 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]) -> strThe 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) -> NoneOne ``qoradfxm ...`` child process, streamed back as signals.
CliJob.start
python
def start(self, argv: list[str], cwd: Path | str | None=None) -> NoneCliJob.cancel
python
def cancel(self) -> NoneCliJob.is_running
python
def is_running(self) -> boolqoradfxm.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) -> NoneDocumentSession.add_roi
python
def add_roi(self, roi) -> NoneDocumentSession.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) -> listqoradfxm.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) -> NonePublic 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) -> boolqoradfxm.gui.icons
logo_icon
python
def logo_icon() -> QtGui.QIconWindow / taskbar icon: the square mark, never the wordmark.
logo_pixmap
python
def logo_pixmap(size: int=120) -> QtGui.QPixmapThe square mark at `size` px — legible down to toolbar height.
wordmark_pixmap
python
def wordmark_pixmap(height: int=72, on_dark: bool=True) -> QtGui.QPixmapThe 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.QIconAppIcons.get_pixmap
python
@staticmethod
def get_pixmap(name_or_qta: str, size: int=14, color: str='#ffffff') -> QtGui.QPixmapPixmap 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) -> NoneImage display with histogram, level control and ellipse fitting.
ImageView.set_image
python
def set_image(self, img: np.ndarray) -> NoneLoad a new raw frame, resetting fit state and autoscaling levels.
ImageView.has_image
python
def has_image(self) -> boolImageView.image_shape
python
def image_shape(self) -> tuple[int, int]ImageView.reset_zoom
python
def reset_zoom(self) -> NoneFit the whole image (or error marker) back into the view.
ImageView.current_zoom
python
def current_zoom(self) -> floatImageView.apply_theme
python
def apply_theme(self, dark: bool) -> NoneImageView.set_grid
python
def set_grid(self, on: bool) -> NoneImageView.zoom_in
python
def zoom_in(self) -> NoneImageView.zoom_out
python
def zoom_out(self) -> NoneImageView.set_zoom
python
def set_zoom(self, percent: float) -> NoneImageView.show_error
python
def show_error(self, message: str) -> NoneBlank the image and show the empty-image placeholder + message.
ImageView.set_scale
python
def set_scale(self, px_size: float, unit: str) -> NoneCalibrate: each pixel spans ``px_size`` of the given real unit.
ImageView.set_scale_pixels
python
def set_scale_pixels(self) -> NoneRevert the scale bar to raw pixels.
ImageView.update_processed
python
def update_processed(self, img: np.ndarray) -> NoneSwap 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) -> NoneImageView.set_overmax
python
def set_overmax(self, enabled: bool) -> NoneImageView.set_overmax_color
python
def set_overmax_color(self, color: tuple[int, int, int] | QtGui.QColor) -> NoneImageView.get_levels
python
def get_levels(self) -> tuple[float, float]ImageView.set_levels
python
def set_levels(self, lo: float, hi: float) -> NoneImageView.autoscale_levels
python
def autoscale_levels(self) -> NoneImageView.display_array
python
def display_array(self) -> np.ndarray | NoneThe processed image exactly as displayed (float, pre-colormap).
ImageView.save_display
python
def save_display(self, path: str) -> NoneWrite the colormapped, level-clipped image at native resolution.
ImageView.histogram_visible
python
def histogram_visible(self) -> boolImageView.is_histogram_detached
python
def is_histogram_detached(self) -> boolImageView.set_histogram_visible
python
def set_histogram_visible(self, vis: bool) -> NoneCollapse (hide) or restore the embedded histogram column.
ImageView.detach_histogram
python
def detach_histogram(self) -> NonePop the histogram out into its own floating window.
ImageView.set_tool
python
def set_tool(self, name: str) -> NoneSwitch the active interaction tool (state machine).
ImageView.current_tool
python
def current_tool(self) -> strImageView.point_count
python
def point_count(self) -> intImageView.clear_fit
python
def clear_fit(self) -> NoneImageView.add_roi_item
python
def add_roi_item(self, roi) -> NoneDraw a managed ROI on the canvas and emit signals on edits.
ImageView.remove_roi_item
python
def remove_roi_item(self, roi) -> NoneImageView.select_roi
python
def select_roi(self, roi) -> NoneHighlight 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) -> NoneImageView.clear_distance
python
def clear_distance(self) -> NoneImageView.distance_count
python
def distance_count(self) -> intImageView.fit_ellipse
python
def fit_ellipse(self) -> dict | NoneFit an ellipse to the picked points and draw it. Returns geometry.
ImageView.last_geometry
python
def last_geometry(self) -> dict | NoneImageView.picked_points
python
def picked_points(self) -> np.ndarrayqoradfxm.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) -> NoneMainWindow.closeEvent
python
def closeEvent(self, event) -> NoneMainWindow.dragEnterEvent
python
def dragEnterEvent(self, e) -> NoneMainWindow.dragMoveEvent
python
def dragMoveEvent(self, e) -> NoneMainWindow.dragLeaveEvent
python
def dragLeaveEvent(self, e) -> NoneMainWindow.dropEvent
python
def dropEvent(self, e) -> NoneMainWindow.resizeEvent
python
def resizeEvent(self, e) -> NoneMainWindow.apply_theme
python
def apply_theme(self, theme: str) -> Noneqoradfxm.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) -> NoneQt view-model over a Core ResultsFrame.
MasterTableModel.results
python
@property
def results(self) -> ResultsFrameMasterTableModel.df
python
@property
def df(self)MasterTableModel.set_results
python
def set_results(self, results: ResultsFrame) -> NoneMasterTableModel.rowCount
python
def rowCount(self, parent=QtCore.QModelIndex()) -> intMasterTableModel.columnCount
python
def columnCount(self, parent=QtCore.QModelIndex()) -> intMasterTableModel.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) -> boolMasterTableModel.data_for_edit
python
def data_for_edit(self, index)MasterTableModel.add_row
python
def add_row(self, row: dict) -> intMasterTableModel.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 | NoneMasterTableModel.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) -> NoneMasterTableModel.set_status
python
def set_status(self, r: int, status: str) -> NoneMasterTableModel.clear
python
def clear(self) -> NoneMasterTableModel.to_csv
python
def to_csv(self, path: str) -> Noneqoradfxm.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) -> NoneAdd an ROI to the active canvas and register it in the session.
PipelineMixin.remove_roi
python
def remove_roi(self, roi_id: str) -> NonePipelineMixin.add_line_roi
python
def add_line_roi(self, positions=None) -> LineProfileROICreate + 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) -> ViewPrefsRead the app-scoped defaults (``tool`` is deliberately not stored).
ViewPrefs.save
python
def save(self, settings) -> NoneViewPrefs.copy
python
def copy(self, **changes) -> ViewPrefsA new instance — documents must never share one prefs object.
ViewPrefs.apply_to
python
def apply_to(self, view) -> NoneThe single path from prefs to an ImageView.
ViewPrefs.to_dict
python
def to_dict(self) -> dictqoradfxm.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) -> NoneQTableView 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) -> NoneDropTableView.dragMoveEvent
python
def dragMoveEvent(self, e) -> NoneDropTableView.dropEvent
python
def dropEvent(self, e) -> NoneResultsWindow
python
class ResultsWindow(QtWidgets.QMainWindow):
def __init__(self, table: QtWidgets.QTableView, actions, settings, parent=None) -> NoneResultsWindow.show_raise
python
def show_raise(self) -> NoneResultsWindow.closeEvent
python
def closeEvent(self, e: QtGui.QCloseEvent) -> Noneqoradfxm.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) -> NonePlot 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) -> NoneCoalesced refresh; ignored while the live checkbox is off (buttons still work).
RingProfilePanel.refresh
python
def refresh(self) -> NoneRingProfilePanel.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) -> boolAnalysisROI.set_visible
python
def set_visible(self, on: bool) -> NoneAnalysisROI.locked
python
@property
def locked(self) -> boolAnalysisROI.set_locked
python
def set_locked(self, on: bool) -> NoneLocked ROIs cannot be moved / resized and hide their handles.
AnalysisROI.update_data
python
def update_data(self) -> NoneAnalysisROI.rename
python
def rename(self, name: str) -> NoneAnalysisROI.to_dict
python
def to_dict(self) -> dictEllipseFitROI
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) -> NoneLineProfileROI
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) -> NoneLineProfileROI.length
python
def length(self) -> floatRectRegionROI
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) -> NoneRectRegionROI.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) -> dictqoradfxm.gui.theme
Qt style sheet built from the light and dark palettes.
build_style
python
def build_style(theme: str) -> strqoradfxm.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) -> NoneDropOverlay.cover
python
def cover(self) -> NoneFilterEdit
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) -> boolFilterEdit.keyPressEvent
python
def keyPressEvent(self, event) -> None