From f2b5a7ccd29eb8633bf935fda531e134565d40ea Mon Sep 17 00:00:00 2001
From: Csanad Tabajdi <tabajdi.csanad@proton.me>
Date: Wed, 19 Feb 2025 19:35:05 +0100
Subject: [PATCH 1/4] Update terminology for AI scoring and enhance settings
 defaults. Fix: missing auto annotations

---
 package-lock.json | 76 +----------------------------------------------
 src/app.html      | 29 ++++++++++++++++++
 2 files changed, 30 insertions(+), 75 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index c0166d6..4390682 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,11 +10,9 @@
       "license": "MIT",
       "dependencies": {
         "grunt-contrib-concat": "^2.1.0",
-        "grunt-contrib-uglify": "^5.2.2"
-      },
-      "devDependencies": {
         "grunt-contrib-cssmin": "^5.0.0",
         "grunt-contrib-htmlmin": "^3.1.0",
+        "grunt-contrib-uglify": "^5.2.2",
         "grunt-minify-html": "^3.0.0"
       }
     },
@@ -29,7 +27,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
       "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -54,7 +51,6 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz",
       "integrity": "sha512-sQN85FUGbEUBLyQiSJp4v8yAHTST2ao1WVXb/L8jkVqQTsypZuJQD0gMVeOLoSZBz21p22izF6HsBQP16QKQtg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/argparse": {
@@ -129,7 +125,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
       "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "no-case": "^2.2.0",
@@ -156,7 +151,6 @@
       "version": "5.3.3",
       "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
       "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "source-map": "~0.6.0"
@@ -169,7 +163,6 @@
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
       "license": "BSD-3-Clause",
       "engines": {
         "node": ">=0.10.0"
@@ -179,7 +172,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz",
       "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "ansi-regex": "^2.1.1",
@@ -194,7 +186,6 @@
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
       "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-convert": "^1.9.3",
@@ -223,7 +214,6 @@
       "version": "1.9.1",
       "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
       "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-name": "^1.0.0",
@@ -234,7 +224,6 @@
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
       "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-name": "1.1.3"
@@ -244,14 +233,12 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
       "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/colornames": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz",
       "integrity": "sha512-/pyV40IrsdulWv+wFPmERh9k/mjsPZ64yUMDmWrtj/k1nmgrzzIENWKdaVKyBbvFdQWqkcaRxr+polCo3VMe7A==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/colors": {
@@ -268,7 +255,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
       "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color": "^3.1.3",
@@ -279,7 +265,6 @@
       "version": "2.20.3",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/concat-map": {
@@ -293,7 +278,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
       "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "es5-ext": "^0.10.64",
@@ -327,7 +311,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
       "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "colorspace": "1.1.x",
@@ -339,7 +322,6 @@
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
       "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "domelementtype": "^2.0.1",
@@ -350,7 +332,6 @@
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
       "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -363,7 +344,6 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
       "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
-      "dev": true,
       "license": "BSD-2-Clause",
       "funding": {
         "url": "https://github.com/fb55/entities?sponsor=1"
@@ -373,14 +353,12 @@
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
       "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
-      "dev": true,
       "license": "BSD-2-Clause"
     },
     "node_modules/domhandler": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
       "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
-      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "domelementtype": "1"
@@ -390,7 +368,6 @@
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
       "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
-      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "dom-serializer": "0",
@@ -407,7 +384,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz",
       "integrity": "sha512-0hJGub96skwr+sUojv7zQ0kc9i4jn3SwLiLk8Jr7KDz7aaaMzkN5UX3a/9ZhzC0OfZVyXHhlHcjC0KVOiKZ+HQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "onetime": "^1.0.0",
@@ -421,14 +397,12 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz",
       "integrity": "sha512-WJSCMaN/qjIkzWy5Ayu0MDENFltcu4zTPPnWqdFPOVBtsENVTN+A3d76G61yuiVALsMK+76MejdPrwmccv/wag==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/enabled": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz",
       "integrity": "sha512-nnzgVSpB35qKrUN8358SjO1bYAmxoThECTWw9s3J0x5G8A9hokKHVDFzBjVpCoSryo6MhN8woVyascN5jheaNA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "env-variable": "0.0.x"
@@ -438,21 +412,18 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
       "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
-      "dev": true,
       "license": "BSD-2-Clause"
     },
     "node_modules/env-variable": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz",
       "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/es5-ext": {
       "version": "0.10.64",
       "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
       "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
-      "dev": true,
       "hasInstallScript": true,
       "license": "ISC",
       "dependencies": {
@@ -469,7 +440,6 @@
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
       "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "d": "1",
@@ -481,7 +451,6 @@
       "version": "3.1.4",
       "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
       "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "d": "^1.0.2",
@@ -495,7 +464,6 @@
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
       "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "d": "1",
@@ -517,7 +485,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
       "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "d": "^1.0.1",
@@ -547,7 +514,6 @@
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
       "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "d": "1",
@@ -587,7 +553,6 @@
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
       "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "type": "^2.7.2"
@@ -869,7 +834,6 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-5.0.0.tgz",
       "integrity": "sha512-SNp4H4+85mm2xaHYi83FBHuOXylpi5vcwgtNoYCZBbkgeXQXoeTAKa59VODRb0woTDBvxouP91Ff5PzCkikg6g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "chalk": "^4.1.2",
@@ -884,7 +848,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/grunt-contrib-htmlmin/-/grunt-contrib-htmlmin-3.1.0.tgz",
       "integrity": "sha512-Khaa+0MUuqqNroDIe9tsjZkioZnW2Y+iTGbonBkLWaG7+SkSFExfb4jLt7M6rxKV3RSqlS7NtVvu4SVIPkmKXg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "chalk": "^2.4.2",
@@ -899,7 +862,6 @@
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
       "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-convert": "^1.9.0"
@@ -912,7 +874,6 @@
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
       "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^3.2.1",
@@ -927,7 +888,6 @@
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
       "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-name": "1.1.3"
@@ -937,14 +897,12 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
       "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/grunt-contrib-htmlmin/node_modules/has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
       "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=4"
@@ -954,7 +912,6 @@
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
       "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "has-flag": "^3.0.0"
@@ -1041,7 +998,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/grunt-minify-html/-/grunt-minify-html-3.0.0.tgz",
       "integrity": "sha512-//Paw/bXj9H+aZltKAC6fGXRbhtr5QKla/ZPOQjMiqZnI1sIrI82+3Q6fIEHhjeW4rJxmZ/Bwc1yJF1L+PySog==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "each-async": "^1.0.0",
@@ -1094,7 +1050,6 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
-      "dev": true,
       "license": "MIT",
       "bin": {
         "he": "bin/he"
@@ -1126,7 +1081,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
       "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "camel-case": "^3.0.0",
@@ -1148,7 +1102,6 @@
       "version": "4.2.4",
       "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
       "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "source-map": "~0.6.0"
@@ -1161,7 +1114,6 @@
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
       "license": "BSD-3-Clause",
       "engines": {
         "node": ">=0.10.0"
@@ -1171,7 +1123,6 @@
       "version": "3.10.1",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
       "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "domelementtype": "^1.3.1",
@@ -1245,7 +1196,6 @@
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
       "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/is-core-module": {
@@ -1314,7 +1264,6 @@
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
       "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/is-relative": {
@@ -1398,7 +1347,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz",
       "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "colornames": "^1.1.1"
@@ -1450,14 +1398,12 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
       "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lru-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
       "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "es5-ext": "~0.10.2"
@@ -1467,7 +1413,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
       "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "pify": "^3.0.0"
@@ -1480,7 +1425,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
       "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=4"
@@ -1531,7 +1475,6 @@
       "version": "0.4.17",
       "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz",
       "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "d": "^1.0.2",
@@ -1578,7 +1521,6 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz",
       "integrity": "sha512-IxR2XMbw9pXCxApkdD9BTcH2U4XlXhbeySUrv71rmMS9XDA8BVXEsIuFu24LtwCfBgfbL7Fuh8/ZzkO5DaTLlQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "argh": "^0.1.4",
@@ -1597,7 +1539,6 @@
       "version": "2.6.4",
       "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
       "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "lodash": "^4.17.14"
@@ -1607,14 +1548,12 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
       "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
-      "dev": true,
       "license": "ISC"
     },
     "node_modules/no-case": {
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
       "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "lower-case": "^1.1.1"
@@ -1690,7 +1629,6 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
       "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -1732,7 +1670,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
       "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "no-case": "^2.2.0"
@@ -1841,7 +1778,6 @@
       "version": "3.6.2",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
       "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "inherits": "^2.0.3",
@@ -1869,7 +1805,6 @@
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
       "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.10"
@@ -1911,7 +1846,6 @@
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
       "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -1939,7 +1873,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
       "integrity": "sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -1949,7 +1882,6 @@
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
       "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "is-arrayish": "^0.3.1"
@@ -1975,7 +1907,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
       "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "safe-buffer": "~5.2.0"
@@ -2010,14 +1941,12 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
       "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/timers-ext": {
       "version": "0.1.8",
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz",
       "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "es5-ext": "^0.10.64",
@@ -2044,7 +1973,6 @@
       "version": "2.7.3",
       "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
       "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
-      "dev": true,
       "license": "ISC"
     },
     "node_modules/uglify-js": {
@@ -2094,7 +2022,6 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
       "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/uri-path": {
@@ -2117,7 +2044,6 @@
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
       "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
       "deprecated": "Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.",
-      "dev": true,
       "license": "MIT",
       "bin": {
         "uuid": "bin/uuid"
diff --git a/src/app.html b/src/app.html
index e5db8fc..cd1132d 100644
--- a/src/app.html
+++ b/src/app.html
@@ -1173,6 +1173,25 @@
   </div>
   <!-- end of message panel -->
 
+  <!-- disclamier modal -->
+  <div class="modal fade" id="disclaimerModal" tabindex="-1" aria-labelledby="disclaimerModalLabel" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title" id="disclaimerModalLabel">Disclaimer</h5>
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+        </div>
+        <div class="modal-body">
+          <p>This application is still in development; therefore, errors or unexpected behavior may be experienced. We would be glad to improve any issues reported at infarctsize@pharmahungary.com</p>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-primary" data-bs-dismiss="modal">I understand</button>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- end of disclamier modal -->
+
   <!-- end of main panel -->
 
   <div id="loading" class="d-flex justify-content-center align-items-center" style="
@@ -1197,6 +1216,16 @@
     $(window).on("load", function () {
       $("#loading").fadeOut(500).addClass("d-none");
     });
+    $(document).ready(function() {
+      // Check if disclaimer has been shown before
+      if (!localStorage.getItem('disclaimerShown')) {
+          // Show the modal
+          $('#disclaimerModal').modal('show');
+          
+          // Set flag in localStorage
+          localStorage.setItem('disclaimerShown', 'true');
+      }
+  });  
   </script>
 
   <script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js"
-- 
GitLab


From 1c5f4004c48648b265fb91c73fa578547d8b8b12 Mon Sep 17 00:00:00 2001
From: Csanad Tabajdi <tabajdi.csanad@proton.me>
Date: Wed, 19 Feb 2025 22:43:48 +0100
Subject: [PATCH 2/4] Improve hash management in metadata. Enhance region
 grouping logic.

---
 src/app.html              |  2 +-
 src/js/buffer.js          |  4 +---
 src/js/file_metadata.js   | 27 ++++++++++++++++++++++++++-
 src/js/science_plugins.js | 28 +++++++++++++++++++---------
 4 files changed, 47 insertions(+), 14 deletions(-)

diff --git a/src/app.html b/src/app.html
index cd1132d..9b8073d 100644
--- a/src/app.html
+++ b/src/app.html
@@ -322,7 +322,7 @@
             <div id="panelsStayOpen-collapseTwo" class="accordion-collapse collapse"
               aria-labelledby="panelsStayOpen-headingTwo">
               <div class="accordion-body">
-                <div id="annotation_editor_container" aria-hidden="true" class="card mb-3 overflow-auto"
+                <div id="annotation_editor_container" class="card mb-3 overflow-auto"
                   style="height: 250px">
                   <div class="card-body" id="annotation_editor_panel">
                     <h5 class="card-title placeholder-glow">
diff --git a/src/js/buffer.js b/src/js/buffer.js
index 3d1658f..6041b87 100644
--- a/src/js/buffer.js
+++ b/src/js/buffer.js
@@ -123,8 +123,6 @@ class ImageBuffer {
   }
 
   postImageLoad(idx) {
-    _via_img_metadata[_via_image_id].clearGroupedRegions();
-
     let arr_idx = this.bufferIdList.indexOf(idx);
     this.bufferTimestamp[arr_idx] = Date.now();
 
@@ -193,7 +191,7 @@ class ImageBuffer {
     zoom.fixCanvasBlur()
     image.show().then(_ => this.loading.addClass("d-none"));
 
-    if (_via_img_metadata[_via_image_id].autoAnnotated === undefined) {
+    if (_via_img_metadata[_via_image_id].autoAnnotated !== undefined) {
       if (_via_img_metadata[_via_image_id].autoAnnotated) {
         plugin.updateSliceRegion()
       }
diff --git a/src/js/file_metadata.js b/src/js/file_metadata.js
index 97bba23..f57a453 100644
--- a/src/js/file_metadata.js
+++ b/src/js/file_metadata.js
@@ -20,7 +20,8 @@ class FileMetadata {
         this.groupedRegions = {
             groupBy: new Array(),
             groups: new Map(),
-            groupIDs: new Map()
+            groupIDs: new Map(),
+            hash: '',
         };
         this.autoAnnotated = false;
     }
@@ -49,6 +50,26 @@ class FileMetadata {
         this.lockedRegions.clear();
     }
 
+    computeHash() {
+        let str = JSON.stringify(this.regions);
+        let hash = 0;
+        if (str.length == 0) return hash;
+        for (let i = 0; i < str.length; i++) {
+            let char = str.charCodeAt(i);
+            hash = ((hash << 5) - hash) + char;
+            hash = hash & hash;
+        }
+        return hash
+    }
+
+    getHash() {
+        return this.groupedRegions.hash;
+    }
+
+    updateHash(hash) {
+        this.groupedRegions.hash = hash;
+    }
+
     clearRegionLock(region) {
         this.lockedRegions.delete(region);
     }
@@ -87,6 +108,10 @@ class FileMetadata {
         this.groupedRegions.groups.set(region, group);
     }
 
+    isGroupEmpty() {
+        return this.groupedRegions.groups.size === 0;
+    }
+
     getGroupedRegion(region) {
         return this.groupedRegions.groups.get(region);
     }
diff --git a/src/js/science_plugins.js b/src/js/science_plugins.js
index 02fb4dd..df34ea3 100644
--- a/src/js/science_plugins.js
+++ b/src/js/science_plugins.js
@@ -6,6 +6,7 @@ function infarctSizeAiWorker() {
     let slice_regions_id = JSON.parse(e.data.slice_regions_id);
     let regions = JSON.parse(e.data.regions);
     let update = e.data.update;
+    let image_id = e.data.image_id;
     let grouped_regions = {};
 
     // Pre-calculate centroids for all regions
@@ -59,6 +60,7 @@ function infarctSizeAiWorker() {
       grouped_regions[element] = tmp;
     }
     postMessage({
+      image_id: image_id,
       slice_regions_id: JSON.stringify(slice_regions_id),
       grouped_regions: JSON.stringify(grouped_regions),
       update: update,
@@ -162,7 +164,7 @@ function Science_plugins() {
   this.GroupingWorker.onmessage = this.groupingWorkerOnMessage.bind(this);
 }
 
-Science_plugins.prototype.updateSliceRegion = function (img_id = _via_image_id, update = true) {
+Science_plugins.prototype.updateSliceRegion = async function (img_id = _via_image_id, update = true) {
   let groupBy = [];
   for (let i = 0; i < _via_img_metadata[img_id].regions.length; ++i) {
     if (
@@ -173,29 +175,37 @@ Science_plugins.prototype.updateSliceRegion = function (img_id = _via_image_id,
     }
   }
   if (groupBy.length === 0) {
-    console.log("No slice regions found");
+    console.warn("No slice regions found");
     return false;
   }
-  if (groupBy === _via_img_metadata[img_id].groupedRegions.groupBy) {
-    console.log("No change in slice regions")
+  let hash = _via_img_metadata[img_id].computeHash(groupBy);
+  if (hash === _via_img_metadata[img_id].getHash() && !_via_img_metadata[img_id].isGroupEmpty()) {
+    console.debug("No change in regions or groups not empty");
     return false;
   }
-  _via_img_metadata[img_id].clearGroups();
+  _via_img_metadata[_via_image_id].updateHash(hash);
   this.GroupingWorker.postMessage({
+    image_id : img_id,
     slice_regions_id: JSON.stringify(groupBy),
     regions: JSON.stringify(_via_img_metadata[img_id].regions),
-    grouped_regions: JSON.stringify(_via_img_metadata[_via_image_id].groupedRegions.groups, Project.replacer),
+    grouped_regions: JSON.stringify(_via_img_metadata[img_id].groupedRegions.groups, Project.replacer),
     update: update,
   });
 };
 
 // Grouping regions on other thread
-Science_plugins.prototype.groupingWorkerOnMessage = function (e) {
+Science_plugins.prototype.groupingWorkerOnMessage = async function (e) {
   let groups = JSON.parse(e.data.grouped_regions);
+  let img_id = e.data.image_id;
+  if (Object.keys(groups).length === 0) {
+    console.debug("No groups found");
+    return;
+  }
+  _via_img_metadata[img_id].clearGroups();
   for (const element in groups) {
-    _via_img_metadata[_via_image_id].addGroupedRegion(parseInt(element), groups[element])
+    _via_img_metadata[img_id].addGroupedRegion(parseInt(element), groups[element])
   }
-  _via_img_metadata[_via_image_id].groupedRegions.groupBy = JSON.parse(e.data.slice_regions_id);
+  _via_img_metadata[img_id].groupedRegions.groupBy = JSON.parse(e.data.slice_regions_id);
   if (e.data.update) {
     sidebar.annotation_editor_update_content();
   }
-- 
GitLab


From 6f7ea2d4dd0171dff4c90a0104fee8a30588ad61 Mon Sep 17 00:00:00 2001
From: Csanad Tabajdi <tabajdi.csanad@proton.me>
Date: Sat, 8 Mar 2025 22:40:38 +0100
Subject: [PATCH 3/4] Enhance grouping logic with worker signal management and
 improve annotation editor updates

---
 src/js/science_plugins.js | 33 ++++++++++++++++++++++++++++++++-
 src/js/sidebar.js         |  6 +++++-
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/src/js/science_plugins.js b/src/js/science_plugins.js
index df34ea3..8e195dc 100644
--- a/src/js/science_plugins.js
+++ b/src/js/science_plugins.js
@@ -161,11 +161,13 @@ function Science_plugins() {
       })
     )
   );
+  this.workerSignal = new Set();
   this.GroupingWorker.onmessage = this.groupingWorkerOnMessage.bind(this);
 }
 
 Science_plugins.prototype.updateSliceRegion = async function (img_id = _via_image_id, update = true) {
   let groupBy = [];
+  this.workerSignal.add(img_id);
   for (let i = 0; i < _via_img_metadata[img_id].regions.length; ++i) {
     if (
       _via_img_metadata[img_id].regions[i].region_attributes.Type ===
@@ -175,11 +177,13 @@ Science_plugins.prototype.updateSliceRegion = async function (img_id = _via_imag
     }
   }
   if (groupBy.length === 0) {
+    this.workerSignal.delete(img_id);
     console.warn("No slice regions found");
     return false;
   }
-  let hash = _via_img_metadata[img_id].computeHash(groupBy);
+  let hash = _via_img_metadata[img_id].computeHash();
   if (hash === _via_img_metadata[img_id].getHash() && !_via_img_metadata[img_id].isGroupEmpty()) {
+    this.workerSignal.delete(img_id);
     console.debug("No change in regions or groups not empty");
     return false;
   }
@@ -209,6 +213,33 @@ Science_plugins.prototype.groupingWorkerOnMessage = async function (e) {
   if (e.data.update) {
     sidebar.annotation_editor_update_content();
   }
+  this.workerSignal.delete(img_id);
+};
+
+Science_plugins.prototype.getGroupingProgress = function (imageId, timeout = 5000) {
+  return new Promise((resolve) => {
+    const startTime = Date.now();
+    
+    const checkProgress = () => {
+       // If no longer processing, resolve success
+       if (!this.workerSignal.has(imageId)) {
+        resolve(true);
+        return;
+      }
+      
+      // If timeout reached, resolve false
+      if (Date.now() - startTime > timeout) {
+        resolve(false);
+        return;
+      }
+      
+      // Check again after small delay
+      setTimeout(checkProgress, 100);
+    };
+
+    // Start checking
+    checkProgress();
+  });
 };
 
 // Grouping modify name to group
diff --git a/src/js/sidebar.js b/src/js/sidebar.js
index 1a68933..baf0a18 100644
--- a/src/js/sidebar.js
+++ b/src/js/sidebar.js
@@ -692,9 +692,11 @@ class Sidebar {
   annotation_editor_update_content() {
     try {
       if (this.ae !== undefined) {
+        this.ae.addClass("d-none");
         this.ae.empty();
         this.annotation_editor_update_header_html();
         this.annotation_editor_update_metadata_html();
+        this.ae.removeClass("d-none"); // show the annotation editor
         drawing.updateCheckedLockHtml();
       }
     } catch (err) {
@@ -880,7 +882,7 @@ class Sidebar {
     }
   }
 
-  annotation_editor_update_metadata_html() {
+  async annotation_editor_update_metadata_html() {
     if (!_via_img_count) {
       return;
     }
@@ -905,6 +907,8 @@ class Sidebar {
               );
             } else {
               if (_via_img_metadata[_via_image_id].isGroupable()) {
+                plugin.updateSliceRegion(_via_image_id);
+                await plugin.getGroupingProgress(_via_image_id);
                 let tmp = [];
                 let colspan = 4;
                 let done_ids = [];
-- 
GitLab


From 2a99f8f683f6b096078624cbb9561d1a444051f6 Mon Sep 17 00:00:00 2001
From: Csanad Tabajdi <tabajdi.csanad@proton.me>
Date: Sun, 9 Mar 2025 12:13:40 +0100
Subject: [PATCH 4/4] Refactor grouping logic and improve area export
 functionality in science plugins

---
 src/js/science_plugins.js | 221 ++++++++++++++++------------------
 src/js/sidebar.js         | 245 +++++++++++++++++++++-----------------
 2 files changed, 239 insertions(+), 227 deletions(-)

diff --git a/src/js/science_plugins.js b/src/js/science_plugins.js
index 8e195dc..53697fd 100644
--- a/src/js/science_plugins.js
+++ b/src/js/science_plugins.js
@@ -189,7 +189,7 @@ Science_plugins.prototype.updateSliceRegion = async function (img_id = _via_imag
   }
   _via_img_metadata[_via_image_id].updateHash(hash);
   this.GroupingWorker.postMessage({
-    image_id : img_id,
+    image_id: img_id,
     slice_regions_id: JSON.stringify(groupBy),
     regions: JSON.stringify(_via_img_metadata[img_id].regions),
     grouped_regions: JSON.stringify(_via_img_metadata[img_id].groupedRegions.groups, Project.replacer),
@@ -219,20 +219,20 @@ Science_plugins.prototype.groupingWorkerOnMessage = async function (e) {
 Science_plugins.prototype.getGroupingProgress = function (imageId, timeout = 5000) {
   return new Promise((resolve) => {
     const startTime = Date.now();
-    
+
     const checkProgress = () => {
-       // If no longer processing, resolve success
-       if (!this.workerSignal.has(imageId)) {
+      // If no longer processing, resolve success
+      if (!this.workerSignal.has(imageId)) {
         resolve(true);
         return;
       }
-      
+
       // If timeout reached, resolve false
       if (Date.now() - startTime > timeout) {
         resolve(false);
         return;
       }
-      
+
       // Check again after small delay
       setTimeout(checkProgress, 100);
     };
@@ -335,7 +335,7 @@ Science_plugins.prototype.calcPolygonArea = function (xCoordinates, yCoordinates
 
 // Export the region sizes to a csv file
 Science_plugins.prototype.ExportArea = async function () {
-  let rows = [
+  const rows = [
     ["Filename", "File_ID", "Treatment_ID", "Slice_ID", "Slice_area", "Infract_area", "Risk_area", "Slice_score", "Infract_score", "Risk_score"]
   ];
   let done = 0;
@@ -344,130 +344,119 @@ Science_plugins.prototype.ExportArea = async function () {
     if (_via_img_metadata[img_id].regions.length === 0) {
       continue;
     }
-    if (!_via_img_metadata[img_id].groupedRegions.groups.size > 0) {
-      if (!this.updateSliceRegion(img_id, false)) {
-        console.log("Slice regions updated but not grouped yet. Skipping image: " + img_id);
-        continue;
-      }
+    this.updateSliceRegion(_via_image_id, false);
+    await this.getGroupingProgress(_via_image_id);
+
+    let slices = [];
+    if (_via_img_metadata[img_id].groupedRegions.groups === 0) {
+      message.showError("No groups found! Ensure that there are slice regions in the image: " + _via_image_filename_list[img_index]);
+      return;
     }
-    await new Promise((resolve) => {
-      let interval = setInterval(() => {
-        if (_via_img_metadata[img_id].groupedRegions.groups.size > 0) {
-          clearInterval(interval);
-          resolve();
-        }
-      }, 100);
-    }).then(() => {
-      let slices = [];
-      if (_via_img_metadata[img_id].groupedRegions.groups === 0) {
-        return;
+    for (let i = 0; i < _via_img_metadata[img_id].groupedRegions.groupBy.length; i++) {
+      let element = _via_img_metadata[img_id].groupedRegions.groupBy[i];
+      let slice = {
+        ID: i + 1,
+        Area: 0,
+        InfarctArea: 0,
+        RiskArea: 0,
+        SliceScore: "n/a",
+        InfarctScore: "n/a",
+        RiskScore: "n/a",
+      };
+      let scores = {
+        score_exists: false,
+        Slice: 0,
+        Slice_no: 0,
+        Infarct: 0,
+        Infarct_no: 0,
+        Risk: 0,
+        Risk_no: 0,
+      };
+      if (_via_img_metadata[img_id].groupedRegions.groupIDs.has(element)) {
+        slice.ID = _via_img_metadata[img_id].groupedRegions.groupIDs.get(element);
       }
-      for (let i = 0; i < _via_img_metadata[img_id].groupedRegions.groupBy.length; i++) {
-        let element = _via_img_metadata[img_id].groupedRegions.groupBy[i];
-        let slice = {
-          ID: i + 1,
-          Area: 0,
-          InfarctArea: 0,
-          RiskArea: 0,
-          SliceScore: "n/a",
-          InfarctScore: "n/a",
-          RiskScore: "n/a",
-        };
-        let scores = {
-          score_exists: false,
-          Slice: 0,
-          Slice_no: 0,
-          Infarct: 0,
-          Infarct_no: 0,
-          Risk: 0,
-          Risk_no: 0,
-        };
-        if (_via_img_metadata[img_id].groupedRegions.groupIDs.has(element)) {
-          slice.ID = _via_img_metadata[img_id].groupedRegions.groupIDs.get(element);
-        }
-        slice.Area = this.calcPolygonArea(_via_img_metadata[img_id].regions[element].shape_attributes.all_points_x, _via_img_metadata[img_id].regions[element].shape_attributes.all_points_y);
-        if (_via_img_metadata[img_id].regions[element].hasOwnProperty("score")) {
+      slice.Area = this.calcPolygonArea(_via_img_metadata[img_id].regions[element].shape_attributes.all_points_x, _via_img_metadata[img_id].regions[element].shape_attributes.all_points_y);
+      if (_via_img_metadata[img_id].regions[element].hasOwnProperty("score")) {
+        scores.score_exists = true;
+        scores.Slice += _via_img_metadata[img_id].regions[element].score;
+        scores.Slice_no++;
+      }
+      for (let i = 0; i < _via_img_metadata[img_id].getGroupedRegion(element).length; i++) {
+        let region = _via_img_metadata[img_id].regions[_via_img_metadata[img_id].getGroupedRegion(element)[i]];
+        let Area = this.calcPolygonArea(region.shape_attributes.all_points_x, region.shape_attributes.all_points_y);
+        if (region.hasOwnProperty("score")) {
           scores.score_exists = true;
-          scores.Slice += _via_img_metadata[img_id].regions[element].score;
-          scores.Slice_no++;
-        }
-        for (let i = 0; i < _via_img_metadata[img_id].getGroupedRegion(element).length; i++) {
-          let region = _via_img_metadata[img_id].regions[_via_img_metadata[img_id].getGroupedRegion(element)[i]];
-          let Area = this.calcPolygonArea(region.shape_attributes.all_points_x, region.shape_attributes.all_points_y);
-          if (region.hasOwnProperty("score")) {
-            scores.score_exists = true;
-            switch (region.region_attributes.Type) {
-              case "Slice":
-                if (region.score === "n/a") {
-                  break;
-                }
-                scores.Slice += region.score;
-                scores.Slice_no++;
-                break;
-              case "Risk":
-                if (region.score === "n/a") {
-                  break;
-                }
-                scores.Risk += region.score;
-                scores.Risk_no++;
-                break;
-              case "Infarct":
-                if (region.score === "n/a") {
-                  break;
-                }
-                scores.Infarct += region.score;
-                scores.Infarct_no++;
-                break;
-            }
-          }
           switch (region.region_attributes.Type) {
             case "Slice":
-              slice.Area -= Area;
+              if (region.score === "n/a") {
+                break;
+              }
+              scores.Slice += region.score;
+              scores.Slice_no++;
               break;
             case "Risk":
-              slice.RiskArea += Area;
+              if (region.score === "n/a") {
+                break;
+              }
+              scores.Risk += region.score;
+              scores.Risk_no++;
               break;
             case "Infarct":
-              slice.InfarctArea += Area;
+              if (region.score === "n/a") {
+                break;
+              }
+              scores.Infarct += region.score;
+              scores.Infarct_no++;
               break;
           }
         }
-        if (scores.score_exists) {
-          if (scores.Slice_no > 0 && scores.Slice > 0) {
-            slice.SliceScore = scores.Slice / scores.Slice_no;
-          } else {
-            slice.SliceScore = "n/a";
-          }
-          if (scores.Infarct_no > 0 && scores.Infarct > 0) {
-            slice.InfarctScore = scores.Infarct / scores.Infarct_no;
-          } else {
-            slice.InfarctScore = "n/a";
-          }
-          if (scores.Risk_no > 0 && scores.Risk > 0) {
-            slice.RiskScore = scores.Risk / scores.Risk_no;
-          } else {
-            slice.RiskScore = "n/a";
-          }
+        switch (region.region_attributes.Type) {
+          case "Slice":
+            slice.Area -= Area;
+            break;
+          case "Risk":
+            slice.RiskArea += Area;
+            break;
+          case "Infarct":
+            slice.InfarctArea += Area;
+            break;
+        }
+      }
+      if (scores.score_exists) {
+        if (scores.Slice_no > 0 && scores.Slice > 0) {
+          slice.SliceScore = scores.Slice / scores.Slice_no;
+        } else {
+          slice.SliceScore = "n/a";
+        }
+        if (scores.Infarct_no > 0 && scores.Infarct > 0) {
+          slice.InfarctScore = scores.Infarct / scores.Infarct_no;
+        } else {
+          slice.InfarctScore = "n/a";
+        }
+        if (scores.Risk_no > 0 && scores.Risk > 0) {
+          slice.RiskScore = scores.Risk / scores.Risk_no;
+        } else {
+          slice.RiskScore = "n/a";
         }
-        slices.push(slice);
       }
-      let img_file = _via_image_filename_list[img_index];
-      let file_id = _via_img_metadata[img_id].file_attributes.ID || "";
-      slices.forEach((slice, i) => {
-        rows.push([
-          img_file,
-          file_id,
-          slice.ID,
-          slice.Area,
-          slice.InfarctArea,
-          slice.RiskArea,
-          slice.SliceScore,
-          slice.InfarctScore,
-          slice.RiskScore
-        ]);
-      });
-      done++;
+      slices.push(slice);
+    }
+    let img_file = _via_image_filename_list[img_index];
+    let file_id = _via_img_metadata[img_id].file_attributes.ID || "";
+    slices.forEach((slice, i) => {
+      rows.push([
+        img_file,
+        file_id,
+        slice.ID,
+        slice.Area,
+        slice.InfarctArea,
+        slice.RiskArea,
+        slice.SliceScore,
+        slice.InfarctScore,
+        slice.RiskScore
+      ]);
     });
+    done++;
 
   }
   let csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n");
diff --git a/src/js/sidebar.js b/src/js/sidebar.js
index baf0a18..7ee6d0d 100644
--- a/src/js/sidebar.js
+++ b/src/js/sidebar.js
@@ -696,7 +696,7 @@ class Sidebar {
         this.ae.empty();
         this.annotation_editor_update_header_html();
         this.annotation_editor_update_metadata_html();
-        this.ae.removeClass("d-none"); // show the annotation editor
+        this.ae.removeClass("d-none");
         drawing.updateCheckedLockHtml();
       }
     } catch (err) {
@@ -882,117 +882,145 @@ class Sidebar {
     }
   }
 
-  async annotation_editor_update_metadata_html() {
-    if (!_via_img_count) {
-      return;
+  ensureTableBodyExists() {
+    if (!this.ae.find("tbody").length) {
+      const tbody = $('<tbody id="annotation_editor_content"></tbody>');
+      this.ae.append(tbody);
+      this.aec = tbody;
     }
+  }
 
-    if (!this.ae.has("tbody").length) {
-      this.ae.append('<tbody id="annotation_editor_content"></tbody>');
-      this.aec = $("#annotation_editor_content");
-    }
+  createGroupRow(groupIndex, groupId) {
+    const rowId = `ae_row_${groupId}`;
+    const row = $("<tr>", {
+      id: rowId,
+      onclick: "plugin.selectAllRegionsInGroup(this)",
+      style: "height: 40px;",
+      class: "align-bottom",
+    });
+    
+    const header = $("<th>", {
+      scope: "row",
+      id: `ae_${groupId}_rid`,
+      html: "O",
+    });
+    
+    const placeholder = _via_img_metadata[_via_image_id].groupedRegions.groupIDs.has(groupId) 
+      ? _via_img_metadata[_via_image_id].groupedRegions.groupIDs.get(groupId)
+      : `Group: ${groupIndex + 1}`;
+    
+    const cell = $("<td>", {
+      id: `ae_row_${groupId}_group`,
+      colspan: 4,
+    });
+    
+    const input = $("<input>", {
+      class: 'form-control form-control-sm',
+      onchange: 'plugin.changeGroupIdentifier(this)',
+      type: 'text',
+      placeholder: placeholder,
+      'aria-label': 'group identifier',
+      id: `group_${groupId}`
+    });
+    
+    cell.append(input);
+    row.append(header, cell);
+    
+    return row;
+  }
+  
 
-    switch (_via_metadata_being_updated) {
-      case "region":
-        let rindex;
-        if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID) {
-          this.aec.append(this.annotation_editor_get_metadata_row_html(0));
+  async updateRegionMetadata(){
+    let rindex;
+    if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID) {
+      this.aec.append(this.annotation_editor_get_metadata_row_html(0));
+    } else {
+      if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE) {
+        if (_via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION) {
+          this.aec.append(
+            this.annotation_editor_get_metadata_row_html(
+              drawing.userSelRegionId()
+            )
+          );
         } else {
-          if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE) {
-            if (_via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION) {
-              this.aec.append(
-                this.annotation_editor_get_metadata_row_html(
-                  drawing.userSelRegionId()
-                )
+          if (_via_img_metadata[_via_image_id].isGroupable()) {
+            plugin.updateSliceRegion(_via_image_id, false);
+            await plugin.getGroupingProgress(_via_image_id);
+            let tmp = [];
+            let colspan = 4;
+            let done_ids = [];
+            const fragment = document.createDocumentFragment();
+            for (rindex = 0; rindex < _via_img_metadata[_via_image_id].groupedRegions.groupBy.length; ++rindex) {
+              if (!_via_img_metadata[_via_image_id].isGroupedKey(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])) {
+                continue;
+              }
+              this.createGroupRow(rindex, _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]).appendTo(fragment);
+              $(fragment).append(
+                this.annotation_editor_get_metadata_row_html(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])
               );
-            } else {
-              if (_via_img_metadata[_via_image_id].isGroupable()) {
-                plugin.updateSliceRegion(_via_image_id);
-                await plugin.getGroupingProgress(_via_image_id);
-                let tmp = [];
-                let colspan = 4;
-                let done_ids = [];
-                for (rindex = 0; rindex < _via_img_metadata[_via_image_id].groupedRegions.groupBy.length; ++rindex) {
-                  if (!_via_img_metadata[_via_image_id].isGroupedKey(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])) {
-                    continue;
-                  }
-                  $("<tr>", {
-                    id: "ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex],
-                    onclick: "plugin.selectAllRegionsInGroup(this)",
-                    style: "height: 40px;",
-                    class: "align-bottom",
-                  }).appendTo(this.aec);
-                  $("<th>", {
-                    scope: "row",
-                    id: "ae_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "_rid",
-                    html: "O",
-                  }).appendTo("#ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
-                  if (_via_img_metadata[_via_image_id].groupedRegions.groupIDs.has(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])) {
-                    $("<td> ", {
-                      id: "ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "_group",
-                      colspan: colspan,
-                      html: "<input class='form-control form-control-sm' onchange='plugin.changeGroupIdentifier(this)' type='text' placeholder='" + _via_img_metadata[_via_image_id].groupedRegions.groupIDs.get(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]) + "' aria-label='group identifier' id='group_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "'>"
-                    }).appendTo("#ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
-                  } else {
-                    $("<td> ", {
-                      id: "ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "_group",
-                      colspan: colspan,
-                      html: "<input class='form-control form-control-sm' onchange='plugin.changeGroupIdentifier(this)' type='text' placeholder='" + "Group: " + (rindex + 1) + "' aria-label='group identifier' id='group_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "'>"
-                    }).appendTo("#ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
-                  }
-
-                  this.aec.append(
-                    this.annotation_editor_get_metadata_row_html(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])
-                  );
-                  done_ids.push(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
-                  tmp = _via_img_metadata[_via_image_id].getGroupedRegion(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
-                  for (const element of tmp) {
-                    this.aec.append(
-                      this.annotation_editor_get_metadata_row_html(element)
-                    );
-                    done_ids.push(element);
-                  }
-                }
-                $("<tr>", {
-                  id: "ae_no_group",
-                  style: "height: 40px;",
-                  class: "align-bottom",
-                }).appendTo(this.aec);
-                $("<th>", {
-                  scope: "row",
-                  id: "ae_no_group_rid",
-                  html: "X",
-                }).appendTo("#ae_no_group");
-                $("<td> ", {
-                  id: "ae_row_no_group",
-                  colspan: colspan,
-                  html: "Other",
-                }).appendTo("#ae_no_group");
-                for (
-                  rindex = 0;
-                  rindex < _via_img_metadata[_via_image_id].regions.length;
-                  ++rindex
-                ) {
-                  if (!done_ids.includes(rindex)) {
-                    this.aec.append(
-                      this.annotation_editor_get_metadata_row_html(rindex)
-                    );
-                  }
-                }
-              } else {
-                for (
-                  rindex = 0;
-                  rindex < _via_img_metadata[_via_image_id].regions.length;
-                  ++rindex
-                ) {
-                  this.aec.append(
-                    this.annotation_editor_get_metadata_row_html(rindex)
-                  );
-                }
+              done_ids.push(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
+              let tmp = _via_img_metadata[_via_image_id].getGroupedRegion(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]);
+              for (const element of tmp) {
+                $(fragment).append(
+                  this.annotation_editor_get_metadata_row_html(element)
+                );
+                done_ids.push(element);
               }
             }
+            this.aec.append(fragment);
+            const row = $("<tr>", {
+              id: "ae_no_group",
+              style: "height: 40px;",
+              class: "align-bottom",
+            });
+            const header = $("<th>", {
+              scope: "row",
+              id: "ae_no_group_rid",
+              html: "X",
+            })
+            const cell = $("<td> ", {
+              id: "ae_row_no_group",
+              colspan: colspan,
+              html: "Other",
+            });
+            row.append(header, cell);
+            $(fragment).append(row);
+            for (rindex = 0; rindex < _via_img_metadata[_via_image_id].regions.length; ++rindex) {
+              if (!done_ids.includes(rindex)) {
+                $(fragment).append(
+                  this.annotation_editor_get_metadata_row_html(rindex)
+                );
+              }
+            }
+            this.aec.append(fragment);
+          } else {
+            const fragment = document.createDocumentFragment();
+            for (
+              rindex = 0;
+              rindex < _via_img_metadata[_via_image_id].regions.length;
+              ++rindex
+            ) {
+              $(fragment).append(
+                this.annotation_editor_get_metadata_row_html(rindex)
+              );
+            }
+            this.aec.append(fragment);
           }
         }
+      }
+    }
+  }
+
+  async annotation_editor_update_metadata_html() {
+    if (!_via_img_count) {
+      return;
+    }
+
+    this.ensureTableBodyExists(); // experiment
+
+    switch (_via_metadata_being_updated) {
+      case "region":
+        await this.updateRegionMetadata();
         break;
 
       case "file":
@@ -1002,10 +1030,8 @@ class Sidebar {
   }
 
   annotation_editor_update_row(row_id) {
-    if (!this.ae.has("tbody").length) {
-      this.ae.append('<tbody id="annotation_editor_content"></tbody>');
-      this.aec = $("#annotation_editor_content");
-    }
+
+    this.ensureTableBodyExists(); // experiment
     let new_row = this.annotation_editor_get_metadata_row_html(row_id);
     let id = new_row.attr("id");
 
@@ -1016,10 +1042,7 @@ class Sidebar {
 
   annotation_editor_add_row(row_id) {
     if (this.is_annotation_editor_visible()) {
-      if (!this.ae.has("tbody").length) {
-        this.ae.append('<tbody id="annotation_editor_content"></tbody>');
-        this.aec = $("#annotation_editor_content");
-      }
+      this.ensureTableBodyExists(); // experiment
       let new_row = this.annotation_editor_get_metadata_row_html(row_id);
       let penultimate_row_id = parseInt(row_id) - 1;
       if (penultimate_row_id >= 0) {
-- 
GitLab