Skip to content

Commit dc83b2b

Browse files
apply suggestions from review
1 parent 4de9a70 commit dc83b2b

2 files changed

Lines changed: 114 additions & 6 deletions

File tree

src/index.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,18 @@ export class ULabel {
10541054
set_subtask(st_key) {
10551055
let old_st = this.get_current_subtask_key();
10561056

1057+
// Clear stale hover on the outgoing subtask so its white outline doesn't linger
1058+
// (its canvases stay visible at reduced opacity in the background).
1059+
const old_subtask = this.subtasks[old_st];
1060+
const old_hovered = old_subtask["state"]["hovered_annid"];
1061+
if (old_hovered !== null) {
1062+
old_subtask["state"]["hovered_annid"] = null;
1063+
const prev_ann = old_subtask["annotations"]["access"][old_hovered];
1064+
if (prev_ann && prev_ann["canvas_id"]) {
1065+
this.redraw_all_annotations_in_annotation_context(prev_ann["canvas_id"], old_st);
1066+
}
1067+
}
1068+
10571069
// Change object state
10581070
this.state["current_subtask"] = st_key;
10591071

@@ -3519,10 +3531,12 @@ export class ULabel {
35193531
let new_top = (cbox["tly"] + cbox["bry"] + 2 * diffY) / (2 * this.config["image_height"]);
35203532
current_subtask["state"]["visible_dialogs"][esid]["left"] = new_lft;
35213533
current_subtask["state"]["visible_dialogs"][esid]["top"] = new_top;
3522-
// Decide confidence card position from the un-offset cbox so it stays stable during moves
3523-
const cbox_center_y_screen = ((cbox["tly"] + cbox["bry"]) / 2) * this.state["zoom_val"];
3534+
// Decide confidence card position from the un-offset cbox so it stays stable during moves.
3535+
// Account for annbox scroll: what matters is the visible position, not the image-space position.
3536+
const cbox_center_y_imwrap = ((cbox["tly"] + cbox["bry"]) / 2) * this.state["zoom_val"];
3537+
const scroll_top = $("#" + this.config["annbox_id"]).scrollTop() || 0;
35243538
const conf_id = `global_annotation_confidence__${subtask_key}`;
3525-
const flip_below = cbox_center_y_screen < 100;
3539+
const flip_below = (cbox_center_y_imwrap - scroll_top) < 100;
35263540
$(`#${conf_id}`).css("margin-top", flip_below ? "-1em" : "-9.5em");
35273541
this.reposition_dialogs();
35283542
idd_x = (cbox["tlx"] + cbox["brx"] + 2 * diffX) / 2;
@@ -6645,15 +6659,17 @@ export class ULabel {
66456659
/** The active annotation's classification payloads. */
66466660
const aacp = active_annotation["classification_payloads"];
66476661

6648-
// Find the highest confidence payload
6649-
let confidence = 0;
6662+
// Match get_annotation_class_id semantics: seed the first payload so all-zero confidences
6663+
// still pick a class instead of falling through to "Unknown".
6664+
let confidence;
66506665
let best_class_id = null;
66516666
aacp.forEach((payload) => {
6652-
if (payload.confidence > confidence) {
6667+
if (confidence === undefined || payload.confidence > confidence) {
66536668
confidence = payload.confidence;
66546669
best_class_id = payload.class_id;
66556670
}
66566671
});
6672+
if (confidence === undefined) confidence = 0;
66576673

66586674
// Resolve class name from class_defs
66596675
let class_name = "Unknown";

tests/e2e/basic-functionality.spec.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,96 @@ test.describe("ULabel Basic Functionality", () => {
184184
const margin_near_top = await page.locator(conf_id).evaluate((el) => el.style.marginTop);
185185
expect(margin_near_top).toBe("-1em");
186186
});
187+
188+
test("confidence card flip check accounts for annbox scroll position", async ({ page }) => {
189+
await wait_for_ulabel_init(page);
190+
191+
const subtask_key = await page.evaluate(() => window.ulabel.get_current_subtask_key());
192+
const conf_id = `#global_annotation_confidence__${subtask_key}`;
193+
194+
// Middle-image annotation displays with card above (no flip)
195+
await draw_bbox(page, [400, 400], [500, 500]);
196+
await page.waitForTimeout(100);
197+
await page.mouse.move(450, 450);
198+
await page.waitForTimeout(200);
199+
200+
const margin_unscrolled = await page.locator(conf_id).evaluate((el) => el.style.marginTop);
201+
expect(margin_unscrolled).toBe("-9.5em");
202+
203+
// Scroll the annbox down so the annotation's visible top approaches viewport top,
204+
// then re-trigger the position calculation (simulating a re-hover after scroll).
205+
await page.evaluate(() => {
206+
const u = window.ulabel;
207+
const annbox = document.getElementById(u.config.annbox_id);
208+
annbox.scrollTop = 500;
209+
const annid = u.get_current_subtask().annotations.ordering[0];
210+
u.get_current_subtask().state.edit_candidate = { annid: annid };
211+
u.show_global_edit_suggestion(annid);
212+
});
213+
await page.waitForTimeout(100);
214+
215+
const margin_scrolled = await page.locator(conf_id).evaluate((el) => el.style.marginTop);
216+
expect(margin_scrolled).toBe("-1em");
217+
});
218+
219+
test("confidence card picks a class name even when all confidences are 0", async ({ page }) => {
220+
await wait_for_ulabel_init(page);
221+
222+
// Directly install an annotation with zero confidence and trigger the confidence dialog.
223+
// annotation.ts pads missing classes with confidence: 0.0, so this represents a common
224+
// "no class info supplied" import case where earlier code showed "Unknown".
225+
await page.evaluate(async () => {
226+
const u = window.ulabel;
227+
const anno = {
228+
id: "test_zero",
229+
spatial_type: "bbox",
230+
spatial_payload: [[150, 150], [250, 250]],
231+
classification_payloads: [{ class_id: 10, confidence: 0 }],
232+
};
233+
await u.set_annotations([anno], "car_detection");
234+
});
235+
await page.waitForTimeout(100);
236+
237+
await page.mouse.move(200, 200);
238+
await page.waitForTimeout(200);
239+
240+
const subtask_key = await page.evaluate(() => window.ulabel.get_current_subtask_key());
241+
const conf_id = `#global_annotation_confidence__${subtask_key}`;
242+
const classname = (await page.locator(`${conf_id} .annotation-confidence-classname`).textContent()).trim();
243+
const value = (await page.locator(`${conf_id} .annotation-confidence-value`).textContent()).trim();
244+
245+
expect(classname).toBe("Sedan");
246+
expect(value).toBe("Confidence: 0.00");
247+
});
248+
249+
test("switching subtasks clears hovered_annid on the outgoing subtask", async ({ page }) => {
250+
await wait_for_ulabel_init(page);
251+
252+
await draw_bbox(page, [200, 200], [300, 300]);
253+
await page.waitForTimeout(100);
254+
255+
// Hover to set hovered_annid on the current subtask
256+
await page.mouse.move(250, 250);
257+
await page.waitForTimeout(200);
258+
259+
const before = await page.evaluate(() => {
260+
const u = window.ulabel;
261+
const key = u.get_current_subtask_key();
262+
return {
263+
key: key,
264+
hovered_annid: u.subtasks[key].state.hovered_annid,
265+
};
266+
});
267+
expect(before.hovered_annid).not.toBeNull();
268+
269+
// Switch to the next subtask
270+
await page.evaluate(() => window.ulabel.switch_to_next_subtask());
271+
await page.waitForTimeout(100);
272+
273+
// The previous subtask's hovered_annid must be cleared so its stale outline doesn't linger
274+
const after = await page.evaluate((old_key) => {
275+
return window.ulabel.subtasks[old_key].state.hovered_annid;
276+
}, before.key);
277+
expect(after).toBeNull();
278+
});
187279
});

0 commit comments

Comments
 (0)