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">
${shape}Labels>
`,
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,
closed: true,
},
};
},
},
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, AtOutliner, AtPanels, 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;
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
LabelStudio.init(getParamsWithLabels(shapeName));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.seeRegions(0);
await AtImageView.lookForStage();
// Draw two regions
I.pressKey("1");
drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
AtOutliner.seeRegions(1);
I.pressKey("1");
drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
AtOutliner.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));
AtOutliner.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);
},
)
.retry(3);
Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)).Scenario(
"Resizing a single region",
async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
AtOutliner.dontSeeIncompleteRegion();
// Select the shape
AtImageView.clickAt(100, 100);
AtOutliner.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));
},
);
// Currently flipping is handled correctly only for rectangles.
Data(shapesTable.filter(({ shapeName }) => shapeName === "Rectangle")).Scenario(
"Flip region during resizing",
async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.seeRegions(0);
await AtImageView.lookForStage();
const canvasSize = await AtImageView.getCanvasSize();
const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
let rectangleResult;
// Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
I.pressKey(Shape.hotKey);
drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView);
AtOutliner.seeRegions(1);
AtOutliner.dontSeeIncompleteRegion();
// Select the shape
AtImageView.clickAt(100, 100);
AtOutliner.seeSelectedRegion();
// Switch to move tool to force appearance of transformer
I.pressKey("v");
const isTransformerExist = await AtImageView.isTransformerExist();
assert.strictEqual(isTransformerExist, true);
// Flip the shape horizontally
// Move the left anchor to the right further than region width, effectively flipping it and reducing width to 50px
AtImageView.drawByDrag(50, 100, 150, 0);
// Check resulting sizes
rectangleResult = await LabelStudio.serialize();
const exceptedResult = Shape.byBBox(150, 50, 50, 100).result;
Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult));
// new center of the region
const center = [150 + 25, 50 + 50];
// Rotate the shape by 45 degrees, rotation handle is 50px above the top anchor
// we move the rotation handle to the right to rotate the shape by 45 degrees
AtImageView.drawByDrag(center[0], 0, center[1], 0);
rectangleResult = await LabelStudio.serialize();
Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 45);
// Flip the shape horizontally with non-zero rotation
const shift = (50 * 2) / Math.SQRT2;
AtImageView.drawByDrag(center[0] - 25 / Math.SQRT2, center[1] - 25 / Math.SQRT2, shift, shift);
const secondFlipResult = {
...convertToImageSize(
Shape.byBBox(
center[0] + 25 / Math.SQRT2 + 50 / Math.SQRT2,
center[1] + 25 / Math.SQRT2 - 50 / Math.SQRT2,
50,
100,
).result,
),
rotation: 45,
};
rectangleResult = await LabelStudio.serialize();
// flipping is not very precise, so we have to increase the tolerance
Asserts.deepEqualWithTolerance(rectangleResult[0].value, secondFlipResult, 0);
},
);
Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)).Scenario(
"Resizing a single region with zoom",
async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, Regions, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
LabelStudio.setFeatureFlags({
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));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.seeRegions(0);
AtDetailsPanel.seeExpandButton();
await AtImageView.lookForStage();
// Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
I.pressKey(Shape.hotKey);
drawShapeByBbox(Shape, 50, 50, 300, 300, AtImageView);
AtOutliner.seeRegions(1);
AtOutliner.dontSeeIncompleteRegion();
// Select the shape
AtImageView.clickAt(100, 100);
AtOutliner.seeSelectedRegion();
// Switch to move tool to force appearance of transformer
I.pressKey("v");
const isTransformerExist = await AtImageView.isTransformerExist();
assert.strictEqual(isTransformerExist, true);
// it won't be real zoom scale, so we have to compensate it,
// and in the current specific situation it should be done my maxScale
const { maxScale } = await AtImageView.getZoomProps();
AtImageView.setZoom(3 * maxScale, 0, 0);
await AtImageView.lookForStage();
const prevRegionBBox = await Regions.getBBoxByRegionIdx(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 regionBBox = await Regions.getBBoxByRegionIdx(0);
Asserts.deepEqualWithTolerance(regionBBox, prevRegionBBox, 2);
},
);
Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
"Simple rotating",
async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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 * 0.4,
y: canvasSize.height * 0.4,
width: canvasSize.width * 0.2,
height: canvasSize.height * 0.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);
AtOutliner.seeRegions(1);
// Select the shape and check that transformer appears
AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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 * 0.2,
width: canvasSize.width * 0.2,
height: canvasSize.height * 0.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);
AtOutliner.seeRegions(1);
// Select the shape and check that transformer appears
AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Select the shape and check that transformer appears
AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
AtOutliner.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);
AtOutliner.seeRegions(1);
// Select the shape and check that transformer appears
AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Select it
AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
AtOutliner.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");
AtOutliner.dontSeeSelectedRegion();
// Select it again
AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
AtOutliner.seeSelectedRegion();
// Wait for transformer to be properly initialized after re-selection
I.waitTicks(3);
// 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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Draw the second region
I.pressKey(Shape.hotKey);
drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
AtOutliner.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],
]);
AtOutliner.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");
AtOutliner.dontSeeSelectedRegion();
// Select them again
AtImageView.drawThroughPoints([
[transformerBbox.x - 20, transformerBbox.y - 20],
[transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
]);
AtOutliner.seeSelectedRegion();
// Wait for transformer to be properly initialized after re-selection
I.waitTicks(3);
// 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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Draw the second region
I.pressKey(Shape.hotKey);
I.pressKeyDown("CommandOrControl");
drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
I.pressKeyUp("CommandOrControl");
AtOutliner.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],
]);
AtOutliner.seeSelectedRegion();
// Scale the shapes vertically
AtImageView.drawByDrag(transformerBboxCenter.x, transformerBbox.y + transformerBbox.height, 0, 50);
transformerBbox.height += 50;
AtOutliner.seeSelectedRegion();
// Scale the shapes horizontally
AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBboxCenter.y, 50, 0);
transformerBbox.width += 50;
AtOutliner.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;
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Draw the second region
I.pressKey(Shape.hotKey);
drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
AtOutliner.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);
AtOutliner.seeRegions(3);
I.pressKey(Shape.hotKey);
drawShapeByBbox(Shape, bbox2.x + bbox2.width, bbox2.y + bbox2.height, 0, 0, AtImageView);
AtOutliner.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],
]);
AtOutliner.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,
);
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Select it
AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Draw the second region
I.pressKey(Shape.hotKey);
I.pressKeyDown("CommandOrControl");
drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
I.pressKeyUp("CommandOrControl");
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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,
);
AtOutliner.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, AtOutliner, AtPanels, current }) => {
const { shapeName } = current;
const Shape = shapes[shapeName];
const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
I.amOnPage("/");
LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
AtDetailsPanel.collapsePanel();
LabelStudio.waitForObjectsReady();
AtOutliner.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);
AtOutliner.seeRegions(1);
// Select it
AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
AtOutliner.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);
AtOutliner.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();
}
},
);