Let a subject's mask be refined, and let several masks be edited at once
Two related gaps in the mask panel, from the same conversation: a subject's outline is only ever as sharp as the whole-frame pass that found it, and a change meant for several layers had to be dragged once per layer. ## Refine mask A "Refine mask" button on a subject layer re-runs detection on a padded crop around that instance's own box instead of the whole frame — the subject reaches the model at its own size rather than squeezed into the model's fixed 640x640 window alongside everything else in the photograph. `RefineJob` mirrors `SegmentationJob`'s split (built on the session, run off it, adopted back), and the crop itself is rendered through `Framing::set_view` — the same ephemeral viewport the interactive zoom already uses to render a region above proxy resolution, so no new render path and no change to the model's own input size was needed. `dr-segment` is untouched: `Tiling::Whole` already treats whatever buffer it is handed as the one window. The result is still downsampled onto the shared proxy grid every instance's mask lives on, but from a sharper source than the whole-frame pass ever saw for that subject, which is what the edge actually reads out of. ## Multi-select `active_mask: Option<String>` is now `active_masks: Vec<String>`. A plain click still replaces the selection; a control- or command-click toggles one layer in or out of it. `set_param` and `reset_op` fan out to every selected layer, each set to the exact value the slider now shows rather than offset by however far it already was — one slider, one reading, applied everywhere selected. Dragging a gradient's on-canvas handle is deliberately not extended to multi-select: several gradients have no single geometry a shared handle could move, so `gradient_handles` stays empty unless exactly one layer is selected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,12 @@ pub struct InstanceSummary {
|
||||
/// This is the *source* a distance field is built from, not the mask
|
||||
/// itself — `dr_segment::Shaped` turns it into one.
|
||||
pub mask: Vec<u8>,
|
||||
/// `(x0, y0, x1, y1)` in [`Segmentation::proxy_size`] pixels.
|
||||
///
|
||||
/// Carried through from `dr_segment::Instance` rather than re-derived
|
||||
/// from the mask, so a refine pass knows what region to crop without
|
||||
/// scanning a proxy-sized buffer for its own extent.
|
||||
pub bbox: (f32, f32, f32, f32),
|
||||
}
|
||||
|
||||
/// One image's recognised objects, ready to mask.
|
||||
@@ -83,6 +89,18 @@ impl Segmentation {
|
||||
self.instances.get(index).map(|i| i.mask.as_slice())
|
||||
}
|
||||
|
||||
/// Replace one instance in place, keeping every other index and the
|
||||
/// signature unchanged.
|
||||
///
|
||||
/// What a refine pass calls once it has a sharper mask for the subject at
|
||||
/// `index`: the layers pointing at this run by index still mean what they
|
||||
/// meant, they just read better pixels now.
|
||||
pub fn replace_instance(&mut self, index: usize, instance: InstanceSummary) {
|
||||
if let Some(slot) = self.instances.get_mut(index) {
|
||||
*slot = instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// The strongest instance covering a point in normalised image
|
||||
/// coordinates.
|
||||
///
|
||||
@@ -245,6 +263,7 @@ pub fn compute(
|
||||
class_name: i.class_name.clone(),
|
||||
score: i.score,
|
||||
mask: quantise(&i.mask),
|
||||
bbox: i.bbox,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -338,11 +357,13 @@ mod tests {
|
||||
class_name: "bus".into(),
|
||||
score: 0.5,
|
||||
mask: big,
|
||||
bbox: (0.0, 0.0, 6.0, h as f32),
|
||||
},
|
||||
InstanceSummary {
|
||||
class_name: "person".into(),
|
||||
score: 0.9,
|
||||
mask: small,
|
||||
bbox: (4.0, 0.0, 8.0, h as f32),
|
||||
},
|
||||
],
|
||||
signature: 1,
|
||||
|
||||
Reference in New Issue
Block a user