diff --git a/e2e/examples/image-keypoints.js b/e2e/examples/image-keypoints.js index b5b813705c..ed7d1cd0b9 100644 --- a/e2e/examples/image-keypoints.js +++ b/e2e/examples/image-keypoints.js @@ -9,7 +9,7 @@ const config = ` `; const data = { - image: 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg', + image: 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg', }; const result = [ @@ -18,14 +18,14 @@ const result = [ from_name: 'tag', to_name: 'img', image_rotation: 0, - original_height: 501, - original_width: 800, + original_height: 576, + original_width: 768, type: 'keypointlabels', origin: 'manual', value: { x: 49.60000000000001, y: 52.34042553191488, - width: 0.6471078324314267, + width: 0.6120428759942558, keypointlabels: ['Hello'], }, }, @@ -34,14 +34,14 @@ const result = [ from_name: 'tag', to_name: 'img', image_rotation: 0, - original_height: 501, - original_width: 800, + original_height: 576, + original_width: 768, type: 'keypointlabels', origin: 'manual', value: { x: 47.73333333333334, y: 52.765957446808514, - width: 0.6666666666666666, + width: 0.6305418719211823, keypointlabels: ['World'], }, }, diff --git a/e2e/fragments/AtImageView.js b/e2e/fragments/AtImageView.js index ad2ccf152e..ff02528e08 100644 --- a/e2e/fragments/AtImageView.js +++ b/e2e/fragments/AtImageView.js @@ -79,6 +79,12 @@ module.exports = { I.waitForVisible('canvas', 5); }, + async getNaturalSize() { + const sizes = await I.executeScript(Helpers.getNaturalSize); + + return sizes; + }, + async getCanvasSize() { const sizes = await I.executeScript(Helpers.getCanvasSize); diff --git a/e2e/tests/helpers.js b/e2e/tests/helpers.js index f879027676..7110e91def 100644 --- a/e2e/tests/helpers.js +++ b/e2e/tests/helpers.js @@ -234,7 +234,7 @@ const convertToFixed = (data, fractionDigits = 2) => { if (['string', 'number'].includes(typeof data)) { const n = Number(data); - return Number.isNaN(n) ? data : Number.isInteger(n) ? n : +Number(n).toFixed(fractionDigits); + return Number.isNaN(n) ? data : Number.isInteger(n) ? n : +n.toFixed(fractionDigits); } if (Array.isArray(data)) { return data.map(n => convertToFixed(n, fractionDigits)); @@ -533,6 +533,14 @@ async function generateImageUrl({ width, height }) { return canvas.toDataURL(); } +const getNaturalSize = () => { + const imageObject = window.Htx.annotationStore.selected.objects.find(o => o.type === 'image'); + + return { + width: imageObject.naturalWidth, + height: imageObject.naturalHeight, + }; +}; const getCanvasSize = () => { const imageObject = window.Htx.annotationStore.selected.objects.find(o => o.type === 'image'); @@ -859,6 +867,7 @@ module.exports = { areEqualRGB, hasKonvaPixelColorAtPoint, getKonvaPixelColorFromPoint, + getNaturalSize, getCanvasSize, getImageSize, getImageFrameSize, diff --git a/e2e/tests/history.test.js b/e2e/tests/history.test.js index cc0a5f1996..27a90e0b0d 100644 --- a/e2e/tests/history.test.js +++ b/e2e/tests/history.test.js @@ -1,6 +1,6 @@ Feature('Time traveling'); -const IMAGE = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; Scenario('Travel through history with the selected brush region', async function({ I, LabelStudio, AtImageView, AtSidebar }) { I.amOnPage('/'); diff --git a/e2e/tests/image-labels.test.js b/e2e/tests/image-labels.test.js index c1924c8b8b..eac9dd22c7 100644 --- a/e2e/tests/image-labels.test.js +++ b/e2e/tests/image-labels.test.js @@ -3,8 +3,7 @@ const { toKebabCase } = require('strman'); Feature('Images\' labels type matching'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const createConfig = ({ shapes = ['Rectangle'], props } = {}) => { return ` diff --git a/e2e/tests/image.gestures.test.js b/e2e/tests/image.gestures.test.js index bea2c3c614..4a723a6796 100644 --- a/e2e/tests/image.gestures.test.js +++ b/e2e/tests/image.gestures.test.js @@ -10,8 +10,7 @@ const DEFAULT_DIMENSIONS = { Feature('Creating regions with gesture'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const BLUEVIOLET = { color: '#8A2BE2', @@ -36,7 +35,7 @@ const createShape = { byMultipleClicks(x, y, radius, opts = {}) { const points = []; - for (let i = 5; i--; ) { + for (let i = 5; i--;) { points.push([x + Math.sin(((2 * Math.PI) / 5) * i) * radius, y - Math.cos(((2 * Math.PI) / 5) * i) * radius]); points.push([ x + (Math.sin(((2 * Math.PI) / 5) * (i - 0.5)) * radius) / 3, @@ -209,7 +208,7 @@ Scenario('Creating regions by various gestures', async function({ I, AtImageView for (const [idx, region] of Object.entries(regions)) { I.pressKey(region.hotKey); AtImageView[region.action](...region.params); - AtSidebar.seeRegions(+idx+1); + AtSidebar.seeRegions(+idx + 1); } const result = await I.executeScript(serialize); diff --git a/e2e/tests/image.magic-wand.test.js b/e2e/tests/image.magic-wand.test.js index a012f383a6..e6afdb7bc5 100644 --- a/e2e/tests/image.magic-wand.test.js +++ b/e2e/tests/image.magic-wand.test.js @@ -3,7 +3,6 @@ const { hasKonvaPixelColorAtPoint, setKonvaLayersOpacity, serialize, - waitForImage, } = require('./helpers'); const assert = require('assert'); diff --git a/e2e/tests/image.shapes.test.js b/e2e/tests/image.shapes.test.js index e699108856..ded9414699 100644 --- a/e2e/tests/image.shapes.test.js +++ b/e2e/tests/image.shapes.test.js @@ -19,8 +19,7 @@ const getConfigWithShape = (shape, props = '') => ` <${shape} ${props} name="tag" toName="img" /> `; -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; // precalculated image size on the screen; may change because of different reasons const WIDTH = 706; diff --git a/e2e/tests/image.test.js b/e2e/tests/image.test.js index 6ece71a08e..e054cc3ba6 100644 --- a/e2e/tests/image.test.js +++ b/e2e/tests/image.test.js @@ -62,8 +62,7 @@ const annotationWithPerRegion = { result: [annotationMoonwalker.result[0], createRegion('answer', 'textarea', { text: ['blah'] })], }; -const image = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const image = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; Scenario('Check Rect region for Image', async function({ I, AtImageView, AtSidebar }) { const params = { @@ -148,10 +147,10 @@ Scenario('Image with perRegion tags', async function({ I, AtImageView, AtSidebar assert.deepStrictEqual(result[0].value.rectanglelabels, ['Moonwalker']); }); -const outOfBoundsFFs = new DataTable(['FF_DEV_3793']) +const outOfBoundsFFs = new DataTable(['FF_DEV_3793']); outOfBoundsFFs.add([true]); -outOfBoundsFFs.add([false]) +outOfBoundsFFs.add([false]); Data(outOfBoundsFFs) .Scenario('Can\'t create rectangles outside of canvas', async ({ diff --git a/e2e/tests/image.transformer.test.js b/e2e/tests/image.transformer.test.js index 43955d2ffa..b8ee2e1c18 100644 --- a/e2e/tests/image.transformer.test.js +++ b/e2e/tests/image.transformer.test.js @@ -1,1270 +1,1269 @@ -const assert = require('assert'); -const Asserts = require('../utils/asserts'); -const Helpers = require('./helpers'); - -Feature('Image transformer'); - -const IMAGE = - 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg'; - -const annotationEmpty = { - id: '1000', - result: [], -}; - -const getParamsWithShape = (shape, params = '') => ({ - config: ` - - - <${shape} ${params} name="tag" toName="img" /> - `, - data: { image: IMAGE }, - annotations: [annotationEmpty], -}); - -const getParamsWithLabels = (shape) => ({ - config: ` - - - <${shape}Labels name="tag" toName="img"> - `, - data: { image: IMAGE }, - annotations: [annotationEmpty], -}); - -const shapes = { - Rectangle: { - drawAction: 'drawByDrag', - hasTransformer: true, - hasRotator: true, - hasMoveToolTransformer: true, - hasMultiSelectionTransformer: true, - hasMultiSelectionRotator: true, - hotKey: 'r', - byBBox(x, y, width, height) { - return { - params: [x, y, width, height], - result: { width, height, rotation: 0, x, y }, - }; - }, - }, - Ellipse: { - drawAction: 'drawByDrag', - hasTransformer: true, - hasRotator: true, - hasMoveToolTransformer: true, - hasMultiSelectionTransformer: true, - hasMultiSelectionRotator: true, - hotKey: 'o', - byBBox(x, y, width, height) { - return { - params: [x + width / 2, y + height / 2, width / 2, height / 2], - result: { radiusX: width / 2, radiusY: height / 2, rotation: 0, x: x + width / 2, y: y + height / 2 }, - }; - }, - }, - Polygon: { - drawAction: 'drawByClickingPoints', - hasTransformer: false, - hasRotator: false, - hasMoveToolTransformer: true, - hasMultiSelectionTransformer: true, - hasMultiSelectionRotator: false, - hotKey: 'p', - byBBox(x, y, width, height) { - const points = []; - - points.push([x, y]); - points.push([x + width, y]); - points.push([x + width / 2, y + height / 2]); - points.push([x + width, y + height]); - points.push([x, y + height]); - return { - params: [[...points, points[0]]], - result: { - points, - }, - }; - }, - }, - KeyPoint: { - drawAction: 'clickAt', - hasTransformer: false, - hasRotator: false, - hasMoveToolTransformer: false, - hasMultiSelectionTransformer: true, - hasMultiSelectionRotator: false, - hotKey: 'k', - params: 'strokeWidth="2"', - byBBox(x, y, width, height) { - return { - params: [x + width / 2, y + height / 2], - result: { - x: x + width / 2, - y: y + height / 2, - width: 2, - }, - }; - }, - }, -}; - -function drawShapeByBbox(Shape, x, y, width, height, where) { - where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params); -} - -const shapesTable = new DataTable(['shapeName']); - -for (const shapeName of Object.keys(shapes)) { - shapesTable.add([shapeName]); -} -/* -Data(shapesTable).Scenario('Check transformer existing for different shapes, their amount and modes.', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - const bbox1 = { - x: 100, - y: 100, - width: 200, - height: 200, - }; - const bbox2 = { - x: 400, - y: 100, - width: 200, - height: 200, - }; - const getCenter = bbox => [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2]; - let isTransformerExist; - - LabelStudio.init(getParamsWithLabels(shapeName)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - - // Draw two regions - I.pressKey('1'); - drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); - AtSidebar.seeRegions(1); - I.pressKey('1'); - drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); - AtSidebar.seeRegions(2); - - // Check that it wasn't a cause to show a transformer - isTransformerExist = await AtImageView.isTransformerExist(); - assert.strictEqual(isTransformerExist, false); - - // Select the first region - AtImageView.clickAt(...getCenter(bbox1)); - AtSidebar.seeSelectedRegion(); - - // Match if transformer exist with expectations in single selected mode - isTransformerExist = await AtImageView.isTransformerExist(); - assert.strictEqual(isTransformerExist, Shape.hasTransformer); - - // Match if rotator at transformer exist with expectations in single selected mode - isTransformerExist = await AtImageView.isRotaterExist(); - assert.strictEqual(isTransformerExist, Shape.hasRotator); - - // Switch to move tool - I.pressKey('v'); - - // Match if rotator at transformer exist with expectations in single selected mode with move tool chosen - isTransformerExist = await AtImageView.isTransformerExist(); - assert.strictEqual(isTransformerExist, Shape.hasMoveToolTransformer); - - // Deselect the previous selected region - I.pressKey(['u']); - - // Select 2 regions - AtImageView.drawThroughPoints([ - [bbox1.x - 5, bbox1.y - 5], - [bbox2.x + bbox2.width + 5, bbox2.y + bbox2.height + 5], - ], 'steps', 10); - - // Match if transformer exist with expectations in multiple selected mode - isTransformerExist = await AtImageView.isTransformerExist(); - assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionTransformer); - - // Match if rotator exist with expectations in multiple selected mode - isTransformerExist = await AtImageView.isRotaterExist(); - assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionRotator); -}); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)) - .Scenario('Resizing a single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); - - // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150} - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView); - AtSidebar.seeRegions(1); - - // Select the shape - AtImageView.clickAt(100, 100); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - - // Transform the shape - // Move the top anchor up for 50px (limited by image border) => {x1:50,y1:0,x2:150,y2:150} - AtImageView.drawByDrag(100, 50, 0, -100); - // Move the left anchor left for 50px (limited by image border) => {x1:0,y1:0,x2:150,y2:150} - AtImageView.drawByDrag(50, 75, -300, -100); - // Move the right anchor left for 50px => {x1:0,y1:0,x2:100,y2:150} - AtImageView.drawByDrag(150, 75, -50, 0); - // Move the bottom anchor down for 100px => {x1:0,y1:0,x2:100,y2:250} - AtImageView.drawByDrag(50, 150, 10, 100); - // Move the right-bottom anchor right for 200px and down for 50px => {x1:0,y1:0,x2:300,y2:300} - AtImageView.drawByDrag(100, 250, 200, 50); - // Check resulting sizes - const rectangleResult = await LabelStudio.serialize(); - const exceptedResult = Shape.byBBox(0, 0, 300, 300).result; - - Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult)); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)) - .Scenario('Resizing a single region with zoom', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - - LabelStudio.setFeatureFlags({ - 'ff_front_dev_2394_zoomed_transforms_260522_short': true, - }); - - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); - - // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150} - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, 50, 50, 300, 300, AtImageView); - AtSidebar.seeRegions(1); - - // Select the shape - AtImageView.clickAt(100, 100); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - AtImageView.setZoom(3, 0, 0); - - // Transform the shape - AtImageView.drawByDrag(150, 150, -150, -150); - I.wait(1); - - AtImageView.drawByDrag(0, 0, -300, -100); - I.wait(1); - - AtImageView.drawByDrag(0, 0, 150, 150); - I.wait(1); - - // Check resulting sizes - const rectangleResult = await LabelStudio.serialize(); - - I.wait(10); - - const exceptedResult = Shape.byBBox(50, 50, 300, 300).result; - - Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult), 0); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Simple rotating', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - // Draw a region in bbox {x1:40%,y1:40%,x2:60%,y2:60%} - const rectangle = { - x: canvasSize.width * .4, - y: canvasSize.height * .4, - width: canvasSize.width * .2, - height: canvasSize.height * .2, - }; - const rectangleCenter = { - x: rectangle.x + rectangle.width / 2, - y: rectangle.y + rectangle.height / 2, - }; - - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); - AtSidebar.seeRegions(1); - - - // Select the shape and check that transformer appears - AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: rectangleCenter.x, - y: rectangle.y - 50, - }; - - // Rotate for 45 degrees clockwise - AtImageView.drawThroughPoints( - [ - [rotatorPosition.x, rotatorPosition.y], - [rectangleCenter.x + 500, rectangleCenter.y - 500], - ], 'steps', 5); - - // Check resulting rotation - const rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance(Math.round(rectangleResult[0].value.rotation), 45); - - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Rotating of unrotatable region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - // Draw a region which we cannot rotate 'cause of position near the image's border {x1:0,y1:20%,x2:20%,y2:50%} - const rectangle = { - x: 0, - y: canvasSize.height * .2, - width: canvasSize.width * .2, - height: canvasSize.height * .3, - }; - const rectangleCenter = { - x: rectangle.x + rectangle.width / 2, - y: rectangle.y + rectangle.height / 2, - }; - - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); - AtSidebar.seeRegions(1); - - // Select the shape and check that transformer appears - AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: rectangleCenter.x, - y: rectangle.y - 50, - }; - - // Rotate for 45 degrees clockwise - AtImageView.drawByDrag(rotatorPosition.x, rotatorPosition.y, rectangleCenter.y - rotatorPosition.y + 100, -100); - - // Check the region hasn't been rotated - const rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 0); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Broke the limits with rotation', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - { - // Draw a region which have limitation at rotating by bbox {x1:5,y1:100,x2:305,y2:350} - const rectangle = { - x: 5, - y: 100, - width: 300, - height: 300, - }; - const rectangleCenter = { - x: rectangle.x + rectangle.width / 2, - y: rectangle.y + rectangle.height / 2, - }; - - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); - AtSidebar.seeRegions(1); - - // Select the shape and check that transformer appears - AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: rectangleCenter.x, - y: rectangle.y - 50, - }; - - // Rotate for 45 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [rectangleCenter.x + 500, rectangleCenter.y - 500], - ], 'steps', 200); - - // Check that we cannot rotate it like this - let rectangleResult = await LabelStudio.serialize(); - - assert.notStrictEqual( - Math.round(rectangleResult[0].value.rotation), - 0, - 'Region must be rotated', - ); - assert.notStrictEqual( - Math.round(rectangleResult[0].value.rotation), - 45, - 'Angle must not be 45 degrees', - ); - - // Undo changes - I.pressKey(['CommandOrControl', 'z']); - - // Rotate for 90 degrees clockwise instead - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [rectangle.x + rectangle.width + 100, rectangleCenter.y], - [rectangle.x + rectangle.width + 200, rectangleCenter.y], - ], 'steps', 200); - - // Check the resulted rotation - rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90, 'Angle must be 90 degrees'); - // remove region - I.pressKey('Backspace'); - } - - I.say('Check that it works same way with right border'); - - { - // Draw a region which have limitation at rotating by bbox {x1:100% - 305,y1:100,x2:100% - 5,y2:350} - const rectangle = { - x: canvasSize.width - 305, - y: 100, - width: 300, - height: 300, - }; - const rectangleCenter = { - x: rectangle.x + rectangle.width / 2, - y: rectangle.y + rectangle.height / 2, - }; - - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); - AtSidebar.seeRegions(1); - - // Select the shape and check that transformer appears - AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: rectangleCenter.x, - y: rectangle.y - 50, - }; - - // Rotate for 45 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [rectangleCenter.x + 500, rectangleCenter.y - 500], - ], 'steps', 200); - - // Check the resulted rotation - let rectangleResult = await LabelStudio.serialize(); - - assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 0); - assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 45); - - // Undo changes - I.pressKey(['CommandOrControl', 'z']); - - // Rotate for 90 degrees clockwise instead - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [rectangle.x + rectangle.width + 100, rectangleCenter.y], - [rectangle.x + rectangle.width + 200, rectangleCenter.y], - ], 'steps', 200); - - // Check that we cannot rotate it like this - rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90); - } - - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Check the initial rotation of transformer for the single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - - const bbox = { - x: 100, - y: 100, - width: 100, - height: 100, - }; - const bboxCenter = { - x: bbox.x + bbox.width / 2, - y: bbox.y + bbox.height / 2, - }; - - // Draw a region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView); - AtSidebar.seeRegions(1); - - // Select it - AtImageView.clickAt(bboxCenter.x, bboxCenter.y); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - // The rotator anchor must be above top anchor by 50 pixels - let rotatorPosition = { - x: bboxCenter.x, - y: bbox.y - 50, - }; - - // Rotate for 90 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [bbox.x + bbox.width + 100, bboxCenter.y], - [bbox.x + bbox.width + 200, bboxCenter.y], - ], 'steps', 10); - - // Unselect current region - I.pressKey('u'); - AtSidebar.dontSeeSelectedRegion(); - - // Select it again - AtImageView.clickAt(bboxCenter.x, bboxCenter.y); - AtSidebar.seeSelectedRegion(); - - // The trick is that we turn it further, based on the assumption that transformer appears in rotated state on region selection - // So let's try to rotate it - // The rotator anchor must be to the right of the right anchor by 50 pixels - - rotatorPosition = { - x: bbox.x + bbox.width + 50, - y: bboxCenter.y, - }; - - // Rotate for 90 degrees clockwise once again - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [bboxCenter.x, bbox.y + bbox.height + 100], - [bboxCenter.x, bbox.y + bbox.height + 200], - ], 'steps', 10); - - // Check that region has been rotated for 180 degrees - const rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Check the initial rotation of transformer for the couple of regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - - const bbox1 = { - x: 100, - y: 100, - width: 40, - height: 40, - }; - - const bbox2 = { - x: 160, - y: 160, - width: 40, - height: 40, - }; - - const transformerBbox = { - x: bbox1.x, - y: bbox1.y, - width: bbox2.x + bbox2.width - bbox1.x, - height: bbox2.y + bbox2.height - bbox1.y, - }; - const transformerBboxCenter = { - x: transformerBbox.x + transformerBbox.width / 2, - y: transformerBbox.y + transformerBbox.height / 2, - }; - - // Draw the first region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); - AtSidebar.seeRegions(1); - - // Draw the second region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); - AtSidebar.seeRegions(2); - - // Switch to move tool and select them - I.pressKey('v'); - AtImageView.drawThroughPoints([ - [transformerBbox.x - 20, transformerBbox.y - 20], - [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], - ]); - AtSidebar.seeSelectedRegion(); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: transformerBboxCenter.x, - y: transformerBbox.y - 50, - }; - - // Rotate for 180 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [transformerBboxCenter.x + 100, transformerBboxCenter.y + 100], - [transformerBboxCenter.x, transformerBboxCenter.y + 100], - [transformerBboxCenter.x, transformerBboxCenter.y + 200], - ], 'steps', 10); - - // Unselect current regions - I.pressKey('u'); - AtSidebar.dontSeeSelectedRegion(); - - // Select them again - AtImageView.drawThroughPoints([ - [transformerBbox.x - 20, transformerBbox.y - 20], - [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], - ]); - AtSidebar.seeSelectedRegion(); - - // So we have couple of rotated regions, let's check if rotates still appears above the top anchor - - // Rotate for 90 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [transformerBboxCenter.x + 100, transformerBboxCenter.y], - [transformerBboxCenter.x + 200, transformerBboxCenter.y], - ], 'steps', 10); - - // Check that region has been rotated for (180 + 90) degrees - const rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180 + 90); - }); - -// KeyPoints are transformed unpredictable so for now just skip them -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer && shapeName !== 'KeyPoint')) - .Scenario('Transforming of multiple regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); - - const bbox1 = { - x: 100, - y: 100, - width: 50, - height: 50, - }; - - const bbox2 = { - x: 150, - y: 150, - width: 50, - height: 50, - }; - - const transformerBbox = { - x: bbox1.x, - y: bbox1.y, - width: bbox2.x + bbox2.width - bbox1.x, - height: bbox2.y + bbox2.height - bbox1.y, - }; - const transformerBboxCenter = { - get x() { - return transformerBbox.x + transformerBbox.width / 2; - }, - get y() { - return transformerBbox.y + transformerBbox.height / 2; - }, - }; - - // Draw the first region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); - AtSidebar.seeRegions(1); - - // Draw the second region - I.pressKey(Shape.hotKey); - I.pressKeyDown('Control'); - drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); - I.pressKeyUp('Control'); - AtSidebar.seeRegions(2); - - // Switch to move tool and select them - I.pressKey('v'); - AtImageView.drawThroughPoints([ - [transformerBbox.x - 20, transformerBbox.y - 20], - [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], - ]); - AtSidebar.seeSelectedRegion(); - // Scale the shapes vertically - AtImageView.drawByDrag(transformerBboxCenter.x, transformerBbox.y + transformerBbox.height, 0, 50); - transformerBbox.height += 50; - AtSidebar.seeSelectedRegion(); - // Scale the shapes horizontally - AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBboxCenter.y, 50, 0); - transformerBbox.width += 50; - AtSidebar.seeSelectedRegion(); - // Scale the shapes in both directions - AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBbox.y + transformerBbox.height, 50, 50); - transformerBbox.height += 50; - transformerBbox.width += 50; - AtSidebar.seeSelectedRegion(); - - // Check resulting sizes - const rectangleResult = await LabelStudio.serialize(); - const exceptedResult1 = Shape.byBBox(bbox1.x, bbox1.y, bbox1.width + 50, bbox1.height + 50).result; - const exceptedResult2 = Shape.byBBox(bbox2.x + 50, bbox2.y + 50, bbox2.width + 50, bbox2.height + 50).result; - - Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1)); - Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2)); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer)) - .Scenario('Move regions by drag', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); - - const bbox1 = { - x: 100, - y: 100, - width: 20, - height: 20, - }; - const bbox1Center = { - x: bbox1.x + bbox1.width / 2, - y: bbox1.y + bbox1.height / 2, - }; - - const bbox2 = { - x: 140, - y: 140, - width: 20, - height: 20, - }; - const bbox2Center = { - x: bbox2.x + bbox2.width / 2, - y: bbox2.y + bbox2.height / 2, - }; - - const transformerBbox = { - x: bbox1.x, - y: bbox1.y, - width: bbox2.x + bbox2.width - bbox1.x, - height: bbox2.y + bbox2.height - bbox1.y, - }; - const transformerBboxCenter = { - x: transformerBbox.x + transformerBbox.width / 2, - y: transformerBbox.y + transformerBbox.height / 2, - }; - - // Draw the first region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); - AtSidebar.seeRegions(1); - - // Draw the second region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); - AtSidebar.seeRegions(2); - - if (shapeName === 'KeyPoint') { - // Draw more points to get more space in transformer - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox1.x, bbox1.y, 0, 0, AtImageView); - AtSidebar.seeRegions(3); - - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox2.x + bbox2.width, bbox2.y + bbox2.height, 0, 0, AtImageView); - AtSidebar.seeRegions(4); - } - - // Switch to move tool and select them - I.pressKey('v'); - AtImageView.drawThroughPoints([ - [transformerBbox.x - 20, transformerBbox.y - 20], - [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], - ]); - AtSidebar.seeSelectedRegion(); - - const dragShapes = (startPoint, shift, rememberShift = true) => { - AtImageView.drawThroughPoints([ - [startPoint.x, startPoint.y], - [startPoint.x + shift.x, startPoint.y + shift.y], - [startPoint.x + shift.x, startPoint.y + shift.y], - ], 'steps', 10); - AtSidebar.seeSelectedRegion(); - - if (rememberShift) { - bbox1Center.x += shift.x; - bbox1Center.y += shift.y; - bbox2Center.x += shift.x; - bbox2Center.y += shift.y; - transformerBboxCenter.x += shift.x; - transformerBboxCenter.y += shift.y; - } - }; - - // Drag shapes by holding onto the first region - dragShapes(bbox1Center, { x: 100, y: 0 }); - // Drag shapes by holding onto the second region - dragShapes(bbox2Center, { x: 0, y: 100 }); - // Drag shapes by holding onto the transformer background - dragShapes(transformerBboxCenter, { x: 150, y: 150 }, false); - // Move back throught history to check that transformer's background moving with it - I.pressKey(['Control', 'z']); - // Drag shapes by holding onto the transformer background again - dragShapes(transformerBboxCenter, { x: 100, y: 100 }, false); - - - // Check that dragging was successful - const rectangleResult = await LabelStudio.serialize(); - const exceptedResult1 = Shape.byBBox(bbox1.x + 200, bbox1.y + 200, bbox1.width, bbox1.height).result; - const exceptedResult2 = Shape.byBBox(bbox2.x + 200, bbox2.y + 200, bbox2.width, bbox2.height).result; - - Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1)); - Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2)); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Limitation of dragging a single rotated region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - const bbox = { - x: canvasSize.width / 2 - 50, - y: canvasSize.height / 2 - 50, - width: 100, - height: 100, - }; - const bboxCenter = { - x: bbox.x + bbox.width / 2, - y: bbox.y + bbox.height / 2, - }; - - // Draw a region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView); - AtSidebar.seeRegions(1); - - // Select it - AtImageView.clickAt(bboxCenter.x, bboxCenter.y); - AtSidebar.seeSelectedRegion(); - - // Switch to move tool to force appearance of transformer - I.pressKey('v'); - const isTransformerExist = await AtImageView.isTransformerExist(); - - assert.strictEqual(isTransformerExist, true); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: bboxCenter.x, - y: bbox.y - 50, - }; - - // Rotate for 180 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [bboxCenter.x + 100, bboxCenter.y], - [bboxCenter.x, bboxCenter.y + 100], - [bboxCenter.x, bboxCenter.y + 200], - ], 'steps', 10); - - // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image - let rectangleResult; - - I.say('Drag the region over the left border'); - AtImageView.drawThroughPoints([ - [bboxCenter.x, bboxCenter.y], - [-500, bboxCenter.y], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.x * canvasSize.width / 100, - Shape.byBBox(bbox.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x, - ); - // reset position by undo - I.pressKey(['Control', 'z']); - - I.say('Drag the region over the top border'); - AtImageView.drawThroughPoints([ - [bboxCenter.x, bboxCenter.y], - [bboxCenter.x, -500], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.y * canvasSize.height / 100, - Shape.byBBox(bbox.x + bbox.width, bbox.height, -bbox.width, -bbox.height).result.y, - ); - // reset position by undo - I.pressKey(['Control', 'z']); - - I.say('Drag the region over the right border'); - AtImageView.drawThroughPoints([ - [bboxCenter.x, bboxCenter.y], - [canvasSize.width + 500, bboxCenter.y], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.x * canvasSize.width / 100, - Shape.byBBox(canvasSize.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x, - ); - // reset position by undo - I.pressKey(['Control', 'z']); - - I.say('Drag the region over the bottom border'); - AtImageView.drawThroughPoints([ - [bboxCenter.x, bboxCenter.y], - [bboxCenter.x, canvasSize.height + 500], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.y * canvasSize.height / 100, - Shape.byBBox(bbox.x + bbox.width, canvasSize.height, -bbox.width, -bbox.height).result.y, - ); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) - .Scenario('Limitation of dragging a couple of rotated regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - const bbox1 = { - x: canvasSize.width / 2 - 50, - y: canvasSize.height / 2 - 50, - width: 50, - height: 50, - }; - - const bbox2 = { - x: canvasSize.width / 2, - y: canvasSize.height / 2, - width: 50, - height: 50, - }; - - const transformerBbox = { - x: bbox1.x, - y: bbox1.y, - width: bbox2.x + bbox2.width - bbox1.x, - height: bbox2.y + bbox2.height - bbox1.y, - }; - const transformerBboxCenter = { - get x() { - return transformerBbox.x + transformerBbox.width / 2; - }, - get y() { - return transformerBbox.y + transformerBbox.height / 2; - }, - }; - - // Draw the first region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); - AtSidebar.seeRegions(1); - - // Draw the second region - I.pressKey(Shape.hotKey); - I.pressKeyDown('Control'); - drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); - I.pressKeyUp('Control'); - AtSidebar.seeRegions(2); - - // Select them by move tool - I.pressKey('v'); - AtImageView.drawThroughPoints( - [ - [transformerBbox.x - 50, transformerBbox.y - 50], - [transformerBbox.x + transformerBbox.width + 50, transformerBbox.y + transformerBbox.height + 50], - ], 'steps', 10, - ); - AtSidebar.seeSelectedRegion(); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: transformerBboxCenter.x, - y: transformerBbox.y - 50, - }; - - // Rotate for 180 degrees clockwise - AtImageView.drawThroughPoints([ - [rotatorPosition.x, rotatorPosition.y], - [transformerBboxCenter.x + 100, transformerBboxCenter.y], - [transformerBboxCenter.x, transformerBboxCenter.y + 100], - [transformerBboxCenter.x, transformerBboxCenter.y + 200], - ], 'steps', 10); - - // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image - let rectangleResult; - - I.say('Drag the region over the left border'); - AtImageView.drawThroughPoints([ - [transformerBboxCenter.x, transformerBboxCenter.y], - [-500, transformerBboxCenter.y], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.x * canvasSize.width / 100, - Shape.byBBox(transformerBbox.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x, - ); - Asserts.deepEqualWithTolerance( - rectangleResult[1].value.x * canvasSize.width / 100, - Shape.byBBox(bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x, - ); - // reset position by undo - I.pressKey(['Control', 'z']); - - I.say('Drag the region over the top border'); - AtImageView.drawThroughPoints([ - [transformerBboxCenter.x, transformerBboxCenter.y], - [transformerBboxCenter.x, -500], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.y * canvasSize.height / 100, - Shape.byBBox(transformerBbox.x + transformerBbox.width, transformerBbox.height, -bbox1.width, -bbox1.height).result.y, - ); - Asserts.deepEqualWithTolerance( - rectangleResult[1].value.y * canvasSize.height / 100, - Shape.byBBox(transformerBbox.x + bbox2.width, bbox2.height, -bbox2.width, -bbox2.height).result.y, - ); - // reset position by undo - I.pressKey(['Control', 'z']); - - I.say('Drag the region over the right border'); - AtImageView.drawThroughPoints([ - [transformerBboxCenter.x, transformerBboxCenter.y], - [canvasSize.width + 500, transformerBboxCenter.y], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.x * canvasSize.width / 100, - Shape.byBBox(canvasSize.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x, - ); - Asserts.deepEqualWithTolerance( - rectangleResult[1].value.x * canvasSize.width / 100, - Shape.byBBox(canvasSize.width - transformerBbox.width + bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x, - ); - // reset position by undo - I.pressKey(['Control', 'z']); - - I.say('Drag the region over the bottom border'); - AtImageView.drawThroughPoints([ - [transformerBboxCenter.x, transformerBboxCenter.y], - [transformerBboxCenter.x, canvasSize.height + 500], - ], 'steps', 20); - AtSidebar.seeSelectedRegion(); - // moving of the region should be constrained by borders - rectangleResult = await LabelStudio.serialize(); - Asserts.deepEqualWithTolerance( - rectangleResult[0].value.y * canvasSize.height / 100, - Shape.byBBox(transformerBbox.x + transformerBbox.width, canvasSize.height, -bbox1.width, -bbox1.height).result.y, - ); - Asserts.deepEqualWithTolerance( - rectangleResult[1].value.y * canvasSize.height / 100, - Shape.byBBox(transformerBbox.x + bbox2.width, canvasSize.height - transformerBbox.height + bbox2.height, -bbox2.width, -bbox2.height).result.y, - ); - }); - -Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasRotator)) - .Scenario('Rotating the region near the border', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { - const { shapeName } = current; - const Shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - const bbox = { - x: canvasSize.width - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50, - y: canvasSize.height - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50, - width: 100, - height: 100, - }; - - const bboxCenter = { - x: bbox.x + bbox.width / 2, - y: bbox.y + bbox.height / 2, - }; - - // Draw the region - I.pressKey(Shape.hotKey); - drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView); - AtSidebar.seeRegions(1); - - // Select it - AtImageView.clickAt(bboxCenter.x, bboxCenter.y); - AtSidebar.seeSelectedRegion(); - - // The rotator anchor must be above top anchor by 50 pixels - const rotatorPosition = { - x: bboxCenter.x, - y: bbox.y - 50, - }; - - // Check 7 different rotations - const rotatorWayPoints = [[rotatorPosition.x, rotatorPosition.y]]; - const angle45 = Math.PI / 4; - - for (let i = 0; i < 8; i++) { - const angle = angle45 * i; - - rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 100, bboxCenter.y - Math.cos(angle) * 100]); - rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 1000, bboxCenter.y - Math.cos(angle) * 1000]); - - // Rotate clockwise by 45 * i degrees - AtImageView.drawThroughPoints(rotatorWayPoints, 'steps', 10); - AtSidebar.seeSelectedRegion(); - // Check that rotating was successful - const rectangleResult = await LabelStudio.serialize(); - - Asserts.deepEqualWithTolerance( - Math.round(rectangleResult[0].value.rotation), - 45 * i, - ); - - // undo rotation - I.pressKey(['Control', 'z']); - // clear unnecessary waypoints - rotatorWayPoints.pop(); - } - }); -*/ \ No newline at end of file +const assert = require('assert'); +const Asserts = require('../utils/asserts'); +const Helpers = require('./helpers'); + +Feature('Image transformer'); + +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; + +const annotationEmpty = { + id: '1000', + result: [], +}; + +const getParamsWithShape = (shape, params = '') => ({ + config: ` + + + <${shape} ${params} name="tag" toName="img" /> + `, + data: { image: IMAGE }, + annotations: [annotationEmpty], +}); + +const getParamsWithLabels = (shape) => ({ + config: ` + + + <${shape}Labels name="tag" toName="img"> + `, + data: { image: IMAGE }, + annotations: [annotationEmpty], +}); + +const shapes = { + Rectangle: { + drawAction: 'drawByDrag', + hasTransformer: true, + hasRotator: true, + hasMoveToolTransformer: true, + hasMultiSelectionTransformer: true, + hasMultiSelectionRotator: true, + hotKey: 'r', + byBBox(x, y, width, height) { + return { + params: [x, y, width, height], + result: { width, height, rotation: 0, x, y }, + }; + }, + }, + Ellipse: { + drawAction: 'drawByDrag', + hasTransformer: true, + hasRotator: true, + hasMoveToolTransformer: true, + hasMultiSelectionTransformer: true, + hasMultiSelectionRotator: true, + hotKey: 'o', + byBBox(x, y, width, height) { + return { + params: [x + width / 2, y + height / 2, width / 2, height / 2], + result: { radiusX: width / 2, radiusY: height / 2, rotation: 0, x: x + width / 2, y: y + height / 2 }, + }; + }, + }, + Polygon: { + drawAction: 'drawByClickingPoints', + hasTransformer: false, + hasRotator: false, + hasMoveToolTransformer: true, + hasMultiSelectionTransformer: true, + hasMultiSelectionRotator: false, + hotKey: 'p', + byBBox(x, y, width, height) { + const points = []; + + points.push([x, y]); + points.push([x + width, y]); + points.push([x + width / 2, y + height / 2]); + points.push([x + width, y + height]); + points.push([x, y + height]); + return { + params: [[...points, points[0]]], + result: { + points, + }, + }; + }, + }, + KeyPoint: { + drawAction: 'clickAt', + hasTransformer: false, + hasRotator: false, + hasMoveToolTransformer: false, + hasMultiSelectionTransformer: true, + hasMultiSelectionRotator: false, + hotKey: 'k', + params: 'strokeWidth="2"', + byBBox(x, y, width, height) { + return { + params: [x + width / 2, y + height / 2], + result: { + x: x + width / 2, + y: y + height / 2, + width: 2, + }, + }; + }, + }, +}; + +function drawShapeByBbox(Shape, x, y, width, height, where) { + where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params); +} + +const shapesTable = new DataTable(['shapeName']); + +for (const shapeName of Object.keys(shapes)) { + shapesTable.add([shapeName]); +} + +Data(shapesTable).Scenario('Check transformer existing for different shapes, their amount and modes.', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + const bbox1 = { + x: 100, + y: 100, + width: 200, + height: 200, + }; + const bbox2 = { + x: 400, + y: 100, + width: 200, + height: 200, + }; + const getCenter = bbox => [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2]; + let isTransformerExist; + + LabelStudio.init(getParamsWithLabels(shapeName)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + + // Draw two regions + I.pressKey('1'); + drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); + AtSidebar.seeRegions(1); + I.pressKey('1'); + drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); + AtSidebar.seeRegions(2); + + // Check that it wasn't a cause to show a transformer + isTransformerExist = await AtImageView.isTransformerExist(); + assert.strictEqual(isTransformerExist, false); + + // Select the first region + AtImageView.clickAt(...getCenter(bbox1)); + AtSidebar.seeSelectedRegion(); + + // Match if transformer exist with expectations in single selected mode + isTransformerExist = await AtImageView.isTransformerExist(); + assert.strictEqual(isTransformerExist, Shape.hasTransformer); + + // Match if rotator at transformer exist with expectations in single selected mode + isTransformerExist = await AtImageView.isRotaterExist(); + assert.strictEqual(isTransformerExist, Shape.hasRotator); + + // Switch to move tool + I.pressKey('v'); + + // Match if rotator at transformer exist with expectations in single selected mode with move tool chosen + isTransformerExist = await AtImageView.isTransformerExist(); + assert.strictEqual(isTransformerExist, Shape.hasMoveToolTransformer); + + // Deselect the previous selected region + I.pressKey(['u']); + + // Select 2 regions + AtImageView.drawThroughPoints([ + [bbox1.x - 5, bbox1.y - 5], + [bbox2.x + bbox2.width + 5, bbox2.y + bbox2.height + 5], + ], 'steps', 10); + + // Match if transformer exist with expectations in multiple selected mode + isTransformerExist = await AtImageView.isTransformerExist(); + assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionTransformer); + + // Match if rotator exist with expectations in multiple selected mode + isTransformerExist = await AtImageView.isRotaterExist(); + assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionRotator); +}); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)) + .Scenario('Resizing a single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); + + // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150} + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView); + AtSidebar.seeRegions(1); + + // Select the shape + AtImageView.clickAt(100, 100); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + + // Transform the shape + // Move the top anchor up for 50px (limited by image border) => {x1:50,y1:0,x2:150,y2:150} + AtImageView.drawByDrag(100, 50, 0, -100); + // Move the left anchor left for 50px (limited by image border) => {x1:0,y1:0,x2:150,y2:150} + AtImageView.drawByDrag(50, 75, -300, -100); + // Move the right anchor left for 50px => {x1:0,y1:0,x2:100,y2:150} + AtImageView.drawByDrag(150, 75, -50, 0); + // Move the bottom anchor down for 100px => {x1:0,y1:0,x2:100,y2:250} + AtImageView.drawByDrag(50, 150, 10, 100); + // Move the right-bottom anchor right for 200px and down for 50px => {x1:0,y1:0,x2:300,y2:300} + AtImageView.drawByDrag(100, 250, 200, 50); + // Check resulting sizes + const rectangleResult = await LabelStudio.serialize(); + const exceptedResult = Shape.byBBox(0, 0, 300, 300).result; + + Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult)); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)) + .Scenario('Resizing a single region with zoom', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + LabelStudio.setFeatureFlags({ + 'ff_front_dev_2394_zoomed_transforms_260522_short': true, + 'fflag_fix_front_dev_3377_image_regions_shift_on_resize_280922_short': true, + 'fflag_fix_front_dev_3793_relative_coords_short': true, + }); + + I.amOnPage('/'); + + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const naturalSize = await AtImageView.getNaturalSize(); + const canvasSize = await AtImageView.getCanvasSize(); + // region sizes are relative (0 to 100) so we have to convert sizes we use for them... + // ...relatively to displayed image size, which is canvas size when we open the page + const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); + + // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150} + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, 50, 50, 300, 300, AtImageView); + AtSidebar.seeRegions(1); + + // Select the shape + AtImageView.clickAt(100, 100); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // we display an image to fit to canvas size on page load, so initial zoom is not 1; + // to do an x3 zoom we have to calculate current zoom and multiply it by 3 + AtImageView.setZoom(3 * canvasSize.width / naturalSize.width, 0, 0); + + // Transform the shape + AtImageView.drawByDrag(150, 150, -150, -150); + + AtImageView.drawByDrag(0, 0, -300, -100); + + AtImageView.drawByDrag(0, 0, 150, 150); + + // Check resulting sizes + const rectangleResult = await LabelStudio.serialize(); + const exceptedResult = Shape.byBBox(50, 50, 300, 300).result; + + Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult), 2); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Simple rotating', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + // Draw a region in bbox {x1:40%,y1:40%,x2:60%,y2:60%} + const rectangle = { + x: canvasSize.width * .4, + y: canvasSize.height * .4, + width: canvasSize.width * .2, + height: canvasSize.height * .2, + }; + const rectangleCenter = { + x: rectangle.x + rectangle.width / 2, + y: rectangle.y + rectangle.height / 2, + }; + + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); + AtSidebar.seeRegions(1); + + + // Select the shape and check that transformer appears + AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: rectangleCenter.x, + y: rectangle.y - 50, + }; + + // Rotate for 45 degrees clockwise + AtImageView.drawThroughPoints( + [ + [rotatorPosition.x, rotatorPosition.y], + [rectangleCenter.x + 500, rectangleCenter.y - 500], + ], 'steps', 5); + + // Check resulting rotation + const rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance(Math.round(rectangleResult[0].value.rotation), 45); + + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Rotating of unrotatable region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + // Draw a region which we cannot rotate 'cause of position near the image's border {x1:0,y1:20%,x2:20%,y2:50%} + const rectangle = { + x: 0, + y: canvasSize.height * .2, + width: canvasSize.width * .2, + height: canvasSize.height * .3, + }; + const rectangleCenter = { + x: rectangle.x + rectangle.width / 2, + y: rectangle.y + rectangle.height / 2, + }; + + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); + AtSidebar.seeRegions(1); + + // Select the shape and check that transformer appears + AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: rectangleCenter.x, + y: rectangle.y - 50, + }; + + // Rotate for 45 degrees clockwise + AtImageView.drawByDrag(rotatorPosition.x, rotatorPosition.y, rectangleCenter.y - rotatorPosition.y + 100, -100); + + // Check the region hasn't been rotated + const rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 0); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Broke the limits with rotation', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + { + // Draw a region which have limitation at rotating by bbox {x1:5,y1:100,x2:305,y2:350} + const rectangle = { + x: 5, + y: 100, + width: 300, + height: 300, + }; + const rectangleCenter = { + x: rectangle.x + rectangle.width / 2, + y: rectangle.y + rectangle.height / 2, + }; + + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); + AtSidebar.seeRegions(1); + + // Select the shape and check that transformer appears + AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: rectangleCenter.x, + y: rectangle.y - 50, + }; + + // Rotate for 45 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [rectangleCenter.x + 500, rectangleCenter.y - 500], + ], 'steps', 200); + + // Check that we cannot rotate it like this + let rectangleResult = await LabelStudio.serialize(); + + assert.notStrictEqual( + Math.round(rectangleResult[0].value.rotation), + 0, + 'Region must be rotated', + ); + assert.notStrictEqual( + Math.round(rectangleResult[0].value.rotation), + 45, + 'Angle must not be 45 degrees', + ); + + // Undo changes + I.pressKey(['CommandOrControl', 'z']); + + // Rotate for 90 degrees clockwise instead + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [rectangle.x + rectangle.width + 100, rectangleCenter.y], + [rectangle.x + rectangle.width + 200, rectangleCenter.y], + ], 'steps', 200); + + // Check the resulted rotation + rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90, 'Angle must be 90 degrees'); + // remove region + I.pressKey('Backspace'); + } + + I.say('Check that it works same way with right border'); + + { + // Draw a region which have limitation at rotating by bbox {x1:100% - 305,y1:100,x2:100% - 5,y2:350} + const rectangle = { + x: canvasSize.width - 305, + y: 100, + width: 300, + height: 300, + }; + const rectangleCenter = { + x: rectangle.x + rectangle.width / 2, + y: rectangle.y + rectangle.height / 2, + }; + + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView); + AtSidebar.seeRegions(1); + + // Select the shape and check that transformer appears + AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: rectangleCenter.x, + y: rectangle.y - 50, + }; + + // Rotate for 45 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [rectangleCenter.x + 500, rectangleCenter.y - 500], + ], 'steps', 200); + + // Check the resulted rotation + let rectangleResult = await LabelStudio.serialize(); + + assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 0); + assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 45); + + // Undo changes + I.pressKey(['CommandOrControl', 'z']); + + // Rotate for 90 degrees clockwise instead + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [rectangle.x + rectangle.width + 100, rectangleCenter.y], + [rectangle.x + rectangle.width + 200, rectangleCenter.y], + ], 'steps', 200); + + // Check that we cannot rotate it like this + rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90); + } + + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Check the initial rotation of transformer for the single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + + const bbox = { + x: 100, + y: 100, + width: 100, + height: 100, + }; + const bboxCenter = { + x: bbox.x + bbox.width / 2, + y: bbox.y + bbox.height / 2, + }; + + // Draw a region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView); + AtSidebar.seeRegions(1); + + // Select it + AtImageView.clickAt(bboxCenter.x, bboxCenter.y); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // The rotator anchor must be above top anchor by 50 pixels + let rotatorPosition = { + x: bboxCenter.x, + y: bbox.y - 50, + }; + + // Rotate for 90 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [bbox.x + bbox.width + 100, bboxCenter.y], + [bbox.x + bbox.width + 200, bboxCenter.y], + ], 'steps', 10); + + // Unselect current region + I.pressKey('u'); + AtSidebar.dontSeeSelectedRegion(); + + // Select it again + AtImageView.clickAt(bboxCenter.x, bboxCenter.y); + AtSidebar.seeSelectedRegion(); + + // The trick is that we turn it further, based on the assumption that transformer appears in rotated state on region selection + // So let's try to rotate it + // The rotator anchor must be to the right of the right anchor by 50 pixels + + rotatorPosition = { + x: bbox.x + bbox.width + 50, + y: bboxCenter.y, + }; + + // Rotate for 90 degrees clockwise once again + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [bboxCenter.x, bbox.y + bbox.height + 100], + [bboxCenter.x, bbox.y + bbox.height + 200], + ], 'steps', 10); + + // Check that region has been rotated for 180 degrees + const rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Check the initial rotation of transformer for the couple of regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + + const bbox1 = { + x: 100, + y: 100, + width: 40, + height: 40, + }; + + const bbox2 = { + x: 160, + y: 160, + width: 40, + height: 40, + }; + + const transformerBbox = { + x: bbox1.x, + y: bbox1.y, + width: bbox2.x + bbox2.width - bbox1.x, + height: bbox2.y + bbox2.height - bbox1.y, + }; + const transformerBboxCenter = { + x: transformerBbox.x + transformerBbox.width / 2, + y: transformerBbox.y + transformerBbox.height / 2, + }; + + // Draw the first region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); + AtSidebar.seeRegions(1); + + // Draw the second region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); + AtSidebar.seeRegions(2); + + // Switch to move tool and select them + I.pressKey('v'); + AtImageView.drawThroughPoints([ + [transformerBbox.x - 20, transformerBbox.y - 20], + [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], + ]); + AtSidebar.seeSelectedRegion(); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: transformerBboxCenter.x, + y: transformerBbox.y - 50, + }; + + // Rotate for 180 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [transformerBboxCenter.x + 100, transformerBboxCenter.y + 100], + [transformerBboxCenter.x, transformerBboxCenter.y + 100], + [transformerBboxCenter.x, transformerBboxCenter.y + 200], + ], 'steps', 10); + + // Unselect current regions + I.pressKey('u'); + AtSidebar.dontSeeSelectedRegion(); + + // Select them again + AtImageView.drawThroughPoints([ + [transformerBbox.x - 20, transformerBbox.y - 20], + [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], + ]); + AtSidebar.seeSelectedRegion(); + + // So we have couple of rotated regions, let's check if rotates still appears above the top anchor + + // Rotate for 90 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [transformerBboxCenter.x + 100, transformerBboxCenter.y], + [transformerBboxCenter.x + 200, transformerBboxCenter.y], + ], 'steps', 10); + + // Check that region has been rotated for (180 + 90) degrees + const rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180 + 90); + }); + +// KeyPoints are transformed unpredictable so for now just skip them +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer && shapeName !== 'KeyPoint')) + .Scenario('Transforming of multiple regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); + + const bbox1 = { + x: 100, + y: 100, + width: 50, + height: 50, + }; + + const bbox2 = { + x: 150, + y: 150, + width: 50, + height: 50, + }; + + const transformerBbox = { + x: bbox1.x, + y: bbox1.y, + width: bbox2.x + bbox2.width - bbox1.x, + height: bbox2.y + bbox2.height - bbox1.y, + }; + const transformerBboxCenter = { + get x() { + return transformerBbox.x + transformerBbox.width / 2; + }, + get y() { + return transformerBbox.y + transformerBbox.height / 2; + }, + }; + + // Draw the first region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); + AtSidebar.seeRegions(1); + + // Draw the second region + I.pressKey(Shape.hotKey); + I.pressKeyDown('Control'); + drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); + I.pressKeyUp('Control'); + AtSidebar.seeRegions(2); + + // Switch to move tool and select them + I.pressKey('v'); + AtImageView.drawThroughPoints([ + [transformerBbox.x - 20, transformerBbox.y - 20], + [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], + ]); + AtSidebar.seeSelectedRegion(); + // Scale the shapes vertically + AtImageView.drawByDrag(transformerBboxCenter.x, transformerBbox.y + transformerBbox.height, 0, 50); + transformerBbox.height += 50; + AtSidebar.seeSelectedRegion(); + // Scale the shapes horizontally + AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBboxCenter.y, 50, 0); + transformerBbox.width += 50; + AtSidebar.seeSelectedRegion(); + // Scale the shapes in both directions + AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBbox.y + transformerBbox.height, 50, 50); + transformerBbox.height += 50; + transformerBbox.width += 50; + AtSidebar.seeSelectedRegion(); + + // Check resulting sizes + const rectangleResult = await LabelStudio.serialize(); + const exceptedResult1 = Shape.byBBox(bbox1.x, bbox1.y, bbox1.width + 50, bbox1.height + 50).result; + const exceptedResult2 = Shape.byBBox(bbox2.x + 50, bbox2.y + 50, bbox2.width + 50, bbox2.height + 50).result; + + Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1)); + Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2)); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer)) + .Scenario('Move regions by drag', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); + + const bbox1 = { + x: 100, + y: 100, + width: 20, + height: 20, + }; + const bbox1Center = { + x: bbox1.x + bbox1.width / 2, + y: bbox1.y + bbox1.height / 2, + }; + + const bbox2 = { + x: 140, + y: 140, + width: 20, + height: 20, + }; + const bbox2Center = { + x: bbox2.x + bbox2.width / 2, + y: bbox2.y + bbox2.height / 2, + }; + + const transformerBbox = { + x: bbox1.x, + y: bbox1.y, + width: bbox2.x + bbox2.width - bbox1.x, + height: bbox2.y + bbox2.height - bbox1.y, + }; + const transformerBboxCenter = { + x: transformerBbox.x + transformerBbox.width / 2, + y: transformerBbox.y + transformerBbox.height / 2, + }; + + // Draw the first region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); + AtSidebar.seeRegions(1); + + // Draw the second region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); + AtSidebar.seeRegions(2); + + if (shapeName === 'KeyPoint') { + // Draw more points to get more space in transformer + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox1.x, bbox1.y, 0, 0, AtImageView); + AtSidebar.seeRegions(3); + + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox2.x + bbox2.width, bbox2.y + bbox2.height, 0, 0, AtImageView); + AtSidebar.seeRegions(4); + } + + // Switch to move tool and select them + I.pressKey('v'); + AtImageView.drawThroughPoints([ + [transformerBbox.x - 20, transformerBbox.y - 20], + [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20], + ]); + AtSidebar.seeSelectedRegion(); + + const dragShapes = (startPoint, shift, rememberShift = true) => { + AtImageView.drawThroughPoints([ + [startPoint.x, startPoint.y], + [startPoint.x + shift.x, startPoint.y + shift.y], + [startPoint.x + shift.x, startPoint.y + shift.y], + ], 'steps', 10); + AtSidebar.seeSelectedRegion(); + + if (rememberShift) { + bbox1Center.x += shift.x; + bbox1Center.y += shift.y; + bbox2Center.x += shift.x; + bbox2Center.y += shift.y; + transformerBboxCenter.x += shift.x; + transformerBboxCenter.y += shift.y; + } + }; + + // Drag shapes by holding onto the first region + dragShapes(bbox1Center, { x: 100, y: 0 }); + // Drag shapes by holding onto the second region + dragShapes(bbox2Center, { x: 0, y: 100 }); + // Drag shapes by holding onto the transformer background + dragShapes(transformerBboxCenter, { x: 150, y: 150 }, false); + // Move back throught history to check that transformer's background moving with it + I.pressKey(['CommandOrControl', 'z']); + // Drag shapes by holding onto the transformer background again + dragShapes(transformerBboxCenter, { x: 100, y: 100 }, false); + + + // Check that dragging was successful + const rectangleResult = await LabelStudio.serialize(); + const exceptedResult1 = Shape.byBBox(bbox1.x + 200, bbox1.y + 200, bbox1.width, bbox1.height).result; + const exceptedResult2 = Shape.byBBox(bbox2.x + 200, bbox2.y + 200, bbox2.width, bbox2.height).result; + + Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1)); + Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2)); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Limitation of dragging a single rotated region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + const bbox = { + x: canvasSize.width / 2 - 50, + y: canvasSize.height / 2 - 50, + width: 100, + height: 100, + }; + const bboxCenter = { + x: bbox.x + bbox.width / 2, + y: bbox.y + bbox.height / 2, + }; + + // Draw a region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView); + AtSidebar.seeRegions(1); + + // Select it + AtImageView.clickAt(bboxCenter.x, bboxCenter.y); + AtSidebar.seeSelectedRegion(); + + // Switch to move tool to force appearance of transformer + I.pressKey('v'); + const isTransformerExist = await AtImageView.isTransformerExist(); + + assert.strictEqual(isTransformerExist, true); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: bboxCenter.x, + y: bbox.y - 50, + }; + + // Rotate for 180 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [bboxCenter.x + 100, bboxCenter.y], + [bboxCenter.x, bboxCenter.y + 100], + [bboxCenter.x, bboxCenter.y + 200], + ], 'steps', 10); + + // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image + let rectangleResult; + + I.say('Drag the region over the left border'); + AtImageView.drawThroughPoints([ + [bboxCenter.x, bboxCenter.y], + [-500, bboxCenter.y], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.x * canvasSize.width / 100, + Shape.byBBox(bbox.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x, + ); + // reset position by undo + I.pressKey(['CommandOrControl', 'z']); + + I.say('Drag the region over the top border'); + AtImageView.drawThroughPoints([ + [bboxCenter.x, bboxCenter.y], + [bboxCenter.x, -500], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.y * canvasSize.height / 100, + Shape.byBBox(bbox.x + bbox.width, bbox.height, -bbox.width, -bbox.height).result.y, + ); + // reset position by undo + I.pressKey(['CommandOrControl', 'z']); + + I.say('Drag the region over the right border'); + AtImageView.drawThroughPoints([ + [bboxCenter.x, bboxCenter.y], + [canvasSize.width + 500, bboxCenter.y], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.x * canvasSize.width / 100, + Shape.byBBox(canvasSize.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x, + ); + // reset position by undo + I.pressKey(['CommandOrControl', 'z']); + + I.say('Drag the region over the bottom border'); + AtImageView.drawThroughPoints([ + [bboxCenter.x, bboxCenter.y], + [bboxCenter.x, canvasSize.height + 500], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.y * canvasSize.height / 100, + Shape.byBBox(bbox.x + bbox.width, canvasSize.height, -bbox.width, -bbox.height).result.y, + ); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)) + .Scenario('Limitation of dragging a couple of rotated regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + const bbox1 = { + x: canvasSize.width / 2 - 50, + y: canvasSize.height / 2 - 50, + width: 50, + height: 50, + }; + + const bbox2 = { + x: canvasSize.width / 2, + y: canvasSize.height / 2, + width: 50, + height: 50, + }; + + const transformerBbox = { + x: bbox1.x, + y: bbox1.y, + width: bbox2.x + bbox2.width - bbox1.x, + height: bbox2.y + bbox2.height - bbox1.y, + }; + const transformerBboxCenter = { + get x() { + return transformerBbox.x + transformerBbox.width / 2; + }, + get y() { + return transformerBbox.y + transformerBbox.height / 2; + }, + }; + + // Draw the first region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView); + AtSidebar.seeRegions(1); + + // Draw the second region + I.pressKey(Shape.hotKey); + I.pressKeyDown('Control'); + drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView); + I.pressKeyUp('Control'); + AtSidebar.seeRegions(2); + + // Select them by move tool + I.pressKey('v'); + AtImageView.drawThroughPoints( + [ + [transformerBbox.x - 50, transformerBbox.y - 50], + [transformerBbox.x + transformerBbox.width + 50, transformerBbox.y + transformerBbox.height + 50], + ], 'steps', 10, + ); + AtSidebar.seeSelectedRegion(); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: transformerBboxCenter.x, + y: transformerBbox.y - 50, + }; + + // Rotate for 180 degrees clockwise + AtImageView.drawThroughPoints([ + [rotatorPosition.x, rotatorPosition.y], + [transformerBboxCenter.x + 100, transformerBboxCenter.y], + [transformerBboxCenter.x, transformerBboxCenter.y + 100], + [transformerBboxCenter.x, transformerBboxCenter.y + 200], + ], 'steps', 10); + + // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image + let rectangleResult; + + I.say('Drag the region over the left border'); + AtImageView.drawThroughPoints([ + [transformerBboxCenter.x, transformerBboxCenter.y], + [-500, transformerBboxCenter.y], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.x * canvasSize.width / 100, + Shape.byBBox(transformerBbox.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x, + ); + Asserts.deepEqualWithTolerance( + rectangleResult[1].value.x * canvasSize.width / 100, + Shape.byBBox(bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x, + ); + // reset position by undo + I.pressKey(['CommandOrControl', 'z']); + + I.say('Drag the region over the top border'); + AtImageView.drawThroughPoints([ + [transformerBboxCenter.x, transformerBboxCenter.y], + [transformerBboxCenter.x, -500], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.y * canvasSize.height / 100, + Shape.byBBox(transformerBbox.x + transformerBbox.width, transformerBbox.height, -bbox1.width, -bbox1.height).result.y, + ); + Asserts.deepEqualWithTolerance( + rectangleResult[1].value.y * canvasSize.height / 100, + Shape.byBBox(transformerBbox.x + bbox2.width, bbox2.height, -bbox2.width, -bbox2.height).result.y, + ); + // reset position by undo + I.pressKey(['CommandOrControl', 'z']); + + I.say('Drag the region over the right border'); + AtImageView.drawThroughPoints([ + [transformerBboxCenter.x, transformerBboxCenter.y], + [canvasSize.width + 500, transformerBboxCenter.y], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.x * canvasSize.width / 100, + Shape.byBBox(canvasSize.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x, + ); + Asserts.deepEqualWithTolerance( + rectangleResult[1].value.x * canvasSize.width / 100, + Shape.byBBox(canvasSize.width - transformerBbox.width + bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x, + ); + // reset position by undo + I.pressKey(['CommandOrControl', 'z']); + + I.say('Drag the region over the bottom border'); + AtImageView.drawThroughPoints([ + [transformerBboxCenter.x, transformerBboxCenter.y], + [transformerBboxCenter.x, canvasSize.height + 500], + ], 'steps', 20); + AtSidebar.seeSelectedRegion(); + // moving of the region should be constrained by borders + rectangleResult = await LabelStudio.serialize(); + Asserts.deepEqualWithTolerance( + rectangleResult[0].value.y * canvasSize.height / 100, + Shape.byBBox(transformerBbox.x + transformerBbox.width, canvasSize.height, -bbox1.width, -bbox1.height).result.y, + ); + Asserts.deepEqualWithTolerance( + rectangleResult[1].value.y * canvasSize.height / 100, + Shape.byBBox(transformerBbox.x + bbox2.width, canvasSize.height - transformerBbox.height + bbox2.height, -bbox2.width, -bbox2.height).result.y, + ); + }); + +Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasRotator)) + .Scenario('Rotating the region near the border', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => { + const { shapeName } = current; + const Shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.init(getParamsWithShape(shapeName, Shape.params)); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + const bbox = { + x: canvasSize.width - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50, + y: canvasSize.height - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50, + width: 100, + height: 100, + }; + + const bboxCenter = { + x: bbox.x + bbox.width / 2, + y: bbox.y + bbox.height / 2, + }; + + // Draw the region + I.pressKey(Shape.hotKey); + drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView); + AtSidebar.seeRegions(1); + + // Select it + AtImageView.clickAt(bboxCenter.x, bboxCenter.y); + AtSidebar.seeSelectedRegion(); + + // The rotator anchor must be above top anchor by 50 pixels + const rotatorPosition = { + x: bboxCenter.x, + y: bbox.y - 50, + }; + + // Check 7 different rotations + const rotatorWayPoints = [[rotatorPosition.x, rotatorPosition.y]]; + const angle45 = Math.PI / 4; + + for (let i = 0; i < 8; i++) { + const angle = angle45 * i; + + rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 100, bboxCenter.y - Math.cos(angle) * 100]); + rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 1000, bboxCenter.y - Math.cos(angle) * 1000]); + + // Rotate clockwise by 45 * i degrees + AtImageView.drawThroughPoints(rotatorWayPoints, 'steps', 10); + AtSidebar.seeSelectedRegion(); + // Check that rotating was successful + const rectangleResult = await LabelStudio.serialize(); + + Asserts.deepEqualWithTolerance( + Math.round(rectangleResult[0].value.rotation), + 45 * i, + ); + + // undo rotation + I.pressKey(['CommandOrControl', 'z']); + // clear unnecessary waypoints + rotatorWayPoints.pop(); + } + }); diff --git a/e2e/tests/image.zoom-rotate.test.js b/e2e/tests/image.zoom-rotate.test.js index 30ea619dbf..07ee2475f0 100644 --- a/e2e/tests/image.zoom-rotate.test.js +++ b/e2e/tests/image.zoom-rotate.test.js @@ -4,8 +4,7 @@ const assert = require('assert'); Feature('Zooming and rotating'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const BLUEVIOLET = { color: '#8A2BE2', diff --git a/e2e/tests/maxUsage.test.js b/e2e/tests/maxUsage.test.js index e00b75e119..fc21118380 100644 --- a/e2e/tests/maxUsage.test.js +++ b/e2e/tests/maxUsage.test.js @@ -1,341 +1,340 @@ -Feature('Max usage'); - -const IMAGE = 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg'; - -const createImageToolsConfig = ({ maxUsage }) => ` - - - - - - - - - -`; - -const createImageLabelsConfig = ({ maxUsage }) => ` - - - - - - - - - - - - - `; - -const shapes = { - Rectangle: { - drawAction: 'drawByDrag', - shortcut: 'r', - hotkey: '1', - byBBox(x, y, width, height) { - return { - params: [x, y, width, height], - }; - }, - }, - Ellipse: { - drawAction: 'drawByDrag', - shortcut: 'o', - hotkey: '2', - byBBox(x, y, width, height) { - return { - params: [x + width / 2, y + height / 2, width / 2, height / 2], - }; - }, - }, - Brush: { - drawAction: 'clickAt', - shortcut: 'b', - hotkey: '3', - byBBox(x, y, width, height) { - return { - params: [x + width / 2, y + height / 2], - }; - }, - }, - KeyPoint: { - drawAction: 'clickAt', - shortcut: 'k', - hotkey: '4', - byBBox(x, y, width, height) { - return { - params: [x + width / 2, y + height / 2], - }; - }, - }, - Polygon: { - drawAction: 'drawByClickingPoints', - shortcut: 'p', - hotkey: '5', - byBBox(x, y, width, height) { - const points = []; - - points.push([x, y]); - points.push([x + width, y]); - points.push([x + width / 2, y + height]); - return { - params: [[...points, points[0]]], - }; - }, - }, -}; - -function drawShapeByBbox(Shape, x, y, width, height, where) { - where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params); -} - -const maxUsageImageToolsDataTable = new DataTable(['maxUsage', 'shapeName']); - -[1,3].forEach(maxUsage => { - Object.keys(shapes).forEach(shapeName => { - maxUsageImageToolsDataTable.add([maxUsage, shapeName]); - }); -}); - -const maxUsageDataTable = new DataTable(['maxUsage']); - -[1,3].forEach(maxUsage => { - maxUsageDataTable.add([maxUsage]); -}); -/* -Data(maxUsageImageToolsDataTable).Scenario('Max usages of separated labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) { - const { maxUsage, shapeName } = current; - const shape = shapes[shapeName]; - const annotations = []; - - for (let k = 0; k < maxUsage; k++) { - annotations.push({ - 'value': { - 'x': k, - 'y': 1, - 'width': 0.6666666666666666, - 'labels': [ - 'Label_1', - ], - }, - 'id': k, - 'from_name': 'Labels', - 'to_name': 'img', - 'type': 'labels', - }); - } - - I.amOnPage('/'); - LabelStudio.setFeatureFlags({ - fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, - }); - LabelStudio.init({ - config: createImageToolsConfig({ maxUsage }), - data: { - image: IMAGE, - }, - annotations: [{ - id: 'test', - result: annotations, - }], - }); - await AtImageView.waitForImage(); - await AtImageView.lookForStage(); - AtSidebar.seeRegions(maxUsage); - - - I.pressKey('1'); - I.pressKey(shape.shortcut); - AtImageView.clickAt(50, 50); - - I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); -}); - -Data(maxUsageImageToolsDataTable).Scenario('Max usages of labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) { - const { maxUsage, shapeName } = current; - const shape = shapes[shapeName]; - - I.amOnPage('/'); - LabelStudio.setFeatureFlags({ - fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, - }); - LabelStudio.init({ - config: createImageLabelsConfig({ maxUsage }), - data: { - image: IMAGE, - }, - }); - - await AtImageView.waitForImage(); - await AtImageView.lookForStage(); - AtSidebar.seeRegions(0); - - for (let k = 0; k < maxUsage; k++) { - I.pressKey(shape.hotkey); - drawShapeByBbox(shape, 1 + 50 * k, 1, 30,30, AtImageView); - I.pressKey('u'); - } - - I.pressKey(shape.hotkey); - AtImageView.clickAt(50, 50); - - I.see(`You can't use ${shapeName}_1 more than ${maxUsage} time(s)`); -}); - -Data(maxUsageDataTable).Scenario('Max usages of labels in Audio on region creation', async function({ I, LabelStudio, AtSidebar, AtAudioView, current }) { - const { maxUsage } = current; - - LabelStudio.setFeatureFlags({ - fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, - ff_front_dev_2715_audio_3_280722_short: true, - }); - I.amOnPage('/'); - LabelStudio.init({ - config: ` - - - - `, - data: { - audio: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3', - }, - }); - - await AtAudioView.waitForAudio(); - await AtAudioView.lookForStage(); - AtSidebar.seeRegions(0); - - for (let k = 0; k < maxUsage; k++) { - I.pressKey('1'); - AtAudioView.dragAudioElement(10 + 40 * k,30); - I.pressKey('u'); - } - I.pressKey('1'); - AtAudioView.dragAudioElement(10 + 40 * maxUsage,30); - - AtSidebar.seeRegions(maxUsage); - I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); -}); - -Data(maxUsageDataTable).Scenario('Max usages of labels in RichText on region creation', async function({ I, LabelStudio, AtSidebar, AtRichText, current }) { - const { maxUsage } = current; - - I.amOnPage('/'); - LabelStudio.setFeatureFlags({ - fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, - }); - LabelStudio.init({ - config: ` - - - - -`, - data: { - url: 'https://htx-pub.s3.amazonaws.com/example.txt', - }, - }); - - LabelStudio.waitForObjectsReady(); - AtSidebar.seeRegions(0); - - for (let k = 0; k < maxUsage; k++) { - I.pressKey('1'); - AtRichText.selectTextByGlobalOffset(1 + 5 * k, 5 * (k + 1)); - I.pressKey('u'); - } - I.pressKey('1'); - AtRichText.selectTextByGlobalOffset(1 + 5 * maxUsage, 5 * (maxUsage + 1)); - - I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); -}); - -Data(maxUsageDataTable).Scenario('Max usages of labels in Paragraphs on region creation', async function({ I, LabelStudio, AtSidebar, AtParagraphs, current }) { - const { maxUsage } = current; - - I.amOnPage('/'); - LabelStudio.setFeatureFlags({ - fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, - }); - LabelStudio.init({ - config: ` - - - - -`, - data: require('../examples/text-paragraphs').data, - }); - - LabelStudio.waitForObjectsReady(); - AtSidebar.seeRegions(0); - - for (let k = 0; k < maxUsage; k++) { - I.pressKey('1'); - AtParagraphs.selectTextByOffset(k + 1, 0, 3); - I.pressKey('u'); - } - I.pressKey('1'); - AtParagraphs.selectTextByOffset(maxUsage + 1, 0, 3); - - I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); -}); - -Data(maxUsageDataTable).Scenario('Max usages of labels in Timeseries on region creation', async function({ I, LabelStudio, AtSidebar, AtTimeSeries, current }) { - const { maxUsage } = current; - - I.amOnPage('/'); - LabelStudio.setFeatureFlags({ - fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, - }); - LabelStudio.init({ - config: ` - - - - - - - -`, - data: require('../examples/data/sample-sin.json'), - }); - - LabelStudio.waitForObjectsReady(); - AtSidebar.seeRegions(0); - await AtTimeSeries.lookForStage(); - - for (let k = 0; k < maxUsage; k++) { - I.pressKey('1'); - AtTimeSeries.drawByDrag(1 + k * 20, 10); - I.pressKey('u'); - } - I.pressKey('1'); - AtTimeSeries.drawByDrag(1 + maxUsage * 20, 10); - - I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); -}); -*/ \ No newline at end of file +Feature('Max usage'); + +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; + +const createImageToolsConfig = ({ maxUsage }) => ` + + + + + + + + + +`; + +const createImageLabelsConfig = ({ maxUsage }) => ` + + + + + + + + + + + + + `; + +const shapes = { + Rectangle: { + drawAction: 'drawByDrag', + shortcut: 'r', + hotkey: '1', + byBBox(x, y, width, height) { + return { + params: [x, y, width, height], + }; + }, + }, + Ellipse: { + drawAction: 'drawByDrag', + shortcut: 'o', + hotkey: '2', + byBBox(x, y, width, height) { + return { + params: [x + width / 2, y + height / 2, width / 2, height / 2], + }; + }, + }, + Brush: { + drawAction: 'clickAt', + shortcut: 'b', + hotkey: '3', + byBBox(x, y, width, height) { + return { + params: [x + width / 2, y + height / 2], + }; + }, + }, + KeyPoint: { + drawAction: 'clickAt', + shortcut: 'k', + hotkey: '4', + byBBox(x, y, width, height) { + return { + params: [x + width / 2, y + height / 2], + }; + }, + }, + Polygon: { + drawAction: 'drawByClickingPoints', + shortcut: 'p', + hotkey: '5', + byBBox(x, y, width, height) { + const points = []; + + points.push([x, y]); + points.push([x + width, y]); + points.push([x + width / 2, y + height]); + return { + params: [[...points, points[0]]], + }; + }, + }, +}; + +function drawShapeByBbox(Shape, x, y, width, height, where) { + where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params); +} + +const maxUsageImageToolsDataTable = new DataTable(['maxUsage', 'shapeName']); + +[1,3].forEach(maxUsage => { + Object.keys(shapes).forEach(shapeName => { + maxUsageImageToolsDataTable.add([maxUsage, shapeName]); + }); +}); + +const maxUsageDataTable = new DataTable(['maxUsage']); + +[1,3].forEach(maxUsage => { + maxUsageDataTable.add([maxUsage]); +}); + +Data(maxUsageImageToolsDataTable).Scenario('Max usages of separated labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) { + const { maxUsage, shapeName } = current; + const shape = shapes[shapeName]; + const annotations = []; + + for (let k = 0; k < maxUsage; k++) { + annotations.push({ + 'value': { + 'x': k, + 'y': 1, + 'width': 0.6666666666666666, + 'labels': [ + 'Label_1', + ], + }, + 'id': k, + 'from_name': 'Labels', + 'to_name': 'img', + 'type': 'labels', + }); + } + + I.amOnPage('/'); + LabelStudio.setFeatureFlags({ + fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, + }); + LabelStudio.init({ + config: createImageToolsConfig({ maxUsage }), + data: { + image: IMAGE, + }, + annotations: [{ + id: 'test', + result: annotations, + }], + }); + await AtImageView.waitForImage(); + await AtImageView.lookForStage(); + AtSidebar.seeRegions(maxUsage); + + + I.pressKey('1'); + I.pressKey(shape.shortcut); + AtImageView.clickAt(50, 50); + + I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); +}); + +Data(maxUsageImageToolsDataTable).Scenario('Max usages of labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) { + const { maxUsage, shapeName } = current; + const shape = shapes[shapeName]; + + I.amOnPage('/'); + LabelStudio.setFeatureFlags({ + fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, + }); + LabelStudio.init({ + config: createImageLabelsConfig({ maxUsage }), + data: { + image: IMAGE, + }, + }); + + await AtImageView.waitForImage(); + await AtImageView.lookForStage(); + AtSidebar.seeRegions(0); + + for (let k = 0; k < maxUsage; k++) { + I.pressKey(shape.hotkey); + drawShapeByBbox(shape, 1 + 50 * k, 1, 30,30, AtImageView); + I.pressKey('u'); + } + + I.pressKey(shape.hotkey); + AtImageView.clickAt(50, 50); + + I.see(`You can't use ${shapeName}_1 more than ${maxUsage} time(s)`); +}); + +Data(maxUsageDataTable).Scenario('Max usages of labels in Audio on region creation', async function({ I, LabelStudio, AtSidebar, AtAudioView, current }) { + const { maxUsage } = current; + + LabelStudio.setFeatureFlags({ + fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, + ff_front_dev_2715_audio_3_280722_short: true, + }); + I.amOnPage('/'); + LabelStudio.init({ + config: ` + + + + `, + data: { + audio: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3', + }, + }); + + await AtAudioView.waitForAudio(); + await AtAudioView.lookForStage(); + AtSidebar.seeRegions(0); + + for (let k = 0; k < maxUsage; k++) { + I.pressKey('1'); + AtAudioView.dragAudioElement(10 + 40 * k,30); + I.pressKey('u'); + } + I.pressKey('1'); + AtAudioView.dragAudioElement(10 + 40 * maxUsage,30); + + AtSidebar.seeRegions(maxUsage); + I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); +}); + +Data(maxUsageDataTable).Scenario('Max usages of labels in RichText on region creation', async function({ I, LabelStudio, AtSidebar, AtRichText, current }) { + const { maxUsage } = current; + + I.amOnPage('/'); + LabelStudio.setFeatureFlags({ + fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, + }); + LabelStudio.init({ + config: ` + + + + +`, + data: { + url: 'https://htx-pub.s3.amazonaws.com/example.txt', + }, + }); + + LabelStudio.waitForObjectsReady(); + AtSidebar.seeRegions(0); + + for (let k = 0; k < maxUsage; k++) { + I.pressKey('1'); + AtRichText.selectTextByGlobalOffset(1 + 5 * k, 5 * (k + 1)); + I.pressKey('u'); + } + I.pressKey('1'); + AtRichText.selectTextByGlobalOffset(1 + 5 * maxUsage, 5 * (maxUsage + 1)); + + I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); +}); + +Data(maxUsageDataTable).Scenario('Max usages of labels in Paragraphs on region creation', async function({ I, LabelStudio, AtSidebar, AtParagraphs, current }) { + const { maxUsage } = current; + + I.amOnPage('/'); + LabelStudio.setFeatureFlags({ + fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, + }); + LabelStudio.init({ + config: ` + + + + +`, + data: require('../examples/text-paragraphs').data, + }); + + LabelStudio.waitForObjectsReady(); + AtSidebar.seeRegions(0); + + for (let k = 0; k < maxUsage; k++) { + I.pressKey('1'); + AtParagraphs.selectTextByOffset(k + 1, 0, 3); + I.pressKey('u'); + } + I.pressKey('1'); + AtParagraphs.selectTextByOffset(maxUsage + 1, 0, 3); + + I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); +}); + +Data(maxUsageDataTable).Scenario('Max usages of labels in Timeseries on region creation', async function({ I, LabelStudio, AtSidebar, AtTimeSeries, current }) { + const { maxUsage } = current; + + I.amOnPage('/'); + LabelStudio.setFeatureFlags({ + fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true, + }); + LabelStudio.init({ + config: ` + + + + + + + +`, + data: require('../examples/data/sample-sin.json'), + }); + + LabelStudio.waitForObjectsReady(); + AtSidebar.seeRegions(0); + await AtTimeSeries.lookForStage(); + + for (let k = 0; k < maxUsage; k++) { + I.pressKey('1'); + AtTimeSeries.drawByDrag(1 + k * 20, 10); + I.pressKey('u'); + } + I.pressKey('1'); + AtTimeSeries.drawByDrag(1 + maxUsage * 20, 10); + + I.see(`You can't use Label_1 more than ${maxUsage} time(s)`); +}); diff --git a/e2e/tests/outliner.test.js b/e2e/tests/outliner.test.js index 0bfcee1875..c99c803857 100644 --- a/e2e/tests/outliner.test.js +++ b/e2e/tests/outliner.test.js @@ -3,8 +3,7 @@ const { centerOfBbox } = require('./helpers'); Feature('Outliner'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; Scenario('Basic details', async ({ I, LabelStudio, AtOutliner, AtDetails }) => { const RESULT_LABELS = ['a', 'b', 'c']; diff --git a/e2e/tests/paragraphs-filter.test.js b/e2e/tests/paragraphs-filter.test.js index 9ebdbdbea2..7d3f58fc6a 100644 --- a/e2e/tests/paragraphs-filter.test.js +++ b/e2e/tests/paragraphs-filter.test.js @@ -1,600 +1,600 @@ -const assert = require('assert'); -const { omitBy } = require('./helpers'); - -Feature('Paragraphs filter'); - -const AUDIO = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3'; - -const ANNOTATIONS = [ - { - 'result': [ - { - 'id':'ryzr4QdL93', - 'from_name':'ner', - 'to_name':'text', - 'source':'$dialogue', - 'type':'paragraphlabels', - 'value':{ - 'start':'2', - 'end':'4', - 'startOffset':0, - 'endOffset':134, - 'paragraphlabels': ['Important Stuff'], - 'text': 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?I dont know. Thats a good question.Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.', - }, - }, - ], - }, -]; - -const DATA = { - audio: AUDIO, - dialogue: [ - { - start: 3.1, - end: 5.6, - author: 'Mia Wallace', - text: 'Dont you hate that?', - }, - { - start: 4.2, - duration: 3.1, - author: 'Vincent Vega:', - text: 'Hate what?', - }, - { - author: 'Mia Wallace:', - text: 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?', - }, - { - start: 90, - author: 'Vincent Vega:', - text: 'I dont know. Thats a good question.', - }, - { - author: 'Mia Wallace:', - text: - 'Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.', - }, - ], -}; - -const CONFIG = ` - - - - - - - -`; - -const FEATURE_FLAGS = { - ff_front_dev_2669_paragraph_author_filter_210622_short: true, - fflag_fix_front_dev_2918_labeling_filtered_paragraphs_250822_short: true, -}; - -Scenario('Create two results using excluding a phrase by the filter', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { - const params = { - data: DATA, - config: CONFIG, - }; - - I.amOnPage('/'); - - LabelStudio.setFeatureFlags(FEATURE_FLAGS); - LabelStudio.init(params); - AtSidebar.seeRegions(0); - - I.say('Select 2 regions in the consecutive phrases of the one person'); - - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Hate what?'), - 5, - AtParagraphs.locateText('Hate what?'), - 10, - ); - - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('I dont know. Thats a good question.'), - 0, - AtParagraphs.locateText('I dont know. Thats a good question.'), - 11, - ); - AtSidebar.seeRegions(2); - - I.say('Take a snapshot'); - const twoActionsResult = LabelStudio.serialize(); - - I.say('Reset to initial state'); - LabelStudio.init(params); - AtSidebar.seeRegions(0); - - I.say('Filter the phrases by that person.'); - AtParagraphs.clickFilter('Vincent Vega:'); - - I.say('Try to get the same result in one action'); - - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Hate what?'), - 5, - AtParagraphs.locateText('I dont know. Thats a good question.'), - 11, - ); - AtSidebar.seeRegions(2); - - I.say('Take a second snapshot'); - const oneActionResult = LabelStudio.serialize(); - - I.say('The results should be identical'); - - assert.deepStrictEqual(twoActionsResult, oneActionResult); - -}); - -Scenario('Check different cases ', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { - const dialogue = [ - 1,// 1 - 3,// 2 - 1,// 3 - 2,// 4 - 3,// 5 - 1,// 6 - 2,// 7 - 1,// 8 - 3,// 9 - 1,// 10 - ].map((authorId, idx)=>({ - start: idx+1, - end: idx+2, - author: `Author ${authorId}`, - text: `Message ${idx+1}`, - })); - const params = { - config: CONFIG, - data: { - audio: AUDIO, - dialogue, - }, - }; - - I.amOnPage('/'); - - LabelStudio.setFeatureFlags(FEATURE_FLAGS); - LabelStudio.init(params); - AtSidebar.seeRegions(0); - - I.say('Hide Author 3'); - AtParagraphs.clickFilter('Author 1', 'Author 2'); - - I.say('Make regions by selecting everything'); - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 1'), - 0, - AtParagraphs.locateText('Message 10'), - 10, - ); - - I.say('There should be 4 new regions'); - AtSidebar.seeRegions(4); - { - const result = await LabelStudio.serialize(); - - assert.strictEqual(result.length, 4); - - assert.deepStrictEqual(omitBy(result[0].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '0', - 'end': '0', - 'startOffset': 0, - 'endOffset': 9, - 'text': 'Message 1', - }); - - assert.deepStrictEqual(omitBy(result[1].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '2', - 'end': '3', - 'startOffset': 0, - 'endOffset': 9, - 'text': 'Message 3\n\nMessage 4', - }); - - assert.deepStrictEqual(omitBy(result[2].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '5', - 'end': '7', - 'startOffset': 0, - 'endOffset': 9, - 'text': 'Message 6\n\nMessage 7\n\nMessage 8', - }); - - assert.deepStrictEqual(omitBy(result[3].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '9', - 'end': '9', - 'startOffset': 0, - 'endOffset': 10, - 'text': 'Message 10', - }); - } - - I.say('Test the overlaps of regions #1'); - AtLabels.clickLabel('Important Stuff'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 3'), - 4, - AtParagraphs.locateText('Message 8'), - 4, - ); - AtSidebar.seeRegions(6); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual(omitBy(result[4].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '2', - 'end': '3', - 'startOffset': 4, - 'endOffset': 9, - 'text': 'age 3\n\nMessage 4', - }); - - assert.deepStrictEqual(omitBy(result[5].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '5', - 'end': '7', - 'startOffset': 0, - 'endOffset': 4, - 'text': 'Message 6\n\nMessage 7\n\nMess', - }); - } - - I.say('Test the overlaps of regions #2'); - AtParagraphs.clickFilter('Author 2', 'Author 3'); - AtLabels.clickLabel('Important Stuff'); - AtParagraphs.setSelection( - AtParagraphs.locateText('age 3'), - 4, - AtParagraphs.locateText('age 8'), - 3, - ); - AtSidebar.seeRegions(9); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual(omitBy(result[6].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '2', - 'end': '2', - 'startOffset': 8, - 'endOffset': 9, - 'text': '3', - }); - - assert.deepStrictEqual(omitBy(result[7].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '4', - 'end': '5', - 'startOffset': 0, - 'endOffset': 9, - 'text': 'Message 5\n\nMessage 6', - }); - - assert.deepStrictEqual(omitBy(result[8].value, (v, key)=> key === 'paragraphlabels'), { - 'start': '7', - 'end': '7', - 'startOffset': 0, - 'endOffset': 7, - 'text': 'Message', - }); - } -}); - -Scenario( - 'Check start and end indices do not leak to other lines', - async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { - const dialogue = [ - 1, // 1 - 3, // 2 - 1, // 3 - 2, // 4 - 3, // 5 - 1, // 6 - 2, // 7 - 1, // 8 - 3, // 9 - 1, // 10 - 3, // 11 - 2, // 12 - 3, // 13 - 2, // 14 - ].map((authorId, idx) => ({ - start: idx + 1, - end: idx + 2, - author: `Author ${authorId}`, - text: `Message ${idx + 1}`, - })); - const params = { - config: CONFIG, - data: { - audio: AUDIO, - dialogue, - }, - }; - - LabelStudio.setFeatureFlags(FEATURE_FLAGS); - I.amOnPage('/'); - - LabelStudio.init(params); - AtSidebar.seeRegions(0); - - I.say( - 'Test selection from the end of one turn to end of the one below correctly creates a single region with proper start,startOffset,end,endOffset', - ); - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 8'), - 9, - AtParagraphs.locateText('Message 9'), - 9, - ); - AtSidebar.seeRegions(1); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), - { - start: '8', - end: '8', - startOffset: 0, - endOffset: 9, - text: 'Message 9', - }, - ); - } - - I.say( - 'Test selection from the end of one turn to the very start of another below correctly creates a single region with proper start,startOffset,end,endOffset', - ); - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 8'), - 9, - AtParagraphs.locateText('Message 10'), - 0, - ); - AtSidebar.seeRegions(2); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[1].value, (v, key) => key === 'paragraphlabels'), - { - start: '8', - end: '8', - startOffset: 0, - endOffset: 9, - text: 'Message 9', - }, - ); - } - - I.say( - 'Test selection from the end of one turn to end of ones below across collapsed text correctly creates regions with proper start,startOffset,end,endOffset', - ); - AtParagraphs.clickFilter('Author 2', 'Author 3'); - AtLabels.clickLabel('Important Stuff'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 2'), - 9, - AtParagraphs.locateText('Message 8'), - 9, - ); - AtSidebar.seeRegions(4); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[2].value, (v, key) => key === 'paragraphlabels'), - { - start: '3', - end: '4', - startOffset: 0, - endOffset: 9, - text: 'Message 4\n\nMessage 5', - }, - ); - assert.deepStrictEqual( - omitBy(result[3].value, (v, key) => key === 'paragraphlabels'), - { - start: '6', - end: '6', - startOffset: 0, - endOffset: 9, - text: 'Message 7', - }, - ); - } - - I.say( - 'Test selection from the end of one turn to very start of ones below across collapsed text correctly creates creates regions with proper start,startOffset,end,endOffset', - ); - AtLabels.clickLabel('Other'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 2'), - 9, - AtParagraphs.locateText('Message 8'), - 0, - ); - AtSidebar.seeRegions(6); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[4].value, (v, key) => key === 'paragraphlabels'), - { - start: '3', - end: '4', - startOffset: 0, - endOffset: 9, - text: 'Message 4\n\nMessage 5', - }, - ); - assert.deepStrictEqual( - omitBy(result[5].value, (v, key) => key === 'paragraphlabels'), - { - start: '6', - end: '6', - startOffset: 0, - endOffset: 9, - text: 'Message 7', - }, - ); - } - - I.say( - 'Test selection from the end of Message 11 to the start of Message 14 to get region over Message 12 and Message 13', - ); - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Message 11'), - 10, - AtParagraphs.locateText('Message 14'), - 0, - ); - AtSidebar.seeRegions(7); - - { - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[6].value, (v, key) => key === 'paragraphlabels'), - { - start: '11', - end: '12', - startOffset: 0, - endOffset: 10, - text: 'Message 12\n\nMessage 13', - }, - ); - } - }, -); - -Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { - const params = { - data: DATA, - config: CONFIG, - }; - - I.amOnPage('/'); - - LabelStudio.setFeatureFlags(FEATURE_FLAGS); - LabelStudio.init(params); - AtSidebar.seeRegions(0); - - I.say('Select 2 regions in the consecutive phrases'); - - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Dont you hate that?'), - 18, - AtParagraphs.locateText('Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?'), - 0, - ); - - AtSidebar.seeRegions(1); - - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), - { - start: '0', - end: '1', - startOffset: 18, - endOffset: 10, - text: '?\n\nHate what?', - }, - ); -}); - -Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases except the very last one', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { - const params = { - data: { - ...DATA, - dialogue: DATA.dialogue.map(d => [d, { ...d, text: `${d.text}2` }]).flat(), - }, - config: CONFIG, - }; - - I.amOnPage('/'); - - LabelStudio.setFeatureFlags(FEATURE_FLAGS); - LabelStudio.init(params); - AtSidebar.seeRegions(0); - - - I.say('Select 2 regions in the consecutive phrases of the one person'); - AtParagraphs.clickFilter('Vincent Vega'); - AtLabels.clickLabel('Random talk'); - AtParagraphs.setSelection( - AtParagraphs.locateText('Hate what?2'), - 10, - AtParagraphs.locateText('I dont know. Thats a good question.2'), - 0, - ); - - AtSidebar.seeRegions(2); - - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), - { - start: '3', - end: '3', - startOffset: 10, - endOffset: 11, - text: '2', - }, - ); - assert.deepStrictEqual( - omitBy(result[1].value, (v, key) => key === 'paragraphlabels'), - { - start: '6', - end: '6', - startOffset: 0, - endOffset: 35, - text: 'I dont know. Thats a good question.', - }, - ); -}); - -Scenario('Initializing a paragraph region range should not include author names in text', async ({ I, LabelStudio, AtSidebar }) => { - const params = { - data: DATA, - annotations: ANNOTATIONS, - config: CONFIG, - }; - - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FEATURE_FLAGS); - - const [{ result : [region] }] = ANNOTATIONS; - const { paragraphlabels: _paragraphlabels, ...value } = region.value; - - LabelStudio.init(params); - AtSidebar.seeRegions(1); - - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual( - omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), - value, - ); -}); +const assert = require('assert'); +const { omitBy } = require('./helpers'); + +Feature('Paragraphs filter'); + +const AUDIO = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3'; + +const ANNOTATIONS = [ + { + 'result': [ + { + 'id': 'ryzr4QdL93', + 'from_name': 'ner', + 'to_name': 'text', + 'source': '$dialogue', + 'type': 'paragraphlabels', + 'value': { + 'start': '2', + 'end': '4', + 'startOffset': 0, + 'endOffset': 134, + 'paragraphlabels': ['Important Stuff'], + 'text': 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?I dont know. Thats a good question.Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.', + }, + }, + ], + }, +]; + +const DATA = { + audio: AUDIO, + dialogue: [ + { + start: 3.1, + end: 5.6, + author: 'Mia Wallace', + text: 'Dont you hate that?', + }, + { + start: 4.2, + duration: 3.1, + author: 'Vincent Vega:', + text: 'Hate what?', + }, + { + author: 'Mia Wallace:', + text: 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?', + }, + { + start: 90, + author: 'Vincent Vega:', + text: 'I dont know. Thats a good question.', + }, + { + author: 'Mia Wallace:', + text: + 'Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.', + }, + ], +}; + +const CONFIG = ` + + + + + + + +`; + +const FEATURE_FLAGS = { + ff_front_dev_2669_paragraph_author_filter_210622_short: true, + fflag_fix_front_dev_2918_labeling_filtered_paragraphs_250822_short: true, +}; + +Scenario('Create two results using excluding a phrase by the filter', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { + const params = { + data: DATA, + config: CONFIG, + }; + + I.amOnPage('/'); + + LabelStudio.setFeatureFlags(FEATURE_FLAGS); + LabelStudio.init(params); + AtSidebar.seeRegions(0); + + I.say('Select 2 regions in the consecutive phrases of the one person'); + + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Hate what?'), + 5, + AtParagraphs.locateText('Hate what?'), + 10, + ); + + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('I dont know. Thats a good question.'), + 0, + AtParagraphs.locateText('I dont know. Thats a good question.'), + 11, + ); + AtSidebar.seeRegions(2); + + I.say('Take a snapshot'); + const twoActionsResult = LabelStudio.serialize(); + + I.say('Reset to initial state'); + LabelStudio.init(params); + AtSidebar.seeRegions(0); + + I.say('Filter the phrases by that person.'); + AtParagraphs.clickFilter('Vincent Vega:'); + + I.say('Try to get the same result in one action'); + + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Hate what?'), + 5, + AtParagraphs.locateText('I dont know. Thats a good question.'), + 11, + ); + AtSidebar.seeRegions(2); + + I.say('Take a second snapshot'); + const oneActionResult = LabelStudio.serialize(); + + I.say('The results should be identical'); + + assert.deepStrictEqual(twoActionsResult, oneActionResult); + +}); + +Scenario('Check different cases ', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { + const dialogue = [ + 1,// 1 + 3,// 2 + 1,// 3 + 2,// 4 + 3,// 5 + 1,// 6 + 2,// 7 + 1,// 8 + 3,// 9 + 1,// 10 + ].map((authorId, idx) => ({ + start: idx + 1, + end: idx + 2, + author: `Author ${authorId}`, + text: `Message ${idx + 1}`, + })); + const params = { + config: CONFIG, + data: { + audio: AUDIO, + dialogue, + }, + }; + + I.amOnPage('/'); + + LabelStudio.setFeatureFlags(FEATURE_FLAGS); + LabelStudio.init(params); + AtSidebar.seeRegions(0); + + I.say('Hide Author 3'); + AtParagraphs.clickFilter('Author 1', 'Author 2'); + + I.say('Make regions by selecting everything'); + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 1'), + 0, + AtParagraphs.locateText('Message 10'), + 10, + ); + + I.say('There should be 4 new regions'); + AtSidebar.seeRegions(4); + { + const result = await LabelStudio.serialize(); + + assert.strictEqual(result.length, 4); + + assert.deepStrictEqual(omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), { + 'start': '0', + 'end': '0', + 'startOffset': 0, + 'endOffset': 9, + 'text': 'Message 1', + }); + + assert.deepStrictEqual(omitBy(result[1].value, (v, key) => key === 'paragraphlabels'), { + 'start': '2', + 'end': '3', + 'startOffset': 0, + 'endOffset': 9, + 'text': 'Message 3\n\nMessage 4', + }); + + assert.deepStrictEqual(omitBy(result[2].value, (v, key) => key === 'paragraphlabels'), { + 'start': '5', + 'end': '7', + 'startOffset': 0, + 'endOffset': 9, + 'text': 'Message 6\n\nMessage 7\n\nMessage 8', + }); + + assert.deepStrictEqual(omitBy(result[3].value, (v, key) => key === 'paragraphlabels'), { + 'start': '9', + 'end': '9', + 'startOffset': 0, + 'endOffset': 10, + 'text': 'Message 10', + }); + } + + I.say('Test the overlaps of regions #1'); + AtLabels.clickLabel('Important Stuff'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 3'), + 4, + AtParagraphs.locateText('Message 8'), + 4, + ); + AtSidebar.seeRegions(6); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual(omitBy(result[4].value, (v, key) => key === 'paragraphlabels'), { + 'start': '2', + 'end': '3', + 'startOffset': 4, + 'endOffset': 9, + 'text': 'age 3\n\nMessage 4', + }); + + assert.deepStrictEqual(omitBy(result[5].value, (v, key) => key === 'paragraphlabels'), { + 'start': '5', + 'end': '7', + 'startOffset': 0, + 'endOffset': 4, + 'text': 'Message 6\n\nMessage 7\n\nMess', + }); + } + + I.say('Test the overlaps of regions #2'); + AtParagraphs.clickFilter('Author 2', 'Author 3'); + AtLabels.clickLabel('Important Stuff'); + AtParagraphs.setSelection( + AtParagraphs.locateText('age 3'), + 4, + AtParagraphs.locateText('age 8'), + 3, + ); + AtSidebar.seeRegions(9); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual(omitBy(result[6].value, (v, key) => key === 'paragraphlabels'), { + 'start': '2', + 'end': '2', + 'startOffset': 8, + 'endOffset': 9, + 'text': '3', + }); + + assert.deepStrictEqual(omitBy(result[7].value, (v, key) => key === 'paragraphlabels'), { + 'start': '4', + 'end': '5', + 'startOffset': 0, + 'endOffset': 9, + 'text': 'Message 5\n\nMessage 6', + }); + + assert.deepStrictEqual(omitBy(result[8].value, (v, key) => key === 'paragraphlabels'), { + 'start': '7', + 'end': '7', + 'startOffset': 0, + 'endOffset': 7, + 'text': 'Message', + }); + } +}); + +Scenario( + 'Check start and end indices do not leak to other lines', + async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { + const dialogue = [ + 1, // 1 + 3, // 2 + 1, // 3 + 2, // 4 + 3, // 5 + 1, // 6 + 2, // 7 + 1, // 8 + 3, // 9 + 1, // 10 + 3, // 11 + 2, // 12 + 3, // 13 + 2, // 14 + ].map((authorId, idx) => ({ + start: idx + 1, + end: idx + 2, + author: `Author ${authorId}`, + text: `Message ${idx + 1}`, + })); + const params = { + config: CONFIG, + data: { + audio: AUDIO, + dialogue, + }, + }; + + LabelStudio.setFeatureFlags(FEATURE_FLAGS); + I.amOnPage('/'); + + LabelStudio.init(params); + AtSidebar.seeRegions(0); + + I.say( + 'Test selection from the end of one turn to end of the one below correctly creates a single region with proper start,startOffset,end,endOffset', + ); + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 8'), + 9, + AtParagraphs.locateText('Message 9'), + 9, + ); + AtSidebar.seeRegions(1); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), + { + start: '8', + end: '8', + startOffset: 0, + endOffset: 9, + text: 'Message 9', + }, + ); + } + + I.say( + 'Test selection from the end of one turn to the very start of another below correctly creates a single region with proper start,startOffset,end,endOffset', + ); + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 8'), + 9, + AtParagraphs.locateText('Message 10'), + 0, + ); + AtSidebar.seeRegions(2); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[1].value, (v, key) => key === 'paragraphlabels'), + { + start: '8', + end: '8', + startOffset: 0, + endOffset: 9, + text: 'Message 9', + }, + ); + } + + I.say( + 'Test selection from the end of one turn to end of ones below across collapsed text correctly creates regions with proper start,startOffset,end,endOffset', + ); + AtParagraphs.clickFilter('Author 2', 'Author 3'); + AtLabels.clickLabel('Important Stuff'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 2'), + 9, + AtParagraphs.locateText('Message 8'), + 9, + ); + AtSidebar.seeRegions(4); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[2].value, (v, key) => key === 'paragraphlabels'), + { + start: '3', + end: '4', + startOffset: 0, + endOffset: 9, + text: 'Message 4\n\nMessage 5', + }, + ); + assert.deepStrictEqual( + omitBy(result[3].value, (v, key) => key === 'paragraphlabels'), + { + start: '6', + end: '6', + startOffset: 0, + endOffset: 9, + text: 'Message 7', + }, + ); + } + + I.say( + 'Test selection from the end of one turn to very start of ones below across collapsed text correctly creates creates regions with proper start,startOffset,end,endOffset', + ); + AtLabels.clickLabel('Other'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 2'), + 9, + AtParagraphs.locateText('Message 8'), + 0, + ); + AtSidebar.seeRegions(6); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[4].value, (v, key) => key === 'paragraphlabels'), + { + start: '3', + end: '4', + startOffset: 0, + endOffset: 9, + text: 'Message 4\n\nMessage 5', + }, + ); + assert.deepStrictEqual( + omitBy(result[5].value, (v, key) => key === 'paragraphlabels'), + { + start: '6', + end: '6', + startOffset: 0, + endOffset: 9, + text: 'Message 7', + }, + ); + } + + I.say( + 'Test selection from the end of Message 11 to the start of Message 14 to get region over Message 12 and Message 13', + ); + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Message 11'), + 10, + AtParagraphs.locateText('Message 14'), + 0, + ); + AtSidebar.seeRegions(7); + + { + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[6].value, (v, key) => key === 'paragraphlabels'), + { + start: '11', + end: '12', + startOffset: 0, + endOffset: 10, + text: 'Message 12\n\nMessage 13', + }, + ); + } + }, +); + +Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { + const params = { + data: DATA, + config: CONFIG, + }; + + I.amOnPage('/'); + + LabelStudio.setFeatureFlags(FEATURE_FLAGS); + LabelStudio.init(params); + AtSidebar.seeRegions(0); + + I.say('Select 2 regions in the consecutive phrases'); + + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Dont you hate that?'), + 18, + AtParagraphs.locateText('Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?'), + 0, + ); + + AtSidebar.seeRegions(1); + + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), + { + start: '0', + end: '1', + startOffset: 18, + endOffset: 10, + text: '?\n\nHate what?', + }, + ); +}); + +Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases except the very last one', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => { + const params = { + data: { + ...DATA, + dialogue: DATA.dialogue.map(d => [d, { ...d, text: `${d.text}2` }]).flat(), + }, + config: CONFIG, + }; + + I.amOnPage('/'); + + LabelStudio.setFeatureFlags(FEATURE_FLAGS); + LabelStudio.init(params); + AtSidebar.seeRegions(0); + + + I.say('Select 2 regions in the consecutive phrases of the one person'); + AtParagraphs.clickFilter('Vincent Vega'); + AtLabels.clickLabel('Random talk'); + AtParagraphs.setSelection( + AtParagraphs.locateText('Hate what?2'), + 10, + AtParagraphs.locateText('I dont know. Thats a good question.2'), + 0, + ); + + AtSidebar.seeRegions(2); + + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), + { + start: '3', + end: '3', + startOffset: 10, + endOffset: 11, + text: '2', + }, + ); + assert.deepStrictEqual( + omitBy(result[1].value, (v, key) => key === 'paragraphlabels'), + { + start: '6', + end: '6', + startOffset: 0, + endOffset: 35, + text: 'I dont know. Thats a good question.', + }, + ); +}); + +Scenario('Initializing a paragraph region range should not include author names in text', async ({ I, LabelStudio, AtSidebar }) => { + const params = { + data: DATA, + annotations: ANNOTATIONS, + config: CONFIG, + }; + + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FEATURE_FLAGS); + + const [{ result: [region] }] = ANNOTATIONS; + const { paragraphlabels: _paragraphlabels, ...value } = region.value; + + LabelStudio.init(params); + AtSidebar.seeRegions(1); + + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual( + omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), + value, + ); +}); diff --git a/e2e/tests/regression-tests/brush-relations.test.js b/e2e/tests/regression-tests/brush-relations.test.js index 5700346ee4..4b748d9a81 100644 --- a/e2e/tests/regression-tests/brush-relations.test.js +++ b/e2e/tests/regression-tests/brush-relations.test.js @@ -1,103 +1,103 @@ -Feature('Brush relations').tag('@regress'); - -const IMAGE = 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg'; - -const config = ` - - - - - `; - -function getPointAtSpiral(t, v , w) { - return { x: v * t * Math.cos(w * t), y: v * t * Math.sin(w * t) }; -} - -function generateSpiralPoints(x0, y0, R, v , w) { - let t = 1, x, y; - const points = []; - - do { - ({ x, y } = getPointAtSpiral(t++, v, w)); - points.push([x + x0, y + y0]); - } while (x ** 2 + y ** 2 < R ** 2); - return points; -} - -xScenario('Brush relations shouldn\'t crash everything', async ({ I, LabelStudio, AtImageView, AtSidebar }) => { - const params = { - config, - data: { image: IMAGE }, - }; - - I.amOnPage('/'); - LabelStudio.init(params); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - const regionsCentralPoints = []; - - // create 4 brush regions - for (let i = 0; i < 4; i++) { - // find start position - const x = canvasSize.width / 4 * ((i % 2) * 2 + 1); - const y = canvasSize.height / 4 * ((Math.floor(i / 2) % 2) * 2 + 1); - // generate points in a spiral - const points = generateSpiralPoints(x, y, Math.min(canvasSize.width / 6, canvasSize.height / 6), .4, Math.PI / 18); - - // select the brush label - I.pressKey('1'); - // draw a brush region - AtImageView.drawThroughPoints(points); - AtSidebar.seeRegions(i+1); - // unselect the region - I.pressKey('u'); - // save the central point - regionsCentralPoints.push({ x, y }); - } - - // Check that we can create a relation between the brush regions - { - // switch to the move tool for easy region selecting - I.pressKey('v'); - // select the first region - AtImageView.clickAt(regionsCentralPoints[0].x, regionsCentralPoints[0].y); - // create relation to the second region - I.pressKey(['alt', 'r']); - AtImageView.clickAt(regionsCentralPoints[1].x, regionsCentralPoints[1].y); - // check that the relation has been created - AtSidebar.seeRelations(1); - } - - // Check that relations work fine on a brush restoration (from rle) - { - // export annotation - const annotation = await LabelStudio.serialize(); - - // reload LS with that datalabel studio logo - LabelStudio.init({ - ...params, - annotations: [{ id: 'imported', result: annotation }], - }); - - AtImageView.waitForImage(); - // Check that relation still exist - AtSidebar.seeRelations(1); - - // Try to create new relation with restored regions - { - // switch to the move tool for easy region selecting - I.pressKey('v'); - // select the third region - AtImageView.clickAt(regionsCentralPoints[2].x, regionsCentralPoints[2].y); - // create relation to the fourth region - I.pressKey(['alt', 'r']); - AtImageView.clickAt(regionsCentralPoints[3].x, regionsCentralPoints[3].y); - // check that the relation has been created - AtSidebar.seeRelations(2); - } - - /// The potential errors should be caught by `errorsCollector` plugin - } -}); +Feature('Brush relations').tag('@regress'); + +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; + +const config = ` + + + + + `; + +function getPointAtSpiral(t, v , w) { + return { x: v * t * Math.cos(w * t), y: v * t * Math.sin(w * t) }; +} + +function generateSpiralPoints(x0, y0, R, v , w) { + let t = 1, x, y; + const points = []; + + do { + ({ x, y } = getPointAtSpiral(t++, v, w)); + points.push([x + x0, y + y0]); + } while (x ** 2 + y ** 2 < R ** 2); + return points; +} + +Scenario('Brush relations shouldn\'t crash everything', async ({ I, LabelStudio, AtImageView, AtSidebar }) => { + const params = { + config, + data: { image: IMAGE }, + }; + + I.amOnPage('/'); + LabelStudio.init(params); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + const regionsCentralPoints = []; + + // create 4 brush regions + for (let i = 0; i < 4; i++) { + // find start position + const x = canvasSize.width / 4 * ((i % 2) * 2 + 1); + const y = canvasSize.height / 4 * ((Math.floor(i / 2) % 2) * 2 + 1); + // generate points in a spiral + const points = generateSpiralPoints(x, y, Math.min(canvasSize.width / 6, canvasSize.height / 6), .4, Math.PI / 18); + + // select the brush label + I.pressKey('1'); + // draw a brush region + AtImageView.drawThroughPoints(points); + AtSidebar.seeRegions(i + 1); + // unselect the region + I.pressKey('u'); + // save the central point + regionsCentralPoints.push({ x, y }); + } + + // Check that we can create a relation between the brush regions + { + // switch to the move tool for easy region selecting + I.pressKey('v'); + // select the first region + AtImageView.clickAt(regionsCentralPoints[0].x, regionsCentralPoints[0].y); + // create relation to the second region + I.pressKey(['alt', 'r']); + AtImageView.clickAt(regionsCentralPoints[1].x, regionsCentralPoints[1].y); + // check that the relation has been created + AtSidebar.seeRelations(1); + } + + // Check that relations work fine on a brush restoration (from rle) + { + // export annotation + const annotation = await LabelStudio.serialize(); + + // reload LS with that datalabel studio logo + LabelStudio.init({ + ...params, + annotations: [{ id: 'imported', result: annotation }], + }); + + AtImageView.waitForImage(); + // Check that relation still exist + AtSidebar.seeRelations(1); + + // Try to create new relation with restored regions + { + // switch to the move tool for easy region selecting + I.pressKey('v'); + // select the third region + AtImageView.clickAt(regionsCentralPoints[2].x, regionsCentralPoints[2].y); + // create relation to the fourth region + I.pressKey(['alt', 'r']); + AtImageView.clickAt(regionsCentralPoints[3].x, regionsCentralPoints[3].y); + // check that the relation has been created + AtSidebar.seeRelations(2); + } + + /// The potential errors should be caught by `errorsCollector` plugin + } +}); diff --git a/e2e/tests/regression-tests/dynamic-choices.test.js b/e2e/tests/regression-tests/dynamic-choices.test.js index 53e3e9ad88..28020b51ce 100644 --- a/e2e/tests/regression-tests/dynamic-choices.test.js +++ b/e2e/tests/regression-tests/dynamic-choices.test.js @@ -1,80 +1,80 @@ -const assert = require('assert'); - -Feature('Dynamic choices').tag('@regress'); - -Scenario('Hotkeys for dynamic choices', async ({ I, LabelStudio })=>{ - const params = { - config: ` - - - - - -`, - data: { - text: 'Some text', - choices: [ - { - value: 'Header 1', - children: [ - { - value: 'Option 1.1', - }, - { - value: 'Option 1.2', - }, - ], - }, - { - value: 'Header 2', - children: [ - { - value: 'Option 2.1', - }, - { - value: 'Option 2.2', - }, - { - value: 'Option 2.3', - hotkey: 'q', - }, - ], - }, - ], - }, - annotations: [{ - id: 'test', - result: [], - }], - }; - - I.amOnPage('/'); - - LabelStudio.init(params); - - I.see('Header 1'); - I.see('Option 1.1'); - I.see('Header 2'); - I.see('Option 2.2'); - - I.say('Select some choices by pressing hotkeys'); - - I.pressKey('1'); - I.pressKey('q'); - I.pressKey('s'); - - I.say('Check the result'); - - I.seeElement('.ant-checkbox-checked [name=\'Header 1\']'); - I.seeElement('.ant-checkbox-indeterminate [name=\'Header 2\']'); - I.seeElement('.ant-checkbox-checked [name=\'Option 1.1\']'); - I.seeElement('.ant-checkbox-checked [name=\'Option 1.2\']'); - I.seeElement('.ant-checkbox-checked [name=\'Option 2.3\']'); - I.seeElement('.ant-checkbox-checked [name=\'Static option\']'); - - const result = await LabelStudio.serialize(); - - assert.deepStrictEqual(result.length, 1); - assert.deepStrictEqual(result[0].value.choices, [['Static option'], ['Header 1'], ['Header 1', 'Option 1.1'], ['Header 1', 'Option 1.2'], ['Header 2', 'Option 2.3']]); - -}); +const assert = require('assert'); + +Feature('Dynamic choices').tag('@regress'); + +Scenario('Hotkeys for dynamic choices', async ({ I, LabelStudio }) => { + const params = { + config: ` + + + + + +`, + data: { + text: 'Some text', + choices: [ + { + value: 'Header 1', + children: [ + { + value: 'Option 1.1', + }, + { + value: 'Option 1.2', + }, + ], + }, + { + value: 'Header 2', + children: [ + { + value: 'Option 2.1', + }, + { + value: 'Option 2.2', + }, + { + value: 'Option 2.3', + hotkey: 'q', + }, + ], + }, + ], + }, + annotations: [{ + id: 'test', + result: [], + }], + }; + + I.amOnPage('/'); + + LabelStudio.init(params); + + I.see('Header 1'); + I.see('Option 1.1'); + I.see('Header 2'); + I.see('Option 2.2'); + + I.say('Select some choices by pressing hotkeys'); + + I.pressKey('1'); + I.pressKey('q'); + I.pressKey('s'); + + I.say('Check the result'); + + I.seeElement('.ant-checkbox-checked [name=\'Header 1\']'); + I.seeElement('.ant-checkbox-indeterminate [name=\'Header 2\']'); + I.seeElement('.ant-checkbox-checked [name=\'Option 1.1\']'); + I.seeElement('.ant-checkbox-checked [name=\'Option 1.2\']'); + I.seeElement('.ant-checkbox-checked [name=\'Option 2.3\']'); + I.seeElement('.ant-checkbox-checked [name=\'Static option\']'); + + const result = await LabelStudio.serialize(); + + assert.deepStrictEqual(result.length, 1); + assert.deepStrictEqual(result[0].value.choices, [['Static option'], ['Header 1'], ['Header 1', 'Option 1.1'], ['Header 1', 'Option 1.2'], ['Header 2', 'Option 2.3']]); + +}); diff --git a/e2e/tests/regression-tests/image-ctrl-drawing.test.js b/e2e/tests/regression-tests/image-ctrl-drawing.test.js index 01e5fc3f35..bf66d8c603 100644 --- a/e2e/tests/regression-tests/image-ctrl-drawing.test.js +++ b/e2e/tests/regression-tests/image-ctrl-drawing.test.js @@ -3,8 +3,7 @@ const Asserts = require('../../utils/asserts'); Feature('Creating regions over other regions').tag('@regress'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const BLUEVIOLET = { color: '#8A2BE2', diff --git a/e2e/tests/regression-tests/image-draw-undo.test.js b/e2e/tests/regression-tests/image-draw-undo.test.js index 96dad595a7..69d44bb64a 100644 --- a/e2e/tests/regression-tests/image-draw-undo.test.js +++ b/e2e/tests/regression-tests/image-draw-undo.test.js @@ -1,7 +1,6 @@ Feature('Undoing drawing in one step').tag('@regress'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const BLUEVIOLET = { color: '#8A2BE2', diff --git a/e2e/tests/regression-tests/image-width.test.js b/e2e/tests/regression-tests/image-width.test.js index 51258f5b28..c19eca4258 100644 --- a/e2e/tests/regression-tests/image-width.test.js +++ b/e2e/tests/regression-tests/image-width.test.js @@ -1,44 +1,44 @@ -const assert = require('assert'); - -Feature('Image width parameter').tag('@regress'); - -const IMAGE = 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg'; - -const config = ` - - - - `; - -xScenario('Setting width 50% shouldn\'t break canvas size on resize of working area', async ({ I, LabelStudio, AtImageView, AtSidebar }) => { - const params = { - config, - data: { image: IMAGE }, - }; - - I.amOnPage('/'); - LabelStudio.init(params); - AtImageView.waitForImage(); - AtSidebar.seeRegions(0); - await AtImageView.lookForStage(); - let canvasSize = await AtImageView.getCanvasSize(); - let imageSize = await AtImageView.getImageSize(); - - // The sizes of the canvas and image element should be equal (or almost equal) - assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width)); - assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height)); - - // Create full-size region - // This step is just for visual identification of the bug - AtImageView.drawByDrag(5, 5, canvasSize.width - 10, canvasSize.height - 10); - - I.resizeWindow(800, 900); - I.resizeWindow(1200, 900); - - canvasSize = await AtImageView.getCanvasSize(); - imageSize = await AtImageView.getImageSize(); - - // The sizes should still be equal (or almost equal) - assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width)); - assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height)); -}); +const assert = require('assert'); + +Feature('Image width parameter').tag('@regress'); + +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; + +const config = ` + + + + `; + +Scenario('Setting width 50% shouldn\'t break canvas size on resize of working area', async ({ I, LabelStudio, AtImageView, AtSidebar }) => { + const params = { + config, + data: { image: IMAGE }, + }; + + I.amOnPage('/'); + LabelStudio.init(params); + AtImageView.waitForImage(); + AtSidebar.seeRegions(0); + await AtImageView.lookForStage(); + let canvasSize = await AtImageView.getCanvasSize(); + let imageSize = await AtImageView.getImageSize(); + + // The sizes of the canvas and image element should be equal (or almost equal) + assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width)); + assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height)); + + // Create full-size region + // This step is just for visual identification of the bug + AtImageView.drawByDrag(5, 5, canvasSize.width - 10, canvasSize.height - 10); + + I.resizeWindow(800, 900); + I.resizeWindow(1200, 900); + + canvasSize = await AtImageView.getCanvasSize(); + imageSize = await AtImageView.getImageSize(); + + // The sizes should still be equal (or almost equal) + assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width)); + assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height)); +}); diff --git a/e2e/tests/regression-tests/image-zoom-position.test.js b/e2e/tests/regression-tests/image-zoom-position.test.js index cfb94a114b..25ba8d01c0 100644 --- a/e2e/tests/regression-tests/image-zoom-position.test.js +++ b/e2e/tests/regression-tests/image-zoom-position.test.js @@ -3,7 +3,7 @@ const Helpers = require('../helpers'); Feature('Image zoom position').tag('@regress'); -const IMAGE = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/005eb6e2010fa7aa.jpg'; const config = ` diff --git a/e2e/tests/regression-tests/image-zoom-transform.test.js b/e2e/tests/regression-tests/image-zoom-transform.test.js index fb3fc7c88e..b53141c488 100644 --- a/e2e/tests/regression-tests/image-zoom-transform.test.js +++ b/e2e/tests/regression-tests/image-zoom-transform.test.js @@ -2,7 +2,7 @@ const assert = require('assert'); Feature('Zoomed transforms').tag('@regress'); -const IMAGE = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const config = ` diff --git a/e2e/tests/regression-tests/image.regions-select.test.js b/e2e/tests/regression-tests/image.regions-select.test.js index ef9ece8d7f..634d4e4057 100644 --- a/e2e/tests/regression-tests/image.regions-select.test.js +++ b/e2e/tests/regression-tests/image.regions-select.test.js @@ -1,7 +1,6 @@ Feature('Select region by clicking on it'); -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const BLUEVIOLET = { color: '#8A2BE2', diff --git a/e2e/tests/regression-tests/multiple-same-named-tools.test.js b/e2e/tests/regression-tests/multiple-same-named-tools.test.js index d1d31f0289..bd09a5dcf5 100644 --- a/e2e/tests/regression-tests/multiple-same-named-tools.test.js +++ b/e2e/tests/regression-tests/multiple-same-named-tools.test.js @@ -16,8 +16,7 @@ const config = ` `; const data = { - image: - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg', + image: 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg', }; Feature('Two or more same named tools referred same image').tag('@regress'); diff --git a/e2e/tests/regression-tests/preselected-choices.test.js b/e2e/tests/regression-tests/preselected-choices.test.js index 7a2cfd1987..226f91fdfe 100644 --- a/e2e/tests/regression-tests/preselected-choices.test.js +++ b/e2e/tests/regression-tests/preselected-choices.test.js @@ -1,91 +1,91 @@ -const assert = require('assert'); - -Feature('Preselected choices'); - -Scenario('Make a duplicate of annotation with preselected choices', async ({ I, LabelStudio, AtTopbar })=>{ - const params = { - config: ` - - - - - - -`, - data: { - text: 'Some text', - }, - annotations: [{ - id: 'test', - result: [ - { - from_name: 'choices', - id: 'mDp7Hpbw6_', - origin: 'manual', - to_name: 'text', - type: 'choices', - value: { - choices: ['Option 2'], - }, - }, - ], - }], - }; - - I.amOnPage('/'); - LabelStudio.init(params); - // Try to create copy of current annotation - AtTopbar.click('[aria-label="Copy Annotation"]'); - const duplicateResult = await LabelStudio.serialize(); - - // Make sure there are no results other than the copied ones - assert.deepStrictEqual(duplicateResult.length, 1); - assert.deepStrictEqual(duplicateResult[0].value.choices, ['Option 2']); - - // Create new annotation - I.click('[aria-label="Annotations List Toggle"]'); - I.click('[aria-label="Create Annotation"]'); - const annotationWithPresetValues = await LabelStudio.serialize(); - - // Check that there is only the result come from selecting by default - assert.deepStrictEqual(annotationWithPresetValues.length, 1); - assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']); -}); - -Scenario('Make a duplicate of empty annotation with preselected choices', async ({ I, LabelStudio, AtTopbar })=>{ - const params = { - config: ` - - - - - - -`, - data: { - text: 'Some text', - }, - annotations: [{ - id: 'test', - result: [], - }], - }; - - I.amOnPage('/'); - LabelStudio.init(params); - // Try to create copy of current annotation - AtTopbar.click('[aria-label="Copy Annotation"]'); - const duplicateResult = await LabelStudio.serialize(); - - // Make sure there are no preselected results - assert.deepStrictEqual(duplicateResult.length, 0); - - // Create new annotation - I.click('[aria-label="Annotations List Toggle"]'); - I.click('[aria-label="Create Annotation"]'); - const annotationWithPresetValues = await LabelStudio.serialize(); - - // Check that there is only the result come from selecting by default - assert.deepStrictEqual(annotationWithPresetValues.length, 1); - assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']); -}); +const assert = require('assert'); + +Feature('Preselected choices'); + +Scenario('Make a duplicate of annotation with preselected choices', async ({ I, LabelStudio, AtTopbar }) => { + const params = { + config: ` + + + + + + +`, + data: { + text: 'Some text', + }, + annotations: [{ + id: 'test', + result: [ + { + from_name: 'choices', + id: 'mDp7Hpbw6_', + origin: 'manual', + to_name: 'text', + type: 'choices', + value: { + choices: ['Option 2'], + }, + }, + ], + }], + }; + + I.amOnPage('/'); + LabelStudio.init(params); + // Try to create copy of current annotation + AtTopbar.click('[aria-label="Copy Annotation"]'); + const duplicateResult = await LabelStudio.serialize(); + + // Make sure there are no results other than the copied ones + assert.deepStrictEqual(duplicateResult.length, 1); + assert.deepStrictEqual(duplicateResult[0].value.choices, ['Option 2']); + + // Create new annotation + I.click('[aria-label="Annotations List Toggle"]'); + I.click('[aria-label="Create Annotation"]'); + const annotationWithPresetValues = await LabelStudio.serialize(); + + // Check that there is only the result come from selecting by default + assert.deepStrictEqual(annotationWithPresetValues.length, 1); + assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']); +}); + +Scenario('Make a duplicate of empty annotation with preselected choices', async ({ I, LabelStudio, AtTopbar }) => { + const params = { + config: ` + + + + + + +`, + data: { + text: 'Some text', + }, + annotations: [{ + id: 'test', + result: [], + }], + }; + + I.amOnPage('/'); + LabelStudio.init(params); + // Try to create copy of current annotation + AtTopbar.click('[aria-label="Copy Annotation"]'); + const duplicateResult = await LabelStudio.serialize(); + + // Make sure there are no preselected results + assert.deepStrictEqual(duplicateResult.length, 0); + + // Create new annotation + I.click('[aria-label="Annotations List Toggle"]'); + I.click('[aria-label="Create Annotation"]'); + const annotationWithPresetValues = await LabelStudio.serialize(); + + // Check that there is only the result come from selecting by default + assert.deepStrictEqual(annotationWithPresetValues.length, 1); + assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']); +}); diff --git a/e2e/tests/regression-tests/richtext.test.js b/e2e/tests/regression-tests/richtext.test.js index 542c897713..4b3d71734b 100644 --- a/e2e/tests/regression-tests/richtext.test.js +++ b/e2e/tests/regression-tests/richtext.test.js @@ -293,7 +293,7 @@ Scenario('Neighboring nested regions misplacement', async ({ I, LabelStudio, AtS }, ]); - Data(startBeforeEndParams).Scenario('Start before end problem', async ({ I, LabelStudio, AtSidebar, AtRichText, current }) => { + Data(startBeforeEndParams).Scenario('Start before end problem', async ({ I, LabelStudio, AtSidebar, current }) => { const { tag, content, range } = current; LabelStudio.setFeatureFlags({ diff --git a/e2e/tests/regression-tests/zoomed-image-displaying.test.js b/e2e/tests/regression-tests/zoomed-image-displaying.test.js index 994e72526c..dc8ad62569 100644 --- a/e2e/tests/regression-tests/zoomed-image-displaying.test.js +++ b/e2e/tests/regression-tests/zoomed-image-displaying.test.js @@ -2,7 +2,7 @@ const assert = require('assert'); Feature('Zoomed image displaying').tag('@regress'); -const IMAGE = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; const config = ` diff --git a/e2e/tests/taxonomy.test.js b/e2e/tests/taxonomy.test.js index 2fd8b96297..840bba6f93 100644 --- a/e2e/tests/taxonomy.test.js +++ b/e2e/tests/taxonomy.test.js @@ -1,4 +1,3 @@ -/* global Before */ const assert = require('assert'); Feature('Taxonomy'); diff --git a/e2e/tests/text-area.test.js b/e2e/tests/text-area.test.js index 6187dec563..6a2dfdea6f 100644 --- a/e2e/tests/text-area.test.js +++ b/e2e/tests/text-area.test.js @@ -1,6 +1,3 @@ - -/* global Feature, Scenario */ - const assert = require('assert'); const { serialize } = require('./helpers'); diff --git a/e2e/tests/timeseries.test.js b/e2e/tests/timeseries.test.js index 836c894e0a..a7ecfec8a4 100644 --- a/e2e/tests/timeseries.test.js +++ b/e2e/tests/timeseries.test.js @@ -285,7 +285,7 @@ Scenario('TimeSeries with optimized data', async ({ I, LabelStudio, AtTimeSeries I.say(`I see ${timestamp}`); assert(timestamp === lastTimestamp || timestamp - lastTimestamp === 1, - `Timestamps should not be skipped. Got ${lastTimestamp} and ${timestamp} but ${timestamp - 1} is missed`); + `Timestamps should not be skipped. Got ${lastTimestamp} and ${timestamp} but ${timestamp - 1} is missed`); } lastTimestamp = timestamp; } diff --git a/e2e/tests/toggle-visibility.test.js b/e2e/tests/toggle-visibility.test.js index 78731b1ee4..055bcd5216 100644 --- a/e2e/tests/toggle-visibility.test.js +++ b/e2e/tests/toggle-visibility.test.js @@ -17,8 +17,7 @@ const config = ` `; const data = { - image: - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg', + image: 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg', }; const createRegion = (from_name, type, values) => ({ @@ -207,7 +206,7 @@ examples.forEach(example => { examplesTable.add([title, config, data, result]); }); -Data(examplesTable).Scenario('Check visibility switcher through all examples', ({ I, AtSidebar, current })=> { +Data(examplesTable).Scenario('Check visibility switcher through all examples', ({ I, AtSidebar, current }) => { const { config, data, result } = current; const params = { annotations: [{ id: 'test', result }], config, data }; diff --git a/e2e/tests/unfinished-polygons.test.js b/e2e/tests/unfinished-polygons.test.js index 8c61bf9a84..0a23e873eb 100644 --- a/e2e/tests/unfinished-polygons.test.js +++ b/e2e/tests/unfinished-polygons.test.js @@ -1,606 +1,605 @@ -const { saveDraftLocally, getLocallySavedDraft } = require('./helpers'); -const assert = require('assert'); - - -Feature('Unfinished polygons'); - -const IMAGE = - 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg'; - -const CONFIG = ` - - - - - - - -`; - -const CONFIG_MULTIPLE = ` - - - - - - - - -`; - -const FLAGS = { - ff_feat_front_DEV_2576_undo_key_points_polygon_short: true, - ff_front_dev_2431_delete_polygon_points_080622_short: true, - ff_front_dev_2432_auto_save_polygon_draft_210622_short: true, -}; - -Scenario('Drafts for unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) { - I.amOnPage('/'); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - params: { - onSubmitDraft: saveDraftLocally, - }, - }); - LabelStudio.setFeatureFlags(FLAGS); - - AtImageView.waitForImage(); - - await AtImageView.lookForStage(); - - I.say('start drawing polygon without finishing it'); - AtLabels.clickLabel('Hello'); - AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]); - - I.say('wait until autosave'); - I.waitForFunction(()=>!!window.LSDraft, .5); - I.say('check result'); - const draft = await I.executeScript(getLocallySavedDraft); - - assert.strictEqual(draft[0].value.points.length, 3); - assert.strictEqual(draft[0].value.closed, false); -}); - -Scenario('Saving polygon drawing steps to history', async function({ I, LabelStudio, AtLabels, AtImageView }) { - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - }); - - AtImageView.waitForImage(); - - await AtImageView.lookForStage(); - - I.say('put one point of polygon'); - AtLabels.clickLabel('Hello'); - AtImageView.drawByClick(50, 50); - - I.say('check current history size'); - let historyStepsCount = await I.executeScript(()=>window.Htx.annotationStore.selected.history.history.length); - - assert.strictEqual(historyStepsCount, 2); - - I.say('try to draw some more points and close polygon'); - AtImageView.drawByClick(100, 50); - AtImageView.drawByClick(125, 100); - AtImageView.drawByClick(50, 50); - - I.say('check current history size and result'); - historyStepsCount = await I.executeScript(()=>window.Htx.annotationStore.selected.history.history.length); - assert.strictEqual(historyStepsCount, 5); - let result = await LabelStudio.serialize(); - - assert.strictEqual(result[0].value.points.length, 3); - assert.strictEqual(result[0].value.closed, true); - - I.say('try to undo closing and 2 last points'); - I.click('button[aria-label=Undo]'); - I.click('button[aria-label=Undo]'); - I.click('button[aria-label=Undo]'); - I.say('check current history index and result'); - historyStepsCount = await I.executeScript(()=>window.Htx.annotationStore.selected.history.undoIdx); - assert.strictEqual(historyStepsCount, 1); - result = await LabelStudio.serialize(); - assert.strictEqual(result[0].value.points.length, 1); - assert.strictEqual(result[0].value.closed, false); - -}); - -Scenario('Init an annotation with old format of closed polygon result', async function({ I, LabelStudio, AtImageView }) { - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - annotations: [ - { - id: 'test', - result: [ - { - 'original_width': 2242, - 'original_height': 2802, - 'image_rotation': 0, - 'value': { - 'points': [ - [ - 22.38442822384428, - 27.042801556420233, - ], - [ - 77.61557177615572, - 24.90272373540856, - ], - [ - 48.90510948905109, - 76.07003891050584, - ], - ], - 'polygonlabels': [ - 'Hello', - ], - }, - 'id': 'tNe7Bjmydb', - 'from_name': 'tag', - 'to_name': 'img', - 'type': 'polygonlabels', - 'origin': 'manual', - }, - ], - }, - ], - }); - - AtImageView.waitForImage(); - - const result = await LabelStudio.serialize(); - - assert.strictEqual(result[0].value.points.length, 3); - assert.strictEqual(result[0].value.closed, true); -}); - -Scenario('Init an annotation with result of new format of polygon results', async function({ I, LabelStudio, AtImageView }) { - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - annotations: [ - { - id: 'test', - result: [ - { - 'original_width': 2242, - 'original_height': 2802, - 'image_rotation': 0, - 'value': { - 'points': [ - [ - 40, - 40, - ], - [ - 50, - 40, - ], - [ - 50, - 50, - ], - [ - 40, - 50, - ], - ], - 'closed': true, - 'polygonlabels': [ - 'World', - ], - }, - 'id': 'tNe7Bjmydb_2', - 'from_name': 'tag', - 'to_name': 'img', - 'type': 'polygonlabels', - 'origin': 'manual', - }, - { - 'original_width': 2242, - 'original_height': 2802, - 'image_rotation': 0, - 'value': { - 'points': [ - [ - 10, - 10, - ], - [ - 30, - 10, - ], - [ - 20, - 20, - ], - ], - 'closed': false, - 'polygonlabels': [ - 'Hello', - ], - }, - 'id': 'tNe7Bjmydb', - 'from_name': 'tag', - 'to_name': 'img', - 'type': 'polygonlabels', - 'origin': 'manual', - }, - ], - }, - ], - }); - - AtImageView.waitForImage(); - - I.say('check loaded regions'); - let result = await LabelStudio.serialize(); - - assert.strictEqual(result.length, 2); - assert.strictEqual(result[0].value.points.length, 4); - assert.strictEqual(result[0].value.closed, true); - assert.strictEqual(result[1].value.points.length, 3); - assert.strictEqual(result[1].value.closed, false); - - I.say('try to continue drawing loaded unfinished region'); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .40 ); - result = await LabelStudio.serialize(); - assert.strictEqual(result[1].value.points.length, 4); - assert.strictEqual(result[1].value.closed, false); - - I.say('try to close loaded region'); - AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); - result = await LabelStudio.serialize(); - assert.strictEqual(result[1].value.points.length, 4); - assert.strictEqual(result[1].value.closed, true); - - // I.say("check that it is possible to go back throught history"); - // I.pressKey(['CommandOrControl', 'Z']); - // result = await LabelStudio.serialize(); - // assert.strictEqual(result[1].value.points.length, 4); - // assert.strictEqual(result[1].value.closed, false); - // - // I.say("check that it is possible to close this region again"); - // AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); - // result = await LabelStudio.serialize(); - // assert.strictEqual(result[1].value.points.length, 4); - // assert.strictEqual(result[1].value.closed, true); - -}); - -Scenario('Removing a polygon by going back through history', async function({ I, LabelStudio, AtLabels, AtImageView }) { - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - params: { - onSubmitDraft: saveDraftLocally, - }, - }); - - AtImageView.waitForImage(); - - await AtImageView.lookForStage(); - - I.say('start drawing polygon'); - AtLabels.clickLabel('Hello'); - AtImageView.drawByClickingPoints([[50,50], [100, 50]]); - - I.say('revert all changes and creating of the region'); - I.pressKey(['CommandOrControl', 'Z']); - I.pressKey(['CommandOrControl', 'Z']); - - I.say('polygon should disappear and polygon tool should be switched of'); - let result = await LabelStudio.serialize(); - - assert.strictEqual(result.length, 0); - - I.say('try to draw after that'); - AtImageView.drawByClickingPoints([[50,50], [100, 50]]); - - I.say('check if it was possible to do this (it shouldn\'t)'); - result = await LabelStudio.serialize(); - assert.strictEqual(result.length, 0); - - I.say('check if there were any errors'); - // The potential errors should be caught by `errorsCollector` plugin -}); - -Scenario('Continue annotating after closing region from draft', async function({ I, LabelStudio, AtLabels, AtImageView }) { - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - annotations: [ - { - id: 'test', - result: [ - { - 'original_width': 2242, - 'original_height': 2802, - 'image_rotation': 0, - 'value': { - 'points': [ - [ - 10, - 10, - ], - [ - 30, - 10, - ], - [ - 20, - 20, - ], - ], - 'closed': false, - 'polygonlabels': [ - 'Hello', - ], - }, - 'id': 'tNe7Bjmydb', - 'from_name': 'tag', - 'to_name': 'img', - 'type': 'polygonlabels', - 'origin': 'manual', - }, - ], - }, - ], - }); - - AtImageView.waitForImage(); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - I.say('close loaded region'); - AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); - - I.say('try to create another region'); - AtLabels.clickLabel('World'); - - AtImageView.drawByClickingPoints([ - [canvasSize.width * .40, canvasSize.height * .40], - [canvasSize.width * .50, canvasSize.height * .40], - [canvasSize.width * .50, canvasSize.height * .50], - [canvasSize.width * .40, canvasSize.height * .50], - [canvasSize.width * .40, canvasSize.height * .40], - ]); - - const result = await LabelStudio.serialize(); - - assert.strictEqual(result.length, 2); - assert.strictEqual(result[1].value.points.length, 4); - assert.strictEqual(result[1].value.closed, true); - -}); - -Scenario('Change label on unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) { - I.amOnPage('/'); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - params: { - onSubmitDraft: saveDraftLocally, - }, - }); - LabelStudio.setFeatureFlags(FLAGS); - - AtImageView.waitForImage(); - - await AtImageView.lookForStage(); - - I.say('start drawing polygon without finishing it'); - AtLabels.clickLabel('Hello'); - AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]); - AtLabels.clickLabel('World'); - - I.say('wait until autosave'); - I.waitForFunction(()=>!!window.LSDraft, .5); - I.say('check result'); - const draft = await I.executeScript(getLocallySavedDraft); - - assert.strictEqual(draft[0].value.polygonlabels[0], 'World'); -}); - - -const selectedLabelsVariants = new DataTable(['labels']); - -selectedLabelsVariants.add([['Label 1']]); -selectedLabelsVariants.add([['Label 2', 'Label 3']]); - -Data(selectedLabelsVariants).Scenario('Indicate selected labels', async function({ I, LabelStudio, AtLabels, AtImageView, current }) { - const { labels } = current; - - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG_MULTIPLE, - data: { - image: IMAGE, - }, - annotations: [ - { - id: 'test', - result: [ - { - 'original_width': 2242, - 'original_height': 2802, - 'image_rotation': 0, - 'value': { - 'points': [ - [ - 10, - 10, - ], - [ - 30, - 10, - ], - [ - 20, - 20, - ], - ], - 'closed': false, - 'polygonlabels': labels, - }, - 'id': 'tNe7Bjmydb', - 'from_name': 'tag', - 'to_name': 'img', - 'type': 'polygonlabels', - 'origin': 'manual', - }, - ], - }, - ], - }); - - AtImageView.waitForImage(); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - I.say('check if we see an indication of selected labels after resuming from draft'); - for (const label of labels) { - AtLabels.seeSelectedLabel(label); - } - - I.say('close loaded region'); - AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); - - I.say('check that we do not see an indication of selected after region completion'); - for (const label of labels) { - AtLabels.dontSeeSelectedLabel(label); - } - - I.say('check if we see an indication of selected labels after going back through the history'); - I.pressKey(['CommandOrControl', 'Z']); - for (const label of labels) { - AtLabels.seeSelectedLabel(label); - } - -}); - -const selectedPolygonAfterCreatingVariants = new DataTable(['shouldSelect', 'description']); - -selectedPolygonAfterCreatingVariants.add([false, 'Without set setting']); -selectedPolygonAfterCreatingVariants.add([true, 'With set setting']); - -Data(selectedPolygonAfterCreatingVariants).Scenario('Select polygon after creating from unfinished draft', async function({ I, LabelStudio, AtImageView, AtSidebar, AtSettings, current }) { - const { shouldSelect, description } = current; - - I.say(description); - I.amOnPage('/'); - LabelStudio.setFeatureFlags(FLAGS); - LabelStudio.init({ - config: CONFIG, - data: { - image: IMAGE, - }, - annotations: [ - { - id: 'test', - result: [ - { - 'original_width': 2242, - 'original_height': 2802, - 'image_rotation': 0, - 'value': { - 'points': [ - [ - 10, - 10, - ], - [ - 30, - 10, - ], - [ - 20, - 20, - ], - ], - 'closed': false, - 'polygonlabels': [ - 'Hello', - ], - }, - 'id': 'tNe7Bjmydb', - 'from_name': 'tag', - 'to_name': 'img', - 'type': 'polygonlabels', - 'origin': 'manual', - }, - ], - }, - ], - }); - - if (shouldSelect) { - AtSettings.open(); - AtSettings.setGeneralSettings({ - [AtSettings.GENERAL_SETTINGS.AUTO_SELECT_REGION]: shouldSelect, - }); - AtSettings.close(); - } - - AtImageView.waitForImage(); - await AtImageView.lookForStage(); - const canvasSize = await AtImageView.getCanvasSize(); - - I.say('close loaded region'); - AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); - - I.say(`check that region ${shouldSelect ? 'is' : 'is not'} selected`); - if (shouldSelect) { - AtSidebar.seeSelectedRegion(); - } else { - AtSidebar.dontSeeSelectedRegion(); - } - - I.say('unselect regions'); - I.pressKey('u'); - AtSidebar.dontSeeSelectedRegion(); - - I.say('go back through the history'); - I.pressKey(['CommandOrControl', 'Z']); - AtSidebar.dontSeeSelectedRegion(); - - I.say('repeat creation and checking'); - AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); - if (shouldSelect) { - AtSidebar.seeSelectedRegion(); - } else { - AtSidebar.dontSeeSelectedRegion(); - } - -}); +const { saveDraftLocally, getLocallySavedDraft } = require('./helpers'); +const assert = require('assert'); + + +Feature('Unfinished polygons'); + +const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg'; + +const CONFIG = ` + + + + + + + +`; + +const CONFIG_MULTIPLE = ` + + + + + + + + +`; + +const FLAGS = { + ff_feat_front_DEV_2576_undo_key_points_polygon_short: true, + ff_front_dev_2431_delete_polygon_points_080622_short: true, + ff_front_dev_2432_auto_save_polygon_draft_210622_short: true, +}; + +Scenario('Drafts for unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) { + I.amOnPage('/'); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + params: { + onSubmitDraft: saveDraftLocally, + }, + }); + LabelStudio.setFeatureFlags(FLAGS); + + AtImageView.waitForImage(); + + await AtImageView.lookForStage(); + + I.say('start drawing polygon without finishing it'); + AtLabels.clickLabel('Hello'); + AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]); + + I.say('wait until autosave'); + I.waitForFunction(() => !!window.LSDraft, .5); + I.say('check result'); + const draft = await I.executeScript(getLocallySavedDraft); + + assert.strictEqual(draft[0].value.points.length, 3); + assert.strictEqual(draft[0].value.closed, false); +}); + +Scenario('Saving polygon drawing steps to history', async function({ I, LabelStudio, AtLabels, AtImageView }) { + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + }); + + AtImageView.waitForImage(); + + await AtImageView.lookForStage(); + + I.say('put one point of polygon'); + AtLabels.clickLabel('Hello'); + AtImageView.drawByClick(50, 50); + + I.say('check current history size'); + let historyStepsCount = await I.executeScript(() => window.Htx.annotationStore.selected.history.history.length); + + assert.strictEqual(historyStepsCount, 2); + + I.say('try to draw some more points and close polygon'); + AtImageView.drawByClick(100, 50); + AtImageView.drawByClick(125, 100); + AtImageView.drawByClick(50, 50); + + I.say('check current history size and result'); + historyStepsCount = await I.executeScript(() => window.Htx.annotationStore.selected.history.history.length); + assert.strictEqual(historyStepsCount, 5); + let result = await LabelStudio.serialize(); + + assert.strictEqual(result[0].value.points.length, 3); + assert.strictEqual(result[0].value.closed, true); + + I.say('try to undo closing and 2 last points'); + I.click('button[aria-label=Undo]'); + I.click('button[aria-label=Undo]'); + I.click('button[aria-label=Undo]'); + I.say('check current history index and result'); + historyStepsCount = await I.executeScript(() => window.Htx.annotationStore.selected.history.undoIdx); + assert.strictEqual(historyStepsCount, 1); + result = await LabelStudio.serialize(); + assert.strictEqual(result[0].value.points.length, 1); + assert.strictEqual(result[0].value.closed, false); + +}); + +Scenario('Init an annotation with old format of closed polygon result', async function({ I, LabelStudio, AtImageView }) { + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + annotations: [ + { + id: 'test', + result: [ + { + 'original_width': 2242, + 'original_height': 2802, + 'image_rotation': 0, + 'value': { + 'points': [ + [ + 22.38442822384428, + 27.042801556420233, + ], + [ + 77.61557177615572, + 24.90272373540856, + ], + [ + 48.90510948905109, + 76.07003891050584, + ], + ], + 'polygonlabels': [ + 'Hello', + ], + }, + 'id': 'tNe7Bjmydb', + 'from_name': 'tag', + 'to_name': 'img', + 'type': 'polygonlabels', + 'origin': 'manual', + }, + ], + }, + ], + }); + + AtImageView.waitForImage(); + + const result = await LabelStudio.serialize(); + + assert.strictEqual(result[0].value.points.length, 3); + assert.strictEqual(result[0].value.closed, true); +}); + +Scenario('Init an annotation with result of new format of polygon results', async function({ I, LabelStudio, AtImageView }) { + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + annotations: [ + { + id: 'test', + result: [ + { + 'original_width': 2242, + 'original_height': 2802, + 'image_rotation': 0, + 'value': { + 'points': [ + [ + 40, + 40, + ], + [ + 50, + 40, + ], + [ + 50, + 50, + ], + [ + 40, + 50, + ], + ], + 'closed': true, + 'polygonlabels': [ + 'World', + ], + }, + 'id': 'tNe7Bjmydb_2', + 'from_name': 'tag', + 'to_name': 'img', + 'type': 'polygonlabels', + 'origin': 'manual', + }, + { + 'original_width': 2242, + 'original_height': 2802, + 'image_rotation': 0, + 'value': { + 'points': [ + [ + 10, + 10, + ], + [ + 30, + 10, + ], + [ + 20, + 20, + ], + ], + 'closed': false, + 'polygonlabels': [ + 'Hello', + ], + }, + 'id': 'tNe7Bjmydb', + 'from_name': 'tag', + 'to_name': 'img', + 'type': 'polygonlabels', + 'origin': 'manual', + }, + ], + }, + ], + }); + + AtImageView.waitForImage(); + + I.say('check loaded regions'); + let result = await LabelStudio.serialize(); + + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].value.points.length, 4); + assert.strictEqual(result[0].value.closed, true); + assert.strictEqual(result[1].value.points.length, 3); + assert.strictEqual(result[1].value.closed, false); + + I.say('try to continue drawing loaded unfinished region'); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .40); + result = await LabelStudio.serialize(); + assert.strictEqual(result[1].value.points.length, 4); + assert.strictEqual(result[1].value.closed, false); + + I.say('try to close loaded region'); + AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); + result = await LabelStudio.serialize(); + assert.strictEqual(result[1].value.points.length, 4); + assert.strictEqual(result[1].value.closed, true); + + // I.say("check that it is possible to go back throught history"); + // I.pressKey(['CommandOrControl', 'Z']); + // result = await LabelStudio.serialize(); + // assert.strictEqual(result[1].value.points.length, 4); + // assert.strictEqual(result[1].value.closed, false); + // + // I.say("check that it is possible to close this region again"); + // AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); + // result = await LabelStudio.serialize(); + // assert.strictEqual(result[1].value.points.length, 4); + // assert.strictEqual(result[1].value.closed, true); + +}); + +Scenario('Removing a polygon by going back through history', async function({ I, LabelStudio, AtLabels, AtImageView }) { + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + params: { + onSubmitDraft: saveDraftLocally, + }, + }); + + AtImageView.waitForImage(); + + await AtImageView.lookForStage(); + + I.say('start drawing polygon'); + AtLabels.clickLabel('Hello'); + AtImageView.drawByClickingPoints([[50,50], [100, 50]]); + + I.say('revert all changes and creating of the region'); + I.pressKey(['CommandOrControl', 'Z']); + I.pressKey(['CommandOrControl', 'Z']); + + I.say('polygon should disappear and polygon tool should be switched of'); + let result = await LabelStudio.serialize(); + + assert.strictEqual(result.length, 0); + + I.say('try to draw after that'); + AtImageView.drawByClickingPoints([[50,50], [100, 50]]); + + I.say('check if it was possible to do this (it shouldn\'t)'); + result = await LabelStudio.serialize(); + assert.strictEqual(result.length, 0); + + I.say('check if there were any errors'); + // The potential errors should be caught by `errorsCollector` plugin +}); + +Scenario('Continue annotating after closing region from draft', async function({ I, LabelStudio, AtLabels, AtImageView }) { + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + annotations: [ + { + id: 'test', + result: [ + { + 'original_width': 2242, + 'original_height': 2802, + 'image_rotation': 0, + 'value': { + 'points': [ + [ + 10, + 10, + ], + [ + 30, + 10, + ], + [ + 20, + 20, + ], + ], + 'closed': false, + 'polygonlabels': [ + 'Hello', + ], + }, + 'id': 'tNe7Bjmydb', + 'from_name': 'tag', + 'to_name': 'img', + 'type': 'polygonlabels', + 'origin': 'manual', + }, + ], + }, + ], + }); + + AtImageView.waitForImage(); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + I.say('close loaded region'); + AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); + + I.say('try to create another region'); + AtLabels.clickLabel('World'); + + AtImageView.drawByClickingPoints([ + [canvasSize.width * .40, canvasSize.height * .40], + [canvasSize.width * .50, canvasSize.height * .40], + [canvasSize.width * .50, canvasSize.height * .50], + [canvasSize.width * .40, canvasSize.height * .50], + [canvasSize.width * .40, canvasSize.height * .40], + ]); + + const result = await LabelStudio.serialize(); + + assert.strictEqual(result.length, 2); + assert.strictEqual(result[1].value.points.length, 4); + assert.strictEqual(result[1].value.closed, true); + +}); + +Scenario('Change label on unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) { + I.amOnPage('/'); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + params: { + onSubmitDraft: saveDraftLocally, + }, + }); + LabelStudio.setFeatureFlags(FLAGS); + + AtImageView.waitForImage(); + + await AtImageView.lookForStage(); + + I.say('start drawing polygon without finishing it'); + AtLabels.clickLabel('Hello'); + AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]); + AtLabels.clickLabel('World'); + + I.say('wait until autosave'); + I.waitForFunction(() => !!window.LSDraft, .5); + I.say('check result'); + const draft = await I.executeScript(getLocallySavedDraft); + + assert.strictEqual(draft[0].value.polygonlabels[0], 'World'); +}); + + +const selectedLabelsVariants = new DataTable(['labels']); + +selectedLabelsVariants.add([['Label 1']]); +selectedLabelsVariants.add([['Label 2', 'Label 3']]); + +Data(selectedLabelsVariants).Scenario('Indicate selected labels', async function({ I, LabelStudio, AtLabels, AtImageView, current }) { + const { labels } = current; + + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG_MULTIPLE, + data: { + image: IMAGE, + }, + annotations: [ + { + id: 'test', + result: [ + { + 'original_width': 2242, + 'original_height': 2802, + 'image_rotation': 0, + 'value': { + 'points': [ + [ + 10, + 10, + ], + [ + 30, + 10, + ], + [ + 20, + 20, + ], + ], + 'closed': false, + 'polygonlabels': labels, + }, + 'id': 'tNe7Bjmydb', + 'from_name': 'tag', + 'to_name': 'img', + 'type': 'polygonlabels', + 'origin': 'manual', + }, + ], + }, + ], + }); + + AtImageView.waitForImage(); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + I.say('check if we see an indication of selected labels after resuming from draft'); + for (const label of labels) { + AtLabels.seeSelectedLabel(label); + } + + I.say('close loaded region'); + AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); + + I.say('check that we do not see an indication of selected after region completion'); + for (const label of labels) { + AtLabels.dontSeeSelectedLabel(label); + } + + I.say('check if we see an indication of selected labels after going back through the history'); + I.pressKey(['CommandOrControl', 'Z']); + for (const label of labels) { + AtLabels.seeSelectedLabel(label); + } + +}); + +const selectedPolygonAfterCreatingVariants = new DataTable(['shouldSelect', 'description']); + +selectedPolygonAfterCreatingVariants.add([false, 'Without set setting']); +selectedPolygonAfterCreatingVariants.add([true, 'With set setting']); + +Data(selectedPolygonAfterCreatingVariants).Scenario('Select polygon after creating from unfinished draft', async function({ I, LabelStudio, AtImageView, AtSidebar, AtSettings, current }) { + const { shouldSelect, description } = current; + + I.say(description); + I.amOnPage('/'); + LabelStudio.setFeatureFlags(FLAGS); + LabelStudio.init({ + config: CONFIG, + data: { + image: IMAGE, + }, + annotations: [ + { + id: 'test', + result: [ + { + 'original_width': 2242, + 'original_height': 2802, + 'image_rotation': 0, + 'value': { + 'points': [ + [ + 10, + 10, + ], + [ + 30, + 10, + ], + [ + 20, + 20, + ], + ], + 'closed': false, + 'polygonlabels': [ + 'Hello', + ], + }, + 'id': 'tNe7Bjmydb', + 'from_name': 'tag', + 'to_name': 'img', + 'type': 'polygonlabels', + 'origin': 'manual', + }, + ], + }, + ], + }); + + if (shouldSelect) { + AtSettings.open(); + AtSettings.setGeneralSettings({ + [AtSettings.GENERAL_SETTINGS.AUTO_SELECT_REGION]: shouldSelect, + }); + AtSettings.close(); + } + + AtImageView.waitForImage(); + await AtImageView.lookForStage(); + const canvasSize = await AtImageView.getCanvasSize(); + + I.say('close loaded region'); + AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); + + I.say(`check that region ${shouldSelect ? 'is' : 'is not'} selected`); + if (shouldSelect) { + AtSidebar.seeSelectedRegion(); + } else { + AtSidebar.dontSeeSelectedRegion(); + } + + I.say('unselect regions'); + I.pressKey('u'); + AtSidebar.dontSeeSelectedRegion(); + + I.say('go back through the history'); + I.pressKey(['CommandOrControl', 'Z']); + AtSidebar.dontSeeSelectedRegion(); + + I.say('repeat creation and checking'); + AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10); + if (shouldSelect) { + AtSidebar.seeSelectedRegion(); + } else { + AtSidebar.dontSeeSelectedRegion(); + } + +});