added ruff test
This commit is contained in:
@@ -9,21 +9,6 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from PySide6.QtGui import QAction, QActionGroup, QIcon, QImage
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QSplitter,
|
||||
QStatusBar,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.acquisition.image_backend import (
|
||||
DEPTH,
|
||||
@@ -39,24 +24,33 @@ from orisys_app_shared.force import select_display_forces
|
||||
from orisys_app_shared.force.calibration_recorder import CalibrationRecorder
|
||||
from orisys_app_shared.force.filters import FilterSettings
|
||||
from orisys_app_shared.force.session import SessionStore
|
||||
from orisys_app_shared.paths import recordings_dir
|
||||
from orisys_app_shared.i18n import set_locale, t
|
||||
from orisys_app_shared.logging import report_error
|
||||
from orisys_app_shared.paths import logo_path, recordings_dir, sensor_json_path
|
||||
from orisys_app_shared.prefs import save_preferences
|
||||
from orisys_app_shared.record import RecordConfig
|
||||
from orisys_app_shared.record.sample import to_record_sample
|
||||
from orisys_app_shared.theme import build_qss, clear_theme_cache, color
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from PySide6.QtGui import QAction, QActionGroup, QIcon
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QSplitter,
|
||||
QStatusBar,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_studio import session_metadata
|
||||
from orisys_studio.calibration_service import CalibrationService
|
||||
from orisys_studio.display_prefs import DisplayPrefsController
|
||||
from orisys_studio.recording import RecordingCoordinator, record_config_from_prefs
|
||||
from orisys_studio.replay import ReplayEngine
|
||||
from orisys_studio.replay_controller import ReplayController
|
||||
from orisys_studio.calibration_service import CalibrationService
|
||||
from orisys_studio.display_prefs import DisplayPrefsController
|
||||
from orisys_studio.view_layout import ViewLayoutController
|
||||
from orisys_studio import session_metadata
|
||||
from orisys_studio.session_controller import SessionController
|
||||
from orisys_studio.scene3d.prefs import settings_to_prefs
|
||||
from orisys_app_shared.i18n import set_locale, t
|
||||
from orisys_app_shared.logging import report_error
|
||||
from orisys_app_shared.paths import logo_path, sensor_json_path
|
||||
from orisys_app_shared.prefs import save_preferences
|
||||
from orisys_app_shared.theme import build_qss, clear_theme_cache, color
|
||||
|
||||
from orisys_studio.session_controller import SessionController
|
||||
from orisys_studio.ui import record_status as record_states
|
||||
from orisys_studio.ui.about_dialog import AboutDialog
|
||||
from orisys_studio.ui.arrow_panel import ArrowPanel, bgr_to_qimage
|
||||
@@ -74,6 +68,7 @@ from orisys_studio.ui.plot_settings_dialog import (
|
||||
)
|
||||
from orisys_studio.ui.sensor_picker import SensorPickerWidget
|
||||
from orisys_studio.ui.toolbar import ToolbarWidget
|
||||
from orisys_studio.view_layout import ViewLayoutController
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -2476,7 +2471,7 @@ class StudioMainWindow(QMainWindow):
|
||||
return ""
|
||||
return t("panels.depth_scale", low=f"{span[0]:.3f}", high=f"{span[1]:.3f}")
|
||||
|
||||
def _latched_domain_mask(self, frame: "ImageFrame"):
|
||||
def _latched_domain_mask(self, frame: ImageFrame):
|
||||
"""The 2D domain boundary to draw, latched for the session.
|
||||
|
||||
Exactly the treatment ``_depth_range`` gives the colour scale, and for the same
|
||||
|
||||
@@ -15,7 +15,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
CHANNEL_KEYS,
|
||||
@@ -37,14 +37,14 @@ logger = logging.getLogger(__name__)
|
||||
class CalibrationService:
|
||||
"""Owns the calibration session's files and its prepared model."""
|
||||
|
||||
def __init__(self, *, store: Optional[CalSessionStore] = None) -> None:
|
||||
def __init__(self, *, store: CalSessionStore | None = None) -> None:
|
||||
self.store = store or CalSessionStore()
|
||||
#: The one prepared model the panel displays and exports.
|
||||
self.prepared: Any = None
|
||||
#: Device identity of a reopened record, while it is open.
|
||||
self.record_identity: Optional[dict] = None
|
||||
self.record_identity: dict | None = None
|
||||
#: The record currently open, if any.
|
||||
self.open_record: Optional[CalibrationSessionRecord] = None
|
||||
self.open_record: CalibrationSessionRecord | None = None
|
||||
|
||||
# --------------------------------------------------------------- identity
|
||||
def export_meta(
|
||||
|
||||
@@ -19,17 +19,17 @@ repaint when the 2D view mode changes.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Optional, Protocol
|
||||
from typing import Any, Protocol
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
from orisys_studio.ui.plot_settings_dialog import (
|
||||
DEFAULT_PLOT_DEADBAND_N,
|
||||
DEFAULT_PLOT_DEADBAND_RAW,
|
||||
DEFAULT_PLOT_FN_MIN,
|
||||
DEFAULT_PLOT_FN_MAX,
|
||||
DEFAULT_PLOT_FN_RAW_MIN,
|
||||
DEFAULT_PLOT_FN_MIN,
|
||||
DEFAULT_PLOT_FN_RAW_MAX,
|
||||
DEFAULT_PLOT_FN_RAW_MIN,
|
||||
DEFAULT_PLOT_SHEAR_MAX,
|
||||
DEFAULT_PLOT_SHEAR_RAW_MAX,
|
||||
DEFAULT_PLOT_WINDOW_SEC,
|
||||
@@ -66,7 +66,7 @@ class DisplayHost(Protocol):
|
||||
prefs: dict
|
||||
|
||||
@property
|
||||
def _latest_fps(self) -> Optional[float]: ...
|
||||
def _latest_fps(self) -> float | None: ...
|
||||
|
||||
def _persist_prefs(self) -> None: ...
|
||||
|
||||
@@ -94,7 +94,7 @@ class DisplayPrefsController(QObject):
|
||||
arrow_rotate_types: frozenset,
|
||||
mesh_panel: Any = None,
|
||||
rotate_arrows_action: Any = None,
|
||||
parent: Optional[QObject] = None,
|
||||
parent: QObject | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._host = host
|
||||
|
||||
@@ -11,17 +11,18 @@ import sys
|
||||
# wrong one. The application is PySide6-only, so say so before anything imports pyqtgraph.
|
||||
os.environ.setdefault("PYQTGRAPH_QT_LIB", "PySide6")
|
||||
|
||||
from PySide6.QtWidgets import QApplication # noqa: E402
|
||||
|
||||
from orisys_app_shared import API_VERSION, __version__ as shared_version
|
||||
from orisys_app_shared import API_VERSION
|
||||
from orisys_app_shared import __version__ as shared_version
|
||||
from orisys_app_shared.compat import check_shared_compat
|
||||
from orisys_app_shared.i18n import set_locale
|
||||
from orisys_app_shared.logging import init_logging, install_exception_hooks
|
||||
from orisys_app_shared.paths import prefer_local_sensor_bundles
|
||||
from orisys_app_shared.prefs import load_preferences
|
||||
from orisys_app_shared.theme import build_qss
|
||||
from PySide6.QtWidgets import QApplication # noqa: E402
|
||||
|
||||
from orisys_studio import REQUIRES_SHARED_API, __version__ as app_version
|
||||
from orisys_studio import REQUIRES_SHARED_API
|
||||
from orisys_studio import __version__ as app_version
|
||||
from orisys_studio.app_window import StudioMainWindow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -16,8 +16,9 @@ here, where they can be tested without a display.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Optional
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.record import ExperimentRecorder, RecordConfig, RecordReport
|
||||
from orisys_app_shared.record.calibration_snapshot import store_calibration_snapshot
|
||||
@@ -34,7 +35,7 @@ class RecordingCoordinator:
|
||||
*,
|
||||
session_store,
|
||||
cal_recorder,
|
||||
recorder: Optional[ExperimentRecorder] = None,
|
||||
recorder: ExperimentRecorder | None = None,
|
||||
) -> None:
|
||||
self.session_store = session_store
|
||||
self.cal_recorder = cal_recorder
|
||||
@@ -47,7 +48,7 @@ class RecordingCoordinator:
|
||||
self._alarms: list[str] = []
|
||||
#: A stop was requested and the writer has not closed its files yet.
|
||||
self.report_pending = False
|
||||
self.last_report: Optional[RecordReport] = None
|
||||
self.last_report: RecordReport | None = None
|
||||
self._active_calibration_id = ""
|
||||
self._record_fps = 30.0
|
||||
self.recorder.set_alarm_handler(self._on_alarm)
|
||||
@@ -210,7 +211,7 @@ class RecordingCoordinator:
|
||||
self.report_pending = False
|
||||
return Path(directory)
|
||||
|
||||
def stop(self, *, wait: bool = True) -> Optional[RecordReport]:
|
||||
def stop(self, *, wait: bool = True) -> RecordReport | None:
|
||||
"""Stop and return the report, or ``None`` when nothing was started."""
|
||||
if self.recorder.directory is None and self.recorder.report is None:
|
||||
self.report_pending = False
|
||||
@@ -221,7 +222,7 @@ class RecordingCoordinator:
|
||||
self.publish(report)
|
||||
return report
|
||||
|
||||
def publish(self, report: Optional[RecordReport]) -> None:
|
||||
def publish(self, report: RecordReport | None) -> None:
|
||||
"""Record a report; a completed one becomes ``last_report``."""
|
||||
if report is None:
|
||||
return
|
||||
@@ -232,7 +233,7 @@ class RecordingCoordinator:
|
||||
self.report_pending = False
|
||||
self.last_report = report
|
||||
|
||||
def poll(self) -> Optional[RecordReport]:
|
||||
def poll(self) -> RecordReport | None:
|
||||
"""The terminal report, once the writer has actually closed the files."""
|
||||
if not self.report_pending:
|
||||
return None
|
||||
@@ -256,7 +257,7 @@ def record_config_from_prefs(
|
||||
*,
|
||||
mode: str,
|
||||
fps: float,
|
||||
on_problem: Optional[Callable[[str], None]] = None,
|
||||
on_problem: Callable[[str], None] | None = None,
|
||||
) -> RecordConfig:
|
||||
"""Build the record config the toolbar/prefs describe."""
|
||||
return RecordConfig(
|
||||
|
||||
@@ -21,13 +21,11 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.record import RecordingReader, VideoOnlySource
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -64,7 +62,7 @@ class ReplayEngine(QObject):
|
||||
self._position = 0.0
|
||||
#: The frame cache: one video image per cursor position.
|
||||
self._video_position: float | None = None
|
||||
self._video_bgr: Optional[np.ndarray] = None
|
||||
self._video_bgr: np.ndarray | None = None
|
||||
self._video_reason = ""
|
||||
self._warnings = ""
|
||||
|
||||
@@ -209,7 +207,7 @@ class ReplayEngine(QObject):
|
||||
return self._unit
|
||||
|
||||
# ----------------------------------------------------------------- frames
|
||||
def video_frame_bgr(self) -> Optional[np.ndarray]:
|
||||
def video_frame_bgr(self) -> np.ndarray | None:
|
||||
"""The recorded frame under the cursor, as BGR.
|
||||
|
||||
The engine hands over pixels, not a prepared widget image: the single 2D
|
||||
|
||||
@@ -25,15 +25,14 @@ import logging
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Protocol
|
||||
from typing import Any, Protocol
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtCore import QObject, QTimer
|
||||
from PySide6.QtWidgets import QFileDialog
|
||||
|
||||
from orisys_app_shared.acquisition.image_backend import SOURCE_REPLAY, ImageFrame
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.paths import recordings_dir
|
||||
from PySide6.QtCore import QObject, QTimer
|
||||
from PySide6.QtWidgets import QFileDialog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -111,7 +110,7 @@ class ReplayController(QObject):
|
||||
charts: Any,
|
||||
image_panel: Any,
|
||||
mesh_panel: Any,
|
||||
parent: Optional[QObject] = None,
|
||||
parent: QObject | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._engine = engine
|
||||
@@ -120,7 +119,7 @@ class ReplayController(QObject):
|
||||
self._charts = charts
|
||||
self._image_panel = image_panel
|
||||
self._mesh_panel = mesh_panel
|
||||
self._actions: Optional[ReplayActions] = None
|
||||
self._actions: ReplayActions | None = None
|
||||
#: True while a recompute pass is running: it is a *run* over a recording, so
|
||||
#: the acquire pipeline owns the panels and the transport stops the pass.
|
||||
self._recompute_running = False
|
||||
@@ -501,7 +500,7 @@ class ReplayController(QObject):
|
||||
self._host._notify(t("toolbar.nothing_to_stop"))
|
||||
|
||||
# ---------------------------------------------------------------- recompute
|
||||
def video_segment(self) -> Optional[Path]:
|
||||
def video_segment(self) -> Path | None:
|
||||
"""The recorded video a recompute pass should read."""
|
||||
reader = self._engine.reader()
|
||||
segments = sorted(reader.segments()) if reader is not None else []
|
||||
|
||||
@@ -33,10 +33,9 @@ Two things the v1 adapters deliberately do **not** do:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.acquisition.models import SdkPacket
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
Diagnostic,
|
||||
@@ -45,7 +44,6 @@ from orisys_app_shared.scene3d.contracts import (
|
||||
FrameIdentity,
|
||||
SurfaceFieldFrame,
|
||||
diagnostic,
|
||||
error,
|
||||
freeze,
|
||||
)
|
||||
from orisys_app_shared.scene3d.sampling import remap_to_domain
|
||||
@@ -84,7 +82,7 @@ def _refusal_rank(item: Diagnostic) -> int:
|
||||
return _REFUSAL_RANK.get(str(item.code), 0)
|
||||
|
||||
|
||||
def flow_magnitude(packet: SdkPacket) -> Optional[np.ndarray]:
|
||||
def flow_magnitude(packet: SdkPacket) -> np.ndarray | None:
|
||||
"""``|flow|`` as float32, or ``None`` when the packet carries no flow."""
|
||||
flow = getattr(packet, "flow", None)
|
||||
if flow is None:
|
||||
@@ -97,11 +95,11 @@ def flow_magnitude(packet: SdkPacket) -> Optional[np.ndarray]:
|
||||
return None
|
||||
|
||||
|
||||
def _shape_of(array: Optional[np.ndarray]) -> str:
|
||||
def _shape_of(array: np.ndarray | None) -> str:
|
||||
return "none" if array is None else "x".join(str(int(v)) for v in array.shape[:2])
|
||||
|
||||
|
||||
def _field_validity(packet: SdkPacket, field: str, frame_id: int) -> Optional[Diagnostic]:
|
||||
def _field_validity(packet: SdkPacket, field: str, frame_id: int) -> Diagnostic | None:
|
||||
"""Whether the packet may claim ``field`` for *this* frame.
|
||||
|
||||
Three answers, and the middle one is the point: ``None`` on the packet means the
|
||||
@@ -145,7 +143,7 @@ def _device_mask(
|
||||
packet: SdkPacket,
|
||||
shape_hw: tuple[int, int],
|
||||
latch: Any = None,
|
||||
) -> tuple[Optional[np.ndarray], Optional[Diagnostic]]:
|
||||
) -> tuple[np.ndarray | None, Diagnostic | None]:
|
||||
"""The device's own validity, or a diagnostic when it describes another grid.
|
||||
|
||||
Both silent choices are wrong: stretching a mask from another grid marks samples
|
||||
@@ -177,7 +175,7 @@ def _device_mask(
|
||||
return array, None
|
||||
|
||||
|
||||
def _intersect(mask: np.ndarray, device: Optional[np.ndarray]) -> np.ndarray:
|
||||
def _intersect(mask: np.ndarray, device: np.ndarray | None) -> np.ndarray:
|
||||
"""Inside the UV island AND the device's own valid area, when it stated one."""
|
||||
if device is None:
|
||||
return freeze(mask, dtype=np.bool_)
|
||||
@@ -218,8 +216,8 @@ class FingerFrameAdapter:
|
||||
geometry_id: str,
|
||||
field_domain_id: str,
|
||||
shape_hw: tuple[int, int],
|
||||
map_x: Optional[np.ndarray] = None,
|
||||
map_y: Optional[np.ndarray] = None,
|
||||
map_x: np.ndarray | None = None,
|
||||
map_y: np.ndarray | None = None,
|
||||
preferred_field: str = "depth",
|
||||
) -> None:
|
||||
self.geometry_id = str(geometry_id)
|
||||
@@ -227,7 +225,7 @@ class FingerFrameAdapter:
|
||||
self.shape_hw = (int(shape_hw[0]), int(shape_hw[1]))
|
||||
self.map_x = map_x
|
||||
self.map_y = map_y
|
||||
self.last_diagnostic: Optional[Diagnostic] = None
|
||||
self.last_diagnostic: Diagnostic | None = None
|
||||
#: Which quantity the operator asked for (``"depth"`` or ``"flow"``). The other
|
||||
#: one is tried when this one cannot be coloured, and the substitution is stated
|
||||
#: rather than silent — a frame that shows another quantity than the setting says
|
||||
@@ -236,7 +234,7 @@ class FingerFrameAdapter:
|
||||
self.preferred_field = _field_name(preferred_field)
|
||||
#: Set (and cleared) by every snapshot: the reason the picture is not the field
|
||||
#: the operator chose. ``None`` when the preferred field was the one drawn.
|
||||
self.last_substitution: Optional[Diagnostic] = None
|
||||
self.last_substitution: Diagnostic | None = None
|
||||
#: The session's island latch (``image_backend.DomainMaskLatch``), when the
|
||||
#: window has one. See ``_device_mask``: the surface's validity is the *latched*
|
||||
#: island — the same mask the 2D canvas paints its island and its "no depth"
|
||||
@@ -247,7 +245,7 @@ class FingerFrameAdapter:
|
||||
self.preferred_field = _field_name(name)
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, domain: Any, *, geometry_id: str) -> "FingerFrameAdapter":
|
||||
def from_domain(cls, domain: Any, *, geometry_id: str) -> FingerFrameAdapter:
|
||||
"""Build from a ``LegacyFieldDomain`` (which carries the map, when loaded)."""
|
||||
return cls(
|
||||
geometry_id=geometry_id,
|
||||
@@ -259,7 +257,7 @@ class FingerFrameAdapter:
|
||||
|
||||
def snapshot(
|
||||
self, packet: SdkPacket, runtime: Any, identity: FrameIdentity
|
||||
) -> Optional[SurfaceFieldFrame]:
|
||||
) -> SurfaceFieldFrame | None:
|
||||
"""Colour the surface with the field the operator chose, or say why not.
|
||||
|
||||
Two quantities can be drawn and the operator picks one (``DisplaySettings.field``);
|
||||
@@ -306,7 +304,7 @@ class FingerFrameAdapter:
|
||||
identity: FrameIdentity,
|
||||
name: str,
|
||||
refused: list[Diagnostic],
|
||||
) -> Optional[SurfaceFieldFrame]:
|
||||
) -> SurfaceFieldFrame | None:
|
||||
"""One quantity, or ``None`` with the reason appended to ``refused``."""
|
||||
if name == "depth":
|
||||
depth = getattr(packet, "depth", None)
|
||||
@@ -401,7 +399,7 @@ class FingerFrameAdapter:
|
||||
mask: np.ndarray,
|
||||
descriptor: FieldDescriptor,
|
||||
refused: list[Diagnostic],
|
||||
) -> Optional[SurfaceFieldFrame]:
|
||||
) -> SurfaceFieldFrame | None:
|
||||
"""Intersect with the device's own mask and build the frame."""
|
||||
# Inside the UV island is the only place a value means anything: the tracker is
|
||||
# looking at masked black outside it.
|
||||
@@ -469,13 +467,13 @@ class BoxFrameAdapter:
|
||||
self.geometry_id = str(geometry_id)
|
||||
self.field_domain_id = str(field_domain_id)
|
||||
self.shape_hw = (int(shape_hw[0]), int(shape_hw[1]))
|
||||
self.last_diagnostic: Optional[Diagnostic] = None
|
||||
self.last_diagnostic: Diagnostic | None = None
|
||||
#: See ``FingerFrameAdapter.domain_latch``: one island, both views.
|
||||
self.domain_latch: Any = None
|
||||
|
||||
def snapshot(
|
||||
self, packet: SdkPacket, identity: FrameIdentity, domain: ExpectedGeometry
|
||||
) -> Optional[SurfaceFieldFrame]:
|
||||
) -> SurfaceFieldFrame | None:
|
||||
if tuple(domain.shape_hw) != self.shape_hw:
|
||||
self.last_diagnostic = diagnostic(
|
||||
"field_domain_mismatch",
|
||||
@@ -492,7 +490,7 @@ class BoxFrameAdapter:
|
||||
if mask_problem is not None:
|
||||
self.last_diagnostic = mask_problem
|
||||
return None
|
||||
refused: Optional[Diagnostic] = None
|
||||
refused: Diagnostic | None = None
|
||||
for name, field in candidates:
|
||||
if field is None:
|
||||
continue
|
||||
|
||||
@@ -28,12 +28,11 @@ exactly the kind of thing this feature is not allowed to cause.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from orisys_app_shared.paths import config_dir
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
@@ -43,6 +42,8 @@ from orisys_app_shared.scene3d.contracts import (
|
||||
diagnostic,
|
||||
error,
|
||||
)
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from orisys_studio.scene3d.keys import SceneKeys, check_release_requirement
|
||||
|
||||
if TYPE_CHECKING: # the description is a value object; the callable returns one
|
||||
@@ -76,7 +77,7 @@ class SceneSource:
|
||||
return f"{self.kind}: {self.path}"
|
||||
|
||||
|
||||
def scene_dir(sensor_type: str, *, root: Optional[Path] = None) -> Path:
|
||||
def scene_dir(sensor_type: str, *, root: Path | None = None) -> Path:
|
||||
"""Where a compiled package for this sensor belongs."""
|
||||
base = Path(root) if root is not None else config_dir()
|
||||
return base / "sensors" / str(sensor_type) / "scene"
|
||||
@@ -86,8 +87,8 @@ def find_package(
|
||||
sensor_type: str,
|
||||
*,
|
||||
override: str | Path | None = None,
|
||||
root: Optional[Path] = None,
|
||||
) -> Optional[SceneSource]:
|
||||
root: Path | None = None,
|
||||
) -> SceneSource | None:
|
||||
"""The package to load, or ``None``. Never raises: absence is a state."""
|
||||
if override:
|
||||
path = Path(str(override)).expanduser()
|
||||
@@ -126,8 +127,8 @@ def no_asset_diagnostic(sensor_type: str, *, override: str | Path | None = None)
|
||||
|
||||
def load_package(
|
||||
source: SceneSource,
|
||||
expected: Optional[ExpectedGeometry] = None,
|
||||
keys: "Optional[SceneKeys]" = None,
|
||||
expected: ExpectedGeometry | None = None,
|
||||
keys: SceneKeys | None = None,
|
||||
) -> SceneAssets:
|
||||
"""Read and validate a package. Runs on a worker thread; raises ``ThreeDError``.
|
||||
|
||||
@@ -153,10 +154,10 @@ def load_package(
|
||||
|
||||
def _resolve_and_load(
|
||||
source: SceneSource,
|
||||
expected: Optional[ExpectedGeometry],
|
||||
provider: "Optional[Callable[[], DeviceGeometry]]",
|
||||
keys: "Optional[SceneKeys]" = None,
|
||||
) -> tuple[SceneAssets, "Optional[DeviceGeometry]"]:
|
||||
expected: ExpectedGeometry | None,
|
||||
provider: Callable[[], DeviceGeometry] | None,
|
||||
keys: SceneKeys | None = None,
|
||||
) -> tuple[SceneAssets, DeviceGeometry | None]:
|
||||
"""Worker-thread half: build the *device's* description, then read the package.
|
||||
|
||||
Deriving the description digests the unroll map the runtime loads, so it belongs on
|
||||
@@ -183,21 +184,21 @@ class SceneAssetLoader(QObject):
|
||||
loaded = Signal(object, str, int, object) # SceneAssets, description, id, device
|
||||
failed = Signal(object, int) # Diagnostic, request id
|
||||
|
||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
def __init__(self, parent: QObject | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._executor: Optional[ThreadPoolExecutor] = None
|
||||
self._executor: ThreadPoolExecutor | None = None
|
||||
self._inflight = False
|
||||
self._request_id = 0
|
||||
#: The newest request that could not start yet (source, expected, provider, id).
|
||||
self._pending: Optional[tuple[SceneSource, Optional[ExpectedGeometry], Any, int]] = None
|
||||
self._pending: tuple[SceneSource, ExpectedGeometry | None, Any, int] | None = None
|
||||
|
||||
def load(
|
||||
self,
|
||||
source: SceneSource,
|
||||
expected: Optional[ExpectedGeometry] = None,
|
||||
expected: ExpectedGeometry | None = None,
|
||||
*,
|
||||
expected_provider: "Optional[Callable[[], DeviceGeometry]]" = None,
|
||||
keys: "Optional[SceneKeys]" = None,
|
||||
expected_provider: Callable[[], DeviceGeometry] | None = None,
|
||||
keys: SceneKeys | None = None,
|
||||
) -> int:
|
||||
"""Ask for a scene; returns the request id this call was given."""
|
||||
self._request_id += 1
|
||||
|
||||
@@ -15,14 +15,12 @@ from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
F32,
|
||||
CameraMatrices,
|
||||
CameraPreset,
|
||||
F32,
|
||||
freeze,
|
||||
)
|
||||
|
||||
@@ -54,7 +52,7 @@ class OrbitCamera:
|
||||
vertical_fov_rad: float = 0.6
|
||||
aspect: float = 1.0
|
||||
_fitted_radius_m: float = 0.05
|
||||
_preset_name: Optional[str] = None
|
||||
_preset_name: str | None = None
|
||||
|
||||
# ------------------------------------------------------------------- setup
|
||||
def fit(self, bounds_sensor_m: F32, aspect: float) -> None:
|
||||
|
||||
@@ -36,9 +36,10 @@ from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import CancelledError, Executor, Future
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
Diagnostic,
|
||||
@@ -106,27 +107,27 @@ class ThreeDCoordinator:
|
||||
#: withdrawn: a stop, a hide, a new session, a new scene. A compose result
|
||||
#: whose epoch is not this one is thrown away instead of presented.
|
||||
self._epoch = 0
|
||||
self._assets: Optional[SceneAssets] = None
|
||||
self._assets: SceneAssets | None = None
|
||||
self._source_id = ""
|
||||
self._session_id: Optional[int] = None
|
||||
self._session_id: int | None = None
|
||||
#: The newest request id seen in this epoch: a producer that falls behind
|
||||
#: (a seek UI, a queue that reorders) must not put an older frame on screen.
|
||||
self._last_request_id = 0
|
||||
self._visible = False
|
||||
self._settings = DisplaySettings(max_fps=self._max_fps)
|
||||
self._inflight: Optional[_Inflight] = None
|
||||
self._inflight: _Inflight | None = None
|
||||
self._stale_frames = 0
|
||||
self._invalid_frames = 0
|
||||
self._last_diagnostic: Optional[Diagnostic] = None
|
||||
self._last_diagnostic: Diagnostic | None = None
|
||||
self._closed = False
|
||||
self._settings_dirty = False
|
||||
self._last_composed_identity: Optional[FrameIdentity] = None
|
||||
self._last_composed_identity: FrameIdentity | None = None
|
||||
#: What is on screen and when it got there, for the pane's frame counter.
|
||||
self._last_presented: Optional[tuple[FrameIdentity, float]] = None
|
||||
self._last_presented: tuple[FrameIdentity, float] | None = None
|
||||
#: When the current measurement stopped being current, or ``None`` while the
|
||||
#: view is live. Set by a field that could not be built, a compose that
|
||||
#: failed, or a frame whose array belongs to an earlier acquisition frame.
|
||||
self._stale_since: Optional[float] = None
|
||||
self._stale_since: float | None = None
|
||||
#: Whether the reference model is already on screen for the current claim.
|
||||
#: Without this the pane re-uploaded the same rest frame on every 16 ms tick
|
||||
#: while it waited for the first measurement.
|
||||
@@ -138,7 +139,7 @@ class ThreeDCoordinator:
|
||||
self._last_result_latency_ms = 0.0
|
||||
#: The field frame currently on screen, so a gain change can redraw exactly
|
||||
#: it rather than a newer instant.
|
||||
self._last_field_frame: Optional[SurfaceFieldFrame] = None
|
||||
self._last_field_frame: SurfaceFieldFrame | None = None
|
||||
|
||||
# ------------------------------------------------------------------- state
|
||||
@property
|
||||
@@ -155,7 +156,7 @@ class ThreeDCoordinator:
|
||||
"""The display claim results are accepted under (see :class:`_Inflight`)."""
|
||||
return self._epoch
|
||||
|
||||
def last_presented(self) -> Optional[tuple[FrameIdentity, float]]:
|
||||
def last_presented(self) -> tuple[FrameIdentity, float] | None:
|
||||
"""What is on screen, and the monotonic time it was handed to the widget."""
|
||||
return self._last_presented
|
||||
|
||||
@@ -190,7 +191,7 @@ class ThreeDCoordinator:
|
||||
return self._invalid_frames
|
||||
|
||||
@property
|
||||
def last_diagnostic(self) -> Optional[Diagnostic]:
|
||||
def last_diagnostic(self) -> Diagnostic | None:
|
||||
return self._last_diagnostic
|
||||
|
||||
@property
|
||||
@@ -355,7 +356,7 @@ class ThreeDCoordinator:
|
||||
self._last_request_id = max(self._last_request_id, identity.request_id)
|
||||
return self._incoming.offer(frame)
|
||||
|
||||
def note_field_failure(self, item: Optional[Diagnostic] = None) -> None:
|
||||
def note_field_failure(self, item: Diagnostic | None = None) -> None:
|
||||
"""A producer could not build a field frame from this packet.
|
||||
|
||||
Called from the acquisition thread by the sink that owns the adapter: when the
|
||||
@@ -373,7 +374,7 @@ class ThreeDCoordinator:
|
||||
@staticmethod
|
||||
def _reject_reason(
|
||||
frame: SurfaceFieldFrame, assets: SceneAssets
|
||||
) -> Optional[Diagnostic]:
|
||||
) -> Diagnostic | None:
|
||||
"""The cheap checks a producer thread may do: identity and shape only."""
|
||||
if frame.geometry_id != assets.geometry_id:
|
||||
return diagnostic(
|
||||
|
||||
@@ -24,14 +24,15 @@ zero: the reference model is not a measurement and must not look like one.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
CameraMatrices,
|
||||
Diagnostic,
|
||||
DisplaySettings,
|
||||
GLCapabilities,
|
||||
MeshPart,
|
||||
@@ -43,7 +44,8 @@ from orisys_app_shared.scene3d.contracts import (
|
||||
error,
|
||||
freeze,
|
||||
)
|
||||
from orisys_app_shared.scene3d.contracts import Diagnostic
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: Renderer names that are not a GPU. A board must not be signed off on one of
|
||||
#: these, and neither must a laptop with a broken driver.
|
||||
@@ -261,7 +263,7 @@ def shader_source(profile: Profile, program: str) -> tuple[str, str]:
|
||||
return header + vertex, header + body
|
||||
|
||||
|
||||
def _valid_scalar_range(scalar, valid) -> Optional[tuple[float, float]]:
|
||||
def _valid_scalar_range(scalar, valid) -> tuple[float, float] | None:
|
||||
"""Robust P5/P95 range of *valid* samples, or ``None`` when there are none.
|
||||
|
||||
Using min/max made empty-field noise paint the whole LUT. A percentile span
|
||||
@@ -294,11 +296,11 @@ def _valid_scalar_range(scalar, valid) -> Optional[tuple[float, float]]:
|
||||
|
||||
|
||||
def _blend_scale_range(
|
||||
previous: Optional[tuple[float, float]],
|
||||
current: Optional[tuple[float, float]],
|
||||
previous: tuple[float, float] | None,
|
||||
current: tuple[float, float] | None,
|
||||
*,
|
||||
alpha: float = 0.15,
|
||||
) -> Optional[tuple[float, float]]:
|
||||
) -> tuple[float, float] | None:
|
||||
"""Slowly update an automatic colour range to avoid frame-to-frame flicker."""
|
||||
if current is None:
|
||||
return previous
|
||||
@@ -310,7 +312,7 @@ def _blend_scale_range(
|
||||
(1.0 - a) * float(previous[1]) + a * float(current[1]),
|
||||
)
|
||||
|
||||
def _scale_bounds(scale, automatic: Optional[tuple[float, float]]) -> tuple[float, float]:
|
||||
def _scale_bounds(scale, automatic: tuple[float, float] | None) -> tuple[float, float]:
|
||||
"""The colour range to draw with: the operator's, or the data's own.
|
||||
|
||||
``automatic`` mode exists because the quantities differ in magnitude by orders
|
||||
@@ -327,7 +329,7 @@ def _scale_bounds(scale, automatic: Optional[tuple[float, float]]) -> tuple[floa
|
||||
else:
|
||||
fallback = (float(scale.lower), float(scale.upper))
|
||||
lower, upper = float(fallback[0]), float(fallback[1])
|
||||
if not (lower == lower) or not (upper == upper): # NaN
|
||||
if math.isnan(lower) or math.isnan(upper):
|
||||
return 0.0, 1.0
|
||||
if upper <= lower:
|
||||
# A constant field still has to be visible rather than all one clipped colour.
|
||||
@@ -404,7 +406,7 @@ def _extend_edges(values: np.ndarray, usable: np.ndarray) -> np.ndarray:
|
||||
return np.where(usable, values, filled)
|
||||
|
||||
|
||||
def pack_part(part: MeshPart, *, uvs: Optional[np.ndarray] = None) -> np.ndarray:
|
||||
def pack_part(part: MeshPart, *, uvs: np.ndarray | None = None) -> np.ndarray:
|
||||
"""Interleave one part into the layout the shaders read.
|
||||
|
||||
``uvs`` are the render-vertex UVs on the domain's pixel grid
|
||||
@@ -431,7 +433,7 @@ def pack_part(part: MeshPart, *, uvs: Optional[np.ndarray] = None) -> np.ndarray
|
||||
|
||||
|
||||
def pack_scene_part(
|
||||
part: MeshPart, *, uvs: Optional[np.ndarray] = None
|
||||
part: MeshPart, *, uvs: np.ndarray | None = None
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""``(vertices, indices)`` as the GPU wants them, both read-only."""
|
||||
indices = np.asarray(part.triangles, dtype=np.uint32).reshape(-1)
|
||||
@@ -598,7 +600,14 @@ class _PartBuffers:
|
||||
"""One part's GPU state. Kept dumb on purpose: no Qt, no ownership games."""
|
||||
|
||||
__slots__ = (
|
||||
"part_id", "role", "vao", "vbo", "ebo", "triangles", "vertices", "rows"
|
||||
"ebo",
|
||||
"part_id",
|
||||
"role",
|
||||
"rows",
|
||||
"triangles",
|
||||
"vao",
|
||||
"vbo",
|
||||
"vertices"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -620,7 +629,7 @@ class GLRenderer:
|
||||
"""Shaders, buffers and draw calls for one scene."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._capabilities: Optional[GLCapabilities] = None
|
||||
self._capabilities: GLCapabilities | None = None
|
||||
self._programs: dict[str, int] = {}
|
||||
self._uniforms: dict[str, dict[str, int]] = {}
|
||||
self._parts: dict[str, _PartBuffers] = {}
|
||||
@@ -632,13 +641,13 @@ class GLRenderer:
|
||||
#: allocation and no per-frame vertex traffic.
|
||||
self._field_texture: int = 0
|
||||
self._valid_texture: int = 0
|
||||
self._field_shape: Optional[tuple[int, int]] = None
|
||||
self._field_shape: tuple[int, int] | None = None
|
||||
self._field_internal_format: int = 0
|
||||
#: Range of the valid samples of the field currently uploaded, for the
|
||||
#: automatic colour scale. ``None`` until a frame carries valid data.
|
||||
self._scalar_range: Optional[tuple[float, float]] = None
|
||||
self._scalar_range: tuple[float, float] | None = None
|
||||
#: The colour bounds of the last draw and how they were chosen.
|
||||
self._last_scale: Optional[tuple[float, float]] = None
|
||||
self._last_scale: tuple[float, float] | None = None
|
||||
self._last_scale_mode = ""
|
||||
self._sensing_part_id: str = ""
|
||||
self._estimated_gpu_bytes: int = 0
|
||||
@@ -926,8 +935,12 @@ class GLRenderer:
|
||||
GL.glDeleteVertexArrays(1, [int(vao)])
|
||||
GL.glDeleteBuffers(1, [int(vbo)])
|
||||
GL.glDeleteBuffers(1, [int(ebo)])
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
except Exception as exc: # noqa: BLE001 - best-effort delete, reported below
|
||||
# A context that has already been lost refuses the delete, and this also
|
||||
# runs on the live overlay-replacement path: raising here would replace a
|
||||
# frame with a crash. Reported rather than swallowed, because a delete
|
||||
# that keeps failing leaks GPU memory for the rest of the session.
|
||||
logger.warning("could not free the %s overlay's GL objects: %s", kind, exc)
|
||||
|
||||
def draw(
|
||||
self,
|
||||
@@ -1021,7 +1034,7 @@ class GLRenderer:
|
||||
scale_mode=self._last_scale_mode,
|
||||
)
|
||||
|
||||
def effective_scale(self) -> Optional[tuple[float, float]]:
|
||||
def effective_scale(self) -> tuple[float, float] | None:
|
||||
"""The colour bounds the last draw actually used (``None`` before any draw)."""
|
||||
return self._last_scale
|
||||
|
||||
@@ -1059,7 +1072,7 @@ class GLRenderer:
|
||||
GL.glActiveTexture(GL.GL_TEXTURE0)
|
||||
|
||||
@property
|
||||
def field_shape(self) -> Optional[tuple[int, int]]:
|
||||
def field_shape(self) -> tuple[int, int] | None:
|
||||
"""The allocated field texture's ``(H, W)``, or ``None`` before the first frame."""
|
||||
return self._field_shape
|
||||
|
||||
@@ -1150,7 +1163,7 @@ class GLRenderer:
|
||||
GL.glBindTexture(GL.GL_TEXTURE_2D, 0)
|
||||
self._lut_id = lut_id
|
||||
|
||||
def capabilities(self) -> Optional[GLCapabilities]:
|
||||
def capabilities(self) -> GLCapabilities | None:
|
||||
return self._capabilities
|
||||
|
||||
@property
|
||||
@@ -1246,7 +1259,7 @@ def _decode(value: Any) -> str:
|
||||
return str(value).strip()
|
||||
|
||||
|
||||
def _set_mat4(GL: Any, location: Optional[int], values: np.ndarray) -> None:
|
||||
def _set_mat4(GL: Any, location: int | None, values: np.ndarray) -> None:
|
||||
if location is None:
|
||||
return
|
||||
# Column-major upload, explicitly: the layout of the NumPy array must not
|
||||
@@ -1254,17 +1267,17 @@ def _set_mat4(GL: Any, location: Optional[int], values: np.ndarray) -> None:
|
||||
GL.glUniformMatrix4fv(location, 1, GL.GL_FALSE, np.ascontiguousarray(values.T))
|
||||
|
||||
|
||||
def _set_float(GL: Any, location: Optional[int], value: float) -> None:
|
||||
def _set_float(GL: Any, location: int | None, value: float) -> None:
|
||||
if location is not None:
|
||||
GL.glUniform1f(location, float(value))
|
||||
|
||||
|
||||
def _set_vec3(GL: Any, location: Optional[int], values: tuple[float, float, float]) -> None:
|
||||
def _set_vec3(GL: Any, location: int | None, values: tuple[float, float, float]) -> None:
|
||||
if location is not None:
|
||||
GL.glUniform3f(location, float(values[0]), float(values[1]), float(values[2]))
|
||||
|
||||
|
||||
def _set_vec4(GL: Any, location: Optional[int], values: tuple[float, float, float, float]) -> None:
|
||||
def _set_vec4(GL: Any, location: int | None, values: tuple[float, float, float, float]) -> None:
|
||||
if location is not None:
|
||||
GL.glUniform4f(
|
||||
location, float(values[0]), float(values[1]), float(values[2]), float(values[3])
|
||||
|
||||
@@ -16,9 +16,10 @@ paths and booleans so a recording can say how a package was authenticated.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping, Optional
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.scene3d.contracts import Diagnostic, diagnostic
|
||||
|
||||
@@ -36,7 +37,7 @@ PREF_REQUIRE_SIGNATURE = "scene3d_require_signature"
|
||||
class SceneKeys:
|
||||
"""What the loader needs to authenticate a package — and how strict to be."""
|
||||
|
||||
verify_key_pem: Optional[bytes] = None
|
||||
verify_key_pem: bytes | None = None
|
||||
provider: Any = None
|
||||
require_signature: bool = False
|
||||
#: Paths/ids only, for logs and for the recording: never material.
|
||||
@@ -59,7 +60,7 @@ class SceneKeys:
|
||||
}
|
||||
|
||||
|
||||
def resolve_scene_keys(prefs: Optional[Mapping[str, Any]]) -> SceneKeys:
|
||||
def resolve_scene_keys(prefs: Mapping[str, Any] | None) -> SceneKeys:
|
||||
"""Build the key material the preferences describe. Never raises for absence.
|
||||
|
||||
A missing or unreadable file is a *diagnostic-bearing state*, not a crash: the 2D
|
||||
@@ -68,7 +69,7 @@ def resolve_scene_keys(prefs: Optional[Mapping[str, Any]]) -> SceneKeys:
|
||||
"""
|
||||
prefs = dict(prefs or {})
|
||||
sources: dict[str, str] = {}
|
||||
verify_pem: Optional[bytes] = None
|
||||
verify_pem: bytes | None = None
|
||||
provider = None
|
||||
|
||||
verify_path = str(prefs.get(PREF_VERIFY_KEY) or "").strip()
|
||||
@@ -113,7 +114,7 @@ def resolve_scene_keys(prefs: Optional[Mapping[str, Any]]) -> SceneKeys:
|
||||
)
|
||||
|
||||
|
||||
def check_release_requirement(path: Path, keys: Optional[SceneKeys]) -> Optional[Diagnostic]:
|
||||
def check_release_requirement(path: Path, keys: SceneKeys | None) -> Diagnostic | None:
|
||||
"""Refuse an unsigned package when the application is in release mode.
|
||||
|
||||
Development builds accept a plain package (the shipped engineering one is exactly
|
||||
|
||||
@@ -18,10 +18,20 @@ Two rules it exists to enforce in the interface:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
ColorScale,
|
||||
Diagnostic,
|
||||
DisplaySettings,
|
||||
FieldDescriptor,
|
||||
RenderStats,
|
||||
SceneAssets,
|
||||
SceneCapabilities,
|
||||
SceneState,
|
||||
)
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QImage
|
||||
from PySide6.QtWidgets import (
|
||||
@@ -38,18 +48,7 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
ColorScale,
|
||||
Diagnostic,
|
||||
DisplaySettings,
|
||||
FieldDescriptor,
|
||||
RenderStats,
|
||||
SceneAssets,
|
||||
SceneCapabilities,
|
||||
SceneState,
|
||||
)
|
||||
from orisys_studio.scene3d.prefs import FPS_CHOICES, DEFAULT_FIELD
|
||||
from orisys_studio.scene3d.prefs import DEFAULT_FIELD, FPS_CHOICES
|
||||
from orisys_studio.ui.fit_text import ShrinkableLabel
|
||||
|
||||
if TYPE_CHECKING: # the panel talks to these two by interface, not by import
|
||||
@@ -82,7 +81,7 @@ class ColorScaleBar(QLabel):
|
||||
because in automatic mode the spin boxes beside it held values nobody was using.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setObjectName("colorScaleBar")
|
||||
self.setFixedHeight(12)
|
||||
@@ -136,23 +135,23 @@ class ThreeDPanel(QFrame):
|
||||
self,
|
||||
coordinator: ThreeDCoordinator,
|
||||
widget: Sensor3DWidget,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setObjectName("contentPanel")
|
||||
self._coordinator = coordinator
|
||||
self._widget = widget
|
||||
self._assets: Optional[SceneAssets] = None
|
||||
self._descriptor: Optional[FieldDescriptor] = None
|
||||
self._assets: SceneAssets | None = None
|
||||
self._descriptor: FieldDescriptor | None = None
|
||||
self._state: SceneState = "disabled"
|
||||
self._source_kind = "live"
|
||||
self._syncing = False
|
||||
#: What the data source can provide (see ``contracts.SceneCapabilities``): an
|
||||
#: unavailable capability disables its control and says why, rather than
|
||||
#: letting the operator operate an input that does not exist.
|
||||
self._capabilities: Optional[SceneCapabilities] = None
|
||||
self._capabilities: SceneCapabilities | None = None
|
||||
#: The bounds the last draw actually used (the widget's RenderStats).
|
||||
self._effective_scale: Optional[RenderStats] = None
|
||||
self._effective_scale: RenderStats | None = None
|
||||
#: Whether the pane explains the pipeline around the picture (the window's
|
||||
#: 「诊断信息」 switch). Off by default: the picture and what it *is* stay.
|
||||
self._show_diagnostics = False
|
||||
@@ -355,7 +354,7 @@ class ThreeDPanel(QFrame):
|
||||
self.set_state("disabled")
|
||||
|
||||
# ------------------------------------------------------------------ inputs
|
||||
def set_scene(self, assets: Optional[SceneAssets]) -> None:
|
||||
def set_scene(self, assets: SceneAssets | None) -> None:
|
||||
"""Fill the preset list from the asset: its default view is the asset's."""
|
||||
self._assets = assets
|
||||
self._syncing = True
|
||||
@@ -377,7 +376,7 @@ class ThreeDPanel(QFrame):
|
||||
self.preset_combo.setEnabled(bool(assets is not None and self.preset_combo.count()))
|
||||
self.reset_button.setEnabled(assets is not None)
|
||||
|
||||
def set_descriptor(self, descriptor: Optional[FieldDescriptor]) -> None:
|
||||
def set_descriptor(self, descriptor: FieldDescriptor | None) -> None:
|
||||
"""The measurement currently on screen — or none, which hides the bar."""
|
||||
if descriptor != self._descriptor:
|
||||
self._effective_scale = None
|
||||
@@ -417,7 +416,7 @@ class ThreeDPanel(QFrame):
|
||||
"""
|
||||
self.scale_bar.set_ramp(ramp)
|
||||
|
||||
def set_capabilities(self, capabilities: Optional[SceneCapabilities]) -> None:
|
||||
def set_capabilities(self, capabilities: SceneCapabilities | None) -> None:
|
||||
"""Offer only the controls this data source can actually feed.
|
||||
|
||||
Today's adapters colour a scalar in place and publish no displacement at all, so
|
||||
@@ -451,7 +450,7 @@ class ThreeDPanel(QFrame):
|
||||
else t("scene3d.contact_arrow_tip")
|
||||
)
|
||||
|
||||
def capabilities(self) -> Optional[SceneCapabilities]:
|
||||
def capabilities(self) -> SceneCapabilities | None:
|
||||
return self._capabilities
|
||||
|
||||
def _render_legend(self) -> None:
|
||||
@@ -551,7 +550,7 @@ class ThreeDPanel(QFrame):
|
||||
self.source_label.setToolTip(t(f"scene3d.source_{kind}_tip"))
|
||||
self.source_label.setVisible(self._show_diagnostics)
|
||||
|
||||
def set_diagnostic(self, diagnostic: Optional[Diagnostic]) -> None:
|
||||
def set_diagnostic(self, diagnostic: Diagnostic | None) -> None:
|
||||
"""One diagnostic line. The 2D views stay usable behind it.
|
||||
|
||||
Warnings and errors are always stated: they are claims about the *measurement*
|
||||
|
||||
@@ -19,13 +19,15 @@ file cannot relabel a measurement as another quantity.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Optional
|
||||
import math
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
UNAVAILABLE_DESCRIPTOR,
|
||||
ColorScale,
|
||||
DisplaySettings,
|
||||
FieldDescriptor,
|
||||
UNAVAILABLE_DESCRIPTOR,
|
||||
)
|
||||
|
||||
#: Frame rates the pane offers. A saved value outside this set is repaired to the
|
||||
@@ -51,7 +53,7 @@ def _number(value: Any, fallback: float) -> float:
|
||||
number = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return fallback
|
||||
if number != number or number in (float("inf"), float("-inf")): # NaN / inf
|
||||
if math.isnan(number) or number in (float("inf"), float("-inf")): # NaN / inf
|
||||
return fallback
|
||||
return number
|
||||
|
||||
@@ -102,9 +104,9 @@ def settings_to_prefs(settings: DisplaySettings) -> dict[str, Any]:
|
||||
|
||||
|
||||
def settings_from_prefs(
|
||||
raw: Optional[Mapping[str, Any]],
|
||||
raw: Mapping[str, Any] | None,
|
||||
*,
|
||||
descriptor: Optional[FieldDescriptor] = None,
|
||||
descriptor: FieldDescriptor | None = None,
|
||||
) -> DisplaySettings:
|
||||
"""Rebuild the display settings, repairing anything unusable.
|
||||
|
||||
@@ -121,7 +123,7 @@ def settings_from_prefs(
|
||||
field = str(raw.get("field") or DEFAULT_FIELD)
|
||||
if field not in FIELD_CHOICES:
|
||||
field = DEFAULT_FIELD
|
||||
scale: Optional[ColorScale] = None
|
||||
scale: ColorScale | None = None
|
||||
saved = raw.get("color_scale")
|
||||
if isinstance(saved, Mapping):
|
||||
mode = str(saved.get("mode") or "fixed")
|
||||
|
||||
@@ -14,8 +14,9 @@ widgets — and one set of signals — whether or not the dialog has ever been o
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
@@ -25,8 +26,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
if TYPE_CHECKING: # the panel is passed in; only its interfaces are used here
|
||||
from orisys_studio.scene3d.panel import ThreeDPanel
|
||||
|
||||
@@ -35,7 +34,7 @@ class Scene3dSettingsDialog(QDialog):
|
||||
"""A modeless home for the 3D pane's controls."""
|
||||
|
||||
def __init__(
|
||||
self, panel: "ThreeDPanel", parent: Optional[QWidget] = None
|
||||
self, panel: ThreeDPanel, parent: QWidget | None = None
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setObjectName("scene3dSettings")
|
||||
|
||||
@@ -25,11 +25,9 @@ description disagrees with the asset is refused with both shapes named.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QVBoxLayout, QWidget
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.scene3d.composer import SceneComposer
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
@@ -47,6 +45,9 @@ from orisys_app_shared.scene3d.contracts import (
|
||||
diagnostic,
|
||||
validate_assets,
|
||||
)
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QVBoxLayout, QWidget
|
||||
|
||||
from orisys_studio.scene3d.adapters import BoxFrameAdapter, FingerFrameAdapter
|
||||
from orisys_studio.scene3d.camera import OrbitCamera
|
||||
from orisys_studio.scene3d.coordinator import ThreeDCoordinator
|
||||
@@ -90,24 +91,24 @@ class Scene3DView(QWidget):
|
||||
descriptorChanged = Signal(object) # Optional[FieldDescriptor]
|
||||
enabledChanged = Signal(bool)
|
||||
|
||||
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._available, self._reason = gl_available()
|
||||
self._widget: Optional[Sensor3DWidget] = None
|
||||
self._panel: Optional[ThreeDPanel] = None
|
||||
self._coordinator: Optional[ThreeDCoordinator] = None
|
||||
self._widget: Sensor3DWidget | None = None
|
||||
self._panel: ThreeDPanel | None = None
|
||||
self._coordinator: ThreeDCoordinator | None = None
|
||||
self._camera = OrbitCamera()
|
||||
self._executor: Optional[ThreadPoolExecutor] = None
|
||||
self._assets: Optional[SceneAssets] = None
|
||||
self._composer: Optional[SceneComposer] = None
|
||||
self._executor: ThreadPoolExecutor | None = None
|
||||
self._assets: SceneAssets | None = None
|
||||
self._composer: SceneComposer | None = None
|
||||
#: The session's island latch, shared with the 2D canvas when the window hands
|
||||
#: one over (``image_backend.DomainMaskLatch``). One latch, two views: the
|
||||
#: surface's validity texture and the canvas's island are the same mask, so
|
||||
#: they cannot drift apart. ``None`` means "no session": the adapter then uses
|
||||
#: each frame's own mask, which is what the tests and the PoC want.
|
||||
self._domain_latch: Any = None
|
||||
self._expected: Optional[ExpectedGeometry] = None
|
||||
self._snapshot: Optional[Callable[[Any, FrameIdentity], Any]] = None
|
||||
self._expected: ExpectedGeometry | None = None
|
||||
self._snapshot: Callable[[Any, FrameIdentity], Any] | None = None
|
||||
#: The adapter behind ``_snapshot``: it owns the reason a packet was refused,
|
||||
#: and (for a source that cannot provide a displacement) what the pane may
|
||||
#: offer. Kept rather than closed over so those answers can be asked.
|
||||
@@ -115,11 +116,11 @@ class Scene3DView(QWidget):
|
||||
self._source_id = ""
|
||||
self._enabled = False
|
||||
self._source_kind = "live"
|
||||
self._last_diagnostic: Optional[Diagnostic] = None
|
||||
self._last_descriptor: Optional[FieldDescriptor] = None
|
||||
self._last_stats: Optional[RenderStats] = None
|
||||
self._last_diagnostic: Diagnostic | None = None
|
||||
self._last_descriptor: FieldDescriptor | None = None
|
||||
self._last_stats: RenderStats | None = None
|
||||
#: The coordinator's last diagnostic as *this* view has already reported it.
|
||||
self._reported_diagnostic: Optional[Diagnostic] = None
|
||||
self._reported_diagnostic: Diagnostic | None = None
|
||||
#: Whether the loaded asset was compared against the device's own description.
|
||||
self._device_verified = False
|
||||
#: Frames the producer did not even build (a frame was already waiting).
|
||||
@@ -173,7 +174,7 @@ class Scene3DView(QWidget):
|
||||
self,
|
||||
assets: SceneAssets,
|
||||
source_id: str,
|
||||
expected: Optional[ExpectedGeometry] = None,
|
||||
expected: ExpectedGeometry | None = None,
|
||||
) -> bool:
|
||||
"""Adopt a validated scene. Returns whether the view accepted it.
|
||||
|
||||
@@ -242,8 +243,8 @@ class Scene3DView(QWidget):
|
||||
return not violations
|
||||
|
||||
def _make_snapshot(
|
||||
self, assets: SceneAssets, expected: Optional[ExpectedGeometry]
|
||||
) -> Optional[Callable[[Any, FrameIdentity], Any]]:
|
||||
self, assets: SceneAssets, expected: ExpectedGeometry | None
|
||||
) -> Callable[[Any, FrameIdentity], Any] | None:
|
||||
"""Bind the adapter that fits this scene, or refuse with a diagnostic.
|
||||
|
||||
The two adapters have different call signatures because they need different
|
||||
@@ -358,12 +359,12 @@ class Scene3DView(QWidget):
|
||||
except Exception: # noqa: BLE001 - a display sink must never break capture
|
||||
logger.exception("the 3D sink failed for one packet")
|
||||
|
||||
def _snapshot_diagnostic(self) -> Optional[Diagnostic]:
|
||||
def _snapshot_diagnostic(self) -> Diagnostic | None:
|
||||
"""Why the adapter refused the last packet (the adapter owns the answer)."""
|
||||
item = getattr(self._adapter, "last_diagnostic", None)
|
||||
return item if isinstance(item, Diagnostic) else None
|
||||
|
||||
def identity_for(self, packet: Any) -> Optional[FrameIdentity]:
|
||||
def identity_for(self, packet: Any) -> FrameIdentity | None:
|
||||
"""The identity a field frame built from this packet would carry."""
|
||||
if self._coordinator is None or self._assets is None:
|
||||
return None
|
||||
@@ -448,7 +449,7 @@ class Scene3DView(QWidget):
|
||||
self._panel.set_source_kind(kind)
|
||||
self._publish_capabilities()
|
||||
|
||||
def capabilities(self) -> Optional[SceneCapabilities]:
|
||||
def capabilities(self) -> SceneCapabilities | None:
|
||||
"""What this data source can provide, for the panel's controls.
|
||||
|
||||
Read off the adapter that is actually bound: an adapter that colours a scalar in
|
||||
@@ -538,27 +539,27 @@ class Scene3DView(QWidget):
|
||||
return self._enabled
|
||||
|
||||
@property
|
||||
def assets(self) -> Optional[SceneAssets]:
|
||||
def assets(self) -> SceneAssets | None:
|
||||
return self._assets
|
||||
|
||||
@property
|
||||
def coordinator(self) -> Optional[ThreeDCoordinator]:
|
||||
def coordinator(self) -> ThreeDCoordinator | None:
|
||||
return self._coordinator
|
||||
|
||||
@property
|
||||
def gl_widget(self) -> Optional[Sensor3DWidget]:
|
||||
def gl_widget(self) -> Sensor3DWidget | None:
|
||||
return self._widget
|
||||
|
||||
@property
|
||||
def panel(self) -> Optional[ThreeDPanel]:
|
||||
def panel(self) -> ThreeDPanel | None:
|
||||
return self._panel
|
||||
|
||||
@property
|
||||
def last_diagnostic(self) -> Optional[Diagnostic]:
|
||||
def last_diagnostic(self) -> Diagnostic | None:
|
||||
return self._last_diagnostic
|
||||
|
||||
@property
|
||||
def last_descriptor(self) -> Optional[FieldDescriptor]:
|
||||
def last_descriptor(self) -> FieldDescriptor | None:
|
||||
return self._last_descriptor
|
||||
|
||||
def state(self) -> SceneState:
|
||||
@@ -568,7 +569,7 @@ class Scene3DView(QWidget):
|
||||
return "disabled"
|
||||
return self._coordinator.state
|
||||
|
||||
def render_stats(self) -> Optional[RenderStats]:
|
||||
def render_stats(self) -> RenderStats | None:
|
||||
return self._last_stats
|
||||
|
||||
def describe(self) -> dict:
|
||||
@@ -751,7 +752,7 @@ class Scene3DView(QWidget):
|
||||
self._panel.set_state(state)
|
||||
self.stateChanged.emit(state)
|
||||
|
||||
def _publish_coordinator_diagnostic(self, item: Optional[Diagnostic]) -> None:
|
||||
def _publish_coordinator_diagnostic(self, item: Diagnostic | None) -> None:
|
||||
"""Raise a coordinator diagnostic once, when it changes."""
|
||||
if item is None or item == self._reported_diagnostic:
|
||||
return
|
||||
|
||||
@@ -17,23 +17,24 @@ two-dimensional application carries on.
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import QEvent, Qt, Signal
|
||||
from PySide6.QtGui import QMouseEvent, QWheelEvent
|
||||
# Qt6 moved QOpenGLWidget out of QtWidgets into its own module; PySide6 follows.
|
||||
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
Diagnostic,
|
||||
DisplaySettings,
|
||||
FrameIdentity,
|
||||
GLCapabilities,
|
||||
RenderFrame,
|
||||
RenderStats,
|
||||
SceneAssets,
|
||||
diagnostic,
|
||||
)
|
||||
from PySide6.QtCore import QEvent, Qt, Signal
|
||||
from PySide6.QtGui import QMouseEvent, QWheelEvent
|
||||
|
||||
# Qt6 moved QOpenGLWidget out of QtWidgets into its own module; PySide6 follows.
|
||||
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from orisys_studio.scene3d.camera import OrbitCamera
|
||||
from orisys_studio.scene3d.gl_backend import (
|
||||
GLDispatch,
|
||||
@@ -62,27 +63,27 @@ class Sensor3DWidget(QOpenGLWidget):
|
||||
self,
|
||||
renderer: GLRenderer,
|
||||
camera: OrbitCamera,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
*,
|
||||
dispatch: Optional[GLDispatch] = None,
|
||||
dispatch: GLDispatch | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._renderer = renderer
|
||||
self._camera = camera
|
||||
self._dispatch = dispatch or GLDispatch()
|
||||
self._assets: Optional[SceneAssets] = None
|
||||
self._frame: Optional[RenderFrame] = None
|
||||
self._assets: SceneAssets | None = None
|
||||
self._frame: RenderFrame | None = None
|
||||
self._settings = DisplaySettings()
|
||||
self._capabilities: Optional[GLCapabilities] = None
|
||||
self._diagnostic: Optional[Diagnostic] = None
|
||||
self._capabilities: GLCapabilities | None = None
|
||||
self._diagnostic: Diagnostic | None = None
|
||||
self._needs_scene_upload = False
|
||||
self._needs_surface_upload = False
|
||||
self._last_stats_at = 0.0
|
||||
#: What reached the screen, counted by identity, and what it cost to compose.
|
||||
self._unique_frames = 0
|
||||
self._last_frame_identity: Optional[FrameIdentity] = None
|
||||
self._last_frame_identity: FrameIdentity | None = None
|
||||
self._last_compose_ms = 0.0
|
||||
self._last_press: Optional[tuple[float, float]] = None
|
||||
self._last_press: tuple[float, float] | None = None
|
||||
self._unavailable = False
|
||||
#: Frames the display never showed, set by the coordinator. Never a
|
||||
#: recording figure.
|
||||
@@ -119,7 +120,7 @@ class Sensor3DWidget(QOpenGLWidget):
|
||||
def camera(self) -> OrbitCamera:
|
||||
return self._camera
|
||||
|
||||
def capabilities(self) -> Optional[GLCapabilities]:
|
||||
def capabilities(self) -> GLCapabilities | None:
|
||||
return self._capabilities
|
||||
|
||||
def displayed_frames(self) -> int:
|
||||
@@ -130,7 +131,7 @@ class Sensor3DWidget(QOpenGLWidget):
|
||||
"""What the frame on screen cost to compose, measured where it was composed."""
|
||||
return float(self._last_compose_ms)
|
||||
|
||||
def diagnostic(self) -> Optional[Diagnostic]:
|
||||
def diagnostic(self) -> Diagnostic | None:
|
||||
return self._diagnostic
|
||||
|
||||
@property
|
||||
|
||||
@@ -24,11 +24,12 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from PySide6.QtCore import QObject, QTimer, Signal
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtCore import QObject, QTimer, Signal
|
||||
|
||||
from orisys_studio import session_metadata
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -58,7 +59,7 @@ class SessionController(QObject):
|
||||
*,
|
||||
recording: Any,
|
||||
packet_for_3d: Callable[[Any], None],
|
||||
packet_sink: Optional[Callable[[Any], None]] = None,
|
||||
packet_sink: Callable[[Any], None] | None = None,
|
||||
sensor_type: str = "box",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
@@ -82,9 +83,9 @@ class SessionController(QObject):
|
||||
self._connect_slow_notified = False
|
||||
self._raw_fallback_notified = False
|
||||
# --- per-session telemetry (read by the rail, the plots and the record) -----
|
||||
self.last_sample_valid: Optional[bool] = None
|
||||
self.latest_fps: Optional[float] = None
|
||||
self.latest_process_ms: Optional[float] = None
|
||||
self.last_sample_valid: bool | None = None
|
||||
self.latest_fps: float | None = None
|
||||
self.latest_process_ms: float | None = None
|
||||
self.latest_capture_fps = 0.0
|
||||
self.latest_capture_dropped = 0
|
||||
#: How many readouts failed this session, for the status line's count.
|
||||
@@ -380,7 +381,7 @@ class SessionController(QObject):
|
||||
return consumer() if callable(consumer) else None
|
||||
|
||||
# ------------------------------------------------------------- presenter help
|
||||
def check_capture_negotiation(self) -> Optional[str]:
|
||||
def check_capture_negotiation(self) -> str | None:
|
||||
"""A once-per-session warning when the camera delivers less than configured.
|
||||
|
||||
The evidence is the *measured* capture rate, not the driver's reported one:
|
||||
@@ -429,7 +430,7 @@ class SessionController(QObject):
|
||||
"""The status line for a failed readout (the phase is deliberately untouched)."""
|
||||
return t("status.sample_invalid", count=self.record_error_count, reason=reason)
|
||||
|
||||
def take_dropped_message(self, dropped: int) -> Optional[str]:
|
||||
def take_dropped_message(self, dropped: int) -> str | None:
|
||||
"""One message per new batch of samples the memory bound discarded."""
|
||||
if int(dropped) <= self.dropped_reported or int(dropped) <= 0:
|
||||
return None
|
||||
|
||||
@@ -25,9 +25,10 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping
|
||||
from typing import Any
|
||||
|
||||
from orisys_app_shared.force.filters import FilterSettings, settings_from_mapping
|
||||
from orisys_app_shared.record import RecordConfig
|
||||
@@ -198,7 +199,8 @@ class SessionFacts:
|
||||
|
||||
def build_session_metadata(config: RecordConfig, facts: SessionFacts) -> dict:
|
||||
"""The ``session.json`` metadata written when a recording starts."""
|
||||
from orisys_app_shared import API_VERSION, __version__ as shared_version
|
||||
from orisys_app_shared import API_VERSION
|
||||
from orisys_app_shared import __version__ as shared_version
|
||||
|
||||
from orisys_studio import __version__ as app_version
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.paths import logo_topbar_path
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QPushButton, QVBoxLayout
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.paths import logo_topbar_path
|
||||
|
||||
from orisys_studio import __version__ as app_version
|
||||
|
||||
|
||||
|
||||
@@ -10,9 +10,14 @@ where the picture comes from (live / replay), which words the footer.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import QSize, Qt, Signal
|
||||
from orisys_app_shared.acquisition.image_backend import (
|
||||
ARROW_PURE,
|
||||
ARROW_STITCHED,
|
||||
DEPTH,
|
||||
ImagePlan,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QImage, QPainter, QPixmap, QTransform
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
@@ -23,15 +28,6 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_app_shared.acquisition.image_backend import (
|
||||
ARROW_PURE,
|
||||
ARROW_STITCHED,
|
||||
DEPTH,
|
||||
ImagePlan,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
from orisys_studio.ui.fit_text import ShrinkableLabel
|
||||
|
||||
VIEW_MODES = (ARROW_PURE, ARROW_STITCHED, DEPTH)
|
||||
@@ -340,7 +336,7 @@ class ArrowPanel(QFrame):
|
||||
self._placeholder = self._empty_message()
|
||||
self.image_label.setText(self._placeholder)
|
||||
|
||||
def set_plan(self, plan: Optional[ImagePlan]) -> None:
|
||||
def set_plan(self, plan: ImagePlan | None) -> None:
|
||||
"""Adopt what the frame on screen can be shown as.
|
||||
|
||||
The modes come from the frame's own fields (see ``image_backend.plan_image``),
|
||||
|
||||
@@ -14,13 +14,12 @@ from __future__ import annotations
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QColor, QPainter, QPixmap
|
||||
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton, QVBoxLayout
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.paths import logo_topbar_path
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QColor, QPainter, QPixmap
|
||||
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton, QVBoxLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
ChannelKey,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.paths import calibration_export_dir
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QColor, QFont, QFontDatabase
|
||||
from PySide6.QtWidgets import (
|
||||
@@ -28,19 +34,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
CalSample,
|
||||
CalSessionStore,
|
||||
ChannelKey,
|
||||
meta_from_sensor_json,
|
||||
)
|
||||
from orisys_app_shared.force.calibration_session import (
|
||||
session_filename,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.paths import calibration_export_dir
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
from orisys_studio.calibration_service import CalibrationService
|
||||
from orisys_studio.ui.cal_point_dialog import CalibrationPointDialog
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ the three-panel layout, while a single point can still be examined in full.
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtCore import Qt
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtGui import QFont, QFontDatabase
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
@@ -17,10 +18,6 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_app_shared.force import NA
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
try:
|
||||
import pyqtgraph as pg
|
||||
except ImportError as exc: # pragma: no cover
|
||||
|
||||
@@ -5,16 +5,6 @@ from __future__ import annotations
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtCore import Qt, QSize, Signal
|
||||
from PySide6.QtGui import QColor, QFont, QFontDatabase, QFontMetrics
|
||||
from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.force import MetricHistory
|
||||
from orisys_app_shared.force.history import DEFAULT_MAX_FPS
|
||||
@@ -27,6 +17,15 @@ from orisys_app_shared.force.source import StoreCurveSource
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.record.sample import plottable_axis_mask
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtCore import QSize, Qt, Signal
|
||||
from PySide6.QtGui import QColor, QFont, QFontDatabase, QFontMetrics
|
||||
from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_studio.ui import icons
|
||||
from orisys_studio.ui.icons import ICON_SIZE
|
||||
@@ -130,11 +129,11 @@ def _mono_font(point_size: int = 9, *, bold: bool = False) -> QFont:
|
||||
class _Sync:
|
||||
"""Marks a block of range changes as programmatic (not a user gesture)."""
|
||||
|
||||
def __init__(self, panel: "ForcePlotsPanel") -> None:
|
||||
def __init__(self, panel: ForcePlotsPanel) -> None:
|
||||
self._panel = panel
|
||||
self._previous = panel._syncing
|
||||
|
||||
def __enter__(self) -> "_Sync":
|
||||
def __enter__(self) -> _Sync:
|
||||
self._panel._syncing = True
|
||||
return self
|
||||
|
||||
@@ -1054,7 +1053,7 @@ class ForcePlotsPanel(QFrame):
|
||||
self._window = (x0, x1)
|
||||
self.refresh_from_store()
|
||||
|
||||
def _guard(self) -> "_Sync":
|
||||
def _guard(self) -> _Sync:
|
||||
"""Context manager marking range changes as programmatic."""
|
||||
return _Sync(self)
|
||||
|
||||
|
||||
@@ -14,11 +14,10 @@ from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtCore import QRectF, Qt
|
||||
from PySide6.QtGui import QColor, QIcon, QPainter, QPen, QPixmap
|
||||
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
#: Names of the icons this module can paint.
|
||||
NAMES = ("zoom_fit", "box_zoom", "latest", "settings", "reset_view")
|
||||
|
||||
|
||||
@@ -13,9 +13,15 @@ acquisition, and no path that reports a 3D problem as a recording problem.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
Diagnostic,
|
||||
ExpectedGeometry,
|
||||
)
|
||||
from PySide6.QtCore import QSize, Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
@@ -27,13 +33,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
Diagnostic,
|
||||
ExpectedGeometry,
|
||||
SceneAssets,
|
||||
diagnostic,
|
||||
)
|
||||
from orisys_studio.scene3d.asset_source import (
|
||||
SceneAssetLoader,
|
||||
SceneSource,
|
||||
@@ -44,6 +43,7 @@ from orisys_studio.ui import icons
|
||||
|
||||
if TYPE_CHECKING: # the view imports GL bindings lazily; the type is enough here
|
||||
from orisys_app_shared.acquisition.geometry import DeviceGeometry
|
||||
|
||||
from orisys_studio.scene3d.view import Scene3DView
|
||||
|
||||
#: Shown when the pane is on but this sensor has no 3D scene at all (box, mini).
|
||||
@@ -81,17 +81,17 @@ class Mesh3dPanel(QFrame):
|
||||
enabledChanged = Signal(bool)
|
||||
|
||||
def __init__(
|
||||
self, parent: Optional[QWidget] = None, *, theme: str = "dark"
|
||||
self, parent: QWidget | None = None, *, theme: str = "dark"
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setObjectName("contentPanel")
|
||||
self._theme = str(theme)
|
||||
self._view: Optional["Scene3DView"] = None
|
||||
self._loader: Optional[SceneAssetLoader] = None
|
||||
self._source: Optional[SceneSource] = None
|
||||
self._view: Scene3DView | None = None
|
||||
self._loader: SceneAssetLoader | None = None
|
||||
self._source: SceneSource | None = None
|
||||
self._sensor_type = ""
|
||||
self._expected: Optional[ExpectedGeometry] = None
|
||||
self._expected_provider: "Optional[Callable[[], DeviceGeometry]]" = None
|
||||
self._expected: ExpectedGeometry | None = None
|
||||
self._expected_provider: Callable[[], DeviceGeometry] | None = None
|
||||
self._source_kind = "live"
|
||||
self._enabled = False
|
||||
self._loaded = False
|
||||
@@ -103,8 +103,8 @@ class Mesh3dPanel(QFrame):
|
||||
self._keys = None
|
||||
#: The package whose scene is in the view, so switching sensor away and back
|
||||
#: does not reload it (and does not pretend to be loading it either).
|
||||
self._loaded_path: Optional[Path] = None
|
||||
self._reported: Optional[Diagnostic] = None
|
||||
self._loaded_path: Path | None = None
|
||||
self._reported: Diagnostic | None = None
|
||||
self._pending_settings = None
|
||||
self._message_kind = _UNAVAILABLE
|
||||
self._message_args: dict[str, str] = {"reason": ""}
|
||||
@@ -225,8 +225,8 @@ class Mesh3dPanel(QFrame):
|
||||
self,
|
||||
sensor_type: str,
|
||||
*,
|
||||
expected: Optional[ExpectedGeometry] = None,
|
||||
expected_provider: "Optional[Callable[[], DeviceGeometry]]" = None,
|
||||
expected: ExpectedGeometry | None = None,
|
||||
expected_provider: Callable[[], DeviceGeometry] | None = None,
|
||||
package_override: str | Path | None = None,
|
||||
) -> None:
|
||||
"""Tell the pane which sensor it is looking at. Cheap; no IO.
|
||||
@@ -276,7 +276,7 @@ class Mesh3dPanel(QFrame):
|
||||
return
|
||||
self._show_message(_LOADING, name=self._source.path.name)
|
||||
|
||||
def attach_view(self, view: "Scene3DView") -> None:
|
||||
def attach_view(self, view: Scene3DView) -> None:
|
||||
"""Adopt a view: the real one (built lazily) or a double in a test.
|
||||
|
||||
Public because it is the seam tests use — poking ``_view`` from a test would
|
||||
@@ -308,7 +308,7 @@ class Mesh3dPanel(QFrame):
|
||||
if not view.available:
|
||||
self._show_message(_UNAVAILABLE, reason=view.reason)
|
||||
|
||||
def ensure_view(self) -> Optional[QWidget]:
|
||||
def ensure_view(self) -> QWidget | None:
|
||||
"""Build the 3D view on first use, so a headless machine never imports GL."""
|
||||
if self._view is not None or self._sensor_type != "finger":
|
||||
return self._view
|
||||
@@ -448,11 +448,11 @@ class Mesh3dPanel(QFrame):
|
||||
|
||||
# ------------------------------------------------------------------ inputs
|
||||
@property
|
||||
def view(self) -> Optional["Scene3DView"]:
|
||||
def view(self) -> Scene3DView | None:
|
||||
return self._view
|
||||
|
||||
@property
|
||||
def last_diagnostic(self) -> Optional[Diagnostic]:
|
||||
def last_diagnostic(self) -> Diagnostic | None:
|
||||
return self._reported
|
||||
|
||||
@property
|
||||
@@ -471,7 +471,7 @@ class Mesh3dPanel(QFrame):
|
||||
"""Whether a 3D picture is possible here at all (GL and an asset)."""
|
||||
return self._view is not None and bool(self._view.available)
|
||||
|
||||
def scene_path(self) -> Optional[Path]:
|
||||
def scene_path(self) -> Path | None:
|
||||
return None if self._source is None else self._source.path
|
||||
|
||||
def set_source_kind(self, kind: str) -> None:
|
||||
|
||||
@@ -10,6 +10,12 @@ reports as done is what the record will say (see ``force/filters.py``).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.force.filters import (
|
||||
FilterSettings,
|
||||
available_presets,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
@@ -25,14 +31,6 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.force.filters import (
|
||||
SUGGESTED_LOWPASS_HZ,
|
||||
FilterSettings,
|
||||
available_presets,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
DEFAULT_PLOT_WINDOW_SEC = 4.0
|
||||
#: Lower bounds. FN is allowed below zero: a resting normal force has no reason
|
||||
#: to sit exactly on the frame, and with the lower bound at 0 the idle curve is
|
||||
|
||||
@@ -14,10 +14,9 @@ timer without touching the writer thread.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtWidgets import QLabel
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtWidgets import QLabel
|
||||
|
||||
#: The recording channel is idle, never started in this session.
|
||||
IDLE = "idle"
|
||||
|
||||
@@ -17,6 +17,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QFrame,
|
||||
@@ -26,8 +27,6 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
from orisys_studio.ui.fit_text import ShrinkableLabel
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ own labels and tooltips instead of a bare "配置" combo.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.sensors import list_configs_for_type, list_sensor_types
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
@@ -18,9 +20,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.sensors import list_configs_for_type, list_sensor_types
|
||||
|
||||
CONTROL_HEIGHT = 30
|
||||
#: Both combos stay narrow: this row shares one line with the session state.
|
||||
TYPE_WIDTH = 76
|
||||
|
||||
@@ -19,6 +19,8 @@ scrolls sideways instead of squeezing the controls into an unreadable row.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.theme import color
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
@@ -33,9 +35,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
from orisys_studio.ui.fit_text import ShrinkableLabel
|
||||
from orisys_studio.ui.record_status import RecordStatusChip
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional, Protocol
|
||||
from typing import Any, Protocol
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
@@ -80,7 +80,7 @@ class ViewLayoutController(QObject):
|
||||
default_splitter_sizes: tuple,
|
||||
min_panel_widths: dict,
|
||||
default_3d_pane_width: int,
|
||||
parent: Optional[QObject] = None,
|
||||
parent: QObject | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._host = host
|
||||
@@ -103,7 +103,7 @@ class ViewLayoutController(QObject):
|
||||
self._view_preset = preset if preset in self._presets else "custom"
|
||||
self._show_cal = bool(host.prefs.get("show_cal_panel", False))
|
||||
self._show_finger_3d = bool(host.prefs.get("show_finger_3d", True))
|
||||
self._actions: Optional[LayoutActions] = None
|
||||
self._actions: LayoutActions | None = None
|
||||
|
||||
# ------------------------------------------------------------------ wiring
|
||||
def bind_actions(
|
||||
@@ -244,7 +244,7 @@ class ViewLayoutController(QObject):
|
||||
self._set_check(self._actions.settings, self._toolbar.settings_expanded())
|
||||
|
||||
@staticmethod
|
||||
def _set_check(action: Any, checked: bool, *, enabled: Optional[bool] = None) -> None:
|
||||
def _set_check(action: Any, checked: bool, *, enabled: bool | None = None) -> None:
|
||||
"""Set a checkable entry without letting it drive its own handler."""
|
||||
if action is None:
|
||||
return
|
||||
|
||||
@@ -62,8 +62,8 @@ def qt_app():
|
||||
@pytest.fixture
|
||||
def make_window(qt_app, tmp_path, monkeypatch):
|
||||
"""Build a ``StudioMainWindow`` whose preference writes stay in tmp_path."""
|
||||
from orisys_app_shared import prefs as prefs_pkg
|
||||
from orisys_app_shared import paths as paths_mod
|
||||
from orisys_app_shared import prefs as prefs_pkg
|
||||
from orisys_studio.app_window import StudioMainWindow
|
||||
|
||||
prefs_file = tmp_path / "preference.json"
|
||||
|
||||
@@ -9,7 +9,6 @@ the curves and the value cards were actually given.
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.force.session import SessionSample, SessionStore
|
||||
from orisys_studio.ui.force_plots import ForcePlotsPanel, _nice_ticks, format_force
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ button, is what tells stopped from paused.
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
from tests.conftest import finish_run, running_window, show_window
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ window-level tests): these are the *rules*, not the decoder.
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_studio import replay_controller
|
||||
from orisys_studio.replay_controller import ReplayController
|
||||
|
||||
@@ -9,16 +9,13 @@ the grid does not match, with a diagnostic that says which shapes were seen.
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.scene3d import synthetic
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
ExpectedGeometry,
|
||||
FrameIdentity,
|
||||
ThreeDError,
|
||||
validate_surface_frame,
|
||||
)
|
||||
from orisys_studio.scene3d.adapters import (
|
||||
@@ -40,16 +37,16 @@ class FakePacket:
|
||||
behaviour under test.
|
||||
"""
|
||||
|
||||
flow: Optional[np.ndarray] = None
|
||||
depth: Optional[np.ndarray] = None
|
||||
valid_mask: Optional[np.ndarray] = None
|
||||
flow: np.ndarray | None = None
|
||||
depth: np.ndarray | None = None
|
||||
valid_mask: np.ndarray | None = None
|
||||
frame_id: int = 1
|
||||
invalid_reason: str = ""
|
||||
flow_valid: Optional[bool] = True
|
||||
flow_frame_id: Optional[int] = None
|
||||
depth_valid: Optional[bool] = True
|
||||
depth_frame_id: Optional[int] = None
|
||||
forces_valid: Optional[bool] = True
|
||||
flow_valid: bool | None = True
|
||||
flow_frame_id: int | None = None
|
||||
depth_valid: bool | None = True
|
||||
depth_frame_id: int | None = None
|
||||
forces_valid: bool | None = True
|
||||
|
||||
|
||||
def _identity(generation: int = 1) -> FrameIdentity:
|
||||
|
||||
@@ -15,7 +15,6 @@ from dataclasses import replace
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.scene3d import synthetic
|
||||
from orisys_app_shared.scene3d.composer import SceneComposer
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
|
||||
@@ -23,7 +23,6 @@ import os
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.scene3d import synthetic
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
ColorScale,
|
||||
@@ -105,7 +104,11 @@ def _pump(qt_app, widget, predicate, *, timeout_s: float = 5.0) -> bool:
|
||||
def gl_widget(qt_app):
|
||||
"""A real, shown QOpenGLWidget with the synthetic scene, driven by Qt."""
|
||||
from orisys_studio.scene3d.camera import OrbitCamera
|
||||
from orisys_studio.scene3d.gl_backend import GLDispatch, GLRenderer, configure_surface_format
|
||||
from orisys_studio.scene3d.gl_backend import (
|
||||
GLDispatch,
|
||||
GLRenderer,
|
||||
configure_surface_format,
|
||||
)
|
||||
from orisys_studio.scene3d.widget import Sensor3DWidget
|
||||
|
||||
configure_surface_format("desktop33")
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.acquisition.arrows import _policy_from_style
|
||||
from orisys_app_shared.scene3d.composer import SceneComposer
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
|
||||
@@ -13,11 +13,10 @@ view menu / the pane's header both reach that dialog.
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_studio.ui import icons
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -186,7 +185,11 @@ def test_the_pane_offers_the_two_quantities_and_says_which_one_it_drew(panel):
|
||||
|
||||
def test_the_diagnostics_switch_governs_what_the_pane_explains(panel):
|
||||
"""Warnings are claims about the measurement and always show; info is explanation."""
|
||||
from orisys_app_shared.scene3d.contracts import FieldDescriptor, RenderStats, diagnostic
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
FieldDescriptor,
|
||||
RenderStats,
|
||||
diagnostic,
|
||||
)
|
||||
|
||||
panel.set_descriptor(FieldDescriptor("flow_magnitude", "px", "approximate", False))
|
||||
panel.set_render_stats(
|
||||
|
||||
@@ -13,7 +13,6 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.scene3d import protect, synthetic
|
||||
from orisys_app_shared.scene3d.contracts import ExpectedGeometry, ThreeDError
|
||||
from orisys_app_shared.scene3d.package import write_package
|
||||
|
||||
@@ -404,8 +404,6 @@ def _render_region(qt_app, assets, island, uvs, preset_name, viewport):
|
||||
RenderFrame,
|
||||
freeze,
|
||||
)
|
||||
from PySide6.QtGui import QImage
|
||||
|
||||
from orisys_studio.scene3d import gl_backend
|
||||
from orisys_studio.scene3d.camera import OrbitCamera
|
||||
from orisys_studio.scene3d.gl_backend import (
|
||||
@@ -415,6 +413,7 @@ def _render_region(qt_app, assets, island, uvs, preset_name, viewport):
|
||||
lut_rgba,
|
||||
)
|
||||
from orisys_studio.scene3d.widget import Sensor3DWidget
|
||||
from PySide6.QtGui import QImage
|
||||
|
||||
configure_surface_format("desktop33")
|
||||
renderer = GLRenderer()
|
||||
|
||||
@@ -49,9 +49,8 @@ def test_the_skeleton_does_not_import_a_gl_binding(qt_app):
|
||||
|
||||
|
||||
def test_configure_surface_format_selects_a_complete_profile(qt_app, restore_surface_format):
|
||||
from PySide6.QtGui import QSurfaceFormat
|
||||
|
||||
from orisys_studio.scene3d.gl_backend import configure_surface_format
|
||||
from PySide6.QtGui import QSurfaceFormat
|
||||
|
||||
configure_surface_format("desktop33")
|
||||
desktop = QSurfaceFormat.defaultFormat()
|
||||
@@ -72,7 +71,7 @@ def test_configure_surface_format_selects_a_complete_profile(qt_app, restore_sur
|
||||
|
||||
def test_the_widget_holds_cpu_state_and_touches_no_context(qt_app):
|
||||
"""Constructing the widget must not need a current context or a GPU."""
|
||||
from orisys_app_shared.scene3d.contracts import DisplaySettings, FrameIdentity
|
||||
from orisys_app_shared.scene3d.contracts import DisplaySettings
|
||||
from orisys_studio.scene3d.camera import OrbitCamera
|
||||
from orisys_studio.scene3d.gl_backend import GLRenderer
|
||||
from orisys_studio.scene3d.widget import Sensor3DWidget
|
||||
|
||||
@@ -9,7 +9,6 @@ from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.scene3d import synthetic
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
CameraPreset,
|
||||
|
||||
@@ -20,6 +20,7 @@ mathematics (the camera, the sampler and the composer have their own files):
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import time
|
||||
from concurrent.futures import Future
|
||||
from dataclasses import replace
|
||||
@@ -27,17 +28,14 @@ from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import ui_check
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QWidget
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.scene3d import synthetic
|
||||
from orisys_app_shared.scene3d.contracts import (
|
||||
UNAVAILABLE_DESCRIPTOR,
|
||||
ColorScale,
|
||||
DisplaySettings,
|
||||
FieldDescriptor,
|
||||
UNAVAILABLE_DESCRIPTOR,
|
||||
)
|
||||
from orisys_app_shared.scene3d.package import write_package
|
||||
from orisys_studio.app_window import MIN_PANEL_WIDTHS
|
||||
@@ -49,9 +47,15 @@ from orisys_studio.scene3d.asset_source import (
|
||||
load_package,
|
||||
no_asset_diagnostic,
|
||||
)
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QWidget
|
||||
from tests.conftest import (
|
||||
finish_run as _finish,
|
||||
)
|
||||
from tests.conftest import (
|
||||
running_window as _running_window,
|
||||
)
|
||||
from tests.conftest import (
|
||||
show_window as _show,
|
||||
)
|
||||
|
||||
@@ -60,7 +64,7 @@ from tests.conftest import (
|
||||
def write_poc(tmp_path):
|
||||
"""A real package on disk, produced by the shipping writer."""
|
||||
|
||||
def _write(name: str = "finger-display-1.0.0") -> "object":
|
||||
def _write(name: str = "finger-display-1.0.0") -> object:
|
||||
path = tmp_path / f"{name}{PACKAGE_SUFFIX}"
|
||||
write_package(
|
||||
synthetic.poc_scene(),
|
||||
@@ -520,7 +524,9 @@ def test_a_hand_edited_settings_block_is_repaired(block):
|
||||
assert settings.field in scene3d_prefs.FIELD_CHOICES
|
||||
if settings.color_scale is not None:
|
||||
lower, upper = settings.color_scale.lower, settings.color_scale.upper
|
||||
assert lower == lower and upper == upper, "a NaN survived the repair"
|
||||
# Not a tautology: ``float("nan")`` is in the parametrisation above, and this
|
||||
# is the assertion that the repair actually replaced it.
|
||||
assert not math.isnan(lower) and not math.isnan(upper), "a NaN survived the repair"
|
||||
assert upper > lower
|
||||
assert abs(lower) <= scene3d_prefs.MAX_SCALE_BOUND
|
||||
assert abs(upper) <= scene3d_prefs.MAX_SCALE_BOUND
|
||||
@@ -1129,8 +1135,8 @@ def test_every_run_gets_a_new_generation(make_window, qt_app):
|
||||
Without this the pane accepts the first run's frames and refuses every later run's,
|
||||
so the picture stays on the first run's last frame forever.
|
||||
"""
|
||||
from orisys_app_shared.scene3d.contracts import FrameIdentity
|
||||
import ui_check
|
||||
from orisys_app_shared.scene3d.contracts import FrameIdentity
|
||||
|
||||
window = _show(make_window(sensor_type="finger"), (1440, 880))
|
||||
try:
|
||||
|
||||
@@ -10,8 +10,6 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.sensors import list_configs_for_type, list_sensor_types
|
||||
from orisys_studio import app_window
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ recording coordinator and fake workers.
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from orisys_studio import session_controller
|
||||
from orisys_studio.session_controller import SessionController
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from orisys_app_shared.force.filters import FilterSettings
|
||||
from orisys_app_shared.record import RecordConfig
|
||||
from orisys_studio import session_metadata as sm
|
||||
@@ -133,7 +132,7 @@ def test_the_session_block_states_the_device_the_filters_and_the_picture():
|
||||
)
|
||||
worker = SimpleNamespace(
|
||||
capture_status=lambda: {"requested_fps": 30.0, "measured_fps": 21.0},
|
||||
filter_state=lambda: {},
|
||||
filter_state=dict,
|
||||
filter_settings=lambda: FilterSettings(),
|
||||
filters_unsupported=lambda: (),
|
||||
)
|
||||
|
||||
@@ -13,7 +13,6 @@ Written against the requirement rather than the implementation:
|
||||
from __future__ import annotations
|
||||
|
||||
import ui_check
|
||||
|
||||
from tests.conftest import finish_run, running_window, show_window
|
||||
|
||||
|
||||
|
||||
@@ -5,14 +5,18 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pytest
|
||||
|
||||
import ui_check
|
||||
from tests.conftest import (
|
||||
finish_run as _finish,
|
||||
)
|
||||
from tests.conftest import (
|
||||
running_window as _running_window,
|
||||
)
|
||||
from tests.conftest import (
|
||||
show_window as _show,
|
||||
)
|
||||
from tests.conftest import (
|
||||
visible_descendants,
|
||||
)
|
||||
|
||||
@@ -369,7 +373,6 @@ def test_the_panel_shows_the_numbers_of_the_file_it_exports(make_window, record_
|
||||
from pathlib import Path
|
||||
|
||||
import orisys.calibration as sdk_calibration
|
||||
|
||||
from orisys_app_shared.force.calibration_io import build_force_calibration_v2
|
||||
|
||||
window = _show(make_window(), (1400, 880))
|
||||
@@ -529,8 +532,11 @@ def test_applying_another_devices_calibration_is_flagged(make_window, record_roo
|
||||
"""The apply path states a sensor/config mismatch instead of silently applying."""
|
||||
import json
|
||||
|
||||
from orisys_app_shared.force.calibration_io import build_export_model, write_force_calibration
|
||||
from orisys_app_shared.force.calibration_io import CalSample, CalSessionStore
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
CalSample,
|
||||
CalSessionStore,
|
||||
build_export_model,
|
||||
)
|
||||
|
||||
window, stuck = _running_window(make_window)
|
||||
store = CalSessionStore()
|
||||
@@ -816,7 +822,6 @@ def test_a_confirmed_calibration_is_snapshotted_into_the_recording(make_window,
|
||||
from pathlib import Path
|
||||
|
||||
import orisys_studio.app_window as app_module
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
window, stuck = _running_window(make_window)
|
||||
app_module.recordings_dir = lambda: record_root
|
||||
@@ -1179,9 +1184,8 @@ def test_the_live_rail_is_a_play_pause_toggle_with_its_own_stop(make_window):
|
||||
icon-only pause button next to it, so the same position meant two different
|
||||
things depending on which view was on screen.
|
||||
"""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
stuck = ui_check._StuckWorker()
|
||||
@@ -1232,7 +1236,6 @@ def test_the_live_rail_is_a_play_pause_toggle_with_its_own_stop(make_window):
|
||||
def test_record_strip_separates_acquiring_from_recording(make_window):
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.record.experiment import RecordStats
|
||||
|
||||
from orisys_studio.ui import record_status
|
||||
|
||||
window, stuck = _running_window(make_window)
|
||||
@@ -1315,9 +1318,8 @@ def test_overview_region_is_the_timeline_control(make_window):
|
||||
|
||||
def test_the_charts_start_in_follow_mode(make_window):
|
||||
"""Every live entry point resumes following; only browsing is a gesture."""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window, stuck = _running_window(make_window)
|
||||
panel = window.force_plots
|
||||
@@ -1424,9 +1426,8 @@ def test_the_arrow_legend_follows_the_view_mode(make_window):
|
||||
|
||||
def test_the_sensor_summary_opens_the_dialog(make_window):
|
||||
"""The rail states the sensor; the pickers live in the dialog it opens."""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
assert window.toolbar.btn_sensor.text() == t(
|
||||
@@ -1614,9 +1615,8 @@ def test_the_replay_view_is_the_live_view_with_a_video_source(make_window):
|
||||
|
||||
def test_the_replay_view_loads_a_recording_and_scrubs_it(make_window, replay_recording):
|
||||
"""The replay view reads the recording: charts, 2D panel and transport."""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
window.brand_bar.btn_playback.click()
|
||||
@@ -1726,7 +1726,6 @@ def test_the_unit_chip_switches_the_charts_and_the_toolbar(make_window):
|
||||
"""One click on the unit chip changes the unit everywhere, not just locally."""
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_studio.ui.force_plots import unit_display
|
||||
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
@@ -1813,7 +1812,6 @@ def test_the_video_follows_the_sensor_rate_and_keeps_every_row(make_window, reco
|
||||
import csv
|
||||
|
||||
import cv2
|
||||
|
||||
import orisys_studio.app_window as app_module
|
||||
|
||||
window, stuck = _running_window(make_window)
|
||||
@@ -1911,9 +1909,8 @@ def test_the_readout_describes_the_point_the_cursor_is_on(
|
||||
A replay used to state the live acquisition in cards sitting next to a chart
|
||||
of 7.5e5 raw counts, which reads as "the replay never opened".
|
||||
"""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
window.brand_bar.btn_playback.click()
|
||||
@@ -1945,9 +1942,8 @@ def test_a_raw_only_recording_is_never_shown_in_newton(make_window, replay_recor
|
||||
for the axes it calibrated. Asking for newton anyway drew three blank charts
|
||||
and a dash in every card, which looks exactly like "the replay did not load".
|
||||
"""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
window.toolbar.set_units("newton")
|
||||
@@ -2056,9 +2052,8 @@ def test_the_replay_view_offers_a_stop_that_always_works(make_window, replay_rec
|
||||
there: plain playback and a recompute pass. The tracker reset owns no device here,
|
||||
so it is disabled rather than repurposed.
|
||||
"""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
window.brand_bar.btn_playback.click()
|
||||
@@ -2135,9 +2130,8 @@ def test_escape_stops_the_replay_too(make_window, replay_recording):
|
||||
|
||||
|
||||
def test_the_live_view_keeps_the_tracker_reset(make_window):
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
window.brand_bar.btn_live.click()
|
||||
@@ -2160,9 +2154,8 @@ def test_a_bare_video_plays_even_though_it_is_not_a_recording(make_window, recor
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
bare = Path(record_root) / "bare_video.mp4"
|
||||
writer = cv2.VideoWriter(
|
||||
@@ -2223,15 +2216,14 @@ def test_the_replay_and_the_live_paths_share_one_image_backend(make_window, reco
|
||||
"""
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
import orisys_studio.app_window as app_module
|
||||
from orisys_app_shared.acquisition.image_backend import (
|
||||
DERIVED_MODES,
|
||||
ImageFrame,
|
||||
SOURCE_LIVE,
|
||||
SOURCE_RECOMPUTE,
|
||||
ImageFrame,
|
||||
)
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
bare = Path(record_root) / "backend_video.mp4"
|
||||
writer = cv2.VideoWriter(str(bare), cv2.VideoWriter_fourcc(*"mp4v"), 30.0, (64, 48))
|
||||
@@ -2318,9 +2310,8 @@ def test_the_replay_entries_exist_only_in_the_replay_view(make_window, record_ro
|
||||
"""
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
bare = Path(record_root) / "gated_video.mp4"
|
||||
writer = cv2.VideoWriter(str(bare), cv2.VideoWriter_fourcc(*"mp4v"), 30.0, (64, 48))
|
||||
@@ -2373,8 +2364,6 @@ def test_a_file_that_is_not_a_video_at_all_is_refused_with_a_reason(
|
||||
"""Not a recording *and* not decodable: say what is missing and offer recompute."""
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
broken = Path(record_root) / "not_really_a_video.mp4"
|
||||
broken.write_text("this is not a video at all\n", encoding="utf-8")
|
||||
|
||||
@@ -2404,9 +2393,8 @@ def test_a_file_that_is_not_a_video_at_all_is_refused_with_a_reason(
|
||||
def test_a_video_inside_a_recording_opens_the_recording_and_plays(
|
||||
make_window, replay_recording
|
||||
):
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
window.brand_bar.btn_playback.click()
|
||||
@@ -2432,9 +2420,8 @@ def test_a_recording_without_a_video_says_it_cannot_be_played(make_window, recor
|
||||
import csv
|
||||
import json
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
directory = Path(record_root) / "no_video_recording"
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
@@ -2531,9 +2518,8 @@ def test_the_status_bar_is_a_notice_line_not_a_state_line(make_window):
|
||||
"""
|
||||
import time
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
import orisys_studio.app_window as app_module
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
assert not hasattr(window, "device_label"), (
|
||||
|
||||
@@ -8,8 +8,6 @@ both can be stood in for — which is the point of the split, and what these tes
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from orisys_studio.view_layout import ViewLayoutController
|
||||
|
||||
VIEW_PRESETS = ("custom", "live", "replay", "calibration")
|
||||
@@ -295,7 +293,8 @@ def test_a_preset_that_gives_no_width_to_the_3d_pane_takes_it_from_the_widest():
|
||||
|
||||
def test_the_shipped_live_layout_makes_the_3d_pane_the_widest_panel():
|
||||
"""The 3D view is the subject when it is on, not an inset beside the charts."""
|
||||
from orisys_studio.app_window import DEFAULT_SPLITTER_SIZES, VIEW_SPLITTER_SIZES as LIVE
|
||||
from orisys_studio.app_window import DEFAULT_SPLITTER_SIZES
|
||||
from orisys_studio.app_window import VIEW_SPLITTER_SIZES as LIVE
|
||||
|
||||
mesh, image, charts = DEFAULT_SPLITTER_SIZES
|
||||
assert mesh > image, DEFAULT_SPLITTER_SIZES
|
||||
|
||||
@@ -18,6 +18,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
APP_ROOT = Path(__file__).resolve().parents[1]
|
||||
REPO_ROOT = APP_ROOT.parents[1]
|
||||
for _path in (APP_ROOT / "src", REPO_ROOT / "shared" / "src"):
|
||||
@@ -185,7 +186,6 @@ def _fake_packet(
|
||||
something visible (see ``ui_snapshot --scene replay``).
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.acquisition.sdk_worker import SdkPacket
|
||||
|
||||
nan = float("nan")
|
||||
@@ -558,12 +558,10 @@ def audit_palette(verbose: bool = False) -> list[str]:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.force.calibration_io import CalSample
|
||||
from orisys_app_shared.i18n import set_locale, t
|
||||
from orisys_app_shared.theme import build_qss, load_colors
|
||||
from orisys_studio.app_window import VIEW_SPLITTER_SIZES, StudioMainWindow
|
||||
from orisys_studio.app_window import StudioMainWindow
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication.instance() or QApplication(sys.argv)
|
||||
set_locale("zh_CN")
|
||||
@@ -768,12 +766,14 @@ def main() -> int:
|
||||
window.cal_panel.table.item(1, 5).text() == t("cal.state_excluded"),
|
||||
"the excluded row does not show its state",
|
||||
)
|
||||
import orisys.calibration as _sdk_calibration
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
build_export_model,
|
||||
build_force_calibration_v2 as _build_v2,
|
||||
predict_channel,
|
||||
)
|
||||
import orisys.calibration as _sdk_calibration
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
build_force_calibration_v2 as _build_v2,
|
||||
)
|
||||
|
||||
store = window.cal_panel._store
|
||||
model = build_export_model(
|
||||
@@ -1798,6 +1798,7 @@ def main() -> int:
|
||||
section("acquisition correctness")
|
||||
import csv as _csv
|
||||
import shutil as _shutil
|
||||
|
||||
import numpy as np
|
||||
|
||||
# A device that only confirms its calibration a moment after the first
|
||||
@@ -2090,6 +2091,10 @@ def main() -> int:
|
||||
env=env,
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
# Deliberately not ``check=True``: a non-zero exit *is* one of the things
|
||||
# being measured here (a crash on shutdown), and it is reported through
|
||||
# this checker's own ``check()`` below rather than as a traceback.
|
||||
check=False,
|
||||
)
|
||||
elapsed = time.perf_counter() - started
|
||||
print(f" window closed and the process returned in {elapsed:.1f}s")
|
||||
@@ -2326,17 +2331,18 @@ def main() -> int:
|
||||
import json as _cal_json
|
||||
from unittest.mock import patch as _patch_mismatch
|
||||
|
||||
import orisys_studio.ui.cal_panel as _cal_panel_module
|
||||
import orisys_studio.calibration_service as _cal_service_module
|
||||
from PySide6.QtWidgets import QMessageBox as _QMessageBox
|
||||
|
||||
import orisys_studio.ui.cal_panel as _cal_panel_module
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
build_force_calibration_v2 as _build_v2b,
|
||||
)
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
write_force_calibration as _write_cal,
|
||||
)
|
||||
from orisys_app_shared.force.calibration_session import (
|
||||
build_session_document as _build_session_doc,
|
||||
)
|
||||
from PySide6.QtWidgets import QMessageBox as _QMessageBox
|
||||
|
||||
record_dir = APP_ROOT / "outputs" / "_cal_record_check"
|
||||
if record_dir.exists():
|
||||
@@ -3065,7 +3071,6 @@ def main() -> int:
|
||||
recorder.on_packet(_fake_packet(index))
|
||||
recorder.stop()
|
||||
|
||||
from orisys_studio.replay import ReplayEngine
|
||||
|
||||
# The replay lives in the main window: one engine, one progress bar, and the
|
||||
# charts read the recording itself.
|
||||
@@ -3314,7 +3319,6 @@ def main() -> int:
|
||||
# Long-history browsing: an interval read is bounded by the interval, and the
|
||||
# whole-session envelope is a band rather than every row.
|
||||
from orisys_app_shared.force.source import RecordingCurveSource
|
||||
from orisys_app_shared.record.index import RecordingIndex
|
||||
from orisys_app_shared.record.playback import RecordingReader as _RecordingReader
|
||||
|
||||
reader = _RecordingReader(recording, max_rows=10)
|
||||
|
||||
@@ -38,9 +38,7 @@ os.chdir(APP_ROOT)
|
||||
def _replay_data(window, app) -> Path:
|
||||
"""Synthesize one short recording (with visible frames) and open it."""
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.record import ExperimentRecorder, RecordConfig
|
||||
|
||||
from ui_check import _fake_packet
|
||||
|
||||
root = APP_ROOT / "outputs" / "_snapshot_replay"
|
||||
@@ -121,7 +119,6 @@ def _run_scene(window, app, scene: str):
|
||||
window._run_timer.stop()
|
||||
|
||||
from orisys_app_shared.record.experiment import RecordStats
|
||||
|
||||
from orisys_studio.ui import record_status
|
||||
|
||||
directory = "20240101T101500Z_session0001_a1b2c3d4e5f6"
|
||||
@@ -160,7 +157,6 @@ def _flow_scene(window, app, *, frames: int = 240) -> None:
|
||||
|
||||
import numpy as np
|
||||
from orisys_app_shared.acquisition.sdk_worker import SdkPacket
|
||||
|
||||
from ui_check import _StuckWorker
|
||||
|
||||
stuck = _StuckWorker()
|
||||
@@ -304,14 +300,12 @@ def _demo_data(window) -> None:
|
||||
|
||||
# Synthetic outward flow so the branded canvas and flow ramp are reviewable.
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.acquisition.arrows import (
|
||||
draw_arrow_display,
|
||||
flow_colormap,
|
||||
hex_to_bgr,
|
||||
)
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
from orisys_studio.app_window import _bgr_to_qimage
|
||||
|
||||
size = 320
|
||||
@@ -393,11 +387,10 @@ def main() -> int:
|
||||
|
||||
panels = {part.strip().lower() for part in args.panels.split(",") if part.strip()}
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import set_locale
|
||||
from orisys_app_shared.theme import build_qss
|
||||
from orisys_studio.app_window import StudioMainWindow
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
set_locale(args.lang)
|
||||
|
||||
Reference in New Issue
Block a user