Files
orisys-app/apps/studio/tests/test_scene3d_adapters.py
T
ykcheungab 7d1fca4c93 fix(studio-3d): 字段级来源与失效状态 —— 旧光流/算法失败不再冒充实时测量
- What:
  1. **采集侧声明每个派生数组的归属**(`acquisition/models.py`):`SdkPacket` 新增
     `flow_valid/flow_frame_id/depth_valid/depth_frame_id/forces_valid`(`None` = 未声明)。两个真实
     生产者都填写:`finger_worker`/`sdk_worker` 逐帧记录"该数组实际算出来的 frame_id",链失败时
     `*_valid=False` 并把上一次成功帧号带出来。光流与形变链分开判定(力读取失败不等于光流失败),
     但算法链失败时本帧的一切派生量都不可声明。
  2. **adapter 按声明收数据**(`scene3d/adapters.py`):`_field_validity()` 三态——未声明 → 拒绝并报
     `field_unverified`(没声明就不算数);声明为假 → `field_stale`(把 `invalid_reason` 带进诊断);
    声明帧号不是本帧 → `field_stale`(两个帧号都写进 details)。`_intersect_device_mask` 换成
     `_device_mask`:设备 mask 形状不符**拒绝该帧**(`field_domain_mismatch`,两个形状都写进诊断),
     既不拉伸也不静默忽略。发布的帧带 `computed_frame_id`,身份一路带到显示侧。
  3. **契约**(`scene3d/contracts.py`):`SceneState` 加 `"stale"`;诊断码加 `field_stale`/`field_unverified`
     (warning);`SurfaceFieldFrame.computed_frame_id` 与 `RenderFrame.computed_frame_id`(由 composer
     原样透传);`validate_surface_frame` 校验帧号非负且不晚于所属帧。新增 locale:
     `scene3d.state_stale`(中英各一条)。
  4. **协调器不再"边失败边报 live"**(`scene3d/coordinator.py`):新增 `_stale_since` 宽限状态机与
     `note_field_failure()`——字段被拒、帧被判 invalid、compose 抛错、或合成结果的 `computed_frame_id`
     落后于本帧,都立即把状态改为 `stale` 并保留最后一帧;宽限(`stale_grace_s`,默认 1.0 s)到期后
     回参考模型并转 `no_data`;收到好帧即回 `live` 并清掉宽限。顺带修掉"等待首帧时每个 tick 都把参考
     模型重新 present 一次"(`_rest_presented`,16 ms 一次的无谓上传)。
  5. **诊断真正到达界面**(`scene3d/view.py`):`submit_packet()` 在 adapter 拒绝时把
     `adapter.last_diagnostic` 交给 `note_field_failure()`;`tick()` 把协调器诊断的变化经已有
     `diagnosticRaised` 冒泡(协调器自身仍然零 Qt 依赖)。新增 `_snapshot_diagnostic()` 与 `_adapter`
     引用(后续"按能力禁用控件"也要问它)。
- Why: 审查报告 P1/R2:`FingerFrameAdapter.snapshot()` 从不读 `valid`/`stale_forces`/`invalid_reason`,
  只看到有限的 `flow` 就当作新数据上色(复现:`valid=False, stale_forces=True` + 有限 flow → 仍有有效
  着色点、diagnostic=None);compose 抛错只写 `last_diagnostic`,画面与 `state` 保持不变;
  `snapshot()` 返回 None 时诊断无人读取;mask 域不符时静默忽略会扩大可信范围。力读数失败不该机械地
  判死三维,但算法失败也不能因为旧数组是有限值就当成新数据——所以按**字段级来源**处理,并让失效有
  明确的、可见的状态而不是继续保持 live。
- Verification:
  · `test_scene3d_adapters.py` 23 通过(+6 新增):stale flow 被拒且带原因、未声明 flow → `field_unverified`、
  帧号不符 → `field_stale`(details 两个帧号)、新帧记录 `computed_frame_id`、box 侧"形变链失败仍用光流"、
  box 全部字段不可声明时拒绝;mask 域不符由"被忽略"改为"拒绝该帧"(原断言已改)。
  · `test_scene3d_coordinator.py` 30 通过(+6 新增):落后帧号的合成结果永不上屏且状态 stale、
  字段被拒 → 宽限内保留画面/宽限后回参考模型/好帧回 live、compose 抛错不再保持 live、invalid 帧也计入
  宽限。
  · `test_scene3d_view.py`(+3 新增,offscreen 可跑):adapter 拒绝 → 协调器 stale 且诊断经
  `diagnosticRaised` 回到面板、`scene3d.state_stale` 不是裸键;未声明字段 → `field_unverified`;
  新鲜字段照常上屏回到 live。
  · 全量:apps/studio 228 项 0 失败 14 跳过;shared 301 项 0 失败 1 跳过(`--junitxml` 计数)。
- Not in this change: `docs/architecture/studio-3d-contracts.pyi` 与新契约的同步放在阶段 6 的文档提交里
  一起做(本阶段契约改动较多,避免每提交改一次文档);设备几何身份(R3)、资产加载请求号(R7)、
  二维后端统一、限速与真实统计均未动;真机上"链失败时 VRAW 是否也过期"这一条仍需实测(若证明链失败
  不影响 VRAW,`flow_valid` 放宽为只看读取结果是一行改动,已写进计划的风险项)。
2026-09-14 21:03:07 +08:00

500 lines
19 KiB
Python

"""Stage 3 acceptance for the frame adapters.
The adapters are the boundary between the acquisition thread's arrays and the
scene, so the tests are about *what they publish and what they refuse*: a field in
the right grid, read-only, with the right unit and quality — and nothing at all when
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 (
DEPTH_RESPONSE,
FLOW_MAGNITUDE,
BoxFrameAdapter,
FingerFrameAdapter,
flow_magnitude,
)
@dataclass
class FakePacket:
"""The parts of ``SdkPacket`` the adapters look at.
The provenance flags default to *stated fresh*: that is what both production
workers now write, and it keeps the older tests about grids and units about grids
and units. A test that wants a stale or unstated field says so — that is the
behaviour under test.
"""
flow: Optional[np.ndarray] = None
depth: Optional[np.ndarray] = None
valid_mask: Optional[np.ndarray] = 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
def _identity(generation: int = 1) -> FrameIdentity:
return FrameIdentity("live", 1, 1, 0.1, generation=generation)
@pytest.fixture
def scene():
return synthetic.poc_scene()
# ------------------------------------------------------------------- flow helper
def test_the_flow_magnitude_handles_both_shapes():
flow = np.zeros((2, 3, 2), dtype=np.float32)
flow[..., 0] = 3.0
flow[..., 1] = 4.0
assert np.allclose(flow_magnitude(FakePacket(flow=flow)), 5.0)
planar = np.arange(4, dtype=np.float32).reshape(2, 2)
assert np.array_equal(flow_magnitude(FakePacket(flow=planar)), planar)
assert flow_magnitude(FakePacket()) is None
assert flow_magnitude(FakePacket(flow=np.zeros((2, 2, 3), dtype=np.float32))) is None
# ----------------------------------------------------------------- finger adapter
def test_the_finger_adapter_publishes_a_field_already_in_the_domain(scene):
adapter = FingerFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
flow = np.zeros((*scene.shape_hw, 2), dtype=np.float32)
flow[0, 0, 0] = 3.0
frame = adapter.snapshot(FakePacket(flow=flow), runtime=None, identity=_identity())
assert frame is not None
assert validate_surface_frame(frame) == ()
assert frame.geometry_id == scene.geometry_id
assert frame.field_domain_id == scene.field_domain_id
assert frame.scalar_descriptor == FLOW_MAGNITUDE
assert frame.scalar_descriptor.unit == "px"
assert frame.scalar_descriptor.quality == "approximate"
assert np.asarray(frame.scalar)[0, 0] == 3.0
assert np.asarray(frame.valid).all()
assert not np.asarray(frame.scalar).flags["WRITEABLE"]
assert frame.displacement_sensor_m is None, (
"an uncalibrated displacement must not be invented"
)
def test_the_finger_adapter_remaps_through_the_unroll_map():
map_x = np.array([[0, 1, 2], [0, 1, 2], [0, 1, 2]], dtype=np.float32)
map_y = np.array([[2, 2, 2], [1, 1, 1], [0, 0, 0]], dtype=np.float32)
adapter = FingerFrameAdapter(
geometry_id="g", field_domain_id="d", shape_hw=(3, 3),
map_x=map_x, map_y=map_y,
)
flow = np.zeros((3, 3, 2), dtype=np.float32)
flow[..., 0] = 2.0
frame = adapter.snapshot(FakePacket(flow=flow), runtime=None, identity=_identity())
assert frame is not None
assert np.allclose(np.asarray(frame.scalar), 2.0)
assert np.asarray(frame.valid).all()
def test_a_remap_with_a_hole_is_invalid_there_and_does_not_read_as_zero():
map_x = np.array([[0, -1], [0, 0]], dtype=np.float32)
map_y = np.array([[0, -1], [0, 0]], dtype=np.float32)
adapter = FingerFrameAdapter(
geometry_id="g", field_domain_id="d", shape_hw=(2, 2),
map_x=map_x, map_y=map_y,
)
flow = np.full((2, 2, 2), 1.0, dtype=np.float32)
frame = adapter.snapshot(FakePacket(flow=flow), runtime=None, identity=_identity())
assert frame is not None
assert np.asarray(frame.valid).tolist() == [[True, False], [True, True]]
assert np.isnan(np.asarray(frame.scalar)[0, 1])
def test_the_finger_adapter_refuses_a_grid_it_cannot_place(scene):
"""No remap and the wrong grid: say which shapes were seen, and publish nothing."""
adapter = FingerFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
flow = np.zeros((40, 30, 2), dtype=np.float32)
frame = adapter.snapshot(FakePacket(flow=flow), runtime=None, identity=_identity())
assert frame is None
item = adapter.last_diagnostic
assert item is not None and item.code == "field_domain_mismatch"
assert item.details["flow"] == "40x30"
assert item.details["domain"] == f"{scene.shape_hw[0]}x{scene.shape_hw[1]}"
def test_the_finger_adapter_without_flow_says_so(scene):
adapter = FingerFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
assert adapter.snapshot(FakePacket(), runtime=None, identity=_identity()) is None
assert adapter.last_diagnostic is not None
assert adapter.last_diagnostic.code == "field_unavailable"
def test_the_finger_adapter_can_be_built_from_a_domain(scene):
from orisys_app_shared.scene3d.legacy import load_legacy_domain
map_path = _run_map()
if map_path is None:
pytest.skip("the shipped unroll map is not present")
domain = load_legacy_domain(map_path)
adapter = FingerFrameAdapter.from_domain(domain, geometry_id="geom")
assert adapter.shape_hw == domain.shape_hw
assert adapter.field_domain_id == domain.domain_id
assert adapter.map_x is not None and adapter.map_y is not None, (
"the domain must carry the unroll map for the adapter to use"
)
assert domain.source_hw == (288, 512), domain.source_hw
def test_the_shipped_map_cannot_be_applied_to_the_shipped_flow():
"""Why the app must not remap the finger's flow: the map is domain→*source*.
The unroll map is the same size as the UV domain, but its values are pixel
coordinates in the 512x288 source image — not in the 300x280 domain. The live
flow is already computed on the unrolled frame, so feeding it through this map
would be a double remap: it would index the flow at source-pixel coordinates.
This test pins the reason the app passes no map, so a later "improvement" has to
argue with it.
Note the asymmetry that makes the mistake look plausible: in x the map exceeds
the domain's column count (so it would read out of range), while in y it stays
below the row count (so it would silently read the wrong rows). Half of a
double remap crashes and half of it lies.
"""
map_path = _run_map()
if map_path is None:
pytest.skip("the shipped unroll map is not present")
from orisys_app_shared.scene3d.legacy import load_legacy_domain
domain = load_legacy_domain(map_path)
assert domain.map_x is not None and domain.map_y is not None
rows, columns = domain.shape_hw
source_rows, source_columns = domain.source_hw
assert (rows, columns) == (300, 280) and (source_rows, source_columns) == (288, 512)
# Bounded by the source, not by the domain: that is what makes them source pixels.
assert 0.0 <= float(domain.map_x[domain.map_x >= 0].max()) <= source_columns - 1
assert 0.0 <= float(domain.map_y[domain.map_y >= 0].max()) <= source_rows - 1
assert float(domain.map_x.max()) > columns - 1, (
"x would index past the last column of a 280-wide flow field"
)
assert float(domain.map_y.max()) > rows - 1 or source_rows <= rows, (
"y stays inside the 300-row field: the y half of the mistake is silent"
)
def test_the_live_flow_is_already_the_scene_domain():
"""The measurement behind that decision, taken from the shipping config.
``camera.resolution_stitch`` is what ``Sensor`` sizes its flow buffer from and
what the unroll stage outputs, so the flow grid *is* the UV domain. If a future
config changes that, this test fails and the adapter wiring has to be revisited
rather than the numbers quietly not matching.
"""
import json
from pathlib import Path
config = Path(__file__).resolve().parents[1] / "config" / "sensors" / "finger"
map_path = _run_map()
if not (config / "sensor.json").is_file() or map_path is None:
pytest.skip("the finger config and the unroll map are needed")
camera = json.loads((config / "sensor.json").read_text(encoding="utf-8"))["camera"]
resolution = camera["resolution_stitch"]
# Sensor builds `flow` as (resolution_stitch[1], resolution_stitch[0]).
flow_hw = (int(resolution[1]), int(resolution[0]))
from orisys_app_shared.scene3d.legacy import load_legacy_domain
domain = load_legacy_domain(map_path)
assert flow_hw == tuple(domain.shape_hw) == (300, 280)
def test_the_finger_adapter_does_not_colour_outside_the_uv_island(scene):
"""The device mask is what says where the surface is; outside it there is none."""
adapter = FingerFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
flow = np.zeros((*scene.shape_hw, 2), dtype=np.float32)
flow[..., 0] = 1.0
mask = np.zeros(scene.shape_hw, dtype=bool)
mask[1, 1] = True
frame = adapter.snapshot(
FakePacket(flow=flow, valid_mask=mask), runtime=None, identity=_identity()
)
assert frame is not None
valid = np.asarray(frame.valid)
assert valid.sum() == 1 and bool(valid[1, 1])
assert np.asarray(frame.scalar)[0, 0] == 1.0, (
"the scalar is kept as measured; only validity says it is not a measurement"
)
def test_a_device_mask_from_another_grid_refuses_the_frame(scene):
"""Neither stretching it nor ignoring it is honest — so the frame is refused."""
adapter = FingerFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
flow = np.ones((*scene.shape_hw, 2), dtype=np.float32)
wrong = np.zeros((7, 7), dtype=bool)
frame = adapter.snapshot(
FakePacket(flow=flow, valid_mask=wrong), runtime=None, identity=_identity()
)
assert frame is None, "a mask from another grid must not widen the trusted area"
item = adapter.last_diagnostic
assert item is not None and item.code == "field_domain_mismatch"
assert item.details["mask"] == "7x7"
assert item.details["domain"] == f"{scene.shape_hw[0]}x{scene.shape_hw[1]}"
# ------------------------------------------------- field provenance (stage 6, R2)
def _finger(scene) -> FingerFrameAdapter:
return FingerFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
def test_a_stale_flow_is_refused_instead_of_coloured(scene):
"""R2: the array is still finite, but it is not this frame's measurement.
This is the audit's reproduction: a packet with ``valid=False``/``stale_forces``
and a filled ``flow`` used to be coloured as valid data with no diagnostic.
"""
adapter = _finger(scene)
flow = np.full((*scene.shape_hw, 2), 4.0, dtype=np.float32)
frame = adapter.snapshot(
FakePacket(
flow=flow,
flow_valid=False,
invalid_reason="compute: ValueError: boom",
),
runtime=None,
identity=_identity(),
)
assert frame is None, "a failed chain's leftover flow was coloured as current"
item = adapter.last_diagnostic
assert item is not None and item.code == "field_stale"
assert "boom" in item.message
def test_an_unstated_flow_is_unverified_not_fresh(scene):
"""No statement is not the same as "it is mine"."""
adapter = _finger(scene)
flow = np.full((*scene.shape_hw, 2), 1.0, dtype=np.float32)
frame = adapter.snapshot(
FakePacket(flow=flow, flow_valid=None), runtime=None, identity=_identity()
)
assert frame is None
item = adapter.last_diagnostic
assert item is not None and item.code == "field_unverified"
def test_flow_computed_for_another_frame_is_refused(scene):
adapter = _finger(scene)
flow = np.full((*scene.shape_hw, 2), 2.0, dtype=np.float32)
frame = adapter.snapshot(
FakePacket(flow=flow, flow_frame_id=3),
runtime=None,
identity=_identity(), # frame_id == 1
)
assert frame is None
item = adapter.last_diagnostic
assert item is not None and item.code == "field_stale"
assert item.details["computed"] == "3" and item.details["frame"] == "1"
def test_a_fresh_flow_records_the_frame_it_belongs_to(scene):
"""The identity travels with the frame, so the display side can re-check it."""
adapter = _finger(scene)
flow = np.full((*scene.shape_hw, 2), 3.0, dtype=np.float32)
frame = adapter.snapshot(
FakePacket(flow=flow, flow_frame_id=1), runtime=None, identity=_identity()
)
assert frame is not None
assert frame.computed_frame_id == 1
assert validate_surface_frame(frame) == ()
def test_the_box_adapter_skips_a_stale_depth_and_uses_the_flow(scene):
"""A failed deformation chain does not have to cost the flow as well."""
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
depth = np.full(scene.shape_hw, 0.5, dtype=np.float32)
flow = np.zeros((*scene.shape_hw, 2), dtype=np.float32)
flow[..., 0] = 1.0
frame = adapter.snapshot(
FakePacket(depth=depth, depth_valid=False, flow=flow),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is not None and frame.scalar_descriptor == FLOW_MAGNITUDE
assert np.allclose(np.asarray(frame.scalar), 1.0)
def test_the_box_adapter_refuses_when_no_field_is_its_own(scene):
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
depth = np.full(scene.shape_hw, 0.5, dtype=np.float32)
frame = adapter.snapshot(
FakePacket(depth=depth, depth_valid=False),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is None
item = adapter.last_diagnostic
assert item is not None and item.code == "field_stale"
def _run_map():
from pathlib import Path
root = Path(__file__).resolve().parents[1] / "config" / "sensors" / "finger" / "assets"
for name in ("30fps-0703", "visualization"):
path = root / name / "unroll_map_uv_s0.4.npz"
if path.is_file():
return path
for path in sorted(root.glob("*/unroll_map_uv*.npz")):
return path
return None
# -------------------------------------------------------------------- box adapter
def test_the_box_adapter_prefers_the_depth_response(scene):
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
depth = np.full(scene.shape_hw, 0.25, dtype=np.float32)
flow = np.ones((*scene.shape_hw, 2), dtype=np.float32)
frame = adapter.snapshot(
FakePacket(depth=depth, flow=flow),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is not None
assert frame.scalar_descriptor == DEPTH_RESPONSE
assert frame.scalar_descriptor.unit == "1", "a deformation response is not a length"
assert np.allclose(np.asarray(frame.scalar), 0.25)
def test_the_box_adapter_falls_back_to_the_flow_magnitude(scene):
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
flow = np.zeros((*scene.shape_hw, 2), dtype=np.float32)
flow[..., 0] = 1.0
frame = adapter.snapshot(
FakePacket(flow=flow),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is not None and frame.scalar_descriptor == FLOW_MAGNITUDE
assert np.allclose(np.asarray(frame.scalar), 1.0)
def test_the_device_mask_wins_over_the_finiteness_test(scene):
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
depth = np.ones(scene.shape_hw, dtype=np.float32)
mask = np.ones(scene.shape_hw, dtype=bool)
mask[0, 0] = False
frame = adapter.snapshot(
FakePacket(depth=depth, valid_mask=mask),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is not None
assert not bool(np.asarray(frame.valid)[0, 0])
assert np.asarray(frame.valid).sum() == mask.sum()
def test_the_box_adapter_refuses_a_device_with_another_domain(scene):
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
frame = adapter.snapshot(
FakePacket(depth=np.ones(scene.shape_hw, dtype=np.float32)),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, (9, 9)),
)
assert frame is None
assert adapter.last_diagnostic is not None
assert adapter.last_diagnostic.code == "field_domain_mismatch"
def test_the_box_adapter_with_nothing_usable_says_what_it_saw(scene):
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
frame = adapter.snapshot(
FakePacket(depth=np.ones((5, 5), dtype=np.float32)),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is None
assert adapter.last_diagnostic is not None
assert adapter.last_diagnostic.details["depth"] == "5x5"
def test_a_published_frame_must_not_share_memory_with_the_packet(scene):
"""The producer keeps writing its arrays; the frame must not alias them."""
adapter = BoxFrameAdapter(
geometry_id=scene.geometry_id,
field_domain_id=scene.field_domain_id,
shape_hw=scene.shape_hw,
)
depth = np.zeros(scene.shape_hw, dtype=np.float32)
frame = adapter.snapshot(
FakePacket(depth=depth),
_identity(),
ExpectedGeometry("box", "rev", scene.geometry_id, scene.field_domain_id, scene.shape_hw),
)
assert frame is not None
assert not frame.scalar.flags["WRITEABLE"]
with pytest.raises(ValueError):
np.asarray(frame.scalar)[0, 0] = 5.0