feat(lidar): add dataset gateway boundary
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { GlassSurface, StatusBadge } from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
fetchDatasetGatewayCatalog,
|
||||
type DatasetGatewayCatalog,
|
||||
} from "../core/lidar/datasetGateway";
|
||||
|
||||
const representationLabels: Record<string, string> = {
|
||||
"native-scan": "Один оборот / скан",
|
||||
"normalized-scan": "Deskew + bounded cleanup",
|
||||
"rolling-local-map": "Pose + TTL + voxel map",
|
||||
};
|
||||
|
||||
export function DatasetGatewayPanel() {
|
||||
const [catalog, setCatalog] = useState<DatasetGatewayCatalog | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
void fetchDatasetGatewayCatalog({ signal: controller.signal })
|
||||
.then((value) => {
|
||||
if (!controller.signal.aborted) setCatalog(value);
|
||||
})
|
||||
.catch((loadError: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setError(
|
||||
loadError instanceof Error
|
||||
? loadError.message
|
||||
: "Dataset Gateway недоступен",
|
||||
);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<GlassSurface className="dataset-gateway" padding="lg">
|
||||
<header className="panel-heading dataset-gateway__heading">
|
||||
<div>
|
||||
<span className="section-eyebrow">DATASET GATEWAY · S0</span>
|
||||
<h2>Три разных LiDAR-продукта</h2>
|
||||
<p>
|
||||
Кольцевой одиночный скан, очищенный sensor-frame и накопленная карта
|
||||
больше не считаются одним облаком.
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge
|
||||
tone={error ? "danger" : catalog?.storage.admitted ? "success" : "warning"}
|
||||
>
|
||||
{error
|
||||
? "Gateway недоступен"
|
||||
: catalog?.storage.admitted
|
||||
? "D: допущен"
|
||||
: "Ожидает D:"}
|
||||
</StatusBadge>
|
||||
</header>
|
||||
|
||||
{catalog ? (
|
||||
<>
|
||||
<div className="dataset-gateway__representations">
|
||||
{catalog.representations.map((representation, index) => (
|
||||
<div key={representation.id}>
|
||||
<span>0{index + 1}</span>
|
||||
<strong>
|
||||
{representationLabels[representation.id] ?? representation.title}
|
||||
</strong>
|
||||
<small>{representation.title}</small>
|
||||
<em>
|
||||
{representation.accumulation
|
||||
? "накопление включено явно"
|
||||
: "без накопления"}
|
||||
</em>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="dataset-gateway__grid">
|
||||
<section>
|
||||
<span className="section-eyebrow">ПЕРВЫЙ BASELINE</span>
|
||||
<h3>{catalog.source.displayName}</h3>
|
||||
<p>
|
||||
Один оборот VLS-128 в SemanticKITTI XYZI + point-wise semantic и
|
||||
instance labels. Это то самое разреженное кольцевое облако,
|
||||
которое корректно сравнивать с алгоритмами.
|
||||
</p>
|
||||
<div className="dataset-gateway__facts">
|
||||
<span>{catalog.source.platforms.join(" · ")}</span>
|
||||
<span>{catalog.source.superclasses.length} superclasses</span>
|
||||
<span>val {catalog.source.validationArchiveGb} ГБ</span>
|
||||
<span>{catalog.source.license}</span>
|
||||
</div>
|
||||
</section>
|
||||
<section data-warning="true">
|
||||
<span className="section-eyebrow">ТЕКУЩИЙ DEVICE INPUT</span>
|
||||
<h3>Mapped feed ≠ raw scan</h3>
|
||||
<p>
|
||||
Внешний MQTT содержит vendor-mapped increment после LIO. В нём
|
||||
нет per-point time и line/ring, поэтому из него нельзя честно
|
||||
восстановить один исходный скан или выполнить deskew.
|
||||
</p>
|
||||
<div className="dataset-gateway__facts">
|
||||
<span>Patchwork++: diagnostic only</span>
|
||||
<span>rolling map: возможно</span>
|
||||
<span>raw reconstruction: невозможно</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<footer className="dataset-gateway__footer">
|
||||
<div>
|
||||
<span>Storage gate</span>
|
||||
<strong>{catalog.storage.requiredWindowsRoot}</strong>
|
||||
<small>{catalog.storage.requiredWslRoot}</small>
|
||||
</div>
|
||||
<div>
|
||||
<span>Следующий исполнимый шаг</span>
|
||||
<strong>
|
||||
{catalog.storage.admitted
|
||||
? "Скачать GOOSE validation и импортировать первый кадр"
|
||||
: "Подключить Dataset Root на D: worker"}
|
||||
</strong>
|
||||
<small>Автозагрузка 3.3 ГБ намеренно запрещена</small>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
) : (
|
||||
<p className="dataset-gateway__pending">
|
||||
{error ?? "Читаем входные контракты Dataset Gateway…"}
|
||||
</p>
|
||||
)}
|
||||
</GlassSurface>
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
LidarGroundPointCloud,
|
||||
type LidarGroundViewMode,
|
||||
} from "./LidarGroundPointCloud";
|
||||
import { DatasetGatewayPanel } from "./DatasetGatewayPanel";
|
||||
|
||||
function formatNumber(value: number | null, digits = 1): string {
|
||||
if (value === null) return "—";
|
||||
@@ -220,6 +221,8 @@ export function LidarQualityWorkspace({
|
||||
<span className="workspace-lead__note">Только проверенные replay-артефакты</span>
|
||||
</section>
|
||||
|
||||
<DatasetGatewayPanel />
|
||||
|
||||
{loading && !detail ? (
|
||||
<GlassSurface className="lidar-quality-message" padding="lg">
|
||||
<StatusBadge tone="accent">Проверка evidence</StatusBadge>
|
||||
|
||||
Reference in New Issue
Block a user