Take the watershed out of the product path

It does not work on a photograph, so nothing should offer it. `Segmentation`
is now one model pass and what it recognised: no region field, no merge
tree, no label upload, no granularity slider, and no readback of the whole
proxy to build a graph that collapses.

A click means "the object under the cursor". The region-selection path went
with the hierarchy it indexed — including the shift-click add/subtract,
which has no meaning for a whole object and would have been a modifier that
silently did nothing.

The passes, the hierarchy and the semantic prior stay in `dr-gpu` and
`dr-segment`, tested and documented. It is the *merge criterion* that
fails — the saddle is the minimum gradient along a boundary, so one weak
pixel merges two regions and real gradient noise puts a weak pixel on every
boundary. That is one function to replace, and the evidence for replacing it
is worth keeping. What is gone is the wiring, the option, and the control
that offered a user a choice with no outcome.

`MaskSource::Regions` remains in the pipeline: it is tested, it round-trips
through the sidecar, and a stored layer that names regions must still load
and be reported stale rather than failing to parse.
This commit is contained in:
2026-08-22 08:39:17 +02:00
parent 6163b63895
commit 37f63edbf9
5 changed files with 228 additions and 975 deletions
+2 -38
View File
@@ -223,16 +223,6 @@ export component MaskPanel inherits Rectangle {
in property <bool> segmented: false;
/// One is being computed now.
in property <bool> segmenting: false;
/// How many regions the ladder is currently cut to.
in property <int> level: 300;
in property <int> region-count: 0;
/// Whether a region hierarchy exists to click into.
///
/// Off in the ordinary case. The watershed's ladder collapses on a
/// photograph, so the controls that drive it are hidden rather than shown
/// doing nothing — a slider that changes no outcome is worse than an
/// absent one, because it invites the user to blame themselves.
in property <bool> has-regions: false;
/// Draw the false-coloured region map over the photograph.
in property <bool> overlay: false;
@@ -247,7 +237,6 @@ export component MaskPanel inherits Rectangle {
callback segment();
callback overlay-toggled(bool);
callback picking-toggled(bool);
callback level-changed(int);
callback mask-selected(string);
callback mask-removed(string);
@@ -279,11 +268,7 @@ export component MaskPanel inherits Rectangle {
HorizontalLayout {
PanelHeading { text: "LOCAL"; }
Rectangle { horizontal-stretch: 1; }
if root.segmented && root.has-regions: Value {
text: root.level + " / " + root.region-count;
}
if root.segmented && !root.has-regions: Value {
text: root.subjects.length + (root.subjects.length == 1 ? " subject" : " subjects");
if root.segmented: Value { text: root.subjects.length + (root.subjects.length == 1 ? " subject" : " subjects");
}
}
@@ -327,12 +312,7 @@ export component MaskPanel inherits Rectangle {
}
}
if root.enabled && root.segmented && root.picking && root.has-regions: Caption {
text: "Click the photograph to select a region. Shift-click to add or remove.";
wrap: word-wrap;
}
if root.enabled && root.segmented && root.picking && !root.has-regions: Caption {
if root.enabled && root.segmented && root.picking: Caption {
text: "Click a subject in the photograph to mask it.";
wrap: word-wrap;
}
@@ -340,22 +320,6 @@ export component MaskPanel inherits Rectangle {
// Granularity, labelled by what it does rather than by its number:
// "detail" is what a photographer is choosing between, where "300
// regions" is an implementation detail they would have to learn.
// **The ceiling is the region count, not a constant.** It was 2000,
// and a photograph that segments into more than that had the finest
// part of its own ladder unreachable — the slider simply stopped
// before the regions did.
if root.enabled && root.segmented && root.has-regions: SliderRow {
label: "Detail";
hint: "How finely a click divides the picture, out of "
+ root.region-count + " regions the watershed found.";
value: root.level;
default-value: 300;
minimum: 8;
maximum: max(root.region-count, 8);
changed(v) => { root.level-changed(v); }
reset => { root.level-changed(300); }
}
// --- what the model found ----------------------------------------
if root.enabled && root.segmented && root.subjects.length > 0: Caption {
// Says where these came from and how they differ from a region.