Polish change detection density
This commit is contained in:
@@ -10,6 +10,8 @@ Map workspace now surfaces the selected AOI, active layer and rendered feature s
|
||||
|
||||
QA/QC Results now separates persisted check summary, candidate/reference evidence, refresh/filter controls and result history into focused surfaces. Existing filters, refresh behavior, metric cards and stored result rendering are unchanged, with denser mobile grids for the same controls.
|
||||
|
||||
Change Detection now follows the same analysis workspace hierarchy: vector input controls, error/empty states, result metrics and warnings are separated into focused surfaces while the existing compare action and map overlay result flow remain unchanged.
|
||||
|
||||
AI Labs uses the same mobile-density baseline for Detection and Segmentation model cards, run forms, QA/result summaries and scroll-safe result tables. This keeps long model ids, tile paths and artifact paths from widening the workbench on phones.
|
||||
|
||||
Exports and System provider capabilities now use compact, mobile-safe cards for handoff actions, artifact filters and provider metadata. Long provider limitations, export ids and artifact paths wrap inside their cards instead of widening the workbench.
|
||||
|
||||
@@ -37,8 +37,8 @@ export function ChangeDetectionPanel({
|
||||
onRun,
|
||||
}: ChangeDetectionPanelProps): JSX.Element {
|
||||
return (
|
||||
<section className="panel">
|
||||
<div className="panel-header">
|
||||
<section className="panel change-detection-shell">
|
||||
<div className="panel-header change-detection-heading">
|
||||
<div>
|
||||
<p className="eyebrow">Analysis</p>
|
||||
<h2>Change Detection</h2>
|
||||
@@ -48,80 +48,99 @@ export function ChangeDetectionPanel({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-grid">
|
||||
<label>
|
||||
Source vector
|
||||
<select value={sourceDatasetId} onChange={(event) => onSourceDatasetChange(event.target.value)}>
|
||||
<option value="">Select source</option>
|
||||
{vectorDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>
|
||||
{datasetLabel(dataset)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Target vector
|
||||
<select value={targetDatasetId} onChange={(event) => onTargetDatasetChange(event.target.value)}>
|
||||
<option value="">Select target</option>
|
||||
{vectorDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>
|
||||
{datasetLabel(dataset)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
IoU threshold
|
||||
<input
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.05"
|
||||
type="number"
|
||||
value={iouThreshold}
|
||||
onChange={(event) => onIouThresholdChange(Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<label className="checkbox-row">
|
||||
<input
|
||||
checked={includeUnchanged}
|
||||
type="checkbox"
|
||||
onChange={(event) => onIncludeUnchangedChange(event.target.checked)}
|
||||
/>
|
||||
Include unchanged geometry
|
||||
</label>
|
||||
<div className="change-detection-input-surface" aria-label="Change detection input controls">
|
||||
<div className="form-grid">
|
||||
<label>
|
||||
Source vector
|
||||
<select value={sourceDatasetId} onChange={(event) => onSourceDatasetChange(event.target.value)}>
|
||||
<option value="">Select source</option>
|
||||
{vectorDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>
|
||||
{datasetLabel(dataset)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Target vector
|
||||
<select value={targetDatasetId} onChange={(event) => onTargetDatasetChange(event.target.value)}>
|
||||
<option value="">Select target</option>
|
||||
{vectorDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>
|
||||
{datasetLabel(dataset)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
IoU threshold
|
||||
<input
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.05"
|
||||
type="number"
|
||||
value={iouThreshold}
|
||||
onChange={(event) => onIouThresholdChange(Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<label className="checkbox-row">
|
||||
<input
|
||||
checked={includeUnchanged}
|
||||
type="checkbox"
|
||||
onChange={(event) => onIncludeUnchangedChange(event.target.checked)}
|
||||
/>
|
||||
Include unchanged geometry
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? <p className="error">{error}</p> : null}
|
||||
{!error && vectorDatasets.length < 2 ? <p className="muted">Upload at least two vector datasets to compare.</p> : null}
|
||||
<div className="change-detection-state-stack">
|
||||
{error ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>Change detection failed.</strong>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{!error && vectorDatasets.length < 2 ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>Not enough vector datasets</strong>
|
||||
<p>Upload at least two vector datasets to compare.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{result ? (
|
||||
<div className="summary-grid">
|
||||
<div>
|
||||
<span className="metric">{result.added_count}</span>
|
||||
<span>Added</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="metric">{result.removed_count}</span>
|
||||
<span>Removed</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="metric">{result.unchanged_count}</span>
|
||||
<span>Unchanged</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="metric">{result.geojson.features.length}</span>
|
||||
<span>Map features</span>
|
||||
<div className="change-detection-result-surface" aria-label="Change detection result summary">
|
||||
<div className="summary-grid">
|
||||
<div>
|
||||
<span className="metric">{result.added_count}</span>
|
||||
<span>Added</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="metric">{result.removed_count}</span>
|
||||
<span>Removed</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="metric">{result.unchanged_count}</span>
|
||||
<span>Unchanged</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="metric">{result.geojson.features.length}</span>
|
||||
<span>Map features</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{result?.warnings.length ? (
|
||||
<ul className="compact-list">
|
||||
{result.warnings.map((warning) => (
|
||||
<li key={warning}>{warning}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="change-detection-warning-surface">
|
||||
<strong>Warnings</strong>
|
||||
<ul className="compact-list">
|
||||
{result.warnings.map((warning) => (
|
||||
<li key={warning}>{warning}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -1465,6 +1465,71 @@ button.entity-card {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.change-detection-shell {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.change-detection-heading {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.change-detection-input-surface,
|
||||
.change-detection-result-surface,
|
||||
.change-detection-warning-surface {
|
||||
min-width: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 0.72rem;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.change-detection-input-surface {
|
||||
background: linear-gradient(180deg, #ffffff, #f7fbf8);
|
||||
}
|
||||
|
||||
.change-detection-state-stack {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.change-detection-shell .form-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
||||
gap: 0.62rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.change-detection-shell .form-grid label {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.change-detection-shell .form-grid input,
|
||||
.change-detection-shell .form-grid select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.change-detection-shell .summary-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
|
||||
gap: 0.55rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.change-detection-warning-surface {
|
||||
border-color: rgba(180, 83, 9, 0.24);
|
||||
background: rgba(180, 83, 9, 0.08);
|
||||
}
|
||||
|
||||
.change-detection-warning-surface strong {
|
||||
display: block;
|
||||
color: #92400e;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.change-detection-warning-surface .compact-list {
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
.quality-results-shell .quality-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -2798,12 +2863,16 @@ button.entity-card {
|
||||
.quality-summary-surface,
|
||||
.quality-evidence-surface,
|
||||
.quality-control-surface,
|
||||
.quality-history-surface {
|
||||
.quality-history-surface,
|
||||
.change-detection-input-surface,
|
||||
.change-detection-result-surface,
|
||||
.change-detection-warning-surface {
|
||||
padding: 0.58rem;
|
||||
}
|
||||
|
||||
.quality-results-shell .quality-summary-grid,
|
||||
.quality-results-shell .quality-history-controls {
|
||||
.quality-results-shell .quality-history-controls,
|
||||
.change-detection-shell .form-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -2811,7 +2880,8 @@ button.entity-card {
|
||||
.quality-results-shell .quality-handoff-grid,
|
||||
.quality-results-shell .quality-score-row,
|
||||
.quality-results-shell .quality-metric-grid,
|
||||
.quality-results-shell .metric-list {
|
||||
.quality-results-shell .metric-list,
|
||||
.change-detection-shell .summary-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user