三维显示与二维共用深度色标,配置名改为 Finger
箭头阈值写入设置;去掉网格告警。三维色标改为立刻采用二维量程(不再 P5/P95 缓变)。选择器显示 Finger。已用 pane UI、shared-scale 与 catalog 单测验证。
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"profile_id": "orisys.finger.r01",
|
"profile_id": "orisys.finger.r01",
|
||||||
"display_name": "Finger R01",
|
"display_name": "Finger",
|
||||||
"product_family": "finger",
|
"product_family": "finger",
|
||||||
"processing_backend": "uv_unroll",
|
"processing_backend": "uv_unroll",
|
||||||
"sensor_json": "sensor.json",
|
"sensor_json": "sensor.json",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ same rows.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `schema_version` | `1` | The SDK's current descriptor schema (`CURRENT_DESCRIPTOR_SCHEMA`). |
|
| `schema_version` | `1` | The SDK's current descriptor schema (`CURRENT_DESCRIPTOR_SCHEMA`). |
|
||||||
| `profile_id` | `orisys.finger.r01` | The processing profile this folder is: finger design revision `r01`, whose unroll map and camera scale the configuration pins. Never a product-family name. |
|
| `profile_id` | `orisys.finger.r01` | The processing profile this folder is: finger design revision `r01`, whose unroll map and camera scale the configuration pins. Never a product-family name. |
|
||||||
| `display_name` | `Finger R01` | Shown in the picker. Excluded from the processing fingerprint. |
|
| `display_name` | `Finger` | Shown in the picker. Excluded from the processing fingerprint. |
|
||||||
| `product_family` | `finger` | What the operator calls it. `finger-mems` is a different family on a different pipeline. |
|
| `product_family` | `finger` | What the operator calls it. `finger-mems` is a different family on a different pipeline. |
|
||||||
| `processing_backend` | `uv_unroll` | The pipeline the finger actually runs: `FingerRuntime` builds a UV domain from the export's unroll map, then flow and force. |
|
| `processing_backend` | `uv_unroll` | The pipeline the finger actually runs: `FingerRuntime` builds a UV domain from the export's unroll map, then flow and force. |
|
||||||
| `sensor_json` | `sensor.json` | The configuration, still the only place camera, tracker and contact settings live. |
|
| `sensor_json` | `sensor.json` | The configuration, still the only place camera, tracker and contact settings live. |
|
||||||
|
|||||||
@@ -263,6 +263,24 @@ def shader_source(profile: Profile, program: str) -> tuple[str, str]:
|
|||||||
return header + vertex, header + body
|
return header + vertex, header + body
|
||||||
|
|
||||||
|
|
||||||
|
def _finite_minmax(scalar, valid) -> tuple[float, float] | None:
|
||||||
|
"""Finite minimum and maximum — the same interval ``FrameView.frame_depth_span`` uses."""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
values = np.asarray(scalar, dtype=np.float64).reshape(-1)
|
||||||
|
mask = np.asarray(valid, dtype=bool).reshape(-1)
|
||||||
|
if values.size != mask.size:
|
||||||
|
return None
|
||||||
|
usable = values[mask & np.isfinite(values)]
|
||||||
|
if usable.size == 0:
|
||||||
|
return None
|
||||||
|
lo = float(np.min(usable))
|
||||||
|
hi = float(np.max(usable))
|
||||||
|
if hi <= lo:
|
||||||
|
hi = lo + 1.0
|
||||||
|
return lo, hi
|
||||||
|
|
||||||
|
|
||||||
def _valid_scalar_range(
|
def _valid_scalar_range(
|
||||||
scalar, valid, *, signed: bool = False
|
scalar, valid, *, signed: bool = False
|
||||||
) -> tuple[float, float] | None:
|
) -> tuple[float, float] | None:
|
||||||
@@ -379,6 +397,10 @@ def _field_scalar_range(
|
|||||||
hi = float(shared_depth_scale[1])
|
hi = float(shared_depth_scale[1])
|
||||||
if hi > lo and not (math.isnan(lo) or math.isnan(hi)):
|
if hi > lo and not (math.isnan(lo) or math.isnan(hi)):
|
||||||
return lo, hi
|
return lo, hi
|
||||||
|
if quantity == "depth_response":
|
||||||
|
# Same interval as the 2D canvas (finite min/max). A P5/P95 blend here
|
||||||
|
# made the 3D colour bar crawl toward a different number than the 2D label.
|
||||||
|
return _finite_minmax(field.scalar, field.valid)
|
||||||
measured = _valid_scalar_range(
|
measured = _valid_scalar_range(
|
||||||
field.scalar,
|
field.scalar,
|
||||||
field.valid,
|
field.valid,
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ class ThreeDPanel(QFrame):
|
|||||||
#: prefs (or the dataclass defaults) so a colour-scale click cannot reset them.
|
#: prefs (or the dataclass defaults) so a colour-scale click cannot reset them.
|
||||||
self._flow_grid_spacing = 10
|
self._flow_grid_spacing = 10
|
||||||
self._max_arrows = 400
|
self._max_arrows = 400
|
||||||
|
#: Depth colour span published by the 2D canvas. Shown here immediately so the
|
||||||
|
#: two legends do not wait on the next GL paint (which is rate-limited).
|
||||||
|
self._shared_depth_span: tuple[float, float] | None = None
|
||||||
|
|
||||||
root = QVBoxLayout(self)
|
root = QVBoxLayout(self)
|
||||||
root.setContentsMargins(8, 6, 8, 6)
|
root.setContentsMargins(8, 6, 8, 6)
|
||||||
@@ -490,7 +493,16 @@ class ThreeDPanel(QFrame):
|
|||||||
if quantity.startswith("scene3d."):
|
if quantity.startswith("scene3d."):
|
||||||
quantity = descriptor.quantity
|
quantity = descriptor.quantity
|
||||||
quality = t(f"scene3d.quality_{descriptor.quality}")
|
quality = t(f"scene3d.quality_{descriptor.quality}")
|
||||||
if effective is None or effective.scale_mode == "":
|
shared = (
|
||||||
|
self._shared_depth_span
|
||||||
|
if str(descriptor.quantity) == "depth_response"
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if shared is not None:
|
||||||
|
lo, hi = shared
|
||||||
|
elif effective is not None and effective.scale_mode:
|
||||||
|
lo, hi = effective.scale_lower, effective.scale_upper
|
||||||
|
else:
|
||||||
self._set_legend(
|
self._set_legend(
|
||||||
t(
|
t(
|
||||||
"scene3d.legend",
|
"scene3d.legend",
|
||||||
@@ -503,7 +515,7 @@ class ThreeDPanel(QFrame):
|
|||||||
return
|
return
|
||||||
mode = t(
|
mode = t(
|
||||||
"scene3d.legend_auto"
|
"scene3d.legend_auto"
|
||||||
if effective.scale_mode == "automatic"
|
if (effective is None or effective.scale_mode != "fixed")
|
||||||
else "scene3d.legend_fixed"
|
else "scene3d.legend_fixed"
|
||||||
)
|
)
|
||||||
self._set_legend(
|
self._set_legend(
|
||||||
@@ -512,15 +524,25 @@ class ThreeDPanel(QFrame):
|
|||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
unit=descriptor.unit,
|
unit=descriptor.unit,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
# Three significant digits: the footer is one line, and "0.033 – 0.90"
|
lower=self._format_bound(lo),
|
||||||
# says as much as "0.03337 – 0.8993" while leaving room for the rest.
|
upper=self._format_bound(hi),
|
||||||
lower=f"{effective.scale_lower:.3g}",
|
|
||||||
upper=f"{effective.scale_upper:.3g}",
|
|
||||||
quality=quality,
|
quality=quality,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.legend_label.setVisible(self._show_diagnostics)
|
self.legend_label.setVisible(self._show_diagnostics)
|
||||||
|
|
||||||
|
def set_shared_depth_scale(self, span: tuple[float, float] | None) -> None:
|
||||||
|
"""Adopt the 2D canvas's depth colour span and refresh the legend now."""
|
||||||
|
if span is None:
|
||||||
|
self._shared_depth_span = None
|
||||||
|
else:
|
||||||
|
self._shared_depth_span = (float(span[0]), float(span[1]))
|
||||||
|
self._render_scale_bounds()
|
||||||
|
self._render_legend()
|
||||||
|
|
||||||
|
def _format_bound(self, value: float) -> str:
|
||||||
|
return f"{float(value):.3f}"
|
||||||
|
|
||||||
def _render_scale_bounds(self) -> None:
|
def _render_scale_bounds(self) -> None:
|
||||||
"""Show only the numeric bounds needed to read the color ramp."""
|
"""Show only the numeric bounds needed to read the color ramp."""
|
||||||
descriptor = self._descriptor
|
descriptor = self._descriptor
|
||||||
@@ -531,16 +553,20 @@ class ThreeDPanel(QFrame):
|
|||||||
upper = ""
|
upper = ""
|
||||||
mode = str(self.scale_combo.currentData() or "fixed")
|
mode = str(self.scale_combo.currentData() or "fixed")
|
||||||
effective = self._effective_scale
|
effective = self._effective_scale
|
||||||
if visible and mode == "fixed":
|
quantity = "" if descriptor is None else str(descriptor.quantity)
|
||||||
lower = f"{self.lower_spin.value():.3g}"
|
if visible and quantity == "depth_response" and self._shared_depth_span is not None:
|
||||||
upper = f"{self.upper_spin.value():.3g}"
|
lower = self._format_bound(self._shared_depth_span[0])
|
||||||
|
upper = self._format_bound(self._shared_depth_span[1])
|
||||||
|
elif visible and mode == "fixed":
|
||||||
|
lower = self._format_bound(self.lower_spin.value())
|
||||||
|
upper = self._format_bound(self.upper_spin.value())
|
||||||
elif (
|
elif (
|
||||||
visible
|
visible
|
||||||
and effective is not None
|
and effective is not None
|
||||||
and effective.scale_mode == "automatic"
|
and effective.scale_mode == "automatic"
|
||||||
):
|
):
|
||||||
lower = f"{effective.scale_lower:.3g}"
|
lower = self._format_bound(effective.scale_lower)
|
||||||
upper = f"{effective.scale_upper:.3g}"
|
upper = self._format_bound(effective.scale_upper)
|
||||||
self.scale_lower_label.setText(lower)
|
self.scale_lower_label.setText(lower)
|
||||||
self.scale_upper_label.setText(upper)
|
self.scale_upper_label.setText(upper)
|
||||||
self.scale_lower_label.setVisible(visible)
|
self.scale_lower_label.setVisible(visible)
|
||||||
|
|||||||
@@ -317,6 +317,8 @@ class Scene3DView(QWidget):
|
|||||||
"""Colour depth with the 2D canvas's latched ``(lo, hi)``."""
|
"""Colour depth with the 2D canvas's latched ``(lo, hi)``."""
|
||||||
if self._widget is not None:
|
if self._widget is not None:
|
||||||
self._widget.set_shared_depth_scale(span)
|
self._widget.set_shared_depth_scale(span)
|
||||||
|
if self._panel is not None:
|
||||||
|
self._panel.set_shared_depth_scale(span)
|
||||||
|
|
||||||
def set_theme(self, theme: str) -> None:
|
def set_theme(self, theme: str) -> None:
|
||||||
"""Adopt the application theme's colour ramp.
|
"""Adopt the application theme's colour ramp.
|
||||||
|
|||||||
@@ -129,6 +129,21 @@ def test_arrow_threshold_round_trips_and_feeds_the_shared_policy(panel):
|
|||||||
assert policy_for_settings(again).flow_on_px == pytest.approx(0.9)
|
assert policy_for_settings(again).flow_on_px == pytest.approx(0.9)
|
||||||
|
|
||||||
|
|
||||||
|
def test_depth_legend_follows_the_shared_2d_span_immediately(panel):
|
||||||
|
"""The 3D colour-bar numbers are the 2D span, not a lagged GL automatic range."""
|
||||||
|
from orisys_app_shared.scene3d.contracts import FieldDescriptor, RenderStats
|
||||||
|
|
||||||
|
panel.set_descriptor(
|
||||||
|
FieldDescriptor("depth_response", "1", "approximate", False)
|
||||||
|
)
|
||||||
|
panel.set_render_stats(
|
||||||
|
RenderStats(scale_lower=0.0, scale_upper=0.0489, scale_mode="automatic")
|
||||||
|
)
|
||||||
|
panel.set_shared_depth_scale((0.0, 0.029))
|
||||||
|
assert panel.scale_lower_label.text() == "0.000"
|
||||||
|
assert panel.scale_upper_label.text() == "0.029"
|
||||||
|
|
||||||
|
|
||||||
def test_source_kind_analysis_uses_its_own_label(panel):
|
def test_source_kind_analysis_uses_its_own_label(panel):
|
||||||
"""A stored result is not labelled as live acquisition."""
|
"""A stored result is not labelled as live acquisition."""
|
||||||
panel.set_diagnostics_visible(True)
|
panel.set_diagnostics_visible(True)
|
||||||
@@ -241,8 +256,8 @@ def test_the_diagnostics_switch_governs_what_the_pane_explains(panel):
|
|||||||
)
|
)
|
||||||
panel.set_diagnostics_visible(False)
|
panel.set_diagnostics_visible(False)
|
||||||
assert not panel.scale_bar.isHidden()
|
assert not panel.scale_bar.isHidden()
|
||||||
assert panel.scale_lower_label.text() == "0.25"
|
assert panel.scale_lower_label.text() == "0.250"
|
||||||
assert panel.scale_upper_label.text() == "2.5"
|
assert panel.scale_upper_label.text() == "2.500"
|
||||||
assert not panel.scale_lower_label.isHidden()
|
assert not panel.scale_lower_label.isHidden()
|
||||||
assert not panel.scale_upper_label.isHidden()
|
assert not panel.scale_upper_label.isHidden()
|
||||||
assert panel.legend_label.isHidden()
|
assert panel.legend_label.isHidden()
|
||||||
|
|||||||
@@ -1018,7 +1018,7 @@ def test_the_legend_shows_the_range_the_shader_actually_used(qt_app):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
legend = panel.legend_label.text()
|
legend = panel.legend_label.text()
|
||||||
assert "0.25" in legend and "7.5" in legend, legend
|
assert "0.250" in legend and "7.500" in legend, legend
|
||||||
assert t("scene3d.legend_auto") in legend, legend
|
assert t("scene3d.legend_auto") in legend, legend
|
||||||
assert panel.scale_bar.isVisible() or panel.scale_bar.parent() is not None
|
assert panel.scale_bar.isVisible() or panel.scale_bar.parent() is not None
|
||||||
assert "flow_magnitude" not in legend
|
assert "flow_magnitude" not in legend
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ EXPECTED_SHIPPED: dict[str, tuple[str, str, str, bool, str]] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#: What an operator reads in the picker, one row per shipped bundle: the bundle's name, and
|
#: What an operator reads in the picker, one row per shipped bundle: the bundle's name, and
|
||||||
#: nothing else. ``finger`` declares ``Finger R01`` in its descriptor; the other three have no
|
#: nothing else. ``finger`` declares ``Finger`` in its descriptor; the other three have no
|
||||||
#: descriptor and are named by the folder they live in, as the SDK's built-in table words it.
|
#: descriptor and are named by the folder they live in, as the SDK's built-in table words it.
|
||||||
#: The origin, the calibration selection and the SDK's ``degraded`` verdict are not rows — they
|
#: The origin, the calibration selection and the SDK's ``degraded`` verdict are not rows — they
|
||||||
#: are read in the dialog's details surface, which sits under the selector.
|
#: are read in the dialog's details surface, which sits under the selector.
|
||||||
EXPECTED_ROWS: dict[str, str] = {
|
EXPECTED_ROWS: dict[str, str] = {
|
||||||
"box": "Box",
|
"box": "Box",
|
||||||
"finger": "Finger R01",
|
"finger": "Finger",
|
||||||
"finger-mems": "Finger (MEMS)",
|
"finger-mems": "Finger (MEMS)",
|
||||||
"mini": "Mini",
|
"mini": "Mini",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,10 @@ def test_depth_without_a_shared_span_still_measures_the_field() -> None:
|
|||||||
field = _field("depth_response", np.linspace(0.0, 9.0, 16).reshape(4, 4))
|
field = _field("depth_response", np.linspace(0.0, 9.0, 16).reshape(4, 4))
|
||||||
span = _field_scalar_range(
|
span = _field_scalar_range(
|
||||||
field,
|
field,
|
||||||
previous=None,
|
previous=(0.0, 2.0),
|
||||||
shared_depth_scale=None,
|
shared_depth_scale=None,
|
||||||
)
|
)
|
||||||
assert span is not None
|
assert span == (0.0, 9.0)
|
||||||
assert span[0] == 0.0
|
|
||||||
assert span[1] > 1.0
|
|
||||||
|
|
||||||
|
|
||||||
def test_a_degenerate_shared_span_falls_back_to_the_measurement() -> None:
|
def test_a_degenerate_shared_span_falls_back_to_the_measurement() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user