Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit f08871a

Browse files
fix: LSDV-4692: Brush segmentation is not supported (#1247)
* Fix tools in MIG * Remove excess FF * Make RLE Export stageless * Remove feature flag * Fix comment * Remove unused helper * Remove unused code * fix: LSDV-4774: Drawing on the hidden image in MIG scenario (#1254) Fixing Brush and Polygon can draw on hiddent images * Fix inability to edit brush regions * Remove RLE cache * Fix helper * Rename test suite * Remove ESLint debug info * Fix audio assertions * Fix audio error matching --------- Co-authored-by: Sergey <[email protected]>
1 parent 3c77159 commit f08871a

File tree

12 files changed

+234
-50
lines changed

12 files changed

+234
-50
lines changed

e2e/fragments/AtAudioView.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,9 @@ module.exports = {
277277
async seeErrorHandler(value, selector = null) {
278278
selector = selector ? this[selector] : this._errorSelector;
279279
const error = await I.grabTextFrom(selector);
280+
const matcher = new RegExp(value);
280281

281-
assert.equal(error, value);
282+
assert.match(error, matcher);
282283
},
283284

284285
/**

e2e/fragments/AtImageView.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ module.exports = {
5050
return this._stageBBox?.y ?? 0;
5151
},
5252

53-
waitForImage() {
53+
async waitForImage() {
5454
I.say('Waiting for image to be loaded');
55-
I.executeScript(Helpers.waitForImage);
55+
await I.executeScript(Helpers.waitForImage);
5656
I.waitForVisible('canvas', 5);
5757
},
5858

@@ -300,4 +300,18 @@ module.exports = {
300300
I.say('Select move tool');
301301
I.pressKey('V');
302302
},
303+
304+
async multiImageGoForwartWithHotkey() {
305+
I.say('Attempting to go to the next image');
306+
I.pressKey('Ctrl+d');
307+
308+
await this.waitForImage();
309+
},
310+
311+
async multiImageGoBackwardWithHotkey() {
312+
I.say('Attempting to go to the next image');
313+
I.pressKey('Ctrl+a');
314+
315+
await this.waitForImage();
316+
},
303317
};

e2e/tests/audio/audio-errors.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Scenario('Check if audio decoder error handler is showing', async function({ I,
5555

5656
await AtAudioView.lookForStage();
5757

58-
AtAudioView.seeErrorHandler('An error occurred while decoding the audio file');
58+
await AtAudioView.seeErrorHandler('An error occurred while decoding the audio file');
5959
});
6060

6161
Scenario('Check if audio http error handler is showing', async function({ I, LabelStudio, AtAudioView }) {
@@ -74,5 +74,5 @@ Scenario('Check if audio http error handler is showing', async function({ I, Lab
7474

7575
await AtAudioView.lookForStage();
7676

77-
AtAudioView.seeErrorHandler('HTTP error status: 404', '_httpErrorSelector');
77+
await AtAudioView.seeErrorHandler('HTTP error status: 404', '_httpErrorSelector');
7878
});

e2e/tests/image-list.test.js

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
Feature('Image list via `valueList`');
1+
Feature('MIG');
22

3-
const config = `
3+
const assert = require('assert');
4+
5+
const rectConfig = `
46
<View>
57
<Image name="img" valueList="$images"/>
68
<RectangleLabels name="tag" toName="img">
@@ -10,6 +12,16 @@ const config = `
1012
</View>
1113
`;
1214

15+
const brushConfig = `
16+
<View>
17+
<Image name="img" valueList="$images"/>
18+
<BrushLabels name="tag" toName="img">
19+
<Label value="Planet"></Label>
20+
<Label value="Moonwalker" background="blue"></Label>
21+
</BrushLabels>
22+
</View>
23+
`;
24+
1325
const data = {
1426
images: [
1527
'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg',
@@ -93,7 +105,7 @@ Before(async ({ LabelStudio }) => {
93105

94106
Scenario('Image list rendering', async ({ I, LabelStudio, AtImageView }) => {
95107
const params = {
96-
config,
108+
config: rectConfig,
97109
data,
98110
annotations: [{ id: 1, result: [] }],
99111
};
@@ -109,7 +121,7 @@ Scenario('Image list rendering', async ({ I, LabelStudio, AtImageView }) => {
109121

110122
Scenario('Image list with page navigation', async ({ I, AtImageView, LabelStudio }) => {
111123
const params = {
112-
config,
124+
config: rectConfig,
113125
data,
114126
annotations: [{ id: 1, result: [] }],
115127
};
@@ -147,7 +159,7 @@ Scenario('Image list with page navigation', async ({ I, AtImageView, LabelStudio
147159

148160
Scenario('Image list with hotkey navigation', async ({ I, AtImageView, LabelStudio }) => {
149161
const params = {
150-
config,
162+
config: rectConfig,
151163
data,
152164
annotations: [{ id: 1, result: [] }],
153165
};
@@ -167,15 +179,13 @@ Scenario('Image list with hotkey navigation', async ({ I, AtImageView, LabelStud
167179
I.say('The number of pages is correct');
168180
I.see('1 of 4');
169181

170-
I.say('Clicking on the next page');
171-
I.pressKey('Ctrl+d');
182+
await AtImageView.multiImageGoForwartWithHotkey();
172183

173184
I.say('Loading second image');
174185
I.seeElement(`img[src="${data.images[1]}"]`);
175186
I.see('2 of 4');
176187

177-
I.say('Clicking on the previous page');
178-
I.pressKey('Ctrl+a');
188+
await AtImageView.multiImageGoBackwardWithHotkey();
179189
I.seeElement(`img[src="${data.images[0]}"]`);
180190
I.see('1 of 4');
181191
});
@@ -186,7 +196,7 @@ Scenario('Ensure that results are the same when exporting existing regions', asy
186196
});
187197

188198
const params = {
189-
config,
199+
config: rectConfig,
190200
data,
191201
annotations: [{ id: 1, result }],
192202
};
@@ -207,7 +217,7 @@ Scenario('Image list exports correct data', async ({ I, LabelStudio, AtImageView
207217
});
208218

209219
const params = {
210-
config,
220+
config: rectConfig,
211221
data,
212222
annotations: [{ id: 1, result }],
213223
};
@@ -218,8 +228,7 @@ Scenario('Image list exports correct data', async ({ I, LabelStudio, AtImageView
218228
await AtImageView.waitForImage();
219229
await AtImageView.lookForStage();
220230

221-
I.say('Attempting to go to the next image');
222-
I.pressKey('Ctrl+d');
231+
AtImageView.multiImageGoForwartWithHotkey();
223232

224233
await AtImageView.waitForImage();
225234
await AtImageView.lookForStage();
@@ -231,7 +240,7 @@ Scenario('Image list exports correct data', async ({ I, LabelStudio, AtImageView
231240
// TODO: temporarily disable, will be fixed in another ticket
232241
Scenario('Regions are not changes when duplicating an annotation', async ({ I, LabelStudio, AtImageView }) => {
233242
const params = {
234-
config,
243+
config: rectConfig,
235244
data,
236245
annotations: [{ id: 1, result }],
237246
};
@@ -251,3 +260,41 @@ Scenario('Regions are not changes when duplicating an annotation', async ({ I, L
251260
I.say('Confirm that result is not changed');
252261
await LabelStudio.resultsNotChanged(result);
253262
});
263+
264+
Scenario('No errors during brush export in MIG', async ({ I, LabelStudio, AtImageView, AtLabels }) => {
265+
const params = {
266+
config: brushConfig,
267+
data,
268+
annotations: [{ id: 1, result: [] }],
269+
};
270+
271+
const brushRegionPoints = [
272+
[20, 20],
273+
[20, 40],
274+
[40, 40],
275+
[40, 20],
276+
[20, 20],
277+
];
278+
279+
I.amOnPage('/');
280+
LabelStudio.init(params);
281+
282+
await AtImageView.waitForImage();
283+
await AtImageView.lookForStage();
284+
285+
I.say('Create brush regions on the first image');
286+
AtLabels.clickLabel('Moonwalker');
287+
AtImageView.drawThroughPoints(brushRegionPoints);
288+
289+
await AtImageView.multiImageGoForwartWithHotkey();
290+
291+
I.pressKey('u');
292+
I.say('Create brush regions on the second image');
293+
AtLabels.clickLabel('Planet');
294+
AtImageView.drawThroughPoints(brushRegionPoints);
295+
296+
const result = await LabelStudio.serialize();
297+
298+
assert.equal(result.length, 2);
299+
});
300+

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"copy-examples": "bash scripts/copy.sh",
3737
"docs:generate": "node scripts/create-docs.js",
3838
"lint:write": "eslint --debug src/ --fix",
39-
"lint": "eslint --debug src/",
39+
"lint": "eslint src/",
4040
"prepublishOnly": "yarn run test && yarn run build:final",
4141
"prettier-styles": "prettier --write src/**/*.{css,scss}",
4242
"prettier": "prettier --write src/**/*.js",

src/components/ImageView/ImageView.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ const Regions = memo(({ regions, useLayers = true, chunkSize = 15, suggestion =
8686

8787
const DrawingRegion = observer(({ item }) => {
8888
const { drawingRegion } = item;
89+
90+
if (!drawingRegion) return null;
91+
if (item.multiImage && item.currentImage !== drawingRegion.item_index) return null;
92+
8993
const Wrapper = drawingRegion && drawingRegion.type === 'brushregion' ? Fragment : Layer;
9094

9195
return (

src/mixins/DrawingTool.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { types } from 'mobx-state-tree';
33
import Utils from '../utils';
44
import throttle from 'lodash.throttle';
55
import { MIN_SIZE } from '../tools/Base';
6-
import { FF_DEV_3666, FF_DEV_3793, FF_LSDV_4583, isFF } from '../utils/feature-flags';
6+
import { FF_DEV_3666, FF_DEV_3793, isFF } from '../utils/feature-flags';
77

88
const DrawingTool = types
99
.model('DrawingTool', {
@@ -111,8 +111,6 @@ const DrawingTool = types
111111
self.currentArea = self.obj.createDrawingRegion(opts, resultValue, control, false);
112112
self.currentArea.setDrawing(true);
113113

114-
if (isFF(FF_LSDV_4583)) self.currentArea.setItemIndex?.(self.obj.currentImage);
115-
116114
self.applyActiveStates(self.currentArea);
117115
self.annotation.setIsDrawing(true);
118116
return self.currentArea;
@@ -138,8 +136,6 @@ const DrawingTool = types
138136

139137
const newArea = self.annotation.createResult(value, currentArea.results[0].value.toJSON(), control, obj);
140138

141-
if (isFF(FF_LSDV_4583)) newArea.setItemIndex?.(obj.currentImage);
142-
143139
currentArea.setDrawing(false);
144140
self.applyActiveStates(newArea);
145141
self.deleteRegion();
@@ -347,6 +343,11 @@ const MultipleClicksDrawingTool = DrawingTool.named('MultipleClicksMixin')
347343
return Super.canStartDrawing() && !self.annotation.regionStore.hasSelection;
348344
},
349345
nextPoint(x, y) {
346+
const area = self.getCurrentArea();
347+
const object = self.obj;
348+
349+
if (area && object && object.multiImage && area.item_index !== object.currentImage) return;
350+
350351
self.getCurrentArea().addPoint(x, y);
351352
pointsCount++;
352353
},

src/regions/BrushRegion.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ const Points = types
6464
}))
6565
.actions(self => {
6666
return {
67-
updateImageSize(wp, hp,sw,sh) {
68-
self.points = self.relativePoints.map((v,idx) => {
67+
updateImageSize(wp, hp, sw, sh) {
68+
self.points = self.relativePoints.map((v, idx) => {
6969
const isX = !(idx % 2);
7070
const stageSize = isX ? sw : sh;
7171

@@ -346,7 +346,7 @@ const Model = types
346346
annotation.autosave && setTimeout(() => annotation.autosave());
347347
},
348348

349-
convertPointsToMask() {},
349+
convertPointsToMask() { },
350350

351351
setScale(x, y) {
352352
self.scaleX = x;
@@ -497,14 +497,14 @@ const HtxBrushView = ({ item }) => {
497497
// that dynamically produce image masks.
498498

499499
if (!item.rle && !item.maskDataURL) return;
500-
if (!item.parent || item.parent.naturalWidth <=1 || item.parent.naturalHeight <= 1) return;
500+
if (!item.parent || item.parent.naturalWidth <= 1 || item.parent.naturalHeight <= 1) return;
501501

502502
let img;
503503

504504
if (item.maskDataURL && isFF(FF_DEV_4081)) {
505505
img = await Canvas.maskDataURL2Image(item.maskDataURL, { color: item.strokeColor });
506506
} else if (item.rle) {
507-
img = Canvas.RLE2Region(item.rle, item.parent, { color: item.strokeColor });
507+
img = Canvas.RLE2Region(item, { color: item.strokeColor });
508508
}
509509

510510
if (img) {
@@ -689,8 +689,8 @@ const HtxBrushView = ({ item }) => {
689689
<Image
690690
name="highlight"
691691
image={highlightedImageRef.current}
692-
sceneFunc={highlightedRef.current.highlighted ? null : () => {}}
693-
hitFunc={() => {}}
692+
sceneFunc={highlightedRef.current.highlighted ? null : () => { }}
693+
hitFunc={() => { }}
694694
{...highlightedRef.current.highlight}
695695
scaleX={1 / item.parent.stageScale}
696696
scaleY={1 / item.parent.stageScale}
@@ -711,7 +711,7 @@ const HtxBrushView = ({ item }) => {
711711
}}
712712
>
713713
<Group>
714-
<LabelOnMask item={item} color={item.strokeColor}/>
714+
<LabelOnMask item={item} color={item.strokeColor} />
715715
</Group>
716716
</Layer>
717717
</RegionWrapper>

src/stores/Annotation/Annotation.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,9 +839,12 @@ export const Annotation = types
839839
};
840840

841841

842-
//TODO: MST is crashing if we don't validate areas?, this problem isn't happening locally. So to reproduce you have to test in production or environment
842+
// TODO: MST is crashing if we don't validate areas?, this problem isn't
843+
// happening locally. So to reproduce you have to test in production or environment
843844
const area = self?.areas?.put(areaRaw);
844845

846+
object?.afterResultCreated?.(area);
847+
845848
if (!area) return;
846849

847850
if (!area.classification) getEnv(self).events.invoke('entityCreate', area);

src/tags/object/Image/Image.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,13 @@ const Model = types.model({
515515

516516
createImageEntities();
517517
}
518+
519+
function afterResultCreated(region) {
520+
if (!region) return;
521+
if (!self.multiImage) return;
522+
523+
region.setItemIndex?.(self.currentImage);
524+
}
518525

519526
function getToolsManager() {
520527
return manager;
@@ -523,6 +530,7 @@ const Model = types.model({
523530
return {
524531
afterAttach,
525532
getToolsManager,
533+
afterResultCreated,
526534
};
527535
}).extend((self) => {
528536
let skipInteractions = false;
@@ -631,6 +639,7 @@ const Model = types.model({
631639
setCurrentImage(index = 0) {
632640
index = index ?? 0;
633641
if (index === self.currentImage) return;
642+
634643
self.currentImage = index;
635644
self.currentImageEntity = self.findImageEntity(index);
636645
if (isFF(FF_LSDV_4583_6)) self.preloadImages();

src/tools/Brush.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,15 @@ const _Tool = types
182182
)
183183
return;
184184
const c = self.control;
185+
const o = self.obj;
185186

186-
// Reset the timer if a user started drawing again
187187
brush = self.getSelectedShape;
188+
189+
// prevent drawing when current image is
190+
// different from image where the brush was started
191+
if (o && brush && o.multiImage && o.currentImage !== brush.item_index) return;
192+
193+
// Reset the timer if a user started drawing again
188194
if (brush && brush.type === 'brushregion') {
189195
self.annotation.history.freeze();
190196
self.mode = 'drawing';

0 commit comments

Comments
 (0)