feat(lidar): qualify Patchwork++ on GOOSE
This commit is contained in:
@@ -90,25 +90,43 @@ export interface DatasetNativeScanPreview {
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface DatasetGroundMetrics {
|
||||
precision: number;
|
||||
recall: number;
|
||||
f1: number;
|
||||
groundIou: number;
|
||||
accuracy: number;
|
||||
artificialGroundRecall: number;
|
||||
naturalGroundRecall: number;
|
||||
obstacleNonGroundRecall: number;
|
||||
}
|
||||
|
||||
export interface DatasetGroundComparison {
|
||||
sourceId: "goose-3d/v2025-08-22";
|
||||
frameId: string;
|
||||
pointCount: number;
|
||||
currentGround: number[];
|
||||
patchworkGround: number[];
|
||||
patchworkAssigned: number[];
|
||||
groundTruthGround: number[];
|
||||
evaluated: number[];
|
||||
disagreement: number[];
|
||||
currentDisagreement: number[];
|
||||
patchworkDisagreement: number[];
|
||||
metrics: {
|
||||
precision: number;
|
||||
recall: number;
|
||||
f1: number;
|
||||
groundIou: number;
|
||||
accuracy: number;
|
||||
artificialGroundRecall: number;
|
||||
naturalGroundRecall: number;
|
||||
obstacleNonGroundRecall: number;
|
||||
current: DatasetGroundMetrics;
|
||||
patchworkpp: DatasetGroundMetrics;
|
||||
};
|
||||
latencyMs: {
|
||||
current: number;
|
||||
patchworkpp: number;
|
||||
};
|
||||
patchworkAssignedFraction: number;
|
||||
inputProfile: {
|
||||
frameId: "sensor/lidar/vls128_roof";
|
||||
sensorHeightM: number;
|
||||
heightEvidence: "published-dimensioned-schematic";
|
||||
normalizedScanProduced: false;
|
||||
};
|
||||
latencyMs: number;
|
||||
}
|
||||
|
||||
export class DatasetGatewayContractError extends Error {}
|
||||
@@ -500,7 +518,7 @@ export function parseDatasetGroundComparison(
|
||||
): DatasetGroundComparison {
|
||||
const source = record(value, "Ground comparison");
|
||||
if (
|
||||
source.schema_version !== "missioncore.dataset-ground-comparison-preview/v1"
|
||||
source.schema_version !== "missioncore.dataset-ground-comparison-preview/v2"
|
||||
|| source.source_id !== "goose-3d/v2025-08-22"
|
||||
|| source.sampling !== "deterministic-even-index"
|
||||
) {
|
||||
@@ -508,41 +526,135 @@ export function parseDatasetGroundComparison(
|
||||
}
|
||||
const pointCount = number(source.point_count, "point_count");
|
||||
const currentGround = integers(source.current_ground, "current_ground", 1);
|
||||
const patchworkGround = integers(source.patchwork_ground, "patchwork_ground", 1);
|
||||
const patchworkAssigned = integers(
|
||||
source.patchwork_assigned,
|
||||
"patchwork_assigned",
|
||||
1,
|
||||
);
|
||||
const groundTruthGround = integers(
|
||||
source.ground_truth_ground,
|
||||
"ground_truth_ground",
|
||||
1,
|
||||
);
|
||||
const evaluated = integers(source.evaluated, "evaluated", 1);
|
||||
const disagreement = integers(source.disagreement, "disagreement", 1);
|
||||
const currentDisagreement = integers(
|
||||
source.current_disagreement,
|
||||
"current_disagreement",
|
||||
1,
|
||||
);
|
||||
const patchworkDisagreement = integers(
|
||||
source.patchwork_disagreement,
|
||||
"patchwork_disagreement",
|
||||
1,
|
||||
);
|
||||
if (
|
||||
pointCount < 1
|
||||
|| pointCount > 50_000
|
||||
|| currentGround.length !== pointCount
|
||||
|| patchworkGround.length !== pointCount
|
||||
|| patchworkAssigned.length !== pointCount
|
||||
|| groundTruthGround.length !== pointCount
|
||||
|| evaluated.length !== pointCount
|
||||
|| disagreement.length !== pointCount
|
||||
|| currentDisagreement.length !== pointCount
|
||||
|| patchworkDisagreement.length !== pointCount
|
||||
) {
|
||||
throw new DatasetGatewayContractError("Ground comparison arrays не выровнены");
|
||||
}
|
||||
const metrics = record(source.metrics, "metrics");
|
||||
const fraction = (key: string): number => {
|
||||
const value = number(metrics[key], `metrics.${key}`);
|
||||
if (value > 1) {
|
||||
throw new DatasetGatewayContractError(`metrics.${key}: ожидалась доля`);
|
||||
}
|
||||
return value;
|
||||
const parseMetrics = (
|
||||
value: unknown,
|
||||
label: string,
|
||||
): DatasetGroundMetrics => {
|
||||
const providerMetrics = record(value, label);
|
||||
const fraction = (key: string): number => {
|
||||
const result = number(providerMetrics[key], `${label}.${key}`);
|
||||
if (result > 1) {
|
||||
throw new DatasetGatewayContractError(`${label}.${key}: ожидалась доля`);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return {
|
||||
precision: fraction("precision"),
|
||||
recall: fraction("recall"),
|
||||
f1: fraction("f1"),
|
||||
groundIou: fraction("ground_iou"),
|
||||
accuracy: fraction("accuracy"),
|
||||
artificialGroundRecall: fraction("artificial_ground_recall"),
|
||||
naturalGroundRecall: fraction("natural_ground_recall"),
|
||||
obstacleNonGroundRecall: fraction("obstacle_non_ground_recall"),
|
||||
};
|
||||
};
|
||||
const provider = record(source.provider, "provider");
|
||||
const currentMetrics = parseMetrics(metrics.current, "metrics.current");
|
||||
const patchworkMetrics = parseMetrics(
|
||||
metrics.patchworkpp,
|
||||
"metrics.patchworkpp",
|
||||
);
|
||||
const providers = record(source.providers, "providers");
|
||||
const currentProvider = record(providers.current, "providers.current");
|
||||
const patchworkProvider = record(providers.patchworkpp, "providers.patchworkpp");
|
||||
if (
|
||||
provider.provider_id !== "missioncore-local-percentile-ground/v1"
|
||||
|| provider.ground_truth !== false
|
||||
currentProvider.provider_id !== "missioncore-local-percentile-ground/v1"
|
||||
|| currentProvider.ground_truth !== false
|
||||
|| !/^[a-f0-9]{64}$/.test(
|
||||
string(provider.implementation_sha256, "provider.implementation_sha256"),
|
||||
string(
|
||||
currentProvider.implementation_sha256,
|
||||
"providers.current.implementation_sha256",
|
||||
),
|
||||
)
|
||||
|| patchworkProvider.provider_id !== "patchworkpp/v1.4.1"
|
||||
|| patchworkProvider.source_tag !== "v1.4.1"
|
||||
|| patchworkProvider.source_commit
|
||||
!== "3e6903a1d5537a4cc2ace897b0bbb98a92d6014c"
|
||||
|| patchworkProvider.ground_truth !== false
|
||||
|| !/^[a-f0-9]{64}$/.test(
|
||||
string(
|
||||
patchworkProvider.binary_sha256,
|
||||
"providers.patchworkpp.binary_sha256",
|
||||
),
|
||||
)
|
||||
) {
|
||||
throw new DatasetGatewayContractError("Ground comparison provider несовместим");
|
||||
}
|
||||
const latency = record(source.latency_ms, "latency_ms");
|
||||
const inputProfile = record(source.input_profile, "input_profile");
|
||||
const sensorFrame = record(inputProfile.sensor_frame, "input_profile.sensor_frame");
|
||||
const height = record(inputProfile.height, "input_profile.height");
|
||||
const scope = record(inputProfile.scope, "input_profile.scope");
|
||||
const sensorHeightM = number(
|
||||
height.sensor_above_ground_m,
|
||||
"height.sensor_above_ground_m",
|
||||
);
|
||||
if (
|
||||
inputProfile.schema_version !== "missioncore.goose-patchwork-profile/v1"
|
||||
|| inputProfile.source_id !== "goose-3d/v2025-08-22"
|
||||
|| inputProfile.representation !== "native-scan"
|
||||
|| sensorFrame.frame_id !== "sensor/lidar/vls128_roof"
|
||||
|| sensorFrame.handedness !== "right"
|
||||
|| sensorFrame.x !== "forward"
|
||||
|| sensorFrame.y !== "left"
|
||||
|| sensorFrame.z !== "up"
|
||||
|| sensorFrame.one_revolution !== true
|
||||
|| height.base_link_above_ground_m !== 0.64
|
||||
|| height.lidar_above_base_link_m !== 1.6
|
||||
|| sensorHeightM !== 2.24
|
||||
|| height.evidence !== "published-dimensioned-schematic"
|
||||
|| scope.patchworkpp_eligible !== true
|
||||
|| scope.normalized_scan_produced !== false
|
||||
|| scope.complete_vehicle_transform_known !== false
|
||||
|| scope.deskew_claimed !== false
|
||||
) {
|
||||
throw new DatasetGatewayContractError("GOOSE Patchwork++ profile несовместим");
|
||||
}
|
||||
const patchworkAssignedFraction = number(
|
||||
source.patchwork_assigned_fraction,
|
||||
"patchwork_assigned_fraction",
|
||||
);
|
||||
if (patchworkAssignedFraction > 1) {
|
||||
throw new DatasetGatewayContractError(
|
||||
"patchwork_assigned_fraction: ожидалась доля",
|
||||
);
|
||||
}
|
||||
const safety = record(source.safety, "safety");
|
||||
if (
|
||||
safety.qualification_only !== true
|
||||
@@ -555,20 +667,27 @@ export function parseDatasetGroundComparison(
|
||||
frameId: string(source.frame_id, "frame_id", true),
|
||||
pointCount,
|
||||
currentGround,
|
||||
patchworkGround,
|
||||
patchworkAssigned,
|
||||
groundTruthGround,
|
||||
evaluated,
|
||||
disagreement,
|
||||
currentDisagreement,
|
||||
patchworkDisagreement,
|
||||
metrics: {
|
||||
precision: fraction("precision"),
|
||||
recall: fraction("recall"),
|
||||
f1: fraction("f1"),
|
||||
groundIou: fraction("ground_iou"),
|
||||
accuracy: fraction("accuracy"),
|
||||
artificialGroundRecall: fraction("artificial_ground_recall"),
|
||||
naturalGroundRecall: fraction("natural_ground_recall"),
|
||||
obstacleNonGroundRecall: fraction("obstacle_non_ground_recall"),
|
||||
current: currentMetrics,
|
||||
patchworkpp: patchworkMetrics,
|
||||
},
|
||||
latencyMs: {
|
||||
current: number(latency.current, "latency_ms.current"),
|
||||
patchworkpp: number(latency.patchworkpp, "latency_ms.patchworkpp"),
|
||||
},
|
||||
patchworkAssignedFraction,
|
||||
inputProfile: {
|
||||
frameId: "sensor/lidar/vls128_roof",
|
||||
sensorHeightM,
|
||||
heightEvidence: "published-dimensioned-schematic",
|
||||
normalizedScanProduced: false,
|
||||
},
|
||||
latencyMs: number(source.latency_ms, "latency_ms"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -138,9 +138,11 @@ export function DatasetGatewayWorkspace() {
|
||||
masks: {
|
||||
currentGround: comparisonAligned?.currentGround ?? emptyMask,
|
||||
currentAssigned: comparisonAligned?.evaluated ?? emptyMask,
|
||||
candidateGround: comparisonAligned?.groundTruthGround ?? emptyMask,
|
||||
candidateAssigned: comparisonAligned?.evaluated ?? emptyMask,
|
||||
disagreement: comparisonAligned?.disagreement ?? emptyMask,
|
||||
candidateGround: comparisonAligned?.patchworkGround ?? emptyMask,
|
||||
candidateAssigned: comparisonAligned?.patchworkAssigned ?? emptyMask,
|
||||
disagreement: comparisonAligned?.currentDisagreement ?? emptyMask,
|
||||
candidateDisagreement:
|
||||
comparisonAligned?.patchworkDisagreement ?? emptyMask,
|
||||
groundTruthGround: preview.groundTruthGround,
|
||||
},
|
||||
};
|
||||
@@ -301,7 +303,9 @@ export function DatasetGatewayWorkspace() {
|
||||
...(comparison
|
||||
? ([
|
||||
["current", "Current"],
|
||||
["disagreement", "Ошибки"],
|
||||
["candidate", "Patchwork++"],
|
||||
["disagreement", "Ошибки Current"],
|
||||
["candidate-disagreement", "Ошибки PW++"],
|
||||
] as const)
|
||||
: []),
|
||||
["intensity", "Remission"],
|
||||
@@ -320,20 +324,42 @@ export function DatasetGatewayWorkspace() {
|
||||
{comparison ? (
|
||||
<dl className="dataset-preview__metrics">
|
||||
<div>
|
||||
<dt>Ground IoU</dt>
|
||||
<dd>{(comparison.metrics.groundIou * 100).toFixed(1)}%</dd>
|
||||
<dt>Current · IoU</dt>
|
||||
<dd>{(comparison.metrics.current.groundIou * 100).toFixed(1)}%</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Precision</dt>
|
||||
<dd>{(comparison.metrics.precision * 100).toFixed(1)}%</dd>
|
||||
<dt>Current · Recall</dt>
|
||||
<dd>{(comparison.metrics.current.recall * 100).toFixed(1)}%</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Recall</dt>
|
||||
<dd>{(comparison.metrics.recall * 100).toFixed(1)}%</dd>
|
||||
<dt>Current · Natural</dt>
|
||||
<dd>
|
||||
{(comparison.metrics.current.naturalGroundRecall * 100).toFixed(1)}%
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Latency</dt>
|
||||
<dd>{comparison.latencyMs.toFixed(0)} ms</dd>
|
||||
<dt>Current · Latency</dt>
|
||||
<dd>{comparison.latencyMs.current.toFixed(0)} ms</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Patchwork++ · IoU</dt>
|
||||
<dd>
|
||||
{(comparison.metrics.patchworkpp.groundIou * 100).toFixed(1)}%
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Patchwork++ · Recall</dt>
|
||||
<dd>{(comparison.metrics.patchworkpp.recall * 100).toFixed(1)}%</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Patchwork++ · Natural</dt>
|
||||
<dd>
|
||||
{(comparison.metrics.patchworkpp.naturalGroundRecall * 100).toFixed(1)}%
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Patchwork++ · Latency</dt>
|
||||
<dd>{comparison.latencyMs.patchworkpp.toFixed(1)} ms</dd>
|
||||
</div>
|
||||
</dl>
|
||||
) : comparisonError ? (
|
||||
@@ -362,9 +388,19 @@ export function DatasetGatewayWorkspace() {
|
||||
<span><i className="dataset-legend-ground" />current ground</span>
|
||||
<span><i className="dataset-legend-other" />current non-ground</span>
|
||||
</>
|
||||
) : previewMode === "candidate" ? (
|
||||
<>
|
||||
<span><i className="dataset-legend-ground" />Patchwork++ ground</span>
|
||||
<span><i className="dataset-legend-other" />Patchwork++ non-ground</span>
|
||||
</>
|
||||
) : previewMode === "disagreement" ? (
|
||||
<>
|
||||
<span><i className="dataset-legend-error" />ошибка относительно labels</span>
|
||||
<span><i className="dataset-legend-error" />ошибка Current</span>
|
||||
<span><i className="dataset-legend-other" />совпадение с labels</span>
|
||||
</>
|
||||
) : previewMode === "candidate-disagreement" ? (
|
||||
<>
|
||||
<span><i className="dataset-legend-error" />ошибка Patchwork++</span>
|
||||
<span><i className="dataset-legend-other" />совпадение</span>
|
||||
</>
|
||||
) : (
|
||||
@@ -380,6 +416,14 @@ export function DatasetGatewayWorkspace() {
|
||||
<p>{previewError ?? "Проверяем point alignment и разметку."}</p>
|
||||
</div>
|
||||
)}
|
||||
{comparison ? (
|
||||
<p className="dataset-preview__comparison-note">
|
||||
Patchwork++ использует опубликованную высоту VLS-128{" "}
|
||||
{comparison.inputProfile.sensorHeightM.toFixed(2)} м; это
|
||||
algorithm-specific admission, а не полный vehicle TF и не
|
||||
normalized scan.
|
||||
</p>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export type LidarGroundViewMode =
|
||||
| "current"
|
||||
| "candidate"
|
||||
| "disagreement"
|
||||
| "candidate-disagreement"
|
||||
| "semantic"
|
||||
| "ground-truth";
|
||||
|
||||
@@ -21,6 +22,7 @@ export interface LidarGroundPointCloudFrame {
|
||||
candidateGround: number[];
|
||||
candidateAssigned: number[];
|
||||
disagreement: number[];
|
||||
candidateDisagreement?: number[];
|
||||
groundTruthGround?: number[];
|
||||
};
|
||||
}
|
||||
@@ -119,6 +121,15 @@ function frameColors(
|
||||
disagreement ? 0.31 : 0.29,
|
||||
disagreement ? 0.22 : 0.35,
|
||||
);
|
||||
} else if (mode === "candidate-disagreement") {
|
||||
const disagreement = frame.masks.candidateDisagreement?.[index] === 1;
|
||||
setRgb(
|
||||
colors,
|
||||
offset,
|
||||
disagreement ? 1 : 0.24,
|
||||
disagreement ? 0.31 : 0.29,
|
||||
disagreement ? 0.22 : 0.35,
|
||||
);
|
||||
} else {
|
||||
setRgb(colors, offset, candidate ? 0.24 : 0.29, candidate ? 0.84 : 0.36, 0.43);
|
||||
}
|
||||
|
||||
@@ -194,36 +194,97 @@ test("decodes one bounded point-aligned native-scan preview", async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("decodes a point-aligned current-vs-ground-truth comparison", async () => {
|
||||
test("decodes a point-aligned current-vs-Patchwork++ comparison", async () => {
|
||||
const payload = {
|
||||
schema_version: "missioncore.dataset-ground-comparison-preview/v1",
|
||||
schema_version: "missioncore.dataset-ground-comparison-preview/v2",
|
||||
source_id: "goose-3d/v2025-08-22",
|
||||
frame_id: "frame-1",
|
||||
sampling: "deterministic-even-index",
|
||||
point_count: 2,
|
||||
current_ground: [1, 1],
|
||||
patchwork_ground: [1, 0],
|
||||
patchwork_assigned: [1, 1],
|
||||
ground_truth_ground: [1, 0],
|
||||
evaluated: [1, 1],
|
||||
disagreement: [0, 1],
|
||||
current_disagreement: [0, 1],
|
||||
patchwork_disagreement: [0, 0],
|
||||
metrics: {
|
||||
true_positive: 1,
|
||||
false_positive: 1,
|
||||
false_negative: 0,
|
||||
true_negative: 0,
|
||||
precision: 0.5,
|
||||
recall: 1,
|
||||
f1: 2 / 3,
|
||||
ground_iou: 0.5,
|
||||
accuracy: 0.5,
|
||||
artificial_ground_recall: 1,
|
||||
natural_ground_recall: 0,
|
||||
obstacle_non_ground_recall: 1,
|
||||
current: {
|
||||
true_positive: 1,
|
||||
false_positive: 1,
|
||||
false_negative: 0,
|
||||
true_negative: 0,
|
||||
precision: 0.5,
|
||||
recall: 1,
|
||||
f1: 2 / 3,
|
||||
ground_iou: 0.5,
|
||||
accuracy: 0.5,
|
||||
artificial_ground_recall: 1,
|
||||
natural_ground_recall: 0,
|
||||
obstacle_non_ground_recall: 1,
|
||||
},
|
||||
patchworkpp: {
|
||||
true_positive: 1,
|
||||
false_positive: 0,
|
||||
false_negative: 0,
|
||||
true_negative: 1,
|
||||
precision: 1,
|
||||
recall: 1,
|
||||
f1: 1,
|
||||
ground_iou: 1,
|
||||
accuracy: 1,
|
||||
artificial_ground_recall: 1,
|
||||
natural_ground_recall: 1,
|
||||
obstacle_non_ground_recall: 1,
|
||||
},
|
||||
},
|
||||
latency_ms: 12.5,
|
||||
provider: {
|
||||
provider_id: "missioncore-local-percentile-ground/v1",
|
||||
implementation_sha256: "a".repeat(64),
|
||||
ground_truth: false,
|
||||
latency_ms: {
|
||||
current: 12.5,
|
||||
patchworkpp: 0.4,
|
||||
},
|
||||
patchwork_assigned_fraction: 1,
|
||||
providers: {
|
||||
current: {
|
||||
provider_id: "missioncore-local-percentile-ground/v1",
|
||||
implementation_sha256: "a".repeat(64),
|
||||
ground_truth: false,
|
||||
},
|
||||
patchworkpp: {
|
||||
provider_id: "patchworkpp/v1.4.1",
|
||||
source_url: "https://github.com/url-kaist/patchwork-plusplus",
|
||||
source_tag: "v1.4.1",
|
||||
source_commit: "3e6903a1d5537a4cc2ace897b0bbb98a92d6014c",
|
||||
binding_version: "0.0.1",
|
||||
binary_sha256: "b".repeat(64),
|
||||
platform: "linux",
|
||||
machine: "x86_64",
|
||||
ground_truth: false,
|
||||
},
|
||||
},
|
||||
input_profile: {
|
||||
schema_version: "missioncore.goose-patchwork-profile/v1",
|
||||
source_id: "goose-3d/v2025-08-22",
|
||||
representation: "native-scan",
|
||||
sensor_frame: {
|
||||
frame_id: "sensor/lidar/vls128_roof",
|
||||
handedness: "right",
|
||||
x: "forward",
|
||||
y: "left",
|
||||
z: "up",
|
||||
one_revolution: true,
|
||||
},
|
||||
height: {
|
||||
base_link_above_ground_m: 0.64,
|
||||
lidar_above_base_link_m: 1.6,
|
||||
sensor_above_ground_m: 2.24,
|
||||
evidence: "published-dimensioned-schematic",
|
||||
},
|
||||
scope: {
|
||||
patchworkpp_eligible: true,
|
||||
normalized_scan_produced: false,
|
||||
complete_vehicle_transform_known: false,
|
||||
deskew_claimed: false,
|
||||
},
|
||||
},
|
||||
safety: {
|
||||
qualification_only: true,
|
||||
@@ -231,15 +292,23 @@ test("decodes a point-aligned current-vs-ground-truth comparison", async () => {
|
||||
},
|
||||
};
|
||||
const parsed = parseDatasetGroundComparison(payload);
|
||||
assert.equal(parsed.metrics.groundIou, 0.5);
|
||||
assert.deepEqual(parsed.disagreement, [0, 1]);
|
||||
assert.equal(parsed.metrics.current.groundIou, 0.5);
|
||||
assert.equal(parsed.metrics.patchworkpp.groundIou, 1);
|
||||
assert.deepEqual(parsed.patchworkDisagreement, [0, 0]);
|
||||
assert.equal(parsed.inputProfile.sensorHeightM, 2.24);
|
||||
|
||||
const fetched = await fetchDatasetGroundComparison({
|
||||
fetcher: async () => new Response(JSON.stringify(payload), { status: 200 }),
|
||||
});
|
||||
assert.equal(fetched.latencyMs, 12.5);
|
||||
assert.equal(fetched.latencyMs.patchworkpp, 0.4);
|
||||
|
||||
payload.disagreement.pop();
|
||||
payload.patchwork_disagreement.pop();
|
||||
assert.throws(
|
||||
() => parseDatasetGroundComparison(payload),
|
||||
DatasetGatewayContractError,
|
||||
);
|
||||
payload.patchwork_disagreement.push(0);
|
||||
payload.input_profile.height.sensor_above_ground_m = 2.25;
|
||||
assert.throws(
|
||||
() => parseDatasetGroundComparison(payload),
|
||||
DatasetGatewayContractError,
|
||||
|
||||
Reference in New Issue
Block a user