image.test.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. const { serialize } = require("./helpers");
  2. const assert = require("assert");
  3. Feature("Test Image object");
  4. const config = `
  5. <View>
  6. <Image name="img" value="$image"></Image>
  7. <RectangleLabels name="tag" toName="img">
  8. <Label value="Planet"></Label>
  9. <Label value="Moonwalker" background="blue"></Label>
  10. </RectangleLabels>
  11. </View>`;
  12. const configEllipse = `
  13. <View>
  14. <Image name="img" value="$image"></Image>
  15. <EllipseLabels name="tag" toName="img">
  16. <Label value="Planet"></Label>
  17. <Label value="Moonwalker" background="blue"></Label>
  18. </EllipseLabels>
  19. </View>`;
  20. const perRegionConfig = `
  21. <View>
  22. <Image name="img" value="$image"></Image>
  23. <RectangleLabels name="tag" toName="img">
  24. <Label value="Planet"></Label>
  25. <Label value="Moonwalker" background="blue"></Label>
  26. </RectangleLabels>
  27. <TextArea name="answer" toName="img" perRegion="true" />
  28. </View>`;
  29. const createRegion = (from_name, type, values) => ({
  30. id: "Dx_aB91ISN",
  31. source: "$image",
  32. from_name,
  33. to_name: "img",
  34. type,
  35. origin: "manual",
  36. value: {
  37. height: 10.458911419423693,
  38. rotation: 0,
  39. width: 12.4,
  40. x: 50.8,
  41. y: 5.869797225186766,
  42. ...values,
  43. },
  44. });
  45. const annotationMoonwalker = {
  46. id: "1001",
  47. lead_time: 15.053,
  48. result: [createRegion("tag", "rectanglelabels", { rectanglelabels: ["Moonwalker"] })],
  49. };
  50. // perregion regions have the same id as main region
  51. // and their own data (`text` in this case)
  52. const annotationWithPerRegion = {
  53. id: "1002",
  54. result: [annotationMoonwalker.result[0], createRegion("answer", "textarea", { text: ["blah"] })],
  55. };
  56. const image =
  57. "https://htx-pub.s3.us-east-1.amazonaws.com/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg";
  58. Scenario("Check Rect region for Image", async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels }) => {
  59. const params = {
  60. config,
  61. data: { image },
  62. annotations: [annotationMoonwalker],
  63. };
  64. const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
  65. I.amOnPage("/");
  66. LabelStudio.init(params);
  67. AtDetailsPanel.collapsePanel();
  68. LabelStudio.waitForObjectsReady();
  69. await AtImageView.lookForStage();
  70. AtOutliner.seeRegions(1);
  71. // select first and only region
  72. AtOutliner.clickRegion(1);
  73. AtOutliner.seeSelectedRegion();
  74. // click on region's rect on the canvas
  75. AtImageView.clickAt(330, 80);
  76. I.waitTicks(1);
  77. AtOutliner.dontSeeSelectedRegion();
  78. });
  79. Scenario("Image with perRegion tags", async ({ I, LabelStudio, AtOutliner }) => {
  80. let result;
  81. const params = {
  82. config: perRegionConfig,
  83. data: { image },
  84. annotations: [annotationWithPerRegion],
  85. };
  86. I.amOnPage("/");
  87. LabelStudio.init(params);
  88. LabelStudio.waitForObjectsReady();
  89. AtOutliner.seeRegions(1);
  90. // select first and only region
  91. AtOutliner.clickRegion(1);
  92. AtOutliner.seeSelectedRegion();
  93. // check that there is deserialized text for this region; and without doubles
  94. I.seeNumberOfElements(locate("mark").withText("blah"), 1);
  95. // add another note via textarea
  96. I.fillField("[name=answer]", "another");
  97. I.pressKey("Enter");
  98. // texts are concatenated in the regions list (now with \n, so check separately)
  99. I.seeNumberOfElements(locate("mark").withText("blah"), 1);
  100. I.seeNumberOfElements(locate("mark").withText("another"), 1);
  101. // and there is only one tag with all these texts
  102. I.seeNumberOfElements("mark", 2);
  103. // serialize with two textarea regions
  104. result = await I.executeScript(serialize);
  105. assert.strictEqual(result.length, 2);
  106. assert.strictEqual(result[0].id, "Dx_aB91ISN");
  107. assert.strictEqual(result[1].id, "Dx_aB91ISN");
  108. assert.deepStrictEqual(result[0].value.rectanglelabels, ["Moonwalker"]);
  109. assert.deepStrictEqual(result[1].value.text, ["blah", "another"]);
  110. // delete first deserialized text and check that only "another" left
  111. I.click(locate('[aria-label="Delete Region"]').inside('[data-testid="textarea-region"]'));
  112. I.dontSeeElement(locate("mark").withText("blah"));
  113. I.seeElement(locate("mark").withText("another"));
  114. result = await I.executeScript(serialize);
  115. assert.strictEqual(result.length, 2);
  116. assert.deepStrictEqual(result[0].value.rectanglelabels, ["Moonwalker"]);
  117. assert.deepStrictEqual(result[1].value.text, ["another"]);
  118. // delete also "another" region
  119. I.click(locate('[aria-label="Delete Region"]').inside('[data-testid="textarea-region"]'));
  120. // there are should be no texts left at all
  121. I.dontSeeElement(locate("mark"));
  122. result = await I.executeScript(serialize);
  123. assert.strictEqual(result.length, 1);
  124. assert.deepStrictEqual(result[0].value.rectanglelabels, ["Moonwalker"]);
  125. });
  126. Scenario(
  127. "Can't create rectangles outside of canvas",
  128. async ({ I, AtLabels, AtOutliner, AtImageView, LabelStudio, AtPanels }) => {
  129. const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
  130. I.amOnPage("/");
  131. LabelStudio.init({
  132. config,
  133. data: { image },
  134. task: {
  135. id: 0,
  136. annotations: [{ id: 1001, result: [] }],
  137. predictions: [],
  138. },
  139. });
  140. AtDetailsPanel.collapsePanel();
  141. LabelStudio.waitForObjectsReady();
  142. await AtImageView.lookForStage();
  143. const stage = AtImageView.stageBBox();
  144. I.say("Drawing region in the upper left corner");
  145. AtLabels.clickLabel("Planet");
  146. AtImageView.drawByDrag(100, 100, -200, -200);
  147. I.say("Drawing region in the upper right corner");
  148. AtLabels.clickLabel("Planet");
  149. AtImageView.drawByDrag(stage.width - 100, 100, stage.width + 100, -100);
  150. I.say("Drawing region in the bottom left corner");
  151. AtLabels.clickLabel("Planet");
  152. AtImageView.drawByDrag(100, stage.height - 100, -100, stage.height + 100);
  153. I.say("Drawing region in the bottom right corner");
  154. AtLabels.clickLabel("Planet");
  155. AtImageView.drawByDrag(stage.width - 100, stage.height - 100, stage.width + 100, stage.height + 100);
  156. AtOutliner.seeRegions(4);
  157. const result = await LabelStudio.serialize();
  158. const [r1, r2, r3, r4] = result.map((r) => r.value);
  159. I.say("First region should be in the corner");
  160. assert.strictEqual(r1.x, 0);
  161. assert.equal(r1.y, 0);
  162. I.say("Second region should be in the corner");
  163. assert.equal(Math.round(r2.x + r2.width), 100);
  164. assert.equal(r2.y, 0);
  165. I.say("Third region should be in the corner");
  166. assert.equal(r3.x, 0);
  167. assert.equal(Math.round(r3.y + r3.height), 100);
  168. I.say("Fourth region should be in the corner");
  169. assert.equal(Math.round(r4.x + r4.width), 100);
  170. assert.equal(Math.round(r4.y + r4.height), 100);
  171. },
  172. );
  173. Scenario(
  174. "Can't create ellipses outside of canvas",
  175. async ({ I, AtLabels, AtOutliner, AtImageView, LabelStudio, AtPanels }) => {
  176. const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
  177. I.amOnPage("/");
  178. LabelStudio.init({
  179. config: configEllipse,
  180. data: { image },
  181. task: {
  182. id: 0,
  183. annotations: [{ id: 1001, result: [] }],
  184. predictions: [],
  185. },
  186. });
  187. AtDetailsPanel.collapsePanel();
  188. LabelStudio.waitForObjectsReady();
  189. await AtImageView.lookForStage();
  190. const stage = AtImageView.stageBBox();
  191. const ellipses = [
  192. // top-left corner
  193. [100, 100, -200, -200],
  194. // top-right corner
  195. [stage.width - 100, 100, stage.width + 100, -100],
  196. // bottom-left corner
  197. [100, stage.height - 100, -100, stage.height + 100],
  198. // bottom-right corner
  199. [stage.width - 100, stage.height - 100, stage.width + 100, stage.height + 100],
  200. ];
  201. for (const ellipse of ellipses) {
  202. I.say("Drawing region in the upper left corner");
  203. AtLabels.clickLabel("Planet");
  204. AtImageView.drawByDrag(...ellipse);
  205. }
  206. AtOutliner.seeRegions(4);
  207. const result = await LabelStudio.serialize();
  208. const radiusX = (100 / stage.width) * 100;
  209. const radiusY = (100 / stage.height) * 100;
  210. for (let i = 0; i < result.length; i++) {
  211. const res = result[i].value;
  212. const region = ellipses[i];
  213. I.say("Make sure ellipse radius is correct (should be same for all)");
  214. // toFixed is to bypass JS floating point precision limitations (f32 sucks)
  215. assert.strictEqual(res.radiusX.toFixed(3), radiusX.toFixed(3));
  216. assert.strictEqual(res.radiusY.toFixed(3), radiusY.toFixed(3));
  217. I.say("Make sure that center is in correct spot");
  218. const [expectedX, expectedY] = [(region[0] / stage.width) * 100, (region[1] / stage.height) * 100];
  219. assert.strictEqual(res.x.toFixed(3), expectedX.toFixed(3));
  220. assert.strictEqual(res.y.toFixed(3), expectedY.toFixed(3));
  221. }
  222. },
  223. );