diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c8e7f49a63e23af60fb4befcd5e0b3c783d295f
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,13 @@
+{
+	"name": "InfarctSize-AI Development Container",
+	"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
+	"features": {
+		"ghcr.io/deep-space-cartel/devcontainers-features/apt:1": {
+			"packages": "ffmpeg libsm6 libev-dev"
+		},
+		"ghcr.io/devcontainers/features/node:1": {
+			"nodeGypDependencies": false
+		}
+	},
+	"postCreateCommand": "pip install --break-system-packages -r ${containerWorkspaceFolder}/requirements.txt"
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ec81f1ba68239bef056228662d0b46ee3f65bf0a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,167 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Custom
+models/*.pt
+.vscode
+upload
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77a62d6f42c0fb350302178735f877822497b4b1
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,16 @@
+image: python:3.12
+
+stages:
+  - test-all
+  
+
+before_script:
+  - apt-get update
+  - apt-get install -y ffmpeg libsm6 libev-dev
+  - pip install --break-system-packages -r requirements.txt
+
+test-all:
+  stage: test-all
+  script:
+    - make test-train || exit 1
+    - make test-app || exit 1
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..cbc8d129891fed3276e8b0a4d5c40b10a4020f25
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "infarctsize-ai-frontend"]
+	path = infarctsize-ai-frontend
+	url = git@dev.itk.ppke.hu:infarctsize-ai/infarctsize-ai-frontend.git
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a6822d68d961250b184a8624532cde33862313fa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+build-frontend:
+	@echo "Building frontend..."
+	@cd infarctsize-ai-frontend && make init && make build
+	@echo "Frontend built successfully."
+	@echo "Copying frontend build to backend..."
+	@cp -r infarctsize-ai-frontend/dist/assets static/
+	@cp -r infarctsize-ai-frontend/dist/*.html templates/
+	@echo "Frontend build copied to backend successfully."
+
+test-train:
+	@echo "Running all tests..."
+	@python train_test.py
+
+test-app:
+	@echo "Running all tests..."
+	@python app_test.py
+
+run:
+	@echo "Running flask server..."
+	@python app.py || python3 app.py || echo "Failed to run flask server."
+
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..907fbf73745641e7506a4cbcf0eac0b02127706d
--- /dev/null
+++ b/app.py
@@ -0,0 +1,69 @@
+import os
+import uuid
+
+from flask import Flask, flash, redirect, render_template, request, url_for
+from flask_cors import CORS
+from werkzeug.utils import secure_filename
+
+from processing.process import process_all_files
+from utils.constants import UPLOAD_FOLDER, debug, port
+
+# Where the HTML templates are stored (yet frontend)
+template_dir = os.path.abspath('templates')
+# Initialize the Flask app
+app = Flask("InfarctSize", template_folder=template_dir,
+            static_url_path='', static_folder='static')
+app.secret_key = uuid.uuid4().hex
+
+# Allow CORS for the app (Cross-Origin Resource Sharing)
+CORS(app)
+
+# Load the web app
+
+
+@app.route('/')
+def home():
+    return render_template('app.html')
+
+# Test if the server is running
+
+
+@app.route('/test')
+def running():
+    return "Server is running"
+
+# Upload the images
+
+
+@app.route('/upload', methods=['POST', 'GET'])
+def upload():
+    if request.method == 'GET':
+        return redirect(url_for('home'))
+    if request.method == 'POST':
+        if 'image' not in request.files:
+            flash('No file part')
+            return redirect(request.url, code=400)
+
+        files = request.files.getlist('image')
+        for file in files:
+            if file.filename == '':
+                flash('No selected file')
+                return redirect(request.url, code=400)
+            filename = secure_filename(file.filename)
+            file.save(os.path.join(UPLOAD_FOLDER, filename))
+            print("File saved")
+
+        return redirect(url_for('segment'))
+
+# Process the uploaded images
+
+
+@app.route('/segment', methods=['POST', 'GET'])
+def segment():
+    results = ''
+    results = process_all_files()
+    return results
+
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', debug=debug, port=port)
diff --git a/app_test.py b/app_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..604d6676fc5b772478134679fdbbef0d0e514124
--- /dev/null
+++ b/app_test.py
@@ -0,0 +1,105 @@
+import io
+import os
+import unittest
+
+import cv2
+import numpy as np
+
+from app import app
+from processing.ainnotate import ainnotate
+from utils.constants import UPLOAD_FOLDER
+
+# test_app.py
+
+
+class FlaskAppTests(unittest.TestCase):
+
+    def setUp(self):
+        app.config['TESTING'] = True
+        app.config['UPLOAD_FOLDER'] = 'test_uploads'
+        self.client = app.test_client()
+        os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
+
+    def tearDown(self):
+        for file in os.listdir(app.config['UPLOAD_FOLDER']):
+            file_path = os.path.join(app.config['UPLOAD_FOLDER'], file)
+            os.unlink(file_path)
+        os.rmdir(app.config['UPLOAD_FOLDER'])
+
+    def test_home(self):
+        response = self.client.get('/')
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b'<!DOCTYPE html>', response.data)
+
+    def test_running(self):
+        response = self.client.get('/test')
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.data, b'Server is running')
+
+    def test_upload_no_file_part(self):
+        response = self.client.post('/upload', data={})
+        self.assertEqual(response.status_code, 400)
+        self.assertIn(b'Redirect', response.data)
+
+    def test_upload_no_selected_file(self):
+        data = {'image': (io.BytesIO(b''), '')}
+        response = self.client.post(
+            '/upload', data=data, content_type='multipart/form-data')
+        self.assertEqual(response.status_code, 400)
+        self.assertIn(b'Redirect', response.data)
+
+    def test_upload_successful(self):
+        data = {'image': (io.BytesIO(b'test image content'), 'test_image.png')}
+        response = self.client.post(
+            '/upload', data=data, content_type='multipart/form-data')
+        self.assertEqual(response.status_code, 302)
+        self.assertIn(b'/segment', response.data)
+
+    def test_segment(self):
+        response = self.client.get('/segment')
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.data.decode('utf-8'), '{}\n')
+
+class TestAinnotate(unittest.TestCase):
+
+    def setUp(self):
+        os.makedirs(UPLOAD_FOLDER, exist_ok=True)
+        self.test_image_path = os.path.join(UPLOAD_FOLDER, 'test_image.png')
+        # Create a dummy image for testing
+        dummy_image = np.ones((100, 100, 3), dtype=np.uint8) * 255
+        cv2.imwrite(self.test_image_path, dummy_image)
+
+    def tearDown(self):
+        if os.path.exists(self.test_image_path):
+            os.remove(self.test_image_path)
+        if os.path.exists(UPLOAD_FOLDER):
+            os.rmdir(UPLOAD_FOLDER)
+
+    def test_ainnotate_valid_image(self):
+        file, result = ainnotate('test_image.png')
+        self.assertEqual(file, 'test_image.png')
+        self.assertIsNotNone(result)
+        self.assertIn('rois', result)
+        self.assertIn('masks', result)
+        self.assertIn('class_ids', result)
+        self.assertIn('slice_scores', result)
+        self.assertIn('infarct_scores', result)
+        self.assertIn('risk_scores', result)
+
+    def test_ainnotate_non_existent_file(self):
+        file, result = ainnotate('non_existent.png')
+        self.assertEqual(file, 'non_existent.png')
+        self.assertIsNone(result)
+
+    def test_ainnotate_invalid_image(self):
+        invalid_image_path = os.path.join(UPLOAD_FOLDER, 'invalid_image.txt')
+        with open(invalid_image_path, 'w') as f:
+            f.write('This is not an image file.')
+        file, result = ainnotate('invalid_image.txt')
+        self.assertEqual(file, 'invalid_image.txt')
+        self.assertIsNone(result)
+        os.remove(invalid_image_path)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/models/load_models.py b/models/load_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0c601799c641ef753820f587179707a0280223a
--- /dev/null
+++ b/models/load_models.py
@@ -0,0 +1,24 @@
+import segmentation_models_pytorch as smp
+from torch import device, load
+from torch.cuda import is_available as cuda_is_available
+
+from models.unet_impl import UNet2
+from utils.constants import (model_smp_deeplab, model_smp_unet,
+                             model_unet_impl, num_classes)
+
+device = device("cuda:0" if cuda_is_available() else "cpu")
+
+# Load the models
+net1 = UNet2(3, num_classes).to(device)
+net1.load_state_dict(load(model_unet_impl, device))
+net2 = smp.UnetPlusPlus(encoder_name='resnet34',
+                        encoder_weights='imagenet', classes=num_classes).to(device)
+net2.load_state_dict(load(model_smp_unet, device))
+net3 = smp.DeepLabV3Plus(encoder_name='resnet34',
+                         encoder_weights='imagenet', classes=num_classes).to(device)
+net3.load_state_dict(load(model_smp_deeplab, device))
+
+# Set the models to evaluation mode
+net1.eval()
+net2.eval()
+net3.eval()
diff --git a/models/unet_impl.py b/models/unet_impl.py
new file mode 100644
index 0000000000000000000000000000000000000000..c73f2e80d6d28ad880363055044f4d570db8240a
--- /dev/null
+++ b/models/unet_impl.py
@@ -0,0 +1,105 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+
+# Building blocks of U-Net
+class DoubleConv2(nn.Module):
+    def __init__(self, in_channels, out_channels):
+        super().__init__()
+        self.double_conv = nn.Sequential(
+            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
+            nn.BatchNorm2d(out_channels),
+            nn.LeakyReLU(inplace=True),
+            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
+            nn.BatchNorm2d(out_channels),
+            nn.LeakyReLU(inplace=True)
+        )
+
+    def forward(self, x):
+        return self.double_conv(x)
+
+
+# Blocks for down-sampling
+class Down2(nn.Module):
+    def __init__(self, in_channels, out_channels, down_with_conv2d=False):
+        super().__init__()
+        self.maxpool_conv = nn.Sequential(
+            DoubleConv2(in_channels, out_channels),
+            nn.MaxPool2d(2)
+        )
+
+    def forward(self, x):
+        return self.maxpool_conv(x)
+
+# Blocks for up-sampling
+
+
+class Up2(nn.Module):
+    def __init__(self, in_channels, out_channels, bilinear=False):
+        super().__init__()
+
+        if bilinear:
+            self.up = nn.Upsample(
+                scale_factor=2, mode='bilinear', align_corners=True)
+        else:
+            self.up = nn.ConvTranspose2d(
+                in_channels, in_channels // 2, kernel_size=2, stride=2)
+
+        self.conv = DoubleConv2(in_channels, out_channels)
+
+    def forward(self, x1, x2):
+        x1 = self.up(x1)
+        diffY = torch.tensor([x2.size()[2] - x1.size()[2]])
+        diffX = torch.tensor([x2.size()[3] - x1.size()[3]])
+
+        x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
+                        diffY // 2, diffY - diffY // 2])
+        x = torch.cat([x2, x1], dim=1)
+        return self.conv(x)
+
+# Output layer
+
+
+class OutConv2(nn.Module):
+    def __init__(self, in_channels, out_channels):
+        super(OutConv2, self).__init__()
+        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
+
+    def forward(self, x):
+        return self.conv(x)
+
+
+# Channel numbers
+ChN = [64, 128, 256, 512, 1024]
+
+
+# Creating U-Net
+class UNet2(nn.Module):
+    def __init__(self, n_channels, n_classes, bilinear=False):
+        super(UNet2, self).__init__()
+        self.n_channels = n_channels
+        self.n_classes = n_classes
+        self.inc = DoubleConv2(n_channels, ChN[0])
+        self.down1 = Down2(ChN[0], ChN[1])
+        self.down2 = Down2(ChN[1], ChN[2])
+        self.down3 = Down2(ChN[2], ChN[3])
+        self.down4 = Down2(ChN[3], ChN[4])
+        self.up1 = Up2(ChN[4], ChN[3], bilinear)
+        self.up2 = Up2(ChN[3], ChN[2], bilinear)
+        self.up3 = Up2(ChN[2], ChN[1], bilinear)
+        self.up4 = Up2(ChN[1], ChN[0], bilinear)
+        self.outc = OutConv2(ChN[0], n_classes)
+
+    def forward(self, x):
+        x1 = self.inc(x)
+        x2 = self.down1(x1)
+        x3 = self.down2(x2)
+        x4 = self.down3(x3)
+        x5 = self.down4(x4)
+        x = self.up1(x5, x4)
+        x = self.up2(x, x3)
+        x = self.up3(x, x2)
+        x = self.up4(x, x1)
+        logits = self.outc(x)
+        return logits
diff --git a/processing/ainnotate.py b/processing/ainnotate.py
new file mode 100644
index 0000000000000000000000000000000000000000..a693c05ef5cca6e393a58871f911190172f1182b
--- /dev/null
+++ b/processing/ainnotate.py
@@ -0,0 +1,125 @@
+import os
+
+import cv2
+import numpy as np
+from rembg import remove
+from torch import float32, from_numpy, no_grad, sigmoid
+
+from models.load_models import device, net1, net2, net3
+from utils.constants import UPLOAD_FOLDER
+from utils.helpers import (calculate_score, cv2_RGBA2RGB, delete_buff_image,
+                           reshape_and_threshold)
+
+
+# Method to ainnotate the image
+def ainnotate(file, thresh=50.0):
+    try:
+        print(f"Processing file: {file}")
+        img = cv2.imread(os.path.join(UPLOAD_FOLDER, file))
+        if img is None:
+            print(f"Error while reading file: {file}")
+            return file, None
+
+        img = remove(img)
+        filtered = np.ones((img.shape[0], img.shape[1]), dtype=np.uint8) * 255
+        filtered[img[:, :, 0] < 10] = 0
+        num_labels, labels_im = cv2.connectedComponents(filtered)
+
+        result = {
+            'rois': [],
+            'masks': [],
+            'class_ids': [],
+            'slice_scores': [],
+            'infarct_scores': [],
+            'risk_scores': [],
+        }
+        cut_names = []
+        counter = 0
+
+        for n in range(1, num_labels + 1):
+            ThisLabel = (labels_im == n)
+
+            if ThisLabel.sum() > 0.005 * (labels_im.shape[0] * labels_im.shape[1]):
+                a = np.where(ThisLabel != 0)
+                Left = np.min(a[0])
+                Right = np.max(a[0])
+                Down = np.min(a[1])
+                Up = np.max(a[1])
+                if (((Up - Down) > 0.01 * labels_im.shape[1]) and
+                        ((Right - Left) > 0.01 * labels_im.shape[0]) and
+                        ((Right - Left) < 5 * (Up - Down)) and ((Up - Down) < 5 * (Right - Left))):
+                    counter += 1
+                    CutImg = img[Left:Right, Down:Up, :]
+                    file_name = 'cut_' + str(n) + '.png'
+                    cut_names.append(file_name)
+
+                    WidthScale = CutImg.shape[0] / 224.0
+                    HeightScale = CutImg.shape[1] / 224.0
+                    CutImg = cv2_RGBA2RGB(CutImg)
+                    CutImg = cv2.resize(CutImg, (224, 224))
+                    Data = np.reshape(CutImg, [1, 224, 224, 3])
+                    Data = np.swapaxes(Data, 1, 3)
+
+                    Data = from_numpy(Data)
+                    Data = Data.to(device=device, dtype=float32) / 127.5 - 1.0
+
+                    with no_grad():
+                        out1 = net1(Data)
+                        prob1 = sigmoid(out1).cpu().detach().numpy()
+                        out2 = net2(Data)
+                        prob2 = sigmoid(out2).cpu().detach().numpy()
+                        out3 = net3(Data)
+                        prob3 = sigmoid(out3).cpu().detach().numpy()
+                        prob_comb = sigmoid(out1.add(out2).add(
+                            out3) / 3).cpu().detach().numpy()
+
+                    mask_pred = out1.cpu().detach().add(out2.cpu().detach()).add(
+                        out3.cpu().detach()).numpy() / 3.0
+                    prob_comb = np.reshape(np.swapaxes(
+                        prob_comb, 1, 2), [224, 224, 3])
+                    prob1 = np.reshape(np.swapaxes(prob1, 1, 2), [224, 224, 3])
+                    prob2 = np.reshape(np.swapaxes(prob2, 1, 2), [224, 224, 3])
+                    prob3 = np.reshape(np.swapaxes(prob3, 1, 2), [224, 224, 3])
+                    ActualOut = reshape_and_threshold(mask_pred)
+
+                    result['slice_scores'].append(
+                        calculate_score(prob1[:, :, 0], prob2[:, :, 0], prob3[:, :, 0], prob_comb[:, :, 0]))
+                    result['risk_scores'].append(
+                        calculate_score(prob1[:, :, 2], prob2[:, :, 2], prob3[:, :, 2], prob_comb[:, :, 2]))
+                    result['infarct_scores'].append(
+                        calculate_score(prob1[:, :, 1], prob2[:, :, 1], prob3[:, :, 1], prob_comb[:, :, 1]))
+
+                    for channel in range(3):
+                        padded = np.pad(ActualOut[0, :, :, channel], pad_width=2, mode='constant',
+                                        constant_values=0)
+                        contours, _ = cv2.findContours(padded.astype(np.uint8), cv2.RETR_TREE,
+                                                       cv2.CHAIN_APPROX_SIMPLE)
+                        for cont in contours:
+                            Points = []
+                            PrevPoint = []
+                            for vert in cont:
+                                vert = vert[0]
+                                if len(PrevPoint) == 2:
+                                    if (vert[0] - PrevPoint[0]) ** 2 + (vert[1] - PrevPoint[1]) ** 2 > thresh:
+                                        Points.append(
+                                            int((vert[0] - 2) * HeightScale + Down))
+                                        Points.append(
+                                            int((vert[1] - 2) * WidthScale + Left))
+                                        PrevPoint = [vert[0], vert[1]]
+                                else:
+                                    Points.append(
+                                        int(((vert[0] - 2) * HeightScale + Down)))
+                                    Points.append(
+                                        int(((vert[1] - 2) * WidthScale + Left)))
+                                    PrevPoint = [vert[0], vert[1]]
+
+                            result['class_ids'].append(channel)
+                            result['masks'].append(Points)
+        delete_buff_image(file)
+        return file, result
+
+    except Exception as e:
+        print(e)
+        print("Error while processing file : ", file)
+        delete_buff_image(file)
+        return file, None
diff --git a/processing/process.py b/processing/process.py
new file mode 100644
index 0000000000000000000000000000000000000000..c26903f94ca63cf98ddb28556b53cab43559f866
--- /dev/null
+++ b/processing/process.py
@@ -0,0 +1,27 @@
+import os
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+from processing.ainnotate import ainnotate
+from utils.constants import UPLOAD_FOLDER
+
+
+def process_files(files):
+    results = {}
+    try:
+        with ThreadPoolExecutor() as executor:
+            futures = [executor.submit(ainnotate, file, 50.0)
+                       for file in files]
+            for future in as_completed(futures):
+                file, result = future.result()
+                if result is not None:
+                    results[file] = result
+    except Exception as e:
+        print(e)
+        print("Error while processing files")
+    return results
+
+
+def process_all_files():
+    files = os.listdir(UPLOAD_FOLDER)
+    results = process_files(files)
+    return results
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9b95bc29daca0c8bf07e994a2292530dd77703c5
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,9 @@
+Flask
+Flask-Cors
+opencv-python
+numpy
+torch
+torchvision
+segmentation-models-pytorch
+six
+rembg
\ No newline at end of file
diff --git a/static/assets/css/main.min.css b/static/assets/css/main.min.css
new file mode 100644
index 0000000000000000000000000000000000000000..cb020b8b824ee316418e21a45e96f81be81c05b1
--- /dev/null
+++ b/static/assets/css/main.min.css
@@ -0,0 +1 @@
+@import url(https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css);@import url(https://fonts.googleapis.com/css2?family=Open+Sans:wght@200;300;400;500;600;700&display=swap);@import url(https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css);#image_grid_panel #image_grid_content{position:relative;overflow:hidden;margin:0;padding:0;outline:0}#image_grid_panel #image_grid_content #image_grid_content_img img{margin:.3em;padding:0;border:2px solid #3db4c4}#image_grid_panel #image_grid_content #image_grid_content_img .not_sel{opacity:.6;border:none}#image_grid_panel #image_grid_content #image_grid_content_img{position:absolute;top:0;left:0;width:100%;height:100%}#image_grid_panel #image_grid_content #image_grid_content_rshape{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}#image_grid_panel #image_grid_content img{float:left;margin:0}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background-color:#e4e4e4;border-radius:50px}::-webkit-scrollbar-thumb{background-color:gray;border-radius:50px}.btn-primary{color:#fff;background-color:#3db4c4;border-color:#3db4c4}.btn-primary:hover{color:#fff;background-color:#1c4073;border-color:#272b2d}.nav-tabs .nav-link{color:#1c4073}.form-range::-webkit-slider-thumb{background-color:#3db4c4;border:none}.form-check .form-switch:checked{background-color:#3db4c4}.form-check .form-switch{color:#3db4c4}.landing-text-card{color:#fff}h1,h2{color:#3db4c4}.text-primary{color:#3db4c4!important}.bg-primary{background-color:#3db4c4!important}.bg-secondary{background-color:#bdf283!important}.underline{text-decoration:underline;text-decoration-color:#bdf283;border-radius:10px}.navbar-pharma{background-color:#3db4c4}.navbar-pharma .nav-link{color:#fff}body{background-color:#f8f9fa!important}.navbar{background-color:#f8f9fa!important}.navbar .nav-link{color:#1c4073}.landing-text{color:#1c4073;font-weight:600}.navbar .nav-link:hover{color:#1c4073}.navbar .navbar-brand{color:#fff}.navbar .navbar-brand:hover{color:#fff}.navbar .nav-link.disabled{color:#8a8484}.navbar .button{color:#fff;background-color:#3db4c4;border-color:#3db4c4}.btn-outline-primary{color:#3db4c4;border-color:#3db4c4}.btn-outline-primary.active{color:#fff;background-color:#3db4c4;border-color:#3db4c4}.btn-outline-primary:hover{color:#fff;background-color:#1c4073;border-color:#3db4c4}.card-link{color:#1c4073}.accordion-button:not(.collapsed){color:#fff;background-color:#3db4c4;border-color:#3db4c4}.accordion-button:not(.collapsed)::after{color:#fff;background-color:#3db4c4;border-color:#3db4c4}.accordion-button:not(.collapsed):hover{color:#fff;background-color:#1c4073;border-color:#3db4c4}.accordion-button:not(.collapsed):hover::after{color:#fff;background-color:#1c4073;border-color:#3db4c4}.accordion-button:not(.collapsed):focus{box-shadow:0 0 0 .25rem rgba(61,180,196,.5)}.accordion-button:not(.collapsed):focus::after{box-shadow:0 0 0 .25rem rgba(61,180,196,.5)}.accordion-button:not(.collapsed):active{color:#fff;background-color:#1c4073;border-color:#3db4c4}.list-group-item.active{color:#fff;background-color:#3db4c4;border-color:#3db4c4}.list-group-item.active:hover{color:#fff;background-color:#1c4073;border-color:#3db4c4}.list-group-item.active:focus{z-index:2;color:#fff;background-color:#1c4073;border-color:#3db4c4}.list-group-item.active.focus{z-index:2;color:#fff;background-color:#1c4073;border-color:#3db4c4}.btn-check:checked+.btn-outline-primary{color:#fff;background-color:#3db4c4;border-color:#3db4c4}.btn-outline-primary:hover{color:#fff;background-color:#1c4073;border-color:#3db4c4}.btn-outline-primary:active{color:#fff;background-color:#1c4073;border-color:#3db4c4}.btn-outline-secondary:disabled:hover{color:#3db4c4;background-color:#fff;border-color:#3db4c4}.table{--bs-table-bg:transparent}
\ No newline at end of file
diff --git a/static/assets/js/main.js b/static/assets/js/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..fea7f55e6ef0c611175b4cb9341a88aed4810f2e
--- /dev/null
+++ b/static/assets/js/main.js
@@ -0,0 +1,16638 @@
+// global_variables.js
+// Description: This file contains all the global variables used in the application.
+
+const SAA_VERSION = "1.1.0 (pre-release)";
+const SAA_NAME = "InfarctSize";
+const SAA_SHORT_NAME = "ISAI";
+const VIA_REGION_SHAPE = {
+    EDIT: "edit",
+    RECT: "rect",
+    CIRCLE: "circle",
+    ELLIPSE: "ellipse",
+    POLYGON: "polygon",
+    POINT: "point",
+    POLYLINE: "polyline",
+    PEN: "pen",
+    REMOVE: "remove",
+    TRIM: "trim",
+    DRAG: "drag",
+};
+
+const VIA_ATTRIBUTE_TYPE = {
+    TEXT: "text",
+    CHECKBOX: "checkbox",
+    RADIO: "radio",
+    IMAGE: "image",
+    DROPDOWN: "dropdown",
+};
+
+const VIA_DISPLAY_AREA_CONTENT_NAME = {
+    IMAGE: "image_panel_container",
+    IMAGE_GRID: "image_grid_panel",
+    SETTINGS: "settings_panel",
+    PAGE_404: "page_404",
+    PAGE_GETTING_STARTED: "page_getting_started",
+    PAGE_ABOUT: "page_about",
+    PAGE_START_INFO: "page_start_info",
+    PAGE_LICENSE: "page_license",
+};
+
+const VIA_ANNOTATION_EDITOR_MODE = {
+    SINGLE_REGION: "single_region",
+    ALL_REGIONS: "all_regions",
+    HYBRID: "hybrid",
+};
+
+const VIA_ANNOTATION_EDITOR_PLACEMENT = {
+    NEAR_REGION: "NEAR_REGION",
+    IMAGE_BOTTOM: "IMAGE_BOTTOM",
+    DISABLE: "DISABLE",
+};
+
+let changes = [];
+
+let VIA_REGION_MIN_DIM = 3;
+
+let VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX = 3;
+let VIA_CANVAS_ZOOM_LEVELS = [
+    0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4, 5, 6, 7, 8, 9, 10,
+];
+
+let VIA_THEME_MESSAGE_TIMEOUT_MS = 6000;
+
+let VIA_CSV_SEP = ",";
+let VIA_CSV_KEYVAL_SEP = ":";
+
+// data structure to store loaded images metadata
+
+let _via_img_metadata = {};
+let _via_img_src = {}; // image content {abs. path, url, base64 data, etc}
+let _via_img_fileref = {}; // reference to local images selected by using browser file selector
+let _via_img_count = 0; // count of the loaded images
+let _via_canvas_regions = []; // image regions spec. in canvas space
+let _via_canvas_scale = 1.0; // current scale of canvas image
+
+let dpi = window.devicePixelRatio;
+
+let _via_image_id = ""; // id={filename+length} of current image
+let _via_image_index = -1; // index
+
+let _via_current_image_filename;
+let _via_current_image;
+let _via_current_image_width;
+let _via_current_image_height;
+
+// a record of image statistics (e.g. width, height)
+let _via_img_stat = {};
+
+// image canvas
+let _via_display_area = document.getElementById("display_area");
+let _via_reg_canvas = document.getElementById("region_canvas");
+let _via_canvas_width, _via_canvas_height;
+
+// canvas zoom
+let _via_canvas_zoom_level_index = VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX; // 1.0
+let _via_canvas_scale_without_zoom = 1.0;
+
+// state of the application
+//let _via_is_user_drawing_region = false;
+let _via_is_all_region_selected = false;
+let _via_is_canvas_zoomed = false;
+let _via_is_loading_current_image = false;
+let _via_is_region_id_visible = true;
+let _via_is_region_boundary_visible = true;
+let _via_is_region_info_visible = false;
+let _via_is_ctrl_pressed = false;
+let _via_is_debug_mode = false;
+let _via_is_message_visible = true;
+
+// region copy/paste
+let _via_region_selected_flag = new Set(); // region select flag for current image
+let _via_copied_image_regions = [];
+let _via_paste_to_multiple_images_input;
+
+// message
+let _via_message_clear_timer;
+
+// attributes
+let _via_attribute_being_updated = "region"; // {region, file}
+
+
+let _via_attributes = {
+    region: {
+        Type: {
+            default_options: { Slice: true },
+            description: "",
+            options: {
+                Slice: "Slice",
+                Hole: "Hole",
+                Risk: "Risk",
+                Infarct: "Infarct",
+            },
+            type: "dropdown",
+        },
+    },
+    file: {
+        ID: { type: "text", description: "", default_value: "" },
+    },
+}; //MODSote
+
+let _via_current_attribute_id = "";
+
+// region group color
+//let _via_canvas_regions_group_color = {}; // color of each region
+
+// invoke a method after receiving user input
+let _via_user_input_ok_handler = null;
+let _via_user_input_cancel_handler = null;
+let _via_user_input_data = {};
+
+// annotation editor
+let _via_metadata_being_updated = "region"; // {region, file}
+let _via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION;
+
+// all the image_id and image_filename of images added by the user is
+// stored in _via_image_id_list and _via_image_filename_list
+//
+// Image filename list (img_fn_list) contains a filtered list of images
+// currently accessible by the user. The img_fn_list is visible in the
+// left side toolbar. image_grid, next/prev, etc operations depend on
+// the contents of _via_img_fn_list_img_index_list.
+let _via_image_id_list = []; // array of all image id (in order they were added by user)
+let _via_image_filename_list = []; // array of all image filename
+let _via_image_load_error = []; // {true, false}
+
+let _via_reload_img_fn_list_table = true;
+let _via_img_fn_list_img_index_list = []; // image index list of images show in img_fn_list
+let _via_img_fn_list_html = []; // html representation of image filename list
+
+// image grid
+let image_grid_panel = document.getElementById("image_grid_panel");
+let _via_display_area_content_name = ""; // describes what is currently shown in display area
+let _via_display_area_content_name_prev = "";
+let _via_image_grid_load_ongoing = false;
+let _via_image_grid_page_first_index = 0; // array index in _via_img_fn_list_img_index_list[]
+let _via_image_grid_page_last_index = -1;
+let _via_image_grid_selected_img_index_list = [];
+let _via_image_grid_page_img_index_list = []; // list of all image index in current page of image grid
+let _via_image_grid_visible_img_index_list = []; // list of images currently visible in grid
+let _via_image_grid_mousedown_img_index = -1;
+let _via_image_grid_mouseup_img_index = -1;
+let _via_image_grid_img_index_list = []; // list of all image index in the image grid
+let _via_image_grid_group = {}; // {'value':[image_index_list]}
+let _via_image_grid_group_var = []; // {type, name, value}
+let _via_image_grid_stack_prev_page = []; // stack of first img index of every page navigated so far
+
+// image buffer
+let VIA_IMG_PRELOAD_INDICES = [1, -1, 2, 3, -2, 4]; // for any image, preload previous 2 and next 4 images
+let VIA_IMG_PRELOAD_COUNT = 4;
+
+// via settings
+let _via_settings = {};
+_via_settings.ui = {};
+_via_settings.ui.annotation_editor_fontsize = 0.8; // in rem
+_via_settings.ui.leftsidebar_width = 18; // in rem
+_via_settings.ui.image_grid = {};
+_via_settings.ui.image_grid.img_height = 80; // in pixel
+_via_settings.ui.image_grid.rshape_fill = "none";
+_via_settings.ui.image_grid.rshape_fill_opacity = 0.3;
+_via_settings.ui.image_grid.rshape_stroke = "yellow";
+_via_settings.ui.image_grid.rshape_stroke_width = 2;
+_via_settings.ui.image_grid.show_region_shape = true;
+_via_settings.ui.image_grid.show_image_policy = "all";
+_via_settings.ui.image = {};
+_via_settings.ui.image.region_label = "__via_region_id__"; // default: region_id
+//_via_settings.ui.image.region_color      = '__via_default_region_color__'; // default color: yellow //MODSote
+_via_settings.ui.image.region_color = "Type"; //MODSote
+_via_settings.ui.image.region_label_font = "20px Sans";
+_via_settings.ui.image.on_image_annotation_editor_placement =
+    VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION;
+
+_via_settings.core = {};
+_via_settings.core.buffer_size = 4 * VIA_IMG_PRELOAD_COUNT + 2;
+_via_settings.core.filepath = {};
+_via_settings.core.default_filepath = "";
+
+// UI html elements
+const fileInput = document.getElementById("fileInput");
+const projectFileInput = document.getElementById("projectHelperFileInput");
+const ui_top_panel = document.getElementById("ui_top_panel");
+const image_panel = document.getElementById("image_panel");
+const img_fn_list_panel = document.getElementById("img_fn_list_panel");
+const img_fn_list = document.getElementById("img_fn_list");
+const attributes_panel = document.getElementById("attributes_panel");
+const leftsidebar = document.getElementById("leftsidebar");
+
+let VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE = 0.1; // in rem
+let VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE = 20; // in percent
+let VIA_LEFTSIDEBAR_WIDTH_CHANGE = 1; // in rem
+let VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE = 5; // in degree (used to approximate shapes using polygon)
+let VIA_FLOAT_PRECISION = 3; // number of decimal places to include in float values
+
+// COCO Export
+let VIA_COCO_EXPORT_RSHAPE = ["rect", "circle", "ellipse", "polygon", "point"];
+let VIA_COCO_EXPORT_ATTRIBUTE_TYPE = [
+    VIA_ATTRIBUTE_TYPE.DROPDOWN,
+    VIA_ATTRIBUTE_TYPE.RADIO,
+];
+
+//Tooltips (bootsrap)
+const tooltipTriggerList = document.querySelectorAll(
+    '[data-bs-toggle="tooltip"]'
+);
+const tooltipList = [...tooltipTriggerList].map(
+    (tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl)
+);
+
+let annotation_editor = document.getElementById("annotation_editor_panel");
+let locked_regions = [];
+
+const SegemntaitonServerIP = "192.168.1.129:5000/";
+
+let is_alt_pressed = false;
+
+let current_shape = "rect";
+
+const img_buffer = $("#image_buffer");
+
+const Shapes = {
+    EDIT: "edit",
+    RECTANGLE: "rect",
+    CIRCLE: "circle",
+    ELLIPSE: "ellipse",
+    POLYGON: "polygon",
+    POINT: "point",
+    PEN: "pen",
+    REMOVE: "remove",
+    TRIM: "trim",
+    DRAG: "drag",
+};
+
+let img_locked_regions = {};
+let Scores = {};
+;// message.js
+// Description: This file contains the message class that controls the bootstrap toast messages
+// Message class controlling the bootstrap toast messages
+class Message {
+    static toast = $("#liveToast");
+    static address = $("#toastAddress");
+    static body = $("#toastBody");
+
+    static show(message) {
+        if (message.address === "" || !_via_is_message_visible && message.address !== "Error") {
+            return;
+        }
+        switch (message.address) {
+            case "Error":
+                Message.address.text("Error");
+                Message.address.attr("class", "badge text-bg-danger me-auto");
+                break;
+            case "Warning":
+                Message.address.text("Warning");
+                Message.address.attr("class", "badge text-bg-warning me-auto");
+                break;
+            case "Info":
+                Message.address.text("Info");
+                Message.address.attr("class", "badge text-bg-info me-auto");
+                break;
+            case "Success":
+                Message.address.text("Success");
+                Message.address.attr("class", "badge text-bg-success me-auto");
+                break;
+            default:
+                Message.address.text(message.address);
+                Message.address.attr("class", "badge text-bg-primary me-auto");
+        }
+        Message.body.text(message.body);
+        Message.toast.toast("show");
+    }
+
+    static test() {
+        Message.show({
+            address: "test",
+            body: "test",
+        });
+    }
+
+    static showError(message) {
+        if (message === "") {
+            return;
+        }
+        Message.show({
+            address: "Error",
+            body: message,
+        });
+    }
+
+    static showInfo(message) {
+        if (message === "" || !_via_is_message_visible) {
+            return;
+        }
+        Message.show({
+            address: "Info",
+            body: message,
+        });
+    }
+}
+const message = new Message();;// settings.js
+// Description: This file contains the Settings class which is used to manage the settings of the application.
+// Settings class contains all the settings for the application
+class Settings {
+  constructor() {
+    // settings panel
+    this.panel = $("#settings_panel");
+
+    // project panel
+    this.projectName = this.projectGetDefaultProjectName();
+    this.defaultPath = "";
+    this.searchPath = "";
+    this.bufferSize = 20;
+    this.projectSave = false;
+
+    // region panel
+    // this.regionLabel = $("#settings_regionLabel")
+    //     .find("option:selected")
+    //     .text();
+    // this.regionColorStyle = $("#settings_regionColorStyle")
+    //     .find("option:selected")
+    //     .text();
+    // this.regionColor = $("#settings_regionColor").val();
+    // this.fontStyle = $("#settings_fontStyle").find("option:selected").val();
+    this.is_highlight_region = $("#settings_highlightRegion").is(":checked");
+    this.is_scrolling = $("#settings_scrollToRow").is(":checked");
+
+    // attribute panel
+    this.attributeShowScore = false;
+    this.attributeShowScoreColor = false;
+    this.attributeShowPixelArea = false;
+    this.attributeShowFile = false;
+
+    // score panel
+    this.serverAddress = "";
+    // $("#settings_serverAddress").val(this.serverAddress);
+    $("#settings_serverReduceRatio").val(this.reduction);
+    this.scoreThreshold = "0.85";
+    this.scoreHigherColor = $("#settings_scoreHighColor").val();
+    this.scoreLowerColor = $("#settings_scoreLowColor").val();
+
+    // other panel
+    this.reduction = 0;
+    this.quickbuttons = {};
+
+
+    this.image_grid_content = "all";
+
+    // project autosave
+    this.autoSave = false;
+    this.project = {};
+
+    // load settings
+    this.load();
+    this.initQuickButtons();
+    this.initProject();
+    this.initRegion();
+    this.initScore();
+    this.initOther();
+  }
+
+  //
+  // Settings UI
+  //
+  toggleSettings() {
+    if (this.panel.hasClass("d-none")) {
+      // show settings panel
+      clear_display_area();
+      this.panel.removeClass("d-none");
+      $(`#selection_panel`).hide();
+    } else {
+      // hide settings panel
+      clear_display_area();
+      show_single_image_view();
+      this.panel.addClass("d-none");
+    }
+  }
+
+  //
+  // Settings Local Storage Management
+  //
+
+  load() {
+    let settings_saved = JSON.parse(localStorage.getItem("saa_settings"));
+    if (settings_saved) {
+      this.defaultPath = settings_saved.defaultPath;
+      this.searchPath = settings_saved.searchPath;
+      this.bufferSize = settings_saved.bufferSize;
+      this.projectSave = settings_saved.projectSave;
+
+      this.is_highlight_region = settings_saved.is_highlight_region;
+      this.is_scrolling = settings_saved.is_scrolling;
+
+      this.attributeShowScore = settings_saved.attributeShowScore;
+      this.attributeShowScoreColor = settings_saved.attributeShowScoreColor;
+      this.attributeShowPixelArea = settings_saved.attributeShowPixelArea;
+
+      this.scoreThreshold = settings_saved.scoreThreshold;
+      this.scoreHigherColor = settings_saved.scoreHigherColor;
+      this.scoreLowerColor = settings_saved.scoreLowerColor;
+
+      this.reduction = settings_saved.reduction;
+      this.quickbuttons = settings_saved.quickbuttons;
+
+      this.image_grid_content = settings_saved.image_grid_content;
+      this.autoSave = settings_saved.autoSave;
+    } else {
+      // no settings found
+      console.log("No settings found");
+      this.save(false); // if no settings in local storage, save default settings
+    }
+  }
+
+  save(msg = true) {
+    try {
+      let settings_save = {
+        defaultPath: this.defaultPath,
+        searchPath: this.searchPath,
+        bufferSize: this.bufferSize,
+        projectSave: this.projectSave,
+
+        is_highlight_region: this.is_highlight_region,
+        is_scrolling: this.is_scrolling,
+
+        attributeShowScore: this.attributeShowScore,
+        attributeShowScoreColor: this.attributeShowScoreColor,
+        attributeShowPixelArea: this.attributeShowPixelArea,
+        attributeShowFile: this.attributeShowFile,
+
+        scoreThreshold: this.scoreThreshold,
+        scoreHigherColor: this.scoreHigherColor,
+        scoreLowerColor: this.scoreLowerColor,
+
+        reduction: this.reduction,
+        quickbuttons: this.quickbuttons,
+        image_grid_content: this.image_grid_content,
+        autoSave: this.autoSave,
+
+      };
+      localStorage.setItem("saa_settings", JSON.stringify(settings_save));
+    } catch (e) {
+      console.log(e);
+      Message.show({
+        address: "Error",
+        body: "There was an error saving settings. Please try again.",
+        color: "#cc1100",
+      });
+    }
+    if (msg) {
+      Message.show({
+        address: "Save",
+        body: "Settings saved successfully",
+        color: "#4ADEDE",
+      });
+    }
+  }
+
+  //
+  // Settings Functions - Project
+  //
+  initProject() {
+    $("#settings_projectName")
+      .val(this.projectName)
+      .on("input", () => {
+        this.projectName = $("#settings_projectName").val();
+        $("#project_name").val(this.projectName);
+        this.save();
+      });
+    $("#project_name")
+      .val(this.projectName)
+      .on("input", () => {
+        this.projectName = $("#project_name").val();
+        $("#settings_projectName").val(this.projectName);
+      });
+    $("#settings_defaultPath")
+      .val(this.defaultPath)
+      .on("input", () => {
+        this.defaultPath = $("#settings_defaultPath").val() + "/";
+        this.save();
+        console.log("Default path changed to " + this.defaultPath);
+      });
+    $("#settings_searchPath")
+      .val(this.searchPath)
+      .on("input", () => {
+        this.searchPath = $("#settings_searchPath").val();
+        this.save();
+      });
+  }
+
+  //
+  // Settings Functions - Region
+  //
+  initRegion() {
+    $("#settings_regionLabel").on("change", () => {
+      this.regionLabel = $("#settings_regionLabel")
+        .find("option:selected")
+        .text();
+      this.save();
+    });
+    $("#settings_regionColorStyle").on("change", () => {
+      this.regionColorStyle = $("#settings_regionColorStyle")
+        .find("option:selected")
+        .text();
+      this.save();
+    });
+    $("#settings_regionColor")
+      .val(this.regionColor)
+      .on("input", () => {
+        this.regionColor = $("#settings_regionColor").val();
+        this.save();
+      });
+    $("#settings_fontStyle")
+      .val(this.fontStyle)
+      .on("input", () => {
+        this.fontStyle = $("#settings_fontStyle").val();
+        this.save();
+      });
+    this.toggleHighlightRegionCheckbox();
+    this.toggleScrollingCheckbox();
+  }
+
+  enableHighlightRegion() {
+    this.is_highlight_region = !this.is_highlight_region;
+    this.save();
+    this.toggleHighlightRegionCheckbox();
+  }
+
+  toggleHighlightRegionCheckbox() {
+    if (this.is_highlight_region) {
+      $("#settings_highlightRegion").attr("checked", true);
+    } else {
+      $("#settings_highlightRegion").attr("checked", false);
+    }
+  }
+
+  enableScrollingCheckbox() {
+    this.is_scrolling = !this.is_scrolling;
+    this.save();
+    this.toggleScrollingCheckbox();
+  }
+
+  toggleScrollingCheckbox() {
+    console.log(this.is_scrolling);
+    if (this.is_scrolling) {
+      $("#settings_scrollToRow").attr("checked", true);
+    } else {
+      $("#settings_scrollToRow").attr("checked", false);
+    }
+  }
+
+  toggleScoreCheckbox() {
+    if (this.attributeShowScore) {
+      $("#settings_showScore").attr("checked", true);
+    } else {
+      $("#settings_showScore").attr("checked", false);
+    }
+  }
+
+  toggleScoreColorCheckbox() {
+    if (this.attributeShowScoreColor) {
+      $("#settings_showScoreColor").attr("checked", true);
+    } else {
+      $("#settings_showScoreColor").attr("checked", false);
+    }
+  }
+
+  togglePixelAreaCheckbox() {
+    if (this.attributeShowPixelArea) {
+      $("#settings_showArea").attr("checked", true);
+    } else {
+      $("#settings_showArea").attr("checked", false);
+    }
+  }
+
+  toggleFileAttributesCheckbox() {
+    if (this.attributeShowFile) {
+      $("#settings_showFileAttributes").attr("checked", true);
+    } else {
+      $("#settings_showFileAttributes").attr("checked", false);
+    }
+  }
+
+  initScore() {
+    $("#settings_scoring")
+      .val(this.scoreThreshold)
+      .on("input", () => {
+        this.scoreThreshold = $("#settings_scoring").val();
+        this.save();
+      });
+    $("#settings_scoreHighColor")
+      .val(this.scoreHigherColor)
+      .on("input", () => {
+        this.scoreHigherColor = $("#settings_scoreHighColor").val();
+        console.log(this.scoreHigherColor);
+        this.save();
+      });
+    $("#settings_scoreLowColor")
+      .val(this.scoreLowerColor)
+      .on("input", () => {
+        this.scoreLowerColor = $("#settings_scoreLowColor").val();
+        this.save();
+      });
+    this.toggleScoreCheckbox();
+    this.toggleScoreColorCheckbox();
+    this.togglePixelAreaCheckbox();
+  }
+
+  projectAutoSave() {
+    this.projectSave = !this.projectSave;
+    // this.save();
+  }
+
+  showScore() {
+    this.attributeShowScore = !this.attributeShowScore;
+    this.save();
+  }
+
+  showScoreColor() {
+    this.attributeShowScoreColor = !this.attributeShowScoreColor;
+    this.save();
+  }
+
+  showPixelArea() {
+    this.attributeShowPixelArea = !this.attributeShowPixelArea;
+    this.save();
+  }
+
+  showFileAttributes() {
+    this.attributeShowFile = !this.attributeShowFile;
+    this.save();
+  }
+
+  initQuickButtons() {
+    if (this.quickbuttons === undefined || this.quickbuttons === null || Object.keys(this.quickbuttons).length === 0) {
+      this.quickbuttons = {
+        openProject: true,
+        saveProject: true,
+        settings: true,
+        imgGrid: true,
+        sidePanel: false,
+        autoAnn: true,
+        calcAreas: true,
+        prev: false,
+        next: false,
+        undo: true,
+        redo: true,
+        zoomIn: false,
+        zoomOut: false,
+        selAllReg: false,
+        copySelReg: false,
+        pasteReg: false,
+        delSelReg: false,
+      };
+      this.save(false);
+    }
+  }
+
+  toggleQuickButtonsCheckbox() {
+    if (this.quickbuttons) {
+      for (let key in this.quickbuttons) {
+        if (this.quickbuttons[key]) {
+          $(`#btn-${key}`).attr("checked", true);
+        } else {
+          $(`#btn-${key}`).attr("checked", false);
+        }
+      }
+    }
+  }
+
+  toggleQuickButtons() {
+    if (this.quickbuttons) {
+      for (let key in this.quickbuttons) {
+        if (this.quickbuttons[key]) {
+          $(`#nav_${key}`).show();
+        } else {
+          $(`#nav_${key}`).hide();
+        }
+      }
+    }
+  }
+
+  initOther() {
+    this.toggleQuickButtonsCheckbox();
+    this.toggleQuickButtons();
+    $("#settings_serverReduceRatio").val(this.reduction).on("input", () => {
+      this.reduction = $("#settings_serverReduceRatio").val();
+      this.reduction = parseInt(this.reduction);
+      this.save();
+    });
+    $("#settings_preloadBuffer").val(this.bufferSize).on("input", () => {
+      this.bufferSize = $("#settings_preloadBuffer").val();
+      this.bufferSize = parseInt(this.bufferSize);
+      this.save();
+    });
+    $("#btn-openProject")
+      .val(this.quickbuttons.openProject)
+      .on("change", () => {
+        this.quickbuttons.openProject = $("#btn-openProject").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-saveProject")
+      .val(this.quickbuttons.saveProject)
+      .on("change", () => {
+        this.quickbuttons.saveProject = $("#btn-saveProject").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-settings")
+      .val(this.quickbuttons.settings)
+      .on("change", () => {
+        this.quickbuttons.settings = $("#btn-settings").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-imgGrid")
+      .val(this.quickbuttons.imgGrid)
+      .on("change", () => {
+        this.quickbuttons.imgGrid = $("#btn-imgGrid").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-sidePanel")
+      .val(this.quickbuttons.sidePanel)
+      .on("change", () => {
+        this.quickbuttons.sidePanel = $("#btn-sidePanel").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-autoAnn")
+      .val(this.quickbuttons.autoAnn)
+      .on("change", () => {
+        this.quickbuttons.autoAnn = $("#btn-autoAnn").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-calcAreas")
+      .val(this.quickbuttons.calcAreas)
+      .on("change", () => {
+        this.quickbuttons.calcAreas = $("#btn-calcAreas").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-prev")
+      .val(this.quickbuttons.prev)
+      .on("change", () => {
+        this.quickbuttons.prev = $("#btn-prev").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-next")
+      .val(this.quickbuttons.next)
+      .on("change", () => {
+        this.quickbuttons.next = $("#btn-next").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-undo")
+      .val(this.quickbuttons.undo)
+      .on("change", () => {
+        this.quickbuttons.undo = $("#btn-undo").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-redo")
+      .val(this.quickbuttons.redo)
+      .on("change", () => {
+        this.quickbuttons.redo = $("#btn-redo").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-settings")
+      .val(this.quickbuttons.settings)
+      .on("change", () => {
+        this.quickbuttons.settings = $("#btn-settings").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+
+    $("#btn-zoomIn")
+      .val(this.quickbuttons.zoomIn)
+      .on("change", () => {
+        this.quickbuttons.zoomIn = $("#btn-zoomIn").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-zoomOut")
+      .val(this.quickbuttons.zoomOut)
+      .on("change", () => {
+        this.quickbuttons.zoomOut = $("#btn-zoomOut").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-selAllReg")
+      .val(this.quickbuttons.selAllReg)
+      .on("change", () => {
+        this.quickbuttons.selAllReg = $("#btn-selAllReg").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-copySelReg")
+      .val(this.quickbuttons.copySelReg)
+      .on("change", () => {
+        this.quickbuttons.copySelReg = $("#btn-copySelReg").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-pasteReg")
+      .val(this.quickbuttons.pasteReg)
+      .on("change", () => {
+        this.quickbuttons.pasteReg = $("#btn-pasteReg").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+    $("#btn-delSelReg")
+      .val(this.quickbuttons.delSelReg)
+      .on("change", () => {
+        this.quickbuttons.delSelReg = $("#btn-delSelReg").is(":checked");
+        this.toggleQuickButtons();
+        this.save();
+      });
+  }
+
+  projectGetDefaultProjectName() {
+    const now = new Date();
+    let MONTH_SHORT_NAME = [
+      "Jan",
+      "Feb",
+      "Mar",
+      "Apr",
+      "May",
+      "Jun",
+      "Jul",
+      "Aug",
+      "Sep",
+      "Oct",
+      "Nov",
+      "Dec",
+    ];
+    let ts =
+      now.getDate() +
+      MONTH_SHORT_NAME[now.getMonth()] +
+      now.getFullYear() +
+      "_" +
+      now.getHours() +
+      ":" +
+      now.getMinutes();
+
+    return "ISAI_" + ts;
+  }
+
+  autoSaveProject() {
+    if (this.autoSave) {
+      this.save(false);
+    }
+  }
+
+  updateProjectData(project = {}) {
+    this.project = project;
+  }
+
+  getProjectData() {
+    return this.project;
+  }
+
+  isThereAProject() {
+    return Object.keys(this.project).length > 0;
+  }
+
+  getSettings() {
+    return {
+      defaultPath: this.defaultPath,
+      searchPath: this.searchPath,
+      bufferSize: this.bufferSize,
+      projectSave: this.projectSave,
+      projectName: this.projectName,
+      is_highlight_region: this.is_highlight_region,
+      is_scrolling: this.is_scrolling,
+      attributeShowScore: this.attributeShowScore,
+      attributeShowScoreColor: this.attributeShowScoreColor,
+      attributeShowPixelArea: this.attributeShowPixelArea,
+      scoreThreshold: this.scoreThreshold,
+      scoreHigherColor: this.scoreHigherColor,
+      scoreLowerColor: this.scoreLowerColor,
+      reduction: this.reduction,
+      quickbuttons: this.quickbuttons,
+      image_grid_content: this.image_grid_content,
+      autoSave: this.autoSave,
+
+    };
+  }
+
+  importSettings(settings) {
+    this.defaultPath = settings.defaultPath;
+    this.searchPath = settings.searchPath;
+    this.bufferSize = settings.bufferSize;
+    this.projectSave = settings.projectSave;
+    this.projectName = settings.projectName;
+    this.is_highlight_region = settings.is_highlight_region;
+    this.is_scrolling = settings.is_scrolling;
+    this.attributeShowScore = settings.attributeShowScore;
+    this.attributeShowScoreColor = settings.attributeShowScoreColor;
+    this.attributeShowPixelArea = settings.attributeShowPixelArea;
+    this.scoreThreshold = settings.scoreThreshold;
+    this.scoreHigherColor = settings.scoreHigherColor;
+    this.scoreLowerColor = settings.scoreLowerColor;
+    this.reduction = settings.reduction;
+    this.quickbuttons = settings.quickbuttons;
+    this.image_grid_content = settings.image_grid_content;
+    this.autoSave = settings.autoSave;
+
+    this.save(false);
+  }
+
+}
+const settings = new Settings();;// project.js
+// Description: This file contains the Project class which is responsible for managing the project data.
+// Project class data includes the project name, attributes, images, metadata, and files.
+class Project {
+    constructor() {
+        this.name = settings.projectGetDefaultProjectName();
+        this.attributes = this.initAttributes();
+        this.images = {};
+        this.metadata = {};
+        this.files = {};
+        this.idlist = [];
+    }
+
+    initAttributes() {
+        return {
+            region: {
+                Type: {
+                    default_options: { Slice: true },
+                    description: "",
+                    options: {
+                        Slice: "Slice",
+                        Hole: "Hole",
+                        Risk: "Risk",
+                        Infarct: "Infarct",
+                    },
+                    type: "dropdown",
+                },
+            },
+            file: {
+                ID: { type: "text", description: "", default_value: "" },
+            }
+        };
+    }
+
+
+    setName(name) {
+        this.name = name;
+    }
+
+    initDefaultProject() {
+        if (!settings.isThereAProject()) {
+            this.project = {};
+        }
+        this.setName(settings.projectGetDefaultProjectName());
+    }
+
+    onNameUpdate(p) {
+        this.setName(p.value);
+    }
+
+    getProjectName() {
+        return this.name;
+    }
+
+    saveWithConfirm() {
+        let config = { title: "Save Project" };
+        let name = this.getProjectName();
+        let projectName = "<div class=\"mb-3\">\n" +
+            "  <label for=\"projectNameModal\" class=\"form-label\">Project name</label>\n" +
+            "  <input type=\"email\" class=\"form-control\" id=\"projectNameModal\" placeholder=\"" + name + "\">\n" +
+            "</div>\n"
+        let fileName = "<div class=\"mb-3\">\n" +
+            "  <label for=\"fileNameModal\" class=\"form-label\">Project file name</label>\n" +
+            "  <input type=\"email\" class=\"form-control\" id=\"fileNameModal\" placeholder=\"" + name + "\">\n" +
+            "</div>\n"
+
+        let footer = "<button type='button' class='btn btn-primary' onclick='project.saveConfirmed(document.getElementById(\"projectNameModal\").value, document.getElementById(\"fileNameModal\").value)'>Save</button>";
+        modal.show("Save Project", projectName + fileName, footer, false);
+
+        //invoke_with_user_inputs(this.saveConfirmed.bind(this), input, config);
+    }
+
+    static replacer(key, value) {
+        if (value instanceof Map) {
+            return {
+                dataType: 'Map',
+                value: Array.from(value.entries()), // or with spread: value: [...value]
+            };
+        } else if (value instanceof Set) {
+            return {
+                dataType: 'Set',
+                value: Array.from(value), // or with spread: value: [...value]
+            };
+        } else {
+            return value;
+        }
+    }
+
+    saveConfirmed(pName, fName) {
+        if (pName === "") {
+            pName = this.getProjectName();
+        }
+        if (fName === "") {
+            fName = this.getProjectName();
+        }
+        let obj_urls = {};
+        let project = {
+            name: pName,
+            settings: settings.getSettings(),
+            img_metadata: _via_img_metadata,
+            attributes: this.attributes,
+            image_ids: _via_image_id_list,
+            img_src: _via_img_src,
+        }
+        let data = JSON.stringify(project, this.replacer);
+        let blob = new Blob([data], { type: "application/json" });
+        fileManager.saveDataToLocalFile(blob, fName + ".json");
+        modal.hide();
+        modal.clear();
+
+    }
+
+    openSelectProjectFile() {
+        modal.show("Open Project", "Select a project file to open", "", false);
+        if (fileInput) {
+            fileInput.accept = ".json";
+            fileInput.onchange = this.projectOpen;
+            fileInput.removeAttribute("multiple");
+            fileInput.click();
+        }
+    }
+
+    projectOpen(e) {
+        let selected_file = e.target.files[0];
+        FileManager.loadTextFile(selected_file, project.parseJsonFile.bind(project));
+    }
+
+    parseJsonFile(data) {
+        let p = JSON.parse(data);
+        if (this.checkProjectFileValidity(p)) {
+            settings.importSettings(p.settings);
+            clearViaData();
+
+            for (let img_id in p.img_metadata) {
+                if (this.checkMetadataValidity(p.img_metadata[img_id])) {
+                    _via_img_metadata[img_id] = new FileMetadata(p.img_metadata[img_id].filename, p.img_metadata[img_id].size);
+                    _via_img_metadata[img_id].loadFromJSON(p.img_metadata[img_id]);
+                    _via_image_id_list.push(img_id);
+                    _via_image_filename_list.push(p.img_metadata[img_id].filename);
+                    _via_img_count += 1;
+                    sidebar.set_file_annotations_to_default_value(img_id);
+                } else {
+                    Message.showError("Invalid metadata for image " + img_id);
+                }
+            }
+            if (p.image_ids) {
+                _via_image_id_list = p.image_ids;
+            }
+
+            if (p.attributes) {
+                this.attributes = p.attributes;
+                this.parseAttributesFromMetadata();
+                let fattr_id_list = Object.keys(this.attributes.file);
+                let rattr_id_list = Object.keys(this.attributes.region);
+                if (rattr_id_list.length) {
+                    _via_attribute_being_updated = "region";
+                    _via_current_attribute_id = rattr_id_list[0];
+                } else {
+                    if (fattr_id_list.length) {
+                        _via_attribute_being_updated = "file";
+                        _via_current_attribute_id = fattr_id_list[0];
+                    }
+                }
+            }
+
+            if (p.img_src) {
+                _via_img_src = p.img_src;
+            }
+
+            if (settings.defaultPath !== "") {
+                _via_file_resolve_all_to_default_filepath();
+                modal.hide();
+                modal.clear();
+            } else {
+                // show modal that inform user that project is loaded but images are not loaded and create footer button to load all images (loadAllImages)
+                modal.update(
+                    "Project Loaded",
+                    "Project loaded successfully, but images are not loaded. Do you want to load all project images?",
+                    "<div class='btn-group' data-toggle='buttons'>" +
+                    "<button type='button' class='btn btn-primary' onclick='sel_local_images();'>Select images</button>" +
+                    "<button type='button' class='btn btn-primary' onclick='project.loadAllImages()'>Select image folder</button>" +
+                    "</div>"
+                );
+            }
+
+            Message.show("Project loaded successfully");
+
+            if (_via_image_id_list.length > 0) {
+                _via_image_index = 0;
+                buffer.showImage(_via_image_index);
+                sidebar.update_img_fn_list();
+                _via_reload_img_fn_list_table = true;
+            }
+
+        } else {
+            modal.hide();
+            modal.clear();
+            Message.showError("Invalid project file");
+        }
+
+    }
+
+    loadAllImages() {
+        if (projectFileInput) {
+            projectFileInput.accept = "image/*";
+            projectFileInput.onchange = this.solveImageLoading;
+            projectFileInput.click();
+        }
+    }
+
+    solveImageLoading(e) {
+        let files = e.target.files;
+        for (const element of files) {
+            let filetype = element.type.substring(0, 5);
+            if (filetype === "image") {
+                if (_via_image_filename_list.indexOf(element.name) === -1) {
+                    continue;
+                }
+                let img_index = _via_image_filename_list.indexOf(element.name);
+                let img_id = _via_image_id_list[img_index];
+                _via_img_fileref[img_id] = element;
+                _via_img_metadata[img_id]["size"] = element.size;
+            }
+        }
+        if (_via_image_id_list.length > 0) {
+            _via_image_index = 0;
+            buffer.showImage(_via_image_index);
+            sidebar.update_img_fn_list();
+            _via_reload_img_fn_list_table = true;
+        }
+        modal.hide();
+        modal.clear();
+        projectFileInput.value = "";
+
+    }
+
+    checkProjectFileValidity(project) {
+        return !!(project.settings && project.img_metadata && project.attributes && project.image_ids);
+    }
+
+    checkMetadataValidity(metadata) {
+        return !!(metadata.filename && metadata.size && metadata.file_attributes && metadata.regions);
+    }
+
+    parseAttributesFromMetadata() {
+        if (!this.attributes.hasOwnProperty('file')) {
+            this.attributes.file = {};
+        }
+        if (!this.attributes.hasOwnProperty('region')) {
+            this.attributes.region = {};
+        }
+
+        for (let img_id in _via_img_metadata) {
+            for (let fa in _via_img_metadata[img_id].file_attributes) {
+                if (!this.attributes.file.hasOwnProperty(fa)) {
+                    this.attributes.file[fa] = {};
+                    this.attributes.file[fa]['type'] = 'text';
+                }
+            }
+
+            for (let ri = 0; ri < _via_img_metadata[img_id].regions.length; ++ri) {
+                for (let ra in _via_img_metadata[img_id].regions[ri].region_attributes) {
+                    if (!this.attributes.region.hasOwnProperty(ra)) {
+                        this.attributes.region[ra] = {};
+                        this.attributes.region[ra]["type"] = "text";
+                    }
+                }
+            }
+
+        }
+    }
+
+    fileRemoveWithConfirm() {
+        let img_id = _via_image_id_list[_via_image_index];
+        let filename = _via_img_metadata[img_id].filename;
+        let region_count = _via_img_metadata[img_id].regions.length;
+
+        let config = { title: "Remove File" };
+        let input = {
+            img_index: {
+                type: "text",
+                name: "File Id",
+                value: _via_image_index + 1,
+                disabled: true,
+                size: 8,
+            },
+            filename: {
+                type: "text",
+                name: "Filename",
+                value: filename,
+                disabled: true,
+                size: 30,
+            },
+            region_count: {
+                type: "text",
+                name: "Number of regions",
+                disabled: true,
+                value: region_count,
+                size: 8,
+            },
+        };
+        invoke_with_user_inputs(this.fileRemoveConfirmed, input, config);
+    }
+
+    fileRemoveConfirmed(input) {
+        let img_idx = input.img_index - 1;
+        this.removeFile(img_idx);
+
+        if (img_idx === _via_img_count) {
+            if (_via_img_count === 0) {
+                buffer.imgLoaded = false;
+                show_home_panel();
+            } else {
+                buffer.showImage(img_idx - 1);
+            }
+        } else {
+            buffer.showImage(img_idx);
+        }
+
+        _via_reload_img_fn_list_table = true;
+        sidebar.update_img_fn_list();
+        Message.show("Removed file: " + input.filename);
+
+        user_input_default_cancel_handler();
+    }
+
+    removeFile(img_idx) {
+        if (img_idx < 0 || img_idx >= _via_img_count) {
+            console.log("project_remove_file(): invalid img_index " + img_idx);
+            return;
+        }
+        let img_id = _via_image_id_list[img_idx];
+
+        // remove img_index from all array
+        // this invalidates all image_index > img_index
+        _via_image_id_list.splice(img_idx, 1);
+        _via_image_filename_list.splice(img_idx, 1);
+
+        let img_fn_list_index = _via_img_fn_list_img_index_list.indexOf(img_idx);
+        if (img_fn_list_index !== -1) {
+            _via_img_fn_list_img_index_list.splice(img_fn_list_index, 1);
+        }
+
+        // @TODO: it is wasteful to clear all the buffer instead of removing a single image
+        buffer.emptyBuffer();
+
+        sidebar.img_fn_list_add_css_class("text-muted");
+
+        drawing.clearCanvas();
+        delete _via_img_metadata[img_id];
+        delete _via_img_src[img_id];
+        delete _via_img_fileref[img_id];
+
+        _via_img_count -= 1;
+    }
+
+    addFile(filename, size, fil_id) {
+        let img_id = fil_id;
+        if (typeof fil_id === "undefined") {
+            if (typeof size === "undefined") {
+                size = -1;
+            }
+            img_id = _via_get_image_id(filename, size);
+        }
+
+        if (!_via_img_metadata.hasOwnProperty(img_id)) {
+            _via_img_metadata[img_id] = new FileMetadata(filename, size);
+            _via_image_id_list.push(img_id);
+            _via_image_filename_list.push(filename);
+            _via_img_count += 1;
+        }
+        return img_id;
+    }
+
+    addLocalFile(event) {
+        let user_selected_images = event.target.files;
+
+        let new_img_index_list = [];
+        let discarded_file_count = 0;
+        for (const element of user_selected_images) {
+            let filetype = element.type.substring(0, 5);
+            if (filetype === "image") {
+                let img_index = _via_image_filename_list.indexOf(element.name);
+                if (img_index === -1) {
+                    // a new file was added to project
+                    let new_img_id = project.addFile(element.name, element.size);
+                    _via_img_fileref[new_img_id] = element;
+                    sidebar.set_file_annotations_to_default_value(new_img_id);
+                    new_img_index_list.push(_via_image_id_list.indexOf(new_img_id));
+                } else {
+                    let img_id = _via_image_id_list[img_index];
+                    _via_img_fileref[img_id] = element;
+                    _via_img_metadata[img_id]["size"] = element.size;
+                }
+            } else {
+                discarded_file_count += 1;
+            }
+        }
+
+        if (_via_img_metadata) {
+            let status_msg = "Loaded " + new_img_index_list.length + " images.";
+            if (discarded_file_count) {
+                status_msg +=
+                    " ( Discarded " + discarded_file_count + " non-image files! )";
+            }
+            show_message(status_msg);
+
+            if (new_img_index_list.length) {
+                buffer.showImage(new_img_index_list[0]);
+            } else {
+                // show original image
+                buffer.showImage(_via_image_index);
+            }
+            sidebar.update_img_fn_list();
+        } else {
+            show_message("Please upload some image files!");
+        }
+    }
+
+    addAbsPathFileWithInput() {
+        let config = { title: "Add File" };
+        let input = {
+            abs_path: {
+                type: "text",
+                name: "Absolute Path",
+                value: "",
+                disabled: false,
+                size: 30,
+            },
+        };
+        invoke_with_user_inputs(this.addAbsPathFileConfirmed, input, config);
+    }
+
+    addAbsPathFileConfirmed(input) {
+        let abs_path = input.abs_path;
+        if (abs_path !== "") {
+            let filename = abs_path.split("/").pop();
+            let img_id = this.addUrlFile(abs_path);
+            let img_index = _via_image_id_list.indexOf(img_id);
+            buffer.showImage(img_index);
+            sidebar.update_img_fn_list();
+            user_input_default_cancel_handler();
+            Message.showInfo("Added file: " + filename);
+        } else {
+            if (input.absolute_path_list.value !== "") {
+                let absolute_path_list_str = input.absolute_path_list.value;
+                import_files_url_from_csv(absolute_path_list_str).then(() =>
+                    console.log("csv")
+                );
+            } else {
+                Message.showError("Please enter a valid path");
+            }
+        }
+    }
+
+    addUrlFileWithInput() {
+        let config = { title: "Add File using URL" };
+        let input = {
+            url: {
+                type: "text",
+                name: "add one URL",
+                placeholder:
+                    "https://example.com/image.jpg",
+                disabled: false,
+                size: 50,
+            },
+            url_list: {
+                type: "textarea",
+                name: "or, add multiple URL (one url per line)",
+                placeholder:
+                    "https://example.com/image1.jpg\nhttps://example.com/image2.jpg\nhttps://example.com/image3.png",
+                disabled: false,
+                rows: 5,
+                cols: 80,
+            },
+        };
+
+        invoke_with_user_inputs(this.addUrlFileConfirmed, input, config);
+    }
+
+    addUrlFileConfirmed(input) {
+        let url = "";
+        if (input && input.url && input.url.value) {
+            url = input.url.value.trim()
+            let img_id = this.addUrlFile(url);
+            let img_index = _via_image_id_list.indexOf(img_id);
+            show_message("Added file at url [" + url + "]");
+            sidebar.update_img_fn_list();
+            buffer.showImage(img_index);
+            user_input_default_cancel_handler();
+        } else {
+            if (input.url_list.value !== "") {
+                let url_list_str = input.url_list.value;
+                import_files_url_from_csv(url_list_str).then(() => console.log("csv"));
+            }
+        }
+    }
+
+    addUrlFile(url) {
+        if (url !== "") {
+            let size = -1; // convention: files added using url have size = -1
+            let img_id = _via_get_image_id(url, size);
+
+            if (!_via_img_metadata.hasOwnProperty(img_id)) {
+                img_id = project.addFile(url);
+                _via_img_src[img_id] = _via_img_metadata[img_id].filename;
+                sidebar.set_file_annotations_to_default_value(img_id);
+                return img_id;
+            }
+        }
+    }
+
+    fileLoadOnFail(img_idx) {
+        let img_id = _via_image_id_list[img_idx];
+        _via_img_src[img_id] = "";
+        _via_image_load_error[img_idx] = true;
+        sidebar.img_fn_list_ith_entry_error(img_idx, true);
+    }
+
+    fileLoadOnSuccess(img_idx) {
+        _via_image_load_error[img_idx] = false;
+        sidebar.img_fn_list_ith_entry_error(img_idx, false);
+    }
+
+    importAttributesFromFile(event) {
+        let selected_files = event.target.files;
+        for (let i = 0; i < selected_files.length; ++i) {
+            let file = selected_files[i];
+            FileManager.loadTextFile(file, this.importAttributesFromJson);
+        }
+    }
+
+    importAttributesFromJson(data) {
+        try {
+            let d = JSON.parse(data);
+            let attr;
+            let fattr_count = 0;
+            let rattr_count = 0;
+            // process file attributes
+            for (attr in d["file"]) {
+                this.attributes.file[attr] = JSON.parse(
+                    JSON.stringify(d["file"][attr])
+                );
+                fattr_count += 1;
+            }
+
+            // process region attributes
+            for (attr in d["region"]) {
+                this.attributes.region[attr] = JSON.parse(
+                    JSON.stringify(d["region"][attr])
+                );
+                rattr_count += 1;
+            }
+
+            if (fattr_count > 0 || rattr_count > 0) {
+                let fattr_id_list = Object.keys(this.attributes.file);
+                let rattr_id_list = Object.keys(this.attributes.region);
+                if (rattr_id_list.length) {
+                    _via_attribute_being_updated = "region";
+                    _via_current_attribute_id = rattr_id_list[0];
+                } else {
+                    if (fattr_id_list.length) {
+                        _via_attribute_being_updated = "file";
+                        _via_current_attribute_id = fattr_id_list[0];
+                    }
+                }
+                attribute_update_panel_set_active_button();
+                update_attributes_update_panel();
+                sidebar.annotation_editor_update_content();
+            }
+            Message.showInfo(
+                "Imported " +
+                fattr_count +
+                " file attributes and " +
+                rattr_count +
+                " region attributes"
+            );
+        } catch (error) {
+            Message.showError("Failed to import attributes: [" + error + "]");
+        }
+    }
+}
+
+function clearViaData() {
+    // clear existing data (if any)
+    _via_image_id_list = [];
+    _via_image_filename_list = [];
+    _via_img_count = 0;
+    _via_img_metadata = {};
+    _via_img_fileref = {};
+    _via_img_src = {};
+    project.attributes = { region: {}, file: {} };
+    buffer.emptyBuffer();
+}
+
+function import_img_metadata(img_metadata) {
+    var img_id;
+    for (img_id in img_metadata) {
+        if (!_via_img_metadata.hasOwnProperty(img_id)) {
+            _via_img_metadata[img_id] = img_metadata[img_id];
+            _via_image_id_list.push(img_id);
+            _via_image_filename_list.push(img_metadata[img_id].filename);
+            _via_img_count += 1;
+        }
+    }
+}
+
+const project = new Project();;// sidebar.js
+// Description: This file contains all the functions related to the sidebar.
+// Sidebar class to controll the sidebar
+class Sidebar {
+  constructor() {
+    this.img_fn_list = $("#img_fn_list");
+    this.img_fn_list_regex = $("#img_fn_list_regex");
+    this.img_fn_list_regex.val("");
+    this.img_fn_list_regex.keyup(
+      function () {
+        this.img_fn_list_onregex();
+      }.bind(this)
+    );
+    this.img_fn_list_preset_filters = $("#filelist_preset_filters_list");
+    this.img_fn_list_preset_filters.change(
+      function () {
+        this.img_fn_list_onpresetfilter_select();
+      }.bind(this)
+    );
+    this.img_fn_list_html = [];
+    this.init_img_fn_list();
+
+    this.aep = $("#annotation_editor_panel");
+    this.ae = $("#annotation_editor");
+    this.aec = $("#annotation_editor_content");
+    this.display_area = $("#display_area");
+    this.descending_order = true;
+    this.addEventlisteners();
+  }
+
+  addEventlisteners() {
+    //for context menu
+    this.img_fn_list.on(
+      "contextmenu",
+      "li",
+      function (e) {
+        e.preventDefault();
+        this.file_manager_contextmenu(e);
+      }.bind(this)
+    );
+    //for display area
+    this.display_area.on(
+      "contextmenu",
+      "div",
+      function (e) {
+        e.preventDefault();
+        this.descending_order = !this.descending_order;
+        this.annotation_editor_contextmenu(e);
+      }.bind(this)
+    );
+  }
+
+  //--------------------------------------------
+  // Image Filename List HTML Generation
+  //--------------------------------------------
+
+  file_manager_contextmenu(e) {
+    //calculate positions for context menu
+    let x = e.pageX;
+    let y = e.pageY;
+    let w = this.aep.width();
+    let h = this.aep.height();
+    let dw = this.display_area.width();
+    let dh = this.display_area.height();
+    let dx = this.display_area.offset().left;
+    let dy = this.display_area.offset().top;
+
+    //check if context menu is out of display area
+    if (x + w > dx + dw) {
+      x = dx + dw - w;
+    }
+    if (y + h > dy + dh) {
+      y = dy + dh - h;
+    }
+
+    //create context menu
+    let contextmenu = $(
+      '<ul class="list-group" id="contextmenu_delete_root" style="position: absolute; z-index: 100"></ul>'
+    );
+    if (contextmenu.length > 0) {
+      $("#contextmenu_delete_root").remove();
+    }
+    contextmenu.css("left", x);
+    contextmenu.css("top", y);
+    contextmenu.append(
+      '<li class="list-group-item" id="contextmenu_delete">Delete</li>'
+    );
+
+    //add event listeners
+    contextmenu.find("li").hover(function () {
+      $(this).addClass("active");
+    });
+    contextmenu.find("li").mouseleave(function () {
+      $(this).removeClass("active");
+    });
+
+    contextmenu.find("#contextmenu_delete").click(
+      function () {
+        //caalculate image id from img_fn_list
+        let image_title = $(e.target).attr("title");
+        let image_id = _via_image_filename_list.indexOf(image_title);
+        project.removeFile(image_id);
+        if (image_id === _via_img_count) {
+          if (_via_img_count === 0) {
+            buffer.imgLoaded = false;
+            show_home_panel();
+          } else {
+            buffer.showImage(image_id - 1);
+          }
+        } else {
+          buffer.showImage(image_id);
+        }
+        _via_reload_img_fn_list_table = true;
+        sidebar.update_img_fn_list();
+        show_message("Removed file [" + image_title + "] from project");
+        user_input_default_cancel_handler();
+        contextmenu.remove();
+        this.update_img_fn_list();
+      }.bind(this)
+    );
+
+    //delete context menu if clicked outside
+    $(document).click(function (e) {
+      if (e.target.id !== "contextmenu_delete") {
+        contextmenu.remove();
+      }
+    });
+
+    //append context menu to display area
+    this.display_area.append(contextmenu);
+  }
+
+  annotation_editor_contextmenu(e) {
+    if ($("#image_panel_container").hasClass("d-none")) {
+      return;
+    }
+
+    //calculate positions for context menu
+    let x = e.pageX;
+    let y = e.pageY;
+
+    //create context menu
+    //let contextmenu = $('<ul id="annotation_editor_contextmenu" class="list-group" style="position: absolute; z-index: 100"></ul>');
+    let contextmenu = $("<table>", {
+      style: "position: absolute; z-index: 100",
+      class: "table table-sm table-borderless",
+      id: "annotation_editor_contextmenu",
+    });
+    //if there is already a context menu, remove it
+    if (contextmenu.length) {
+      $("#annotation_editor_contextmenu").remove();
+    }
+
+    //set width of context menu
+    contextmenu.css("width", "auto");
+    //set position of context menu
+    contextmenu.css("left", x);
+    contextmenu.css("top", y);
+    contextmenu.addClass("position-absolute card p-1 m");
+    contextmenu.css({
+      display: "inline-block",
+      width: "auto",
+      padding: "8px!important",
+    });
+
+    let thead = $("<thead>");
+    //contextmenu.append(thead);
+    //thead.append('<tr><div class="text-center">Quick annotator</div></tr>');
+    let tbody = $("<tbody>");
+    contextmenu.append(tbody);
+    //tbody.append('<tr><td class="text-center" id="contextmenu_delete">Delete</td></tr>');
+
+    if (
+      Object.keys(project.attributes.region).length &&
+      project.attributes.region.constructor === Object
+    ) {
+      // to enable automatic hiding of this content
+      // add annotation editor to image_panel
+      let ids = drawing.isInsideAllRegion(
+        { x: e.offsetX, y: e.offsetY },
+        this.descending_order,
+        true
+      );
+      if (ids.length === 0 || ids[0] === undefined || ids[0] === -1) {
+        return;
+      }
+      for (let i = 0; i < ids.length; i++) {
+        let id = ids[i];
+        if (id === undefined) {
+          continue;
+        }
+        let row = this.annotation_editor_get_metadata_row_html(id, true);
+        tbody.append(row);
+      }
+
+      this.display_area.append(contextmenu);
+      this.annotation_editor_update_content();
+      //append context menu to display area
+    }
+
+    //delete context menu if clicked outside
+    $(document).click(function (e) {
+      // detect de all objects in the context menu
+      let contextmenu = $("#annotation_editor_contextmenu");
+      let contextmenu_children = contextmenu.find("*");
+      let is_contextmenu = false;
+      for (let i = 0; i < contextmenu_children.length; i++) {
+        if (e.target === contextmenu_children[i]) {
+          is_contextmenu = true;
+        }
+      }
+      if (!is_contextmenu) {
+        contextmenu.remove();
+      }
+    });
+
+    //todo: checkbox
+    drawing.updateCheckedLockHtml();
+  }
+
+  init_img_fn_list() {
+    this.img_fn_list_html = [];
+    this.img_fn_list_html.push('<ul class="list-group">');
+    for (let i = 0; i < _via_image_filename_list.length; ++i) {
+      this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(i));
+    }
+    this.img_fn_list_html.push("</ul>");
+    this.img_fn_list.innerHTML = this.img_fn_list_html.join("");
+    this.img_fn_list_scroll_to_current_file();
+  }
+
+  img_fn_list_onregex() {
+    let regex = this.img_fn_list_regex.val();
+    let p = this.img_fn_list_preset_filters;
+    if (p.selectedIndex === 6) {
+      this.filter_selected_region_attribute(regex);
+      return;
+    }
+
+    this.img_fn_list_generate_html(regex);
+    img_fn_list.innerHTML = this.img_fn_list_html.join("");
+    this.img_fn_list_scroll_to_current_file();
+
+    // select 'regex' in the predefined filter list
+    if (regex === "") {
+      p.selectedIndex = 0;
+    }
+  }
+
+  update_img_fn_list() {
+    let regex = this.img_fn_list_regex.val();
+    let p = this.img_fn_list_preset_filters;
+    if (regex === "" || regex === null) {
+      if (p.selectedIndex === 0) {
+        // show all files
+        this.img_fn_list_html = [];
+        _via_img_fn_list_img_index_list = [];
+        this.img_fn_list_html.push('<ul class="list-group">');
+        for (let i = 0; i < _via_image_filename_list.length; ++i) {
+          this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(i));
+          _via_img_fn_list_img_index_list.push(i);
+        }
+        this.img_fn_list_html.push("</ul>");
+        this.img_fn_list.innerHTML = this.img_fn_list_html.join("");
+        this.img_fn_list_scroll_to_current_file();
+      } else {
+        // filter according to preset filters
+        this.img_fn_list_onpresetfilter_select();
+      }
+    } else {
+      if (p.selectedIndex === 6) {
+        // Filter By Region Attribute
+        this.filter_selected_region_attribute(regex);
+      } else {
+        // RegExp
+        this.img_fn_list_generate_html(regex);
+        img_fn_list.innerHTML = _via_img_fn_list_html.join("");
+        this.img_fn_list_scroll_to_current_file();
+      }
+    }
+  }
+
+  img_fn_list_onpresetfilter_select() {
+    let p = this.img_fn_list_preset_filters;
+    let filter = p.find(":selected").val();
+    switch (filter) {
+      case "all":
+        document.getElementById("img_fn_list_regex").value = "";
+        this.img_fn_list_generate_html();
+        img_fn_list.innerHTML = this.img_fn_list_html.join("");
+        this.img_fn_list_scroll_to_current_file();
+        break;
+      case "regex":
+        this.img_fn_list_onregex();
+        this.img_fn_list_regex.focus();
+        break;
+      case "files_region_attribute":
+        this.img_fn_list_onregex();
+        this.img_fn_list_regex.focus();
+        break;
+      default:
+        this.img_fn_list_html = [];
+        _via_img_fn_list_img_index_list = [];
+        this.img_fn_list_html.push('<ul class="list-group">');
+        let i;
+        for (i = 0; i < _via_image_filename_list.length; ++i) {
+          let img_id = _via_image_id_list[i];
+          let add_to_list = false;
+          switch (filter) {
+            case "files_without_region":
+              if (_via_img_metadata[img_id].regions.length === 0) {
+                add_to_list = true;
+              }
+              break;
+            case "files_missing_region_annotations":
+              if (this.is_region_annotation_missing(img_id)) {
+                add_to_list = true;
+              }
+              break;
+            case "files_missing_file_annotations":
+              if (this.is_file_annotation_missing(img_id)) {
+                add_to_list = true;
+              }
+              break;
+            case "files_error_loading":
+              if (_via_image_load_error[i] === true) {
+                add_to_list = true;
+              }
+          }
+          if (add_to_list) {
+            this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(i));
+            _via_img_fn_list_img_index_list.push(i);
+          }
+        }
+        this.img_fn_list_html.push("</ul>");
+        img_fn_list.innerHTML = this.img_fn_list_html.join("");
+        this.img_fn_list_scroll_to_current_file();
+        break;
+    }
+  }
+
+  is_region_annotation_missing(img_id) {
+    let region_attribute;
+    let i;
+    for (i = 0; i < _via_img_metadata[img_id].regions.length; ++i) {
+      for (region_attribute in project.attributes.region) {
+        if (
+          _via_img_metadata[img_id].regions[i].region_attributes.hasOwnProperty(
+            region_attribute
+          )
+        ) {
+          if (
+            _via_img_metadata[img_id].regions[i].region_attributes[
+            region_attribute
+            ] === ""
+          ) {
+            return true;
+          }
+        } else {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  is_file_annotation_missing(img_id) {
+    let file_attribute;
+    for (file_attribute in project.attributes.file) {
+      if (
+        _via_img_metadata[img_id].file_attributes.hasOwnProperty(file_attribute)
+      ) {
+        if (_via_img_metadata[img_id].file_attributes[file_attribute] === "") {
+          return true;
+        }
+      } else {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  img_fn_list_ith_entry_selected(img_index, is_selected) {
+    if (is_selected) {
+      this.img_fn_list_ith_entry_add_css_class(img_index, "active");
+    } else {
+      this.img_fn_list_ith_entry_remove_css_class(img_index, "active");
+    }
+  }
+
+  img_fn_list_ith_entry_error(img_index, is_error) {
+    if (is_error) {
+      this.img_fn_list_ith_entry_add_css_class(img_index, "error");
+    } else {
+      this.img_fn_list_ith_entry_remove_css_class(img_index, "error");
+    }
+  }
+
+  img_fn_list_ith_entry_add_css_class(img_index, classname) {
+    let li = document.getElementById("fl" + img_index);
+    if (li && !li.classList.contains(classname)) {
+      li.classList.add(classname);
+    }
+  }
+
+  img_fn_list_ith_entry_remove_css_class(img_index, classname) {
+    let li = document.getElementById("fl" + img_index);
+    if (li && li.classList.contains(classname)) {
+      li.classList.remove(classname);
+    }
+  }
+
+  img_fn_list_clear_all_style() {
+    $("#img_fn_list li").removeClass("active");
+  }
+
+  img_fn_list_add_css_class(classname) {
+    $("#img_fn_list li").removeClass();
+    $("#img_fn_list li").addClass(classname);
+  }
+
+  img_fn_list_ith_entry_html(i) {
+    let htmlI = "";
+    let filename = _via_image_filename_list[i];
+    if (is_url(filename)) {
+      filename =
+        filename.substring(0, 4) + "..." + get_filename_from_url(filename);
+    }
+    let isActive = false;
+    htmlI += '<li  id="fl' + i + '"';
+    if (
+      _via_display_area_content_name ===
+      VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+    ) {
+      if (_via_image_grid_page_img_index_list.includes(i)) {
+        // highlight images being shown in image grid
+        isActive = true;
+      }
+    } else {
+      if (i === _via_image_index) {
+        // highlight the current entry
+        isActive = true;
+      }
+    }
+    if (isActive) {
+      htmlI += ' class="list-group-item active"';
+    } else {
+      htmlI += ' class="list-group-item text-muted"';
+    }
+    htmlI +=
+      ' style="height:40px; overflow: hidden; white-space: nowrap; cursor: default;" onclick="jump_to_image(' +
+      i +
+      ')" title="' +
+      _via_image_filename_list[i] +
+      '">[' +
+      (i + 1) +
+      "] " +
+      decodeURIComponent(filename) +
+      "</li>";
+    return htmlI;
+  }
+
+  img_fn_list_generate_html(regex) {
+    this.img_fn_list_html = [];
+    _via_img_fn_list_img_index_list = [];
+    this.img_fn_list_html.push('<ul class="list-group">');
+    for (let i = 0; i < _via_image_filename_list.length; ++i) {
+      let filename = _via_image_filename_list[i];
+      if (filename.match(regex) !== null) {
+        this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(i));
+        _via_img_fn_list_img_index_list.push(i);
+      }
+    }
+    this.img_fn_list_html.push("</ul>");
+  }
+
+  img_fn_list_scroll_to_current_file() {
+    this.img_fn_list_scroll_to_file(_via_image_index);
+  }
+
+  img_fn_list_scroll_to_file(file_index) {
+    if (_via_img_fn_list_img_index_list.includes(file_index)) {
+      let sel_file = document.getElementById("fl" + file_index);
+      let panel_height = img_fn_list.clientHeight - 20;
+      let window_top = img_fn_list.scrollTop;
+      let window_bottom = img_fn_list.scrollTop + panel_height;
+      if (sel_file.offsetTop > window_top) {
+        if (sel_file.offsetTop > window_bottom) {
+          img_fn_list.scrollTop = sel_file.offsetTop;
+        }
+      } else {
+        img_fn_list.scrollTop = sel_file.offsetTop - panel_height;
+      }
+    }
+  }
+
+  filter_selected_region_attribute(property_entry) {
+    const propArray = property_entry.split("=");
+    if (propArray.length !== 2 || propArray[0] === "" || propArray[1] === "")
+      return;
+    let selected_attribute = propArray[0];
+    let selected_value = propArray[1];
+    if (selected_value === '""') selected_value = "";
+
+    _via_img_fn_list_html = [];
+    _via_img_fn_list_img_index_list = [];
+    _via_img_fn_list_html.push("<ul>");
+
+    if (project.attributes.region.hasOwnProperty(selected_attribute)) {
+      let i;
+
+      for (i = 0; i < _via_image_filename_list.length; ++i) {
+        let img_id = _via_image_id_list[i];
+        let idx;
+
+        for (idx = 0; idx < _via_img_metadata[img_id].regions.length; ++idx) {
+          if (
+            _via_img_metadata[img_id].regions[
+              idx
+            ].region_attributes.hasOwnProperty(selected_attribute)
+          ) {
+            if (
+              JSON.stringify(
+                _via_img_metadata[img_id].regions[idx].region_attributes[
+                selected_attribute
+                ]
+              ) === JSON.stringify(selected_value)
+            ) {
+              _via_img_fn_list_html.push(this.img_fn_list_ith_entry_html(i));
+              _via_img_fn_list_img_index_list.push(i);
+              break;
+            }
+          }
+        }
+      }
+    }
+    _via_img_fn_list_html.push("</ul>");
+    img_fn_list.innerHTML = _via_img_fn_list_html.join("");
+    this.img_fn_list_scroll_to_current_file();
+  }
+
+  //--------------------------------------------
+  // Annotation Editor Panel
+  //--------------------------------------------
+
+  /**
+   * Show the annotation editor panel
+   * Create the annotation editor panel if it does not exist
+   * and also an on image annotation editor panel
+   */
+  annotation_editor_show() {
+    // remove existing annotation editor (if any)
+    this.annotation_editor_remove();
+
+    // create new container of annotation editor
+    this.aep.empty();
+
+    let ae_tmp = $("<table>", {
+      id: "annotation_editor",
+      class: "table table-sm",
+    });
+
+    if (
+      _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION
+    ) {
+      if (
+        _via_settings.ui.image.on_image_annotation_editor_placement ===
+        VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE
+      ) {
+        return;
+      }
+
+      // only display on-image annotation editor if
+      // - region attribute are defined
+      // - region is selected
+      if (
+        drawing.isRegionSelected() &&
+        Object.keys(project.attributes.region).length &&
+        project.attributes.region.constructor === Object
+      ) {
+        ae_tmp.addClass("position-absolute card p-1 m");
+        ae_tmp.css({ display: "inline-block", width: "auto" });
+
+        // to enable automatic hiding of this content
+        // add annotation editor to image_panel
+        if (
+          _via_settings.ui.image.on_image_annotation_editor_placement ===
+          VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION
+        ) {
+          let html_position = this.annotation_editor_get_placement(
+            drawing.userSelRegionId()
+          );
+          ae_tmp.css({ top: html_position.top, left: html_position.left });
+        }
+
+        this.display_area.append(ae_tmp);
+        //this.ae = $('#annotation_editor');
+        this.annotation_editor_update_content();
+      }
+    } else {
+      // show annotation editor in a separate panel at the bottom
+      this.aep.append(ae_tmp);
+      this.ae = $("#annotation_editor");
+
+      this.annotation_editor_update_content();
+
+      if (drawing.isRegionSelected()) {
+        // highlight entry for region_id in annotation editor panel
+        this.annotation_editor_scroll_to_row(drawing.userSelRegionId());
+        this.annotation_editor_highlight_row(drawing.userSelRegionId());
+      }
+    }
+  }
+
+  annotation_editor_contextmenu_handler(e) {
+    e.stopPropagation();
+    e.preventDefault();
+
+    let region_id = parseInt(e.target.dataset.regionId);
+    let region = _via_img_metadata[_via_image_id].regions[region_id];
+    let region_attributes = region.region_attributes;
+
+    let menu = new Menu();
+    menu.append(
+      new MenuItem({
+        label: "Delete",
+        click: () => {
+          drawing.deleteRegion(region_id);
+          this.annotation_editor_remove();
+        },
+      })
+    );
+
+    if (region_attributes.hasOwnProperty("type")) {
+      let type = region_attributes["type"];
+      if (type === "text") {
+        menu.append(
+          new MenuItem({
+            label: "Edit Text",
+            click: () => {
+              drawing.editRegionText(region_id);
+              this.annotation_editor_remove();
+            },
+          })
+        );
+      }
+    }
+
+    menu.popup(remote.getCurrentWindow());
+  }
+
+  /**
+   * Remove the annotation editor panel
+   */
+  annotation_editor_hide() {
+    if (
+      _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION
+    ) {
+      // remove existing annotation editor (if any)
+      this.annotation_editor_remove();
+    } else {
+      this.annotation_editor_clear_row_highlight();
+    }
+  }
+
+  /**
+   * Toggle the annotation editor panel
+   * Create the annotation editor panel if it does not exist
+   */
+  annotation_editor_toggle_on_image_editor() {
+    if (
+      _via_settings.ui.image.on_image_annotation_editor_placement ===
+      VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE
+    ) {
+      _via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION;
+      _via_settings.ui.image.on_image_annotation_editor_placement =
+        VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION;
+      this.annotation_editor_show();
+      show_message("Enabled on image annotation editor");
+    } else {
+      _via_settings.ui.image.on_image_annotation_editor_placement =
+        VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE;
+      _via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS;
+      this.annotation_editor_hide();
+      show_message("Disabled on image annotation editor");
+    }
+  }
+
+  /**
+   * Update the annotation editor panel
+   */
+  annotation_editor_update_content() {
+    try {
+      if (this.ae !== undefined) {
+        this.ae.empty();
+        this.annotation_editor_update_header_html();
+        this.annotation_editor_update_metadata_html();
+        drawing.updateCheckedLockHtml();
+      }
+    } catch (err) {
+      console.log(err);
+      Message.show({
+        address: "Warning",
+        body: "Cannot update annotation editor content",
+        color: "#e8f800",
+      });
+    }
+  }
+
+  /**
+   * Calculate the position of on image annotation editor panel
+   * @param region_id
+   * @returns {{}} position of on image annotation editor panel top and left properties
+   */
+  annotation_editor_get_placement(region_id) {
+    let html_position = {};
+    let r = _via_canvas_regions[region_id]["shape_attributes"];
+    let shape = r["name"];
+    switch (shape) {
+      case "rect":
+        html_position.top = r["y"] + r["height"];
+        html_position.left = r["x"] + r["width"];
+        break;
+      case "circle":
+        html_position.top = r["cy"] + r["r"];
+        html_position.left = r["cx"];
+        break;
+      case "ellipse":
+        html_position.top = r["cy"] + r["ry"] * Math.cos(r["theta"]);
+        html_position.left = r["cx"] - r["ry"] * Math.sin(r["theta"]);
+        break;
+      case "polygon":
+      case "polyline":
+        let most_left = Object.keys(r["all_points_x"]).reduce(function (a, b) {
+          return r["all_points_x"][a] > r["all_points_x"][b] ? a : b;
+        });
+        html_position.top = Math.max(r["all_points_y"][most_left]);
+        html_position.left = Math.max(r["all_points_x"][most_left]);
+        break;
+      case "point":
+        html_position.top = r["cy"];
+        html_position.left = r["cx"];
+        break;
+    }
+    html_position.top = html_position.top + image_panel.offsetTop;
+    // drawing.regionEdgeTol() + panzoom.getPan().y +'px';
+    html_position.left = html_position.left + image_panel.offsetLeft;
+    //drawing.regionEdgeTol() + panzoom.getPan().x +'px';
+    return html_position;
+  }
+
+  /**
+   * Remove the annotation editor panel
+   */
+  annotation_editor_remove() {
+    if (this.ae !== undefined) {
+      this.ae.remove();
+    }
+  }
+
+  /**
+   * @returns {boolean} true if annotation editor panel is visible
+   */
+  is_annotation_editor_visible() {
+    return this.aep !== undefined;
+  }
+
+  /**
+   * Toggle the annotation editor panel
+   */
+  annotation_editor_toggle_all_regions_editor() {
+
+    _via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS;
+    this.aep.css(
+      "font-size",
+      _via_settings.ui.annotation_editor_fontsize + "rem"
+    );
+
+    this.annotation_editor_show();
+  }
+
+  /**
+   * Set the active row in annotation editor panel
+   */
+  annotation_editor_set_active_button() {
+    let attribute_type;
+    for (attribute_type in project.attributes) {
+      let bid = "button_edit_" + attribute_type + "_metadata";
+      $("#" + bid).removeClass("active");
+    }
+    let bid = "button_edit_" + _via_metadata_being_updated + "_metadata";
+    $("#" + bid).addClass("active");
+  }
+
+  edit_region_metadata_in_annotation_editor() {
+    _via_metadata_being_updated = "region";
+    this.annotation_editor_set_active_button();
+    this.annotation_editor_update_content();
+  }
+
+  edit_file_metadata_in_annotation_editor() {
+    _via_metadata_being_updated = "file";
+    this.annotation_editor_set_active_button();
+    this.annotation_editor_update_content();
+  }
+
+  annotation_editor_update_header_html(rt = false) {
+    let head = $("<thead>", { id: "annotation_editor_header" });
+    let tr = $("<tr>");
+
+    if (_via_metadata_being_updated === "region") {
+      let rid_col = $("<th>", { scope: "col", html: "#" });
+      tr.append(rid_col);
+      //problem
+    }
+
+    if (_via_metadata_being_updated === "file") {
+      let rid_col = $("<th>", { scope: "col" });
+      if (
+        _via_display_area_content_name ===
+        VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+      ) {
+        //sidebar.toggleAEMode(true, "regions");
+        rid_col.html("group");
+      } else {
+        rid_col.html("file");
+      }
+      tr.append(rid_col);
+    }
+
+    let attr_id;
+    for (attr_id in project.attributes[_via_metadata_being_updated]) {
+      $("<th>", { scope: "col", html: attr_id }).appendTo(tr);
+    }
+    if (_via_metadata_being_updated === "region") {
+      $("<th>", {
+        scope: "col",
+        html: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-lock-fill\" viewBox=\"0 0 16 16\">\n" +
+          "  <path d=\"M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2m3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2\"/>\n" +
+          "</svg>",
+      }).appendTo(tr);
+      if (settings.attributeShowScore) {
+        $("<th>", {
+          scope: "col",
+          html: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-calculator-fill\" viewBox=\"0 0 16 16\">\n" +
+            "  <path d=\"M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm2 .5v2a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5h-7a.5.5 0 0 0-.5.5m0 4v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5M4.5 9a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM4 12.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5M7.5 6a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM7 9.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5m.5 2.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM10 6.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5m.5 2.5a.5.5 0 0 0-.5.5v4a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 0-.5-.5z\"/>\n" +
+            "</svg>",
+        }).appendTo(tr);
+      }
+      if (settings.attributeShowPixelArea) {
+        $("<th>", {
+          scope: "col",
+          html: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrows-angle-expand\" viewBox=\"0 0 16 16\">\n" +
+            "  <path fill-rule=\"evenodd\" d=\"M5.828 10.172a.5.5 0 0 0-.707 0l-4.096 4.096V11.5a.5.5 0 0 0-1 0v3.975a.5.5 0 0 0 .5.5H4.5a.5.5 0 0 0 0-1H1.732l4.096-4.096a.5.5 0 0 0 0-.707m4.344-4.344a.5.5 0 0 0 .707 0l4.096-4.096V4.5a.5.5 0 1 0 1 0V.525a.5.5 0 0 0-.5-.5H11.5a.5.5 0 0 0 0 1h2.768l-4.096 4.096a.5.5 0 0 0 0 .707\"/>\n" +
+            "</svg>",
+        }).appendTo(tr);
+      }
+
+      /*$('<th>', {scope: 'col',
+                html:'<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 40 40"><path d="M8.458 34.167q-1.083 0-1.854-.771-.771-.771-.771-1.854V8.458q0-1.083.771-1.854.771-.771 1.854-.771h23.084q1.083 0 1.854.771.771.771.771 1.854v23.084q0 1.083-.771 1.854-.771.771-1.854.771Zm6.417-12.334 6.792 6.792 10.416-10.417v-9.75q0-.208-.166-.375-.167-.166-.375-.166H8.458q-.208 0-.375.166-.166.167-.166.375V28.75Zm5.5-2.208v-9.5h1.792v9.5Zm4.708 0-2.916-4.75 2.916-4.75h2.167l-2.917 4.625 2.917 4.875Zm-13.291 0v-5.708H16v-2h-4.208v-1.792h6v5.542h-4.209v2.166h4.209v1.792Zm3.083 5.083L7.833 31.75q0 .083.146.208.146.125.354.125h23.209q.208 0 .375-.166.166-.167.166-.375V21.125L21.667 31.542Zm-6.958 6.834v.541V7.917v.541Z"/></svg>'
+            }).appendTo(tr);*/
+    }
+    head.append(tr);
+    if (rt) {
+      return head;
+    }
+
+    if (this.ae.length === 0) {
+      this.ae.append(head);
+      return;
+    } else {
+      if (this.ae.first().attr("id") === "annotation_editor_header") {
+        this.ae.first().replaceWith(head);
+        //problem
+      } else {
+        // header node is absent
+        this.ae.prepend(head);
+      }
+      return;
+    }
+  }
+
+  annotation_editor_update_metadata_html() {
+    if (!_via_img_count) {
+      return;
+    }
+
+    if (!this.ae.has("tbody").length) {
+      this.ae.append('<tbody id="annotation_editor_content"></tbody>');
+      this.aec = $("#annotation_editor_content");
+    }
+
+    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));
+        } 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_img_metadata[_via_image_id].isGroupable()) {
+                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)
+                  );
+                }
+              }
+            }
+          }
+        }
+        break;
+
+      case "file":
+        this.aec.append(this.annotation_editor_get_metadata_row_html(0));
+        break;
+    }
+  }
+
+  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");
+    }
+    let new_row = this.annotation_editor_get_metadata_row_html(row_id);
+    let id = new_row.attr("id");
+
+    this.aec
+      .find(id)
+      .replaceWith(this.annotation_editor_get_metadata_row_html(row_id));
+  }
+
+  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");
+      }
+      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) {
+        let penultimate_row_html_id =
+          "ae_" + _via_metadata_being_updated + "_" + penultimate_row_id;
+        $("#" + penultimate_row_html_id).after(new_row);
+      } else {
+        this.aec.append(new_row);
+      }
+    }
+  }
+
+  annotation_editor_get_metadata_row_html(row_id, is_context_menu = false, is_example = false) {
+    let id = "ae_" + _via_metadata_being_updated + "_" + row_id;
+    if (is_context_menu) {
+      id = "ae_cm_" + _via_metadata_being_updated + "_" + row_id;
+    }
+    if (is_example) {
+      id = "ae_example_" + _via_metadata_being_updated + "_" + row_id;
+    }
+    let row = $("<tr>", { id: id, class: "align-middle" });
+    if (
+      _via_metadata_being_updated === "region" &&
+      _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE && settings.attributeShowScoreColor
+    ) {
+      if (_via_img_metadata[_via_image_id].regions[row_id] !== undefined) {
+        if (
+          _via_img_metadata[_via_image_id].regions[row_id].hasOwnProperty(
+            "score"
+          )
+        ) {
+          let score = _via_img_metadata[_via_image_id].regions[row_id].score;
+          if (score > settings.scoreThreshold) {
+            row.css("background-color", settings.scoreHigherColor);
+          } else if (score < 0) {
+            row.removeAttr("style");
+          } else {
+            row.css("background-color", settings.scoreLowerColor);
+          }
+        }
+      }
+    }
+
+    if (_via_metadata_being_updated === "region") {
+      let rid = $("<th>", { scope: "row" });
+
+      switch (_via_display_area_content_name) {
+        case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
+          rid.html(
+            "Grouped regions in " +
+            _via_image_grid_selected_img_index_list.length +
+            " files"
+          );
+          break;
+        case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
+          rid.html(row_id + 1);
+          rid.attr("id", "0__" + row_id);
+          rid.attr("onclick", "sidebar.annotation_editor_on_metadata_focus(this)");
+          //problem
+          break;
+        default:
+          rid.html(row_id + 1);
+          rid.attr("id", "0__" + row_id);
+          break;
+      }
+      row.append(rid);
+    }
+
+    if (_via_metadata_being_updated === "file") {
+      let rid = $("<th>", { scope: "row" });
+      switch (_via_display_area_content_name) {
+        case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
+          rid.html(
+            "Group of " +
+            _via_image_grid_selected_img_index_list.length +
+            " files"
+          );
+          break;
+        case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
+          rid.html(_via_image_filename_list[_via_image_index]);
+          break;
+
+        default:
+          rid.html("file_example.png");
+          break;
+      }
+
+      row.append(rid);
+    }
+
+    let attr_id;
+    for (attr_id in project.attributes[_via_metadata_being_updated]) {
+      let col = $("<td>");
+
+      let attr_type =
+        project.attributes[_via_metadata_being_updated][attr_id].type;
+      let attr_desc = "";
+      if (project.attributes[_via_metadata_being_updated][attr_id].hasOwnProperty("desc")) {
+        attr_desc = project.attributes[_via_metadata_being_updated][attr_id].desc;
+      }
+
+      let attr_html_id = attr_id + "__" + row_id;
+
+      let attr_value = "";
+      let attr_placeholder = "";
+      if (
+        _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE
+      ) {
+        switch (_via_metadata_being_updated) {
+          case "region":
+            if (
+              _via_img_metadata[_via_image_id].regions[
+                row_id
+              ].region_attributes.hasOwnProperty(attr_id)
+            ) {
+              attr_value =
+                _via_img_metadata[_via_image_id].regions[row_id]
+                  .region_attributes[attr_id];
+            } else {
+              attr_placeholder = "not defined yet!";
+            }
+            break;
+          case "file":
+            if (
+              _via_img_metadata[_via_image_id].file_attributes.hasOwnProperty(
+                attr_id
+              )
+            ) {
+              attr_value =
+                _via_img_metadata[_via_image_id].file_attributes[attr_id];
+            } else {
+              attr_placeholder = "not defined yet!";
+            }
+            break;
+        }
+      }
+
+      if (
+        _via_display_area_content_name ===
+        VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+      ) {
+        let attr_metadata_stat;
+        switch (_via_metadata_being_updated) {
+          case "region":
+            attr_metadata_stat = this._via_get_region_metadata_stat(
+              _via_image_grid_selected_img_index_list,
+              attr_id
+            );
+            break;
+          case "file":
+            attr_metadata_stat = this._via_get_file_metadata_stat(
+              _via_image_grid_selected_img_index_list,
+              attr_id
+            );
+            break;
+        }
+
+        switch (attr_type) {
+          case "text":
+            if (attr_metadata_stat.hasOwnProperty(attr_id)) {
+              let attr_value_set = Object.keys(attr_metadata_stat[attr_id]);
+              if (attr_value_set.includes("undefined")) {
+                attr_value = "";
+                attr_placeholder =
+                  "includes " +
+                  attr_metadata_stat[attr_id]["undefined"] +
+                  " undefined values";
+              } else {
+                switch (attr_value_set.length) {
+                  case 0:
+                    attr_value = "";
+                    attr_placeholder = "not applicable";
+                    break;
+                  case 1:
+                    attr_value = attr_value_set[0];
+                    attr_placeholder = "";
+                    break;
+                  default:
+                    attr_value = "";
+                    attr_placeholder =
+                      attr_value_set.length +
+                      " different values: " +
+                      JSON.stringify(attr_value_set).replace(/"/g, "'");
+                }
+              }
+            } else {
+              attr_value = "";
+              attr_placeholder = "not defined yet!";
+            }
+            break;
+
+          case "radio": // fallback
+          case "dropdown": // fallback
+          case "image": // fallback
+            if (attr_metadata_stat.hasOwnProperty(attr_id)) {
+              let attr_value_set = Object.keys(attr_metadata_stat[attr_id]);
+              if (attr_value_set.length === 1) {
+                attr_value = attr_value_set[0];
+              } else {
+                attr_value = "";
+              }
+            } else {
+              attr_value = "";
+            }
+            break;
+
+          case "checkbox":
+            attr_value = {};
+            if (attr_metadata_stat.hasOwnProperty(attr_id)) {
+              let attr_value_set = Object.keys(attr_metadata_stat[attr_id]);
+              let same_count = true;
+              let i, n;
+              let attr_value_curr, attr_value_next;
+              n = attr_value_set.length;
+              for (i = 0; i < n - 1; ++i) {
+                attr_value_curr = attr_value_set[i];
+                attr_value_next = attr_value_set[i + 1];
+
+                if (
+                  attr_metadata_stat[attr_id][attr_value_curr] !==
+                  attr_metadata_stat[attr_id][attr_value_next]
+                ) {
+                  same_count = false;
+                  break;
+                }
+              }
+              if (same_count) {
+                let attr_value_i;
+                for (attr_value_i in attr_metadata_stat[attr_id]) {
+                  attr_value[attr_value_i] = true;
+                }
+              }
+            }
+            break;
+        }
+      }
+
+      switch (attr_type) {
+        case "text":
+          col.attr("id", attr_html_id);
+          col.html(
+            "<textarea " +
+            'title="' +
+            attr_desc +
+            '" ' +
+            'placeholder="' +
+            attr_placeholder +
+            '" ' +
+            'onchange="' +
+            "sidebar.annotation_editor_on_metadata_update(this); return false;" +
+            '" ' +
+            'onfocus="' +
+            "sidebar.annotation_editor_on_metadata_focus(this); return false;" +
+            '">' +
+            attr_value +
+            "</textarea>"
+          );
+          if (!is_example) {
+            col.attr("id", attr_html_id);
+          }
+
+          break;
+        case "checkbox": {
+          let options =
+            project.attributes[_via_metadata_being_updated][attr_id].options;
+          let option_id;
+          for (option_id in options) {
+            let option_html_id = attr_html_id + "__" + option_id;
+
+            let option = $("<input>", {
+              value: option_id,
+              class: "form-check-input",
+              type: "checkbox",
+            });
+            if (!is_example) {
+              option.attr("id", option_html_id);
+              option.attr(
+                "onfocus",
+                "sidebar.annotation_editor_on_metadata_focus(this); return false;"
+              );
+              option.attr(
+                "onchange",
+                "sidebar.annotation_editor_on_metadata_update(this); return false;"
+              );
+            }
+
+            let option_desc =
+              project.attributes[_via_metadata_being_updated][attr_id].options[
+              option_id
+              ];
+            if (option_desc === "" || typeof option_desc === "undefined") {
+              // option description is optional, use option_id when description is not present
+              option_desc = option_id;
+            }
+
+            // set the value of options based on the user annotations
+            if (typeof attr_value !== "undefined") {
+              if (attr_value.hasOwnProperty(option_id)) {
+                option.prop("checked", attr_value[option_id]);
+              }
+            }
+
+            let label = $("<label>");
+            label.attr("for", option_html_id);
+            label.html(option_desc);
+
+            let container = $("<div>", { class: "row" });
+            let col_ = $("<div>", { class: "col" });
+            col_.append(option);
+            col_.append(label);
+            container.append(col_);
+            col.append(container);
+          }
+          break;
+        }
+        case "radio": {
+          let option_id;
+          for (option_id in project.attributes[_via_metadata_being_updated][
+            attr_id
+          ].options) {
+            let option_html_id = attr_html_id + "__" + option_id;
+            let option = $("<input>", {
+              type: "radio",
+              name: attr_html_id,
+              class: "form-check-input",
+              value: option_id,
+            });
+            if (!is_example) {
+              option.attr("id", option_html_id);
+              option.attr(
+                "onchange",
+                "sidebar.annotation_editor_on_metadata_update(this); return false;"
+              );
+              option.attr(
+                "oncfocus",
+                "sidebar.annotation_editor_on_metadata_focus(this); return false;"
+              );
+            }
+
+            let option_desc =
+              project.attributes[_via_metadata_being_updated][attr_id].options[
+              option_id
+              ];
+            if (option_desc === "" || typeof option_desc === "undefined") {
+              // option description is optional, use option_id when description is not present
+              option_desc = option_id;
+            }
+
+            if (attr_value === option_id) {
+              option.prop("checked", true)
+            }
+
+            let label = $("<label>", {
+              for: option_html_id,
+              html: option_desc,
+            });
+
+            let container = $("<div>", { class: "row" });
+            let col_ = $("<div>", { class: "col" });
+            col_.append(option);
+            col_.append(label);
+            container.append(col_);
+            col.append(container);
+          }
+          break;
+        }
+        case "image": {
+          let option_id;
+          let option_count = 0;
+          for (option_id in project.attributes[_via_metadata_being_updated][
+            attr_id
+          ].options) {
+            option_count = option_count + 1;
+          }
+          let img_options = $("<div>", { class: "image_options" });
+          //problem bootstrap
+          col.append(img_options);
+
+          for (option_id in project.attributes[_via_metadata_being_updated][
+            attr_id
+          ].options) {
+            let option_html_id = attr_html_id + "__" + option_id;
+            let option = $("<input>", {
+              type: "radio",
+              name: attr_html_id,
+              class: "form-check-input",
+              value: option_id,
+            });
+            if (!is_example) {
+              option.attr("id", option_html_id);
+              option.attr(
+                "onchange",
+                "sidebar.annotation_editor_on_metadata_update(this); return false;"
+              );
+              option.attr(
+                "onfocus",
+                "sidebar.annotation_editor_on_metadata_focus(this); return false;"
+              );
+            }
+
+            let option_desc =
+              project.attributes[_via_metadata_being_updated][attr_id].options[
+              option_id
+              ];
+            if (option_desc === "" || typeof option_desc === "undefined") {
+              // option description is optional, use option_id when description is not present
+              option_desc = option_id;
+            }
+
+            if (attr_value === option_id) {
+              option.prop("checked", true);
+            }
+
+            let label = $("<label>", { for: option_html_id });
+            label.html(
+              '<img src="' + option_desc + '" alt=""><p>' + option_id + "</p>"
+            );
+
+            let container = $("<div>", { class: "row" });
+            let col_ = $("<div>", { class: "col" });
+            col_.append(option);
+            col_.append(label);
+            container.append(col_);
+            img_options.append(container);
+          }
+          break;
+        }
+        case "dropdown": {
+          let sel = $("<select>");
+          if (!is_example) {
+            sel.attr("id", attr_html_id);
+            sel.attr(
+              "onfocus",
+              "sidebar.annotation_editor_on_metadata_focus(this); return false;"
+            );
+            sel.attr(
+              "onchange",
+              "sidebar.annotation_editor_on_metadata_update(this); return false;"
+            );
+          }
+          sel.addClass("form-select form-select-sm");
+          //problem
+          let option_id;
+          let option_selected = false;
+          for (option_id in project.attributes[_via_metadata_being_updated][
+            attr_id
+          ].options) {
+            let option = $("<option>", { value: option_id });
+
+            let option_desc =
+              project.attributes[_via_metadata_being_updated][attr_id].options[
+              option_id
+              ];
+            if (option_desc === "" || typeof option_desc === "undefined") {
+              // option description is optional, use option_id when description is not present
+              option_desc = option_id;
+            }
+
+            if (option_id === attr_value) {
+              option.attr("selected", "selected");
+              option_selected = true;
+            }
+            option.html(option_desc);
+            sel.append(option);
+          }
+
+          if (!option_selected) {
+            sel.selectedIndex = -1; //MODSote
+          }
+          col.append(sel);
+          break;
+        }
+      }
+      row.append(col);
+    }
+    try {
+      if (is_example) {
+        let lock = $("<td>").html($('<input>', { type: 'checkbox', class: 'form-check-input' }));
+        row.append(lock);
+        if (settings.attributeShowScore) {
+          let score = $("<td>").html("n/a");
+          row.append(score);
+        }
+        if (settings.attributeShowPixelArea) {
+          let actAreaHtml = $("<td>").html("100");
+          row.append(actAreaHtml);
+        }
+        return row;
+      }
+      if (
+        _via_metadata_being_updated === "region" &&
+        _via_img_metadata[_via_image_id].regions[row_id] !== undefined
+      ) {
+        let lock = $("<td>");
+
+        let id_lock = "lock_" + row_id;
+        if (is_context_menu) {
+          id_lock = "lock_" + row_id + "_context";
+        }
+        let checkbox = $("<input>", {
+          type: "checkbox",
+          class: "form-check-input",
+        });
+        checkbox.attr("id", id_lock);
+        checkbox.attr("onchange", "drawing.lockRegionHandler(this); return false;");
+        lock.append(checkbox);
+        row.append(lock);
+        let act_region = _via_img_metadata[_via_image_id].regions[row_id];
+        if (settings.attributeShowScore) {
+          let score = $("<td>").html("n/a");
+          if (act_region.hasOwnProperty("score") && !isNaN(act_region.score)) {
+            score.html(Math.round(act_region.score * 100) / 100);
+          }
+          row.append(score);
+        }
+        if (settings.attributeShowPixelArea) {
+          let actArea;
+          if (act_region.shape_attributes.name === "polygon") {
+            actArea = plugin.calcPolygonArea(
+              act_region.shape_attributes.all_points_x,
+              act_region.shape_attributes.all_points_y
+            );
+          } else {
+            actArea =
+              '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.46457 14.1213C8.07404 14.5118 8.07404 15.145 8.46457 15.5355C8.85509 15.926 9.48825 15.926 9.87878 15.5355L15.5356 9.87862C15.9262 9.4881 15.9262 8.85493 15.5356 8.46441C15.1451 8.07388 14.5119 8.07388 14.1214 8.46441L8.46457 14.1213Z" fill="currentColor" /><path fill-rule="evenodd" clip-rule="evenodd" d="M6.34315 17.6569C9.46734 20.781 14.5327 20.781 17.6569 17.6569C20.781 14.5327 20.781 9.46734 17.6569 6.34315C14.5327 3.21895 9.46734 3.21895 6.34315 6.34315C3.21895 9.46734 3.21895 14.5327 6.34315 17.6569ZM16.2426 16.2426C13.8995 18.5858 10.1005 18.5858 7.75736 16.2426C5.41421 13.8995 5.41421 10.1005 7.75736 7.75736C10.1005 5.41421 13.8995 5.41421 16.2426 7.75736C18.5858 10.1005 18.5858 13.8995 16.2426 16.2426Z" fill="currentColor" /></svg>';
+          }
+          let actAreaHtml = $("<td>").html(actArea);
+          row.append(actAreaHtml);
+        }
+      }
+      drawing.updateCheckedLockHtml();
+    } catch (e) {
+      console.log("Ok, no problem, just no lock for this region :D");
+    }
+    return row;
+  }
+
+  annotation_editor_scroll_to_row(row_id) {
+    if (this.is_annotation_editor_visible() && settings.is_scrolling) {
+      if (_via_metadata_being_updated === "file") {
+        row_id = 0;
+      }
+      let row_html_id = "ae_" + _via_metadata_being_updated + "_" + row_id;
+      let row = document.getElementById(row_html_id);
+      row.scrollIntoView(false);
+      //problem
+    }
+  }
+
+  annotation_editor_highlight_row(row_id) {
+    if (this.is_annotation_editor_visible()) {
+      let row_html_id = "ae_" + _via_metadata_being_updated + "_" + row_id;
+      $("#" + row_html_id).addClass("table-active");
+    }
+  }
+
+  annotation_editor_clear_row_highlight() {
+    if (this.is_annotation_editor_visible()) {
+      let ae = $("#annotation_editor_content");
+      $(".table-active", ae).removeClass("table-active");
+    }
+  }
+
+  annotation_editor_extract_html_id_components(html_id) {
+    // html_id : attribute_name__row-id__option_id
+    let parts = html_id.split("__");
+    let parsed_id = {};
+    switch (parts.length) {
+      case 3:
+        // html_id : attribute-id__row-id__option_id
+        parsed_id.attr_id = parts[0];
+        parsed_id.row_id = parts[1];
+        parsed_id.option_id = parts[2];
+        break;
+      case 2:
+        // html_id : attribute-id__row-id
+        parsed_id.attr_id = parts[0];
+        parsed_id.row_id = parts[1];
+        break;
+      default:
+    }
+    return parsed_id;
+  }
+
+  _via_get_file_metadata_stat(img_index_list, attr_id) {
+    let stat = {};
+    stat[attr_id] = {};
+    let i, n, img_id, img_index, value;
+    n = img_index_list.length;
+    for (i = 0; i < n; ++i) {
+      img_index = img_index_list[i];
+      img_id = _via_image_id_list[img_index];
+      if (_via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id)) {
+        value = _via_img_metadata[img_id].file_attributes[attr_id];
+        if (typeof value === "object") {
+          // checkbox has multiple values and hence is object
+          let key;
+          for (key in value) {
+            if (stat[attr_id].hasOwnProperty(key)) {
+              stat[attr_id][key] += 1;
+            } else {
+              stat[attr_id][key] = 1;
+            }
+          }
+        } else {
+          if (stat[attr_id].hasOwnProperty(value)) {
+            stat[attr_id][value] += 1;
+          } else {
+            stat[attr_id][value] = 1;
+          }
+        }
+      }
+    }
+    return stat;
+  }
+
+  _via_get_region_metadata_stat(img_index_list, attr_id) {
+    let stat = {};
+    stat[attr_id] = {};
+    let i, n, img_id, img_index, value;
+    let j, m;
+    n = img_index_list.length;
+    for (i = 0; i < n; ++i) {
+      img_index = img_index_list[i];
+      img_id = _via_image_id_list[img_index];
+      m = _via_img_metadata[img_id].regions.length;
+      for (j = 0; j < m; ++j) {
+        if (
+          !image_grid_is_region_in_current_group(
+            _via_img_metadata[img_id].regions[j].region_attributes
+          )
+        ) {
+          // skip region not in current group
+          continue;
+        }
+
+        value = _via_img_metadata[img_id].regions[j].region_attributes[attr_id];
+        if (typeof value === "object") {
+          // checkbox has multiple values and hence is object
+          let key;
+          for (key in value) {
+            if (stat[attr_id].hasOwnProperty(key)) {
+              stat[attr_id][key] += 1;
+            } else {
+              stat[attr_id][key] = 1;
+            }
+          }
+        } else {
+          if (stat[attr_id].hasOwnProperty(value)) {
+            stat[attr_id][value] += 1;
+          } else {
+            stat[attr_id][value] = 1;
+          }
+        }
+      }
+    }
+    return stat;
+  }
+
+  annotation_editor_on_metadata_focus(p) {
+    if (
+      _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS
+    ) {
+      let pid = this.annotation_editor_extract_html_id_components(p.id);
+      let region_id = pid.row_id;
+      // clear existing highlights (if any)
+      toggle_all_regions_selection(false);
+      this.annotation_editor_clear_row_highlight();
+      // set new selection highlights
+      set_region_select_state(region_id, true);
+      this.annotation_editor_highlight_row(region_id);
+      drawing.setIsRegionSelected(true);
+      drawing.setUserSelRegionId(region_id);
+      drawing.redrawRegCanvas();
+    }
+  }
+
+  annotation_editor_on_metadata_update(p) {
+    let pid = this.annotation_editor_extract_html_id_components(p.id);
+    let img_index_list = [_via_image_index];
+    let region_id = pid.row_id;
+    if (
+      _via_display_area_content_name ===
+      VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+    ) {
+      img_index_list = _via_image_grid_selected_img_index_list.slice(0);
+      region_id = -1; // this flag denotes that we want to update all regions
+    }
+
+    if (_via_metadata_being_updated === "file") {
+      let update_count = this.annotation_editor_update_file_metadata(
+        img_index_list,
+        pid.attr_id,
+        p.value,
+        p.checked
+      );
+      this.annotation_editor_on_metadata_update_done(
+        "file",
+        pid.attr_id,
+        update_count
+      );
+      return;
+    }
+
+    if (_via_metadata_being_updated === "region") {
+      let update_count = this.annotation_editor_update_region_metadata(
+        img_index_list,
+        region_id,
+        pid.attr_id,
+        p.value,
+        p.checked
+      );
+      this.annotation_editor_on_metadata_update_done(
+        "region",
+        pid.attr_id,
+        update_count
+      );
+    }
+  }
+
+  annotation_editor_on_metadata_update_done(type, attr_id, update_count) {
+    show_message(
+      "Updated " + type + " attributes of " + update_count + " " + type + "s"
+    );
+    // check if the updated attribute is one of the group variables
+    let i, n; //problem
+    n = _via_image_grid_group_var.length;
+    let clear_all_group = false;
+    for (i = 0; i < n; ++i) {
+      if (
+        _via_image_grid_group_var[i].type === type &&
+        _via_image_grid_group_var[i].name === attr_id
+      ) {
+        clear_all_group = true;
+        break;
+      }
+    }
+    drawing.regionsGroupColorInit();
+    drawing.redrawRegCanvas();
+
+    // @todo: it is wasteful to cancel the full set of groups.
+    // we should only cancel the groups that are affected by this update.
+    if (
+      _via_display_area_content_name ===
+      VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+    ) {
+      if (clear_all_group) {
+        image_grid_show_all_project_images();
+      }
+    }
+  }
+
+  annotation_editor_update_file_metadata(
+    img_index_list,
+    attr_id,
+    new_value,
+    new_checked
+  ) {
+    try {
+      let i, n, img_id, img_index;
+      n = img_index_list.length;
+      let update_count = 0;
+      for (i = 0; i < n; ++i) {
+        img_index = img_index_list[i];
+        img_id = _via_image_id_list[img_index];
+
+        switch (project.attributes.file[attr_id].type) {
+          case "text": // fallback
+          case "radio": // fallback
+          case "dropdown": // fallback
+          case "image":
+            _via_img_metadata[img_id].file_attributes[attr_id] = new_value;
+            update_count += 1;
+            break;
+
+          case "checkbox":
+            let option_id = new_value;
+            if (
+              _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id)
+            ) {
+              if (
+                typeof _via_img_metadata[img_id].file_attributes[attr_id] !==
+                "object"
+              ) {
+                let old_value =
+                  _via_img_metadata[img_id].file_attributes[attr_id];
+                _via_img_metadata[img_id].file_attributes[attr_id] = {};
+                if (
+                  Object.keys(
+                    project.attributes.file[attr_id]["options"]
+                  ).includes(old_value)
+                ) {
+                  // transform existing value as checkbox option
+                  _via_img_metadata[img_id].file_attributes[attr_id] = {};
+                  _via_img_metadata[img_id].file_attributes[attr_id][
+                    old_value
+                  ] = true;
+                }
+              }
+            } else {
+              _via_img_metadata[img_id].file_attributes[attr_id] = {};
+            }
+            if (new_checked) {
+              _via_img_metadata[img_id].file_attributes[attr_id][
+                option_id
+              ] = true;
+            } else {
+              // false option values are not stored
+              delete _via_img_metadata[img_id].file_attributes[attr_id][
+                option_id
+              ];
+            }
+            update_count += 1;
+            break;
+        }
+      }
+      return update_count;
+    } catch (err) {
+      console.log(err);
+      Message.show({
+        address: "Error",
+        body: "Problem with update file metadata",
+        color: "#cc1100",
+      });
+    }
+  }
+
+  annotation_editor_update_region_metadata(
+    img_index_list,
+    region_id,
+    attr_id,
+    new_value,
+    new_checked
+  ) {
+    try {
+      let i, n, img_id, img_index;
+      n = img_index_list.length;
+      let update_count = 0;
+      let j, m;
+
+      if (region_id === -1) {
+        // update all regions on a file (for image grid view)
+        for (i = 0; i < n; ++i) {
+          img_index = img_index_list[i];
+          img_id = _via_image_id_list[img_index];
+
+          m = _via_img_metadata[img_id].regions.length;
+          for (j = 0; j < m; ++j) {
+            if (
+              !image_grid_is_region_in_current_group(
+                _via_img_metadata[img_id].regions[j].region_attributes
+              )
+            ) {
+              continue;
+            }
+
+            switch (project.attributes.region[attr_id].type) {
+              case "text": // fallback
+              case "dropdown": // fallback
+              case "radio": // fallback
+              case "image":
+                _via_img_metadata[img_id].regions[j].region_attributes[
+                  attr_id
+                ] = new_value;
+                update_count += 1;
+                break;
+              case "checkbox":
+                let option_id = new_value;
+                if (
+                  _via_img_metadata[img_id].regions[
+                    j
+                  ].region_attributes.hasOwnProperty(attr_id)
+                ) {
+                  if (
+                    typeof _via_img_metadata[img_id].regions[j]
+                      .region_attributes[attr_id] !== "object"
+                  ) {
+                    let old_value =
+                      _via_img_metadata[img_id].regions[j].region_attributes[
+                      attr_id
+                      ];
+                    _via_img_metadata[img_id].regions[j].region_attributes[
+                      attr_id
+                    ] = {};
+                    if (
+                      Object.keys(
+                        project.attributes.region[attr_id]["options"]
+                      ).includes(old_value)
+                    ) {
+                      // transform existing value as checkbox option
+                      _via_img_metadata[img_id].regions[j].region_attributes[
+                        attr_id
+                      ][old_value] = true;
+                    }
+                  }
+                } else {
+                  _via_img_metadata[img_id].regions[j].region_attributes[
+                    attr_id
+                  ] = {};
+                }
+
+                if (new_checked) {
+                  _via_img_metadata[img_id].regions[j].region_attributes[
+                    attr_id
+                  ][option_id] = true;
+                } else {
+                  // false option values are not stored
+                  delete _via_img_metadata[img_id].regions[j].region_attributes[
+                    attr_id
+                  ][option_id];
+                }
+                update_count += 1;
+                break;
+            }
+          }
+        }
+      } else {
+        // update a single region in a file (for single image view)
+        // update all regions on a file (for image grid view)
+        for (i = 0; i < n; ++i) {
+          img_index = img_index_list[i];
+          img_id = _via_image_id_list[img_index];
+
+          switch (project.attributes.region[attr_id].type) {
+            case "text": // fallback
+            case "dropdown": // fallback
+            case "radio": // fallback
+            case "image":
+              _via_img_metadata[img_id].regions[region_id].region_attributes[
+                attr_id
+              ] = new_value;
+              update_count += 1;
+              break;
+            case "checkbox":
+              let option_id = new_value;
+
+              if (
+                _via_img_metadata[img_id].regions[
+                  region_id
+                ].region_attributes.hasOwnProperty(attr_id)
+              ) {
+                if (
+                  typeof _via_img_metadata[img_id].regions[region_id]
+                    .region_attributes[attr_id] !== "object"
+                ) {
+                  let old_value =
+                    _via_img_metadata[img_id].regions[region_id]
+                      .region_attributes[attr_id];
+                  _via_img_metadata[img_id].regions[
+                    region_id
+                  ].region_attributes[attr_id] = {};
+                  if (
+                    Object.keys(
+                      project.attributes.region[attr_id]["options"]
+                    ).includes(old_value)
+                  ) {
+                    // transform existing value as checkbox option
+                    _via_img_metadata[img_id].regions[
+                      region_id
+                    ].region_attributes[attr_id][old_value] = true;
+                  }
+                }
+              } else {
+                _via_img_metadata[img_id].regions[region_id].region_attributes[
+                  attr_id
+                ] = {};
+              }
+
+              if (new_checked) {
+                _via_img_metadata[img_id].regions[region_id].region_attributes[
+                  attr_id
+                ][option_id] = true;
+              } else {
+                // false option values are not stored
+                delete _via_img_metadata[img_id].regions[region_id]
+                  .region_attributes[attr_id][option_id];
+              }
+              update_count += 1;
+              break;
+          }
+        }
+      }
+      return update_count;
+    } catch (err) {
+      console.log(err);
+      Message.show({
+        address: "Error",
+        body: "Problem with update region metadata",
+        color: "#cc1100",
+      });
+    }
+  }
+
+  set_region_annotations_to_default_value(rid) {
+    let attr_id;
+    for (attr_id in project.attributes.region) {
+      let attr_type = project.attributes.region[attr_id].type;
+      switch (attr_type) {
+        case "text":
+          let default_value = project.attributes.region[attr_id].default_value;
+          if (default_value !== undefined) {
+            _via_img_metadata[_via_image_id].regions[rid].region_attributes[
+              attr_id
+            ] = default_value;
+          }
+          break;
+        case "image": // fallback
+        case "dropdown": // fallback
+        case "radio": {
+          _via_img_metadata[_via_image_id].regions[rid].region_attributes[
+            attr_id
+          ] = "";
+          let default_options =
+            project.attributes.region[attr_id].default_options;
+          if (typeof default_options !== "undefined") {
+            _via_img_metadata[_via_image_id].regions[rid].region_attributes[
+              attr_id
+            ] = Object.keys(default_options)[0];
+          }
+          break;
+        }
+        case "checkbox": {
+          _via_img_metadata[_via_image_id].regions[rid].region_attributes[
+            attr_id
+          ] = {};
+          let default_options =
+            project.attributes.region[attr_id].default_options;
+          if (default_options !== undefined) {
+            let option_id;
+            for (option_id in default_options) {
+              let default_value = default_options[option_id];
+              if (default_value !== undefined) {
+                _via_img_metadata[_via_image_id].regions[rid].region_attributes[
+                  attr_id
+                ][option_id] = default_value;
+              }
+            }
+          }
+          break;
+        }
+      }
+    }
+  }
+
+  set_file_annotations_to_default_value(image_id) {
+    let attr_id;
+    for (attr_id in project.attributes.file) {
+      let attr_type = project.attributes.file[attr_id].type;
+      switch (attr_type) {
+        case "text": {
+          _via_img_metadata[image_id].file_attributes[attr_id] =
+            project.attributes.file[attr_id].default_value;
+          break;
+        }
+        case "image": // fallback
+        case "dropdown": // fallback
+        case "radio": {
+          _via_img_metadata[image_id].file_attributes[attr_id] = "";
+          let default_options =
+            project.attributes.file[attr_id].default_options;
+          _via_img_metadata[image_id].file_attributes[attr_id] =
+            Object.keys(default_options)[0];
+          break;
+        }
+        case "checkbox": {
+          _via_img_metadata[image_id].file_attributes[attr_id] = {};
+          let default_options =
+            project.attributes.file[attr_id].default_options;
+          let option_id;
+          for (option_id in default_options) {
+            _via_img_metadata[image_id].file_attributes[attr_id][option_id] =
+              default_options[option_id];
+          }
+          break;
+        }
+      }
+    }
+  }
+
+  toggleAEMode(disable = false) {
+    if (_via_metadata_being_updated === "region") {
+      this.edit_file_metadata_in_annotation_editor();
+      $("#sidebar_ToggleAEModes").prop("disabled", disable);
+      $("#sidebar_ToggleAEModes").html("Files");
+    } else {
+      this.edit_region_metadata_in_annotation_editor();
+      if (disable) {
+        $("#sidebar_ToggleAEModes").prop("disabled", true);
+      } else {
+        $("#sidebar_ToggleAEModes").prop("disabled", false);
+      }
+      $("#sidebar_ToggleAEModes").html("Regions");
+    }
+  }
+}
+const sidebar = new Sidebar();;// draw.js
+class Drawer {
+  constructor() {
+    //History -> _via_reg_canvas = document.getElementById("region_canvas");
+    this.ctx = _via_reg_canvas.getContext("2d");
+
+    //Init mouse handlers
+    this.initMouseHandlers();
+
+    //Mouse click position
+    this.click0 = { x: 0, y: 0 };
+    this.click1 = { x: 0, y: 0 };
+    this.current_point = { x: 0, y: 0 };
+    this.region_click = { x: 0, y: 0 };
+
+    //Colors for regions
+    this.theme_control_point_color = "#ff0000";
+    this.selected_region_boundary_line_color = "#ffc300";
+    this.selected_region_fill_color = "#808080";
+    this.region_boundary_line_color = "#000000";
+    this.region_fill_color = "#ffc300";
+    this.region_color_list = [
+      "#e69f00",
+      "#56b4e9",
+      "#009e73",
+      "#d55e00",
+      "#cc79a7",
+      "#f0e442",
+      "#ffffff",
+    ];
+    this.canvas_regions_group_color = {};
+
+    //Region edge tolerance
+    this.region_edge = [-1, -1];
+    this.polygon_vertex_match_tol = 10;
+    this.ellipse_edge_tol = 0.2;
+    this.theta_tol = Math.PI;
+    this.region_edge_tol = 5;
+    this.mouse_click_tol = 2;
+
+    //Current shape (default: editor mode)
+    this.current_shape = VIA_REGION_SHAPE.EDIT;
+
+    //Trimming
+    this.trim_points = {};
+    this.trim_region_id = -1;
+    this.trim_line = {};
+    this.trim_choose_points = [];
+    this.trim_choose_points_id = [];
+    this.trim_phase_id = 0;
+
+    //User interaction flags
+    this.is_user_moving_region = false;
+    this.is_user_drawing_region = false;
+    this.is_user_triming_region = false;
+    this.is_region_selected = false;
+    this.is_user_drawing_polygon = false;
+    this.is_user_resizing_region = false;
+    this.is_window_resized = false;
+
+    //Current region id-s
+    this.user_sel_region_id = -1;
+    this.current_polygon_region_id = -1;
+
+    //Etc drawing parameters
+    this.polygon_resize_vertex_offset = 100;
+    this.theme_region_boundary_width = this.setBoundarySize();
+    this.region_shapes_points_radius = 3;
+    this.theme_sel_region_opacity = 0.5;
+    this.region_point_radius = 3;
+    this.region_point_radius_default = 3;
+    this.current_browser = this.getBrowser();
+  }
+
+  setFontSize(size) {
+    let fontSize = parseInt(
+      window.getComputedStyle(document.body).getPropertyValue("font-size")
+    );
+    fontSize += size;
+    this.ctx.font = "bold " + fontSize + "px Arial";
+  }
+
+  setBoundarySize(size = 3) {
+    this.theme_region_boundary_width = size;
+  }
+
+  getBrowser() {
+    let ua = navigator.userAgent,
+      tem,
+      M =
+        ua.match(
+          /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i
+        ) || [];
+    if (/trident/i.test(M[1])) {
+      tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
+      return "IE " + (tem[1] || "");
+    }
+    if (M[1] === "Chrome") {
+      tem = ua.match(/\bOPR\/(\d+)/);
+      if (tem != null) {
+        return "Opera " + tem[1];
+      }
+    }
+    M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];
+    if ((tem = ua.match(/version\/(\d+)/i)) != null) {
+      M.splice(1, 1, tem[1]);
+    }
+
+    return M[0];
+  }
+
+  //--------------------------------------------
+  // Setters and getters
+  //--------------------------------------------
+
+  setCurrentShape(shape) {
+    this.current_shape = shape;
+  }
+
+  currentShape() {
+    return this.current_shape;
+  }
+
+  setIsUserDrawingPolygon(state) {
+    this.is_user_drawing_polygon = state;
+  }
+
+  isUserDrawingPolygon() {
+    return this.is_user_drawing_polygon;
+  }
+
+  setCurrentPolygonRegionId(state) {
+    this.current_polygon_region_id = state;
+  }
+
+  currentPolygonRegionId() {
+    return this.current_polygon_region_id;
+  }
+
+  setIsRegionSelected(state) {
+    this.is_region_selected = state;
+  }
+
+  isRegionSelected() {
+    return this.is_region_selected;
+  }
+
+  setIsUserResizingRegion(state) {
+    this.is_user_resizing_region = state;
+  }
+
+  isUserResizingRegion() {
+    return this.is_user_resizing_region;
+  }
+
+  userSelRegionId() {
+    return this.user_sel_region_id;
+  }
+
+  setUserSelRegionId(id) {
+    this.user_sel_region_id = id;
+  }
+
+  isUserDrawingRegion() {
+    return this.is_user_drawing_region;
+  }
+
+  setIsUserDrawingRegion(state) {
+    this.is_user_drawing_region = state;
+  }
+
+  isUserMovingRegion() {
+    return this.is_user_moving_region;
+  }
+
+  setIsUserMovingRegion(state) {
+    this.is_user_moving_region = state;
+  }
+
+  regionEdgeTol() {
+    return this.region_edge_tol;
+  }
+
+  //--------------------------------------------
+  // Canvas CTX helpers
+  //--------------------------------------------
+
+  lineStyle(
+    width = this.theme_region_boundary_width / 2,
+    color = this.region_fill_color
+  ) {
+    this.ctx.strokeStyle = color;
+    this.ctx.lineWidth = width;
+  }
+
+  beginFill(
+    color = this.selected_region_fill_color,
+    alpha = this.theme_sel_region_opacity
+  ) {
+    this.ctx.fillStyle = color;
+    this.ctx.globalAlpha = alpha;
+  }
+
+  //--------------------------------------------
+  // Auto colors for regions (for each group)
+  //--------------------------------------------
+
+  regionsGroupColorInit() {
+    this.canvas_regions_group_color = {};
+    let aid = _via_settings.ui.image.region_color;
+    if (aid !== "__via_default_region_color__") {
+      let avalue;
+      for (const element of _via_img_metadata[_via_image_id].regions) {
+        if (element.region_attributes.hasOwnProperty(aid)) {
+          avalue = element.region_attributes[aid];
+          if (!this.canvas_regions_group_color.hasOwnProperty(avalue)) {
+            this.canvas_regions_group_color[avalue] =
+              this.region_color_list[
+              Object.keys(this.canvas_regions_group_color).length
+              ];
+          }
+        }
+      }
+      let color_index = 0;
+      for (avalue in this.canvas_regions_group_color) {
+        this.canvas_regions_group_color[avalue] =
+          this.region_color_list[color_index % this.region_color_list.length];
+        color_index = color_index + 1;
+      }
+    }
+  }
+
+  //--------------------------------------------
+  // Image click handlers
+  //--------------------------------------------
+
+  /**
+   * Handle mouse click events on the canvas
+   */
+  initMouseHandlers() {
+    //canvas click, also for pan
+    _via_reg_canvas.addEventListener(
+      "pointerdown",
+      this.canvasMouseDownHandler.bind(this),
+      false
+    );
+    _via_reg_canvas.addEventListener(
+      "pointerup",
+      this.canvasMouseUpHandler.bind(this),
+      false
+    );
+    _via_reg_canvas.addEventListener(
+      "pointermove",
+      this.canvasMouseMoveHandler.bind(this),
+      false
+    );
+    _via_reg_canvas.addEventListener(
+      "pointerover",
+      this.canvasMouseOverHandler.bind(this),
+      false
+    );
+    _via_reg_canvas.addEventListener(
+      "contextmenu",
+      this.canvasContextMenuHandler.bind(this),
+      false
+    );
+  }
+
+  /**
+   * Handle mouse down event
+   * Detect if user is drawing a new region or moving an existing region or trimming
+   * @param e event
+   */
+  canvasMouseDownHandler(e) {
+    e.stopPropagation();
+    zoom.handleMoseDown(e);
+    if (this.current_shape === VIA_REGION_SHAPE.DRAG || is_alt_pressed || e.button === 2) {
+      return;
+    }
+    if (this.current_shape === VIA_REGION_SHAPE.TRIM) {
+      this.is_user_triming_region = true;
+    } else {
+      this.is_user_triming_region = false;
+      this.destroyTrim();
+    }
+    this.click0.x = e.offsetX;
+    this.click0.y = e.offsetY;
+
+    if (this.trim_phase_id === 1) {
+      this.trim_line["x0"] = e.offsetX;
+      this.trim_line["y0"] = e.offsetY;
+      return;
+    }
+    if (this.trim_phase_id === 4) {
+      return;
+    }
+
+    this.region_edge = this.isOnRegionCorner(this.click0);
+    let region_id = this.isInsideRegion(this.click0);
+    if (this.is_region_selected) {
+      // check if user clicked on the region boundary
+      if (this.region_edge[1] > 0) {
+        if (!this.is_user_resizing_region) {
+          if (this.region_edge[0] !== this.user_sel_region_id) {
+            this.user_sel_region_id = this.region_edge[0];
+          } // resize region
+          this.is_user_resizing_region = true;
+        }
+      } else {
+        let yes = this.isInsideThisRegion(this.click0, this.user_sel_region_id);
+
+        if (yes) {
+          if (!this.is_user_moving_region) {
+            this.is_user_moving_region = true;
+            this.region_click.x = this.click0.x;
+            this.region_click.y = this.click0.y;
+          }
+        }
+
+        if (region_id === -1) {
+          // mousedown on outside any region
+          this.is_user_drawing_region = true; //modSote
+          // unselect all regions
+          this.is_region_selected = false;
+          this.user_sel_region_id = -1;
+          toggle_all_regions_selection(false);
+        }
+      }
+    } else {
+      if (region_id === -1) {
+        // mousedown outside a region
+        if (
+          this.current_shape !== VIA_REGION_SHAPE.POLYGON &&
+          this.current_shape !== VIA_REGION_SHAPE.POLYLINE &&
+          this.current_shape !== VIA_REGION_SHAPE.POINT
+        ) {
+          // this is a bounding box drawing event
+          this.is_user_drawing_region = true;
+        }
+      } else {
+        // mousedown inside a region
+        // this could lead to (1) region selection or (2) region drawing
+        this.is_user_drawing_region = true;
+      }
+    }
+  }
+
+  /**
+   * Handle mouse up event
+   * It fires up the region drawing event and region selection event
+   * It also calculates the region attributes
+   * @param e event
+   */
+  canvasMouseUpHandler(e) {
+    e.stopPropagation();
+    zoom.handleMoseUp(e);
+    if (this.current_shape === VIA_REGION_SHAPE.DRAG || is_alt_pressed || e.button === 2) {
+      return;
+    }
+    this.click1.x = e.offsetX;
+    this.click1.y = e.offsetY;
+
+    if (this.trim_phase_id === 1) {
+      this.trim_line["x1"] = e.offsetX;
+      this.trim_line["y1"] = e.offsetY;
+      this.trim_phase_id = 2;
+      this.calculateTrimPoints();
+      return;
+    }
+
+    if (this.trim_phase_id === 4) {
+      if (this.isInsideTrimPolygon(this.click1, this.trim_points["polygon1"])) {
+        this.trimFromMetadata(this.trim_points["polygon1"]);
+      }
+      if (this.isInsideTrimPolygon(this.click1, this.trim_points["polygon2"])) {
+        this.trimFromMetadata(this.trim_points["polygon2"]);
+      }
+    }
+
+    let click_dx = Math.abs(this.click1.x - this.click0.x);
+    let click_dy = Math.abs(this.click1.y - this.click0.y);
+
+    if (this.is_user_moving_region) {
+      this.is_user_moving_region = false;
+      _via_reg_canvas.style.cursor = "default";
+
+      let move_x = Math.round(this.click1.x - this.region_click.x);
+      let move_y = Math.round(this.click1.y - this.region_click.y);
+
+      if (
+        Math.abs(move_x) > this.mouse_click_tol ||
+        Math.abs(move_y) > this.mouse_click_tol
+      ) {
+        // move all selected regions
+        this.moveSelectedRegions(move_x, move_y);
+      } else {
+        // indicates a user click on an already selected region
+        // this could indicate the user's intention to select another
+        // nested region within this region
+        // OR
+        // draw a nested region (i.e. region inside a region)
+        // traverse the canvas regions in alternating ascending
+        // and descending order to solve the issue of nested regions
+
+        let nested_region_id = this.isInsideRegion(this.click0, true);
+
+        if (
+          nested_region_id >= 0 &&
+          nested_region_id !== this.user_sel_region_id
+        ) {
+          this.user_sel_region_id = nested_region_id;
+          this.is_region_selected = true;
+          this.is_user_moving_region = false; // de-select all other regions if the user has not pressed Shift
+          if (this.is_user_triming_region) {
+            this.trimRegionSection(nested_region_id);
+          }
+
+          if (!e.shiftKey) {
+            toggle_all_regions_selection(false);
+          }
+
+          set_region_select_state(nested_region_id, true);
+          sidebar.annotation_editor_show();
+        } else {
+          // user clicking inside an already selected region
+          // indicates that the user intends to draw a nested region
+          toggle_all_regions_selection(false);
+          this.is_region_selected = false;
+
+          switch (this.current_shape) {
+            case VIA_REGION_SHAPE.POLYLINE: // handled by case for POLYGON
+
+            case VIA_REGION_SHAPE.POLYGON:
+              // user has clicked on the first point in a new polygon
+              // see also event 'mouseup' for this.is_user_drawing_polygon=true
+              this.is_user_drawing_polygon = true;
+              let canvas_polygon_region = new File_Region();
+              canvas_polygon_region.shape_attributes["name"] =
+                this.current_shape;
+              canvas_polygon_region.shape_attributes["all_points_x"] = [
+                Math.round(this.click0.x),
+              ];
+              canvas_polygon_region.shape_attributes["all_points_y"] = [
+                Math.round(this.click0.y),
+              ];
+
+              let new_length = _via_canvas_regions.push(canvas_polygon_region);
+
+              this.current_polygon_region_id = new_length - 1;
+              break;
+
+            case VIA_REGION_SHAPE.POINT:
+              // user has marked a landmark point
+              let point_region = new File_Region();
+              point_region.shape_attributes["name"] = VIA_REGION_SHAPE.POINT;
+              point_region.shape_attributes["cx"] = Math.round(
+                this.click0.x * _via_canvas_scale
+              );
+              point_region.shape_attributes["cy"] = Math.round(
+                this.click0.y * _via_canvas_scale
+              );
+
+              _via_img_metadata[_via_image_id].regions.push(point_region);
+
+              let canvas_point_region = new File_Region();
+              canvas_point_region.shape_attributes["name"] =
+                VIA_REGION_SHAPE.POINT;
+              canvas_point_region.shape_attributes["cx"] = Math.round(
+                this.click0.x
+              );
+              canvas_point_region.shape_attributes["cy"] = Math.round(
+                this.click0.y
+              );
+
+              _via_canvas_regions.push(canvas_point_region);
+
+              break;
+          }
+
+          sidebar.annotation_editor_update_content();
+        }
+      }
+
+      this.redrawRegCanvas();
+
+      _via_reg_canvas.focus();
+
+      return;
+    } // indicates that user has finished resizing a region
+
+    if (this.is_user_resizing_region) {
+      // _via_click(x0,y0) to _via_click(x1,y1)
+      this.is_user_resizing_region = false;
+      _via_reg_canvas.style.cursor = "default"; // update the region
+
+      let region_id = this.region_edge[0];
+      let image_attr =
+        _via_img_metadata[_via_image_id].regions[region_id].shape_attributes;
+      let canvas_attr = _via_canvas_regions[region_id].shape_attributes;
+      if (
+        _via_img_metadata[_via_image_id].regions[region_id].hasOwnProperty(
+          "score"
+        )
+      ) {
+        _via_img_metadata[_via_image_id].regions[region_id].score = "n/a";
+        plugin.updateScoresBasedOnGroup()
+      }
+
+      if (!this.is_user_triming_region) {
+        switch (canvas_attr["name"]) {
+          case VIA_REGION_SHAPE.RECT: {
+            let d = [canvas_attr["x"], canvas_attr["y"], 0, 0];
+            d[2] = d[0] + canvas_attr["width"];
+            d[3] = d[1] + canvas_attr["height"];
+
+            let mx = this.current_point.x;
+            let my = this.current_point.y;
+
+            let preserve_aspect_ratio = false; // constrain (mx,my) to lie on a line connecting a diagonal of rectangle
+
+            if (_via_is_ctrl_pressed) {
+              preserve_aspect_ratio = true;
+            }
+
+            this.rectUpdateCorner(
+              this.region_edge[1],
+              d,
+              mx,
+              my,
+              preserve_aspect_ratio
+            );
+            this.rectStandardizeCoordinates(d);
+            let w = Math.abs(d[2] - d[0]);
+            let h = Math.abs(d[3] - d[1]);
+            image_attr["x"] = Math.round(d[0] * _via_canvas_scale);
+            image_attr["y"] = Math.round(d[1] * _via_canvas_scale);
+            image_attr["width"] = Math.round(w * _via_canvas_scale);
+            image_attr["height"] = Math.round(h * _via_canvas_scale);
+            canvas_attr["x"] = Math.round(image_attr["x"] / _via_canvas_scale);
+            canvas_attr["y"] = Math.round(image_attr["y"] / _via_canvas_scale);
+            canvas_attr["width"] = Math.round(
+              image_attr["width"] / _via_canvas_scale
+            );
+            canvas_attr["height"] = Math.round(
+              image_attr["height"] / _via_canvas_scale
+            );
+            break;
+          }
+          case VIA_REGION_SHAPE.CIRCLE: {
+            let dx = Math.abs(canvas_attr["cx"] - this.current_point.x);
+            let dy = Math.abs(canvas_attr["cy"] - this.current_point.y);
+            let new_r = Math.sqrt(dx * dx + dy * dy);
+            image_attr["r"] = fixfloat(new_r * _via_canvas_scale);
+            canvas_attr["r"] = Math.round(image_attr["r"] / _via_canvas_scale);
+            break;
+          }
+          case VIA_REGION_SHAPE.ELLIPSE: {
+            let new_rx = canvas_attr["rx"];
+            let new_ry = canvas_attr["ry"];
+            let new_theta = canvas_attr["theta"];
+            let dx = Math.abs(canvas_attr["cx"] - this.current_point.x);
+            let dy = Math.abs(canvas_attr["cy"] - this.current_point.y);
+
+            switch (this.region_edge[1]) {
+              case 5:
+                new_ry = Math.sqrt(dx * dx + dy * dy);
+                new_theta = Math.atan2(
+                  -(this.current_point.x - canvas_attr["cx"]),
+                  this.current_point.y - canvas_attr["cy"]
+                );
+                break;
+
+              case 6:
+                new_rx = Math.sqrt(dx * dx + dy * dy);
+                new_theta = Math.atan2(
+                  this.current_point.y - canvas_attr["cy"],
+                  this.current_point.x - canvas_attr["cx"]
+                );
+                break;
+
+              default:
+                new_rx = dx;
+                new_ry = dy;
+                new_theta = 0;
+                break;
+            }
+
+            image_attr["rx"] = fixfloat(new_rx * _via_canvas_scale);
+            image_attr["ry"] = fixfloat(new_ry * _via_canvas_scale);
+            image_attr["theta"] = fixfloat(new_theta);
+            canvas_attr["rx"] = Math.round(
+              image_attr["rx"] / _via_canvas_scale
+            );
+            canvas_attr["ry"] = Math.round(
+              image_attr["ry"] / _via_canvas_scale
+            );
+            canvas_attr["theta"] = fixfloat(new_theta);
+            break;
+          }
+          case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+          case VIA_REGION_SHAPE.POLYGON: {
+            let moved_vertex_id =
+              this.region_edge[1] - this.polygon_resize_vertex_offset;
+
+            if (e.ctrlKey || e.metaKey) {
+              // if on vertex, delete it
+              // if on edge, add a new vertex
+              let r =
+                _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+              let shape = r.name;
+
+              let is_on_vertex = this.isOnPolygonVertex(
+                r["all_points_x"],
+                r["all_points_y"],
+                this.current_point
+              );
+
+              if (is_on_vertex === this.region_edge[1]) {
+                // click on vertex, hence delete vertex
+                if (this.polygonDelVertex(region_id, moved_vertex_id)) {
+                  show_message(
+                    "Deleted vertex " + moved_vertex_id + " from region"
+                  );
+                }
+              } else {
+                let is_on_edge = this.is_on_polygon_edge(
+                  r["all_points_x"],
+                  r["all_points_y"],
+                  this.current_point.x,
+                  this.current_point.y
+                );
+
+                // click on edge, hence add new vertex
+                let vertex_index =
+                  is_on_edge - this.polygon_resize_vertex_offset;
+
+                let canvas_x0 = Math.round(this.click1.x);
+                let canvas_y0 = Math.round(this.click1.y);
+                let img_x0 = Math.round(canvas_x0 * _via_canvas_scale);
+                let img_y0 = Math.round(canvas_y0 * _via_canvas_scale);
+                canvas_x0 = Math.round(img_x0 / _via_canvas_scale);
+                canvas_y0 = Math.round(img_y0 / _via_canvas_scale);
+
+                _via_canvas_regions[region_id].shape_attributes[
+                  "all_points_x"
+                ].splice(vertex_index + 1, 0, canvas_x0);
+
+                _via_canvas_regions[region_id].shape_attributes[
+                  "all_points_y"
+                ].splice(vertex_index + 1, 0, canvas_y0);
+
+                _via_img_metadata[_via_image_id].regions[
+                  region_id
+                ].shape_attributes["all_points_x"].splice(
+                  vertex_index + 1,
+                  0,
+                  img_x0
+                );
+
+                _via_img_metadata[_via_image_id].regions[
+                  region_id
+                ].shape_attributes["all_points_y"].splice(
+                  vertex_index + 1,
+                  0,
+                  img_y0
+                );
+
+                show_message("Added 1 new vertex to " + shape + " region");
+
+              }
+            } else {
+              // update coordinate of vertex
+              let imx = Math.round(this.current_point.x * _via_canvas_scale);
+              let imy = Math.round(this.current_point.y * _via_canvas_scale);
+              image_attr["all_points_x"][moved_vertex_id] = imx;
+              image_attr["all_points_y"][moved_vertex_id] = imy;
+              canvas_attr["all_points_x"][moved_vertex_id] = Math.round(
+                imx / _via_canvas_scale
+              );
+              canvas_attr["all_points_y"][moved_vertex_id] = Math.round(
+                imy / _via_canvas_scale
+              );
+            }
+
+            break;
+          }
+        } // end of switch()
+      }
+
+      this.redrawRegCanvas();
+
+      _via_reg_canvas.focus();
+
+      return;
+    } // denotes a single click (= mouse down + mouse up)
+
+    if (click_dx < this.mouse_click_tol || click_dy < this.mouse_click_tol) {
+      // if user is already drawing polygon, then each click adds a new point
+      if (this.is_user_drawing_polygon) {
+        let canvas_x0 = Math.round(this.click1.x);
+        let canvas_y0 = Math.round(this.click1.y);
+
+        let n =
+          _via_canvas_regions[this.current_polygon_region_id].shape_attributes[
+            "all_points_x"
+          ].length;
+
+        let last_x0 =
+          _via_canvas_regions[this.current_polygon_region_id].shape_attributes[
+          "all_points_x"
+          ][n - 1];
+        let last_y0 =
+          _via_canvas_regions[this.current_polygon_region_id].shape_attributes[
+          "all_points_y"
+          ][n - 1]; // discard if the click was on the last vertex
+
+        if (canvas_x0 !== last_x0 || canvas_y0 !== last_y0) {
+          // user clicked on a new polygon point
+          _via_canvas_regions[this.current_polygon_region_id].shape_attributes[
+            "all_points_x"
+          ].push(canvas_x0);
+
+          _via_canvas_regions[this.current_polygon_region_id].shape_attributes[
+            "all_points_y"
+          ].push(canvas_y0);
+        }
+      } else {
+        let region_id = this.isInsideRegion(this.click0);
+
+        if (region_id >= 0) {
+          // first click selects region
+          this.user_sel_region_id = region_id;
+          this.is_region_selected = true;
+          this.is_user_moving_region = false;
+          this.is_user_drawing_region = false; // de-select all other regions if the user has not pressed Shift
+
+          if (!e.shiftKey) {
+            sidebar.annotation_editor_highlight_row();
+            toggle_all_regions_selection(false);
+          }
+
+          set_region_select_state(region_id, true); // show annotation editor only when a single region is selected
+
+          if (!e.shiftKey) {
+            sidebar.annotation_editor_show();
+          } else {
+            sidebar.annotation_editor_hide();
+          } // show the region info
+
+          if (_via_is_region_info_visible) {
+            let canvas_attr = _via_canvas_regions[region_id].shape_attributes;
+
+            switch (canvas_attr["name"]) {
+              case VIA_REGION_SHAPE.RECT: {
+                break;
+              }
+              case VIA_REGION_SHAPE.CIRCLE: {
+                let rf = document.getElementById("region_info");
+                let attr =
+                  _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+                rf.innerHTML += "," + " Radius:" + attr["r"];
+                break;
+              }
+              case VIA_REGION_SHAPE.ELLIPSE: {
+                let rf = document.getElementById("region_info");
+                let attr =
+                  _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+                rf.innerHTML +=
+                  "," +
+                  " X-radius:" +
+                  attr["rx"] +
+                  "," +
+                  " Y-radius:" +
+                  attr["ry"];
+                break;
+              }
+              case VIA_REGION_SHAPE.POLYLINE:
+              case VIA_REGION_SHAPE.POLYGON: {
+                break;
+              }
+            }
+          }
+
+          if (this.is_user_triming_region) {
+            this.trimRegionSection(region_id);
+          }
+
+          show_message(
+            "Region selected. If you intended to draw a region, click again inside the selected region to start drawing a region."
+          );
+        } else {
+          if (this.is_user_drawing_region) {
+            // clear all region selection
+            this.is_user_drawing_region = false;
+            this.is_region_selected = false;
+            toggle_all_regions_selection(false);
+            sidebar.annotation_editor_hide();
+          } else {
+            switch (this.current_shape) {
+              case VIA_REGION_SHAPE.POLYLINE: // handled by case for POLYGON
+
+              case VIA_REGION_SHAPE.POLYGON: {
+                // user has clicked on the first point in a new polygon
+                // see also event 'mouseup' for this._via_is_user_moving_region=true
+                this.is_user_drawing_polygon = true;
+                let canvas_polygon_region = new File_Region();
+                canvas_polygon_region.shape_attributes["name"] =
+                  this.current_shape;
+                canvas_polygon_region.shape_attributes["all_points_x"] = [
+                  Math.round(this.click0.x),
+                ];
+                canvas_polygon_region.shape_attributes["all_points_y"] = [
+                  Math.round(this.click0.y),
+                ];
+
+                let new_length = _via_canvas_regions.push(
+                  canvas_polygon_region
+                );
+
+                this.current_polygon_region_id = new_length - 1;
+                break;
+              }
+              case VIA_REGION_SHAPE.POINT: {
+                // user has marked a landmark point
+                let point_region = new File_Region();
+                point_region.shape_attributes["name"] = VIA_REGION_SHAPE.POINT;
+                point_region.shape_attributes["cx"] = Math.round(
+                  this.click0.x * _via_canvas_scale
+                );
+                point_region.shape_attributes["cy"] = Math.round(
+                  this.click0.y * _via_canvas_scale
+                );
+
+                let region_count =
+                  _via_img_metadata[_via_image_id].regions.push(point_region);
+                let new_region_id = region_count - 1;
+                sidebar.set_region_annotations_to_default_value(new_region_id);
+
+                let canvas_point_region = new File_Region();
+                canvas_point_region.shape_attributes["name"] =
+                  VIA_REGION_SHAPE.POINT;
+                canvas_point_region.shape_attributes["cx"] = Math.round(
+                  this.click0.x
+                );
+                canvas_point_region.shape_attributes["cy"] = Math.round(
+                  this.click0.y
+                );
+
+                _via_canvas_regions.push(canvas_point_region);
+
+                sidebar.annotation_editor_update_content();
+                break;
+              }
+            }
+          }
+        }
+      }
+
+      this.redrawRegCanvas();
+
+      _via_reg_canvas.focus();
+
+      return;
+    } // indicates that user has finished drawing a new region
+
+    if (this.is_user_drawing_region) {
+      this.is_user_drawing_region = false;
+      let region_x0 = this.click0.x;
+      let region_y0 = this.click0.y;
+      let original_img_region = new File_Region();
+      let canvas_img_region = new File_Region();
+      let region_dx = Math.abs(this.click1.x - region_x0);
+      let region_dy = Math.abs(this.click1.y - region_y0);
+      let new_region_added = false;
+
+      if (region_dx > VIA_REGION_MIN_DIM && region_dy > VIA_REGION_MIN_DIM) {
+        // avoid regions with 0 dim
+        switch (this.current_shape) {
+          case VIA_REGION_SHAPE.RECT: {
+            // ensure that (x0,y0) is top-left and (x1,y1) is bottom-right
+            if (this.click0.x < this.click1.x) {
+              region_x0 = this.click0.x;
+            } else {
+              region_x0 = this.click1.x;
+            }
+
+            if (this.click0.y < this.click1.y) {
+              region_y0 = this.click0.y;
+            } else {
+              region_y0 = this.click1.y;
+            }
+
+            let x = Math.round(region_x0 * _via_canvas_scale);
+            let y = Math.round(region_y0 * _via_canvas_scale);
+            let width = Math.round(region_dx * _via_canvas_scale);
+            let height = Math.round(region_dy * _via_canvas_scale);
+            original_img_region.shape_attributes["name"] = "rect";
+            original_img_region.shape_attributes["x"] = x;
+            original_img_region.shape_attributes["y"] = y;
+            original_img_region.shape_attributes["width"] = width;
+            original_img_region.shape_attributes["height"] = height;
+            canvas_img_region.shape_attributes["name"] = "rect";
+            canvas_img_region.shape_attributes["x"] = Math.round(
+              x / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["y"] = Math.round(
+              y / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["width"] = Math.round(
+              width / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["height"] = Math.round(
+              height / _via_canvas_scale
+            );
+            new_region_added = true;
+            break;
+          }
+          case VIA_REGION_SHAPE.CIRCLE: {
+            let cx = Math.round(region_x0 * _via_canvas_scale);
+            let cy = Math.round(region_y0 * _via_canvas_scale);
+            let r = Math.round(
+              Math.sqrt(region_dx * region_dx + region_dy * region_dy) *
+              _via_canvas_scale
+            );
+            original_img_region.shape_attributes["name"] = "circle";
+            original_img_region.shape_attributes["cx"] = cx;
+            original_img_region.shape_attributes["cy"] = cy;
+            original_img_region.shape_attributes["r"] = r;
+            canvas_img_region.shape_attributes["name"] = "circle";
+            canvas_img_region.shape_attributes["cx"] = Math.round(
+              cx / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["cy"] = Math.round(
+              cy / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["r"] = Math.round(
+              r / _via_canvas_scale
+            );
+            new_region_added = true;
+            break;
+          }
+          case VIA_REGION_SHAPE.ELLIPSE: {
+            let cx = Math.round(region_x0 * _via_canvas_scale);
+            let cy = Math.round(region_y0 * _via_canvas_scale);
+            let rx = Math.round(region_dx * _via_canvas_scale);
+            let ry = Math.round(region_dy * _via_canvas_scale);
+            let theta = 0;
+            original_img_region.shape_attributes["name"] = "ellipse";
+            original_img_region.shape_attributes["cx"] = cx;
+            original_img_region.shape_attributes["cy"] = cy;
+            original_img_region.shape_attributes["rx"] = rx;
+            original_img_region.shape_attributes["ry"] = ry;
+            original_img_region.shape_attributes["theta"] = theta;
+            canvas_img_region.shape_attributes["name"] = "ellipse";
+            canvas_img_region.shape_attributes["cx"] = Math.round(
+              cx / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["cy"] = Math.round(
+              cy / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["rx"] = Math.round(
+              rx / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["ry"] = Math.round(
+              ry / _via_canvas_scale
+            );
+            canvas_img_region.shape_attributes["theta"] = theta;
+            new_region_added = true;
+            break;
+          }
+          case VIA_REGION_SHAPE.REMOVE: {
+            // ensure that (x0,y0) is top-left and (x1,y1) is bottom-right
+            if (this.click0.x < this.click1.x) {
+              region_x0 = this.click0.x;
+            } else {
+              region_x0 = this.click1.x;
+            }
+
+            if (this.click0.y < this.click1.y) {
+              region_y0 = this.click0.y;
+            } else {
+              region_y0 = this.click1.y;
+            }
+
+            let xx = Math.round(region_x0 * _via_canvas_scale);
+            let yy = Math.round(region_y0 * _via_canvas_scale);
+            let widthh = Math.round(region_dx * _via_canvas_scale);
+            let heightt = Math.round(region_dy * _via_canvas_scale);
+            let canvx = Math.round(xx / _via_canvas_scale);
+            let canvy = Math.round(yy / _via_canvas_scale);
+            let canvw = Math.round(widthh / _via_canvas_scale);
+            let canvh = Math.round(heightt / _via_canvas_scale);
+            this.drawSelector(canvx, canvy, canvw, canvh); //handled by via_plugin
+
+            break;
+          }
+          case VIA_REGION_SHAPE.POINT: // handled by case VIA_REGION_SHAPE.POLYGON
+
+          case VIA_REGION_SHAPE.POLYLINE: // handled by case VIA_REGION_SHAPE.POLYGON
+
+          case VIA_REGION_SHAPE.POLYGON: {
+            // handled by this.is_user_drawing_polygon
+            break;
+          }
+        } // end of switch
+
+        if (new_region_added) {
+          let n1 =
+            _via_img_metadata[_via_image_id].regions.push(original_img_region);
+
+          let n2 = _via_canvas_regions.push(canvas_img_region);
+
+          if (n1 !== n2) {
+            console.log(
+              "_via_img_metadata.regions[" +
+              n1 +
+              "] and _via_canvas_regions[" +
+              n2 +
+              "] count mismatch"
+            );
+            Message.show({
+              address: "Error",
+              body: "Region count mismatch",
+              color: "#cc1100",
+            });
+          }
+
+          let new_region_id = n1 - 1;
+          sidebar.set_region_annotations_to_default_value(new_region_id);
+          select_only_region(new_region_id);
+
+          if (
+            _via_annotation_editor_mode ===
+            VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS &&
+            _via_metadata_being_updated === "region"
+          ) {
+            sidebar.annotation_editor_add_row(new_region_id);
+            sidebar.annotation_editor_scroll_to_row(new_region_id);
+            sidebar.annotation_editor_clear_row_highlight();
+
+            sidebar.annotation_editor_highlight_row(new_region_id);
+          }
+          sidebar.annotation_editor_show();
+        }
+
+        this.redrawRegCanvas();
+
+        _via_reg_canvas.focus();
+      } else {
+        show_message("Prevented accidental addition of a very small region.");
+      }
+    }
+  }
+
+  /**
+   * Handle mouse over event on the region canvas
+   * Redraws the region canvas with all the regions
+   * if the region canvas is visible and the image is loaded
+   */
+  canvasMouseOverHandler() {
+    // change the mouse cursor icon
+    if (this.is_user_drawing_polygon) {
+      _via_reg_canvas.style.cursor = "crosshair";
+    } else {
+      _via_reg_canvas.style.cursor = "default";
+    }
+    this.redrawRegCanvas(); //handled by via_plugin;
+    _via_reg_canvas.focus();
+  }
+
+  /**
+   * Handle mouse out event on the region canvas
+   * It draws the regions state when mouse downed, before mouseup event
+   * @param e event
+   */
+  canvasMouseMoveHandler(e) {
+    if (!buffer.imgLoaded) {
+      return;
+    }
+
+    zoom.handleMoseMove(e);
+
+    if (this.current_shape === VIA_REGION_SHAPE.DRAG || is_alt_pressed) {
+      return;
+    }
+
+    this.current_point.x = e.offsetX;
+    this.current_point.y = e.offsetY;
+
+    if (this.trim_phase_id === 1 && this.trim_line.hasOwnProperty("x0")) {
+      this.redrawRegCanvas();
+      this.drawLineRegion(
+        this.trim_line["x0"],
+        this.trim_line["y0"],
+        this.current_point.x,
+        this.current_point.y,
+        false
+      );
+      return;
+    }
+
+    let rf = document.getElementById("region_info");
+
+    if (rf != null && _via_is_region_info_visible) {
+      let img_x = Math.round(this.current_point.x * _via_canvas_scale);
+      let img_y = Math.round(this.current_point.y * _via_canvas_scale);
+
+      rf.innerHTML = "X:" + img_x + "," + " Y:" + img_y;
+    }
+
+    if (this.is_region_selected) {
+      // display the region's info if a region is selected
+      if (
+        rf != null &&
+        _via_is_region_info_visible &&
+        this.user_sel_region_id !== -1
+      ) {
+        let canvas_attr =
+          _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+
+        switch (canvas_attr["name"]) {
+          case VIA_REGION_SHAPE.RECT: {
+            break;
+          }
+          case VIA_REGION_SHAPE.CIRCLE: {
+            let rf = document.getElementById("region_info");
+            let attr =
+              _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+            rf.innerHTML += "," + " Radius:" + attr["r"];
+            break;
+          }
+          case VIA_REGION_SHAPE.ELLIPSE: {
+            let rf = document.getElementById("region_info");
+            let attr =
+              _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+            rf.innerHTML +=
+              "," + " X-radius:" + attr["rx"] + "," + " Y-radius:" + attr["ry"];
+            break;
+          }
+          case VIA_REGION_SHAPE.POLYLINE:
+          case VIA_REGION_SHAPE.POLYGON: {
+            break;
+          }
+        }
+      }
+
+      if (!this.is_user_resizing_region) {
+        // check if user moved mouse cursor to region boundary
+        // which indicates an intention to resize the region
+        this.region_edge = this.isOnRegionCorner(this.current_point);
+
+        if (this.region_edge[0] === this.user_sel_region_id) {
+          switch (this.region_edge[1]) {
+            // rect
+            case 1: // Fall-through // top-left corner of rect
+
+            case 3: {
+              // bottom-right corner of rect
+              _via_reg_canvas.style.cursor = "nwse-resize";
+              break;
+            }
+            case 2: // Fall-through // top-right corner of rect
+
+            case 4: {
+              // bottom-left corner of rect
+              _via_reg_canvas.style.cursor = "nesw-resize";
+              break;
+            }
+            case 5: // Fall-through // top-middle point of rect
+
+            case 7: {
+              // bottom-middle point of rect
+              _via_reg_canvas.style.cursor = "ns-resize";
+              break;
+            }
+            case 6: // Fall-through // top-middle point of rect
+
+            case 8: {
+              // bottom-middle point of rect
+              _via_reg_canvas.style.cursor = "ew-resize";
+              break;
+              // circle and ellipse
+            }
+            case 5: {
+              _via_reg_canvas.style.cursor = "n-resize";
+              break;
+            }
+            case 6: {
+              _via_reg_canvas.style.cursor = "e-resize";
+              break;
+            }
+            default: {
+              _via_reg_canvas.style.cursor = "default";
+              break;
+            }
+          }
+
+          if (this.region_edge[1] >= this.polygon_resize_vertex_offset) {
+            // indicates mouse over polygon vertex
+            _via_reg_canvas.style.cursor = "crosshair";
+            show_message(
+              "To move vertex, simply drag the vertex. To add vertex, press [Ctrl] key and click on the edge. To delete vertex, press [Ctrl] (or [Command]) key and click on vertex."
+            );
+          }
+        } else {
+          let yes = this.isInsideThisRegion(
+            this.current_point,
+            this.user_sel_region_id
+          );
+
+          if (yes) {
+            _via_reg_canvas.style.cursor = "move";
+          } else {
+            _via_reg_canvas.style.cursor = "default";
+          }
+        }
+      } else {
+        sidebar.annotation_editor_hide(); // resizing
+      }
+    }
+
+    if (this.is_user_drawing_region) {
+      // draw region as the user drags the mouse cursor
+      if (_via_canvas_regions.length) {
+        this.redrawRegCanvas(); // clear old intermediate rectangle
+      } else {
+        // first region being drawn, just clear the full region canvas
+        this.clearCanvas();
+      }
+
+      let region_x0 = this.click0.x;
+      let region_y0 = this.click0.y;
+      let dx = Math.round(Math.abs(this.current_point.x - this.click0.x));
+      let dy = Math.round(Math.abs(this.current_point.y - this.click0.y));
+
+      this.lineStyle();
+
+      switch (this.current_shape) {
+        case VIA_REGION_SHAPE.RECT: {
+          if (this.click0.x < this.current_point.x) {
+            if (this.click0.y < this.current_point.y) {
+              region_x0 = this.click0.x;
+              region_y0 = this.click0.y;
+            } else {
+              region_x0 = this.click0.x;
+              region_y0 = this.current_point.y;
+            }
+          } else {
+            if (this.click0.y < this.current_point.y) {
+              region_x0 = this.current_point.x;
+              region_y0 = this.click0.y;
+            } else {
+              region_x0 = this.current_point.x;
+              region_y0 = this.current_point.y;
+            }
+          }
+
+          this.drawRectRegion(region_x0, region_y0, dx, dy, false); // display the current region info
+
+          if (rf != null && _via_is_region_info_visible) {
+            rf.innerHTML += "," + " W:" + dx + "," + " H:" + dy;
+          }
+
+          break;
+        }
+        case VIA_REGION_SHAPE.CIRCLE: {
+          let circle_radius = Math.round(Math.sqrt(dx * dx + dy * dy));
+
+          this.drawCircleRegion(region_x0, region_y0, circle_radius, false); // display the current region info
+
+          if (rf != null && _via_is_region_info_visible) {
+            rf.innerHTML += "," + " Radius:" + circle_radius;
+          }
+
+          break;
+        }
+        case VIA_REGION_SHAPE.ELLIPSE: {
+          this.drawEllipseRegion(region_x0, region_y0, dx, dy, 0, false); // display the current region info
+
+          if (rf != null && _via_is_region_info_visible) {
+            rf.innerHTML +=
+              "," +
+              " X-radius:" +
+              fixfloat(dx) +
+              "," +
+              " Y-radius:" +
+              fixfloat(dy);
+          }
+
+          break;
+        }
+        case VIA_REGION_SHAPE.REMOVE: {
+          if (this.click0.x < this.current_point.x) {
+            if (this.click0.y < this.current_point.y) {
+              region_x0 = this.click0.x;
+              region_y0 = this.click0.y;
+            } else {
+              region_x0 = this.click0.x;
+              region_y0 = this.current_point.y;
+            }
+          } else {
+            if (this.click0.y < this.current_point.y) {
+              region_x0 = this.current_point.x;
+              region_y0 = this.click0.y;
+            } else {
+              region_x0 = this.current_point.x;
+              region_y0 = this.current_point.y;
+            }
+          }
+
+          this.lineStyle("", "#dc143c");
+
+          this.ctx.setLineDash([5, 3]);
+          this.drawRectRegion(region_x0, region_y0, dx, dy, false); // display the current region info
+          this.ctx.setLineDash([]);
+
+          if (rf != null && _via_is_region_info_visible) {
+            rf.innerHTML += "," + " W:" + dx + "," + " H:" + dy;
+          }
+
+          break;
+        }
+        case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+        case VIA_REGION_SHAPE.POLYGON: {
+          // this is handled by the if ( this.is_user_drawing_polygon ) { ... }
+          // see below
+          break;
+        }
+      }
+
+      _via_reg_canvas.focus();
+    }
+
+    if (this.is_user_resizing_region) {
+      // user has clicked mouse on bounding box edge and is now moving it
+      // draw region as the user drags the mouse coursor
+      if (_via_canvas_regions.length) {
+        this.redrawRegCanvas(); // clear old intermediate rectangle
+      } else {
+        // first region being drawn, just clear the full region canvas
+        this.clearCanvas();
+      }
+
+      let region_id = this.region_edge[0];
+      //if(_via_canvas_regions[region_id] === undefined || !_via_canvas_regions[region_id].hasOwnProperty("shape_attributes")) {
+      let attr = _via_canvas_regions[region_id].shape_attributes;
+      if (!this.is_user_triming_region) {
+        switch (attr["name"]) {
+          case VIA_REGION_SHAPE.RECT: {
+            // original rectangle
+            let d = [attr["x"], attr["y"], 0, 0];
+            d[2] = d[0] + attr["width"];
+            d[3] = d[1] + attr["height"];
+
+            let mx = this.current_point.x;
+            let my = this.current_point.y;
+            let preserve_aspect_ratio = false; // constrain (mx,my) to lie on a line connecting a diagonal of rectangle
+
+            if (_via_is_ctrl_pressed) {
+              preserve_aspect_ratio = true;
+            }
+
+            this.rectUpdateCorner(
+              this.region_edge[1],
+              d,
+              mx,
+              my,
+              preserve_aspect_ratio
+            );
+            this.rectStandardizeCoordinates(d);
+            let w = Math.abs(d[2] - d[0]);
+            let h = Math.abs(d[3] - d[1]);
+
+            this.drawRectRegion(d[0], d[1], w, h, true);
+
+            if (rf != null && _via_is_region_info_visible) {
+              rf.innerHTML += "," + " W:" + w + "," + " H:" + h;
+            }
+
+            break;
+          }
+          case VIA_REGION_SHAPE.CIRCLE: {
+            let dx = Math.abs(attr["cx"] - this.current_point.x);
+            let dy = Math.abs(attr["cy"] - this.current_point.y);
+            let new_r = Math.sqrt(dx * dx + dy * dy);
+
+            this.drawCircleRegion(attr["cx"], attr["cy"], new_r, true);
+
+            if (rf != null && _via_is_region_info_visible) {
+              let curr_texts = rf.innerHTML.split(",");
+              rf.innerHTML = "";
+              rf.innerHTML +=
+                curr_texts[0] +
+                "," +
+                curr_texts[1] +
+                "," +
+                " Radius:" +
+                Math.round(new_r);
+            }
+
+            break;
+          }
+          case VIA_REGION_SHAPE.ELLIPSE: {
+            let new_rx = attr["rx"];
+            let new_ry = attr["ry"];
+            let new_theta = attr["theta"];
+            let dx = Math.abs(attr["cx"] - this.current_point.x);
+            let dy = Math.abs(attr["cy"] - this.current_point.y);
+
+            switch (this.region_edge[1]) {
+              case 5:
+                new_ry = Math.sqrt(dx * dx + dy * dy);
+                new_theta = Math.atan2(
+                  -(this.current_point.x - attr["cx"]),
+                  this.current_point.y - attr["cy"]
+                );
+                break;
+
+              case 6:
+                new_rx = Math.sqrt(dx * dx + dy * dy);
+
+                new_theta = Math.atan2(
+                  this.current_point.y - attr["cy"],
+                  this.current_point.x - attr["cx"]
+                );
+
+                break;
+
+              default:
+                new_rx = dx;
+                new_ry = dy;
+                new_theta = 0;
+                break;
+            }
+
+            this.drawEllipseRegion(
+              attr["cx"],
+              attr["cy"],
+              new_rx,
+              new_ry,
+              new_theta,
+              true
+            );
+
+            if (rf != null && _via_is_region_info_visible) {
+              let curr_texts = rf.innerHTML.split(",");
+              rf.innerHTML = "";
+              rf.innerHTML =
+                curr_texts[0] +
+                "," +
+                curr_texts[1] +
+                "," +
+                " X-radius:" +
+                fixfloat(new_rx) +
+                "," +
+                " Y-radius:" +
+                fixfloat(new_ry);
+            }
+
+            break;
+          }
+          case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+          case VIA_REGION_SHAPE.POLYGON: {
+            let moved_all_points_x = attr["all_points_x"].slice(0);
+            let moved_all_points_y = attr["all_points_y"].slice(0);
+            let moved_vertex_id =
+              this.region_edge[1] - this.polygon_resize_vertex_offset;
+            moved_all_points_x[moved_vertex_id] = this.current_point.x;
+            moved_all_points_y[moved_vertex_id] = this.current_point.y;
+
+            this.drawPolygonRegion(
+              moved_all_points_x,
+              moved_all_points_y,
+              true,
+              attr["name"]
+            );
+
+            if (rf != null && _via_is_region_info_visible) {
+              rf.innerHTML += "," + " Vertices:" + attr["all_points_x"].length;
+            }
+
+            break;
+          }
+        }
+      }
+
+      _via_reg_canvas.focus();
+      //}
+    }
+
+    if (this.is_user_moving_region) {
+      // draw region as the user drags the mouse coursor
+      if (_via_canvas_regions.length) {
+        this.redrawRegCanvas(); // clear old intermediate rectangle
+      } else {
+        // first region being drawn, just clear the full region canvas
+        this.clearCanvas();
+      }
+
+      let move_x = this.current_point.x - this.region_click.x;
+      let move_y = this.current_point.y - this.region_click.y;
+      let attr = _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+
+      switch (attr["name"]) {
+        case VIA_REGION_SHAPE.RECT: {
+          this.drawRectRegion(
+            attr["x"] + move_x,
+            attr["y"] + move_y,
+            attr["width"],
+            attr["height"],
+            true
+          ); // display the current region info
+
+          if (rf != null && _via_is_region_info_visible) {
+            rf.innerHTML +=
+              "," + " W:" + attr["width"] + "," + " H:" + attr["height"];
+          }
+
+          break;
+        }
+        case VIA_REGION_SHAPE.CIRCLE: {
+          this.drawCircleRegion(
+            attr["cx"] + move_x,
+            attr["cy"] + move_y,
+            attr["r"],
+            true
+          );
+
+          break;
+        }
+        case VIA_REGION_SHAPE.ELLIPSE: {
+          if (typeof attr["theta"] === "undefined") {
+            attr["theta"] = 0;
+          }
+
+          this.drawEllipseRegion(
+            attr["cx"] + move_x,
+            attr["cy"] + move_y,
+            attr["rx"],
+            attr["ry"],
+            attr["theta"],
+            true
+          );
+
+          break;
+        }
+        case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+        case VIA_REGION_SHAPE.POLYGON: {
+          let moved_all_points_x = attr["all_points_x"].slice(0);
+          let moved_all_points_y = attr["all_points_y"].slice(0);
+
+          for (let i = 0; i < moved_all_points_x.length; ++i) {
+            moved_all_points_x[i] += move_x;
+            moved_all_points_y[i] += move_y;
+          }
+
+          this.drawPolygonRegion(
+            moved_all_points_x,
+            moved_all_points_y,
+            true,
+            attr["name"]
+          );
+
+          if (rf != null && _via_is_region_info_visible) {
+            rf.innerHTML += "," + " Vertices:" + attr["all_points_x"].length;
+          }
+
+          break;
+        }
+        case VIA_REGION_SHAPE.POINT: {
+          this.drawPointRegion(attr["cx"] + move_x, attr["cy"] + move_y, true);
+
+          break;
+        }
+      }
+
+      _via_reg_canvas.focus();
+
+      sidebar.annotation_editor_hide(); // moving
+
+      return;
+    }
+
+    if (this.is_user_drawing_polygon) {
+      if (_via_canvas_regions.length) {
+        this.redrawRegCanvas(); // clear old intermediate rectangle
+      } else {
+        // first region being drawn, just clear the full region canvas
+        this.clearCanvas();
+      }
+
+      // if(_via_canvas_regions[this.current_polygon_region_id] === undefined || _via_canvas_regions[this.current_polygon_region_id].hasOwnProperty("shape_attributes")) {
+      let attr =
+        _via_canvas_regions[this.current_polygon_region_id].shape_attributes;
+      let all_points_x = attr["all_points_x"];
+      let all_points_y = attr["all_points_y"];
+      let npts = all_points_x.length;
+
+      if (npts > 0) {
+        let line_x = [all_points_x.slice(npts - 1), this.current_point.x];
+        let line_y = [all_points_y.slice(npts - 1), this.current_point.y];
+
+        this.drawPolygonRegion(line_x, line_y, false, attr["name"]);
+      }
+
+      if (rf != null && _via_is_region_info_visible) {
+        rf.innerHTML += "," + " Vertices:" + npts;
+      }
+      //}
+    }
+  }
+
+  canvasContextMenuHandler(e) {
+    e.preventDefault();
+    if (this.is_user_drawing_polygon) {
+      _via_polyshape_finish_drawing();
+    }
+  }
+
+  //--------------------------------------------
+  // Move region
+  //--------------------------------------------
+
+  /**
+   * Move all selected regions by (dx,dy)
+   * @param move_x {number} - x offset
+   * @param move_y {number} - y offset
+   */
+  moveSelectedRegions(move_x, move_y) {
+    let i, n;
+    n = _via_canvas_regions.length;
+
+    for (i = 0; i < n; ++i) {
+      if (_via_region_selected_flag.has(i)) {
+        this.moveRegion(i, move_x, move_y);
+      }
+    }
+  }
+
+  /**
+   * Move region by (dx,dy)
+   * @param x {number} - x offset
+   * @param y {number} - y offset
+   * @param canvas_attr {object} - shape attributes of the region
+   * @returns {boolean} - true if region is moved, false otherwise
+   */
+  validateMoveRegion(x, y, canvas_attr) {
+    switch (canvas_attr["name"]) {
+      case VIA_REGION_SHAPE.RECT:
+        // left and top boundary check
+        if (x < 0 || y < 0) {
+          show_message("Region moved beyond image boundary. Resetting.");
+          return false;
+        } // right and bottom boundary check
+
+        if (
+          y + canvas_attr["height"] > _via_current_image_height ||
+          x + canvas_attr["width"] > _via_current_image_width
+        ) {
+          show_message("Region moved beyond image boundary. Resetting.");
+          return false;
+        }
+
+      // same validation for all
+
+      case VIA_REGION_SHAPE.CIRCLE:
+      case VIA_REGION_SHAPE.ELLIPSE:
+      case VIA_REGION_SHAPE.POINT:
+      case VIA_REGION_SHAPE.POLYLINE:
+      case VIA_REGION_SHAPE.POLYGON:
+        if (
+          x < 0 ||
+          y < 0 ||
+          x > _via_current_image_width ||
+          y > _via_current_image_height
+        ) {
+          show_message("Region moved beyond image boundary. Resetting.");
+          return false;
+        }
+    }
+    return true;
+  }
+
+  /**
+   * Move one region by (dx,dy)
+   * @param region_id {number} - id of the region to be moved
+   * @param move_x {number} - x offset
+   * @param move_y {number} - y offset
+   */
+  moveRegion(region_id, move_x, move_y) {
+    let image_attr =
+      _via_img_metadata[_via_image_id].regions[region_id].shape_attributes;
+    let canvas_attr = _via_canvas_regions[region_id].shape_attributes;
+
+    switch (canvas_attr["name"]) {
+      case VIA_REGION_SHAPE.RECT: {
+        let xnew = image_attr["x"] + Math.round(move_x * _via_canvas_scale);
+        let ynew = image_attr["y"] + Math.round(move_y * _via_canvas_scale);
+
+        let is_valid = this.validateMoveRegion(xnew, ynew, image_attr);
+
+        if (!is_valid) {
+          break;
+        }
+
+        image_attr["x"] = xnew;
+        image_attr["y"] = ynew;
+        canvas_attr["x"] = Math.round(image_attr["x"] / _via_canvas_scale);
+        canvas_attr["y"] = Math.round(image_attr["y"] / _via_canvas_scale);
+        break;
+      }
+      case VIA_REGION_SHAPE.CIRCLE: // Fall-through
+
+      case VIA_REGION_SHAPE.ELLIPSE: // Fall-through
+
+      case VIA_REGION_SHAPE.POINT: {
+        let cxnew = image_attr["cx"] + Math.round(move_x * _via_canvas_scale);
+        let cynew = image_attr["cy"] + Math.round(move_y * _via_canvas_scale);
+
+        let is_valid = this.validateMoveRegion(cxnew, cynew, image_attr);
+
+        if (!is_valid) {
+          break;
+        }
+
+        image_attr["cx"] = cxnew;
+        image_attr["cy"] = cynew;
+        canvas_attr["cx"] = Math.round(image_attr["cx"] / _via_canvas_scale);
+        canvas_attr["cy"] = Math.round(image_attr["cy"] / _via_canvas_scale);
+        break;
+      }
+      case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+      case VIA_REGION_SHAPE.POLYGON: {
+        let img_px = image_attr["all_points_x"];
+        let img_py = image_attr["all_points_y"];
+        let canvas_px = canvas_attr["all_points_x"];
+        let canvas_py = canvas_attr["all_points_y"]; // clone for reverting if valiation fails
+
+        let img_px_old = Object.assign({}, img_px);
+        let img_py_old = Object.assign({}, img_py); // validate move
+
+        for (let i = 0; i < img_px.length; ++i) {
+          let pxnew = img_px[i] + Math.round(move_x * _via_canvas_scale);
+          let pynew = img_py[i] + Math.round(move_y * _via_canvas_scale);
+
+          if (!this.validateMoveRegion(pxnew, pynew, image_attr)) {
+            img_px = img_px_old;
+            img_py = img_py_old;
+            break;
+          }
+        } // move points
+
+        for (let i = 0; i < img_px.length; ++i) {
+          img_px[i] = img_px[i] + Math.round(move_x * _via_canvas_scale);
+          img_py[i] = img_py[i] + Math.round(move_y * _via_canvas_scale);
+        }
+
+        for (let i = 0; i < canvas_px.length; ++i) {
+          canvas_px[i] = Math.round(img_px[i] / _via_canvas_scale);
+          canvas_py[i] = Math.round(img_py[i] / _via_canvas_scale);
+        }
+
+        break;
+      }
+    }
+  }
+
+  //--------------------------------------------
+  // Canvas update routines
+  //--------------------------------------------
+
+  /**
+   * Update canvas, by clearing it and redrawing all regions
+   */
+  redrawRegCanvas() {
+    if (buffer.imgLoaded) {
+      this.clearCanvas();
+      if (_via_canvas_regions.length > 0) {
+        if (_via_is_region_boundary_visible) {
+          this.drawAllRegions();
+        }
+
+        if (_via_is_region_id_visible) {
+          this.drawAllRegionId();
+        }
+      }
+    }
+  } // draw all regions
+
+  /**
+   *  Clear all regions from canvas
+   */
+  clearCanvas() {
+    this.ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height);
+  } // clear all regions from canvas
+
+  /**
+   *  Draw all regions
+   *  It iterates over all regions and calls draw...Region()
+   */
+  drawAllRegions() {
+    let aid = _via_settings.ui.image.region_color;
+    let attr, is_selected, avalue;
+
+    if (this.is_user_triming_region && this.trim_points !== null) {
+      this.lineStyle(this.theme_region_boundary_width + 1, "#ff5733");
+
+      this.ctx.setLineDash([5, 3]);
+      this.updateTrimPoints();
+      this.drawPolygonRegion(
+        this.trim_points["x"],
+        this.trim_points["y"],
+        false
+      );
+      this.ctx.setLineDash([]);
+    }
+
+    for (let i = 0; i < _via_canvas_regions.length; ++i) {
+      attr = _via_canvas_regions[i].shape_attributes;
+      is_selected = _via_region_selected_flag.has(i); // region stroke style may depend on attribute value
+
+      this.lineStyle();
+
+      if (
+        !this.is_user_drawing_polygon &&
+        aid !== "__via_default_region_color__"
+      ) {
+        if (
+          _via_img_metadata[_via_image_id].regions[i].region_attributes !==
+          undefined
+        ) {
+          avalue =
+            _via_img_metadata[_via_image_id].regions[i].region_attributes[aid];
+
+          if (this.canvas_regions_group_color.hasOwnProperty(avalue)) {
+            this.lineStyle(
+              this.theme_region_boundary_width / 2,
+              this.canvas_regions_group_color[avalue]
+            );
+          }
+        }
+      }
+
+      switch (attr["name"]) {
+        case VIA_REGION_SHAPE.RECT:
+          this.drawRectRegion(
+            attr["x"],
+            attr["y"],
+            attr["width"],
+            attr["height"],
+            is_selected
+          );
+
+          break;
+
+        case VIA_REGION_SHAPE.CIRCLE:
+          this.drawCircleRegion(attr["cx"], attr["cy"], attr["r"], is_selected);
+
+          break;
+
+        case VIA_REGION_SHAPE.ELLIPSE:
+          if (typeof attr["theta"] === "undefined") {
+            attr["theta"] = 0;
+          }
+
+          this.drawEllipseRegion(
+            attr["cx"],
+            attr["cy"],
+            attr["rx"],
+            attr["ry"],
+            attr["theta"],
+            is_selected
+          );
+
+          break;
+
+        case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+        case VIA_REGION_SHAPE.POLYGON:
+          this.drawPolygonRegion(
+            attr["all_points_x"],
+            attr["all_points_y"],
+            is_selected,
+            attr["name"]
+          );
+
+          break;
+
+        case VIA_REGION_SHAPE.POINT:
+          this.drawPointRegion(attr["cx"], attr["cy"], is_selected);
+
+          break;
+      }
+    }
+  } // draw all region
+
+  //--------------------------------------------
+  // Region draw functions
+  //--------------------------------------------
+
+  /**
+   * Draw circle point
+   * This is a helper function for client (e.g. it creates the circles on the rectangle corners)
+   * @param cx - x coordinate of circle center
+   * @param cy - y coordinate of circle center
+   */
+  drawControlPoint(cx, cy) {
+    this.ctx.beginPath();
+    this.ctx.arc(
+      cx,
+      cy,
+      this.region_shapes_points_radius,
+      0,
+      2 * Math.PI,
+      false
+    );
+    this.ctx.closePath();
+    this.ctx.fillStyle = this.theme_control_point_color;
+    this.ctx.fill();
+    this.ctx.globalAlpha = 1.0;
+  } // draw control point
+
+  /**
+   * Draw a rectangle region
+   * @param x - x coordinate of top-left corner
+   * @param y - y coordinate of top-left corner
+   * @param w - width of rectangle
+   * @param h - height of rectangle
+   * @param is_selected - true if this region is selected
+   */
+  drawRectRegion(x, y, w, h, is_selected) {
+    if (is_selected) {
+      this.lineStyle(
+        this.theme_region_boundary_width / 2,
+        this.selected_region_boundary_line_color
+      );
+      this.beginFill();
+
+      this.ctx.beginPath();
+      if (settings.is_highlight_region) {
+        this.ctx.fillRect(x, y, w, h);
+      }
+      this.ctx.globalAlpha = 1.0;
+      this.ctx.strokeRect(x, y, w, h);
+      this.ctx.closePath();
+
+      this.drawControlPoint(x, y);
+      this.drawControlPoint(x + w, y);
+      this.drawControlPoint(x, y + h);
+      this.drawControlPoint(x + w, y + h);
+      this.drawControlPoint(x + w / 2, y);
+      this.drawControlPoint(x + w / 2, y + h);
+      this.drawControlPoint(x, y + h / 2);
+      this.drawControlPoint(x + w, y + h / 2);
+    } else {
+      this.ctx.beginPath();
+      this.ctx.strokeRect(x, y, w, h);
+      this.ctx.closePath();
+    }
+  } // draw rect region
+
+  /**
+   * Draw a circle region
+   * @param cx - x coordinate of circle center
+   * @param cy - y coordinate of circle center
+   * @param r - radius of circle
+   * @param is_selected - true if this region is selected
+   */
+  drawCircleRegion(cx, cy, r, is_selected) {
+    if (is_selected) {
+      this.ctx.beginPath();
+      this.ctx.arc(cx, cy, r, 0, 2 * Math.PI, false);
+      this.ctx.closePath();
+
+      this.lineStyle(
+        this.theme_region_boundary_width / 2,
+        this.selected_region_boundary_line_color
+      );
+      this.beginFill();
+      this.ctx.stroke();
+      if (settings.is_highlight_region) {
+        this.ctx.fill();
+      }
+      this.drawControlPoint(cx + r, cy);
+    } else {
+      this.ctx.beginPath();
+      this.ctx.arc(cx, cy, r, 0, 2 * Math.PI, false);
+      this.ctx.closePath();
+      this.ctx.stroke();
+    }
+  } // draw circle region
+
+  /**
+   * Draw an ellipse region
+   * @param cx - x coordinate of ellipse center
+   * @param cy - y coordinate of ellipse center
+   * @param rx - x radius of ellipse
+   * @param ry - y radius of ellipse
+   * @param rr - rotation angle of ellipse
+   * @param is_selected - true if this region is selected
+   */
+  drawEllipseRegion(cx, cy, rx, ry, rr, is_selected) {
+    if (is_selected) {
+      this.lineStyle(
+        this.theme_region_boundary_width / 2,
+        this.selected_region_boundary_line_color
+      );
+      this.beginFill();
+
+      this.ctx.beginPath();
+      this.ctx.ellipse(cx, cy, rx, ry, rr, 0, 2 * Math.PI, false);
+      this.ctx.closePath();
+
+      this.ctx.stroke();
+      if (settings.is_highlight_region) {
+        this.ctx.fill();
+      }
+      this.drawControlPoint(cx + rx * Math.cos(rr), cy + rx * Math.sin(rr));
+      this.drawControlPoint(cx - rx * Math.cos(rr), cy - rx * Math.sin(rr));
+      this.drawControlPoint(cx + ry * Math.sin(rr), cy - ry * Math.cos(rr));
+      this.drawControlPoint(cx - ry * Math.sin(rr), cy + ry * Math.cos(rr));
+    } else {
+      this.ctx.beginPath();
+      this.ctx.ellipse(cx, cy, rx, ry, rr, 0, 2 * Math.PI, false);
+      this.ctx.closePath();
+
+      this.ctx.stroke();
+    }
+  } // draw ellipse region
+
+  /**
+   * Draw a polygon region
+   * @param x - array of x coordinates of polygon vertices
+   * @param y - array of y coordinates of polygon vertices
+   * @param is_selected - true if this region is selected
+   */
+  drawPolygonRegion(x, y, is_selected) {
+    if (x === undefined || y === undefined) {
+      return;
+    }
+
+    if (is_selected) {
+      this.lineStyle(
+        this.theme_region_boundary_width / 2,
+        this.selected_region_boundary_line_color
+      );
+      this.beginFill();
+      this.ctx.beginPath();
+      this.ctx.moveTo(x[0], y[0]);
+      for (let i = 1; i < x.length; i++) {
+        this.ctx.lineTo(x[i], y[i]);
+      }
+      if (this.is_user_drawing_polygon) {
+        this.ctx.lineTo(x[0], y[0]);
+      }
+      this.ctx.closePath();
+      this.ctx.stroke();
+      if (settings.is_highlight_region) {
+        this.ctx.fill();
+      }
+      for (let i = 0; i < x.length; i++) {
+        this.drawControlPoint(x[i], y[i]);
+      }
+    } else {
+      this.ctx.beginPath();
+      this.ctx.moveTo(x[0], y[0]);
+      for (let i = 1; i < x.length; i++) {
+        this.ctx.lineTo(x[i], y[i]);
+      }
+      if (this.is_user_drawing_polygon) {
+        this.ctx.lineTo(x[0], y[0]);
+      }
+      this.ctx.closePath();
+      this.ctx.stroke();
+    }
+  } // draw polygon region
+
+  /**
+   * Draw a point region
+   * @param x - x coordinate of point
+   * @param y - y coordinate of point
+   * @param is_selected - true if this region is selected
+   */
+  drawPointRegion(x, y, is_selected) {
+    if (is_selected) {
+      this.ctx.beginPath();
+      this.ctx.arc(x, y, this.region_point_radius, 0, 2 * Math.PI, false);
+      this.ctx.closePath();
+
+      this.lineStyle(
+        this.theme_region_boundary_width / 2,
+        this.selected_region_boundary_line_color
+      );
+      this.beginFill();
+      this.ctx.stroke();
+      if (settings.is_highlight_region) {
+        this.ctx.fill();
+      }
+      this.drawControlPoint(x, y);
+    } else {
+      this.ctx.beginPath();
+      this.ctx.arc(x, y, this.region_point_radius, 0, 2 * Math.PI, false);
+      this.ctx.closePath();
+
+      this.ctx.stroke();
+    }
+  } // draw point region
+
+  /**
+   * Draw line region
+   */
+
+  drawLineRegion(x1, y1, x2, y2, is_selected) {
+    if (is_selected) {
+      this.lineStyle(
+        this.theme_region_boundary_width / 2,
+        this.selected_region_boundary_line_color
+      );
+      this.beginFill();
+      this.ctx.beginPath();
+      this.ctx.moveTo(x1, y1);
+      this.ctx.lineTo(x2, y2);
+      this.ctx.closePath();
+      this.ctx.stroke();
+      if (settings.is_highlight_region) {
+        this.ctx.fill();
+      }
+      this.drawControlPoint(x1, y1);
+      this.drawControlPoint(x2, y2);
+    } else {
+      this.ctx.beginPath();
+      this.ctx.moveTo(x1, y1);
+      this.ctx.lineTo(x2, y2);
+      this.ctx.closePath();
+      this.ctx.stroke();
+    }
+  } // draw line region
+
+  //--------------------------------------------
+  // Region ID, label drawing
+  //--------------------------------------------
+
+  /**
+   * Draw a little rounded rectangle at the calculated position
+   * witch fill color and line color are the same as the region boundary
+   * And fill with a region id number
+   * It iterates over all regions and draw the region ID
+   */
+  drawAllRegionId() {
+    let aid = _via_settings.ui.image.region_color;
+    for (let i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i) {
+      let canvas_reg = _via_canvas_regions[i];
+      let bbox = this.getRegionBoundingBox(canvas_reg);
+      let x = bbox[0];
+      let y = bbox[1];
+      let w = Math.abs(bbox[2] - bbox[0]);
+      let char_width = Math.floor(this.ctx.measureText("M").width);
+      let char_height = 1.8 * char_width;
+      let annotation_str = (i + 1).toString();
+      let rattr =
+        _via_img_metadata[_via_image_id].regions[i].region_attributes[
+        _via_settings.ui.image.region_label
+        ];
+      if (_via_settings.ui.image.region_label !== "__via_region_id__") {
+        if (typeof rattr !== "undefined") {
+          switch (typeof rattr) {
+            case "string":
+              annotation_str = rattr;
+              break;
+            case "object":
+              annotation_str = Object.keys(rattr).join(",");
+              break;
+            default:
+              annotation_str = rattr;
+              break;
+          }
+        } else {
+          annotation_str = "undefined";
+        }
+      }
+      let bgnd_rect_width;
+      let strw = this.ctx.measureText(annotation_str).width;
+      if (strw > w) {
+        if (_via_settings.ui.image.region_label === "__via_region_id__") {
+          // region-id is always visible in full
+          bgnd_rect_width = strw + char_width;
+        } else {
+          // if text overflows, crop it
+          let str_max = Math.floor((w * annotation_str.length) / strw);
+          if (str_max > 1) {
+            annotation_str = annotation_str.substring(0, str_max - 1) + ".";
+            bgnd_rect_width = w;
+          } else {
+            annotation_str = annotation_str.substring(0, 1) + ".";
+            bgnd_rect_width = 2 * char_width;
+          }
+        }
+      } else {
+        bgnd_rect_width = strw + char_width;
+      }
+      if (
+        canvas_reg.shape_attributes["name"] === VIA_REGION_SHAPE.POLYGON ||
+        canvas_reg.shape_attributes["name"] === VIA_REGION_SHAPE.POLYLINE
+      ) {
+        // put label near the first vertex
+        //MODSote
+        if (
+          canvas_reg.shape_attributes["id_x"] === undefined ||
+          !canvas_reg.shape_attributes["all_points_x"].includes(
+            canvas_reg.shape_attributes["id_x"]
+          )
+        ) {
+          let random = Math.floor(
+            Math.random() * canvas_reg.shape_attributes["all_points_x"].length
+          );
+          x = canvas_reg.shape_attributes["all_points_x"][random];
+          y = canvas_reg.shape_attributes["all_points_y"][random];
+          _via_canvas_regions[i].shape_attributes["id_x"] = x;
+          _via_canvas_regions[i].shape_attributes["id_y"] = y;
+        } else {
+          x = canvas_reg.shape_attributes["id_x"];
+          y = canvas_reg.shape_attributes["id_y"];
+        }
+      } else {
+        // center the label
+        x = x - (bgnd_rect_width / 2 - w / 2);
+      }
+      // ensure that the text is within the image boundaries
+      if (y < char_height) {
+        y = char_height;
+      }
+      let avalue =
+        _via_img_metadata[_via_image_id].regions[i].region_attributes[aid];
+
+      if (this.canvas_regions_group_color.hasOwnProperty(avalue)) {
+        this.beginFill(this.canvas_regions_group_color[avalue], 0.8);
+      } else {
+        this.beginFill("#0d6efd", 0.8);
+      }
+      this.ctx.beginPath();
+
+      if (this.current_browser === "Firefox") {
+        this.ctx.rect(
+          Math.floor(x),
+          Math.floor(y - 1.1 * char_height),
+          Math.floor(bgnd_rect_width),
+          Math.floor(char_height)
+        );
+      } else {
+        this.ctx.roundRect(
+          Math.floor(x),
+          Math.floor(y - 1.1 * char_height),
+          Math.floor(bgnd_rect_width),
+          Math.floor(char_height),
+          5
+        );
+      }
+      this.ctx.fill();
+      this.ctx.closePath();
+      this.ctx.globalAlpha = 1.0;
+      this.ctx.fillStyle = "white";
+      this.ctx.fillText(
+        annotation_str,
+        Math.floor(x + 0.5 * char_width),
+        Math.floor(y - 0.35 * char_height)
+      );
+    }
+  } // draw all region labels
+
+  /**
+   * Calculates region ID position
+   * Helper function for drawAllRegionId()
+   * @param region - region to calculate bounding box
+   * @returns {any[]} - bounding box of the region
+   */
+  getRegionBoundingBox(region) {
+    let d = region.shape_attributes;
+    let bbox = new Array(4);
+
+    switch (d["name"]) {
+      case "rect": {
+        bbox[0] = d["x"];
+        bbox[1] = d["y"];
+        bbox[2] = d["x"] + d["width"];
+        bbox[3] = d["y"] + d["height"];
+        break;
+      }
+      case "circle": {
+        bbox[0] = d["cx"] - d["r"];
+        bbox[1] = d["cy"] - d["r"];
+        bbox[2] = d["cx"] + d["r"];
+        bbox[3] = d["cy"] + d["r"];
+        break;
+      }
+      case "ellipse": {
+        let radians = d["theta"];
+        let radians90 = radians + Math.PI / 2;
+        let ux = d["rx"] * Math.cos(radians);
+        let uy = d["rx"] * Math.sin(radians);
+        let vx = d["ry"] * Math.cos(radians90);
+        let vy = d["ry"] * Math.sin(radians90);
+        let width = Math.sqrt(ux * ux + vx * vx) * 2;
+        let height = Math.sqrt(uy * uy + vy * vy) * 2;
+        bbox[0] = d["cx"] - width / 2;
+        bbox[1] = d["cy"] - height / 2;
+        bbox[2] = d["cx"] + width / 2;
+        bbox[3] = d["cy"] + height / 2;
+        break;
+      }
+      case "polyline": // handled by polygon
+
+      case "polygon": {
+        let all_points_x = d["all_points_x"];
+        let all_points_y = d["all_points_y"];
+        let minx = Number.MAX_SAFE_INTEGER;
+        let miny = Number.MAX_SAFE_INTEGER;
+        let maxx = 0;
+        let maxy = 0;
+
+        for (let i = 0; i < all_points_x.length; ++i) {
+          if (all_points_x[i] < minx) {
+            minx = all_points_x[i];
+          }
+
+          if (all_points_x[i] > maxx) {
+            maxx = all_points_x[i];
+          }
+
+          if (all_points_y[i] < miny) {
+            miny = all_points_y[i];
+          }
+
+          if (all_points_y[i] > maxy) {
+            maxy = all_points_y[i];
+          }
+        }
+
+        bbox[0] = minx;
+        bbox[1] = miny;
+        bbox[2] = maxx;
+        bbox[3] = maxy;
+        break;
+      }
+      case "point": {
+        bbox[0] = d["cx"] - this.region_point_radius;
+        bbox[1] = d["cy"] - this.region_point_radius;
+        bbox[2] = d["cx"] + this.region_point_radius;
+        bbox[3] = d["cy"] + this.region_point_radius;
+        break;
+      }
+    }
+
+    return bbox;
+  }
+
+  //--------------------------------------------
+  // Region collision routines
+  //--------------------------------------------
+
+  /**
+   * Checks if a point is inside a region
+   * @param p - point to check
+   * @param descending_order - if true, then regions are checked in descending order of their area
+   * @returns {number} - region id if point is inside a region, else -1
+   */
+  isInsideRegion(p, descending_order = false, ignore_lock = false) {
+    let N = _via_canvas_regions.length;
+
+    if (N === 0) {
+      return -1;
+    }
+
+    let start, end, del; // traverse the canvas regions in alternating ascending
+    // and descending order to solve the issue of nested regions
+
+    if (descending_order) {
+      start = N - 1;
+      end = -1;
+      del = -1;
+    } else {
+      start = 0;
+      end = N;
+      del = 1;
+    }
+    let i = start;
+
+    while (i !== end) {
+      if (this.isInsideThisRegion(p, i, ignore_lock)) {
+        return i;
+      }
+
+      i = i + del;
+    }
+    return -1;
+  } // is that point inside any region on the canvas? Mouse click detection
+
+  //check all region if the point is inside and return all region id
+  isInsideAllRegion(p, descending_order = false, ignore_lock = false) {
+    let N = _via_canvas_regions.length;
+
+    if (N === 0) {
+      return -1;
+    }
+
+    let start, end, del; // traverse the canvas regions in alternating ascending
+    // and descending order to solve the issue of nested regions
+
+    if (descending_order) {
+      start = N - 1;
+      end = -1;
+      del = -1;
+    } else {
+      start = 0;
+      end = N;
+      del = 1;
+    }
+    let i = start;
+    let result = [];
+    while (i !== end) {
+      if (this.isInsideThisRegion(p, i, ignore_lock)) {
+        result.push(i);
+      }
+      i = i + del;
+    }
+    return result;
+  }
+
+  /**
+   * Checks if a point is inside a region
+   * Helper function for isInsideRegion()
+   * @param p - point to check
+   * @param region_id - region to check
+   * @returns {boolean} - true if point is inside the region, else false
+   */
+  isInsideThisRegion(p, region_id, ignore_lock = false) {
+    let attr = _via_canvas_regions[region_id].shape_attributes;
+    let result = false;
+    if (_via_img_metadata[_via_image_id].isRegionLocked(region_id) && !ignore_lock) {
+      return false;
+    }
+
+    switch (attr["name"]) {
+      case VIA_REGION_SHAPE.RECT:
+        result = this.isInsideRect(
+          attr["x"],
+          attr["y"],
+          attr["width"],
+          attr["height"],
+          p.x,
+          p.y
+        );
+        break;
+
+      case VIA_REGION_SHAPE.CIRCLE:
+        result = this.isInsideCircle(
+          attr["cx"],
+          attr["cy"],
+          attr["r"],
+          p.x,
+          p.y
+        );
+        break;
+
+      case VIA_REGION_SHAPE.ELLIPSE:
+        result = this.isInsideEllipse(
+          attr["cx"],
+          attr["cy"],
+          attr["rx"],
+          attr["ry"],
+          attr["theta"],
+          p.x,
+          p.y
+        );
+        break;
+
+      case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON
+
+      case VIA_REGION_SHAPE.POLYGON:
+        result = this.isInsidePolygon(
+          attr["all_points_x"],
+          attr["all_points_y"],
+          p.x,
+          p.y
+        );
+        break;
+
+      case VIA_REGION_SHAPE.POINT:
+        result = this.isInsidePoint(attr["cx"], attr["cy"], p.x, p.y);
+        break;
+    }
+
+    return result;
+  } // is that point inside a shape? Helper function for isInsideRegion()
+
+  /**
+   * Checks if a point is inside a circle region
+   * @param cx - x-coordinate of circle center
+   * @param cy - y-coordinate of circle center
+   * @param r - radius of circle
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {boolean} - true if point is inside the circle, else false
+   */
+  isInsideCircle(cx, cy, r, px, py) {
+    let dx = px - cx;
+    let dy = py - cy;
+    return dx * dx + dy * dy < r * r;
+  } // is that point inside a circle? Helper function for isInsideThisRegion()
+
+  /**
+   * Checks if a point is inside a rectangle region
+   * @param x - x-coordinate of top-left corner of rectangle
+   * @param y - y-coordinate of top-left corner of rectangle
+   * @param w - width of rectangle
+   * @param h - height of rectangle
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {boolean} - true if point is inside the rectangle, else false
+   */
+  isInsideRect(x, y, w, h, px, py) {
+    return px > x && px < x + w && py > y && py < y + h;
+  } // is that point inside a rectangle? Helper function for isInsideThisRegion()
+
+  /**
+   * Checks if a point is inside an ellipse region
+   * @param cx - x-coordinate of ellipse center
+   * @param cy - y-coordinate of ellipse center
+   * @param rx - x-radius of ellipse
+   * @param ry - y-radius of ellipse
+   * @param rr - rotation angle of ellipse
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {boolean} - true if point is inside the ellipse, else false
+   */
+  isInsideEllipse(cx, cy, rx, ry, rr, px, py) {
+    // Inverse rotation of pixel coordinates
+    let dx = Math.cos(-rr) * (cx - px) - Math.sin(-rr) * (cy - py);
+    let dy = Math.sin(-rr) * (cx - px) + Math.cos(-rr) * (cy - py);
+    return (dx * dx) / (rx * rx) + (dy * dy) / (ry * ry) < 1;
+  } // is that point inside an ellipse? Helper function for isInsideThisRegion()
+  // returns 0 when (px,py) is outside the polygon
+  // source: http://geomalgorithms.com/a03-_inclusion.html
+
+  /**
+   * Checks if a point is inside a polygon region
+   * @param all_points_x - x-coordinates of polygon vertices
+   * @param all_points_y - y-coordinates of polygon vertices
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {number} - 0 if point is outside the polygon, 1 if point is inside the polygon
+   */
+  isInsidePolygon(all_points_x, all_points_y, px, py) {
+    if (all_points_x.length === 0 || all_points_y.length === 0) {
+      return 0;
+    }
+
+    let wn = 0; // the  winding number counter
+
+    let n = all_points_x.length;
+    let i; // loop through all edges of the polygon
+
+    for (i = 0; i < n - 1; ++i) {
+      // edge from V[i] to  V[i+1]
+      let is_left_value = this.isLeft(
+        all_points_x[i],
+        all_points_y[i],
+        all_points_x[i + 1],
+        all_points_y[i + 1],
+        px,
+        py
+      );
+
+      if (all_points_y[i] <= py) {
+        if (all_points_y[i + 1] > py && is_left_value > 0) {
+          ++wn;
+        }
+      } else {
+        if (all_points_y[i + 1] <= py && is_left_value < 0) {
+          --wn;
+        }
+      }
+    } // also take into account the loop closing edge that connects last point with first point
+
+    let is_left_value = this.isLeft(
+      all_points_x[n - 1],
+      all_points_y[n - 1],
+      all_points_x[0],
+      all_points_y[0],
+      px,
+      py
+    );
+
+    if (all_points_y[n - 1] <= py) {
+      if (all_points_y[0] > py && is_left_value > 0) {
+        ++wn;
+      }
+    } else {
+      if (all_points_y[0] <= py && is_left_value < 0) {
+        --wn;
+      }
+    }
+
+    if (wn === 0) {
+      return 0;
+    } else {
+      return 1;
+    }
+  } // is that point inside a polygon? Helper function for isInsideThisRegion()
+
+  /**
+   * Checks if a point is inside a point region
+   * @param cx - x-coordinate of point
+   * @param cy - y-coordinate of point
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {boolean} - true if point is inside the point, else false
+   */
+  isInsidePoint(cx, cy, px, py) {
+    let dx = px - cx;
+    let dy = py - cy;
+    let r2 = this.polygon_vertex_match_tol * this.polygon_vertex_match_tol;
+    return dx * dx + dy * dy < r2;
+  } // is that point inside a point? Helper function for isInsideThisRegion()
+  // >0 if (x2,y2) lies on the left side of line joining (x0,y0) and (x1,y1)
+  // =0 if (x2,y2) lies on the line joining (x0,y0) and (x1,y1)
+  // >0 if (x2,y2) lies on the right side of line joining (x0,y0) and (x1,y1)
+  // source: http://geomalgorithms.com/a03-_inclusion.html
+
+  /**
+   * Checks if a point is left of a line
+   * @param x0 - x-coordinate of first point of line
+   * @param y0 - y-coordinate of first point of line
+   * @param x1 - x-coordinate of second point of line
+   * @param y1 - y-coordinate of second point of line
+   * @param x2 - x-coordinate of point
+   * @param y2 - y-coordinate of point
+   * @returns {number} - >0 if point is left of line, =0 if point is on line, <0 if point is right of line
+   */
+  isLeft(x0, y0, x1, y1, x2, y2) {
+    return (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0);
+  } //helper function for isInsidePolygon()
+
+  /**
+   * Checks if a point is on a region boundary
+   * @param p - point
+   * @returns {number[]} - array of region indices that the point is on the boundary of
+   */
+  isOnRegionCorner(p) {
+    let _region_edge = [-1, -1]; // region_id, corner_id [top-left=1,top-right=2,bottom-right=3,bottom-left=4]
+
+    /*if(this.is_region_selected){
+        let attr = _via_canvas_regions[this.user_sel_region_id].shape_attributes;
+        let result = false;
+        switch (attr["name"]) {
+            case VIA_REGION_SHAPE.RECT:
+                result = this.isOnRectEdge(
+                    attr["x"],
+                    attr["y"],
+                    attr["width"],
+                    attr["height"],
+                    p.x,
+                    p.y
+                );
+                break;
+
+            case VIA_REGION_SHAPE.CIRCLE:
+                result = this.isOnCircleEdge(attr["cx"], attr["cy"], attr["r"], p.x, p.y);
+                break;
+
+            case VIA_REGION_SHAPE.ELLIPSE:
+                result = this.isOnEllipseEdge(
+                    attr["cx"],
+                    attr["cy"],
+                    attr["rx"],
+                    attr["ry"],
+                    attr["theta"],
+                    p.x,
+                    p.y
+                );
+                break;
+
+            case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+            case VIA_REGION_SHAPE.POLYGON:
+                result = this.isOnPolygonVertex(
+                    attr["all_points_x"],
+                    attr["all_points_y"],
+                    p
+                );
+
+                if (result === 0) {
+                    result = this.isOnPolygonEdge(
+                        attr["all_points_x"],
+                        attr["all_points_y"],
+                        p.x,
+                        p.y
+                    );
+                }
+
+                break;
+
+            case VIA_REGION_SHAPE.POINT:
+                // since there are no edges of a point
+                result = 0;
+                break;
+        }
+
+
+        if (result > 0) {
+            _region_edge[1] = result;
+            return _region_edge;
+        }
+    }*/
+
+    for (let i = 0; i < _via_canvas_regions.length; ++i) {
+      let attr = _via_canvas_regions[i].shape_attributes;
+      let result = false;
+      _region_edge[0] = i;
+      if (_via_img_metadata[_via_image_id].isRegionLocked(i)) {
+        continue;
+      }
+
+      switch (attr["name"]) {
+        case VIA_REGION_SHAPE.RECT:
+          result = this.isOnRectEdge(
+            attr["x"],
+            attr["y"],
+            attr["width"],
+            attr["height"],
+            p.x,
+            p.y
+          );
+          break;
+
+        case VIA_REGION_SHAPE.CIRCLE:
+          result = this.isOnCircleEdge(
+            attr["cx"],
+            attr["cy"],
+            attr["r"],
+            p.x,
+            p.y
+          );
+          break;
+
+        case VIA_REGION_SHAPE.ELLIPSE:
+          result = this.isOnEllipseEdge(
+            attr["cx"],
+            attr["cy"],
+            attr["rx"],
+            attr["ry"],
+            attr["theta"],
+            p.x,
+            p.y
+          );
+          break;
+
+        case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+
+        case VIA_REGION_SHAPE.POLYGON:
+          result = this.isOnPolygonVertex(
+            attr["all_points_x"],
+            attr["all_points_y"],
+            p
+          );
+
+          if (result === 0) {
+            result = this.isOnPolygonEdge(
+              attr["all_points_x"],
+              attr["all_points_y"],
+              p.x,
+              p.y
+            );
+          }
+
+          break;
+
+        case VIA_REGION_SHAPE.POINT:
+          // since there are no edges of a point
+          result = 0;
+          break;
+      }
+
+      if (result > 0) {
+        _region_edge[1] = result;
+        return _region_edge;
+      }
+    }
+
+    _region_edge[0] = -1;
+    return _region_edge;
+  } // is the mouse pointer on a region corner?
+
+  /**
+   * Checks if a point is on a rectangle boundary
+   * @param x - x-coordinate of rectangle
+   * @param y - y-coordinate of rectangle
+   * @param w - width of rectangle
+   * @param h - height of rectangle
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {number} - 0 if point is not on boundary, else corner id
+   */
+  isOnRectEdge(x, y, w, h, px, py) {
+    let dx0 = Math.abs(x - px);
+    let dy0 = Math.abs(y - py);
+    let dx1 = Math.abs(x + w - px);
+    let dy1 = Math.abs(y + h - py); //[top-left=1,top-right=2,bottom-right=3,bottom-left=4]
+
+    if (dx0 < this.region_edge_tol && dy0 < this.region_edge_tol) {
+      return 1;
+    }
+
+    if (dx1 < this.region_edge_tol && dy0 < this.region_edge_tol) {
+      return 2;
+    }
+
+    if (dx1 < this.region_edge_tol && dy1 < this.region_edge_tol) {
+      return 3;
+    }
+
+    if (dx0 < this.region_edge_tol && dy1 < this.region_edge_tol) {
+      return 4;
+    }
+
+    let mx0 = Math.abs(x + w / 2 - px);
+    let my0 = Math.abs(y + h / 2 - py); //[top-middle=5,right-middle=6,bottom-middle=7,left-middle=8]
+
+    if (mx0 < this.region_edge_tol && dy0 < this.region_edge_tol) {
+      return 5;
+    }
+
+    if (dx1 < this.region_edge_tol && my0 < this.region_edge_tol) {
+      return 6;
+    }
+
+    if (mx0 < this.region_edge_tol && dy1 < this.region_edge_tol) {
+      return 7;
+    }
+
+    if (dx0 < this.region_edge_tol && my0 < this.region_edge_tol) {
+      return 8;
+    }
+    return 0;
+  } // is the mouse pointer on a rectangle edge? Helper function for isOnRegionCorner()
+
+  /**
+   * Checks if a point is on a circle boundary
+   * @param cx - x-coordinate of circle center
+   * @param cy - y-coordinate of circle center
+   * @param r - radius of circle
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {number} - 0 if point is not on boundary, else corner id
+   */
+  isOnCircleEdge(cx, cy, r, px, py) {
+    let dx = cx - px;
+    let dy = cy - py;
+
+    if (Math.abs(Math.sqrt(dx * dx + dy * dy) - r) < this.region_edge_tol) {
+      let theta = Math.atan2(py - cy, px - cx);
+
+      if (
+        Math.abs(theta - Math.PI / 2) < this.theta_tol ||
+        Math.abs(theta + Math.PI / 2) < this.theta_tol
+      ) {
+        return 5;
+      }
+
+      if (
+        Math.abs(theta) < this.theta_tol ||
+        Math.abs(Math.abs(theta) - Math.PI) < this.theta_tol
+      ) {
+        return 6;
+      }
+
+      if (theta > 0 && theta < Math.PI / 2) {
+        return 1;
+      }
+
+      if (theta > Math.PI / 2 && theta < Math.PI) {
+        return 4;
+      }
+
+      if (theta < 0 && theta > -(Math.PI / 2)) {
+        return 2;
+      }
+
+      if (theta < -(Math.PI / 2) && theta > -Math.PI) {
+        return 3;
+      }
+    } else {
+      return 0;
+    }
+  } // is the mouse pointer on a circle edge? Helper function for isOnRegionCorner()
+
+  /**
+   * Checks if a point is on an ellipse boundary
+   * @param cx - x-coordinate of ellipse center
+   * @param cy - y-coordinate of ellipse center
+   * @param rx - x-radius of ellipse
+   * @param ry - y-radius of ellipse
+   * @param rr - rotation of ellipse
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {number} - 0 if point is not on boundary, else corner id
+   */
+  isOnEllipseEdge(cx, cy, rx, ry, rr, px, py) {
+    // Inverse rotation of pixel coordinates
+    px = px - cx;
+    py = py - cy;
+    let px_ = Math.cos(-rr) * px - Math.sin(-rr) * py;
+    let py_ = Math.sin(-rr) * px + Math.cos(-rr) * py;
+    px = px_ + cx;
+    py = py_ + cy;
+    let dx = (cx - px) / rx;
+    let dy = (cy - py) / ry;
+
+    if (Math.abs(Math.sqrt(dx * dx + dy * dy) - 1) < this.ellipse_edge_tol) {
+      let theta = Math.atan2(py - cy, px - cx);
+
+      if (
+        Math.abs(theta - Math.PI / 2) < this.theta_tol ||
+        Math.abs(theta + Math.PI / 2) < this.theta_tol
+      ) {
+        return 5;
+      }
+
+      if (
+        Math.abs(theta) < this.theta_tol ||
+        Math.abs(Math.abs(theta) - Math.PI) < this.theta_tol
+      ) {
+        return 6;
+      }
+    } else {
+      return 0;
+    }
+  } // is the mouse pointer on an ellipse edge? Helper function for isOnRegionCorner()
+
+  /**
+   * Checks if a point is on a polygon vertex
+   * @param all_points_x - x-coordinates of polygon vertices
+   * @param all_points_y - y-coordinates of polygon vertices
+   * @param p - point to check
+   * @returns {number} - 0 if point is not on a vertex, else corner id
+   */
+  isOnPolygonVertex(all_points_x, all_points_y, p) {
+    let i, n;
+    n = all_points_x.length;
+
+    for (i = 0; i < n; ++i) {
+      if (
+        Math.abs(all_points_x[i] - p.x) < this.polygon_vertex_match_tol &&
+        Math.abs(all_points_y[i] - p.y) < this.polygon_vertex_match_tol
+      ) {
+        return this.polygon_resize_vertex_offset + i;
+      }
+    }
+
+    return 0;
+  } // is the mouse pointer on a polygon vertex? Helper function for isOnRegionCorner()
+
+  /**
+   * Checks if a point is on a polygon edge
+   * @param all_points_x - x-coordinates of polygon vertices
+   * @param all_points_y - y-coordinates of polygon vertices
+   * @param px - x-coordinate of point
+   * @param py - y-coordinate of point
+   * @returns {number} - 0 if point is not on the edge, else corner id
+   */
+  isOnPolygonEdge(all_points_x, all_points_y, px, py) {
+    let i, n, di, d;
+    n = all_points_x.length;
+    d = [];
+
+    for (i = 0; i < n - 1; ++i) {
+      di = this.distToLine(
+        px,
+        py,
+        all_points_x[i],
+        all_points_y[i],
+        all_points_x[i + 1],
+        all_points_y[i + 1]
+      );
+      d.push(di);
+    } // closing edge
+
+    di = this.distToLine(
+      px,
+      py,
+      all_points_x[n - 1],
+      all_points_y[n - 1],
+      all_points_x[0],
+      all_points_y[0]
+    );
+    d.push(di);
+    let smallest_value = d[0];
+    let smallest_index = 0;
+    n = d.length;
+
+    for (i = 1; i < n; ++i) {
+      if (d[i] < smallest_value) {
+        smallest_value = d[i];
+        smallest_index = i;
+      }
+    }
+    let sm_index = smallest_index + 1;
+    if (sm_index >= n) {
+      sm_index = 0;
+    }
+    let smx = all_points_x[sm_index];
+    let smy = all_points_y[sm_index];
+    let smm = all_points_x[smallest_index];
+    let smn = all_points_y[smallest_index];
+
+    //measure the distance from the px py to sx sy and smx smy
+    let d1 = Math.sqrt((px - smx) * (px - smx) + (py - smy) * (py - smy));
+    let d2 = Math.sqrt((px - smm) * (px - smm) + (py - smn) * (py - smn));
+
+    if (d1 < d2) {
+      smallest_index = sm_index;
+    } // if the distance from the point to the edge is less than the distance from the point to the vertex, then the point is on the edge
+
+    if (smallest_value < this.polygon_vertex_match_tol) {
+      return this.polygon_resize_vertex_offset + smallest_index;
+    } else {
+      return 0;
+    }
+  } // is the mouse pointer on a polygon edge? Helper function for isOnRegionCorner()
+
+  is_on_polygon_edge(all_points_x, all_points_y, px, py) {
+    var i, n, di, d;
+    n = all_points_x.length;
+    d = [];
+    for (i = 0; i < n - 1; ++i) {
+      di = this.distToLine(px, py, all_points_x[i], all_points_y[i], all_points_x[i + 1], all_points_y[i + 1]);
+      d.push(di);
+    }
+    // closing edge
+    di = this.distToLine(px, py, all_points_x[n - 1], all_points_y[n - 1], all_points_x[0], all_points_y[0]);
+    d.push(di);
+
+    var smallest_value = d[0];
+    var smallest_index = 0;
+    n = d.length;
+    for (i = 1; i < n; ++i) {
+      if (d[i] < smallest_value) {
+        smallest_value = d[i];
+        smallest_index = i;
+      }
+    }
+    if (smallest_value < this.polygon_vertex_match_tol) {
+      return (this.polygon_resize_vertex_offset + smallest_index);
+    } else {
+      return 0;
+    }
+  }
+
+
+  /**
+   * Checks if a point is inside a generated rectangle
+   * Helper function for distToLine()
+   * @param x - x-coordinate of point
+   * @param y - y-coordinate of point
+   * @param x1 - x-coordinate of rectangle top left corner
+   * @param y1 - y-coordinate of rectangle top left corner
+   * @param x2 - x-coordinate of rectangle bottom right corner
+   * @param y2 - y-coordinate of rectangle bottom right corner
+   * @returns {boolean} - true if point is inside rectangle, else false
+   */
+  isPointInsideBoundingBox(x, y, x1, y1, x2, y2) {
+    // ensure that (x1,y1) is top left and (x2,y2) is bottom right corner of rectangle
+    let rect = {};
+
+    if (x1 < x2) {
+      rect.x1 = x1;
+      rect.x2 = x2;
+    } else {
+      rect.x1 = x2;
+      rect.x2 = x1;
+    }
+
+    if (y1 < y2) {
+      rect.y1 = y1;
+      rect.y2 = y2;
+    } else {
+      rect.y1 = y2;
+      rect.y2 = y1;
+    }
+
+    return !!(x >= rect.x1 && x <= rect.x2 && y >= rect.y1 && y <= rect.y2); // return true if point is inside rectangle modSote
+  } // is the mouse pointer inside a bounding box? Helper function for isOnRegionCorner()
+
+  /**
+   * Helper of isOnPolygonEdge()
+   * @param x - x-coordinate of point
+   * @param y - y-coordinate of point
+   * @param x1 - x-coordinate of line start
+   * @param y1 - y-coordinate of line start
+   * @param x2 - x-coordinate of line end
+   * @param y2 - y-coordinate of line end
+   * @returns {number} - distance from point to line
+   */
+  distToLine(x, y, x1, y1, x2, y2) {
+    if (this.isPointInsideBoundingBox(x, y, x1, y1, x2, y2)) {
+      let dy = y2 - y1;
+      let dx = x2 - x1;
+      let nr = Math.abs(dy * x - dx * y + x2 * y1 - y2 * x1);
+      let dr = Math.sqrt(dx * dx + dy * dy);
+      let dist = nr / dr;
+      return Math.round(dist);
+    } else {
+      return Number.MAX_SAFE_INTEGER;
+    }
+  } // distance from point to line segment
+
+  /**
+   * ensure that (d[0],d[1]) is top-left corner while (d[2],d[3]) is bottom-right corner
+   * @param d - region data
+   */
+  rectStandardizeCoordinates(d) {
+    // d[x0,y0,x1,y1]
+    // ensures that (d[0],d[1]) is top-left corner while
+    // (d[2],d[3]) is bottom-right corner
+    if (d[0] > d[2]) {
+      // swap
+      let t = d[0];
+      d[0] = d[2];
+      d[2] = t;
+    }
+
+    if (d[1] > d[3]) {
+      // swap
+      let t = d[1];
+      d[1] = d[3];
+      d[3] = t;
+    }
+  } // ensure that (d[0],d[1]) is top-left corner while (d[2],d[3]) is bottom-right corner
+
+  /**
+   * Efficiently move a corner of a rectangle
+   * @param corner_id - corner id
+   * @param d - region data
+   * @param x - x-coordinate of mouse pointer
+   * @param y - y-coordinate of mouse pointer
+   * @param preserve_aspect_ratio - preserve aspect ratio of rectangle
+   */
+  rectUpdateCorner(corner_id, d, x, y, preserve_aspect_ratio) {
+    // pre-condition : d[x0,y0,x1,y1] is standardized
+    // post-condition : corner is moved ( d may not stay standardized )
+    if (preserve_aspect_ratio) {
+      switch (corner_id) {
+        case 1: // Fall-through // top-left
+
+        case 3: {
+          // bottom-right
+          let dx = d[2] - d[0];
+          let dy = d[3] - d[1];
+          let norm = Math.sqrt(dx * dx + dy * dy);
+          let nx = dx / norm; // x component of unit vector along the diagonal of rect
+
+          let ny = dy / norm; // y component
+
+          let proj = (x - d[0]) * nx + (y - d[1]) * ny;
+          let proj_x = nx * proj;
+          let proj_y = ny * proj; // constrain (mx,my) to lie on a line connecting (x0,y0) and (x1,y1)
+
+          x = Math.round(d[0] + proj_x);
+          y = Math.round(d[1] + proj_y);
+          break;
+        }
+        case 2: // Fall-through // top-right
+
+        case 4: {
+          // bottom-left
+          let dx = d[2] - d[0];
+          let dy = d[1] - d[3];
+          let norm = Math.sqrt(dx * dx + dy * dy);
+          let nx = dx / norm; // x component of unit vector along the diagonal of rect
+
+          let ny = dy / norm; // y component
+
+          let proj = (x - d[0]) * nx + (y - d[3]) * ny;
+          let proj_x = nx * proj;
+          let proj_y = ny * proj; // constrain (mx,my) to lie on a line connecting (x0,y0) and (x1,y1)
+
+          x = Math.round(d[0] + proj_x);
+          y = Math.round(d[3] + proj_y);
+          break;
+        }
+      }
+    }
+
+    switch (corner_id) {
+      case 1:
+        // top-left
+        d[0] = x;
+        d[1] = y;
+        break;
+
+      case 3:
+        // bottom-right
+        d[2] = x;
+        d[3] = y;
+        break;
+
+      case 2:
+        // top-right
+        d[2] = x;
+        d[1] = y;
+        break;
+
+      case 4:
+        // bottom-left
+        d[0] = x;
+        d[3] = y;
+        break;
+
+      case 5:
+        // top-middle
+        d[1] = y;
+        break;
+
+      case 6:
+        // right-middle
+        d[2] = x;
+        break;
+
+      case 7:
+        // bottom-middle
+        d[3] = y;
+        break;
+
+      case 8:
+        // left-middle
+        d[0] = x;
+        break;
+    }
+  } // move a corner of a rectangle
+
+  /**
+   * Set the canvas size
+   * @param w - width of the canvas
+   * @param h - height of the canvas
+   */
+  setAllCanvasSize(w, h) {
+    _via_reg_canvas.height = h;
+    _via_reg_canvas.width = w;
+  }
+
+
+  /**
+   * Set the canvas scale
+   */
+  setCanvasScale(scale) {
+    this.ctx.scale(scale, scale);
+  }
+
+  //--------------------------------------------
+  // Tools
+  //--------------------------------------------
+
+  /**
+   * Trim the selected Polygon region
+   * @param region_id - id of the region to be trimmed
+   */
+  trimRegionSection(region_id) {
+    let attr = _via_canvas_regions[region_id].shape_attributes;
+    if (attr.name === "polygon") {
+      this.trim_region_id = region_id;
+      this.trim_phase_id = 1;
+      this.trim_points = { x: attr["all_points_x"], y: attr["all_points_y"] };
+    }
+  }
+
+  updateTrimPoints() {
+    if (this.trim_phase_id === -1) {
+      return;
+    }
+    if (_via_canvas_regions.hasOwnProperty(this.trim_region_id)) {
+      let attr = _via_canvas_regions[this.trim_region_id].shape_attributes;
+      this.trim_points = { x: attr["all_points_x"], y: attr["all_points_y"] };
+    }
+  }
+
+  calculateTrimPoints() {
+    if (this.trim_phase_id !== 2) {
+      return;
+    }
+
+    let intersection = [];
+    // Calculate the intersection points
+    for (let i = 0; i < this.trim_points.x.length; i++) {
+      let x1 = this.trim_points.x[i];
+      let y1 = this.trim_points.y[i];
+      let x2 = this.trim_points.x[(i + 1) % this.trim_points.x.length];
+      let y2 = this.trim_points.y[(i + 1) % this.trim_points.x.length];
+      let x3 = this.trim_line["x0"];
+      let y3 = this.trim_line["y0"];
+      let x4 = this.trim_line["x1"];
+      let y4 = this.trim_line["y1"];
+      intersection.push(
+        this.lineLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4)
+      );
+    }
+
+    for (let i = 0; i < intersection.length; i++) {
+      if (intersection[i][0] !== -1 && intersection[i][1] !== -1) {
+        this.trim_choose_points.push(intersection[i][0]);
+        this.trim_choose_points.push(intersection[i][1]);
+      }
+    }
+
+    if (this.trim_choose_points.length < 4) {
+      this.destroyTrim();
+      return;
+    }
+    if (this.trim_choose_points.length >= 4) {
+      this.trim_phase_id = 3;
+      this.drawTrimemdRegions();
+    } else {
+      this.destroyTrim();
+    }
+  }
+
+  lineLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
+    const denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+    if (denominator === 0) {
+      return [-1, -1];
+    }
+
+    const numeratorX = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
+    const numeratorY = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
+
+    let x = Math.round(numeratorX / denominator);
+    let y = Math.round(numeratorY / denominator);
+
+    if (x >= Math.min(x1, x2) && x <= Math.max(x1, x2) &&
+      x >= Math.min(x3, x4) && x <= Math.max(x3, x4) &&
+      y >= Math.min(y1, y2) && y <= Math.max(y1, y2) &&
+      y >= Math.min(y3, y4) && y <= Math.max(y3, y4)) {
+      return [x, y];
+    }
+
+    return [-1, -1];
+  }
+
+  drawTrimemdRegions() {
+    if (this.trim_phase_id < 3) {
+      return;
+    }
+
+    //separate two polygons by the line
+    let polygon1 = { x: [], y: [] };
+    let polygon2 = { x: [], y: [] };
+    let is_first_polygon = true;
+    for (let i = 0; i < this.trim_points.x.length; i++) {
+      let x1 = this.trim_points.x[i];
+      let y1 = this.trim_points.y[i];
+      let x2 = this.trim_points.x[(i + 1) % this.trim_points.x.length];
+      let y2 = this.trim_points.y[(i + 1) % this.trim_points.x.length];
+      let x3 = this.trim_line["x0"];
+      let y3 = this.trim_line["y0"];
+      let x4 = this.trim_line["x1"];
+      let y4 = this.trim_line["y1"];
+      let intersection = this.lineLineIntersection(
+        x1,
+        y1,
+        x2,
+        y2,
+        x3,
+        y3,
+        x4,
+        y4
+      );
+      if (intersection[0] !== -1 && intersection[1] !== -1) {
+        if (is_first_polygon) {
+          polygon1.x.push(x1);
+          polygon1.y.push(y1);
+          polygon1.x.push(intersection[0]);
+          polygon1.y.push(intersection[1]);
+          polygon2.x.push(intersection[0]);
+          polygon2.y.push(intersection[1]);
+          is_first_polygon = false;
+        } else {
+          polygon2.x.push(x1);
+          polygon2.y.push(y1);
+          polygon2.x.push(intersection[0]);
+          polygon2.y.push(intersection[1]);
+          polygon1.x.push(intersection[0]);
+          polygon1.y.push(intersection[1]);
+          is_first_polygon = true;
+        }
+      } else {
+        if (is_first_polygon) {
+          polygon1.x.push(x1);
+          polygon1.y.push(y1);
+        } else {
+          polygon2.x.push(x1);
+          polygon2.y.push(y1);
+        }
+      }
+    }
+    this.trim_points["polygon1"] = polygon1;
+    this.trim_points["polygon2"] = polygon2;
+
+    //draw polygons
+    this.ctx.beginPath();
+    this.ctx.moveTo(polygon1.x[0], polygon1.y[0]);
+    for (let i = 1; i < polygon1.x.length; i++) {
+      this.ctx.lineTo(polygon1.x[i], polygon1.y[i]);
+    }
+    this.ctx.closePath();
+    this.ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
+    this.ctx.fill();
+    this.ctx.beginPath();
+    this.ctx.moveTo(polygon2.x[0], polygon2.y[0]);
+    for (let i = 1; i < polygon2.x.length; i++) {
+      this.ctx.lineTo(polygon2.x[i], polygon2.y[i]);
+    }
+    this.ctx.closePath();
+    this.ctx.fillStyle = "rgba(0, 255, 0, 0.5)";
+    this.ctx.fill();
+    this.trim_phase_id = 4;
+  }
+
+  isInsideTrimPolygon(p, polygon) {
+    if (polygon === undefined) {
+      return false;
+    }
+    let inside = false;
+    for (let i = 0, j = polygon.x.length - 1; i < polygon.x.length; j = i++) {
+      let xi = polygon.x[i],
+        yi = polygon.y[i];
+      let xj = polygon.x[j],
+        yj = polygon.y[j];
+      let intersect =
+        yi > p.y !== yj > p.y &&
+        p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;
+      if (intersect) inside = !inside;
+    }
+    return inside;
+  }
+
+  trimFromMetadata(polygon) {
+    if (this.trim_phase_id < 4) {
+      return;
+    }
+    let metaAttr =
+      _via_img_metadata[_via_image_id].regions[this.trim_region_id]
+        .shape_attributes;
+
+    metaAttr = this.thisPointIsThePolygonPoint(polygon, metaAttr);
+
+    _via_img_metadata[_via_image_id].regions[
+      this.trim_region_id
+    ].shape_attributes = metaAttr;
+    drawing.setIsRegionSelected(false);
+    drawing.setUserSelRegionId(-1);
+    drawing.clearCanvas();
+    _via_load_canvas_regions();
+    if (_via_canvas_regions.length === 0) {
+      drawing.clearCanvas();
+    } else {
+      drawing.redrawRegCanvas();
+    }
+    this.destroyTrim();
+  }
+
+  thisPointIsThePolygonPoint(polygon, metaAttr) {
+    let newPoints = [];
+    let newPointsY = [];
+    let pointsX = metaAttr["all_points_x"];
+    let pointsY = metaAttr["all_points_y"];
+    let point = {
+      x: Math.round(pointsX[0] / _via_canvas_scale),
+      y: Math.round(pointsY[0] / _via_canvas_scale),
+    };
+    for (let i = 0; i < polygon.x.length; i++) {
+      for (let j = 0; j < pointsX.length; j++) {
+        point = {
+          x: Math.round(pointsX[j] / _via_canvas_scale),
+          y: Math.round(pointsY[j] / _via_canvas_scale),
+        };
+        if (polygon.x[i] === point.x && polygon.y[i] === point.y) {
+          newPoints.push(point.x * _via_canvas_scale);
+          newPointsY.push(point.y * _via_canvas_scale);
+          break;
+        }
+      }
+      for (let j = 0; j < this.trim_choose_points.length; j += 2) {
+        if (
+          polygon.x[i] === this.trim_choose_points[j] &&
+          polygon.y[i] === this.trim_choose_points[j + 1]
+        ) {
+          newPoints.push(polygon.x[i] * _via_canvas_scale);
+          newPointsY.push(polygon.y[i] * _via_canvas_scale);
+          break;
+        }
+      }
+    }
+    metaAttr["all_points_x"] = newPoints;
+    metaAttr["all_points_y"] = newPointsY;
+    return metaAttr;
+  }
+
+  /**
+   * Destroy the trimming helper variables
+   */
+  destroyTrim() {
+    this.trim_points = {};
+    this.trim_choose_points = [];
+    this.trim_choose_points_id = [];
+    this.trim_phase_id = 0;
+    this.trim_region_id = -1;
+    this.trim_line = {};
+  }
+
+  /**
+   * The function to check if the rectangle is on the vertex of the polygon
+   * @param x - x coordinate of rectangle
+   * @param y - y coordinate of the rectangle
+   * @param w - width of the rectangle
+   * @param h - height of the rectangle
+   * @param px - x coordinate of the point to be checked
+   * @param py - y coordinate of the point to be checked
+   * @returns {boolean} - true if the rect is on the vertex of the polygon
+   */
+  is_inside_points(x, y, w, h, px, py) {
+    return px > x && px < x + w && py > y && py < y + h;
+  }
+
+  /**
+   * Delete a polygon vertex
+   * @param region_id {number} - id of the region to which the vertex belongs (polygon)
+   * @param vertex_id {number} - id of the vertex to be deleted
+   * @returns {boolean} - true if vertex is deleted, false otherwise
+   */
+  polygonDelVertex(region_id, vertex_id) {
+    let rs = _via_canvas_regions[region_id].shape_attributes;
+    let npts = rs["all_points_x"].length;
+    let shape = rs["name"];
+
+    if (
+      shape !== VIA_REGION_SHAPE.POLYGON &&
+      shape !== VIA_REGION_SHAPE.POLYLINE
+    ) {
+      show_message("Vertices can only be deleted from polygon/polyline.");
+      return false;
+    }
+
+    if (npts <= 3 && shape === VIA_REGION_SHAPE.POLYGON) {
+      show_message(
+        "Failed to delete vertex because a polygon must have at least 3 vertices."
+      );
+      return false;
+    }
+
+    if (npts <= 2 && shape === VIA_REGION_SHAPE.POLYLINE) {
+      show_message(
+        "Failed to delete vertex because a polyline must have at least 2 vertices."
+      );
+      return false;
+    } // delete vertex from canvas
+
+    _via_canvas_regions[region_id].shape_attributes["all_points_x"].splice(
+      vertex_id,
+      1
+    );
+
+    _via_canvas_regions[region_id].shape_attributes["all_points_y"].splice(
+      vertex_id,
+      1
+    ); // delete vertex from image metadata
+
+    _via_img_metadata[_via_image_id].regions[region_id].shape_attributes[
+      "all_points_x"
+    ].splice(vertex_id, 1);
+
+    _via_img_metadata[_via_image_id].regions[region_id].shape_attributes[
+      "all_points_y"
+    ].splice(vertex_id, 1);
+
+    return true;
+  }
+
+  drawSelector(x, y, w, h) {
+    let N = _via_canvas_regions.length;
+    if (N === 0) {
+      return;
+    }
+    for (let i = 0; i < N; ++i) {
+      if (!_via_img_metadata[_via_image_id].isRegionLocked(i)) {
+        let rs = _via_canvas_regions[i].shape_attributes;
+        if (rs.name === "polygon") {
+          for (let j = 0; j < rs.all_points_x.length; ++j) {
+            if (
+              this.is_inside_points(
+                x,
+                y,
+                w,
+                h,
+                rs.all_points_x[j],
+                rs.all_points_y[j]
+              )
+            ) {
+              this.polygonDelVertex(i, j);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  //--------------------------------------------
+  // Etc. functions
+  //--------------------------------------------
+
+  lockRegionHandler(element) {
+    let region_id = parseInt(element.id.slice(5));
+    let context = element.id.slice(element.id.length - 8);
+    //determine id contain _context or not
+    if (context === "_context") {
+      region_id = parseInt(element.id.slice(5, element.id.length - 8));
+    }
+    if (!_via_img_metadata[_via_image_id].lockedRegions.has(region_id)) {
+      _via_img_metadata[_via_image_id].lockedRegions.delete(region_id);
+    }
+    if (context === "_context") {
+      if ($(
+        "#lock_" + region_id + "_context"
+      ).is(":checked")) {
+        _via_img_metadata[_via_image_id].addLockedRegion(region_id);
+      } else {
+        _via_img_metadata[_via_image_id].clearRegionLock(region_id);
+      }
+    } else {
+      if ($("#lock_" + region_id).is(
+        ":checked"
+      )) {
+        _via_img_metadata[_via_image_id].addLockedRegion(region_id);
+      }
+      else {
+        _via_img_metadata[_via_image_id].clearRegionLock(region_id);
+      }
+    }
+
+    if (_via_img_metadata[_via_image_id].isRegionLocked(region_id)) {
+      $("#lock_" + region_id).prop("checked", true);
+      if ($("#lock_" + region_id + "_context")) {
+        $("#lock_" + region_id + "_context").prop("checked", true);
+      }
+      return;
+    }
+    $("#lock_" + region_id).prop("checked", false);
+    if ($("#lock_" + region_id + "_context")) {
+      $("#lock_" + region_id + "_context").prop("checked", false);
+    }
+  }
+
+  updateCheckedLockHtml() {
+    if (!_via_img_metadata.hasOwnProperty(_via_image_id)) {
+      return;
+    }
+    for (let i = 0; i < _via_img_metadata[_via_image_id].regions.length; i++) {
+      if (_via_img_metadata[_via_image_id].isRegionLocked(i)) {
+        $("#lock_" + i).prop("checked", true);
+        if ($("#lock_" + i + "_context")) {
+          $("#lock_" + i + "_context").prop("checked", true);
+        }
+      }
+    }
+  }
+
+  updateUiComponents() {
+    if (!buffer.imgLoaded) {
+      return;
+    }
+
+    show_message("Updating user interface components.");
+
+    switch (_via_display_area_content_name) {
+      case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
+        image_grid_set_content_panel_height_fixed();
+        image_grid_set_content_to_current_group();
+        break;
+
+      case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
+        if (!this.is_window_resized && buffer.imgLoaded) {
+          this.is_window_resized = true;
+
+          buffer.showImage(_via_image_index);
+        }
+
+        break;
+    }
+  } // update UI components based on current state
+
+  drawingSetVariables() {
+    this.click0.x = 0;
+    this.click0.y = 0;
+    this.click1.x = 0;
+    this.click1.y = 0;
+    this.is_user_drawing_region = false;
+    this.is_window_resized = false;
+    this.is_user_resizing_region = false;
+    this.is_user_moving_region = false;
+    this.is_user_drawing_polygon = false;
+    this.is_region_selected = false;
+    this.user_sel_region_id = -1;
+  } // reset all drawing variables
+
+  toString() {
+    // for debugging
+    return "Canvas controller -> " + _via_reg_canvas;
+  }
+}
+const drawing = new Drawer();;// modal.js
+// Description: This file contains the code for the modal class.
+// Modal class is used to create a modal and show it on the screen. Interface to control the bootstrap modal.
+class Modal {
+  constructor() {
+    this.modal = $("#staticBackdropModal");
+    this.modalBody = $("#staticBackdropModal .modal-body");
+    this.modalTitle = $("#staticBackdropModal .modal-title");
+    this.modalFooter = $("#staticBackdropModal .modal-footer");
+    this.modalClose = $("#staticBackdropModal .btn-close");
+    this.modalClose.click(() => {
+      this.modal.modal("hide");
+    });
+  }
+
+  show(title, body, footer, dis_close = false) {
+    this.modalTitle.html(title);
+    this.modalBody.html(body);
+    this.modalFooter.html(footer);
+    this.modal.modal("show");
+    if (dis_close) {
+      this.modalClose.hide();
+    } else {
+      this.modalClose.show();
+    }
+  }
+
+  update(title, body, footer) {
+    this.modalTitle.html(title);
+    this.modalBody.html(body);
+    this.modalFooter.html(footer);
+  }
+
+  hide() {
+    this.modal.modal("hide");
+  }
+
+  clear() {
+    this.modalTitle.html("");
+    this.modalBody.html("");
+    this.modalFooter.html("");
+  }
+}
+const modal = new Modal();;// file_manager.js
+// Description: This file contains the code for the file manager class.
+// The file manager class is responsible for managing the file operations like downloading the region data, importing annotations from a file, etc.
+class FileManager {
+    constructor() {
+        this.modal = new Modal();
+    }
+
+    downloadAllRegionDataModal() {
+        let header = "Download all region data";
+        let body =
+            '<div class="form-check form-switch">' +
+            '<input class="form-check-input" type="checkbox" id="download_all_region_data_csv">' + '' +
+            '<label class="form-check-label" for="download_all_region_data_csv">Download as CSV</label>' +
+            '</div>' +
+            '<div class="form-check form-switch">' +
+            '<input class="form-check-input" type="checkbox" id="download_all_region_data_json">' +
+            '<label class="form-check-label" for="download_all_region_data_json">Download as JSON</label>' +
+            '</div>' +
+            '<div class="form-check form-switch">' +
+            '<input class="form-check-input" type="checkbox" id="download_all_region_data_coco">' +
+            '<label class="form-check-label" for="download_all_region_data_coco">Download as COCO</label>' +
+            '</div>';
+        let footer =
+            '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>' +
+            '<button type="button" class="btn btn-primary" onclick="fileManager.downloadAllRegionDataModalDownload()">Download</button>';
+        this.modal = new Modal();
+        this.modal.show(header, body, footer);
+    }
+
+    downloadAllRegionDataModalDownload() {
+        let download_all_region_data_csv = document.getElementById("download_all_region_data_csv").checked;
+        let download_all_region_data_json = document.getElementById("download_all_region_data_json").checked;
+        let download_all_region_data_coco = document.getElementById("download_all_region_data_coco").checked;
+        if (download_all_region_data_csv) {
+            this.downloadAllRegionData("csv");
+        }
+        if (download_all_region_data_json) {
+            this.downloadAllRegionData("json");
+        }
+        if (download_all_region_data_coco) {
+            this.downloadAllRegionData("coco");
+        }
+        this.modal.hide();
+        delete this.modal;
+    }
+
+    downloadAllRegionData(type, file_extension = type) {
+        this.packMetadata(type).then(
+            data => {
+                let blob_attr = { type: `text/${file_extension};charset=utf-8` };
+                let all_region_data_blob = new Blob(data, blob_attr);
+
+                let filename = settings.projectName || "InfarctSizeExport";
+                if (file_extension !== "csv" || file_extension !== "json") {
+                    file_extension = "json";
+                    filename += `_${type}.${file_extension}`;
+                }
+                this.saveDataToLocalFile(all_region_data_blob, filename);
+            },
+            err => {
+                show_message(`Failed to download data: [${err}]`);
+            }
+        );
+    }
+
+    packMetadata(type) {
+        let return_type = type;
+        let csvheader = "filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes";
+        let csvdata = [csvheader];
+
+        return new Promise((ok_callback, err_callback) => {
+            let returnData = return_type === "csv" ? csvDataToReturn() :
+                return_type === "coco" ? cocoDataToReturn() : [JSON.stringify(_via_img_metadata)];
+
+            ok_callback(returnData);
+        });
+
+        function csvDataToReturn() {
+            for (let image_id in _via_img_metadata) {
+                let fattr = fileManager.escapeForCsv(map_to_json(_via_img_metadata[image_id].file_attributes));
+                let prefix = `\n${_via_img_metadata[image_id].filename},${_via_img_metadata[image_id].size},"${fattr}"`;
+                let r = _via_img_metadata[image_id].regions;
+
+                if (r.length !== 0) {
+                    csvdata.push(...r.map((region, i) => {
+                        let sattr = '"' + fileManager.escapeForCsv(map_to_json(region.shape_attributes)) + '"';
+                        let rattr = '"' + fileManager.escapeForCsv(map_to_json(region.region_attributes)) + '"';
+                        return `${prefix},${r.length},${i},${sattr},${rattr}`;
+                    }));
+                } else {
+                    csvdata.push(`${prefix},0,0,"{}","{}"`);
+                }
+            }
+            return csvdata;
+        }
+
+        function cocoDataToReturn() {
+            return img_stat_set_all().then(
+                () => [fileManager.exportProjectToCocoFormat()],
+                err => {
+                    throw err;
+                }
+            );
+        }
+    }
+
+    saveDataToLocalFile(data, filename) {
+        let a = document.createElement("a");
+        let url = URL.createObjectURL(data);
+        a.href = url;
+        a.download = filename;
+
+        // simulate a mouse click event
+        let event = new MouseEvent("click", {
+            view: window,
+            bubbles: true,
+            cancelable: true,
+        });
+        a.dispatchEvent(event);
+
+        // revoke the object URL to free up memory
+        URL.revokeObjectURL(url);
+    }
+
+    escapeForCsv(s) {
+        return s.replace(/["]/g, '""');
+    }
+
+    incrementSkippedAnnotationCount() {
+        this.skipped_annotation_count++;
+        show_message(
+            "Skipped " +
+            this.skipped_annotation_count +
+            " annotations. COCO format only supports the following attribute types: " +
+            JSON.stringify(VIA_COCO_EXPORT_ATTRIBUTE_TYPE) +
+            " and region shapes: " +
+            JSON.stringify(VIA_COCO_EXPORT_RSHAPE)
+        );
+    }
+
+    shouldAssignUniqueId() {
+        for (let img_id in _via_img_metadata) {
+            if (Number.isNaN(parseInt(img_id))) {
+                return true;
+            }
+        }
+
+        for (let attr_name in project.attributes) {
+            if (!VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(project.attributes[attr_name]["type"])) {
+                continue;
+            }
+
+            for (let attr_option_id in project.attributes[attr_name]["options"]) {
+                if (this.attribute_option_id_list.includes(attr_option_id) || Number.isNaN(parseInt(attr_option_id))) {
+                    return true;
+                } else {
+                    this.attribute_option_id_list.push(attr_option_id);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    exportProjectToCocoFormat() {
+        let coco = {
+            info: {},
+            images: [],
+            annotations: [],
+            licenses: [],
+            categories: [],
+        };
+        coco["info"] = {
+            year: new Date().getFullYear(),
+            version: "1.0",
+            description:
+                "Annotations exported to COCO format using InfarctSize",
+            contributor: "",
+            url: "https://infarctsize.com/",
+            date_created: new Date().toString(),
+        };
+        coco["licenses"] = [{ id: 0, name: "Unknown License", url: "" }]; // indicates that license is unknown
+
+        this.skipped_annotation_count = 0;
+        this.attribute_option_id_list = [];
+        let assign_unique_id = this.shouldAssignUniqueId();
+
+        // add categories
+        let attr_option_id_to_category_id = {};
+        let unique_category_id = 1;
+        for (let attr_name in project.attributes.region) {
+            if (VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(project.attributes.region[attr_name]["type"])) {
+                for (let attr_option_id in project.attributes.region[attr_name]["options"]) {
+                    let category_id = assign_unique_id ? unique_category_id++ : parseInt(attr_option_id);
+                    coco["categories"].push({
+                        supercategory: attr_name,
+                        id: category_id,
+                        name: project.attributes.region[attr_name]["options"][attr_option_id],
+                    });
+                    attr_option_id_to_category_id[attr_option_id] = category_id;
+                }
+            }
+        }
+
+        // add files and all their associated annotations
+        let annotation_id = 1;
+        let unique_img_id = 1;
+        for (let img_index in _via_image_id_list) {
+            let img_id = _via_image_id_list[img_index];
+            let file_src = _via_settings["core"]["default_filepath"] + _via_img_metadata[img_id].filename;
+            if (_via_img_fileref[img_id] instanceof File) {
+                file_src = _via_img_fileref[img_id].filename;
+            }
+
+            let coco_img_id = assign_unique_id ? unique_img_id++ : parseInt(img_id);
+
+            coco["images"].push({
+                id: coco_img_id,
+                width: _via_img_stat[img_index][0],
+                height: _via_img_stat[img_index][1],
+                file_name: _via_img_metadata[img_id].filename,
+                license: 0,
+                flickr_url: file_src,
+                coco_url: file_src,
+                date_captured: "",
+            });
+
+            // add all annotations associated with this file
+            for (let rindex in _via_img_metadata[img_id].regions) {
+                let region = _via_img_metadata[img_id].regions[rindex];
+                if (!VIA_COCO_EXPORT_RSHAPE.includes(region.shape_attributes["name"])) {
+                    this.incrementSkippedAnnotationCount();
+                    continue;
+                }
+
+                let coco_annotation = via_region_shape_to_coco_annotation(region.shape_attributes);
+                coco_annotation["id"] = annotation_id;
+                coco_annotation["image_id"] = coco_img_id;
+
+                for (let region_attribute_id in region["region_attributes"]) {
+                    let region_attribute_value = region["region_attributes"][region_attribute_id];
+                    if (attr_option_id_to_category_id.hasOwnProperty(region_attribute_value)) {
+                        coco_annotation["category_id"] = attr_option_id_to_category_id[region_attribute_value];
+                        coco["annotations"].push(coco_annotation);
+                        annotation_id++;
+                    } else {
+                        this.incrementSkippedAnnotationCount();
+                    }
+                }
+            }
+        }
+
+        return [JSON.stringify(coco)];
+    }
+
+    importAnnotationsFromFile() {
+        let input = document.createElement("input");
+        input.type = "file";
+        input.accept = ".csv,.json,.coco";
+        //add event listener
+        input.addEventListener("change", this.importAnnotationsFromFileOnchange);
+        input.click();
+    }
+
+    importAnnotationsFromFileOnchange(e) {
+        let files = e.target.files;
+
+        for (let i = 0; i < files.length; i++) {
+            let file = files[i];
+            let type = file.type;
+            if (type === "application/json" || type === "text/json") {
+                this.importAnnotationsFromJson(file);
+            } else if (type === "text/csv") {
+                FileManager.loadTextFile(file, import_annotations_from_csv);
+            }
+        }
+    }
+
+
+    importAnnotationsFromJson(file) {
+        let reader = new FileReader();
+        let file_extension = file.name.split(".").pop();
+        reader.readAsText(file);
+
+        reader.onload = function (e) {
+            let data = e.target.result;
+            let parsed = JSON.parse(data);
+            if (parsed.hasOwnProperty("images") && parsed.hasOwnProperty("annotations")) {
+                FileManager.loadTextFile(file, import_coco_annotations_from_json);
+            } else {
+                FileManager.loadTextFile(file, import_annotations_from_json);
+            }
+        }
+
+    }
+
+    static loadTextFile(text_file, callback_function) {
+        if (text_file) {
+            let text_reader = new FileReader();
+            text_reader.addEventListener(
+                "progress",
+                function (e) {
+                    Message.showInfo("Loading data from file : " + text_file.name + " ... ");
+                },
+                false
+            );
+
+            text_reader.addEventListener(
+                "error",
+                function () {
+                    Message.showError(
+                        "Error loading data text file :  " + text_file.name + " !"
+                    );
+                    callback_function("");
+                },
+                false
+            );
+
+            text_reader.addEventListener(
+                "load",
+                function () {
+                    callback_function(text_reader.result);
+                },
+                false
+            );
+            text_reader.readAsText(text_file, "utf-8");
+        }
+    }
+}
+const fileManager = new FileManager();;// science_plugins.js
+// Description: This file contains the group and score functionality for the InfarctSize. Also contains the export functionality for detailed region sizes.
+// Worker function for grouping regions
+function infarctSizeAiWorker() {
+  onmessage = function (e) {
+    let slice_regions_id = JSON.parse(e.data.slice_regions_id);
+    let regions = JSON.parse(e.data.regions);
+    let update = e.data.update;
+    let grouped_regions = {};
+
+    // Pre-calculate centroids for all regions
+    let centroids = regions.map(region => centroidOfPolygon({
+      x: region.shape_attributes["all_points_x"],
+      y: region.shape_attributes["all_points_y"]
+    }));
+
+    for (let i = 0; i < slice_regions_id.length; ++i) {
+      for (let j = 0; j < slice_regions_id.length; ++j) {
+        if (i !== j) {
+          let polygon1 = regions[slice_regions_id[i]].shape_attributes;
+          let centroid = centroids[slice_regions_id[j]];
+          if (
+            isInsidePolygon(
+              polygon1["all_points_x"],
+              polygon1["all_points_y"],
+              centroid.x,
+              centroid.y
+            )
+          ) {
+            slice_regions_id.splice(j, 1);
+            j--;
+            if (j < i) {
+              i--;
+            }
+          }
+        }
+      }
+    }
+
+    for (const element of slice_regions_id) {
+      let polygon = regions[element].shape_attributes;
+      let tmp = [];
+
+      for (let j = 0; j < regions.length; j++) {
+        if (j !== element && regions[j].shape_attributes.name === "polygon") {
+          let centroid = centroids[j];
+          if (
+            isInsidePolygon(
+              polygon["all_points_x"],
+              polygon["all_points_y"],
+              centroid.x,
+              centroid.y
+            )
+          ) {
+            tmp.push(j);
+          }
+        }
+      }
+      grouped_regions[element] = tmp;
+    }
+    postMessage({
+      slice_regions_id: JSON.stringify(slice_regions_id),
+      grouped_regions: JSON.stringify(grouped_regions),
+      update: update,
+    });
+  };
+
+  /**
+   * Calculates the centroid of a polygon
+   * @param vertices - x,y -coordinates of polygon vertices
+   * @returns {{x: number, y: number}} - x,y -coordinates of centroid
+   */
+  function centroidOfPolygon(vertices) {
+    let length = vertices.x.length;
+
+    let sumX = vertices.x.reduce((a, b) => a + b, 0);
+    let sumY = vertices.y.reduce((a, b) => a + b, 0);
+
+    return { x: Math.floor(sumX / length), y: Math.floor(sumY / length) };
+  }
+
+  /**
+   * Checks if a point is inside a polygon region
+   * @param all_points_x - x-coordinates of polygon vertices
+   * @param all_points_y - y-coordinates of polygon vertices
+   * @param px - x,y -coordinates of point
+   * @param py - x,y -coordinates of point
+   * @returns {boolean} - 0 if point is outside the polygon, 1 if point is inside the polygon
+   */
+  function isInsidePolygon(all_points_x, all_points_y, px, py) {
+    if (all_points_x === undefined || all_points_y === undefined) {
+      return false;
+    }
+    if (all_points_x.length === 0 || all_points_y.length === 0) {
+      return false;
+    }
+
+    let wn = 0; // the  winding number counter
+
+
+    let n = all_points_x.length;
+    let i;
+
+    for (i = 0; i < n; ++i) {
+      let next = (i + 1) % n; // Use the next point, or the first point if at the end of the array
+
+      let is_left_value = isLeft(
+        all_points_x[i],
+        all_points_y[i],
+        all_points_x[next],
+        all_points_y[next],
+        px,
+        py
+      );
+
+      if (all_points_y[i] <= py) {
+        if (all_points_y[next] > py && is_left_value > 0) {
+          ++wn;
+        }
+      } else {
+        if (all_points_y[next] <= py && is_left_value < 0) {
+          --wn;
+        }
+      }
+    }
+
+    return wn !== 0;
+  }
+
+  /**
+   * Checks if a point is left of a line
+   * @param x0 - x-coordinate of first point of line
+   * @param y0 - y-coordinate of first point of line
+   * @param x1 - x-coordinate of second point of line
+   * @param y1 - y-coordinate of second point of line
+   * @param x2 - x-coordinate of point
+   * @param y2 - y-coordinate of point
+   * @returns {number} - >0 if point is left of line, =0 if point is on line, <0 if point is right of line
+   */
+  function isLeft(x0, y0, x1, y1, x2, y2) {
+    return (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0);
+  } //helper function for isInsidePolygon()
+}
+
+/**
+* @fileOverview This file contains the Infarct Size AI plugin.
+* name: "InfarctSizeAI",
+* description: "Plugins for SAA Infarct Size AI",
+* version: "0.1.0",
+* @constructor
+*/
+function Science_plugins() {
+  this.grouped_regions = {};
+  this.slice_regions_id = [];
+  this.GroupingWorker = new Worker(
+    URL.createObjectURL(
+      new Blob(["(" + infarctSizeAiWorker.toString() + ")()"], {
+        type: "text/javascript",
+      })
+    )
+  );
+  this.GroupingWorker.onmessage = this.groupingWorkerOnMessage.bind(this);
+}
+
+Science_plugins.prototype.updateSliceRegion = function (img_id = _via_image_id, update = true) {
+  let groupBy = [];
+  for (let i = 0; i < _via_img_metadata[img_id].regions.length; ++i) {
+    if (
+      _via_img_metadata[img_id].regions[i].region_attributes.Type ===
+      "Slice"
+    ) {
+      groupBy.push(i);
+    }
+  }
+  if (groupBy.length === 0) {
+    console.log("No slice regions found");
+    return false;
+  }
+  if (groupBy === _via_img_metadata[img_id].groupedRegions.groupBy) {
+    console.log("No change in slice regions")
+    return false;
+  }
+  _via_img_metadata[img_id].clearGroups();
+  this.GroupingWorker.postMessage({
+    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),
+    update: update,
+  });
+};
+
+// Grouping regions on other thread
+Science_plugins.prototype.groupingWorkerOnMessage = function (e) {
+  let groups = JSON.parse(e.data.grouped_regions);
+  for (const element in groups) {
+    _via_img_metadata[_via_image_id].addGroupedRegion(parseInt(element), groups[element])
+  }
+  _via_img_metadata[_via_image_id].groupedRegions.groupBy = JSON.parse(e.data.slice_regions_id);
+  if (e.data.update) {
+    sidebar.annotation_editor_update_content();
+  }
+};
+
+// Grouping modify name to group
+Science_plugins.prototype.changeGroupIdentifier = function (dom) {
+  // get value from input by onclick event
+  let groupId = parseInt(dom.id.split("_")[1]);
+  let newIdentifier = dom.value
+  _via_img_metadata[_via_image_id].groupedRegions.groupIDs.set(groupId, newIdentifier);
+}
+
+// Get and select all regions in group and highlight the row in the annotation editor
+Science_plugins.prototype.selectAllRegionsInGroup = function (dom) {
+  let groupId = parseInt(dom.id.split("_")[2]);
+  toggle_all_regions_selection(false);
+  sidebar.annotation_editor_clear_row_highlight();
+  _via_region_selected_flag.add(groupId);
+  let regions = _via_img_metadata[_via_image_id].getGroupedRegion(groupId);
+  for (const region in regions) {
+    _via_region_selected_flag.add(regions[region]);
+  }
+  sidebar.annotation_editor_highlight_row(groupId);
+  drawing.setIsRegionSelected(true);
+  drawing.setUserSelRegionId(groupId);
+  drawing.redrawRegCanvas();
+
+}
+
+// Get the score of a region
+Science_plugins.prototype.getScores = function (region_id) {
+  if (_via_img_metadata[_via_image_id].regions[region_id].hasOwnProperty("score")) {
+    return _via_img_metadata[_via_image_id].regions[region_id].score;
+  }
+  return "n/a";
+}
+
+// Set the score of a region
+Science_plugins.prototype.setScores = function (region_id, score) {
+  _via_img_metadata[_via_image_id].regions[region_id].score = score;
+  sidebar.annotation_editor_update_content();
+}
+
+// Update the scores of the regions based on the group
+Science_plugins.prototype.updateScoresBasedOnGroup = function () {
+  // update the scores based on the group and if there is a n/a score in the group from the same type then set the score to n/a
+  for (const element in _via_img_metadata[_via_image_id].groupedRegions.groups) {
+    let isModified = {
+      Infarct: false,
+      Risk: false,
+    };
+    for (let i = 0; i < _via_img_metadata[_via_image_id].groupedRegions.groups[element].length; i++) {
+      let region = _via_img_metadata[_via_image_id].
+        regions[_via_img_metadata[_via_image_id].groupedRegions.groups[element][i]];
+      if (region.hasOwnProperty("score") && region.score === "n/a") {
+        switch (region.region_attributes.Type) {
+          case "Infarct":
+            isModified.Infarct = true;
+            break;
+          case "Risk":
+            isModified.Risk = true;
+            break;
+        }
+      }
+    }
+    if (isModified.Infarct || isModified.Risk) {
+      for (let i = 0; i < _via_img_metadata[_via_image_id].groupedRegions.groups[element].length; i++) {
+        let region = _via_img_metadata[_via_image_id].
+          regions[_via_img_metadata[_via_image_id].groupedRegions.groups[element][i]];
+        if (isModified.Infarct && region.region_attributes.Type === "Infarct") {
+          region.score = "n/a";
+        }
+        if (isModified.Risk && region.region_attributes.Type === "Risk") {
+          region.score = "n/a";
+        }
+      }
+    }
+  }
+}
+
+// Efficiently calculate the area of a polygon
+Science_plugins.prototype.calcPolygonArea = function (xCoordinates, yCoordinates) {
+  let total = 0;
+  const n = xCoordinates.length;
+
+  for (let i = 0; i < n; i++) {
+    const nextIndex = (i + 1) % n;
+    const addTerm = xCoordinates[i] * yCoordinates[nextIndex];
+    const subTerm = yCoordinates[i] * xCoordinates[nextIndex];
+    total += addTerm - subTerm;
+  }
+
+  return 0.5 * Math.abs(total);
+}
+
+// Export the region sizes to a csv file
+Science_plugins.prototype.ExportArea = async function () {
+  let rows = [
+    ["Filename", "File_ID", "Treatment_ID", "Slice_ID", "Slice_area", "Infract_area", "Risk_area", "Slice_score", "Infract_score", "Risk_score"]
+  ];
+  let done = 0;
+  for (let img_index in _via_image_id_list) {
+    let img_id = _via_image_id_list[img_index];
+    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;
+      }
+    }
+    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);
+        }
+        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;
+            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;
+              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++;
+    });
+
+  }
+  let csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n");
+  let encodedUri = encodeURI(csvContent);
+  let link = document.createElement("a");
+  link.setAttribute("href", encodedUri);
+  link.setAttribute("download", "RegionSizes.csv");
+  document.body.appendChild(link);
+  link.click();
+};
+
+const plugin = new Science_plugins();;// img_manipulation.js
+// Description: This file contains the code for image manipulation, like change brightness, contrast, hue, saturation, resize, scale, rotate, flip, mirror.
+// ImgManipulation class is used to manipulate the image.
+class ImgManipulation {
+  constructor() {
+    this.image = null
+
+    this.contrast = 100;
+    this.brightness = 100;
+    this.hue = 0;
+    this.saturation = 100;
+
+    this.brightnessSlider = $("#brightnessRange");
+    this.contrastSlider = $("#contrastRange");
+    this.hueSlider = $("#hueRange");
+    this.saturationSlider = $("#saturationRange");
+
+    this.setSliderValues();
+    this.addEventListeners();
+  }
+
+  addEventListeners() {
+    this.brightnessSlider.on("input", () => {
+      this.brightness = this.brightnessSlider.val();
+      this.changeImgSettings();
+    });
+    this.contrastSlider.on("input", () => {
+      this.contrast = this.contrastSlider.val();
+      this.changeImgSettings();
+    });
+    this.hueSlider.on("input", () => {
+      this.hue = this.hueSlider.val();
+      this.changeImgSettings();
+    });
+    this.saturationSlider.on("input", () => {
+      this.saturation = this.saturationSlider.val();
+      this.changeImgSettings();
+    });
+  }
+
+  hook(img = _via_current_image) {
+    this.image = document.getElementById(img.id);
+    this.image.style.transformOrigin = "top left";
+  }
+
+  setSliderValues() {
+    this.brightnessSlider.val(this.brightness);
+    this.contrastSlider.val(this.contrast);
+    this.hueSlider.val(this.hue);
+    this.saturationSlider.val(this.saturation);
+  }
+
+  backToSidebar() {
+    $("#image_man_container").addClass("d-none");
+    $("#sidebar_container").removeClass("d-none");
+  }
+
+  async show() {
+    this.image.classList.add("position-absolute");
+    this.image.classList.remove("d-none");
+  }
+
+  changeImgSettings() {
+    this.image.style.filter = `brightness(${this.brightnessSlider.val()}%) contrast(${this.contrastSlider.val()}%) hue-rotate(${this.hueSlider.val()}deg) saturate(${this.saturationSlider.val()}%)`;
+  }
+
+  //reset all filters
+  resetFilters() {
+    this.brightness = 100;
+    this.contrast = 100;
+    this.hue = 0;
+    this.saturation = 100;
+    this.setSliderValues();
+    this.changeImgSettings();
+  }
+
+  // resize image
+  resize(w = _via_current_image_width, h = _via_current_image_height) {
+    this.image.width = w;
+    this.image.height = h;
+  }
+
+  scaleImage(scale) {
+    this.image.style.transform = `scale(${scale})`;
+  }
+
+  // // rotate image
+  // rotate(angle) {
+  //   this.ctx.rotate(angle);
+  //   this.image.style.transform = `rotate(${angle}deg)`;
+  // }
+  //
+  // // flip image
+  // flip() {
+  //   this.ctx.scale(-1, 1);
+  //   this.image.style.transform = "scaleX(-1)";
+  // }
+  //
+  // // mirror image
+  // mirror() {
+  //   this.ctx.scale(1, -1);
+  //   this.image.style.transform = "scaleY(-1)";
+  // }
+
+}
+const image = new ImgManipulation();;// zoom.js
+// Description: This file contains the code for zooming in and out of the image.
+// Zoom class is responsible for handling the zooming functionality and controlling external dependency called Panzoom.
+class Zoom {
+  constructor() {
+    this.panzoom = Panzoom(image_panel, {
+      maxScale: 12,
+      minScale: 0.1,
+      disablePan: true,
+    });
+    this.addEventListeners();
+    this.isZooming = false;
+    this.init = true;
+  }
+
+  setStartScale(scale) {
+    let startX = 0;
+    if (_via_current_image_width > _via_current_image_height) {
+      startX = _via_current_image_width / 2 - _via_current_image_width * scale / 2
+    } else {
+      startX = _via_current_image_height / 2 - _via_current_image_height * scale / 2
+    }
+    this.panzoom.setOptions({
+      startScale: scale,
+      startX: -startX,
+    });
+    // fix translation
+
+  }
+
+  addEventListeners() {
+    image_panel.addEventListener("wheel", (e) => {
+      e.preventDefault();
+      this.zoomWithWheel(e);
+    });
+    image_panel.addEventListener("panzoomzoom", (_) => {
+      if (this.init) {
+        this.init = false;
+      } else {
+        this.startPanzoomOperation();
+      }
+    });
+    image_panel.addEventListener("panzoomend", (_) => {
+      this.endPanzoomOperation();
+    });
+
+  }
+  startPanzoomOperation() {
+    this.isZooming = true;
+  }
+
+  endPanzoomOperation() {
+    if (this.isZooming) {
+      this.fixCanvasBlur();
+      this.isZooming = false;
+    }
+  }
+
+  showFixCanvasBlurButton() {
+    let button = document.createElement("button");
+    button.innerHTML = "Fix canvas blur";
+    button.style.position = "absolute";
+    button.style.color = "white";
+    button.style.top = "8px";
+    button.style.right = "80px";
+    button.style.zIndex = "100";
+    button.classList.add("btn");
+    button.classList.add("btn-primary");
+    button.onclick = () => {
+      this.fixCanvasBlur();
+      document.body.removeChild(button);
+    };
+    setTimeout(() => {
+      document.body.removeChild(button);
+    }, 5000);
+    document.body.appendChild(button);
+  }
+
+  zoomWithWheel(e) {
+    this.panzoom.zoomWithWheel(e);
+  }
+
+  handleMoseDown(e) {
+    this.panzoom.handleDown(e);
+  }
+
+  handleMoseUp(e) {
+    this.panzoom.handleUp(e);
+  }
+
+  handleMoseMove(e) {
+    this.panzoom.handleMove(e);
+  }
+
+  fixCanvasBlur() {
+    if (!buffer.imgLoaded) {
+      return;
+    }
+    requestAnimationFrame(() => {
+      let currentScale = this.panzoom.getScale();
+      if (currentScale > 1) {
+        currentScale = 1;
+      }
+      drawing.setBoundarySize(Math.round(4 / currentScale));
+      drawing.setFontSize(Math.round(6 / currentScale));
+      drawing.redrawRegCanvas();
+      _via_reg_canvas.focus();
+    });
+  }
+
+  zoomIn() {
+    this.panzoom.zoomIn();
+  }
+
+  zoomOut() {
+    this.panzoom.zoomOut();
+  }
+
+  resetZoom() {
+    this.panzoom.reset();
+    image.scaleImage(1);
+    _via_reg_canvas.style.transform = "scale(1)";
+    _via_canvas_scale = 1;
+  }
+
+  enablePan() {
+    this.panzoom.setOptions({ disablePan: false });
+  }
+
+  disablePan() {
+    this.panzoom.setOptions({ disablePan: true });
+  }
+}
+const zoom = new Zoom();;// file_metadata.js
+// Description: FileMetadata class to store metadata of a file
+// FileMetadata class is used to store metadata of a file. It stores the filename, size, regions, file attributes, locked regions, grouped regions, and autoAnnotated flag. It also provides methods to set and get the filename, size, regions, file attributes, locked regions, grouped regions, and autoAnnotated flag. It also provides methods to add a region, add a locked region, check if a region is locked, clear locked regions, clear region lock, clear regions, load from JSON, add a grouped region, get a grouped region, clear grouped regions, clear groups, clear grouped region, check if it is groupable, and check if it is grouped key.
+class FileMetadata {
+    constructor(filename, size) {
+        this.filename = filename;
+        this.size = size; // file size in bytes
+        this.regions = []; // array of File_Region()
+        this.file_attributes = {
+            ID: { type: "text", description: "", default_value: "" },
+            Treatment: { type: "text", description: "", default_value: "" },
+        };
+        this.fileAttributes = new Map(
+            [
+                ["ID", { type: "text", description: "", default_value: "" }],
+                ["Treatment", { type: "text", description: "", default_value: "" }],
+            ]
+        )
+        this.lockedRegions = new Set();
+        this.groupedRegions = {
+            groupBy: new Array(),
+            groups: new Map(),
+            groupIDs: new Map()
+        };
+        this.autoAnnotated = false;
+    }
+
+    setFilename(filename) {
+        this.filename = filename;
+    }
+
+    setFileAttributes(file_attributes) {
+        this.file_attributes = file_attributes;
+    }
+
+    addRegion(region) {
+        this.regions.push(region);
+    }
+
+    addLockedRegion(region) {
+        this.lockedRegions.add(region);
+    }
+
+    isRegionLocked(region) {
+        return this.lockedRegions.has(region);
+    }
+
+    clearLockedRegions() {
+        this.lockedRegions.clear();
+    }
+
+    clearRegionLock(region) {
+        this.lockedRegions.delete(region);
+    }
+
+    clearRegions() {
+        this.regions = [];
+    }
+
+    loadFromJSON(data) {
+        if (data.regions) {
+            this.regions = data.regions;
+        }
+        if (data.file_attributes) {
+            this.file_attributes = data.file_attributes;
+        }
+        if (data.lockedRegions.dataType === "Set") {
+            this.lockedRegions = new Set(data.lockedRegions.value);
+        } else {
+            this.lockedRegions = new Set();
+        }
+        if (data.groupedRegions.groupBy) {
+            this.groupedRegions.groupBy = new Set(data.groupedRegions.groupBy.value);
+        }
+        if (data.groupedRegions.groups) {
+            this.groupedRegions.groups = new Map(data.groupedRegions.groups.value);
+        }
+        if (data.groupedRegions.groupIDs) {
+            this.groupedRegions.groupIDs = new Map(data.groupedRegions.groupIDs.value);
+        }
+        if (data.autoAnnotated) {
+            this.autoAnnotated = data.autoAnnotated;
+        }
+    }
+
+    addGroupedRegion(region, group) {
+        this.groupedRegions.groups.set(region, group);
+    }
+
+    getGroupedRegion(region) {
+        return this.groupedRegions.groups.get(region);
+    }
+
+    clearGroupedRegions() {
+        this.groupedRegions.groups = new Map();
+        this.groupedRegions.groupBy = [];
+    }
+
+    clearGroups() {
+        this.groupedRegions.groups = new Map();
+    }
+
+    clearGroupedRegion(region) {
+        this.groupedRegions.groups.delete(region);
+    }
+
+    isGroupable() {
+        return this.groupedRegions.groupBy.length > 0;
+    }
+
+    isGroupedKey(region) {
+        return this.groupedRegions.groups.has(region);
+    }
+
+
+};// buffer.js
+// Description: This file contains the code for the ImageBuffer class.
+// ImageBuffer class is responsible for loading images into the buffer and displaying them on the image panel. It also handles the preloading of images to improve the user experience. (mainly legacy code from VIA)
+class ImageBuffer {
+  constructor() {
+    this.loading = $("#loading");
+    this.imgLoading = false; //
+    this.imgLoaded = false;
+    this.bufferIdList = []; //_via_buffer_img_index_list
+    this.bufferTimestamp = []; //_via_buffer_img_shown_timestamp
+    this.preLoadedId = -1; //_via_buffer_preload_img_index
+    this.preLoadPromiseList = [];
+  }
+
+  stopLoading() {
+    this.imgLoading = false;
+  }
+
+  showImage(idx) {
+    this.loading.removeClass("d-none");
+    if (this.imgLoading) {
+      return;
+    }
+
+    let imgID = _via_image_id_list[idx];
+
+    if (!_via_img_metadata.hasOwnProperty(imgID)) {
+      this.loading.addClass("d-none");
+      Message.showError("The requested image does not exist!");
+      return;
+    }
+
+    if (_via_img_fileref[imgID] === undefined || !(_via_img_fileref[imgID] instanceof File)) {
+      if (_via_img_src[imgID] === undefined || _via_img_src[imgID] === "") {
+        if (is_url(_via_img_metadata[imgID].filename)) {
+          _via_img_src[imgID] = _via_img_metadata[imgID].filename;
+          this.showImage(idx);
+          return;
+        } else {
+          this.searchPathSearch(idx);
+        }
+      }
+    }
+
+    if (this.bufferIdList.includes(idx)) {
+      this.imgLoaded = false;
+      this.showImageFromBuffer(idx).then(
+        function (_) {
+          Promise.all(this.preLoadPromiseList).then(function (_) {
+            this.preLoadPromiseList = [];
+            let preloadPromise = this.startPreload(idx, 0);
+            this.preLoadPromiseList.push(preloadPromise);
+          }.bind(this));
+          undoredo_worker.postMessage({
+            commands: "reset",
+          });
+        }.bind(this),
+        function (errImgIndex) {
+          console.log("showImageFromBuffer() failed for file: " + _via_image_filename_list[errImgIndex]);
+          this.loading.addClass("d-none");
+          this.imgLoading = false;
+        }.bind(this)
+      );
+    } else {
+      this.imgLoading = true;
+      this.addImageToBuffer(idx).then(
+        function (_) {
+          this.imgLoading = false;
+          this.showImage(idx);
+        }.bind(this),
+        function (_) {
+          this.imgLoading = false;
+          this.loading.addClass("d-none");
+          Message.showError("The requested image does not exist!");
+          show_page_404(idx);
+        }.bind(this)
+      );
+
+    }
+
+  }
+
+  searchPathSearch(idx) {
+    let search_path_list = _via_file_get_search_path_list();
+    if (search_path_list.length === 0) {
+      search_path_list.push(""); // search using just the filename
+    }
+
+    _via_file_resolve(idx, search_path_list).then(
+      function (_) {
+        this.showImage(idx);
+      }.bind(this),
+      function (_) {
+        this.loading.addClass("d-none");
+        Message.showError("The requested image does not exist!");
+        show_page_404(idx);
+      }.bind(this)
+    );
+  }
+
+  showImageFromBuffer(idx) {
+    return new Promise(function (ok_callback, err_callback) {
+      this.hideCurrentImage();
+
+      let domID = "bim" + idx;
+      _via_current_image = document.getElementById(domID);
+      if (!_via_current_image) {
+        err_callback(idx);
+        return;
+      }
+      //_via_current_image.classList.add('d-none'); // now show the new image
+      _via_image_index = idx;
+      _via_image_id = _via_image_id_list[_via_image_index];
+      _via_current_image_filename = _via_img_metadata[_via_image_id].filename;
+      this.imgLoaded = true;
+
+      image.hook();
+
+      this.postImageLoad(idx);
+
+      ok_callback(idx);
+    }.bind(this));
+  }
+
+  postImageLoad(idx) {
+    _via_img_metadata[_via_image_id].clearGroupedRegions();
+
+    let arr_idx = this.bufferIdList.indexOf(idx);
+    this.bufferTimestamp[arr_idx] = Date.now();
+
+    drawing.drawingSetVariables();
+
+    _via_current_image_width = _via_current_image.naturalWidth;
+    _via_current_image_height = _via_current_image.naturalHeight;
+
+    if (_via_current_image_width === 0 || _via_current_image_height === 0) {
+      // for error image icon
+      _via_current_image_width = 640;
+      _via_current_image_height = 480;
+    }
+
+    let de = document.documentElement;
+    let imgPanelWidth = de.clientWidth - leftsidebar.clientWidth - 20;
+    if (imgPanelWidth < 50) {
+      imgPanelWidth = de.clientWidth;
+    }
+    if (leftsidebar.style.display === "none") {
+      imgPanelWidth = de.clientWidth;
+    }
+    let imgPanelHeight = de.clientHeight - 2 * ui_top_panel.offsetHeight;
+
+    _via_canvas_width = _via_current_image_width;
+    _via_canvas_height = _via_current_image_height;
+
+    if (_via_canvas_width > imgPanelWidth) {
+      // resize image to match the panel width
+      let scale_width = imgPanelWidth / _via_current_image.naturalWidth;
+      _via_canvas_width = imgPanelWidth;
+      _via_canvas_height = _via_current_image.naturalHeight * scale_width;
+    }
+    if (_via_canvas_height > imgPanelHeight) {
+      // resize further image if its height is larger than the image panel
+      let scale_height = imgPanelHeight / _via_canvas_height;
+      _via_canvas_height = imgPanelHeight;
+      _via_canvas_width = _via_canvas_width * scale_height;
+    }
+    _via_canvas_width = Math.round(_via_canvas_width);
+    _via_canvas_height = Math.round(_via_canvas_height);
+
+    _via_canvas_scale = _via_current_image.naturalWidth / _via_canvas_width
+    zoom.setStartScale(1 / _via_canvas_scale);
+    _via_canvas_scale_without_zoom = _via_canvas_scale;
+    _via_canvas_scale = 1;
+
+    drawing.setAllCanvasSize(_via_current_image_width, _via_current_image_height);
+
+    // reset all regions to "not selected" state
+    toggle_all_regions_selection(false);
+
+    // ensure that all the canvas are visible
+    set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE);
+
+    // update img_fn_list
+    sidebar.img_fn_list_ith_entry_selected(_via_image_index, true);
+    sidebar.img_fn_list_scroll_to_current_file();
+    sidebar.annotation_editor_update_content();
+
+    _via_reg_canvas.focus();
+    zoom.resetZoom();
+    _via_load_canvas_regions(); // image to canvas space transform
+    drawing.redrawRegCanvas();
+    image.resize();
+    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) {
+        plugin.updateSliceRegion()
+      }
+    }
+  }
+
+  addImageToBuffer(idx) {
+    return new Promise(function (ok_callback, err_callback) {
+      if (this.bufferIdList.includes(idx)) {
+        ok_callback(idx);
+        return;
+      }
+
+      let imgID = _via_image_id_list[idx];
+      if (!_via_img_metadata.hasOwnProperty(imgID)) {
+        err_callback(idx);
+        return;
+      }
+
+      if (_via_img_fileref[imgID] instanceof File) {
+        this.loadFromFileRef(idx, imgID).then(
+          function (okImgIndex) {
+            ok_callback(okImgIndex);
+          },
+          function (errImgIndex) {
+            err_callback(errImgIndex);
+            this.loading.addClass("d-none");
+          }
+        );
+        return;
+      }
+
+      if (_via_img_src[imgID] === undefined || _via_img_src[imgID] === "") {
+        err_callback(idx);
+        this.loading.addClass("d-none");
+        return;
+      } else {
+        this.loadFromSrc(idx, imgID).then(
+          function (okImgIndex) {
+            ok_callback(okImgIndex);
+          },
+          function (errImgIndex) {
+            err_callback(errImgIndex);
+            this.loading.addClass("d-none");
+          }
+        );
+      }
+    }.bind(this), false);
+  }
+
+  loadFromFileRef(idx, imgID) {
+    return new Promise(function (ok_callback, err_callback) {
+      let tmpUrl = URL.createObjectURL(_via_img_fileref[imgID]);
+      let imgElement = new Image();
+      imgElement.id = "bim" + idx;
+      imgElement.src = tmpUrl;
+      imgElement.alt = "Image loaded from base64 data of a local file selected by user.";
+      imgElement.classList.add("d-none");
+      image_panel.insertBefore(imgElement, _via_reg_canvas);
+      imgElement.addEventListener("error", function () {
+        URL.revokeObjectURL(tmpUrl);
+        project.fileLoadOnFail(idx);
+        err_callback(idx);
+        this.loading.addClass("d-none");
+      });
+      imgElement.addEventListener("load", function () {
+        URL.revokeObjectURL(tmpUrl);
+        img_stat_set(idx, [imgElement.naturalWidth, imgElement.naturalHeight]);
+        image_panel.insertBefore(imgElement, _via_reg_canvas);
+        project.fileLoadOnSuccess(idx);
+        sidebar.img_fn_list_ith_entry_remove_css_class(idx, "text-muted");
+        let arr_idx = this.bufferIdList.length;
+        this.bufferIdList.push(idx);
+        this.bufferTimestamp[arr_idx] = Date.now();
+        ok_callback(idx);
+      }.bind(this));
+    }.bind(this), false);
+  }
+
+  loadFromSrc(idx, imgID) {
+    return new Promise(function (ok_callback, err_callback) {
+      let imageElement = new Image();
+      imageElement.id = "bim" + idx;
+      _via_img_src[imgID] = _via_img_src[imgID].replace("#", "%23");
+      imageElement.src = _via_img_src[imgID];
+      if (_via_img_src[imgID].startsWith("data:image")) {
+        imageElement.alt = "Source: image data in base64 format";
+      } else {
+        imageElement.alt = "Source: " + _via_img_src[imgID];
+      }
+      image_panel.insertBefore(imageElement, _via_reg_canvas);
+      imageElement.addEventListener("abort", function () {
+        project.fileLoadOnFail(idx);
+        err_callback(idx);
+      });
+      imageElement.addEventListener("error", function () {
+        project.fileLoadOnFail(idx);
+        err_callback(idx);
+        this.loading.addClass("d-none");
+      });
+      imageElement.addEventListener("load", function () {
+        img_stat_set(idx, [imageElement.naturalWidth, imageElement.naturalHeight]);
+        image_panel.insertBefore(imageElement, _via_reg_canvas);
+        project.fileLoadOnSuccess(idx);
+        sidebar.img_fn_list_ith_entry_remove_css_class(idx, "text-muted");
+        let arr_idx = this.bufferIdList.length;
+        this.bufferIdList.push(idx);
+        this.bufferTimestamp[arr_idx] = Date.now();
+        ok_callback(idx);
+      }.bind(this), false);
+    }.bind(this), false);
+  }
+
+  hideCurrentImage() {
+    sidebar.img_fn_list_ith_entry_selected(_via_image_index, false);
+    drawing.clearCanvas(); // clear old region shapes
+    if (_via_current_image) {
+      _via_current_image.classList.add("d-none");
+    }
+  }
+
+  startPreload(idx, _) {
+    return new Promise(function (ok_callback, _) {
+      this.preLoadedId = idx;
+      this.preloadImage(this.preLoadedId, 0).then(
+        function (ok_img_index_list) {
+          ok_callback(ok_img_index_list);
+        }
+      );
+    }.bind(this));
+  }
+
+  preloadImage(idx, preloadIdx) {
+    return new Promise(function (ok_callback, _) {
+      let preloadImgIdx = this.getPreloadImgIndex(
+        idx,
+        preloadIdx
+      );
+
+      if (this.preLoadedId !== _via_image_index) {
+        ok_callback([]);
+        return;
+      }
+
+      // ensure that there is sufficient buffer space left for preloading image
+      if (this.bufferIdList.length > settings.bufferSize) {
+        while (this.bufferIdList.length > settings.bufferSize) { //@todo setting
+          console.log("Buffer full. Removing least useful image ...");
+          console.log("Buffer size: ", this.bufferIdList.length);
+          console.log("Buffer: ", settings.bufferSize);
+          this.removeLeastUsefulImage();
+          if (_via_image_index !== this.preLoadedId) {
+            // current image has changed therefore, we need to cancel this preload operation
+            ok_callback([]);
+            return;
+          }
+        }
+      }
+
+      this.addImageToBuffer(preloadImgIdx).then(
+        function (ok_img_index) {
+          if (_via_image_index !== this.preLoadedId) {
+            ok_callback([ok_img_index]);
+            return;
+          }
+          let nextPreloadIdx = preloadIdx + 1;
+          if (nextPreloadIdx !== VIA_IMG_PRELOAD_COUNT) {
+            this.preloadImage(idx, nextPreloadIdx).then(
+              function (ok_img_index_list) {
+                ok_img_index_list.push(ok_img_index);
+                ok_callback(ok_img_index_list);
+              }
+            );
+          } else {
+            ok_callback([ok_img_index]);
+          }
+        }.bind(this),
+        function (_) {
+          // continue with preload of other images in sequence
+          let nextPreloadIdx = preloadIdx + 1;
+          if (nextPreloadIdx !== VIA_IMG_PRELOAD_COUNT) {
+            this.preloadImage(preloadImgIdx, nextPreloadIdx).then(
+              function (ok_img_index_list) {
+                ok_callback(ok_img_index_list);
+              }
+            );
+          } else {
+            ok_callback([]);
+          }
+        }.bind(this)
+      );
+    }.bind(this));
+  }
+
+  getPreloadImgIndex(idx, preloadIdx) {
+    let preloadImgIdx = idx + VIA_IMG_PRELOAD_INDICES[preloadIdx];
+    if ((preloadImgIdx < 0) || (preloadImgIdx >= _via_img_count)) {
+      if (preloadImgIdx < 0) {
+        preloadImgIdx = _via_img_count + preloadImgIdx;
+      } else {
+        preloadImgIdx = preloadImgIdx - _via_img_count;
+      }
+    }
+    return preloadImgIdx;
+  }
+
+  removeLeastUsefulImage() {
+    let notInPreload = this.getNotInPreloadList();
+    let oldestBufferIdx = this.getOldestInList(notInPreload);
+
+    if (this.bufferIdList[oldestBufferIdx] !== _via_image_index) {
+      this.removeImageFromBuffer(oldestBufferIdx);
+    } else {
+      let furthestBufferIdx =
+        this.getFurthestFromCurrentImg();
+      this.removeImageFromBuffer(furthestBufferIdx);
+    }
+  }
+
+  removeImageFromBuffer(_idx) {
+    let idx = this.bufferIdList[_idx];
+    let domID = "bim" + idx;
+    let e = document.getElementById(domID);
+    if (e) {
+      this.bufferIdList.splice(_idx, 1);
+      this.bufferTimestamp.splice(_idx, 1);
+      e.parentNode.removeChild(e);
+      sidebar.img_fn_list_ith_entry_add_css_class(idx, "text-muted");
+    }
+  }
+
+  emptyBuffer() {
+    let i, n;
+    n = this.bufferIdList.length;
+    for (i = 0; i < n; ++i) {
+      let idx = this.bufferIdList[i];
+      let domID = "bim" + idx;
+      let e = document.getElementById(domID);
+      if (e) {
+        e.parentNode.removeChild(e);
+        sidebar.img_fn_list_ith_entry_add_css_class(idx, "text-muted");
+      }
+    }
+    this.bufferIdList = [];
+    this.bufferTimestamp = [];
+  }
+
+  getOldestInList(notInPreload) {
+    let i;
+    let n = notInPreload.length;
+    let oldestBufferIdx = -1;
+    let oldestBufferTimestamp = Date.now();
+
+    for (i = 0; i < n; ++i) {
+      let _idx = notInPreload[i];
+      if (this.bufferTimestamp[_idx] < oldestBufferTimestamp) {
+        oldestBufferTimestamp = this.bufferTimestamp[i];
+        oldestBufferIdx = i;
+      }
+    }
+    return oldestBufferIdx;
+  }
+
+  getFurthestFromCurrentImg() {
+    let i, dist1, dist2, dist;
+    let n = this.bufferIdList.length;
+    let furthestIdx = 0;
+    dist1 = Math.abs(this.bufferIdList[0] - _via_image_index);
+    dist2 = _via_img_count - dist1; // assuming the list is circular
+    let furthestDist = Math.min(dist1, dist2);
+
+    for (i = 1; i < n; ++i) {
+      dist1 = Math.abs(this.bufferIdList[i] - _via_image_index);
+      dist2 = _via_img_count - dist1; // assuming the list is circular
+      dist = Math.min(dist1, dist2);
+      // image has been seen by user at least once
+      if (dist > furthestDist) {
+        furthestDist = dist;
+        furthestIdx = i;
+      }
+    }
+    return furthestIdx;
+
+  }
+
+  getNotInPreloadList() {
+    let preloadList = this.getPreloadList()
+    let notInPreload = [];
+    for (let i = 0; i < this.bufferIdList.length; i++) {
+      if (!preloadList.includes(this.bufferIdList[i])) {
+        notInPreload.push(i);
+      }
+    }
+    return notInPreload;
+  }
+
+  getPreloadList() {
+    let preloadList = [_via_image_index];
+    for (let i = 0; i < VIA_IMG_PRELOAD_COUNT; ++i) {
+      let preloadIdx = _via_image_index + VIA_IMG_PRELOAD_INDICES[i];
+      if (preloadIdx < 0) {
+        preloadIdx = _via_img_count + preloadIdx;
+      }
+      if (preloadIdx >= _via_img_count) {
+        preloadIdx = preloadIdx - _via_img_count;
+      }
+      preloadList.push(preloadIdx);
+    }
+    return preloadList;
+  }
+
+}
+const buffer = new ImageBuffer();;/*
+  VGG Image Annotator (via)
+  www.robots.ox.ac.uk/~vgg/software/via/
+
+  Copyright (c) 2016-2019, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors.
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+  Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+  POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+  Links:
+  - https://gitlab.com/vgg/via/blob/master/Contributors.md : list of developers who have contributed code to the VIA project.
+  - https://gitlab.com/vgg/via/blob/master/CodeDoc.md : source code documentation
+  - https://gitlab.com/vgg/via/blob/master/CONTRIBUTING.md : guide for contributors
+
+  This source code can be grouped into the following categories:
+  - Data structure for annotations
+  - Initialization routine
+  - Handlers for top navigation bar
+  - Local file uploaders
+  - Data Importer
+  - Data Exporter
+  - Maintainers of user interface
+  - Image click handlers
+  - Canvas update routines
+  - Region collision routines
+  - Shortcut key handlers
+  - Persistence of annotation data in browser cache (i.e. localStorage)
+  - Handlers for attributes input panel (spreadsheet like user input panel)
+*/
+
+//
+// Data structure [1] to store metadata about file and regions
+//
+
+function file_metadata(filename, size) {
+  this.filename = filename;
+  this.size = size; // file size in bytes
+  this.regions = []; // array of File_Region()
+  this.file_attributes = {
+    ID: { type: "text", description: "", default_value: "" },
+    Treatment: { type: "text", description: "", default_value: "" },
+  };
+  this.fileAttributes = new Map(
+    [
+      ["ID", { type: "text", description: "", default_value: "" }],
+      ["Treatment", { type: "text", description: "", default_value: "" }],
+    ]
+  );
+  this.lockedRegions = new Set();
+  this.groupedRegions = new Map();
+  this.autoAnnotated = false;
+}
+
+//
+// Initialization routine
+//
+function _via_init() {
+  console.log(SAA_NAME);
+  show_message(
+    SAA_NAME + " (" + SAA_SHORT_NAME + ") version " + SAA_VERSION + ". Ready !"
+  );
+
+  if (_via_is_debug_mode) {
+    document.getElementById("ui_top_panel").innerHTML +=
+      "<span>DEBUG MODE</span>";
+  }
+
+  // initialize default project
+  project.initDefaultProject()
+
+  // initialize region canvas 2D context
+  //_via_init_reg_canvas_context();
+
+  // initialize user input handlers (for both window and via_reg_canvas)
+  // handles drawing of regions by user over the image
+  _via_init_keyboard_handlers();
+
+  // initialize image grid
+  image_grid_init();
+
+  //show_annotator_editor();
+  show_single_image_view();
+  update_attributes_update_panel();
+  attribute_update_panel_set_active_button();
+
+  sidebar.annotation_editor_set_active_button();
+  sidebar.annotation_editor_toggle_all_regions_editor();
+
+  // run attached sub-modules (if any)
+  // e.g. demo modules
+  if (typeof _via_load_submodules === "function") {
+    console.log("Loading VIA submodule");
+    setTimeout(async function () {
+      await _via_load_submodules();
+    }, 100);
+  }
+}
+
+function _via_init_keyboard_handlers() {
+  window.addEventListener("keydown", _via_window_keydown_handler, false);
+  _via_reg_canvas.addEventListener(
+    "keydown",
+    _via_reg_canvas_keydown_handler,
+    false
+  );
+  _via_reg_canvas.addEventListener(
+    "keyup",
+    _via_reg_canvas_keyup_handler,
+    false
+  );
+}
+
+//
+// Download image with annotations
+//
+
+function download_as_image() {
+  if (
+    _via_display_area_content_name !== VIA_DISPLAY_AREA_CONTENT_NAME["IMAGE"]
+  ) {
+    show_message(
+      "This functionality is only available in single image view mode"
+    );
+  } else {
+    var c = document.createElement("canvas");
+
+    // ensures that downloaded image is scaled at current zoom level
+    c.width = _via_reg_canvas.width;
+    c.height = _via_reg_canvas.height;
+
+    var ct = c.getContext("2d");
+    // draw current image
+    ct.drawImage(
+      _via_current_image,
+      0,
+      0,
+      _via_reg_canvas.width,
+      _via_reg_canvas.height
+    );
+    // draw current regions
+    ct.drawImage(_via_reg_canvas, 0, 0);
+
+    var cur_img_mime = "image/jpeg";
+    if (_via_current_image.src.startsWith("data:")) {
+      var c1 = _via_current_image.src.indexOf(":", 0);
+      var c2 = _via_current_image.src.indexOf(";", c1);
+      cur_img_mime = _via_current_image.src.substring(c1 + 1, c2);
+    }
+
+    // extract image data from canvas
+    var saved_img = c.toDataURL(cur_img_mime);
+    saved_img.replace(cur_img_mime, "image/octet-stream");
+
+    // simulate user click to trigger download of image
+    var a = document.createElement("a");
+    a.href = saved_img;
+    a.target = "_blank";
+    a.download = _via_current_image_filename;
+
+    // simulate a mouse click event
+    var event = new MouseEvent("click", {
+      view: window,
+      bubbles: true,
+      cancelable: true,
+    });
+
+    a.dispatchEvent(event);
+  }
+}
+
+async function download_grid_selected_images() {
+  //add annotations to image as well
+  for (const element of _via_image_grid_selected_img_index_list) {
+    let image_index = element;
+    let img_id = _via_image_id_list[image_index];
+    let img_filename = _via_img_metadata[img_id].filename;
+    let imageElement = $("#bim" + image_index)[0];
+    if (imageElement === undefined) {
+      await buffer.addImageToBuffer(image_index);
+      imageElement = $("#bim" + image_index)[0];
+    }
+    let canvas = document.createElement("canvas");
+    canvas.width = imageElement.naturalWidth;
+    canvas.height = imageElement.naturalHeight;
+    let ctx = canvas.getContext("2d");
+    ctx.drawImage(imageElement, 0, 0);
+    let i;
+    let n = _via_img_metadata[img_id].regions.length;
+    for (i = 0; i < n; ++i) {
+      if (
+        !image_grid_is_region_in_current_group(
+          _via_img_metadata[img_id].regions[i].region_attributes
+        )
+      ) {
+        // skip drawing this region which is not in current group
+        continue;
+      }
+
+      let r = _via_img_metadata[img_id].regions[i].shape_attributes;
+      let dimg; // region coordinates in original image space
+      switch (r.name) {
+        case VIA_REGION_SHAPE.RECT:
+          dimg = [r["x"], r["y"], r["x"] + r["width"], r["y"] + r["height"]];
+          break;
+        case VIA_REGION_SHAPE.CIRCLE:
+          dimg = [r["cx"], r["cy"], r["cx"] + r["r"], r["cy"] + r["r"]];
+          break;
+        case VIA_REGION_SHAPE.ELLIPSE:
+          dimg = [r["cx"], r["cy"], r["cx"] + r["rx"], r["cy"] + r["ry"]];
+          break;
+        case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON
+        case VIA_REGION_SHAPE.POLYGON:
+          let j;
+          dimg = [];
+          for (j = 0; j < r["all_points_x"].length; ++j) {
+            dimg.push(r["all_points_x"][j]);
+            dimg.push(r["all_points_y"][j]);
+          }
+          break;
+        case VIA_REGION_SHAPE.POINT:
+          dimg = [r["cx"], r["cy"]];
+          break;
+      }
+      let scale_factor = imageElement.height / imageElement.naturalHeight; // new_height / original height
+      let offset_x = 0;
+      let offset_y = 0;
+      let r2 = new _via_region(
+        r.name,
+        i,
+        dimg,
+        scale_factor,
+        offset_x,
+        offset_y
+      );
+      let r2_svg = r2.get_svg_element();
+      r2_svg.setAttribute("class", "region");
+      r2_svg.setAttribute("fill", "none");
+      r2_svg.setAttribute("stroke", "red");
+      r2_svg.setAttribute("stroke-width", "2");
+
+      let svgXml = new XMLSerializer().serializeToString(r2_svg);
+      let svgImage = new Image();
+      svgImage.src = "data:image/svg+xml;base64," + btoa(svgXml);
+      svgImage.onload = function () {
+        ctx.drawImage(svgImage, 0, 0);
+      };
+    }
+    let saved_img = canvas.toDataURL("image/jpeg");
+    saved_img.replace("image/jpeg", "image/octet-stream");
+    let a = document.createElement("a");
+    a.href = saved_img;
+    a.target = "_blank";
+    a.download = img_filename;
+    let event = new MouseEvent("click", {
+      view: window,
+      bubbles: true,
+      cancelable: true,
+    });
+    a.dispatchEvent(event);
+  }
+}
+
+//
+// Display area content
+//
+function clear_display_area() {
+  let panels = document.getElementsByClassName("display_area_content");
+  let i;
+  for (i = 0; i < panels.length; ++i) {
+    panels[i].classList.add("d-none"); //modSote
+  }
+  $(`#selection_panel`).hide();
+}
+
+function is_content_name_valid(content_name) {
+  var e;
+  for (e in VIA_DISPLAY_AREA_CONTENT_NAME) {
+    if (VIA_DISPLAY_AREA_CONTENT_NAME[e] === content_name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function show_home_panel() {
+  show_single_image_view();
+}
+
+function set_display_area_content(content_name) {
+  if (is_content_name_valid(content_name)) {
+    _via_display_area_content_name_prev = _via_display_area_content_name;
+    clear_display_area();
+    var p = document.getElementById(content_name);
+    p.classList.remove("d-none");
+    _via_display_area_content_name = content_name;
+    if (content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE) {
+      $(`#selection_panel`).show();
+    }
+  }
+}
+
+function show_single_image_view() {
+  if (buffer.imgLoaded) {
+    sidebar.img_fn_list_clear_all_style();
+    buffer.showImage(_via_image_index);
+    set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE);
+    $("#selection_panel").show();
+    sidebar.annotation_editor_update_content();
+  } else {
+    set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO);
+    $("#selection_panel").hide();
+  }
+}
+
+function show_image_grid_view() {
+  if (buffer.imgLoaded) {
+    sidebar.img_fn_list_clear_all_style();
+    set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID);
+    $(`#selection_panel`).hide();
+    image_grid_toolbar_update_group_by_select();
+
+    if (_via_image_grid_group_var.length === 0) {
+      image_grid_show_all_project_images();
+    }
+    sidebar.annotation_editor_update_content();
+
+    sidebar.edit_file_metadata_in_annotation_editor();
+  } else {
+    set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO);
+    $(`#selection_panel`).hide();
+  }
+}
+
+//
+// Handlers for top navigation bar
+//
+function sel_local_images() {
+  // source: https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
+  if (fileInput) {
+    fileInput.setAttribute("multiple", "multiple");
+    fileInput.accept = ".jpg,.jpeg,.png,.bmp";
+    fileInput.onchange = project.addLocalFile;
+    fileInput.click();
+  }
+}
+
+// invoked by menu-item buttons in HTML UI
+function download_all_region_data(type, file_extension) {
+  if (typeof file_extension === "undefined") {
+    file_extension = type;
+  }
+  // Javascript strings (DOMString) is automatically converted to utf-8
+  // see: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
+  pack_via_metadata(type).then(
+    function (data) {
+      var blob_attr = { type: "text/" + file_extension + ";charset=utf-8" };
+      var all_region_data_blob = new Blob(data, blob_attr);
+
+      var filename = "via_export";
+      if (
+        typeof _via_settings !== "undefined" &&
+        _via_settings.hasOwnProperty("project") &&
+        _via_settings["project"]["name"] !== ""
+      ) {
+        filename = _via_settings["project"]["name"];
+      }
+      if (file_extension !== "csv" || file_extension !== "json") {
+        filename += "_" + type + "." + file_extension;
+      }
+      save_data_to_local_file(all_region_data_blob, filename);
+    }.bind(this),
+    function (err) {
+      show_message("Failed to download data: [" + err + "]");
+    }.bind(this)
+  );
+}
+
+function sel_local_data_file(type) {
+  if (fileInput) {
+    switch (type) {
+      case "annotations":
+        fileInput.accept = ".csv,.json";
+        fileInput.onchange = import_annotations_from_file;
+        break;
+
+      case "annotations_coco":
+        fileInput.accept = ".json";
+        fileInput.onchange = load_coco_annotations_json_file;
+        break;
+
+      case "files_url":
+        fileInput.accept = "";
+        fileInput.onchange = import_files_url_from_file;
+        break;
+
+      case "attributes":
+        fileInput.accept = "json";
+        fileInput.onchange = project.importAttributesFromFile;
+        break;
+
+      default:
+        console.log("sel_local_data_file() : unknown type " + type);
+        return;
+    }
+    fileInput.removeAttribute("multiple");
+    fileInput.click();
+  }
+}
+
+//
+// Data Importer
+//
+function import_files_url_from_file(event) {
+  var selected_files = event.target.files;
+  var i, file;
+  for (i = 0; i < selected_files.length; ++i) {
+    file = selected_files[i];
+    FileManager.loadTextFile(file, import_files_url_from_csv);
+  }
+}
+
+function import_annotations_from_file(event) {
+  var selected_files = event.target.files;
+  var i, file;
+  for (i = 0; i < selected_files.length; ++i) {
+    file = selected_files[i];
+    switch (file.type) {
+      case "": // Fall-through // Windows 10: Firefox and Chrome do not report filetype
+        show_message(
+          "File type for " +
+          file.name +
+          " cannot be determined! Assuming text/plain."
+        );
+      case "text/plain": // Fall-through
+      case "application/vnd.ms-excel": // Fall-through // @todo: filetype of VIA csv annotations in Windows 10 , fix this (reported by @Eli Walker)
+      case "text/csv":
+        FileManager.loadTextFile(file, import_annotations_from_csv);
+        break;
+
+      case "text/json": // Fall-through
+      case "application/json":
+        FileManager.loadTextFile(file, import_annotations_from_json);
+        break;
+
+      default:
+        show_message(
+          "Annotations cannot be imported from file of type " + file.type
+        );
+        break;
+    }
+  }
+}
+
+function load_coco_annotations_json_file(event) {
+  FileManager.loadTextFile(event.target.files[0], import_coco_annotations_from_json);
+}
+
+function import_annotations_from_csv(data) {
+  return new Promise(function (ok_callback, err_callback) {
+    if (data === '' || typeof (data) === 'undefined') {
+      err_callback();
+    }
+
+    var region_import_count = 0;
+    var malformed_csv_lines_count = 0;
+    var file_added_count = 0;
+
+    var line_split_regex = new RegExp('\n|\r|\r\n', 'g');
+    var csvdata = data.split(line_split_regex);
+
+    var parsed_header = parse_csv_header_line(csvdata[0]);
+    if (!parsed_header.is_header) {
+      show_message('Header line missing in the CSV file');
+      err_callback();
+      return;
+    }
+
+    var n = csvdata.length;
+    var i;
+    var first_img_id = '';
+    for (i = 1; i < n; ++i) {
+      // ignore blank lines
+      if (csvdata[i].charAt(0) === '\n' || csvdata[i].charAt(0) === '') {
+        continue;
+      }
+
+      var d = parse_csv_line(csvdata[i]);
+
+      // check if csv line was malformed
+      if (d.length !== parsed_header.csv_column_count) {
+        malformed_csv_lines_count += 1;
+        continue;
+      }
+
+      var filename = d[parsed_header.filename_index];
+      var size = d[parsed_header.size_index];
+      var img_id = _via_get_image_id(filename, size);
+
+      // check if file is already present in this project
+      if (!_via_img_metadata.hasOwnProperty(img_id)) {
+        img_id = project.addFile(filename, size);
+        if (_via_settings.core.default_filepath === '') {
+          _via_img_src[img_id] = filename;
+        } else {
+          _via_file_resolve_file_to_default_filepath(img_id);
+        }
+        file_added_count += 1;
+
+        if (first_img_id === '') {
+          first_img_id = img_id;
+        }
+      }
+
+      // copy file attributes
+      if (d[parsed_header.file_attr_index] !== '"{}"') {
+        var fattr = d[parsed_header.file_attr_index];
+        fattr = remove_prefix_suffix_quotes(fattr);
+        fattr = unescape_from_csv(fattr);
+
+        var m = json_str_to_map(fattr);
+        for (var key in m) {
+          _via_img_metadata[img_id].file_attributes[key] = m[key];
+
+          // add this file attribute to _via_attributes
+          if (!project.attributes.file.hasOwnProperty(key)) {
+            project.attributes.file[key] = { 'type': 'text' };
+          }
+        }
+      }
+
+      var region_i = new File_Region();
+      // copy regions shape attributes
+      if (d[parsed_header.region_shape_attr_index] !== '"{}"') {
+        var sattr = d[parsed_header.region_shape_attr_index];
+        sattr = remove_prefix_suffix_quotes(sattr);
+        sattr = unescape_from_csv(sattr);
+
+        var m = json_str_to_map(sattr);
+        for (var key in m) {
+          region_i.shape_attributes[key] = m[key];
+        }
+      }
+
+      // copy region attributes
+      if (d[parsed_header.region_attr_index] !== '"{}"') {
+        var rattr = d[parsed_header.region_attr_index];
+        rattr = remove_prefix_suffix_quotes(rattr);
+        rattr = unescape_from_csv(rattr);
+
+        var m = json_str_to_map(rattr);
+        for (var key in m) {
+          region_i.region_attributes[key] = m[key];
+
+          // add this region attribute to _via_attributes
+          if (!project.attributes.region.hasOwnProperty(key)) {
+            project.attributes.region[key] = { 'type': 'text' };
+          }
+        }
+      }
+
+      // add regions only if they are present
+      if (Object.keys(region_i.shape_attributes).length > 0 ||
+        Object.keys(region_i.region_attributes).length > 0) {
+        _via_img_metadata[img_id].regions.push(region_i);
+        region_import_count += 1;
+      }
+    }
+    show_message('Import Summary : [' + file_added_count + '] new files, ' +
+      '[' + region_import_count + '] regions, ' +
+      '[' + malformed_csv_lines_count + '] malformed csv lines.');
+
+    if (file_added_count) {
+      sidebar.update_img_fn_list();
+    }
+
+    if (buffer.imgLoaded) {
+      if (region_import_count) {
+        update_attributes_update_panel();
+        sidebar.annotation_editor_update_content();
+        _via_load_canvas_regions(); // image to canvas space transform
+        drawing.redrawRegCanvas();
+        _via_reg_canvas.focus();
+      }
+    } else {
+      if (file_added_count) {
+        var first_img_index = _via_image_id_list.indexOf(first_img_id);
+        buffer.showImage(first_img_index);
+      }
+    }
+    ok_callback([file_added_count, region_import_count, malformed_csv_lines_count]);
+  });
+}
+
+function parse_csv_header_line(line) {
+  var header_via_10x = '#filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; // VIA versions 1.0.x
+  var header_via_11x = 'filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; // VIA version 1.1.x
+
+  if (line === header_via_10x || line === header_via_11x) {
+    return {
+      'is_header': true,
+      'filename_index': 0,
+      'size_index': 1,
+      'file_attr_index': 2,
+      'region_shape_attr_index': 5,
+      'region_attr_index': 6,
+      'csv_column_count': 7
+    }
+  } else {
+    return { 'is_header': false };
+  }
+}
+
+
+// see http://cocodataset.org/#format-data
+function import_coco_annotations_from_json(data_str) {
+  return new Promise(function (ok_callback, err_callback) {
+    if (data_str === "" || typeof data_str === "undefined") {
+      show_message("Empty file");
+      return;
+    }
+    var coco = JSON.parse(data_str);
+    if (
+      !coco.hasOwnProperty("info") ||
+      !coco.hasOwnProperty("categories") ||
+      !coco.hasOwnProperty("annotations") ||
+      !coco.hasOwnProperty("images")
+    ) {
+      show_message("File does not contain valid annotations in COCO format.");
+      return;
+    }
+
+    // create _via_attributes from coco['categories']
+    var category_id_to_attribute_name = {};
+    for (var i in coco["categories"]) {
+      var sc = coco["categories"][i]["supercategory"];
+      var cid = coco["categories"][i]["id"];
+      var cname = coco["categories"][i]["name"];
+      if (!project.attributes.region.hasOwnProperty(sc)) {
+        project.attributes.region[sc] = {
+          type: VIA_ATTRIBUTE_TYPE.RADIO,
+          description:
+            'coco["categories"][' +
+            i +
+            "]=" +
+            JSON.stringify(coco["categories"][i]),
+          options: {},
+          default_options: {},
+        };
+      }
+      project.attributes.region[sc]["options"][cid] = cname;
+      category_id_to_attribute_name[cid] = sc;
+    }
+    // if more than 5 options, convert the attribute type to DROPDOWN
+    for (var attr_name in project.attributes.region) {
+      if (
+        Object.keys(project.attributes.region[attr_name]["options"]).length > 5
+      ) {
+        project.attributes.region[attr_name]["type"] =
+          VIA_ATTRIBUTE_TYPE.DROPDOWN;
+      }
+    }
+
+    // create an map of image_id and their annotations
+    var image_id_to_annotation_index = {};
+    for (var annotation_index in coco["annotations"]) {
+      var coco_image_id = coco.annotations[annotation_index]["image_id"];
+      if (!image_id_to_annotation_index.hasOwnProperty(coco_image_id)) {
+        image_id_to_annotation_index[coco_image_id] = [];
+      }
+      image_id_to_annotation_index[coco_image_id].push(annotation_index);
+    }
+
+    // add all files and annotations
+    _via_img_metadata = {};
+    _via_image_id_list = [];
+    _via_image_filename_list = [];
+    _via_img_count = 0;
+    var imported_file_count = 0;
+    var imported_region_count = 0;
+    for (var coco_img_index in coco["images"]) {
+      var coco_img_id = coco["images"][coco_img_index]["id"];
+      var filename;
+      if (
+        coco.images[coco_img_index].hasOwnProperty("coco_url") &&
+        coco.images[coco_img_index]["coco_url"] !== ""
+      ) {
+        filename = coco.images[coco_img_index]["coco_url"];
+      } else {
+        filename = coco.images[coco_img_index]["file_name"];
+      }
+      _via_img_metadata[coco_img_id] = {
+        filename: filename,
+        size: -1,
+        regions: [],
+        file_attributes: {
+          width: coco.images[coco_img_index]["width"],
+          height: coco.images[coco_img_index]["height"],
+        },
+      };
+      _via_image_id_list.push(coco_img_id);
+      _via_image_filename_list.push(filename);
+      _via_img_count = _via_img_count + 1;
+
+      // add all annotations associated with this file
+      if (image_id_to_annotation_index.hasOwnProperty(coco_img_id)) {
+        for (var i in image_id_to_annotation_index[coco_img_id]) {
+          var annotation_i =
+            coco["annotations"][image_id_to_annotation_index[coco_img_id][i]];
+          var bbox_from_polygon = polygon_to_bbox(
+            annotation_i["segmentation"][0]
+          );
+
+          // ensure rectangles get imported as rectangle (and not as polygon)
+          var is_rectangle = true;
+          for (var j = 0; j < annotation_i["bbox"].length; ++j) {
+            if (annotation_i["bbox"][j] !== bbox_from_polygon[j]) {
+              is_rectangle = false;
+              break;
+            }
+          }
+
+          var region_i = { shape_attributes: {}, region_attributes: {} };
+          var attribute_name =
+            category_id_to_attribute_name[annotation_i["category_id"]];
+          var attribute_value = annotation_i["category_id"].toString();
+          region_i["region_attributes"][attribute_name] = attribute_value;
+
+          if (annotation_i["segmentation"][0].length === 8 && is_rectangle) {
+            region_i["shape_attributes"] = {
+              name: "rect",
+              x: annotation_i["bbox"][0],
+              y: annotation_i["bbox"][1],
+              width: annotation_i["bbox"][2],
+              height: annotation_i["bbox"][3],
+            };
+          } else {
+            region_i["shape_attributes"] = {
+              name: "polygon",
+              all_points_x: [],
+              all_points_y: [],
+            };
+            for (
+              var j = 0;
+              j < annotation_i["segmentation"][0].length;
+              j = j + 2
+            ) {
+              region_i["shape_attributes"]["all_points_x"].push(
+                annotation_i["segmentation"][0][j]
+              );
+              region_i["shape_attributes"]["all_points_y"].push(
+                annotation_i["segmentation"][0][j + 1]
+              );
+            }
+          }
+          _via_img_metadata[coco_img_id]["regions"].push(region_i);
+          imported_region_count = imported_region_count + 1;
+        }
+      }
+    }
+    show_message(
+      "Import Summary : [" +
+      _via_img_count +
+      "] new files, " +
+      "[" +
+      imported_region_count +
+      "] regions."
+    );
+
+    if (_via_img_count) {
+      sidebar.update_img_fn_list();
+    }
+
+    if (buffer.imgLoaded) {
+      if (imported_region_count) {
+        update_attributes_update_panel();
+        sidebar.annotation_editor_update_content();
+        _via_load_canvas_regions(); // image to canvas space transform
+        drawing.redrawRegCanvas();
+        _via_reg_canvas.focus();
+      }
+    } else {
+      if (_via_img_count) {
+        buffer.showImage(0);
+      }
+    }
+    ok_callback([_via_img_count, imported_region_count, 0]);
+  });
+}
+
+function import_annotations_from_json(data_str) {
+  return new Promise(function (ok_callback, err_callback) {
+    if (data_str === "" || typeof data_str === "undefined") {
+      return;
+    }
+
+    var d = JSON.parse(data_str);
+    var region_import_count = 0;
+    var file_added_count = 0;
+    var malformed_entries_count = 0;
+    for (var img_id in d) {
+      if (!_via_img_metadata.hasOwnProperty(img_id)) {
+        project.addFile(d[img_id].filename, d[img_id].size, img_id);
+        if (settings.defaultPath === "") {
+          _via_img_src[img_id] = d[img_id].filename;
+        } else {
+          _via_file_resolve_file_to_default_filepath(img_id);
+        }
+        file_added_count += 1;
+      }
+
+      // copy file attributes
+      for (var key in d[img_id].file_attributes) {
+        if (key === "") {
+          continue;
+        }
+
+        _via_img_metadata[img_id].file_attributes[key] =
+          d[img_id].file_attributes[key];
+
+        // add this file attribute to _via_attributes
+        if (!project.attributes.file.hasOwnProperty(key)) {
+          project.attributes.file[key] = { type: "text" };
+        }
+      }
+
+      // copy regions
+      var regions = d[img_id].regions;
+      for (var i in regions) {
+        var region_i = new File_Region();
+        for (var sid in regions[i].shape_attributes) {
+          region_i.shape_attributes[sid] = regions[i].shape_attributes[sid];
+        }
+        for (var rid in regions[i].region_attributes) {
+          if (rid === "") {
+            continue;
+          }
+
+          region_i.region_attributes[rid] = regions[i].region_attributes[rid];
+
+          // add this region attribute to _via_attributes
+          if (!project.attributes.region.hasOwnProperty(rid)) {
+            project.attributes.region[rid] = { type: "text" };
+          }
+        }
+
+        // add regions only if they are present
+        if (
+          Object.keys(region_i.shape_attributes).length > 0 ||
+          Object.keys(region_i.region_attributes).length > 0
+        ) {
+          _via_img_metadata[img_id].regions.push(region_i);
+          region_import_count += 1;
+        }
+      }
+    }
+    show_message(
+      "Import Summary : [" +
+      file_added_count +
+      "] new files, " +
+      "[" +
+      region_import_count +
+      "] regions, " +
+      "[" +
+      malformed_entries_count +
+      "] malformed entries."
+    );
+
+    if (file_added_count) {
+      sidebar.update_img_fn_list();
+    }
+
+    if (buffer.imgLoaded) {
+      if (region_import_count) {
+        update_attributes_update_panel();
+        sidebar.annotation_editor_update_content();
+        _via_load_canvas_regions(); // image to canvas space transform
+        drawing.redrawRegCanvas();
+        _via_reg_canvas.focus();
+      }
+    } else {
+      if (file_added_count) {
+        buffer.showImage(0);
+      }
+    }
+
+    ok_callback([
+      file_added_count,
+      region_import_count,
+      malformed_entries_count,
+    ]);
+  });
+}
+
+// assumes that csv line follows the RFC 4180 standard
+// see: https://en.wikipedia.org/wiki/Comma-separated_values
+function parse_csv_line(s, field_separator) {
+  if (typeof (s) === 'undefined' || s.length === 0) {
+    return [];
+  }
+
+  if (typeof (field_separator) === 'undefined') {
+    field_separator = ',';
+  }
+  var double_quote_seen = false;
+  var start = 0;
+  var d = [];
+
+  var i = 0;
+  while (i < s.length) {
+    if (s.charAt(i) === field_separator) {
+      if (double_quote_seen) {
+        // field separator inside double quote is ignored
+        i = i + 1;
+      } else {
+        //var part = s.substr(start, i - start);
+        d.push(s.substr(start, i - start));
+        start = i + 1;
+        i = i + 1;
+      }
+    } else {
+      if (s.charAt(i) === '"') {
+        if (double_quote_seen) {
+          if (s.charAt(i + 1) === '"') {
+            // ignore escaped double quotes
+            i = i + 2;
+          } else {
+            // closing of double quote
+            double_quote_seen = false;
+            i = i + 1;
+          }
+        } else {
+          double_quote_seen = true;
+          start = i;
+          i = i + 1;
+        }
+      } else {
+        i = i + 1;
+      }
+    }
+
+  }
+  // extract the last field (csv rows have no trailing comma)
+  d.push(s.substr(start));
+  return d;
+}
+
+// s = '{"name":"rect","x":188,"y":90,"width":243,"height":233}'
+function json_str_to_map(s) {
+  if (typeof s === "undefined" || s.length === 0) {
+    return {};
+  }
+  return JSON.parse(s);
+}
+
+// ensure the exported json string conforms to RFC 4180
+// see: https://en.wikipedia.org/wiki/Comma-separated_values
+function map_to_json(m) {
+  var s = [];
+  for (var key in m) {
+    var v = m[key];
+    var si = JSON.stringify(key);
+    si += VIA_CSV_KEYVAL_SEP;
+    si += JSON.stringify(v);
+    s.push(si);
+  }
+  return "{" + s.join(VIA_CSV_SEP) + "}";
+}
+
+function escape_for_csv(s) {
+  return s.replace(/["]/g, '""');
+}
+
+function unescape_from_csv(s) {
+  return s.replace(/""/g, '"');
+}
+
+function remove_prefix_suffix_quotes(s) {
+  if (s.charAt(0) === '"' && s.charAt(s.length - 1) === '"') {
+    return s.substring(1, s.length - 1);
+  } else {
+    return s;
+  }
+}
+
+function clone_image_region(r0) {
+  var r1 = new File_Region();
+
+  // copy shape attributes
+  for (var key in r0.shape_attributes) {
+    r1.shape_attributes[key] = clone_value(r0.shape_attributes[key]);
+  }
+
+  // copy region attributes
+  for (var key in r0.region_attributes) {
+    r1.region_attributes[key] = clone_value(r0.region_attributes[key]);
+  }
+  return r1;
+}
+
+function clone_value(value) {
+  if (typeof value === "object") {
+    if (Array.isArray(value)) {
+      return value.slice(0);
+    } else {
+      var copy = {};
+      for (var p in value) {
+        if (value.hasOwnProperty(p)) {
+          copy[p] = clone_value(value[p]);
+        }
+      }
+      return copy;
+    }
+  }
+  return value;
+}
+
+function _via_get_image_id(filename, size) {
+  if (typeof size === "undefined") {
+    return filename;
+  } else {
+    return filename + size;
+  }
+}
+
+function load_text_file(text_file, callback_function) {
+  if (text_file) {
+    var text_reader = new FileReader();
+    text_reader.addEventListener(
+      "progress",
+      function (e) {
+        show_message("Loading data from file : " + text_file.name + " ... ");
+      },
+      false
+    );
+
+    text_reader.addEventListener(
+      "error",
+      function () {
+        show_message(
+          "Error loading data text file :  " + text_file.name + " !"
+        );
+        callback_function("");
+      },
+      false
+    );
+
+    text_reader.addEventListener(
+      "load",
+      function () {
+        callback_function(text_reader.result);
+      },
+      false
+    );
+    text_reader.readAsText(text_file, "utf-8");
+  }
+}
+
+function import_files_url_from_csv(data) {
+  return new Promise(function (ok_callback, err_callback) {
+    if (data === "" || typeof data === "undefined") {
+      err_callback();
+    }
+
+    var malformed_url_count = 0;
+    var url_added_count = 0;
+
+    var line_split_regex = new RegExp("\n|\r|\r\n", "g");
+    var csvdata = data.split(line_split_regex);
+
+    var percent_completed = 0;
+    var n = csvdata.length;
+    var i;
+    var img_id;
+    var first_img_id = "";
+    for (i = 0; i < n; ++i) {
+      // ignore blank lines
+      if (csvdata[i].charAt(0) === "\n" || csvdata[i].charAt(0) === "") {
+        malformed_url_count += 1;
+      } else {
+        img_id = project.addUrlFile(csvdata[i]);
+        if (first_img_id === "") {
+          first_img_id = img_id;
+        }
+        url_added_count += 1;
+      }
+    }
+    show_message("Added " + url_added_count + " files to project");
+    if (url_added_count) {
+      var first_img_index = _via_image_id_list.indexOf(first_img_id);
+      buffer.showImage(first_img_index);
+      sidebar.update_img_fn_list();
+    }
+  });
+}
+
+//
+// Data Exporter
+//
+function pack_via_metadata(return_type) {
+  return new Promise(
+    function (ok_callback, err_callback) {
+      if (return_type === "csv") {
+        var csvdata = [];
+        var csvheader =
+          "filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes";
+        csvdata.push(csvheader);
+
+        for (var image_id in _via_img_metadata) {
+          var fattr = map_to_json(_via_img_metadata[image_id].file_attributes);
+          fattr = escape_for_csv(fattr);
+
+          var prefix = "\n" + _via_img_metadata[image_id].filename;
+          prefix += "," + _via_img_metadata[image_id].size;
+          prefix += ',"' + fattr + '"';
+
+          var r = _via_img_metadata[image_id].regions;
+
+          if (r.length !== 0) {
+            for (var i = 0; i < r.length; ++i) {
+              var csvline = [];
+              csvline.push(prefix);
+              csvline.push(r.length);
+              csvline.push(i);
+
+              var sattr = map_to_json(r[i].shape_attributes);
+              sattr = '"' + escape_for_csv(sattr) + '"';
+              csvline.push(sattr);
+
+              var rattr = map_to_json(r[i].region_attributes);
+              rattr = '"' + escape_for_csv(rattr) + '"';
+              csvline.push(rattr);
+              csvdata.push(csvline.join(VIA_CSV_SEP));
+            }
+          } else {
+            // @todo: reconsider this practice of adding an empty entry
+            csvdata.push(prefix + ',0,0,"{}","{}"');
+          }
+        }
+        ok_callback(csvdata);
+      }
+
+      // see http://cocodataset.org/#format-data
+      if (return_type === "coco") {
+        img_stat_set_all().then(
+          function (ok) {
+            var coco = export_project_to_coco_format();
+            ok_callback([coco]);
+          }.bind(this),
+          function (err) {
+            err_callback(err);
+          }.bind(this)
+        );
+      } else {
+        // default format is JSON
+        ok_callback([JSON.stringify(_via_img_metadata)]);
+      }
+    }.bind(this)
+  );
+}
+
+function export_project_to_coco_format() {
+  var coco = {
+    info: {},
+    images: [],
+    annotations: [],
+    licenses: [],
+    categories: [],
+  };
+  coco["info"] = {
+    year: new Date().getFullYear(),
+    version: "1.0",
+    description:
+      "VIA project exported to COCO format using VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via/)",
+    contributor: "",
+    url: "http://www.robots.ox.ac.uk/~vgg/software/via/",
+    date_created: new Date().toString(),
+  };
+  coco["licenses"] = [{ id: 0, name: "Unknown License", url: "" }]; // indicates that license is unknown
+
+  var skipped_annotation_count = 0;
+  // We want to ensure that a COCO project imported in VIA and then exported again back to
+  // COCO format using VIA retains the image_id and category_id present in the original COCO project.
+  // A VIA project that has been created by importing annotations from a COCO project contains
+  // unique image_id of type integer and contains all unique option id. If we detect this, we reuse
+  // the existing image_id and category_id, otherwise we assign a new unique id sequentially.
+  // Currently, it is not possible to preserve the annotation_id
+  var assign_unique_id = false;
+  for (var img_id in _via_img_metadata) {
+    if (Number.isNaN(parseInt(img_id))) {
+      assign_unique_id = true; // since COCO only supports image_id of type integer, we cannot reuse the VIA's image-id
+      break;
+    }
+  }
+  if (assign_unique_id) {
+    // check if all the options have unique id
+    var attribute_option_id_list = [];
+    for (var attr_name in project.attributes) {
+      if (
+        !VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(
+          project.attributes[attr_name]["type"]
+        )
+      ) {
+        continue; // skip this attribute as it will not be included in COCO export
+      }
+
+      for (var attr_option_id in project.attributes[attr_name]["options"]) {
+        if (
+          attribute_option_id_list.includes(attr_option_id) ||
+          Number.isNaN(parseInt(attr_option_id))
+        ) {
+          assign_unique_id = true;
+          break;
+        } else {
+          attribute_option_id_list.push(assign_unique_id);
+        }
+      }
+    }
+  }
+
+  // add categories
+  var attr_option_id_list = []; //MODSote
+  var attr_option_id_to_category_id = {};
+  var unique_category_id = 1;
+  for (var attr_name in project.attributes.region) {
+    if (
+      VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(
+        project.region[attr_name]["type"]
+      )
+    ) {
+      for (var attr_option_id in project.project.attributes.region[attr_name][
+        "options"
+      ]) {
+        var category_id;
+        if (assign_unique_id) {
+          category_id = unique_category_id;
+          unique_category_id = unique_category_id + 1;
+        } else {
+          category_id = parseInt(attr_option_id);
+        }
+        coco["categories"].push({
+          supercategory: attr_name,
+          id: category_id,
+          name: project.attributes.region[attr_name]["options"][attr_option_id],
+        });
+        attr_option_id_to_category_id[attr_option_id] = category_id;
+      }
+    }
+  }
+
+  // add files and all their associated annotations
+  var annotation_id = 1;
+  var unique_img_id = 1;
+  for (var img_index in _via_image_id_list) {
+    var img_id = _via_image_id_list[img_index];
+    var file_src =
+      _via_settings["core"]["default_filepath"] +
+      _via_img_metadata[img_id].filename;
+    if (_via_img_fileref[img_id] instanceof File) {
+      file_src = _via_img_fileref[img_id].filename;
+    }
+
+    var coco_img_id;
+    if (assign_unique_id) {
+      coco_img_id = unique_img_id;
+      unique_img_id = unique_img_id + 1;
+    } else {
+      coco_img_id = parseInt(img_id);
+    }
+
+    coco["images"].push({
+      id: coco_img_id,
+      width: _via_img_stat[img_index][0],
+      height: _via_img_stat[img_index][1],
+      file_name: _via_img_metadata[img_id].filename,
+      license: 0,
+      flickr_url: file_src,
+      coco_url: file_src,
+      date_captured: "",
+    });
+
+    // add all annotations associated with this file
+    for (var rindex in _via_img_metadata[img_id].regions) {
+      var region = _via_img_metadata[img_id].regions[rindex];
+      if (!VIA_COCO_EXPORT_RSHAPE.includes(region.shape_attributes["name"])) {
+        skipped_annotation_count = skipped_annotation_count + 1;
+        continue; // skip this region as COCO does not allow it
+      }
+
+      var coco_annotation = via_region_shape_to_coco_annotation(
+        region.shape_attributes
+      );
+      coco_annotation["id"] = annotation_id;
+      coco_annotation["image_id"] = coco_img_id;
+
+      var region_aid_list = Object.keys(region["region_attributes"]);
+      for (var region_attribute_id in region["region_attributes"]) {
+        var region_attribute_value =
+          region["region_attributes"][region_attribute_id];
+        if (
+          attr_option_id_to_category_id.hasOwnProperty(region_attribute_value)
+        ) {
+          coco_annotation["category_id"] =
+            attr_option_id_to_category_id[region_attribute_value];
+          coco["annotations"].push(coco_annotation);
+          annotation_id = annotation_id + 1;
+        } else {
+          skipped_annotation_count = skipped_annotation_count + 1;
+          // skip attribute value not supported by COCO format
+        }
+      }
+    }
+  }
+
+  show_message(
+    "Skipped " +
+    skipped_annotation_count +
+    " annotations. COCO format only supports the following attribute types: " +
+    JSON.stringify(VIA_COCO_EXPORT_ATTRIBUTE_TYPE) +
+    " and region shapes: " +
+    JSON.stringify(VIA_COCO_EXPORT_RSHAPE)
+  );
+  return [JSON.stringify(coco)];
+}
+
+function via_region_shape_to_coco_annotation(shape_attributes) {
+  var annotation = { segmentation: [[]], area: [], bbox: [], iscrowd: 0 };
+
+  switch (shape_attributes["name"]) {
+    case "rect":
+      var x0 = shape_attributes["x"];
+      var y0 = shape_attributes["y"];
+      var w = parseInt(shape_attributes["width"]);
+      var h = parseInt(shape_attributes["height"]);
+      var x1 = x0 + w;
+      var y1 = y0 + h;
+      annotation["segmentation"][0] = [x0, y0, x1, y0, x1, y1, x0, y1];
+      annotation["area"] = w * h;
+
+      annotation["bbox"] = [x0, y0, w, h];
+      break;
+
+    case "point":
+      var cx = shape_attributes["cx"];
+      var cy = shape_attributes["cy"];
+      // 2 is for visibility - currently set to always inside segmentation.
+      // see Keypoint Detection: http://cocodataset.org/#format-data
+      annotation["keypoints"] = [cx, cy, 2];
+      annotation["num_keypoints"] = 1;
+      break;
+
+    case "circle":
+      var a, b;
+      a = shape_attributes["r"];
+      b = shape_attributes["r"];
+      var theta_to_radian = Math.PI / 180;
+
+      for (
+        var theta = 0;
+        theta < 360;
+        theta = theta + VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE
+      ) {
+        var theta_radian = theta * theta_to_radian;
+        var x = shape_attributes["cx"] + a * Math.cos(theta_radian);
+        var y = shape_attributes["cy"] + b * Math.sin(theta_radian);
+        annotation["segmentation"][0].push(fixfloat(x), fixfloat(y));
+      }
+      annotation["bbox"] = polygon_to_bbox(annotation["segmentation"][0]);
+      annotation["area"] = annotation["bbox"][2] * annotation["bbox"][3];
+      break;
+
+    case "ellipse":
+      var a, b;
+      a = shape_attributes["rx"];
+      b = shape_attributes["ry"];
+      var rotation = 0;
+      // older version of VIA2 did not support rotated ellipse and hence 'theta' attribute may not be available
+      if (shape_attributes.hasOwnProperty("theta")) {
+        rotation = shape_attributes["theta"];
+      }
+
+      var theta_to_radian = Math.PI / 180;
+
+      for (
+        var theta = 0;
+        theta < 360;
+        theta = theta + VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE
+      ) {
+        var theta_radian = theta * theta_to_radian;
+        var x =
+          shape_attributes["cx"] +
+          a * Math.cos(theta_radian) * Math.cos(rotation) -
+          b * Math.sin(theta_radian) * Math.sin(rotation);
+        var y =
+          shape_attributes["cy"] +
+          a * Math.cos(theta_radian) * Math.sin(rotation) +
+          b * Math.sin(theta_radian) * Math.cos(rotation);
+        annotation["segmentation"][0].push(fixfloat(x), fixfloat(y));
+      }
+      annotation["bbox"] = polygon_to_bbox(annotation["segmentation"][0]);
+      annotation["area"] = annotation["bbox"][2] * annotation["bbox"][3];
+      break;
+
+    case "polygon":
+      annotation["segmentation"][0] = [];
+      var x0 = +Infinity;
+      var y0 = +Infinity;
+      var x1 = -Infinity;
+      var y1 = -Infinity;
+      for (var i in shape_attributes["all_points_x"]) {
+        annotation["segmentation"][0].push(shape_attributes["all_points_x"][i]);
+        annotation["segmentation"][0].push(shape_attributes["all_points_y"][i]);
+        if (shape_attributes["all_points_x"][i] < x0) {
+          x0 = shape_attributes["all_points_x"][i];
+        }
+        if (shape_attributes["all_points_y"][i] < y0) {
+          y0 = shape_attributes["all_points_y"][i];
+        }
+        if (shape_attributes["all_points_x"][i] > x1) {
+          x1 = shape_attributes["all_points_x"][i];
+        }
+        if (shape_attributes["all_points_y"][i] > y1) {
+          y1 = shape_attributes["all_points_y"][i];
+        }
+      }
+      var w = x1 - x0;
+      var h = y1 - y0;
+      annotation["bbox"] = [x0, y0, w, h];
+      annotation["area"] = w * h; // approximate area
+  }
+  return annotation;
+}
+
+function save_data_to_local_file(data, filename) {
+  var a = document.createElement("a");
+  a.href = URL.createObjectURL(data);
+  a.download = filename;
+
+  // simulate a mouse click event
+  var event = new MouseEvent("click", {
+    view: window,
+    bubbles: true,
+    cancelable: true,
+  });
+  a.dispatchEvent(event);
+
+  // @todo: replace a.dispatchEvent() with a.click()
+  // a.click() based trigger is supported in Chrome 70 and Safari 11/12 but **not** in Firefox 63
+  //a.click();
+}
+
+//
+// Maintainers of user interface
+//
+
+function init_message_panel() {
+  var p = document.getElementById("message_panel");
+  p.addEventListener(
+    "mousedown",
+    function () {
+      this.style.display = "none";
+    },
+    false
+  );
+  p.addEventListener(
+    "mouseover",
+    function () {
+      clearTimeout(_via_message_clear_timer); // stop any previous timeouts
+    },
+    false
+  );
+}
+
+function toggle_message_visibility() {
+  if (_via_is_message_visible) {
+    show_message("Disabled status messages");
+    _via_is_message_visible = false;
+  } else {
+    _via_is_message_visible = true;
+    show_message("Status messages are now visible");
+  }
+}
+
+function show_message(msg, t) {
+  if (!_via_is_message_visible) {
+    return;
+  }
+  Message.show({
+    address: "Info",
+    body: msg,
+    color: "#4ADEDE",
+  });
+}
+
+// transform regions in image space to canvas space
+function _via_load_canvas_regions() {
+  drawing.regionsGroupColorInit();
+
+  // load all existing annotations into _via_canvas_regions
+  var regions = _via_img_metadata[_via_image_id].regions;
+  _via_canvas_regions = [];
+  for (var i = 0; i < regions.length; ++i) {
+    var region_i = new File_Region();
+    for (var key in regions[i].shape_attributes) {
+      region_i.shape_attributes[key] = regions[i].shape_attributes[key];
+    }
+    _via_canvas_regions.push(region_i);
+
+    switch (_via_canvas_regions[i].shape_attributes["name"]) {
+      case VIA_REGION_SHAPE.RECT:
+        var x = regions[i].shape_attributes["x"] / _via_canvas_scale;
+        var y = regions[i].shape_attributes["y"] / _via_canvas_scale;
+        var width = regions[i].shape_attributes["width"] / _via_canvas_scale;
+        var height = regions[i].shape_attributes["height"] / _via_canvas_scale;
+
+        _via_canvas_regions[i].shape_attributes["x"] = Math.round(x);
+        _via_canvas_regions[i].shape_attributes["y"] = Math.round(y);
+        _via_canvas_regions[i].shape_attributes["width"] = Math.round(width);
+        _via_canvas_regions[i].shape_attributes["height"] = Math.round(height);
+        break;
+
+      case VIA_REGION_SHAPE.CIRCLE:
+        var cx = regions[i].shape_attributes["cx"] / _via_canvas_scale;
+        var cy = regions[i].shape_attributes["cy"] / _via_canvas_scale;
+        var r = regions[i].shape_attributes["r"] / _via_canvas_scale;
+        _via_canvas_regions[i].shape_attributes["cx"] = Math.round(cx);
+        _via_canvas_regions[i].shape_attributes["cy"] = Math.round(cy);
+        _via_canvas_regions[i].shape_attributes["r"] = Math.round(r);
+        break;
+
+      case VIA_REGION_SHAPE.ELLIPSE:
+        var cx = regions[i].shape_attributes["cx"] / _via_canvas_scale;
+        var cy = regions[i].shape_attributes["cy"] / _via_canvas_scale;
+        var rx = regions[i].shape_attributes["rx"] / _via_canvas_scale;
+        var ry = regions[i].shape_attributes["ry"] / _via_canvas_scale;
+        // rotation in radians
+        var theta = regions[i].shape_attributes["theta"];
+        _via_canvas_regions[i].shape_attributes["cx"] = Math.round(cx);
+        _via_canvas_regions[i].shape_attributes["cy"] = Math.round(cy);
+        _via_canvas_regions[i].shape_attributes["rx"] = Math.round(rx);
+        _via_canvas_regions[i].shape_attributes["ry"] = Math.round(ry);
+        _via_canvas_regions[i].shape_attributes["theta"] = theta;
+        break;
+
+      case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
+      case VIA_REGION_SHAPE.POLYGON:
+        var all_points_x = regions[i].shape_attributes["all_points_x"].slice(0);
+        var all_points_y = regions[i].shape_attributes["all_points_y"].slice(0);
+        for (var j = 0; j < all_points_x.length; ++j) {
+          all_points_x[j] = Math.round(all_points_x[j] / _via_canvas_scale);
+          all_points_y[j] = Math.round(all_points_y[j] / _via_canvas_scale);
+        }
+        _via_canvas_regions[i].shape_attributes["all_points_x"] = all_points_x;
+        _via_canvas_regions[i].shape_attributes["all_points_y"] = all_points_y;
+        break;
+
+      case VIA_REGION_SHAPE.POINT:
+        var cx = regions[i].shape_attributes["cx"] / _via_canvas_scale;
+        var cy = regions[i].shape_attributes["cy"] / _via_canvas_scale;
+
+        _via_canvas_regions[i].shape_attributes["cx"] = Math.round(cx);
+        _via_canvas_regions[i].shape_attributes["cy"] = Math.round(cy);
+        break;
+    }
+  }
+}
+
+// updates currently selected region shape
+function select_region_shape(sel_shape_name) {
+  if (drawing.currentShape() === sel_shape_name) {
+    return;
+  }
+
+  if (drawing.is_user_drawing_polygon) {
+    return;
+  }
+
+  $("#selection_panel button").removeClass("active");
+
+  if (sel_shape_name === "img_set") {
+    $('#selection_panel button[id="image_settings"]').addClass("active");
+    $("#image_man_container").removeClass("d-none");
+    $("#sidebar_container").addClass("d-none");
+  }
+
+  if (sel_shape_name === "drag") {
+    zoom.enablePan();
+    _via_reg_canvas.cursor = "move";
+  } else {
+    zoom.disablePan();
+    _via_reg_canvas.cursor = "crosshair";
+  }
+
+  $('#selection_panel button[id="shape_' + sel_shape_name + '"]').addClass(
+    "active"
+  );
+
+  drawing.setCurrentShape(sel_shape_name);
+
+  switch (
+  drawing.currentShape() //classm
+  ) {
+    case VIA_REGION_SHAPE.RECT: // Fall-through
+    case VIA_REGION_SHAPE.CIRCLE: // Fall-through
+    case VIA_REGION_SHAPE.ELLIPSE:
+      Message.show({
+        address: "Info",
+        body:
+          "Press single click and drag mouse to draw " +
+          drawing.currentShape() +
+          " region", //classm
+        color: "#1d3557",
+      });
+      break;
+
+    case VIA_REGION_SHAPE.POLYLINE:
+    case VIA_REGION_SHAPE.POLYGON:
+      drawing.setIsUserDrawingPolygon(false);
+      drawing.setCurrentPolygonRegionId(-1);
+      Message.show({
+        address: "Info",
+        body:
+          "[Single Click] to define polygon/polyline vertices, " +
+          "[Backspace] to delete last vertex, [Enter] to finish, [Esc] to cancel drawing.",
+      });
+      break;
+
+    case VIA_REGION_SHAPE.POINT:
+      Message.show({
+        address: "Info",
+        body: "Press single click to define points (or landmarks)",
+      });
+
+      break;
+    case VIA_REGION_SHAPE.REMOVE:
+      Message.show({
+        address: "Info",
+        body: "Draw a rectangle with this tool to remove some points from the regions (poly and pen regions) within the rectangle",
+      });
+
+      break;
+
+    case VIA_REGION_SHAPE.TRIM:
+      Message.show({
+        address: "Info",
+        body: "Select a polygon to trim",
+      });
+      break;
+
+    case VIA_REGION_SHAPE.DRAG:
+      Message.show({
+        address: "Info",
+        body: "Now you can pan the image",
+      });
+      break;
+
+    default:
+      Message.show({
+        address: "Info",
+        body: "Editor mode, drawing is disabled",
+      });
+      break;
+  }
+}
+
+function set_all_canvas_size(w, h) {
+  image_panel.style.height = h + "px";
+  image_panel.style.width = w + "px";
+}
+
+function set_all_canvas_scale(s) {
+  //_via_reg_ctx.scale(s, s);
+}
+
+function show_all_canvas() {
+  image_panel.style.display = "inline-block";
+}
+
+function hide_all_canvas() {
+  image_panel.style.display = "none";
+}
+
+function jump_to_image(image_index) {
+  if (_via_img_count <= 0) {
+    return;
+  }
+
+  undoredo_worker.postMessage({
+    commands: "reset",
+  });
+  undoredo_worker.postMessage({
+    commands: "add",
+  });
+  switch (_via_display_area_content_name) {
+    case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
+      if (image_index >= 0 && image_index < _via_img_count) {
+        // @todo: jump to image grid page view with the given first image index
+        show_single_image_view();
+        buffer.showImage(image_index);
+      }
+      break;
+    default:
+      if (image_index >= 0 && image_index < _via_img_count) {
+        buffer.showImage(image_index);
+      }
+      break;
+  }
+}
+
+function count_missing_region_attr(img_id) {
+  var miss_region_attr_count = 0;
+  var attr_count = Object.keys(_via_region_attributes).length;
+  for (var i = 0; i < _via_img_metadata[img_id].regions.length; ++i) {
+    var set_attr_count = Object.keys(
+      _via_img_metadata[img_id].regions[i].region_attributes
+    ).length;
+    miss_region_attr_count += attr_count - set_attr_count;
+  }
+  return miss_region_attr_count;
+}
+
+function count_missing_file_attr(img_id) {
+  return (
+    Object.keys(_via_file_attributes).length -
+    Object.keys(_via_img_metadata[img_id].file_attributes).length
+  );
+}
+
+function toggle_all_regions_selection(is_selected) {
+  var n = _via_img_metadata[_via_image_id].regions.length;
+  var i;
+  _via_region_selected_flag.clear();
+  if (is_selected) {
+    for (i = 0; i < n; ++i) {
+      _via_region_selected_flag.add(i);
+    }
+  }
+  _via_is_all_region_selected = is_selected;
+  sidebar.annotation_editor_hide();
+  if (_via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS) {
+    sidebar.annotation_editor_clear_row_highlight();
+  }
+}
+
+function select_only_region(region_id) {
+  toggle_all_regions_selection(false);
+  set_region_select_state(region_id, true);
+  drawing.setIsRegionSelected(true); //classm
+  _via_is_all_region_selected = false;
+  drawing.setUserSelRegionId(region_id); //classm
+}
+
+function set_region_select_state(region_id, is_selected) {
+  if (is_selected) {
+    _via_region_selected_flag.add(parseInt(region_id));
+  } else {
+    _via_region_selected_flag.delete(parseInt(region_id));
+  }
+}
+
+function show_annotation_data() {
+  pack_via_metadata("csv").then(
+    function (data) {
+      var hstr = "<pre>" + data.join("") + "</pre>";
+      var window_features =
+        "toolbar=no,menubar=no,location=no,resizable=yes,scrollbars=yes,status=no";
+      window_features += ",width=800,height=600";
+      var annotation_data_window = window.open(
+        "",
+        "Annotations (preview) ",
+        window_features
+      );
+      annotation_data_window.document.body.innerHTML = hstr;
+    }.bind(this),
+    function (err) {
+      show_message("Failed to collect annotation data!");
+    }.bind(this)
+  );
+}
+
+//
+// Shortcut key handlers
+//
+function _via_window_keydown_handler(e) {
+  if (e.target === document.body) {
+    // process the keyboard event
+    _via_handle_global_keydown_event(e);
+  }
+}
+
+// global keys are active irrespective of element focus
+// arrow keys, n, p, s, o, space, d, Home, End, PageUp, PageDown
+function _via_handle_global_keydown_event(e) {
+  // zoom
+  if (buffer.imgLoaded) {
+    if (e.key === "+") {
+      zoom.zoomIn();
+      return;
+    }
+
+    if (e.key === "=") {
+      zoom.resetZoom();
+      return;
+    }
+
+    if (e.key === "-") {
+      zoom.zoomOut();
+      return;
+    }
+  }
+
+  if (e.key === "Delete") {
+    if (
+      drawing.isRegionSelected() || //classm
+      _via_is_all_region_selected
+    ) {
+      del_sel_regions();
+    }
+    e.preventDefault();
+    return;
+  }
+
+  if (e.key === "ArrowRight" || e.key === "n") {
+    move_to_next_image();
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "ArrowLeft" || e.key === "p") {
+    move_to_prev_image();
+    e.preventDefault();
+    return;
+  }
+
+  if (e.key === "ArrowUp") {
+    region_visualisation_update("region_label", "__via_region_id__", 1);
+    e.preventDefault();
+    return;
+  }
+
+  if (e.key === "ArrowDown") {
+    region_visualisation_update(
+      "region_color",
+      "__via_default_region_color__",
+      -1
+    );
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "s") {
+    //MODSote
+    AutoAnnotator.run_annotation();
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "Home") {
+    show_first_image();
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "End") {
+    show_last_image();
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "PageDown") {
+    jump_to_next_image_block();
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "PageUp") {
+    jump_to_prev_image_block();
+    e.preventDefault();
+    return;
+  }
+
+  if (e.key === "a") {
+    if (
+      _via_display_area_content_name ===
+      VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+    ) {
+      // select all in image grid
+      image_grid_group_toggle_select_all();
+    }
+  }
+
+  if (e.key === "Escape") {
+    e.preventDefault();
+    if (_via_is_loading_current_image) {
+      buffer.stopLoading();
+    }
+
+    if (drawing.isUserResizingRegion()) {
+      //classm
+      // cancel region resizing action
+      drawing.setIsUserResizingRegion(false); //classm
+    }
+
+    if (drawing.isRegionSelected()) {
+      //classm
+      // clear all region selections
+      drawing.setIsRegionSelected(false); //classm
+      drawing.setUserSelRegionId(-1); //classm
+      toggle_all_regions_selection(false);
+    }
+
+    if (drawing.isUserDrawingPolygon()) {
+      //classm
+      drawing.setIsUserDrawingPolygon(false); //classm
+      _via_canvas_regions.splice(drawing.currentPolygonRegionId(), 1); //classm
+    }
+
+    if (drawing.isUserDrawingRegion()) {
+      //classm
+      drawing.setIsUserDrawingRegion(false); //classm
+    }
+
+    if (drawing.isUserResizingRegion()) {
+      //classm
+      drawing.setIsUserResizingRegion(false); //classm
+    }
+
+    if (drawing.isUserMovingRegion()) {
+      //classm
+      drawing.setIsUserMovingRegion(false); //classm
+    }
+
+    drawing.redrawRegCanvas();
+    return;
+  }
+
+  if (e.key === " ") {
+    // Space key
+    if (e.ctrlKey) {
+      sidebar.annotation_editor_toggle_on_image_editor();
+    } else {
+      leftsidebar_toggle();
+      _via_reg_canvas.focus();
+    }
+    e.preventDefault();
+    return;
+  }
+
+  if (e.key === "F1") {
+    // F1 for help
+    set_display_area_content(
+      VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED
+    );
+    $(`#selection_panel`).hide();
+    e.preventDefault();
+    return;
+  }
+  if (e.key === "F2") {
+    // F2 for about
+    set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_ABOUT);
+    $(`#selection_panel`).hide();
+    e.preventDefault();
+  }
+}
+
+function _via_reg_canvas_keyup_handler(e) {
+  if (e.key === "Control") {
+    _via_is_ctrl_pressed = false;
+  }
+  if (e.key === "Alt") {
+    select_region_shape(current_shape);
+    is_alt_pressed = false;
+    zoom.disablePan();
+  }
+}
+
+function _via_reg_canvas_keydown_handler(e) {
+  if (e.key === "Control") {
+    _via_is_ctrl_pressed = true;
+  }
+
+  if (e.key === "Alt") {
+    if (!is_alt_pressed) {
+      current_shape = drawing.currentShape();
+      select_region_shape("edit");
+      zoom.enablePan();
+    }
+    is_alt_pressed = true;
+  }
+
+  if (buffer.imgLoaded) {
+    if (e.key === "Enter") {
+      if (
+        drawing.currentShape() === VIA_REGION_SHAPE.POLYLINE || //classm
+        drawing.currentShape() === VIA_REGION_SHAPE.POLYGON
+      ) {
+        //classm
+        _via_polyshape_finish_drawing();
+      }
+    }
+    if (e.key === "Backspace") {
+      if (
+        drawing.currentShape() === VIA_REGION_SHAPE.POLYLINE || //classm
+        drawing.currentShape() === VIA_REGION_SHAPE.POLYGON
+      ) {
+        //classm
+        _via_polyshape_delete_last_vertex();
+      }
+    }
+
+    if (e.key === "a") {
+      sel_all_regions();
+      e.preventDefault();
+      return;
+    }
+
+    if (e.key === "c") {
+      if (
+        drawing.isRegionSelected() || //classm
+        _via_is_all_region_selected
+      ) {
+        copy_sel_regions();
+      }
+      e.preventDefault();
+      return;
+    }
+
+    if (e.key === "v") {
+      paste_sel_regions_in_current_image();
+      e.preventDefault();
+      return;
+    }
+
+    if (e.key === "b") {
+      toggle_region_boundary_visibility();
+      e.preventDefault();
+      return;
+    }
+
+    if (e.key === "l") {
+      toggle_region_id_visibility();
+      e.preventDefault();
+      return;
+    }
+
+    if (e.key === "r") {
+      if (drawing.isRegionSelected() || _via_is_all_region_selected) {
+        reducePointsSelRegion();
+      }
+      e.preventDefault();
+      return;
+    }
+
+    if (drawing.isRegionSelected()) {
+      //classm
+      if (
+        e.key === "ArrowRight" ||
+        e.key === "ArrowLeft" ||
+        e.key === "ArrowDown" ||
+        e.key === "ArrowUp"
+      ) {
+        var del = 1;
+        if (e.shiftKey) {
+          del = 10;
+        }
+        var move_x = 0;
+        var move_y = 0;
+        switch (e.key) {
+          case "ArrowLeft":
+            move_x = -del;
+            break;
+          case "ArrowUp":
+            move_y = -del;
+            break;
+          case "ArrowRight":
+            move_x = del;
+            break;
+          case "ArrowDown":
+            move_y = del;
+            break;
+        }
+        drawing.moveSelectedRegions(move_x, move_y);
+        drawing.redrawRegCanvas();
+        e.preventDefault();
+        return;
+      }
+    }
+    if (e.key === "d" && _via_is_ctrl_pressed) {
+      del_sel_regions();
+    }
+    if (e.key === "z" && _via_is_ctrl_pressed) {
+      undoredo_worker.postMessage({
+        commands: "undo",
+      });
+    }
+    if (e.key === "v" && _via_is_ctrl_pressed) {
+      undoredo_worker.postMessage({
+        commands: "redo",
+      });
+    }
+  }
+  _via_handle_global_keydown_event(e);
+}
+
+function _via_polyshape_finish_drawing() {
+  if (drawing.isUserDrawingPolygon()) {
+    var new_region_id = drawing.currentPolygonRegionId(); //classm
+    var new_region_shape = drawing.currentShape(); //classm
+
+    var npts =
+      _via_canvas_regions[new_region_id].shape_attributes["all_points_x"]
+        .length;
+    if (npts <= 2 && new_region_shape === VIA_REGION_SHAPE.POLYGON) {
+      show_message(
+        "For a polygon, you must define at least 3 points. " +
+        "Press [Esc] to cancel drawing operation.!"
+      );
+      return;
+    }
+    if (npts <= 1 && new_region_shape === VIA_REGION_SHAPE.POLYLINE) {
+      show_message(
+        "A polyline must have at least 2 points. " +
+        "Press [Esc] to cancel drawing operation.!"
+      );
+      return;
+    }
+
+    var img_id = _via_image_id;
+    drawing.setCurrentPolygonRegionId(-1); //classm
+    drawing.setIsUserDrawingPolygon(false); //classm
+    drawing.setIsUserDrawingRegion(false); //classm
+
+    _via_img_metadata[img_id].regions[new_region_id] = {}; // create placeholder
+    _via_polyshape_add_new_polyshape(img_id, new_region_shape, new_region_id);
+    select_only_region(new_region_id); // select new region
+    sidebar.set_region_annotations_to_default_value(new_region_id);
+    sidebar.annotation_editor_add_row(new_region_id);
+    sidebar.annotation_editor_scroll_to_row(new_region_id);
+
+    drawing.redrawRegCanvas();
+    _via_reg_canvas.focus();
+  }
+}
+
+function _via_polyshape_delete_last_vertex() {
+  if (drawing.isUserDrawingPolygon()) {
+    //classm
+    var npts =
+      _via_canvas_regions[drawing.currentPolygonRegionId()].shape_attributes[
+        "all_points_x"
+      ].length; //classm
+    if (npts > 0) {
+      _via_canvas_regions[drawing.currentPolygonRegionId()].shape_attributes[
+        "all_points_x"
+      ].splice(npts - 1, 1); //classm
+      _via_canvas_regions[drawing.currentPolygonRegionId()].shape_attributes[
+        "all_points_y"
+      ].splice(npts - 1, 1); //classm
+
+      drawing.redrawRegCanvas();
+      _via_reg_canvas.focus();
+    }
+  }
+}
+
+function _via_polyshape_add_new_polyshape(img_id, region_shape, region_id) {
+  // add all polygon points stored in _via_canvas_regions[]
+  var all_points_x =
+    _via_canvas_regions[region_id].shape_attributes["all_points_x"].slice(0);
+  var all_points_y =
+    _via_canvas_regions[region_id].shape_attributes["all_points_y"].slice(0);
+
+  var canvas_all_points_x = [];
+  var canvas_all_points_y = [];
+  var n = all_points_x.length;
+  var i;
+  for (i = 0; i < n; ++i) {
+    all_points_x[i] = Math.round(all_points_x[i] * _via_canvas_scale);
+    all_points_y[i] = Math.round(all_points_y[i] * _via_canvas_scale);
+
+    canvas_all_points_x[i] = Math.round(all_points_x[i] / _via_canvas_scale);
+    canvas_all_points_y[i] = Math.round(all_points_y[i] / _via_canvas_scale);
+  }
+
+  var polygon_region = new File_Region();
+  polygon_region.shape_attributes["name"] = region_shape;
+  polygon_region.shape_attributes["all_points_x"] = all_points_x;
+  polygon_region.shape_attributes["all_points_y"] = all_points_y;
+  _via_img_metadata[img_id].regions[region_id] = polygon_region;
+
+  // update canvas
+  if (img_id === _via_image_id) {
+    _via_canvas_regions[region_id].shape_attributes["name"] = region_shape;
+    _via_canvas_regions[region_id].shape_attributes["all_points_x"] =
+      canvas_all_points_x;
+    _via_canvas_regions[region_id].shape_attributes["all_points_y"] =
+      canvas_all_points_y;
+  }
+}
+
+function del_sel_regions() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  if (!buffer.imgLoaded) {
+    show_message("First load some images!");
+    return;
+  }
+
+  var del_region_count = 0;
+  if (_via_is_all_region_selected) {
+    del_region_count = _via_canvas_regions.length;
+    _via_canvas_regions.splice(0);
+    _via_img_metadata[_via_image_id].clearRegions();
+    _via_img_metadata[_via_image_id].clearLockedRegions();
+  } else {
+    var sorted_sel_reg_id = [];
+    for (var i = 0; i < _via_canvas_regions.length; ++i) {
+      if (_via_region_selected_flag.has(i)) {
+        sorted_sel_reg_id.push(i);
+        _via_region_selected_flag.delete(i);
+      }
+    }
+    sorted_sel_reg_id.sort(function (a, b) {
+      return b - a;
+    });
+    for (const element of sorted_sel_reg_id) {
+      _via_canvas_regions.splice(element, 1);
+      _via_img_metadata[_via_image_id].regions.splice(element, 1);
+      if (_via_img_metadata[_via_image_id].isRegionLocked(element)) {
+        _via_img_metadata[_via_image_id].clearRegionLock(element);
+      }
+      del_region_count += 1;
+    }
+
+    if (sorted_sel_reg_id.length) {
+      _via_reg_canvas.style.cursor = "default";
+    }
+  }
+
+  _via_is_all_region_selected = false;
+  drawing.setIsRegionSelected(false); //classm
+  drawing.setUserSelRegionId(-1); //classm
+  drawing.updateCheckedLockHtml();
+
+  if (_via_canvas_regions.length === 0) {
+    // all regions were deleted, hence clear region canvas
+    drawing.clearCanvas();
+  } else {
+    drawing.redrawRegCanvas();
+  }
+  _via_reg_canvas.focus();
+  sidebar.annotation_editor_show();
+
+  show_message("Deleted " + del_region_count + " selected regions");
+}
+
+function reducePointsSelRegion() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  if (!buffer.imgLoaded) {
+    show_message("First load some images!");
+    return;
+  }
+
+  let del_region_count = 0;
+  if (_via_is_all_region_selected) {
+    let N = _via_canvas_regions.length;
+    if (N === 0) {
+      return;
+    }
+
+    for (let i = 0; i < N; ++i) {
+      if (!_via_img_metadata[_via_image_id].isRegionLocked(i)) {
+        let rs = _via_canvas_regions[i].shape_attributes;
+        if (rs.name === "polygon") {
+          for (let j = 0; j < rs.all_points_x.length; ++j) {
+            drawing.polygonDelVertex(i, j);
+          }
+        }
+      }
+    }
+  } else {
+    let sorted_sel_reg_id = [];
+    for (let i = 0; i < _via_canvas_regions.length; ++i) {
+      if (_via_region_selected_flag.has(i)) {
+        sorted_sel_reg_id.push(i);
+        _via_region_selected_flag.delete(i);
+      }
+    }
+    sorted_sel_reg_id.sort(function (a, b) {
+      return b - a;
+    });
+    for (const element of sorted_sel_reg_id) {
+      let rs = _via_canvas_regions[element].shape_attributes;
+      if (rs.name === "polygon") {
+        for (let j = 0; j < rs.all_points_x.length; ++j) {
+          if (!_via_img_metadata[_via_image_id].isRegionLocked(element)) {
+            drawing.polygonDelVertex(element, j);
+          }
+        }
+      }
+      del_region_count += 1;
+    }
+
+    if (sorted_sel_reg_id.length) {
+      _via_reg_canvas.style.cursor = "default";
+    }
+  }
+
+  _via_is_all_region_selected = false;
+  drawing.setIsRegionSelected(false); //classm
+  drawing.setUserSelRegionId(-1); //classm
+
+  if (_via_canvas_regions.length === 0) {
+    // all regions were deleted, hence clear region canvas
+    drawing.clearCanvas();
+  } else {
+    drawing.redrawRegCanvas();
+  }
+  _via_reg_canvas.focus();
+  sidebar.annotation_editor_show();
+
+  show_message("Reduced points for" + del_region_count + " selected regions");
+}
+
+function sel_all_regions() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    image_grid_group_toggle_select_all();
+    return;
+  }
+
+  if (!buffer.imgLoaded) {
+    show_message("First load some images!");
+    return;
+  }
+
+  toggle_all_regions_selection(true);
+  _via_is_all_region_selected = true;
+  drawing.redrawRegCanvas();
+}
+
+function copy_sel_regions() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  if (!buffer.imgLoaded) {
+    show_message("First load some images!");
+    return;
+  }
+
+  if (
+    drawing.isRegionSelected() || //classm
+    _via_is_all_region_selected
+  ) {
+    _via_copied_image_regions.splice(0);
+    for (var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i) {
+      var img_region = _via_img_metadata[_via_image_id].regions[i];
+      var canvas_region = _via_canvas_regions[i];
+      if (_via_region_selected_flag.has(i)) {
+        _via_copied_image_regions.push(clone_image_region(img_region));
+      }
+    }
+    show_message(
+      "Copied " +
+      _via_copied_image_regions.length +
+      " selected regions. Press Ctrl + v to paste"
+    );
+  } else {
+    show_message("Select a region first!");
+  }
+}
+
+function paste_sel_regions_in_current_image() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  if (!buffer.imgLoaded) {
+    show_message("First load some images!");
+    return;
+  }
+
+  if (_via_copied_image_regions.length) {
+    var pasted_reg_count = 0;
+    for (var i = 0; i < _via_copied_image_regions.length; ++i) {
+      // ensure copied the regions are within this image's boundaries
+      var bbox = drawing.getRegionBoundingBox(_via_copied_image_regions[i]);
+      if (
+        bbox[2] < _via_current_image_width &&
+        bbox[3] < _via_current_image_height
+      ) {
+        var r = clone_image_region(_via_copied_image_regions[i]);
+        _via_img_metadata[_via_image_id].regions.push(r);
+
+        pasted_reg_count += 1;
+      }
+    }
+    _via_load_canvas_regions();
+    var discarded_reg_count =
+      _via_copied_image_regions.length - pasted_reg_count;
+    show_message(
+      "Pasted " +
+      pasted_reg_count +
+      " regions. " +
+      "Discarded " +
+      discarded_reg_count +
+      " regions exceeding image boundary."
+    );
+    drawing.redrawRegCanvas();
+    _via_reg_canvas.focus();
+  } else {
+    show_message(
+      "To paste a region, you first need to select a region and copy it!"
+    );
+  }
+}
+
+function paste_to_multiple_images_with_confirm() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  if (_via_copied_image_regions.length === 0) {
+    show_message("First copy some regions!");
+    return;
+  }
+
+  var config = { title: "Paste Regions to Multiple Images" };
+  var input = {
+    region_count: {
+      type: "text",
+      name: "Number of copied regions",
+      value: _via_copied_image_regions.length,
+      disabled: true,
+    },
+    prev_next_count: {
+      type: "text",
+      name: 'Copy to (count format)<br><span style="font-size:0.8rem">For example: to paste copied regions to the <i>previous 2 images</i> and <i>next 3 images</i>, type <strong>2,3</strong> in the textbox and to paste only in <i>next 5 images</i>, type <strong>0,5</strong></span>',
+      placeholder: "2,3",
+      disabled: false,
+      size: 30,
+    },
+    img_index_list: {
+      type: "text",
+      name: 'Copy to (image index list)<br><span style="font-size:0.8rem">For example: <strong>2-5,7,9</strong> pastes the copied regions to the images with the following id <i>2,3,4,5,7,9</i> and <strong>3,8,141</strong> pastes to the images with id <i>3,8 and 141</i></span>',
+      placeholder: "2-5,7,9",
+      disabled: false,
+      size: 30,
+    },
+    regex: {
+      type: "text",
+      name: 'Copy to filenames matching a regular expression<br><span style="font-size:0.8rem">For example: <strong>_large</strong> pastes the copied regions to all images whose filename contain the keyword <i>_large</i></span>',
+      placeholder: "regular expression",
+      disabled: false,
+      size: 30,
+    },
+    include_region_attributes: {
+      type: "checkbox",
+      name: "Paste also the region annotations",
+      checked: true,
+    },
+  };
+
+  invoke_with_user_inputs(paste_to_multiple_images_confirmed, input, config);
+}
+
+function paste_to_multiple_images_confirmed(input) {
+  // keep a copy of user inputs for the undo operation
+  _via_paste_to_multiple_images_input = input;
+  var intersect = generate_img_index_list(input);
+  var i;
+  var total_pasted_region_count = 0;
+  for (i = 0; i < intersect.length; i++) {
+    total_pasted_region_count += paste_regions(intersect[i]);
+  }
+
+  show_message(
+    "Pasted [" +
+    total_pasted_region_count +
+    "] regions " +
+    "in " +
+    intersect.length +
+    " images"
+  );
+
+  if (intersect.includes(_via_image_index)) {
+    _via_load_canvas_regions();
+    drawing.redrawRegCanvas();
+    _via_reg_canvas.focus();
+  }
+  user_input_default_cancel_handler();
+}
+
+function paste_regions(img_index) {
+  var pasted_reg_count = 0;
+  if (_via_copied_image_regions.length) {
+    var img_id = _via_image_id_list[img_index];
+    var i;
+    for (i = 0; i < _via_copied_image_regions.length; ++i) {
+      var r = clone_image_region(_via_copied_image_regions[i]);
+      _via_img_metadata[img_id].regions.push(r);
+
+      pasted_reg_count += 1;
+    }
+  }
+  return pasted_reg_count;
+}
+
+function del_sel_regions_with_confirm() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  if (_via_copied_image_regions.length === 0) {
+    show_message("First copy some regions!");
+    return;
+  }
+
+  var prev_next_count, img_index_list, regex;
+  if (_via_paste_to_multiple_images_input) {
+    prev_next_count = _via_paste_to_multiple_images_input.prev_next_count.value;
+    img_index_list = _via_paste_to_multiple_images_input.img_index_list.value;
+    regex = _via_paste_to_multiple_images_input.regex.value;
+  }
+
+  var config = { title: "Undo Regions Pasted to Multiple Images" };
+  var input = {
+    region_count: {
+      type: "text",
+      name: "Number of regions selected",
+      value: _via_copied_image_regions.length,
+      disabled: true,
+    },
+    prev_next_count: {
+      type: "text",
+      name: 'Delete from (count format)<br><span style="font-size:0.8rem">For example: to delete copied regions from the <i>previous 2 images</i> and <i>next 3 images</i>, type <strong>2,3</strong> in the textbox and to delete regions only in <i>next 5 images</i>, type <strong>0,5</strong></span>',
+      placeholder: "2,3",
+      disabled: false,
+      size: 30,
+      value: prev_next_count,
+    },
+    img_index_list: {
+      type: "text",
+      name: 'Delete from (image index list)<br><span style="font-size:0.8rem">For example: <strong>2-5,7,9</strong> deletes the copied regions to the images with the following id <i>2,3,4,5,7,9</i> and <strong>3,8,141</strong> deletes regions from the images with id <i>3,8 and 141</i></span>',
+      placeholder: "2-5,7,9",
+      disabled: false,
+      size: 30,
+      value: img_index_list,
+    },
+    regex: {
+      type: "text",
+      name: 'Delete from filenames matching a regular expression<br><span style="font-size:0.8rem">For example: <strong>_large</strong> deletes the copied regions from all images whose filename contain the keyword <i>_large</i></span>',
+      placeholder: "regular expression",
+      disabled: false,
+      size: 30,
+      value: regex,
+    },
+  };
+
+  invoke_with_user_inputs(del_sel_regions_confirmed, input, config);
+}
+
+function del_sel_regions_confirmed(input) {
+  user_input_default_cancel_handler();
+  var intersect = generate_img_index_list(input);
+  var i;
+  var total_deleted_region_count = 0;
+  for (i = 0; i < intersect.length; i++) {
+    total_deleted_region_count += delete_regions(intersect[i]);
+  }
+
+  show_message(
+    "Deleted [" +
+    total_deleted_region_count +
+    "] regions " +
+    "in " +
+    intersect.length +
+    " images"
+  );
+
+  if (intersect.includes(_via_image_index)) {
+    _via_load_canvas_regions();
+    drawing.redrawRegCanvas();
+    _via_reg_canvas.focus();
+  }
+}
+
+function delete_regions(img_index) {
+  var del_region_count = 0;
+  if (_via_copied_image_regions.length) {
+    var img_id = _via_image_id_list[img_index];
+    var i;
+    for (i = 0; i < _via_copied_image_regions.length; ++i) {
+      var copied_region_shape_str = JSON.stringify(
+        _via_copied_image_regions[i].shape_attributes
+      );
+      var j;
+      // start from last region in order to delete the last pasted region
+      for (j = _via_img_metadata[img_id].regions.length - 1; j >= 0; --j) {
+        if (
+          JSON.stringify(
+            _via_img_metadata[img_id].regions[j].shape_attributes
+          ) === copied_region_shape_str
+        ) {
+          _via_img_metadata[img_id].regions.splice(j, 1);
+          del_region_count += 1;
+          break; // delete only one matching region
+        }
+      }
+    }
+  }
+  return del_region_count;
+}
+
+function show_first_image() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    if (_via_image_grid_group_var.length) {
+      image_grid_group_prev({ value: 0 }); // simulate button click
+    } else {
+      show_message(
+        'First, create groups by selecting items from "Group by" dropdown list'
+      );
+    }
+    return;
+  }
+
+  if (_via_img_count > 0) {
+    buffer.showImage(_via_img_fn_list_img_index_list[0]);
+  }
+}
+
+function show_last_image() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    if (_via_image_grid_group_var.length) {
+      image_grid_group_prev({ value: _via_image_grid_group_var.length - 1 }); // simulate button click
+    } else {
+      show_message(
+        'First, create groups by selecting items from "Group by" dropdown list'
+      );
+    }
+    return;
+  }
+
+  if (_via_img_count > 0) {
+    var last_img_index = _via_img_fn_list_img_index_list.length - 1;
+    buffer.showImage(_via_img_fn_list_img_index_list[last_img_index]);
+  }
+}
+
+function jump_image_block_get_count() {
+  var n = _via_img_fn_list_img_index_list.length;
+  if (n < 20) {
+    return 2;
+  }
+  if (n < 100) {
+    return 10;
+  }
+  if (n < 1000) {
+    return 25;
+  }
+  if (n < 5000) {
+    return 50;
+  }
+  if (n < 10000) {
+    return 100;
+  }
+  if (n < 50000) {
+    return 500;
+  }
+
+  return Math.round(n / 50);
+}
+
+function jump_to_next_image_block() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  var jump_count = jump_image_block_get_count();
+  if (jump_count > 1) {
+    var current_img_index = _via_image_index;
+    if (_via_img_fn_list_img_index_list.includes(current_img_index)) {
+      var list_index =
+        _via_img_fn_list_img_index_list.indexOf(current_img_index);
+      var next_list_index = list_index + jump_count;
+      if (next_list_index + 1 > _via_img_fn_list_img_index_list.length) {
+        next_list_index = 0;
+      }
+      var next_img_index = _via_img_fn_list_img_index_list[next_list_index];
+      buffer.showImage(next_img_index);
+    }
+  } else {
+    move_to_next_image();
+  }
+}
+
+function jump_to_prev_image_block() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    return;
+  }
+
+  var jump_count = jump_image_block_get_count();
+  if (jump_count > 1) {
+    var current_img_index = _via_image_index;
+    if (_via_img_fn_list_img_index_list.includes(current_img_index)) {
+      var list_index =
+        _via_img_fn_list_img_index_list.indexOf(current_img_index);
+      var prev_list_index = list_index - jump_count;
+      if (prev_list_index < 0) {
+        prev_list_index = _via_img_fn_list_img_index_list.length - 1;
+      }
+      var prev_img_index = _via_img_fn_list_img_index_list[prev_list_index];
+      buffer.showImage(prev_img_index);
+    }
+  } else {
+    move_to_prev_image();
+  }
+}
+
+function move_to_prev_image() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    if (_via_image_grid_group_var.length) {
+      var last_group_index = _via_image_grid_group_var.length - 1;
+      image_grid_group_prev({ value: last_group_index }); // simulate button click
+    } else {
+      show_message(
+        'First, create groups by selecting items from "Group by" dropdown list'
+      );
+    }
+    return;
+  }
+
+  if (_via_img_count > 0) {
+    var current_img_index = _via_image_index;
+    if (_via_img_fn_list_img_index_list.includes(current_img_index)) {
+      var list_index =
+        _via_img_fn_list_img_index_list.indexOf(current_img_index);
+      var next_list_index = list_index - 1;
+      if (next_list_index === -1) {
+        next_list_index = _via_img_fn_list_img_index_list.length - 1;
+      }
+      var next_img_index = _via_img_fn_list_img_index_list[next_list_index];
+      buffer.showImage(next_img_index);
+    } else {
+      if (_via_img_fn_list_img_index_list.length === 0) {
+        show_message("Filtered file list does not any files!");
+      } else {
+        buffer.showImage(_via_img_fn_list_img_index_list[0]);
+      }
+    }
+
+    if (typeof _via_hook_prev_image === "function") {
+      _via_hook_prev_image(current_img_index);
+    }
+  }
+}
+
+function move_to_next_image() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    if (_via_image_grid_group_var.length) {
+      var last_group_index = _via_image_grid_group_var.length - 1;
+      image_grid_group_next({ value: last_group_index }); // simulate button click
+    } else {
+      show_message(
+        'First, create groups by selecting items from "Group by" dropdown list'
+      );
+    }
+    return;
+  }
+
+  if (_via_img_count > 0) {
+    var current_img_index = _via_image_index;
+    if (_via_img_fn_list_img_index_list.includes(current_img_index)) {
+      var list_index =
+        _via_img_fn_list_img_index_list.indexOf(current_img_index);
+      var next_list_index = list_index + 1;
+      if (next_list_index === _via_img_fn_list_img_index_list.length) {
+        next_list_index = 0;
+      }
+      var next_img_index = _via_img_fn_list_img_index_list[next_list_index];
+      buffer.showImage(next_img_index);
+    } else {
+      if (_via_img_fn_list_img_index_list.length === 0) {
+        show_message("Filtered file list does not contain any files!");
+      } else {
+        buffer.showImage(_via_img_fn_list_img_index_list[0]);
+      }
+    }
+
+    if (typeof _via_hook_next_image === "function") {
+      _via_hook_next_image(current_img_index);
+    }
+  }
+}
+
+function toggle_region_boundary_visibility() {
+  if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE) {
+    _via_is_region_boundary_visible = !_via_is_region_boundary_visible;
+    drawing.redrawRegCanvas();
+    _via_reg_canvas.focus();
+  }
+
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    if (_via_settings.ui.image_grid.show_region_shape) {
+      _via_settings.ui.image_grid.show_region_shape = false;
+      document.getElementById("image_grid_content_rshape").innerHTML = "";
+    } else {
+      _via_settings.ui.image_grid.show_region_shape = true;
+      image_grid_page_show_all_regions();
+    }
+  }
+}
+
+function toggle_region_id_visibility() {
+  _via_is_region_id_visible = !_via_is_region_id_visible;
+  drawing.redrawRegCanvas();
+  _via_reg_canvas.focus();
+}
+
+function toggle_region_info_visibility() {
+  var elem = document.getElementById("region_info");
+  // toggle between displaying and not displaying
+  if (elem.classList.contains("d-none")) {
+    elem.classList.remove("d-none");
+    _via_is_region_info_visible = true;
+  } else {
+    elem.classList.add("d-none"); //modSote
+    _via_is_region_info_visible = false;
+  }
+}
+
+function region_visualisation_update(type, default_id, next_offset) {
+  var attr_list = [default_id];
+  attr_list = attr_list.concat(Object.keys(project.attributes.region));
+  var n = attr_list.length;
+  var current_index = attr_list.indexOf(_via_settings.ui.image[type]);
+  var new_index;
+  if (current_index !== -1) {
+    new_index = current_index + next_offset;
+
+    if (new_index < 0) {
+      new_index = n + new_index;
+    }
+    if (new_index >= n) {
+      new_index = new_index - n;
+    }
+    switch (type) {
+      case "region_label":
+        _via_settings.ui.image.region_label = attr_list[new_index];
+        drawing.redrawRegCanvas();
+        break;
+      case "region_color":
+        _via_settings.ui.image.region_color = attr_list[new_index];
+        drawing.regionsGroupColorInit();
+        drawing.redrawRegCanvas();
+    }
+
+    var type_str = type.replace("_", " ");
+    if (_via_settings.ui.image[type].startsWith("__via")) {
+      show_message(type_str + " cleared");
+    } else {
+      show_message(
+        type_str +
+        " set to region attribute [" +
+        _via_settings.ui.image[type] +
+        "]"
+      );
+    }
+  }
+}
+
+//
+// left sidebar toolbox maintainer
+//
+
+function leftsidebar_toggle() {
+  $("#sidebar_container").toggle("fast");
+  drawing.updateUiComponents();
+}
+
+//
+// region and file attributes update panel
+//
+function attribute_update_panel_set_active_button() {
+  var attribute_type;
+  for (attribute_type in project.attributes) {
+    var bid = "button_show_" + attribute_type + "_attributes";
+    document.getElementById(bid).classList.remove("active");
+  }
+  var bid = "button_show_" + _via_attribute_being_updated + "_attributes";
+  document.getElementById(bid).classList.add("active");
+}
+
+function show_region_attributes_update_panel() {
+  _via_attribute_being_updated = "region";
+  var rattr_list = Object.keys(project.attributes.region);
+  if (rattr_list.length) {
+    _via_current_attribute_id = rattr_list[0];
+  } else {
+    _via_current_attribute_id = "";
+  }
+  update_attributes_update_panel();
+  attribute_update_panel_set_active_button();
+}
+
+function show_file_attributes_update_panel() {
+  _via_attribute_being_updated = "file";
+  var fattr_list = Object.keys(project.attributes.file);
+  if (fattr_list.length) {
+    _via_current_attribute_id = fattr_list[0];
+  } else {
+    _via_current_attribute_id = "";
+  }
+  update_attributes_update_panel();
+  attribute_update_panel_set_active_button();
+}
+
+function update_attributes_name_list() {
+  var p = document.getElementById("attributes_name_list");
+  p.innerHTML = "";
+
+  var attr;
+  for (attr in project.attributes[_via_attribute_being_updated]) {
+    var option = document.createElement("option");
+    option.setAttribute("value", attr);
+    option.innerHTML = attr;
+    if (attr === _via_current_attribute_id) {
+      option.setAttribute("selected", "selected");
+    }
+    p.appendChild(option);
+  }
+}
+
+function update_attributes_update_panel() {
+  update_attributes_name_list();
+  show_attribute_properties();
+  show_attribute_options();
+  show_attribute_example();
+}
+
+function update_attribute_properties_panel() {
+  if (
+    !document
+      .getElementById("settings_panel")
+      .classList.contains("d-none")
+  ) {
+    show_attribute_properties();
+    show_attribute_options();
+    show_attribute_example();
+  }
+}
+
+function show_attribute_example() {
+  let p = $("#attributes_example");
+  p.addClass("d-none");
+  p.html("");
+  let e = $("<table>", {
+    class: "table table-sm table-borderless",
+    id: "annotation_editor_example_table",
+  });
+  let tbody = $("<tbody>");
+  tbody.append(sidebar.annotation_editor_get_metadata_row_html(100, false, true));
+  e.append(sidebar.annotation_editor_update_header_html(true));
+  e.append(tbody);
+  p.append(e);
+
+  p.removeClass("d-none");
+}
+
+function show_attribute_properties() {
+  let attr_list = document.getElementById("attributes_name_list");
+  let prop = document.getElementById("attribute_properties");
+  prop.classList.add("d-none");
+  prop.innerHTML = "";
+
+  if (attr_list.options.length === 0) {
+    return;
+  }
+
+  if (
+    typeof _via_current_attribute_id === "undefined" ||
+    _via_current_attribute_id === ""
+  ) {
+    _via_current_attribute_id = attr_list.options[0].value;
+  }
+
+  let attr_id = _via_current_attribute_id;
+  let attr_type = _via_attribute_being_updated;
+  let attr_input_type = project.attributes[attr_type][attr_id].type;
+  let attr_desc = project.attributes[attr_type][attr_id].description;
+
+  attribute_property_add_input_property(
+    "Name of attribute (appears in exported annotations)",
+    "Name",
+    attr_id,
+    "attribute_name",
+    prop
+  );
+  attribute_property_add_input_property(
+    "Description of attribute (shown to user during annotation session)",
+    "Desc.",
+    attr_desc,
+    "attribute_description",
+    prop
+  );
+
+  if (attr_input_type === "text") {
+    var attr_default_value = project.attributes[attr_type][attr_id].default_value;
+    attribute_property_add_input_property(
+      "Default value of this attribute",
+      "Def.",
+      attr_default_value,
+      "attribute_default_value",
+      prop
+    );
+  }
+
+  // add dropdown for type of attribute
+  let p = document.createElement("div");
+  p.setAttribute("class", "input-group mb-1");
+  let c0 = document.createElement("span");
+  c0.setAttribute("class", "input-group-text");
+  c0.setAttribute("title", "Attribute type (e.g. text, checkbox, radio, etc)");
+  c0.innerHTML = "Type";
+  let c1b = document.createElement("select");
+  c1b.setAttribute("class", "form-select")
+  c1b.setAttribute("aria-label", "Attribute type");
+  c1b.setAttribute("onchange", "attribute_property_on_update(this)");
+  c1b.setAttribute("id", "attribute_type");
+  let type_id;
+  for (type_id in VIA_ATTRIBUTE_TYPE) {
+    let type = VIA_ATTRIBUTE_TYPE[type_id];
+    let option = document.createElement("option");
+    option.setAttribute("value", type);
+    option.innerHTML = type;
+    if (attr_input_type == type) {
+      option.setAttribute("selected", "selected");
+    }
+    c1b.appendChild(option);
+  }
+  p.appendChild(c0);
+  p.appendChild(c1b);
+  document.getElementById("attribute_properties").appendChild(p);
+  prop.classList.remove("d-none");
+}
+
+function show_attribute_options() {
+  let attr_list = document.getElementById("attributes_name_list");
+  let opt = document.getElementById("attribute_options")
+  opt.classList.add("d-none");
+  opt.innerHTML = "";
+  if (attr_list.options.length === 0) {
+    return;
+  }
+
+  let attr_id = attr_list.value;
+  let attr_type = project.attributes[_via_attribute_being_updated][attr_id].type;
+
+  // populate additional options based on attribute type
+  switch (attr_type) {
+    case VIA_ATTRIBUTE_TYPE.TEXT:
+      // text does not have any additional properties
+      break;
+    case VIA_ATTRIBUTE_TYPE.IMAGE:
+      var p = document.createElement("div");
+      p.setAttribute("class", "row mt-2");
+      var c0 = document.createElement("span");
+      c0.setAttribute("class", "col");
+      c0.setAttribute(
+        "title",
+        "When selected, this is the value that appears in exported annotations"
+      );
+      c0.innerHTML = "id";
+      var c1 = document.createElement("span");
+      c1.setAttribute("class", "col");
+      c1.setAttribute(
+        "title",
+        "URL or base64 (see https://www.base64-image.de/) encoded image data that corresponds to the image shown as an option to the annotator"
+      );
+      c1.innerHTML = "image url or b64";
+      var c2 = document.createElement("span");
+      c2.setAttribute("class", "col");
+      c2.setAttribute("title", "The default value of this attribute");
+      c2.innerHTML = "def.";
+      p.appendChild(c0);
+      p.appendChild(c1);
+      p.appendChild(c2);
+      document.getElementById("attribute_options").appendChild(p);
+
+      var options =
+        project.attributes[_via_attribute_being_updated][attr_id].options;
+      var option_id;
+      for (option_id in options) {
+        var option_desc = options[option_id];
+
+        var option_default =
+          project.attributes[_via_attribute_being_updated][attr_id]
+            .default_options[option_id];
+        attribute_property_add_option(
+          attr_id,
+          option_id,
+          option_desc,
+          option_default,
+          attr_type
+        );
+      }
+      attribute_property_add_new_entry_option(attr_id, attr_type);
+      break;
+    case VIA_ATTRIBUTE_TYPE.CHECKBOX: // handled by next case
+    case VIA_ATTRIBUTE_TYPE.DROPDOWN: // handled by next case
+    case VIA_ATTRIBUTE_TYPE.RADIO:
+      var p = document.createElement("div");
+      p.setAttribute("class", "row mt-2");
+      var c0 = document.createElement("span");
+      c0.setAttribute("class", "col");
+      c0.setAttribute(
+        "title",
+        "When selected, this is the value that appears in exported annotations"
+      );
+      c0.innerHTML = "id";
+      var c1 = document.createElement("span");
+      c1.setAttribute("class", "col");
+      c1.setAttribute(
+        "title",
+        "This is the text shown as an option to the annotator"
+      );
+      c1.innerHTML = "description";
+      var c2 = document.createElement("span");
+      c2.setAttribute("title", "The default value of this attribute");
+      c2.setAttribute("class", "col");
+      c2.innerHTML = "def.";
+      p.appendChild(c0);
+      p.appendChild(c1);
+      p.appendChild(c2);
+      document.getElementById("attribute_options").appendChild(p);
+
+      var options =
+        project.attributes[_via_attribute_being_updated][attr_id].options;
+      var option_id;
+      for (option_id in options) {
+        var option_desc = options[option_id];
+
+        var option_default =
+          project.attributes[_via_attribute_being_updated][attr_id]
+            .default_options[option_id];
+        attribute_property_add_option(
+          attr_id,
+          option_id,
+          option_desc,
+          option_default,
+          attr_type
+        );
+      }
+      attribute_property_add_new_entry_option(attr_id, attr_type);
+      break;
+    default:
+      console.log("Attribute type " + attr_type + " is unavailable");
+  }
+  opt.classList.remove("d-none");
+}
+
+function attribute_property_add_input_property(title, name, value, id, prop) {
+  let p = document.createElement("div");
+  p.setAttribute("class", "input-group mb-1");
+  let c0 = document.createElement("span");
+  c0.setAttribute("title", title);
+  c0.setAttribute("class", "input-group-text")
+  c0.innerHTML = name;
+  let c1b = document.createElement("input");
+  c1b.setAttribute("onchange", "attribute_property_on_update(this)");
+  c1b.setAttribute("type", "text");
+  c1b.setAttribute("class", "form-control");
+  if (typeof value !== "undefined") {
+    c1b.setAttribute("value", value);
+  }
+  c1b.setAttribute("id", id);
+  p.appendChild(c0);
+  p.appendChild(c1b);
+
+  prop.appendChild(p);
+}
+
+function attribute_property_add_option(
+  attr_id,
+  option_id,
+  option_desc,
+  option_default,
+  attribute_type
+) {
+  let p = document.createElement("div");
+  p.setAttribute("class", "row mb-1");
+  var c0 = document.createElement("div");
+  c0.setAttribute("class", "col")
+  var c0b = document.createElement("input");
+  c0b.setAttribute("type", "text");
+  c0b.setAttribute("value", option_id);
+  c0b.setAttribute("title", option_id);
+  c0b.setAttribute("onchange", "attribute_property_on_option_update(this)");
+  c0b.setAttribute("id", "_via_attribute_option_id_" + option_id);
+  c0b.setAttribute("class", "col form-control");
+
+  var c1 = document.createElement("div");
+  c1.setAttribute("class", "col")
+  var c1b = document.createElement("input");
+  c1b.setAttribute("type", "text");
+  c1b.setAttribute("class", "form-control")
+
+  if (attribute_type === VIA_ATTRIBUTE_TYPE.IMAGE) {
+    var option_desc_info = option_desc.length + " bytes of base64 image data";
+    c1b.setAttribute("value", option_desc_info);
+    c1b.setAttribute(
+      "title",
+      "To update, copy and paste base64 image data in this text box"
+    );
+  } else {
+    c1b.setAttribute("value", option_desc);
+    c1b.setAttribute("title", option_desc);
+  }
+  c1b.setAttribute("onchange", "attribute_property_on_option_update(this)");
+  c1b.setAttribute("id", "_via_attribute_option_description_" + option_id);
+
+  var c2 = document.createElement("div");
+  c2.setAttribute("class", "col");
+  var c2b = document.createElement("input");
+  c2b.setAttribute("type", attribute_type);
+  c2b.setAttribute("class", "form-check-input");
+  if (typeof option_default !== "undefined") {
+    c2b.checked = option_default;
+  }
+  if (
+    attribute_type === "radio" ||
+    attribute_type === "image" ||
+    attribute_type === "dropdown"
+  ) {
+    // ensured that user can activate only one radio button
+    c2b.setAttribute("type", "radio");
+    c2b.setAttribute("name", attr_id);
+  }
+
+  c2b.setAttribute("onchange", "attribute_property_on_option_update(this)");
+  c2b.setAttribute("id", "_via_attribute_option_default_" + option_id);
+
+  c0.appendChild(c0b);
+  c1.appendChild(c1b);
+  c2.appendChild(c2b);
+  p.appendChild(c0);
+  p.appendChild(c1);
+  p.appendChild(c2);
+
+  document.getElementById("attribute_options").appendChild(p);
+}
+
+function attribute_property_add_new_entry_option(attr_id, attribute_type) {
+  var p = document.createElement("div");
+  p.setAttribute("class", "row")
+  let c0 = document.createElement("div");
+  c0.setAttribute("class", "col mt-2 mb-3");
+  let c0b = document.createElement("input");
+  c0b.setAttribute("type", "text");
+  c0b.setAttribute("onchange", "attribute_property_on_option_add(this)");
+  c0b.setAttribute("id", "_via_attribute_new_option_id");
+  c0b.setAttribute("placeholder", "Add new option id");
+  c0b.setAttribute("class", "form-control")
+  c0.appendChild(c0b);
+  p.appendChild(c0);
+  document.getElementById("attribute_options").appendChild(p);
+}
+
+function attribute_property_on_update(p) {
+  var attr_id = get_current_attribute_id();
+  var attr_type = _via_attribute_being_updated;
+  var new_attr_type = p.value;
+
+  switch (p.id) {
+    case "attribute_name":
+      if (new_attr_type !== attr_id) {
+        Object.defineProperty(
+          project.attributes[attr_type],
+          new_attr_type,
+          Object.getOwnPropertyDescriptor(project.attributes[attr_type], attr_id)
+        );
+
+        delete project.attributes[attr_type][attr_id];
+        update_attributes_update_panel();
+        sidebar.annotation_editor_update_content();
+      }
+      break;
+    case "attribute_description":
+      project.attributes[attr_type][attr_id].description = new_attr_type;
+      update_attributes_update_panel();
+      sidebar.annotation_editor_update_content();
+      break;
+    case "attribute_default_value":
+      project.attributes[attr_type][attr_id].default_value = new_attr_type;
+      update_attributes_update_panel();
+      sidebar.annotation_editor_update_content();
+      break;
+    case "attribute_type":
+      var old_attr_type = project.attributes[attr_type][attr_id].type;
+      project.attributes[attr_type][attr_id].type = new_attr_type;
+      if (new_attr_type === VIA_ATTRIBUTE_TYPE.TEXT) {
+        project.attributes[attr_type][attr_id].default_value = "";
+        delete project.attributes[attr_type][attr_id].options;
+        delete project.attributes[attr_type][attr_id].default_options;
+      } else {
+        // add options entry (if missing)
+        if (!project.attributes[attr_type][attr_id].hasOwnProperty("options")) {
+          project.attributes[attr_type][attr_id].options = {};
+          project.attributes[attr_type][attr_id].default_options = {};
+        }
+        if (
+          project.attributes[attr_type][attr_id].hasOwnProperty("default_value")
+        ) {
+          delete project.attributes[attr_type][attr_id].default_value;
+        }
+
+        // 1. gather all the attribute values in existing metadata
+        var existing_attr_values = attribute_get_unique_values(
+          attr_type,
+          attr_id
+        );
+
+        // 2. for checkbox, radio, dropdown: create options based on existing options and existing values
+        for (var option_id in project.attributes[attr_type][attr_id]["options"]) {
+          if (!existing_attr_values.includes(option_id)) {
+            project.attributes[attr_type][attr_id]["options"][option_id] =
+              option_id;
+          }
+        }
+
+        // update existing metadata to reflect changes in attribute type
+        // ensure that attribute has only one value
+        for (var img_id in _via_img_metadata) {
+          for (var rindex in _via_img_metadata[img_id]["regions"]) {
+            if (
+              _via_img_metadata[img_id]["regions"][rindex][
+                "region_attributes"
+              ].hasOwnProperty(attr_id)
+            ) {
+              if (
+                old_attr_type === VIA_ATTRIBUTE_TYPE.CHECKBOX &&
+                (new_attr_type === VIA_ATTRIBUTE_TYPE.RADIO ||
+                  new_attr_type === VIA_ATTRIBUTE_TYPE.DROPDOWN)
+              ) {
+                // add only if checkbox has only single option selected
+                var sel_option_count = 0;
+                var sel_option_id;
+                for (var option_id in _via_img_metadata[img_id]["regions"][
+                  rindex
+                ]["region_attributes"][attr_id]) {
+                  if (
+                    _via_img_metadata[img_id]["regions"][rindex][
+                    "region_attributes"
+                    ][attr_id][option_id]
+                  ) {
+                    sel_option_count = sel_option_count + 1;
+                    sel_option_id = option_id;
+                  }
+                }
+                if (sel_option_count === 1) {
+                  _via_img_metadata[img_id]["regions"][rindex][
+                    "region_attributes"
+                  ][attr_id] = sel_option_id;
+                } else {
+                  // delete as multiple options cannot be represented as radio or dropdown
+                  delete _via_img_metadata[img_id]["regions"][rindex][
+                    "region_attributes"
+                  ][attr_id];
+                }
+              }
+              if (
+                (old_attr_type === VIA_ATTRIBUTE_TYPE.RADIO ||
+                  old_attr_type === VIA_ATTRIBUTE_TYPE.DROPDOWN) &&
+                new_attr_type === VIA_ATTRIBUTE_TYPE.CHECKBOX
+              ) {
+                var old_option_id =
+                  _via_img_metadata[img_id]["regions"][rindex][
+                  "region_attributes"
+                  ][attr_id];
+                _via_img_metadata[img_id]["regions"][rindex][
+                  "region_attributes"
+                ][attr_id] = {};
+                _via_img_metadata[img_id]["regions"][rindex][
+                  "region_attributes"
+                ][attr_id][old_option_id] = true;
+              }
+            }
+          }
+        }
+      }
+      show_attribute_properties();
+      show_attribute_options();
+      show_attribute_example();
+      sidebar.annotation_editor_update_content();
+      break;
+  }
+}
+
+function attribute_get_unique_values(attr_type, attr_id) {
+  var values = [];
+  switch (attr_type) {
+    case "file":
+      var img_id, attr_val;
+      for (img_id in _via_img_metadata) {
+        if (_via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id)) {
+          attr_val = _via_img_metadata[img_id].file_attributes[attr_id];
+          if (!values.includes(attr_val)) {
+            values.push(attr_val);
+          }
+        }
+      }
+      break;
+    case "region":
+      var img_id, attr_val, i;
+      for (img_id in _via_img_metadata) {
+        for (i = 0; i < _via_img_metadata[img_id].regions.length; ++i) {
+          if (
+            _via_img_metadata[img_id].regions[
+              i
+            ].region_attributes.hasOwnProperty(attr_id)
+          ) {
+            attr_val =
+              _via_img_metadata[img_id].regions[i].region_attributes[attr_id];
+            if (typeof attr_val === "object") {
+              for (var option_id in _via_img_metadata[img_id].regions[i]
+                .region_attributes[attr_id]) {
+                if (!values.includes(option_id)) {
+                  values.push(option_id);
+                }
+              }
+            } else {
+              if (!values.includes(attr_val)) {
+                values.push(attr_val);
+              }
+            }
+          }
+        }
+      }
+      break;
+    default:
+      break;
+  }
+  return values;
+}
+
+function attribute_property_on_option_update(p) {
+  var attr_id = get_current_attribute_id();
+  if (p.id.startsWith("_via_attribute_option_id_")) {
+    var old_key = p.id.substring("_via_attribute_option_id_".length);
+    var new_key = p.value;
+    if (old_key !== new_key) {
+      var option_id_test = attribute_property_option_id_is_valid(
+        attr_id,
+        new_key
+      );
+      if (option_id_test.is_valid) {
+        update_attribute_option_id_with_confirm(
+          _via_attribute_being_updated,
+          attr_id,
+          old_key,
+          new_key
+        );
+      } else {
+        p.value = old_key; // restore old value
+        show_message(option_id_test.message);
+        show_attribute_properties();
+      }
+      return;
+    }
+  }
+
+  if (p.id.startsWith("_via_attribute_option_description_")) {
+    var key = p.id.substring("_via_attribute_option_description_".length);
+    var old_value =
+      project.attributes[_via_attribute_being_updated][attr_id].options[key];
+    var new_value = p.value;
+    if (new_value !== old_value) {
+      project.attributes[_via_attribute_being_updated][attr_id].options[key] =
+        new_value;
+      show_attribute_properties();
+      sidebar.annotation_editor_update_content();
+    }
+  }
+
+  if (p.id.startsWith("_via_attribute_option_default_")) {
+    var new_default_option_id = p.id.substring(
+      "_via_attribute_option_default_".length
+    );
+    var old_default_option_id_list = Object.keys(
+      project.attributes[_via_attribute_being_updated][attr_id].default_options
+    );
+
+    if (old_default_option_id_list.length === 0) {
+      // default set for the first time
+      project.attributes[_via_attribute_being_updated][attr_id].default_options[
+        new_default_option_id
+      ] = p.checked;
+    } else {
+      switch (project.attributes[_via_attribute_being_updated][attr_id].type) {
+        case "image": // fallback
+        case "dropdown": // fallback
+        case "radio": // fallback
+          // to ensure that only one radio button is selected at a time
+          project.attributes[_via_attribute_being_updated][
+            attr_id
+          ].default_options = {};
+          project.attributes[_via_attribute_being_updated][
+            attr_id
+          ].default_options[new_default_option_id] = p.checked;
+          break;
+        case "checkbox":
+          project.attributes[_via_attribute_being_updated][
+            attr_id
+          ].default_options[new_default_option_id] = p.checked;
+          break;
+      }
+    }
+    // default option updated
+    attribute_property_on_option_default_update(
+      _via_attribute_being_updated,
+      attr_id,
+      new_default_option_id
+    ).then(function () {
+      show_attribute_properties();
+      sidebar.annotation_editor_update_content();
+    });
+  }
+}
+
+function attribute_property_on_option_default_update(
+  attribute_being_updated,
+  attr_id,
+  new_default_option_id
+) {
+  return new Promise(function (ok_callback, err_callback) {
+    // set all metadata to new_value if:
+    // - metadata[attr_id] is missing
+    // - metadata[attr_id] is set to option_old_value
+    var img_id, attr_value, n, i;
+    var attr_type = project.attributes[attribute_being_updated][attr_id].type;
+    switch (attribute_being_updated) {
+      case "file":
+        for (img_id in _via_img_metadata) {
+          if (
+            !_via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id)
+          ) {
+            _via_img_metadata[img_id].file_attributes[attr_id] =
+              new_default_option_id;
+          }
+        }
+        break;
+      case "region":
+        for (img_id in _via_img_metadata) {
+          n = _via_img_metadata[img_id].regions.length;
+          for (i = 0; i < n; ++i) {
+            if (
+              !_via_img_metadata[img_id].regions[
+                i
+              ].region_attributes.hasOwnProperty(attr_id)
+            ) {
+              _via_img_metadata[img_id].regions[i].region_attributes[attr_id] =
+                new_default_option_id;
+            }
+          }
+        }
+        break;
+    }
+    ok_callback();
+  });
+}
+
+function attribute_property_on_option_add(p) {
+  if (p.value === "" || p.value === null) {
+    return;
+  }
+
+  if (p.id === "_via_attribute_new_option_id") {
+    var attr_id = get_current_attribute_id();
+    var option_id = p.value;
+    var option_id_test = attribute_property_option_id_is_valid(
+      attr_id,
+      option_id
+    );
+    if (option_id_test.is_valid) {
+      project.attributes[_via_attribute_being_updated][attr_id].options[
+        option_id
+      ] = "";
+      show_attribute_options();
+      sidebar.annotation_editor_update_content();
+    } else {
+      show_message(option_id_test.message);
+      attribute_property_reset_new_entry_inputs();
+    }
+  }
+}
+
+function attribute_property_reset_new_entry_inputs() {
+  var container = document.getElementById("attribute_options");
+  var p = container.lastChild;
+  if (p.childNodes[0]) {
+    p.childNodes[0].value = "";
+  }
+  if (p.childNodes[1]) {
+    p.childNodes[1].value = "";
+  }
+}
+
+function attribute_property_show_new_entry_inputs(attr_id, attribute_type) {
+  var n0 = document.createElement("div");
+  n0.classList.add("property");
+  var n1a = document.createElement("span");
+  var n1b = document.createElement("input");
+  n1b.setAttribute("onchange", "attribute_property_on_option_add(this)");
+  n1b.setAttribute("placeholder", "Add new id");
+  n1b.setAttribute("value", "");
+  n1b.setAttribute("id", "_via_attribute_new_option_id");
+  n1a.appendChild(n1b);
+
+  var n2a = document.createElement("span");
+  var n2b = document.createElement("input");
+  n2b.setAttribute("onchange", "attribute_property_on_option_add(this)");
+  n2b.setAttribute("placeholder", "Optional description");
+  n2b.setAttribute("value", "");
+  n2b.setAttribute("id", "_via_attribute_new_option_description");
+  n2a.appendChild(n2b);
+
+  var n3a = document.createElement("span");
+  var n3b = document.createElement("input");
+  n3b.setAttribute("type", attribute_type);
+  if (attribute_type === "radio") {
+    n3b.setAttribute("name", attr_id);
+  }
+  n3b.setAttribute("onchange", "attribute_property_on_option_add(this)");
+  n3b.setAttribute("id", "_via_attribute_new_option_default");
+  n3a.appendChild(n3b);
+
+  n0.appendChild(n1a);
+  n0.appendChild(n2a);
+  n0.appendChild(n3a);
+
+  var container = document.getElementById("attribute_options");
+  container.appendChild(n0);
+}
+
+function attribute_property_option_id_is_valid(attr_id, new_option_id) {
+  var option_id;
+  for (option_id in project.attributes[_via_attribute_being_updated][attr_id]
+    .options) {
+    if (option_id === new_option_id) {
+      return {
+        is_valid: false,
+        message: "Option id [" + attr_id + "] already exists",
+      };
+    }
+  }
+
+  if (new_option_id.includes("__")) {
+    // reserved separator for attribute-id, row-id, option-id
+    return {
+      is_valid: false,
+      message: "Option id cannot contain two consecutive underscores",
+    };
+  }
+
+  return { is_valid: true };
+}
+
+function attribute_property_id_exists(name) {
+  var attr_name;
+  for (attr_name in project.attributes[_via_attribute_being_updated]) {
+    if (attr_name === name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function delete_existing_attribute_with_confirm() {
+  var attr_id = document.getElementById("user_input_attribute_id").value;
+  if (attr_id === "") {
+    show_message("Enter the name of attribute that you wish to delete");
+    return;
+  }
+  if (attribute_property_id_exists(attr_id)) {
+    var config = {
+      title:
+        "Delete " +
+        _via_attribute_being_updated +
+        " attribute [" +
+        attr_id +
+        "]",
+      warning:
+        "Warning: Deleting an attribute will lead to the attribute being deleted in all the annotations. Please click OK only if you are sure.",
+    };
+    var input = {
+      attr_type: {
+        type: "text",
+        name: "Attribute Type",
+        value: _via_attribute_being_updated,
+        disabled: true,
+      },
+      attr_id: {
+        type: "text",
+        name: "Attribute Id",
+        value: attr_id,
+        disabled: true,
+      },
+    };
+    invoke_with_user_inputs(delete_existing_attribute_confirmed, input, config);
+  } else {
+    show_message("Attribute [" + attr_id + "] does not exist!");
+  }
+}
+
+function delete_existing_attribute_confirmed(input) {
+  var attr_type = input.attr_type.value;
+  var attr_id = input.attr_id.value;
+  delete_existing_attribute(attr_type, attr_id);
+  document.getElementById("user_input_attribute_id").value = "";
+  show_message("Deleted " + attr_type + " attribute [" + attr_id + "]");
+  user_input_default_cancel_handler();
+}
+
+function delete_existing_attribute(attribute_type, attr_id) {
+  if (project.attributes[attribute_type].hasOwnProperty(attr_id)) {
+    var attr_id_list = Object.keys(project.attributes[attribute_type]);
+    if (attr_id_list.length === 1) {
+      _via_current_attribute_id = "";
+    } else {
+      var current_index = attr_id_list.indexOf(attr_id);
+      var next_index = current_index + 1;
+      if (next_index === attr_id_list.length) {
+        next_index = current_index - 1;
+      }
+      _via_current_attribute_id = attr_id_list[next_index];
+    }
+    delete project.attributes[attribute_type][attr_id];
+    delete_region_attribute_in_all_metadata(attr_id);
+    update_attributes_update_panel();
+    sidebar.annotation_editor_update_content();
+  }
+}
+
+function add_new_attribute_from_user_input() {
+  var attr_id = document.getElementById("user_input_attribute_id").value;
+  if (attr_id === "") {
+    show_message("Enter the name of attribute that you wish to delete");
+    return;
+  }
+
+  if (attribute_property_id_exists(attr_id)) {
+    show_message(
+      "The " +
+      _via_attribute_being_updated +
+      " attribute [" +
+      attr_id +
+      "] already exists."
+    );
+  } else {
+    _via_current_attribute_id = attr_id;
+    add_new_attribute(attr_id);
+    update_attributes_update_panel();
+    sidebar.annotation_editor_update_content();
+    show_message(
+      "Added " + _via_attribute_being_updated + " attribute [" + attr_id + "]."
+    );
+  }
+}
+
+function add_new_attribute(attribute_id) {
+  project.attributes[_via_attribute_being_updated][attribute_id] = {};
+  project.attributes[_via_attribute_being_updated][attribute_id].type = "text";
+  project.attributes[_via_attribute_being_updated][attribute_id].description = "";
+  project.attributes[_via_attribute_being_updated][attribute_id].default_value =
+    "";
+}
+
+function update_current_attribute_id(p) {
+  _via_current_attribute_id = p.options[p.selectedIndex].value;
+  update_attribute_properties_panel();
+}
+
+function get_current_attribute_id() {
+  return document.getElementById("attributes_name_list").value;
+}
+
+function update_attribute_option_id_with_confirm(
+  attr_type,
+  attr_id,
+  option_id,
+  new_option_id
+) {
+  var is_delete = false;
+  var config;
+  if (new_option_id === "" || typeof new_option_id === "undefined") {
+    // an empty new_option_id indicates deletion of option_id
+    config = { title: "Delete an option for " + attr_type + " attribute" };
+    is_delete = true;
+  } else {
+    config = { title: "Rename an option for " + attr_type + " attribute" };
+  }
+
+  var input = {
+    attr_type: {
+      type: "text",
+      name: "Attribute Type",
+      value: attr_type,
+      disabled: true,
+    },
+    attr_id: {
+      type: "text",
+      name: "Attribute Id",
+      value: attr_id,
+      disabled: true,
+    },
+  };
+
+  if (is_delete) {
+    input["option_id"] = {
+      type: "text",
+      name: "Attribute Option",
+      value: option_id,
+      disabled: true,
+    };
+  } else {
+    (input["option_id"] = {
+      type: "text",
+      name: "Attribute Option (old)",
+      value: option_id,
+      disabled: true,
+    }),
+      (input["new_option_id"] = {
+        type: "text",
+        name: "Attribute Option (new)",
+        value: new_option_id,
+        disabled: true,
+      });
+  }
+
+  invoke_with_user_inputs(
+    update_attribute_option_id_confirmed,
+    input,
+    config,
+    update_attribute_option_id_cancel
+  );
+}
+
+function update_attribute_option_id_cancel(input) {
+  update_attribute_properties_panel();
+}
+
+function update_attribute_option_id_confirmed(input) {
+  var attr_type = input.attr_type.value;
+  var attr_id = input.attr_id.value;
+  var option_id = input.option_id.value;
+  var is_delete;
+  var new_option_id;
+  if (
+    typeof input.new_option_id === "undefined" ||
+    input.new_option_id === ""
+  ) {
+    is_delete = true;
+    new_option_id = "";
+  } else {
+    is_delete = false;
+    new_option_id = input.new_option_id.value;
+  }
+
+  update_attribute_option(
+    is_delete,
+    attr_type,
+    attr_id,
+    option_id,
+    new_option_id
+  );
+
+  if (is_delete) {
+    show_message(
+      "Deleted option [" +
+      option_id +
+      "] for " +
+      attr_type +
+      " attribute [" +
+      attr_id +
+      "]."
+    );
+  } else {
+    show_message(
+      "Renamed option [" +
+      option_id +
+      "] to [" +
+      new_option_id +
+      "] for " +
+      attr_type +
+      " attribute [" +
+      attr_id +
+      "]."
+    );
+  }
+  update_attribute_properties_panel();
+  sidebar.annotation_editor_update_content();
+  user_input_default_cancel_handler();
+}
+
+function update_attribute_option(
+  is_delete,
+  attr_type,
+  attr_id,
+  option_id,
+  new_option_id
+) {
+  switch (attr_type) {
+    case "region":
+      update_region_attribute_option_in_all_metadata(
+        is_delete,
+        attr_id,
+        option_id,
+        new_option_id
+      );
+      if (!is_delete) {
+        Object.defineProperty(
+          project.attributes[attr_type][attr_id].options,
+          new_option_id,
+          Object.getOwnPropertyDescriptor(
+            project.attributes[_via_attribute_being_updated][attr_id].options,
+            option_id
+          )
+        );
+      }
+      delete project.attributes.region[attr_id].options[option_id];
+
+      break;
+    case "file":
+      update_file_attribute_option_in_all_metadata(attr_id, option_id);
+      if (!is_delete) {
+        Object.defineProperty(
+          project.attributes[attr_type][attr_id].options,
+          new_option_id,
+          Object.getOwnPropertyDescriptor(
+            project.attributes[_via_attribute_being_updated][attr_id].options,
+            option_id
+          )
+        );
+      }
+
+      delete project.attributes.file[attr_id].options[option_id];
+      break;
+  }
+}
+
+function update_file_attribute_option_in_all_metadata(
+  is_delete,
+  attr_id,
+  option_id,
+  new_option_id
+) {
+  var image_id;
+  for (image_id in _via_img_metadata) {
+    if (_via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id)) {
+      if (
+        _via_img_metadata[image_id].file_attributes[attr_id].hasOwnProperty(
+          option_id
+        )
+      ) {
+        Object.defineProperty(
+          _via_img_metadata[image_id].file_attributes[attr_id],
+          new_option_id,
+          Object.getOwnPropertyDescriptor(
+            _via_img_metadata[image_id].file_attributes[attr_id],
+            option_id
+          )
+        );
+        delete _via_img_metadata[image_id].file_attributes[attr_id][option_id];
+      }
+    }
+  }
+}
+
+function update_region_attribute_option_in_all_metadata(
+  is_delete,
+  attr_id,
+  option_id,
+  new_option_id
+) {
+  var image_id;
+  for (image_id in _via_img_metadata) {
+    for (var i = 0; i < _via_img_metadata[image_id].regions.length; ++i) {
+      if (
+        _via_img_metadata[image_id].regions[i].region_attributes.hasOwnProperty(
+          attr_id
+        )
+      ) {
+        if (
+          _via_img_metadata[image_id].regions[i].region_attributes[
+            attr_id
+          ].hasOwnProperty(option_id)
+        ) {
+          Object.defineProperty(
+            _via_img_metadata[image_id].regions[i].region_attributes[attr_id],
+            new_option_id,
+            Object.getOwnPropertyDescriptor(
+              _via_img_metadata[image_id].regions[i].region_attributes[attr_id],
+              option_id
+            )
+          );
+          delete _via_img_metadata[image_id].regions[i].region_attributes[
+            attr_id
+          ][option_id];
+        }
+      }
+    }
+  }
+}
+
+function delete_region_attribute_in_all_metadata(attr_id) {
+  var image_id;
+  for (image_id in _via_img_metadata) {
+    for (var i = 0; i < _via_img_metadata[image_id].regions.length; ++i) {
+      if (
+        _via_img_metadata[image_id].regions[i].region_attributes.hasOwnProperty(
+          attr_id
+        )
+      ) {
+        delete _via_img_metadata[image_id].regions[i].region_attributes[
+          attr_id
+        ];
+      }
+    }
+  }
+}
+
+function delete_file_attribute_option_from_all_metadata(attr_id, option_id) {
+  var image_id;
+  for (image_id in _via_img_metadata) {
+    if (_via_img_metadata.hasOwnProperty(image_id)) {
+      delete_file_attribute_option_from_metadata(image_id, attr_id, option_id);
+    }
+  }
+}
+
+function delete_file_attribute_option_from_metadata(
+  image_id,
+  attr_id,
+  option_id
+) {
+  var i;
+  if (_via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id)) {
+    if (
+      _via_img_metadata[image_id].file_attributes[attr_id].hasOwnProperty(
+        option_id
+      )
+    ) {
+      delete _via_img_metadata[image_id].file_attributes[attr_id][option_id];
+    }
+  }
+}
+
+function delete_file_attribute_from_all_metadata(image_id, attr_id) {
+  var image_id;
+  for (image_id in _via_img_metadata) {
+    if (_via_img_metadata.hasOwnProperty(image_id)) {
+      if (_via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id)) {
+        delete _via_img_metadata[image_id].file_attributes[attr_id];
+      }
+    }
+  }
+}
+
+//
+// invoke a method after receiving inputs from user
+//
+function invoke_with_user_inputs(ok_handler, input, config, cancel_handler) {
+  setup_user_input_panel(ok_handler, input, config, cancel_handler);
+  show_user_input_panel();
+}
+
+function setup_user_input_panel(
+  ok_handler,
+  input,
+  config,
+  cancel_handler,
+  fileinput = false
+) {
+  // create html page with OK and CANCEL button
+  // when OK is clicked
+  //  - setup input with all the user entered values
+  //  - invoke handler with input
+  // when CANCEL is clicked
+  //  - invoke user_input_cancel()
+  _via_user_input_ok_handler = ok_handler;
+  _via_user_input_cancel_handler = cancel_handler;
+  _via_user_input_data = input;
+
+  //var p = document.getElementById('user_input_panel');
+  var c = document.createElement("div");
+  c.setAttribute("class", "content");
+  var html = [];
+
+  html.push('<div class="container">');
+  var key;
+  for (key in _via_user_input_data) {
+    html.push('<div class="row">');
+    html.push('<div class="col">' + _via_user_input_data[key].name + "</div>");
+    var disabled_html = "";
+    if (_via_user_input_data[key].disabled) {
+      disabled_html = 'disabled="disabled"';
+    }
+    var value_html = "";
+    if (_via_user_input_data[key].value) {
+      value_html = 'value="' + _via_user_input_data[key].value + '"';
+    }
+    switch (_via_user_input_data[key].type) {
+      case "checkbox":
+        if (_via_user_input_data[key].checked) {
+          value_html = 'checked="checked"';
+        } else {
+          value_html = "";
+        }
+        html.push(
+          '<div class="col">' +
+          '<input class="form-check-input" ' +
+          value_html +
+          " " +
+          disabled_html +
+          " " +
+          'type="checkbox" id="' +
+          key +
+          '"></div>'
+        );
+        break;
+      case "text":
+        var size = "50";
+        if (_via_user_input_data[key].size) {
+          size = _via_user_input_data[key].size;
+        }
+        var placeholder = "";
+        if (_via_user_input_data[key].placeholder) {
+          placeholder = _via_user_input_data[key].placeholder;
+        }
+        html.push(
+          '<div class="col">' +
+          '<input class="form-control" ' +
+          value_html +
+          " " +
+          disabled_html +
+          " " +
+          'size="' +
+          size +
+          '" ' +
+          'placeholder="' +
+          placeholder +
+          '" ' +
+          'type="text" id="' +
+          key +
+          '"></div>'
+        );
+
+        break;
+      case "textarea":
+        var rows = "5";
+        var cols = "50";
+        if (_via_user_input_data[key].rows) {
+          rows = _via_user_input_data[key].rows;
+        }
+        if (_via_user_input_data[key].cols) {
+          cols = _via_user_input_data[key].cols;
+        }
+        var placeholder = "";
+        if (_via_user_input_data[key].placeholder) {
+          placeholder = _via_user_input_data[key].placeholder;
+        }
+        html.push(
+          '<div class="col">' +
+          '<textarea class="form-control" ' +
+          disabled_html +
+          " " +
+          'rows="' +
+          rows +
+          '" ' +
+          'cols="' +
+          cols +
+          '" ' +
+          'placeholder="' +
+          placeholder +
+          '" ' +
+          'id="' +
+          key +
+          '">' +
+          value_html +
+          "</textarea></div>"
+        );
+
+        break;
+    }
+    html.push("</div>"); // end of row
+  }
+  html.push("</div>"); // end of user_input div
+  // optional warning before confirmation
+  if (config.hasOwnProperty("warning")) {
+    html.push('<div class="alert alert-warning">' + config.warning + "</div>");
+  }
+  //html.push("<button type=\"button\" class=\"btn btn-primary\" onclick=\"user_input_parse_and_invoke_handler()\" id=\"user_input_ok_button\">Ok</button>" + "<button type=\"button\" class=\"btn btn-secondary\" onclick=\"user_input_cancel_handler()\" id=\"user_input_cancel_button\">Cancel</button>");
+
+  c.innerHTML = html.join("");
+  //p.innerHTML = '';
+
+  // html.push("<button type=\"button\" class=\"btn btn-primary\" onclick=\"user_input_parse_and_invoke_handler()\" id=\"user_input_ok_button\">Ok</button>" + "<button type=\"button\" class=\"btn btn-secondary\" onclick=\"user_input_cancel_handler()\" id=\"user_input_cancel_button\">Cancel</button>");
+
+  let body = html.join("");
+
+  let footer =
+    '<button type="button" class="btn btn-primary" onclick="user_input_parse_and_invoke_handler()" id="user_input_ok_button">Ok</button>' +
+    '<button type="button" class="btn btn-secondary" onclick="user_input_cancel_handler()" id="user_input_cancel_button">Cancel</button>';
+
+  modal.show(config.title, body, footer);
+  //p.appendChild(c);
+}
+
+function user_input_default_cancel_handler() {
+  hide_user_input_panel();
+  _via_user_input_data = {};
+  _via_user_input_ok_handler = null;
+  _via_user_input_cancel_handler = null;
+}
+
+function user_input_cancel_handler() {
+  if (_via_user_input_cancel_handler) {
+    _via_user_input_cancel_handler();
+  }
+  user_input_default_cancel_handler();
+}
+
+function user_input_parse_and_invoke_handler() {
+  var elist = document.getElementsByClassName("_via_user_input_variable");
+  var i;
+  for (i = 0; i < elist.length; ++i) {
+    var eid = elist[i].id;
+    if (_via_user_input_data.hasOwnProperty(eid)) {
+      switch (_via_user_input_data[eid].type) {
+        case "checkbox":
+          _via_user_input_data[eid].value = elist[i].checked;
+          break;
+        default:
+          _via_user_input_data[eid].value = elist[i].value;
+          break;
+      }
+    }
+  }
+  if (typeof _via_user_input_data.confirm !== "undefined") {
+    if (_via_user_input_data.confirm.value) {
+      _via_user_input_ok_handler(_via_user_input_data);
+    } else {
+      if (_via_user_input_cancel_handler) {
+        _via_user_input_cancel_handler();
+      }
+    }
+  } else {
+    _via_user_input_ok_handler(_via_user_input_data);
+  }
+  user_input_default_cancel_handler();
+}
+
+function show_user_input_panel() {
+  document.getElementById("user_input_panel").style.display = "block";
+}
+
+function hide_user_input_panel() {
+  $("#staticBackdropModal").modal("hide");
+  document.getElementById("user_input_panel").style.display = "none";
+}
+
+//
+// image grid
+//
+function image_grid_init() {
+  var p = document.getElementById("image_grid_content");
+  p.focus();
+  p.addEventListener("mousedown", image_grid_mousedown_handler, false);
+  p.addEventListener("mouseup", image_grid_mouseup_handler, false);
+  p.addEventListener("dblclick", image_grid_dblclick_handler, false);
+
+  image_grid_set_content_panel_height_fixed();
+
+  //add event listeeners for dropdown options
+
+  let policy = settings.image_grid_content;
+  image_grid_set_policy_btn_text(policy);
+  $("#image_grid_show_image_policy_dropdown").on("click", "a", function () {
+    let policy = $(this).data("value");
+    image_grid_set_policy_btn_text(policy);
+    image_grid_onchange_show_image_policy(policy);
+  });
+}
+
+function image_grid_set_policy_btn_text(policy) {
+  switch (policy) {
+    case "all":
+      $("#image_grid_show_image_policy").text("all");
+      break;
+    case "first_mid_last":
+      $("#image_grid_show_image_policy").text("first, mid, last");
+      break;
+    case "even_indexed":
+      $("#image_grid_show_image_policy").text("even");
+      break;
+    case "odd_indexed":
+      $("#image_grid_show_image_policy").text("odd");
+      break;
+    case "gap5":
+      $("#image_grid_show_image_policy").text("gap 5");
+      break;
+    case "gap25":
+      $("#image_grid_show_image_policy").text("gap 25");
+      break;
+    case "gap50":
+      $("#image_grid_show_image_policy").text("gap 50");
+      break;
+    default:
+      $("#image_grid_show_image_policy").text("all");
+  }
+}
+
+function image_grid_update() {
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    image_grid_set_content(_via_image_grid_img_index_list);
+  }
+}
+
+function image_grid_toggle() {
+  var p = document.getElementById("toolbar_image_grid_toggle");
+  if (
+    _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID
+  ) {
+    image_grid_clear_all_groups();
+    show_single_image_view();
+  } else {
+    show_image_grid_view();
+  }
+}
+
+function image_grid_show_all_project_images() {
+  var all_img_index_list = [];
+  var i, n;
+  //n = _via_image_id_list.length;
+  n = _via_img_fn_list_img_index_list.length;
+  for (i = 0; i < n; ++i) {
+    all_img_index_list.push(_via_img_fn_list_img_index_list[i]);
+  }
+
+  image_grid_clear_all_groups();
+
+  var p = document.getElementById("image_grid_toolbar_group_by_select");
+  p.selectedIndex = 0;
+
+  image_grid_set_content(all_img_index_list);
+}
+
+function image_grid_clear_all_groups() {
+  var i, n;
+  n = _via_image_grid_group_var.length;
+  for (i = 0; i < n; ++i) {
+    image_grid_remove_html_group_panel(_via_image_grid_group_var[i]);
+    image_grid_group_by_select_set_disabled(
+      _via_image_grid_group_var[i].type,
+      _via_image_grid_group_var[i].name,
+      false
+    );
+  }
+  _via_image_grid_group = {};
+  _via_image_grid_group_var = [];
+}
+
+function image_grid_set_content(img_index_list) {
+  if (img_index_list.length === 0) {
+    return;
+  }
+  if (_via_image_grid_load_ongoing) {
+    return;
+  }
+
+  _via_image_grid_img_index_list = img_index_list.slice(0);
+  _via_image_grid_selected_img_index_list = img_index_list.slice(0);
+
+  document.getElementById("image_grid_group_by_img_count").innerHTML =
+    _via_image_grid_img_index_list.length.toString();
+
+  _via_image_grid_page_first_index = 0;
+  _via_image_grid_page_last_index = null;
+  _via_image_grid_stack_prev_page = [];
+  _via_image_grid_page_img_index_list = [];
+
+  image_grid_clear_content();
+  image_grid_set_content_panel_height_fixed();
+  _via_image_grid_load_ongoing = true;
+
+  var n = _via_image_grid_img_index_list.length;
+  switch (settings.image_grid_content) {
+    case "all":
+      _via_image_grid_page_img_index_list =
+        _via_image_grid_img_index_list.slice(0);
+      break;
+    case "first_mid_last":
+      if (n < 3) {
+        var i;
+        for (i = 0; i < n; ++i) {
+          _via_image_grid_page_img_index_list.push(
+            _via_image_grid_img_index_list[i]
+          );
+        }
+      } else {
+        _via_image_grid_page_img_index_list.push(
+          _via_image_grid_img_index_list[0]
+        );
+        _via_image_grid_page_img_index_list.push(
+          _via_image_grid_img_index_list[Math.floor(n / 2)]
+        );
+        _via_image_grid_page_img_index_list.push(
+          _via_image_grid_img_index_list[n - 1]
+        );
+      }
+      break;
+    case "even_indexed":
+      var i;
+      for (i = 0; i < n; ++i) {
+        if (i % 2 !== 0) {
+          // since the user views (i+1) based indexing
+          _via_image_grid_page_img_index_list.push(
+            _via_image_grid_img_index_list[i]
+          );
+        }
+      }
+      break;
+    case "odd_indexed":
+      var i;
+      for (i = 0; i < n; ++i) {
+        if (i % 2 === 0) {
+          // since the user views (i+1) based indexing
+          _via_image_grid_page_img_index_list.push(
+            _via_image_grid_img_index_list[i]
+          );
+        }
+      }
+      break;
+    case "gap5": // fallback
+    case "gap25": // fallback
+    case "gap50": // fallback
+      var del = parseInt(
+        _via_settings.ui.image_grid.show_image_policy.substring("gap".length)
+      );
+      var i;
+      for (i = 0; i < n; i = i + del) {
+        _via_image_grid_page_img_index_list.push(
+          _via_image_grid_img_index_list[i]
+        );
+      }
+      break;
+    default:
+      _via_image_grid_page_img_index_list =
+        _via_image_grid_img_index_list.slice(0);
+  }
+
+  _via_image_grid_visible_img_index_list = [];
+
+  image_grid_update_sel_count_html();
+  sidebar.annotation_editor_update_content();
+
+  image_grid_content_append_img(_via_image_grid_page_first_index);
+
+  show_message(
+    "[Click] toggles selection, " +
+    "[Shift + Click] selects everything a image, " +
+    "[Click] or [Ctrl + Click] removes selection of all subsequent or preceeding images."
+  );
+}
+
+function image_grid_clear_content() {
+  var img_container = document.getElementById("image_grid_content_img");
+  var img_rshape = document.getElementById("image_grid_content_rshape");
+  img_container.innerHTML = "";
+  img_rshape.innerHTML = "";
+  _via_image_grid_visible_img_index_list = [];
+}
+
+function image_grid_set_content_panel_height_fixed() {
+  var pc = document.getElementById("image_grid_content");
+  var de = document.documentElement;
+  pc.style.height = de.clientHeight - 3 * ui_top_panel.offsetHeight + "px";
+}
+
+// We do not know how many images will fit in the display area.
+// Therefore, we add images one-by-one until overflow of parent
+// container is detected.
+function image_grid_content_append_img(img_grid_index) {
+  let img_index = _via_image_grid_page_img_index_list[img_grid_index];
+  let html_img_id = image_grid_get_html_img_id(img_index);
+  let img_id = _via_image_id_list[img_index];
+  let e = document.createElement("img");
+  if (_via_img_fileref[img_id] instanceof File) {
+    var img_reader = new FileReader();
+    img_reader.addEventListener(
+      "error",
+      function () {
+        //@todo
+      },
+      false
+    );
+    img_reader.addEventListener(
+      "load",
+      function () {
+        e.src = img_reader.result.toString(); //MODSote
+      },
+      false
+    );
+    img_reader.readAsDataURL(_via_img_fileref[img_id]);
+  } else {
+    e.src = _via_img_src[img_id];
+  }
+  e.setAttribute("id", html_img_id);
+  //e.setAttribute('height', _via_settings.ui.image_grid.img_height + 'px');
+  e.style.height = _via_settings.ui.image_grid.img_height + "px";
+  e.setAttribute(
+    "title",
+    "[" + (img_index + 1) + "] " + _via_img_metadata[img_id].filename
+  );
+  e.addEventListener("load", image_grid_on_img_load, false);
+  e.addEventListener("error", image_grid_on_img_error, false);
+  e.classList.add("img-thumbnail");
+
+  document.getElementById("image_grid_content_img").appendChild(e);
+}
+
+function image_grid_on_img_load(e) {
+  var img = e.target;
+  var img_index = image_grid_parse_html_img_id(img.id);
+  project.fileLoadOnSuccess(img_index);
+
+  image_grid_add_img_if_possible(img);
+}
+
+function image_grid_on_img_error(e) {
+  var img = e.target;
+  var img_index = image_grid_parse_html_img_id(img.id);
+  project.fileLoadOnFail(img_index);
+  image_grid_add_img_if_possible(img);
+}
+
+function image_grid_add_img_if_possible(img) {
+  let img_index = image_grid_parse_html_img_id(img.id);
+
+  let p = document.getElementById("image_grid_content_img");
+  let img_bottom_right_corner = parseInt(img.offsetTop) + parseInt(img.height);
+  if (p.clientHeight < img_bottom_right_corner) {
+    // stop as addition of this image caused overflow of parent container
+    let img_container = document.getElementById("image_grid_content_img");
+    img_container.removeChild(img);
+
+    if (_via_settings.ui.image_grid.show_region_shape) {
+      image_grid_page_show_all_regions();
+    }
+    _via_image_grid_load_ongoing = false;
+
+    var index = _via_image_grid_page_img_index_list.indexOf(img_index);
+    _via_image_grid_page_last_index = index;
+
+    // setup prev, next navigation
+    var info = document.getElementById("image_grid_nav");
+    var html = [];
+    var first_index = _via_image_grid_page_first_index;
+    var last_index = _via_image_grid_page_last_index - 1;
+    html.push(
+      '<span class="input-group-text">Showing&nbsp;' +
+      (first_index + 1) +
+      " to " +
+      (last_index + 1) +
+      "&nbsp;:</span>"
+    );
+    if (_via_image_grid_stack_prev_page.length) {
+      html.push(
+        '<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_prev()">Prev</button>'
+      );
+    } else {
+      html.push(
+        '<button type="button" class="btn btn-sm btn-outline-secondary disabled">Prev</button>'
+      );
+    }
+    html.push(
+      '<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_next()">Next</button>'
+    );
+
+    info.innerHTML = html.join("");
+  } else {
+    // process this image and trigger addition of next image in sequence
+    var img_fn_list_index =
+      _via_image_grid_page_img_index_list.indexOf(img_index);
+    var next_img_fn_list_index = img_fn_list_index + 1;
+
+    _via_image_grid_visible_img_index_list.push(img_index);
+    var is_selected =
+      _via_image_grid_selected_img_index_list.indexOf(img_index) !== -1;
+    if (!is_selected) {
+      image_grid_update_img_select(img_index, "unselect");
+    }
+
+    if (next_img_fn_list_index !== _via_image_grid_page_img_index_list.length) {
+      if (_via_image_grid_load_ongoing) {
+        image_grid_content_append_img(img_fn_list_index + 1);
+      } else {
+        // image grid load operation was cancelled
+        _via_image_grid_page_last_index = _via_image_grid_page_first_index; // load this page again
+
+        var info = document.getElementById("image_grid_nav");
+        var html = [];
+        html.push('<span class="input-group-text">Cancelled&nbsp;:</span>');
+        if (_via_image_grid_stack_prev_page.length) {
+          html.push(
+            '<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_prev()">Prev</button>'
+          );
+        } else {
+          html.push(
+            '<button type="button" class="btn btn-sm btn-outline-secondary disabled">Prev</button>'
+          );
+        }
+        html.push(
+          '<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_next()">Next</button>'
+        );
+        info.innerHTML = html.join("");
+      }
+    } else {
+      // last page
+      var index = _via_image_grid_page_img_index_list.indexOf(img_index);
+      _via_image_grid_page_last_index = index;
+
+      if (_via_settings.ui.image_grid.show_region_shape) {
+        image_grid_page_show_all_regions();
+      }
+      _via_image_grid_load_ongoing = false;
+
+      // setup prev, next navigation
+      var info = document.getElementById("image_grid_nav");
+      var html = [];
+      var first_index = _via_image_grid_page_first_index;
+      var last_index = _via_image_grid_page_last_index;
+      html.push(
+        '<span class="input-group-text">Showing&nbsp;' +
+        (first_index + 1) +
+        " to " +
+        (last_index + 1) +
+        " (end)&nbsp;</span>"
+      );
+      if (_via_image_grid_stack_prev_page.length) {
+        html.push(
+          '<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_prev()">Prev</button>'
+        );
+      } else {
+        html.push(
+          '<button type="button" class="btn btn-sm btn-outline-secondary disabled">Prev</button>'
+        );
+      }
+      html.push(
+        '<button type="button" class="btn btn-sm btn-outline-secondary disabled">Next</button>'
+      );
+
+      info.innerHTML = html.join("");
+    }
+  }
+}
+
+function image_grid_onchange_show_image_policy(policy) {
+  settings.image_grid_content = policy;
+  settings.save();
+  image_grid_set_content(_via_image_grid_img_index_list);
+}
+
+function image_grid_page_show_all_regions() {
+  var all_promises = [];
+  if (_via_settings.ui.image_grid.show_region_shape) {
+    var p = document.getElementById("image_grid_content_img");
+    var n = p.childNodes.length;
+    var i;
+    for (i = 0; i < n; ++i) {
+      // draw region shape into global canvas for image grid
+      var img_index = image_grid_parse_html_img_id(p.childNodes[i].id);
+      var img_param = []; // [width, height, originalWidth, originalHeight, x, y]
+      img_param.push(parseInt(p.childNodes[i].width));
+      img_param.push(parseInt(p.childNodes[i].height));
+      img_param.push(parseInt(p.childNodes[i].naturalWidth));
+      img_param.push(parseInt(p.childNodes[i].naturalHeight));
+      img_param.push(
+        parseInt(p.childNodes[i].offsetLeft) +
+        parseInt(p.childNodes[i].clientLeft)
+      );
+      img_param.push(
+        parseInt(p.childNodes[i].offsetTop) +
+        parseInt(p.childNodes[i].clientTop)
+      );
+      var promise = image_grid_show_region_shape(img_index, img_param);
+      all_promises.push(promise);
+    }
+    // @todo: ensure that all promises are fulfilled
+  }
+}
+
+function image_grid_is_region_in_current_group(r) {
+  var i, n;
+  n = _via_image_grid_group_var.length;
+  if (n === 0) {
+    return true;
+  }
+
+  for (i = 0; i < n; ++i) {
+    if (_via_image_grid_group_var[i].type === "region") {
+      var group_value =
+        _via_image_grid_group_var[i].values[
+        _via_image_grid_group_var[i].current_value_index
+        ];
+      if (r[_via_image_grid_group_var[i].name] != group_value) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+function image_grid_show_region_shape(img_index, img_param) {
+  return new Promise(function (ok_callback, err_callback) {
+    var i;
+    var img_id = _via_image_id_list[img_index];
+    var html_img_id = image_grid_get_html_img_id(img_index);
+    var n = _via_img_metadata[img_id].regions.length;
+    var is_in_group = false;
+    for (i = 0; i < n; ++i) {
+      if (
+        !image_grid_is_region_in_current_group(
+          _via_img_metadata[img_id].regions[i].region_attributes
+        )
+      ) {
+        // skip drawing this region which is not in current group
+        continue;
+      }
+
+      var r = _via_img_metadata[img_id].regions[i].shape_attributes;
+      var dimg; // region coordinates in original image space
+      switch (r.name) {
+        case VIA_REGION_SHAPE.RECT:
+          dimg = [r["x"], r["y"], r["x"] + r["width"], r["y"] + r["height"]];
+          break;
+        case VIA_REGION_SHAPE.CIRCLE:
+          dimg = [r["cx"], r["cy"], r["cx"] + r["r"], r["cy"] + r["r"]];
+          break;
+        case VIA_REGION_SHAPE.ELLIPSE:
+          dimg = [r["cx"], r["cy"], r["cx"] + r["rx"], r["cy"] + r["ry"]];
+          break;
+        case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON
+        case VIA_REGION_SHAPE.POLYGON:
+          var j;
+          dimg = [];
+          for (j = 0; j < r["all_points_x"].length; ++j) {
+            dimg.push(r["all_points_x"][j]);
+            dimg.push(r["all_points_y"][j]);
+          }
+          break;
+        case VIA_REGION_SHAPE.POINT:
+          dimg = [r["cx"], r["cy"]];
+          break;
+      }
+      var scale_factor = img_param[1] / img_param[3]; // new_height / original height
+      var offset_x = img_param[4];
+      var offset_y = img_param[5];
+      var r2 = new _via_region(
+        r.name,
+        i,
+        dimg,
+        scale_factor,
+        offset_x,
+        offset_y
+      );
+      var r2_svg = r2.get_svg_element();
+      r2_svg.setAttribute("id", image_grid_get_html_region_id(img_index, i));
+      r2_svg.setAttribute("class", html_img_id);
+      r2_svg.setAttribute("fill", _via_settings.ui.image_grid.rshape_fill);
+      //r2_svg.setAttribute('fill-opacity', _via_settings.ui.image_grid.rshape_fill_opacity);
+      r2_svg.setAttribute("stroke", _via_settings.ui.image_grid.rshape_stroke);
+      r2_svg.setAttribute(
+        "stroke-width",
+        _via_settings.ui.image_grid.rshape_stroke_width
+      );
+      document.getElementById("image_grid_content_rshape").appendChild(r2_svg);
+    }
+  });
+}
+
+function image_grid_image_size_increase() {
+  var new_img_height =
+    _via_settings.ui.image_grid.img_height + VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE;
+  _via_settings.ui.image_grid.img_height = new_img_height;
+
+  _via_image_grid_page_last_index = null;
+  image_grid_update();
+}
+
+function image_grid_image_size_decrease() {
+  var new_img_height =
+    _via_settings.ui.image_grid.img_height - VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE;
+  if (new_img_height > 1) {
+    _via_settings.ui.image_grid.img_height = new_img_height;
+    _via_image_grid_page_last_index = null;
+    image_grid_update();
+  }
+}
+
+function image_grid_image_size_reset() {
+  var new_img_height = _via_settings.ui.image_grid.img_height;
+  if (new_img_height > 1) {
+    _via_settings.ui.image_grid.img_height = new_img_height;
+    _via_image_grid_page_last_index = null;
+    image_grid_update();
+  }
+}
+
+function image_grid_mousedown_handler(e) {
+  e.preventDefault();
+  _via_image_grid_mousedown_img_index = image_grid_parse_html_img_id(
+    e.target.id
+  );
+}
+
+function image_grid_mouseup_handler(e) {
+  e.preventDefault();
+  var last_mouseup_img_index = _via_image_grid_mouseup_img_index;
+  _via_image_grid_mouseup_img_index = image_grid_parse_html_img_id(e.target.id);
+  if (
+    isNaN(_via_image_grid_mousedown_img_index) ||
+    isNaN(_via_image_grid_mouseup_img_index)
+  ) {
+    last_mouseup_img_index = _via_image_grid_img_index_list[0];
+    image_grid_group_select_none();
+    return;
+  }
+
+  var mousedown_img_arr_index = _via_image_grid_img_index_list.indexOf(
+    _via_image_grid_mousedown_img_index
+  );
+  var mouseup_img_arr_index = _via_image_grid_img_index_list.indexOf(
+    _via_image_grid_mouseup_img_index
+  );
+
+  var start = -1;
+  var end = -1;
+  var operation = "select"; // {'select', 'unselect', 'toggle'}
+  if (mousedown_img_arr_index === mouseup_img_arr_index) {
+    if (e.shiftKey) {
+      // select all elements until this element
+      start =
+        _via_image_grid_img_index_list.indexOf(last_mouseup_img_index) + 1;
+      end = mouseup_img_arr_index + 1;
+    } else {
+      // toggle selection of single image
+      start = mousedown_img_arr_index;
+      end = start + 1;
+      operation = "toggle";
+    }
+  } else {
+    if (mousedown_img_arr_index < mouseup_img_arr_index) {
+      start = mousedown_img_arr_index;
+      end = mouseup_img_arr_index + 1;
+    } else {
+      start = mouseup_img_arr_index + 1;
+      end = mousedown_img_arr_index;
+    }
+    operation = "toggle";
+  }
+
+  if (start > end) {
+    return;
+  }
+
+  var i, img_index;
+  for (i = start; i < end; ++i) {
+    img_index = _via_image_grid_img_index_list[i];
+    image_grid_update_img_select(img_index, operation);
+  }
+  image_grid_update_sel_count_html();
+  sidebar.annotation_editor_update_content();
+}
+
+function image_grid_update_sel_count_html() {
+  document.getElementById("image_grid_group_by_sel_img_count").innerHTML =
+    _via_image_grid_selected_img_index_list.length.toString(); //modsote
+}
+
+// state \in {'select', 'unselect', 'toggle'}
+function image_grid_update_img_select(img_index, state) {
+  var html_img_id = image_grid_get_html_img_id(img_index);
+  var is_selected =
+    _via_image_grid_selected_img_index_list.indexOf(img_index) !== -1;
+  if (state === "toggle") {
+    if (is_selected) {
+      state = "unselect";
+    } else {
+      state = "select";
+    }
+  }
+
+  switch (state) {
+    case "select":
+      if (!is_selected) {
+        _via_image_grid_selected_img_index_list.push(img_index);
+      }
+      if (_via_image_grid_visible_img_index_list.indexOf(img_index) !== -1) {
+        document.getElementById(html_img_id).classList.remove("not_sel");
+      }
+      break;
+    case "unselect":
+      if (is_selected) {
+        var arr_index =
+          _via_image_grid_selected_img_index_list.indexOf(img_index);
+        _via_image_grid_selected_img_index_list.splice(arr_index, 1);
+      }
+      if (_via_image_grid_visible_img_index_list.indexOf(img_index) !== -1) {
+        document.getElementById(html_img_id).classList.add("not_sel");
+      }
+      break;
+  }
+}
+
+function image_grid_group_select_all() {
+  image_grid_group_set_all_selection_state("select");
+  image_grid_update_sel_count_html();
+  sidebar.annotation_editor_update_content();
+  show_message("Selected all images in the current group");
+}
+
+function image_grid_group_select_none() {
+  image_grid_group_set_all_selection_state("unselect");
+  image_grid_update_sel_count_html();
+  sidebar.annotation_editor_update_content();
+  show_message("Removed selection of all images in the current group");
+}
+
+function image_grid_group_set_all_selection_state(state) {
+  var i, img_index;
+  for (i = 0; i < _via_image_grid_img_index_list.length; ++i) {
+    img_index = _via_image_grid_img_index_list[i];
+    image_grid_update_img_select(img_index, state);
+  }
+}
+
+function image_grid_group_toggle_select_all() {
+  if (
+    _via_image_grid_selected_img_index_list.length ===
+    _via_image_grid_img_index_list.length
+  ) {
+    image_grid_group_select_none();
+  } else {
+    image_grid_group_select_all();
+  }
+}
+
+function image_grid_parse_html_img_id(html_img_id) {
+  let img_index = html_img_id.substring(2);
+  return parseInt(img_index);
+}
+
+function image_grid_get_html_img_id(img_index) {
+  return "im" + img_index;
+}
+
+function image_grid_parse_html_region_id(html_region_id) {
+  var chunks = html_region_id.split("_");
+  if (chunks.length === 2) {
+    var img_index = parseInt(chunks[0].substring(2));
+    var region_id = parseInt(chunks[1].substring(2));
+    return { img_index: img_index, region_id: region_id };
+  } else {
+    console.log("image_grid_parse_html_region_id(): invalid html_region_id");
+    return {};
+  }
+}
+
+function image_grid_get_html_region_id(img_index, region_id) {
+  return image_grid_get_html_img_id(img_index) + "_rs" + region_id;
+}
+
+function image_grid_dblclick_handler(e) {
+  _via_image_index = image_grid_parse_html_img_id(e.target.id);
+  show_single_image_view();
+}
+
+function image_grid_toolbar_update_group_by_select() {
+  var p = document.getElementById("image_grid_toolbar_group_by_select");
+  p.innerHTML = "";
+
+  var o = document.createElement("option");
+  o.setAttribute("value", "");
+  o.setAttribute("selected", "selected");
+  o.innerHTML = "All Images";
+  p.appendChild(o);
+
+  // add file attributes
+  var fattr;
+  for (fattr in project.attributes.file) {
+    var o = document.createElement("option");
+    o.setAttribute(
+      "value",
+      image_grid_toolbar_group_by_select_get_html_id("file", fattr)
+    );
+    o.innerHTML = "[file] " + fattr;
+    p.appendChild(o);
+  }
+
+  // add region attributes
+  var rattr;
+  for (rattr in project.attributes.region) {
+    var o = document.createElement("option");
+    o.setAttribute(
+      "value",
+      image_grid_toolbar_group_by_select_get_html_id("region", rattr)
+    );
+    o.innerHTML = "[region] " + rattr;
+    p.appendChild(o);
+  }
+}
+
+function image_grid_toolbar_group_by_select_get_html_id(type, name) {
+  if (type === "file") {
+    return "f_" + name;
+  }
+  if (type === "region") {
+    return "r_" + name;
+  }
+}
+
+function image_grid_toolbar_group_by_select_parse_html_id(id) {
+  if (id.startsWith("f_")) {
+    return { attr_type: "file", attr_name: id.substring(2) };
+  }
+  if (id.startsWith("r_")) {
+    return { attr_type: "region", attr_name: id.substring(2) };
+  }
+}
+
+function image_grid_toolbar_onchange_group_by_select(p) {
+  if (p.options[p.selectedIndex].value === "") {
+    image_grid_show_all_project_images();
+    return;
+  }
+
+  var v = image_grid_toolbar_group_by_select_parse_html_id(
+    p.options[p.selectedIndex].value
+  );
+  var attr_type = v.attr_type;
+  var attr_name = v.attr_name;
+  image_grid_group_by(attr_type, attr_name);
+
+  image_grid_group_by_select_set_disabled(attr_type, attr_name, true);
+  p.blur(); // to avoid adding new groups using keyboard keys as dropdown is still in focus
+}
+
+function image_grid_remove_html_group_panel(d) {
+  var p = document.getElementById("group_toolbar_" + d.group_index);
+  document.getElementById("image_grid_group_panel").removeChild(p);
+  //if image_grid_group_panel is empty, hide it
+  if ($("#image_grid_group_panel").children().length === 0) {
+    $("#image_grid_group_panel_card").addClass("d-none");
+  }
+}
+
+function image_grid_add_html_group_panel(d) {
+  //create div elment with jquery
+  let panel = $(
+    '<div class="input-group input-group-sm" id="group_toolbar_' +
+    d.group_index +
+    '"></div>'
+  );
+  let del = $(
+    '<button class="btn btn-outline-secondary" onclick="image_grid_remove_group_by(this)"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">\n' +
+    '  <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>\n' +
+    "</svg></button>"
+  );
+  panel.append(del);
+
+  let prev = $(
+    '<button class="btn btn-outline-secondary" value="' +
+    d.group_index +
+    '" onclick="image_grid_group_prev(this)"><</button>'
+  );
+  panel.append(prev);
+  let sel = $(
+    '<select class="form-select form-select-sm" id="' +
+    image_grid_group_select_get_html_id(d.group_index) +
+    '" onchange="image_grid_group_value_onchange(this)"></select>'
+  );
+  let i, value;
+
+  let n = d.values.length;
+  let current_value = d.values[d.current_value_index];
+
+  for (i = 0; i < n; ++i) {
+    value = d.values[i];
+    let o = $(
+      '<option value="' +
+      value +
+      '">' +
+      (i + 1) +
+      "/" +
+      n +
+      ": " +
+      d.name +
+      " = " +
+      value +
+      "</option>"
+    );
+    if (value === current_value) {
+      o.attr("selected", "selected");
+    }
+    sel.append(o);
+  }
+  panel.append(sel);
+
+  let next = $(
+    '<button class="btn btn-outline-secondary" value="' +
+    d.group_index +
+    '" onclick="image_grid_group_next(this)">></button>'
+  );
+  panel.append(next);
+
+  //add panel to image_grid_group_panel
+  $("#image_grid_group_panel").append(panel);
+  $("#image_grid_group_panel_card").removeClass("d-none");
+}
+
+function image_grid_group_panel_set_selected_value(group_index) {
+  var sel = document.getElementById(
+    image_grid_group_select_get_html_id(group_index)
+  );
+  sel.selectedIndex =
+    _via_image_grid_group_var[group_index].current_value_index;
+}
+
+function image_grid_group_panel_set_options(group_index) {
+  var sel = document.getElementById(
+    image_grid_group_select_get_html_id(group_index)
+  );
+  sel.innerHTML = "";
+
+  var i, value;
+  if (_via_image_grid_group_var[group_index] === undefined) {
+    return;
+  }
+  var n = _via_image_grid_group_var[group_index].values.length;
+  var name = _via_image_grid_group_var[group_index].name;
+  var current_value =
+    _via_image_grid_group_var[group_index].values[
+    _via_image_grid_group_var[group_index].current_value_index
+    ];
+  for (i = 0; i < n; ++i) {
+    value = _via_image_grid_group_var[group_index].values[i];
+    var o = document.createElement("option");
+    o.setAttribute("value", value);
+    o.innerHTML = i + 1 + "/" + n + ": " + name + " = " + value;
+    if (value === current_value) {
+      o.setAttribute("selected", "selected");
+    }
+    sel.appendChild(o);
+  }
+}
+
+function image_grid_group_select_get_html_id(group_index) {
+  return "gi_" + group_index;
+}
+
+function image_grid_group_select_parse_html_id(id) {
+  return parseInt(id.substring(3));
+}
+
+function image_grid_group_by_select_set_disabled(type, name, is_disabled) {
+  var p = document.getElementById("image_grid_toolbar_group_by_select");
+  var sel_option_value = image_grid_toolbar_group_by_select_get_html_id(
+    type,
+    name
+  );
+
+  var n = p.options.length;
+  var option_value;
+  var i;
+  for (i = 0; i < n; ++i) {
+    if (sel_option_value === p.options[i].value) {
+      if (is_disabled) {
+        p.options[i].setAttribute("disabled", "disabled");
+      } else {
+        p.options[i].removeAttribute("disabled");
+      }
+      break;
+    }
+  }
+}
+
+function image_grid_remove_group_by(p) {
+  var prefix = "group_toolbar_";
+  var group_index = parseInt(p.parentNode.id.substring(prefix.length));
+
+  if (group_index === 0) {
+    image_grid_show_all_project_images();
+  } else {
+    // merge all groups that are child of group_index
+    image_grid_group_by_merge(_via_image_grid_group, 0, group_index);
+
+    var n = _via_image_grid_group_var.length;
+    var p = document.getElementById("image_grid_group_panel");
+    var group_panel_id;
+    var i;
+    for (i = group_index; i < n; ++i) {
+      image_grid_remove_html_group_panel(_via_image_grid_group_var[i]);
+      image_grid_group_by_select_set_disabled(
+        _via_image_grid_group_var[i].type,
+        _via_image_grid_group_var[i].name,
+        false
+      );
+    }
+    _via_image_grid_group_var.splice(group_index);
+
+    image_grid_set_content_to_current_group();
+  }
+}
+
+function image_grid_group_by(type, name) {
+  if (Object.keys(_via_image_grid_group).length === 0) {
+    // first group
+    var img_index_array = [];
+    var n = _via_img_fn_list_img_index_list.length;
+    var i;
+    for (i = 0; i < n; ++i) {
+      img_index_array.push(_via_img_fn_list_img_index_list[i]);
+    }
+
+    _via_image_grid_group = image_grid_split_array_to_group(
+      img_index_array,
+      type,
+      name
+    );
+    var new_group_values = Object.keys(_via_image_grid_group);
+    _via_image_grid_group_var = [];
+    _via_image_grid_group_var.push({
+      type: type,
+      name: name,
+      current_value_index: 0,
+      values: new_group_values,
+      group_index: 0,
+    });
+
+    image_grid_add_html_group_panel(_via_image_grid_group_var[0]);
+  } else {
+    image_grid_group_split_all_arrays(_via_image_grid_group, type, name);
+
+    var i, n, value;
+    var current_group_value = _via_image_grid_group;
+    n = _via_image_grid_group_var.length;
+
+    for (i = 0; i < n; ++i) {
+      value =
+        _via_image_grid_group_var[i].values[
+        _via_image_grid_group_var[i].current_value_index
+        ];
+      current_group_value = current_group_value[value];
+    }
+    var new_group_values = Object.keys(current_group_value);
+    var group_var_index = _via_image_grid_group_var.length;
+    _via_image_grid_group_var.push({
+      type: type,
+      name: name,
+      current_value_index: 0,
+      values: new_group_values,
+      group_index: group_var_index,
+    });
+    image_grid_add_html_group_panel(_via_image_grid_group_var[group_var_index]);
+  }
+
+  image_grid_set_content_to_current_group();
+}
+
+function image_grid_group_by_merge(group, current_level, target_level) {
+  var child_value;
+  var group_data = [];
+  if (current_level === target_level) {
+    return image_grid_group_by_collapse(group);
+  } else {
+    for (child_value in group) {
+      group[child_value] = image_grid_group_by_merge(
+        group[child_value],
+        current_level + 1,
+        target_level
+      );
+    }
+  }
+}
+
+function image_grid_group_by_collapse(group) {
+  var child_value;
+  var child_collapsed_value;
+  var group_data = [];
+  for (child_value in group) {
+    if (Array.isArray(group[child_value])) {
+      group_data = group_data.concat(group[child_value]);
+    } else {
+      group_data = group_data.concat(
+        image_grid_group_by_collapse(group[child_value])
+      );
+    }
+  }
+  return group_data;
+}
+
+// recursively collapse all arrays to list
+function image_grid_group_split_all_arrays(group, type, name) {
+  if (Array.isArray(group)) {
+    return image_grid_split_array_to_group(group, type, name);
+  } else {
+    var group_value;
+    for (group_value in group) {
+      if (Array.isArray(group[group_value])) {
+        group[group_value] = image_grid_split_array_to_group(
+          group[group_value],
+          type,
+          name
+        );
+      } else {
+        image_grid_group_split_all_arrays(group[group_value], type, name);
+      }
+    }
+  }
+}
+
+function image_grid_split_array_to_group(
+  img_index_array,
+  attr_type,
+  attr_name
+) {
+  var grp = {};
+  var img_index, img_id, i;
+  var n = img_index_array.length;
+  var attr_value;
+
+  switch (attr_type) {
+    case "file":
+      for (i = 0; i < n; ++i) {
+        img_index = img_index_array[i];
+        img_id = _via_image_id_list[img_index];
+        if (
+          _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_name)
+        ) {
+          attr_value = _via_img_metadata[img_id].file_attributes[attr_name];
+
+          if (!grp.hasOwnProperty(attr_value)) {
+            grp[attr_value] = [];
+          }
+          grp[attr_value].push(img_index);
+        }
+      }
+      break;
+    case "region":
+      var j;
+      var region_count;
+      for (i = 0; i < n; ++i) {
+        img_index = img_index_array[i];
+        img_id = _via_image_id_list[img_index];
+        region_count = _via_img_metadata[img_id].regions.length;
+        for (j = 0; j < region_count; ++j) {
+          if (
+            _via_img_metadata[img_id].regions[
+              j
+            ].region_attributes.hasOwnProperty(attr_name)
+          ) {
+            attr_value =
+              _via_img_metadata[img_id].regions[j].region_attributes[attr_name];
+
+            if (!grp.hasOwnProperty(attr_value)) {
+              grp[attr_value] = [];
+            }
+            if (grp[attr_value].includes(img_index)) {
+            } else {
+              grp[attr_value].push(img_index);
+            }
+          }
+        }
+      }
+      break;
+  }
+  return grp;
+}
+
+function image_grid_group_next(p) {
+  var group_index = parseInt(p.value);
+  if (_via_image_grid_group_var[group_index] === undefined) {
+    return;
+  }
+  var group_value_list = _via_image_grid_group_var[group_index].values;
+  var n = group_value_list.length;
+  var current_index =
+    _via_image_grid_group_var[group_index].current_value_index;
+  var next_index = current_index + 1;
+  if (next_index >= n) {
+    if (group_index === 0) {
+      next_index = next_index - n;
+      image_grid_jump_to_group(group_index, next_index);
+    } else {
+      // next of parent group
+      var parent_group_index = group_index - 1;
+      var parent_current_val_index =
+        _via_image_grid_group_var[parent_group_index].current_value_index;
+      var parent_next_val_index = parent_current_val_index + 1;
+      while (parent_group_index !== 0) {
+        if (
+          parent_next_val_index >=
+          _via_image_grid_group_var[parent_group_index].values.length
+        ) {
+          parent_group_index = group_index - 1;
+          parent_current_val_index =
+            _via_image_grid_group_var[parent_group_index].current_value_index;
+          parent_next_val_index = parent_current_val_index + 1;
+        } else {
+          break;
+        }
+      }
+
+      if (
+        parent_next_val_index >=
+        _via_image_grid_group_var[parent_group_index].values.length
+      ) {
+        parent_next_val_index = 0;
+      }
+      image_grid_jump_to_group(parent_group_index, parent_next_val_index);
+    }
+  } else {
+    image_grid_jump_to_group(group_index, next_index);
+  }
+  image_grid_set_content_to_current_group();
+}
+
+function image_grid_group_prev(p) {
+  var group_index = parseInt(p.value);
+  var group_value_list = _via_image_grid_group_var[group_index].values;
+  var n = group_value_list.length;
+  var current_index =
+    _via_image_grid_group_var[group_index].current_value_index;
+  var prev_index = current_index - 1;
+  if (prev_index < 0) {
+    if (group_index === 0) {
+      prev_index = n + prev_index;
+      image_grid_jump_to_group(group_index, prev_index);
+    } else {
+      // prev of parent group
+      var parent_group_index = group_index - 1;
+      var parent_current_val_index =
+        _via_image_grid_group_var[parent_group_index].current_value_index;
+      var parent_prev_val_index = parent_current_val_index - 1;
+      while (parent_group_index !== 0) {
+        if (parent_prev_val_index < 0) {
+          parent_group_index = group_index - 1;
+          parent_current_val_index =
+            _via_image_grid_group_var[parent_group_index].current_value_index;
+          parent_prev_val_index = parent_current_val_index - 1;
+        } else {
+          break;
+        }
+      }
+
+      if (parent_prev_val_index < 0) {
+        parent_prev_val_index =
+          _via_image_grid_group_var[parent_group_index].values.length - 1;
+      }
+      image_grid_jump_to_group(parent_group_index, parent_prev_val_index);
+    }
+  } else {
+    image_grid_jump_to_group(group_index, prev_index);
+  }
+  image_grid_set_content_to_current_group();
+}
+
+function image_grid_group_value_onchange(p) {
+  var group_index = image_grid_group_select_parse_html_id(p.id);
+  image_grid_jump_to_group(group_index, p.selectedIndex);
+  image_grid_set_content_to_current_group();
+}
+
+function image_grid_jump_to_group(group_index, value_index) {
+  var n = _via_image_grid_group_var[group_index].values.length;
+  if (value_index >= n || value_index < 0) {
+    return;
+  }
+
+  _via_image_grid_group_var[group_index].current_value_index = value_index;
+  image_grid_group_panel_set_selected_value(group_index);
+
+  // reset the value of lower groups
+  var i, value;
+  if (group_index + 1 < _via_image_grid_group_var.length) {
+    var e = _via_image_grid_group;
+    for (i = 0; i <= group_index; ++i) {
+      value =
+        _via_image_grid_group_var[i].values[
+        _via_image_grid_group_var[i].current_value_index
+        ];
+      e = e[value];
+    }
+
+    for (i = group_index + 1; i < _via_image_grid_group_var.length; ++i) {
+      _via_image_grid_group_var[i].values = Object.keys(e);
+      if (_via_image_grid_group_var[i].values.length === 0) {
+        _via_image_grid_group_var[i].current_value_index = -1;
+        _via_image_grid_group_var.splice(i);
+        image_grid_group_panel_set_options(i);
+        break;
+      } else {
+        _via_image_grid_group_var[i].current_value_index = 0;
+        value = _via_image_grid_group_var[i].values[0];
+        e = e[value];
+        image_grid_group_panel_set_options(i);
+      }
+    }
+  }
+}
+
+function image_grid_set_content_to_current_group() {
+  var n = _via_image_grid_group_var.length;
+
+  if (n === 0) {
+    image_grid_show_all_project_images();
+  } else {
+    var group_img_index_list = [];
+    var img_index_list = _via_image_grid_group;
+    var i, n, value, current_value_index;
+    for (i = 0; i < n; ++i) {
+      value =
+        _via_image_grid_group_var[i].values[
+        _via_image_grid_group_var[i].current_value_index
+        ];
+      img_index_list = img_index_list[value];
+    }
+
+    if (Array.isArray(img_index_list)) {
+      image_grid_set_content(img_index_list);
+    } else {
+      console.log(
+        "Error: image_grid_set_content_to_current_group(): expected array while got " +
+        typeof img_index_list
+      );
+    }
+  }
+}
+
+function image_grid_page_next() {
+  _via_image_grid_stack_prev_page.push(_via_image_grid_page_first_index);
+  _via_image_grid_page_first_index = _via_image_grid_page_last_index;
+
+  image_grid_clear_content();
+  _via_image_grid_load_ongoing = true;
+  image_grid_page_nav_show_cancel();
+  image_grid_content_append_img(_via_image_grid_page_first_index);
+}
+
+function image_grid_page_prev() {
+  _via_image_grid_page_first_index = _via_image_grid_stack_prev_page.pop();
+  _via_image_grid_page_last_index = -1;
+
+  image_grid_clear_content();
+  _via_image_grid_load_ongoing = true;
+  image_grid_page_nav_show_cancel();
+  image_grid_content_append_img(_via_image_grid_page_first_index);
+}
+
+function image_grid_page_nav_show_cancel() {
+  var info = document.getElementById("image_grid_nav");
+  var html = [];
+  html.push("<span>Loading images ... </span>");
+  html.push(
+    '<span class="text_button" onclick="image_grid_cancel_load_ongoing()">Cancel</span>'
+  );
+  info.innerHTML = html.join("");
+}
+
+function image_grid_cancel_load_ongoing() {
+  _via_image_grid_load_ongoing = false;
+}
+
+// everything to do with image zooming
+
+//
+// hooks for sub-modules
+// implemented by sub-modules
+//
+//function _via_hook_next_image() {}
+//function _via_hook_prev_image() {}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Code borrowed from via2 branch
+// - in future, the <canvas> based reigon shape drawing will be replaced by <svg>
+//   because svg allows independent manipulation of individual regions without
+//   requiring to clear the canvas every time some region is updated.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// @file        _via_region.src
+// @description Implementation of region shapes like rectangle, circle, etc.
+// @author      Abhishek Dutta <adutta@robots.ox.ac.uk>
+// @date        17 June 2017
+//
+////////////////////////////////////////////////////////////////////////////////
+
+function _via_region(
+  shape,
+  id,
+  data_img_space,
+  view_scale_factor,
+  view_offset_x,
+  view_offset_y
+) {
+  // Note the following terminology:
+  //   view space  :
+  //     - corresponds to the x-y plane on which the scaled version of original image is shown to the user
+  //     - all the region query operations like is_inside(), is_on_edge(), etc are performed in view space
+  //     - all svg draw operations like get_svg() are also in view space
+  //
+  //   image space :
+  //     - corresponds to the x-y plane which corresponds to the spatial space of the original image
+  //     - region save, export, git push operations are performed in image space
+  //     - to avoid any rounding issues (caused by floating scale factor),
+  //        * user drawn regions in view space is first converted to image space
+  //        * this region in image space is now used to initialize region in view space
+  //
+  //   The two spaces are related by _via_model.now.tform.scale which is computed by the method
+  //     _via_ctrl.compute_view_panel_to_nowfile_tform()
+  //   and applied as follows:
+  //     x coordinate in image space = scale_factor * x coordinate in view space
+  //
+  // shape : {rect, circle, ellipse, line, polyline, polygon, point}
+  // id    : unique region-id
+  // d[]   : (in view space) data whose meaning depend on region shape as follows:
+  //        rect     : d[x1,y1,x2,y2] or d[corner1_x, corner1_y, corner2_x, corner2_y]
+  //        circle   : d[x1,y1,x2,y2] or d[center_x, center_y, circumference_x, circumference_y]
+  //        ellipse  : d[x1,y1,x2,y2,transform]
+  //        line     : d[x1,y1,x2,y2]
+  //        polyline : d[x1,y1,...,xn,yn]
+  //        polygon  : d[x1,y1,...,xn,yn]
+  //        point    : d[cx,cy]
+  // scale_factor : for conversion from view space to image space
+  //
+  // Note: no svg data are stored with prefix "_". For example: _scale_factor, _x2
+  this.shape = shape;
+  this.id = id;
+  this.scale_factor = view_scale_factor;
+  this.offset_x = view_offset_x;
+  this.offset_y = view_offset_y;
+  this.recompute_svg = false;
+  this.attributes = {};
+
+  var n = data_img_space.length;
+  var i;
+  this.dview = new Array(n);
+  this.dimg = new Array(n);
+
+  if (n !== 0) {
+    // IMPORTANT:
+    // to avoid any rounding issues (caused by floating scale factor), we stick to
+    // the principal that image space coordinates are the ground truth for every region.
+    // Hence, we proceed as:
+    //   * user drawn regions in view space is first converted to image space
+    //   * this region in image space is now used to initialize region in view space
+    for (i = 0; i < n; i++) {
+      this.dimg[i] = data_img_space[i];
+
+      var offset = this.offset_x;
+      if (i % 2 !== 0) {
+        // y coordinate
+        offset = this.offset_y;
+      }
+      this.dview[i] = Math.round(this.dimg[i] * this.scale_factor) + offset;
+    }
+  }
+
+  // set svg attributes for each shape
+  switch (this.shape) {
+    case "rect":
+      _via_region_rect.call(this);
+      this.svg_attributes = ["x", "y", "width", "height"];
+      break;
+    case "circle":
+      _via_region_circle.call(this);
+      this.svg_attributes = ["cx", "cy", "r"];
+      break;
+    case "ellipse":
+      _via_region_ellipse.call(this);
+      this.svg_attributes = ["cx", "cy", "rx", "ry", "transform"];
+      break;
+    case "line":
+      _via_region_line.call(this);
+      this.svg_attributes = ["x1", "y1", "x2", "y2"];
+      break;
+    case "polyline":
+      _via_region_polyline.call(this);
+      this.svg_attributes = ["points"];
+      break;
+    case "polygon":
+      _via_region_polygon.call(this);
+      this.svg_attributes = ["points"];
+      break;
+    case "point":
+      _via_region_point.call(this);
+      // point is a special circle with minimal radius required for visualization
+      this.shape = "circle";
+      this.svg_attributes = ["cx", "cy", "r"];
+      break;
+  }
+
+  this.initialize();
+}
+
+_via_region.prototype.prepare_svg_element = function () {
+  var _VIA_SVG_NS = "http://www.w3.org/2000/svg";
+  this.svg_element = document.createElementNS(_VIA_SVG_NS, this.shape);
+  this.svg_string = "<" + this.shape;
+  this.svg_element.setAttributeNS(null, "id", this.id);
+
+  var n = this.svg_attributes.length;
+  for (var i = 0; i < n; i++) {
+    this.svg_element.setAttributeNS(
+      null,
+      this.svg_attributes[i],
+      this[this.svg_attributes[i]]
+    );
+    this.svg_string +=
+      " " + this.svg_attributes[i] + '="' + this[this.svg_attributes[i]] + '"';
+  }
+  this.svg_string += "/>";
+};
+
+_via_region.prototype.get_svg_element = function () {
+  if (this.recompute_svg) {
+    this.prepare_svg_element();
+    this.recompute_svg = false;
+  }
+  return this.svg_element;
+};
+
+_via_region.prototype.get_svg_string = function () {
+  if (this.recompute_svg) {
+    this.prepare_svg_element();
+    this.recompute_svg = false;
+  }
+  return this.svg_string;
+};
+
+///
+/// Region shape : rectangle
+///
+function _via_region_rect() {
+  this.is_inside = _via_region_rect.prototype.is_inside;
+  this.is_on_edge = _via_region_rect.prototype.is_on_edge;
+  this.move = _via_region_rect.prototype.move;
+  this.resize = _via_region_rect.prototype.resize;
+  this.initialize = _via_region_rect.prototype.initialize;
+  this.dist_to_nearest_edge = _via_region_rect.prototype.dist_to_nearest_edge;
+}
+
+_via_region_rect.prototype.initialize = function () {
+  // ensure that this.(x,y) corresponds to top-left corner of rectangle
+  // Note: this.(x2,y2) is defined for convenience in calculations
+  if (this.dview[0] < this.dview[2]) {
+    this.x = this.dview[0];
+    this.x2 = this.dview[2];
+  } else {
+    this.x = this.dview[2];
+    this.x2 = this.dview[0];
+  }
+  if (this.dview[1] < this.dview[3]) {
+    this.y = this.dview[1];
+    this.y2 = this.dview[3];
+  } else {
+    this.y = this.dview[3];
+    this.y2 = this.dview[1];
+  }
+  this.width = this.x2 - this.x;
+  this.height = this.y2 - this.y;
+  this.recompute_svg = true;
+};
+
+///
+/// Region shape : circle
+///
+function _via_region_circle() {
+  this.is_inside = _via_region_circle.prototype.is_inside;
+  this.is_on_edge = _via_region_circle.prototype.is_on_edge;
+  this.move = _via_region_circle.prototype.move;
+  this.resize = _via_region_circle.prototype.resize;
+  this.initialize = _via_region_circle.prototype.initialize;
+  this.dist_to_nearest_edge = _via_region_circle.prototype.dist_to_nearest_edge;
+}
+
+_via_region_circle.prototype.initialize = function () {
+  this.cx = this.dview[0];
+  this.cy = this.dview[1];
+  var dx = this.dview[2] - this.dview[0];
+  var dy = this.dview[3] - this.dview[1];
+  this.r = Math.round(Math.sqrt(dx * dx + dy * dy));
+  this.r2 = this.r * this.r;
+  this.recompute_svg = true;
+};
+
+///
+/// Region shape : ellipse
+///
+function _via_region_ellipse() {
+  this.is_inside = _via_region_ellipse.prototype.is_inside;
+  this.is_on_edge = _via_region_ellipse.prototype.is_on_edge;
+  this.move = _via_region_ellipse.prototype.move;
+  this.resize = _via_region_ellipse.prototype.resize;
+  this.initialize = _via_region_ellipse.prototype.initialize;
+  this.dist_to_nearest_edge =
+    _via_region_ellipse.prototype.dist_to_nearest_edge;
+}
+
+_via_region_ellipse.prototype.initialize = function () {
+  this.cx = this.dview[0];
+  this.cy = this.dview[1];
+  this.rx = Math.abs(this.dview[2] - this.dview[0]);
+  this.ry = Math.abs(this.dview[3] - this.dview[1]);
+
+  this.inv_rx2 = 1 / (this.rx * this.rx);
+  this.inv_ry2 = 1 / (this.ry * this.ry);
+
+  this.recompute_svg = true;
+};
+
+///
+/// Region shape : line
+///
+function _via_region_line() {
+  this.is_inside = _via_region_line.prototype.is_inside;
+  this.is_on_edge = _via_region_line.prototype.is_on_edge;
+  this.move = _via_region_line.prototype.move;
+  this.resize = _via_region_line.prototype.resize;
+  this.initialize = _via_region_line.prototype.initialize;
+  this.dist_to_nearest_edge = _via_region_line.prototype.dist_to_nearest_edge;
+}
+
+_via_region_line.prototype.initialize = function () {
+  this.x1 = this.dview[0];
+  this.y1 = this.dview[1];
+  this.x2 = this.dview[2];
+  this.y2 = this.dview[3];
+  this.dx = this.x1 - this.x2;
+  this.dy = this.y1 - this.y2;
+  this.mconst = this.x1 * this.y2 - this.x2 * this.y1;
+
+  this.recompute_svg = true;
+};
+
+///
+/// Region shape : polyline
+///
+function _via_region_polyline() {
+  this.is_inside = _via_region_polyline.prototype.is_inside;
+  this.is_on_edge = _via_region_polyline.prototype.is_on_edge;
+  this.move = _via_region_polyline.prototype.move;
+  this.resize = _via_region_polyline.prototype.resize;
+  this.initialize = _via_region_polyline.prototype.initialize;
+  this.dist_to_nearest_edge =
+    _via_region_polyline.prototype.dist_to_nearest_edge;
+}
+
+_via_region_polyline.prototype.initialize = function () {
+  var n = this.dview.length;
+  var points = new Array(n / 2);
+  var points_index = 0;
+  for (var i = 0; i < n; i += 2) {
+    points[points_index] = this.dview[i] + " " + this.dview[i + 1];
+    points_index++;
+  }
+  this.points = points.join(",");
+  this.recompute_svg = true;
+};
+
+///
+/// Region shape : polygon
+///
+function _via_region_polygon() {
+  this.is_inside = _via_region_polygon.prototype.is_inside;
+  this.is_on_edge = _via_region_polygon.prototype.is_on_edge;
+  this.move = _via_region_polygon.prototype.move;
+  this.resize = _via_region_polygon.prototype.resize;
+  this.initialize = _via_region_polygon.prototype.initialize;
+  this.dist_to_nearest_edge =
+    _via_region_polygon.prototype.dist_to_nearest_edge;
+}
+
+_via_region_polygon.prototype.initialize = function () {
+  var n = this.dview.length;
+  var points = new Array(n / 2);
+  var points_index = 0;
+  for (var i = 0; i < n; i += 2) {
+    points[points_index] = this.dview[i] + " " + this.dview[i + 1];
+    points_index++;
+  }
+  this.points = points.join(",");
+  this.recompute_svg = true;
+};
+
+///
+/// Region shape : point
+///
+function _via_region_point() {
+  this.is_inside = _via_region_point.prototype.is_inside;
+  this.is_on_edge = _via_region_point.prototype.is_on_edge;
+  this.move = _via_region_point.prototype.move;
+  this.resize = _via_region_point.prototype.resize;
+  this.initialize = _via_region_point.prototype.initialize;
+  this.dist_to_nearest_edge = _via_region_point.prototype.dist_to_nearest_edge;
+}
+
+_via_region_point.prototype.initialize = function () {
+  this.cx = this.dview[0];
+  this.cy = this.dview[1];
+  this.r = 2;
+  this.r2 = this.r * this.r;
+  this.recompute_svg = true;
+};
+
+//
+// find location of file
+//
+
+function _via_file_resolve_all_to_default_filepath() {
+  var img_id;
+  for (img_id in _via_img_metadata) {
+    if (_via_img_metadata.hasOwnProperty(img_id)) {
+      _via_file_resolve_file_to_default_filepath(img_id);
+    }
+  }
+}
+
+function _via_file_resolve_file_to_default_filepath(img_id) {
+  if (_via_img_metadata.hasOwnProperty(img_id)) {
+    if (
+      typeof _via_img_fileref[img_id] === "undefined" ||
+      !_via_img_fileref[img_id] instanceof File
+    ) {
+      if (is_url(_via_img_metadata[img_id].filename)) {
+        _via_img_src[img_id] = _via_img_metadata[img_id].filename;
+      } else {
+        let file_path = "file:/" + settings.defaultPath + _via_img_metadata[img_id].filename;
+        _via_img_src[img_id] = file_path
+
+      }
+    }
+  }
+}
+
+function _via_file_resolve_all() {
+  return new Promise(function (ok_callback, err_callback) {
+    var all_promises = [];
+
+    var search_path_list = _via_file_get_search_path_list();
+    var i, img_id;
+    for (i = 0; i < _via_img_count; ++i) {
+      img_id = _via_image_id_list[i];
+      if (
+        typeof _via_img_src[img_id] === "undefined" ||
+        _via_img_src[img_id] === ""
+      ) {
+        var p = _via_file_resolve(i, search_path_list);
+        all_promises.push(p);
+      }
+    }
+
+    Promise.all(all_promises).then(
+      function (ok_file_index_list) {
+        console.log(ok_file_index_list);
+        ok_callback();
+        //project_file_load_on_success(ok_file_index);
+      },
+      function (err_file_index_list) {
+        console.log(err_file_index_list);
+        err_callback();
+        //project_file_load_on_fail(err_file_index);
+      }
+    );
+  });
+}
+
+function _via_file_get_search_path_list() {
+  var search_path_list = [];
+  var path;
+  for (path in _via_settings.core.filepath) {
+    if (_via_settings.core.filepath[path] !== 0) {
+      search_path_list.push(path);
+    }
+  }
+  return search_path_list;
+}
+
+function _via_file_resolve(file_index, search_path_list) {
+  return new Promise(function (ok_callback, err_callback) {
+    var path_index = 0;
+    var p = _via_file_resolve_check_path(
+      file_index,
+      path_index,
+      search_path_list
+    ).then(
+      function (ok) {
+        ok_callback(ok);
+      },
+      function (err) {
+        err_callback(err);
+      }
+    );
+  }, false);
+}
+
+function _via_file_resolve_check_path(
+  file_index,
+  path_index,
+  search_path_list
+) {
+  return new Promise(function (ok_callback, err_callback) {
+    var img_id = _via_image_id_list[file_index];
+    var img = new Image(0, 0);
+
+    var img_path =
+      search_path_list[path_index] + _via_img_metadata[img_id].filename;
+    if (is_url(_via_img_metadata[img_id].filename)) {
+      if (search_path_list[path_index] !== "") {
+        // we search for the the image filename pointed by URL in local search paths
+        img_path =
+          search_path_list[path_index] +
+          get_filename_from_url(_via_img_metadata[img_id].filename);
+      }
+    }
+
+    img.setAttribute("src", img_path);
+
+    img.addEventListener(
+      "load",
+      function () {
+        _via_img_src[img_id] = img_path;
+        ok_callback(file_index);
+      },
+      false
+    );
+    img.addEventListener("abort", function () {
+      err_callback(file_index);
+    });
+    img.addEventListener(
+      "error",
+      function () {
+        var new_path_index = path_index + 1;
+        if (new_path_index < search_path_list.length) {
+          _via_file_resolve_check_path(
+            file_index,
+            new_path_index,
+            search_path_list
+          ).then(
+            function (ok) {
+              ok_callback(file_index);
+            },
+            function (err) {
+              err_callback(file_index);
+            }
+          );
+        } else {
+          err_callback(file_index);
+        }
+      },
+      false
+    );
+  }, false);
+}
+
+//
+// page 404 (file not found)
+//
+function show_page_404(img_index) {
+  $("#loading").addClass("d-none");
+  buffer.hideCurrentImage();
+
+  set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_404);
+  $(`#selection_panel`).hide();
+
+  _via_image_index = img_index;
+  _via_image_id = _via_image_id_list[_via_image_index];
+  buffer.imgLoaded = false;
+  sidebar.img_fn_list_ith_entry_selected(_via_image_index, true);
+
+  document.getElementById("page_404_filename").innerHTML =
+    "[" +
+    (_via_image_index + 1) +
+    "]" +
+    _via_img_metadata[_via_image_id].filename;
+}
+
+//
+// utils
+//
+
+function is_url(s) {
+  // @todo: ensure that this is sufficient to capture all image url
+  return !!(
+    s.startsWith("http://") ||
+    s.startsWith("https://") ||
+    s.startsWith("www.")
+  );
+}
+
+function get_filename_from_url(url) {
+  return url.substring(url.lastIndexOf("/") + 1);
+}
+
+function fixfloat(x) {
+  return parseFloat(x.toFixed(VIA_FLOAT_PRECISION));
+}
+
+function shape_attribute_fixfloat(sa) {
+  for (var attr in sa) {
+    switch (attr) {
+      case "x":
+      case "y":
+      case "width":
+      case "height":
+      case "r":
+      case "rx":
+      case "ry":
+        sa[attr] = fixfloat(sa[attr]);
+        break;
+      case "all_points_x":
+      case "all_points_y":
+        for (var i in sa[attr]) {
+          sa[attr][i] = fixfloat(sa[attr][i]);
+        }
+    }
+  }
+}
+
+// start with the array having smallest number of elements
+// check the remaining arrays if they all contain the elements of this shortest array
+function array_intersect(array_list) {
+  if (array_list.length === 0) {
+    return [];
+  }
+  if (array_list.length === 1) {
+    return array_list[0];
+  }
+
+  var shortest_array = array_list[0];
+  var shortest_array_index = 0;
+  var i;
+  for (i = 1; i < array_list.length; ++i) {
+    if (array_list[i].length < shortest_array.length) {
+      shortest_array = array_list[i];
+      shortest_array_index = i;
+    }
+  }
+
+  var intersect = [];
+  var element_count = {};
+
+  var array_index_i;
+  for (i = 0; i < array_list.length; ++i) {
+    if (i === 0) {
+      // in the first iteration, process the shortest element array
+      array_index_i = shortest_array_index;
+    } else {
+      array_index_i = i;
+    }
+
+    var j;
+    for (j = 0; j < array_list[array_index_i].length; ++j) {
+      if (element_count[array_list[array_index_i][j]] === i - 1) {
+        if (i === array_list.length - 1) {
+          intersect.push(array_list[array_index_i][j]);
+          element_count[array_list[array_index_i][j]] = 0;
+        } else {
+          element_count[array_list[array_index_i][j]] = i;
+        }
+      } else {
+        element_count[array_list[array_index_i][j]] = 0;
+      }
+    }
+  }
+  return intersect;
+}
+
+function generate_img_index_list(input) {
+  var all_img_index_list = [];
+
+  // condition: count format a,b
+  var count_format_img_index_list = [];
+  if (input.prev_next_count.value !== "") {
+    var prev_next_split = input.prev_next_count.value.split(",");
+    if (prev_next_split.length === 2) {
+      var prev = parseInt(prev_next_split[0]);
+      var next = parseInt(prev_next_split[1]);
+      var i;
+      for (i = _via_image_index - prev; i <= _via_image_index + next; i++) {
+        count_format_img_index_list.push(i);
+      }
+    }
+  }
+  if (count_format_img_index_list.length !== 0) {
+    all_img_index_list.push(count_format_img_index_list);
+  }
+
+  //condition: image index list expression
+  var expr_img_index_list = [];
+  if (input.img_index_list.value !== "") {
+    var img_index_expr = input.img_index_list.value.split(",");
+    if (img_index_expr.length !== 0) {
+      var i;
+      for (i = 0; i < img_index_expr.length; ++i) {
+        if (img_index_expr[i].includes("-")) {
+          var ab = img_index_expr[i].split("-");
+          var a = parseInt(ab[0]) - 1; // 0 based indexing
+          var b = parseInt(ab[1]) - 1;
+          var j;
+          for (j = a; j <= b; ++j) {
+            expr_img_index_list.push(j);
+          }
+        } else {
+          expr_img_index_list.push(parseInt(img_index_expr[i]) - 1);
+        }
+      }
+    }
+  }
+  if (expr_img_index_list.length !== 0) {
+    all_img_index_list.push(expr_img_index_list);
+  }
+
+  // condition: regular expression
+  var regex_img_index_list = [];
+  if (input.regex.value !== "") {
+    var regex = input.regex.value;
+    for (var i = 0; i < _via_image_filename_list.length; ++i) {
+      var filename = _via_image_filename_list[i];
+      if (filename.match(regex) !== null) {
+        regex_img_index_list.push(i);
+      }
+    }
+  }
+  if (regex_img_index_list.length !== 0) {
+    all_img_index_list.push(regex_img_index_list);
+  }
+
+  var intersect = array_intersect(all_img_index_list);
+  return intersect;
+}
+
+if (!_via_is_debug_mode) {
+  // warn user of possible loss of data
+  window.onbeforeunload = function (e) {
+    e = e || window.event;
+
+    // For IE and Firefox prior to version 4
+    if (e) {
+      e.returnValue = "Did you save your data?";
+    }
+
+    // For Safari
+    return "Did you save your data?";
+  };
+}
+
+//
+// keep a record of image statistics (e.g. width, height, ...)
+//
+function img_stat_set(img_index, stat) {
+  if (stat.length) {
+    _via_img_stat[img_index] = stat;
+  } else {
+    delete _via_img_stat[img_index];
+  }
+}
+
+function img_stat_set_all() {
+  return new Promise(
+    function (ok_callback, err_callback) {
+      var promise_list = [];
+      var img_id;
+      for (var img_index in _via_image_id_list) {
+        if (!_via_img_stat.hasOwnProperty(img_index)) {
+          img_id = _via_image_id_list[img_index];
+          if (
+            _via_img_metadata[img_id].file_attributes.hasOwnProperty("width") &&
+            _via_img_metadata[img_id].file_attributes.hasOwnProperty("height")
+          ) {
+            _via_img_stat[img_index] = [
+              _via_img_metadata[img_id].file_attributes["width"],
+              _via_img_metadata[img_id].file_attributes["height"],
+            ];
+          } else {
+            promise_list.push(img_stat_get(img_index));
+          }
+        }
+      }
+      if (promise_list.length) {
+        Promise.all(promise_list).then(
+          function (ok) {
+            ok_callback();
+          }.bind(this),
+          function (err) {
+            console.warn("Failed to read statistics of all images!");
+            err_callback();
+          }
+        );
+      } else {
+        ok_callback();
+      }
+    }.bind(this)
+  );
+}
+
+function img_stat_get(img_index) {
+  return new Promise(
+    function (ok_callback, err_callback) {
+      var img_id = _via_image_id_list[img_index];
+      var tmp_img = document.createElement("img");
+      var tmp_file_object_url = null;
+      tmp_img.addEventListener(
+        "load",
+        function () {
+          _via_img_stat[img_index] = [
+            tmp_img.naturalWidth,
+            tmp_img.naturalHeight,
+          ];
+          if (tmp_file_object_url !== null) {
+            URL.revokeObjectURL(tmp_file_object_url);
+          }
+          ok_callback();
+        }.bind(this)
+      );
+      tmp_img.addEventListener(
+        "error",
+        function () {
+          _via_img_stat[img_index] = [-1, -1];
+          if (tmp_file_object_url !== null) {
+            URL.revokeObjectURL(tmp_file_object_url);
+          }
+          ok_callback();
+        }.bind(this)
+      );
+
+      if (_via_img_fileref[img_id] instanceof File) {
+        tmp_file_object_url = URL.createObjectURL(_via_img_fileref[img_id]);
+        tmp_img.src = tmp_file_object_url;
+      } else {
+        tmp_img.src = _via_img_src[img_id];
+      }
+    }.bind(this)
+  );
+}
+
+// pts = [x0,y0,x1,y1,....]
+function polygon_to_bbox(pts) {
+  var xmin = +Infinity;
+  var xmax = -Infinity;
+  var ymin = +Infinity;
+  var ymax = -Infinity;
+  for (var i = 0; i < pts.length; i = i + 2) {
+    if (pts[i] > xmax) {
+      xmax = pts[i];
+    }
+    if (pts[i] < xmin) {
+      xmin = pts[i];
+    }
+    if (pts[i + 1] > ymax) {
+      ymax = pts[i + 1];
+    }
+    if (pts[i + 1] < ymin) {
+      ymin = pts[i + 1];
+    }
+  }
+  return [xmin, ymin, xmax - xmin, ymax - ymin];
+};// servercom.js
+// Description: This file contains the functions for the server communication.
+// Annotations are sent to the server and the server sends the annotations back.
+// The annotations are then added to the image metadata.
+// Annotate function and prototype for the annotation process
+function Annotate() {
+  this.canvas = document.createElement("canvas");
+  this.ctx = this.canvas.getContext("2d");
+  this.modal = $("#staticBackdropProgress");
+  this.progress = $("#annotationProgress");
+  this.progressNum = 0;
+  this.reAnnotateCommand = "";
+}
+
+const AutoAnnotator = new Annotate();
+
+Annotate.prototype.run_annotation = function () {
+  switch (_via_display_area_content_name) {
+      case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
+          this.modal.modal("show");
+          this.annotate_all_imgs();
+          break;
+
+      case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
+          this.modal.modal("show");
+          this.annotate_single_img().then(() => {
+              this.setProgressBar(100);
+              if (this.reAnnotateCommand === "cancel") {
+                  this.reAnnotateCommand = "";
+                  return;
+              }
+              $("#annotation_spinner").removeClass("d-none");
+              Message.show({
+                  address: "Success",
+                  body: "The image uploaded successfully.",
+              });
+          });
+          break;
+
+      default:
+          Message.show({
+              address: "Error",
+              body: "Please load an image first.",
+          });
+  }
+};
+
+Annotate.prototype.annotate_all_imgs = function () {
+  try {
+      this.progressNum = 10;
+      this.setProgressBar(this.progressNum);
+      this.sendImageAll().then(() => {
+          this.setProgressBar(100);
+          $("#annotation_spinner").removeClass("d-none");
+      });
+  } catch (e) {
+      this.progressNum = 0;
+      setTimeout(function () {
+          $("#staticBackdropProgress").modal("hide");
+      }, 1000);
+      this.progress.css("width", 0 + "%");
+      console.log(e);
+      Message.show({
+          address: "Error",
+          body: "Please check your network connection or load the image again.",
+      });
+  }
+};
+
+Annotate.prototype.annotate_single_img = async function () {
+  try {
+      this.progressNum = 10;
+      this.setProgressBar(this.progressNum);
+      let image_index = _via_image_index;
+      let img_id = _via_image_id_list[image_index];
+      const imageElement = $("#bim" + image_index)[0];
+      const imageBlob = await this.getImageBlobFromElement(imageElement);
+      await this.isAnnotated(img_id).then((res) => {
+      });
+      switch (this.reAnnotateCommand) {
+          case "reAnnotate":
+              _via_img_metadata[img_id].autoAnnotated = false;
+              break;
+          case "cancel":
+              this.setProgressBar(100);
+              Message.show({
+                  address: "Info",
+                  body: "The image was not annotated.",
+              });
+              return;
+          default:
+              break;
+      }
+      await this.sendImage(
+          imageBlob,
+          _via_img_metadata[_via_image_id_list[image_index]].filename
+      );
+  } catch (e) {
+      console.log(e);
+      this.progressNum = 0;
+      setTimeout(function () {
+          $("#staticBackdropProgress").modal("hide");
+      }, 1000);
+      this.progress.css("width", 0 + "%");
+
+      Message.show({
+          address: "Error",
+          body: "Please check your network connection or load the image again.",
+      });
+  }
+};
+
+Annotate.prototype.setProgressBar = function (num) {
+  this.progress.animate(
+      {
+          width: num + "%",
+      },
+      100
+  );
+  if (num === 100) {
+      this.progress.css("width", 100 + "%");
+      setTimeout(
+          function () {
+              this.modal.modal("hide");
+              this.progress.css("width", 0 + "%");
+          }.bind(this),
+          1000
+      );
+  }
+};
+
+Annotate.prototype.setModal = function (title, body, footer) {
+  $("#staticBackdropModal h1").html(title);
+  $("#modalBody").html(body);
+  $("#modalFooter").html(footer);
+  setTimeout(function () {
+      $("#staticBackdropModal").modal("show");
+  }, 1000);
+};
+
+Annotate.prototype.clearModal = function () {
+  $("#staticBackdropModal").modal("hide");
+  $("#staticBackdropModal h1").html("");
+  $("#modalBody").html("");
+  $("#modalFooter").html("");
+};
+
+Annotate.prototype.awaitUserInput = function (isAll = false) {
+  return new Promise((resolve) => {
+      $("#modalFooter").on("click", "#reAnnotate", function () {
+          resolve("reAnnotate");
+      });
+      $("#modalFooter").on("click", "#cancel", function () {
+          resolve("cancel");
+      });
+      if (isAll) {
+          $("#modalFooter").on("click", "#reAnnotateAll", function () {
+              resolve("reAnnotateAll");
+          });
+      }
+  });
+};
+
+Annotate.prototype.addScores = function (img_id, res, attr, slice_id) {
+  if (
+      res.hasOwnProperty("slice_scores") &&
+      res.hasOwnProperty("infarct_scores") &&
+      res.hasOwnProperty("risk_scores")
+  ) {
+      if (attr !== undefined) {
+          switch (attr["region_attributes"]["Type"]) {
+              case "Slice":
+                  attr.score = res["slice_scores"][slice_id];
+                  break;
+              case "Infarct":
+                  attr.score = res["infarct_scores"][slice_id];
+                  break;
+              case "Risk":
+                  attr.score = res["risk_scores"][slice_id];
+                  break;
+          }
+      }
+  }
+};
+
+Annotate.prototype.getImageBlobFromElement = async function (imageElement) {
+  return new Promise((resolve, reject) => {
+      const canvas = document.createElement("canvas");
+      const context = canvas.getContext("2d");
+      canvas.width = imageElement.width;
+      canvas.height = imageElement.height;
+      context.drawImage(
+          imageElement,
+          0,
+          0,
+          imageElement.width,
+          imageElement.height
+      );
+      canvas.toBlob((blob) => {
+          resolve(blob);
+      }, "image/png");
+  });
+};
+
+Annotate.prototype.isAnnotated = async function (img_id) {
+  if (_via_img_metadata[img_id].autoAnnotated) {
+      this.setModal(
+          "Notice",
+          '<p class ="fw-bold" >This image is already annotated. Do you want to re-annotate them?</p>' +
+          '<p class ="fw-light">If you click "Re-Annotate", the old annotations will be deleted.</p>' +
+          '<p class ="fw-light">If you click "Cancel", the old annotations will be kept.</p>',
+          '<button type="button" class="btn btn-primary" id="reAnnotate">Re-Annotate</button>' +
+          '<button type="button" class="btn btn-secondary" id="cancel">Cancel</button>'
+      );
+      this.reAnnotateCommand = await this.awaitUserInput();
+      this.clearModal();
+  }
+};
+
+Annotate.prototype.isAnnotatedAll = async function (img_id) {
+  if (
+      _via_img_metadata[img_id].autoAnnotated &&
+      this.reAnnotateCommand !== "reAnnotateAll"
+  ) {
+      $("#staticBackdropModal h1").html("Notice");
+      this.setModal(
+          "Notice",
+          '<p class ="fw-bold" >This image is already annotated. Do you want to re-annotate them?</p>' +
+          '<p class ="fw-light">If you click "Re-Annotate", the old annotations will be deleted.</p>' +
+          '<p class ="fw-light">If you click "Cancel", the old annotations will be kept.</p>',
+          '<button type="button" class="btn btn-primary" id="reAnnotate">Re-Annotate</button>' +
+          '<button type="button" class="btn btn-primary" id="reAnnotateAll">Re-Annotate all</button>' +
+          '<button type="button" class="btn btn-secondary" id="cancel">Cancel</button>'
+      );
+      this.reAnnotateCommand = await this.awaitUserInput(true);
+      this.clearModal();
+  }
+};
+
+Annotate.prototype.addAnnotations = async function (img_id, res) {
+  let enableSlice = true;
+  let slice_id = -1;
+  if (this.reAnnotateCommand === "reAnnotate" || this.reAnnotateCommand === "reAnnotateAll") {
+      _via_img_metadata[img_id].regions = [];
+      _via_img_metadata[img_id].autoAnnotated = false;
+      _via_img_metadata[img_id].lockedRegions.clear();
+  }
+  for (let m = 0; m < res["masks"].length; m++) {
+      let region_i = { shape_attributes: {}, region_attributes: {} };
+      let Type = "Risk";
+      if (res["class_ids"][m] === 0) Type = "Slice";
+      else if (res["class_ids"][m] === 1) Type = "Infarct";
+      region_i["region_attributes"]["Type"] = Type;
+      if (Type === "Slice" && enableSlice) {
+          slice_id++;
+          enableSlice = false;
+      }
+      if (Type !== "Slice" && !enableSlice) {
+          enableSlice = true;
+      }
+      this.addScores(img_id, res, region_i, slice_id);
+      let Xpoints = [];
+      let Ypoints = [];
+      let iter = 1;
+      if (res["masks"][m].length > 1) {
+          for (let i = 0; i < res["masks"][m].length; i = i + 2) {
+              if (res["masks"][m].length > 15) {
+                  if (iter % (settings.reduction + 1) === 0) {
+                      Xpoints.push(res["masks"][m][i]);
+                      Ypoints.push(res["masks"][m][i + 1]);
+                  }
+              } else {
+                  Xpoints.push(res["masks"][m][i]);
+                  Ypoints.push(res["masks"][m][i + 1]);
+              }
+              iter++;
+          }
+          region_i["shape_attributes"] = {
+              name: "polygon",
+              all_points_x: Xpoints,
+              all_points_y: Ypoints,
+          };
+          if (Xpoints.length > 2) {
+              _via_img_metadata[img_id].regions.push(region_i);
+              _via_img_metadata[img_id].addLockedRegion(
+                  _via_img_metadata[img_id].regions.length - 1
+              );
+          }
+      }
+  }
+  _via_img_metadata[img_id].autoAnnotated = true;
+};
+
+Annotate.prototype.sendImage = async function (img_blob, img_filename) {
+  try {
+      const formData = new FormData();
+      formData.append("image", img_blob, img_filename);
+      $.ajax({
+          type: "POST",
+          url: settings.serverAddress + "/upload",
+          data: formData,
+          contentType: false,
+          processData: false,
+          xhr: function () {
+              const xhr = new window.XMLHttpRequest();
+              xhr.upload.addEventListener("progress", function (event) {
+                  if (event.lengthComputable) {
+                      const progress = (event.loaded / event.total) * 100;
+                      AutoAnnotator.setProgressBar(progress);
+                  }
+              });
+              return xhr;
+          },
+          success: function (response) {
+              $("#annotation_spinner").addClass("d-none");
+              if (response === undefined || response === null) {
+                  Message.show({
+                      address: "Error",
+                      body: "Error sending images!",
+                  });
+                  return;
+              }
+              if (response === "login error") {
+                  Message.show({
+                      address: "Error",
+                      body: "Please login again.",
+                  });
+                  setTimeout(function () {
+                      window.location.href = "/login";
+                  }, 2000);
+                  return;
+              }
+              zoom.resetZoom();
+              for (let file in response) {
+                  let img_id =
+                      _via_image_id_list[_via_image_filename_list.indexOf(file)];
+                  AutoAnnotator.addAnnotations(img_id, response[file]);
+              }
+              AutoAnnotator.setAllItemSuccess();
+              Message.show({
+                  address: "Success",
+                  body: "the image annotated successfully.",
+              });
+          },
+          error: function (error) {
+              console.error(error);
+              AutoAnnotator.setProgressBar(0);
+              $("#annotation_spinner").addClass("d-none");
+              setTimeout(function () {
+                  $("#staticBackdropProgress").modal("hide");
+              }, 1000);
+              Message.show({
+                  address: "Error",
+                  body: "Error sending images!",
+              });
+          },
+      });
+  } catch (e) {
+      console.log(e);
+      return false;
+  }
+};
+
+Annotate.prototype.sendImageAll = async function () {
+  try {
+      const formData = new FormData();
+      let reannotate = false;
+      for (const element of _via_image_grid_selected_img_index_list) {
+          let image_index = element;
+          let img_id = _via_image_id_list[image_index];
+          await this.isAnnotatedAll(img_id);
+          if (this.reAnnotateCommand === "cancel") {
+              continue;
+          }
+          if (this.reAnnotateCommand === "reAnnotateAll" || this.reAnnotateCommand === "reAnnotate") {
+              reannotate = true;
+          }
+          let imageElement = $("#bim" + image_index)[0];
+          if (imageElement === undefined) {
+              await buffer.addImageToBuffer(image_index);
+              imageElement = $("#bim" + image_index)[0];
+          }
+          let img_blob = await this.getImageBlobFromElement(imageElement);
+          let img_filename = _via_img_metadata[img_id].filename;
+          formData.append("image", img_blob, img_filename);
+      }
+      if (reannotate) {
+          this.reAnnotateCommand = "reAnnotate";
+      }
+      if (this.reAnnotateCommand === "cancel") {
+          this.progressNum = 100;
+          this.setProgressBar(this.progressNum);
+          return;
+      }
+      $.ajax({
+          type: "POST",
+          url: settings.serverAddress + "/upload",
+          data: formData,
+          contentType: false,
+          processData: false,
+          xhr: function () {
+              const xhr = new window.XMLHttpRequest();
+              xhr.upload.addEventListener("progress", function (event) {
+                  if (event.lengthComputable) {
+                      const progress = (event.loaded / event.total) * 100;
+                      AutoAnnotator.setProgressBar(progress);
+                  }
+              });
+              return xhr;
+          },
+          success: function (response) {
+              $("#annotation_spinner").addClass("d-none");
+              if (response === undefined || response === null) {
+                  Message.show({
+                      address: "Error",
+                      body: "Error sending images!",
+                  });
+                  return;
+              }
+              if (response === "login error") {
+                  Message.show({
+                      address: "Error",
+                      body: "Please login again.",
+                  });
+                  setTimeout(function () {
+                      window.location.href = "/login";
+                  }, 2000);
+                  return;
+              }
+              zoom.resetZoom();
+              for (let file in response) {
+                  let img_id = _via_image_id_list[_via_image_filename_list.indexOf(file)];
+                  if (reannotate) {
+                      _via_img_metadata[img_id].regions = [];
+                  }
+                  AutoAnnotator.addAnnotations(img_id, response[file]);
+              }
+              AutoAnnotator.setAllItemSuccess();
+          }.bind(this),
+          error: function (error) {
+              console.error(error);
+              $("#annotation_spinner").addClass("d-none");
+              AutoAnnotator.setProgressBar(0);
+              setTimeout(function () {
+                  $("#staticBackdropProgress").modal("hide");
+              }, 1000);
+              Message.show({
+                  address: "Error",
+                  body: "Error sending images!",
+              });
+          },
+      });
+  } catch (e) {
+      console.log(e);
+      return false;
+  }
+};
+
+Annotate.prototype.setAllItemSuccess = function () {
+  _via_is_all_region_selected = false;
+  drawing.setIsRegionSelected(false);
+  drawing.setUserSelRegionId(-1);
+  zoom.resetZoom();
+  _via_load_canvas_regions();
+  if (_via_canvas_regions.length === 0) {
+      drawing.clearCanvas();
+  } else {
+      drawing.redrawRegCanvas();
+  }
+  zoom.fixCanvasBlur();
+  _via_reg_canvas.focus();
+  setTimeout(function () {
+      plugin.updateSliceRegion();
+  }, 1000);
+
+  this.progressNum = 100;
+  this.setProgressBar(this.progressNum);
+  this.reAnnotateCommand = "";
+};;// undo_redo.js
+// Description: This file contains the implementation of the undo/redo feature.
+// The undo/redo feature is implemented using the Command pattern.
+// The undo/redo feature is implemented in a separate worker thread.
+function undoRedoWorker() {
+  const INCREMENT = "INCREMENT";
+  const DECREMENT = "DECREMENT";
+
+  let region_metadata = {};
+
+  /**
+   * Named counter, to make it reusable for other undo/redo implementations
+   * @param name - name of the counter
+   * @returns {{name, count: number}} - counter number
+   */
+  const createNamedCounter = (name) => {
+    return {
+      name,
+      count: 0,
+    };
+  };
+
+  /**
+   * It defines the incrementation of the named counter
+   * @param counter - the named counter
+   * @returns {{undo(): void, execute(): void}} - the incrementation command
+   */
+  const createIncrementCommand = (counter) => {
+    const previousCount = counter.count;
+
+    return {
+      execute() {
+        counter.count += 1;
+      },
+      undo() {
+        counter.count = previousCount;
+      },
+    };
+  };
+
+  /**
+   * It defines the decrement of the named counter
+   * @param counter - the named counter
+   * @returns {{undo(): void, execute(): void}} - the decrement command
+   */
+  const createDecrementCommand = (counter) => {
+    const previousCount = counter.count;
+
+    return {
+      execute() {
+        counter.count -= 1;
+      },
+      undo() {
+        counter.count = previousCount;
+      },
+    };
+  };
+
+  const commands = {
+    [INCREMENT]: createIncrementCommand,
+    [DECREMENT]: createDecrementCommand,
+  };
+
+  /**
+   * It defines the undo/redo commands
+   * @param target - the target of the command
+   * @returns {{undo(): void, redo(): void, doCommand(*): void}} - the undo/redo command
+   */
+  const createCommandManager = (target) => {
+    let history = [null];
+    let position = 0;
+
+    return {
+      doCommand(commandType) {
+        if (position < history.length - 1) {
+          history = history.slice(0, position + 1);
+        }
+
+        if (commands[commandType]) {
+          const concreteCommand = commands[commandType](target);
+          history.push(concreteCommand);
+          position += 1;
+
+          concreteCommand.execute();
+        }
+      },
+
+      undo() {
+        if (position > 0) {
+          history[position].undo();
+          position -= 1;
+        }
+        updateState();
+      },
+
+      redo() {
+        if (position < history.length - 1) {
+          position += 1;
+          history[position].execute();
+        }
+        updateState();
+      },
+    };
+  };
+
+  const counter = createNamedCounter("via_metadata");
+  const commandManager = createCommandManager(counter);
+
+  let regionMementos = [];
+
+  /**
+   * It adds the region's metadata to the regionMementos array
+   */
+  function addState() {
+    if (counter.count !== regionMementos.length) {
+      if (counter.count > regionMementos.length) {
+        regionMementos.splice(0, regionMementos.length);
+        counter.count = regionMementos.length;
+      } else {
+        regionMementos.splice(counter.count, regionMementos.length);
+        counter.count = regionMementos.length;
+      }
+    }
+
+    let memento = JSON.stringify(region_metadata);
+    for (const element of regionMementos) {
+      if (element === memento) {
+        return;
+      }
+    }
+    regionMementos.push(memento);
+    if (regionMementos.length > 1) {
+      self.postMessage({ undo_enabled: true, redo_enabled: false });
+    }
+    commandManager.doCommand(INCREMENT);
+  }
+
+  /**
+   * It updates the state of the valid data, when the undo/redo is called
+   */
+  function updateState() {
+    if (counter.count < regionMementos.length) {
+      region_metadata = JSON.parse(regionMementos[counter.count]);
+      if (counter.count === 0 && regionMementos.length > 1) {
+        self.postMessage({
+          undo_enabled: false,
+          redo_enabled: true,
+          region_metadata: region_metadata,
+          is_update: true,
+        });
+        return;
+      }
+      if (
+        counter.count === regionMementos.length - 1 &&
+        regionMementos.length > 1
+      ) {
+        self.postMessage({
+          undo_enabled: true,
+          redo_enabled: false,
+          region_metadata: region_metadata,
+          is_update: true,
+        });
+        return;
+      }
+      if (counter.count === 0 && regionMementos.length === 1) {
+        self.postMessage({
+          undo_enabled: false,
+          redo_enabled: false,
+          region_metadata: region_metadata,
+          is_update: true,
+        });
+        return;
+      }
+      self.postMessage({
+        undo_enabled: true,
+        redo_enabled: true,
+        region_metadata: region_metadata,
+        is_update: true,
+      });
+    } else {
+      commandManager.undo();
+    }
+  }
+
+  function resetState() {
+    regionMementos = [];
+    counter.count = 0;
+    self.postMessage({ undo_enabled: false, redo_enabled: false });
+  }
+
+  /**
+   * It handles the undo/redo commands from the main thread
+   * @param e - the event
+   */
+  self.onmessage = function (e) {
+    switch (e.data.commands) {
+      case "undo":
+        commandManager.undo();
+        region_metadata = e.data.region_metadata;
+        break;
+      case "redo":
+        commandManager.redo();
+        region_metadata = e.data.region_metadata;
+        break;
+      case "add":
+        region_metadata = e.data.region_metadata;
+        addState();
+        break;
+      case "reset":
+        resetState();
+        break;
+    }
+  };
+}
+
+const undoredo_worker = new Worker(
+  URL.createObjectURL(
+    new Blob(["(" + undoRedoWorker.toString() + ")()"], {
+      type: "text/javascript",
+    })
+  )
+);
+
+//
+// Data structure [2] to store metadata about file and regions
+//
+
+/**
+* Element of the region data structure
+* The two element object is used to store the region's and its children's metadata
+* Two proxies are used to handle the changes of the region's metadata
+* @constructor  - creates a new region element
+*/
+let File_Region = function (shape_attributes = {}, region_attributes = {}) {
+  this.shape_attributes = new Proxy(shape_attributes, validator); // region shape attributes
+  this.region_attributes = new Proxy(region_attributes, validator); // region attributes
+};
+
+/**
+* It detects the data structures inside the object
+* It needs to make new proxies for these data structures
+* @param obj - the object to be validated
+* @returns {string} - the type of the object
+*/
+function trueTypeOf(obj) {
+  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
+}
+
+/**
+* It validates the changes of the region's metadata
+* Set function is called when a new property is added to the object, or change an existing property
+* @type {{set: (function(*, *, *): boolean), get: ((function(*, *): (boolean|*))|*), deleteProperty: (function(*, *): boolean)}}
+*/
+let validator = {
+  get: function (obj, prop) {
+    // If the property is "_isProxy", item is already being intercepted by this proxy handler
+    // return true
+    if (prop === "_isProxy") {
+      return true;
+    }
+
+    // If the property is an array or object and not already a proxy, make it one
+    if (
+      ["object", "array"].includes(trueTypeOf(obj[prop])) &&
+      !obj[prop]._isProxy
+    ) {
+      obj[prop] = new Proxy(obj[prop], validator);
+    }
+
+    return obj[prop];
+  },
+  set: function (obj, prop, value) {
+    obj[prop] = value;
+    if (undo_redo_enabled.enabled) {
+      enableUndoRedo();
+      setTimeout(function () {
+        enableUndoRedo();
+        return true;
+      }, 250);
+    }
+    return true;
+  },
+
+  deleteProperty: function (obj, prop) {
+    delete obj[prop];
+    return true;
+  },
+};
+
+function enableUndoRedo() {
+  undo_redo_enabled.enabled = !undo_redo_enabled.enabled;
+}
+
+/**
+* It is a helper to filter the changes of the region's metadata
+* @type {{set: validator2.set, get: (function(*, *): *)}}
+*/
+const validator2 = {
+  get: function (obj, prop) {
+    return obj[prop];
+  },
+  set: function (obj, prop, value) {
+    obj[prop] = value;
+    if (undo_redo_enabled.enabled) {
+      if (now_update === false) {
+        undoredo_worker.postMessage({
+          commands: "add",
+          region_metadata: JSON.stringify(
+            _via_img_metadata[_via_image_id].regions
+          ),
+        });
+      } else {
+        now_update = false;
+      }
+    }
+    return true;
+  },
+};
+
+const undo_redo_enabled = new Proxy({ enabled: true }, validator2);
+let now_update = false;
+
+/**
+* This is an event listener for the undo redo worker thread
+* It receives the changes of the region's metadata in the worker thread
+*/
+undoredo_worker.addEventListener(
+  "message",
+  function handleMessageFromWorker(msg) {
+    if (msg.data.undo_enabled) {
+      $("#nav_undo").removeClass("disabled");
+    } else {
+      $("#nav_undo").addClass("disabled");
+    }
+    if (msg.data.redo_enabled) {
+      $("#nav_redo").removeClass("disabled");
+    } else {
+      $("#nav_redo").addClass("disabled");
+    }
+
+    if (!msg.data.hasOwnProperty("region_metadata")) {
+      return;
+    }
+
+    undo_redo_enabled.enabled = false;
+    now_update = true;
+    let tmp_metadata = JSON.parse(msg.data.region_metadata);
+    _via_img_metadata[_via_image_id].regions = [];
+    for (const element of tmp_metadata) {
+      _via_img_metadata[_via_image_id].regions.push(
+        new File_Region(element.shape_attributes, element.region_attributes)
+      );
+    }
+    _via_is_all_region_selected = false;
+    _via_load_canvas_regions();
+    drawing.redrawRegCanvas();
+    drawing.setIsRegionSelected(false);
+    drawing.setUserSelRegionId(-1);
+    _via_reg_canvas.focus();
+    undo_redo_enabled.enabled = true;
+  }
+);;// ui_handler.js
+// Description: UI elements event handlers
+$("#nav_brand").click(function () {
+  show_home_panel();
+  return false;
+});
+$("#nav_home").click(function () {
+  show_home_panel();
+  return false;
+});
+
+//project dropdown elements
+$("#nav_project_load").click(function () {
+  project.openSelectProjectFile();
+  return false;
+});
+$("#nav_project_save").click(function () {
+  project.saveWithConfirm();
+  return false;
+});
+$("#nav_project_setting").click(function () {
+  settings.toggleSettings();
+  return false;
+});
+$("#nav_project_addLocFiles").click(function () {
+  sel_local_images();
+  return false;
+});
+$("#nav_project_addFilUrl").click(function () {
+  project.addUrlFileWithInput();
+  return false;
+});
+$("#nav_project_addFilAbs").click(function () {
+  project.addAbsPathFileWithInput();
+  return false;
+});
+//$('#nav_project_addFilText').click(function(){ sel_local_data_file('files_url'); return false; });
+//$('#nav_project_removeFile').click(function(){ project_file_remove_with_confirm(); return false; });
+//$('#nav_project_importAttributes').click(function(){ sel_local_data_file('attributes'); return false; });
+//$('#nav_project_exportAttributes').click(function(){ project_save_attributes(); return false; });
+
+//annotation dropdown elements
+$("#nav_annotation_export").click(function () {
+  fileManager.downloadAllRegionDataModal();
+  return false;
+});
+$("#nav_annotation_import").click(function () {
+  fileManager.importAnnotationsFromFile();
+  return false;
+});
+$("#nav_annotation_prevAnn").click(function () {
+  show_annotation_data();
+  return false;
+});
+$("#nav_annotation_downImage").click(function () {
+  download_as_image();
+  return false;
+});
+$("#nav_annotation_autoAnn").click(function () {
+  AutoAnnotator.run_annotation();
+  return false;
+});
+
+//view dropdown elements
+$("#nav_view_imgGrid").click(function () {
+  image_grid_toggle();
+  return false;
+});
+$("#nav_view_sidebar").click(function () {
+  leftsidebar_toggle();
+  return false;
+});
+$("#nav_view_status").click(function () {
+  toggle_message_visibility();
+  return false;
+});
+$("#nav_view_regionBound").click(function () {
+  toggle_region_boundary_visibility();
+  return false;
+});
+$("#nav_view_regionLabel").click(function () {
+  toggle_region_id_visibility();
+  return false;
+});
+
+//icons
+$("#nav_openProject").click(function () {
+  project.openSelectProjectFile();
+  return false;
+});
+$("#nav_saveProject").click(function () {
+  project.saveWithConfirm();
+  return false;
+});
+$("#nav_settings").click(function () {
+  settings.toggleSettings();
+  return false;
+});
+$("#nav_imgGrid").click(function () {
+  image_grid_toggle();
+  return false;
+});
+$("#nav_sidePanel").click(function () {
+  leftsidebar_toggle();
+  return false;
+});
+$("#nav_autoAnn").click(function () {
+  AutoAnnotator.run_annotation();
+  return false;
+});
+$("#nav_calcAreas").click(function () {
+  plugin.ExportArea();
+  return false;
+});
+$("#nav_prev").click(function () {
+  move_to_prev_image();
+  return false;
+});
+$("#nav_next").click(function () {
+  move_to_next_image();
+  return false;
+});
+$("#nav_undo").click(function () {
+  undoredo_worker.postMessage({ commands: "undo" });
+  return false;
+});
+$("#nav_redo").click(function () {
+  undoredo_worker.postMessage({ commands: "redo" });
+  return false;
+});
+$("#nav_zoomIn").click(function () {
+  zoom.zoomIn();
+  return false;
+});
+$("#nav_zoomOut").click(function () {
+  zoom.zoomOut();
+  return false;
+});
+$("#nav_selAllReg").click(function () {
+  sel_all_regions();
+  return false;
+});
+$("#nav_copySelReg").click(function () {
+  copy_sel_regions();
+  return false;
+});
+$("#nav_pasteReg").click(function () {
+  paste_sel_regions_in_current_image();
+  return false;
+});
+$("#nav_delSelReg").click(function () {
+  del_sel_regions();
+  return false;
+});
+
+//
+//Drawing elements
+//
+$("#shape_drag").click(function () {
+  select_region_shape("drag");
+  return false;
+});
+$("#shape_circle").click(function () {
+  select_region_shape("circle");
+  return false;
+});
+$("#shape_ellipse").click(function () {
+  select_region_shape("ellipse");
+  return false;
+});
+$("#shape_pen").click(function () {
+  select_region_shape("pen");
+  return false;
+});
+$("#shape_polygon").click(function () {
+  select_region_shape("polygon");
+  return false;
+});
+$("#shape_edit").click(function () {
+  select_region_shape("edit");
+  return false;
+});
+$("#shape_remove").click(function () {
+  select_region_shape("remove");
+  return false;
+});
+$("#shape_rect").click(function () {
+  select_region_shape("rect");
+  return false;
+});
+$("#shape_trim").click(function () {
+  select_region_shape("trim");
+  return false;
+});
+$("#image_settings").click(function () {
+  select_region_shape("img_set");
+  return false;
+});
+
+//
+//Sidebar
+//
+$("#project_name").change(function () {
+  project.onNameUpdate(this);
+  return false;
+});
+$("#sidebar_addFiles").click(function () {
+  sel_local_images();
+  return false;
+});
+$("#sidebar_addUrl").click(function () {
+  project.addUrlFileWithInput();
+  return false;
+});
+$("#sidebar_remove").click(function () {
+  project.fileRemoveWithConfirm();
+  return false;
+});
+$("#sidebar_ToggleAEModes").click(function () {
+  sidebar.toggleAEMode();
+  return false;
+});
+$("#sidebar_UpdateSlices").click(function () {
+  plugin.updateSliceRegion();
+  return false;
+});
+
+//
+//Image manipulation
+//
+$("#sidebar_reset").click(function () {
+  image.backToSidebar();
+  select_region_shape("edit");
+  return false;
+});
+$("#image_reset").click(function () {
+  image.resetFilters();
+  return false;
+});
+$("#sidebar_onImageEditor").click(function () {
+  sidebar.annotation_editor_toggle_on_image_editor();
+  return false;
+});
+
+
+$("#start_selectImages").click(function () {
+  sel_local_images();
+  return false;
+});
+
+$("#start_addUrlImages").click(function () {
+  project.addUrlFileWithInput();
+  return false;
+});
+
+$("#start_settings").click(function () {
+  settings.toggleSettings();
+  return false;
+});
+
+$("#start_saveProject").click(function () {
+  project.saveWithConfirm();
+  return false;
+});
+
+$("#start_loadProject").click(function () {
+  project.openSelectProjectFile();
+  return false;
+});
+
+$("#start_gettingStarted").click(function () {
+  set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED);
+  return false;
+});
+
+$("#404_settings").click(function () {
+  settings.toggleSettings();
+  return false;
+});
+
+$("#404_selectImages").click(function () {
+  sel_local_images();
+  return false;
+});
+
+$("#404_selectFolder").click(function () {
+  project.loadAllImages();
+  return false;
+});
+
+$("#404_gettingStarted").click(function () {
+  set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED);
+  return false;
+});
+
+$("#nav_help_getStart").click(function () {
+  set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED);
+  return false;
+});
+
+$("#image_grid_toolbar_group_by_select").change(function () {
+  image_grid_toolbar_onchange_group_by_select(this);
+  return false;
+});
\ No newline at end of file
diff --git a/static/assets/js/main.min.js b/static/assets/js/main.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..1642237f6a3b23c63afdf13a58edb55717b65bcb
--- /dev/null
+++ b/static/assets/js/main.min.js
@@ -0,0 +1,2 @@
+let SAA_VERSION="1.1.0 (pre-release)",SAA_NAME="InfarctSize",SAA_SHORT_NAME="ISAI",VIA_REGION_SHAPE={EDIT:"edit",RECT:"rect",CIRCLE:"circle",ELLIPSE:"ellipse",POLYGON:"polygon",POINT:"point",POLYLINE:"polyline",PEN:"pen",REMOVE:"remove",TRIM:"trim",DRAG:"drag"},VIA_ATTRIBUTE_TYPE={TEXT:"text",CHECKBOX:"checkbox",RADIO:"radio",IMAGE:"image",DROPDOWN:"dropdown"},VIA_DISPLAY_AREA_CONTENT_NAME={IMAGE:"image_panel_container",IMAGE_GRID:"image_grid_panel",SETTINGS:"settings_panel",PAGE_404:"page_404",PAGE_GETTING_STARTED:"page_getting_started",PAGE_ABOUT:"page_about",PAGE_START_INFO:"page_start_info",PAGE_LICENSE:"page_license"},VIA_ANNOTATION_EDITOR_MODE={SINGLE_REGION:"single_region",ALL_REGIONS:"all_regions",HYBRID:"hybrid"},VIA_ANNOTATION_EDITOR_PLACEMENT={NEAR_REGION:"NEAR_REGION",IMAGE_BOTTOM:"IMAGE_BOTTOM",DISABLE:"DISABLE"},changes=[],VIA_REGION_MIN_DIM=3,VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX=3,VIA_CANVAS_ZOOM_LEVELS=[.25,.5,.75,1,1.5,2,2.5,3,4,5,6,7,8,9,10],VIA_THEME_MESSAGE_TIMEOUT_MS=6e3,VIA_CSV_SEP=",",VIA_CSV_KEYVAL_SEP=":",_via_img_metadata={},_via_img_src={},_via_img_fileref={},_via_img_count=0,_via_canvas_regions=[],_via_canvas_scale=1,dpi=window.devicePixelRatio,_via_image_id="",_via_image_index=-1,_via_current_image_filename,_via_current_image,_via_current_image_width,_via_current_image_height,_via_img_stat={},_via_display_area=document.getElementById("display_area"),_via_reg_canvas=document.getElementById("region_canvas"),_via_canvas_width,_via_canvas_height,_via_canvas_zoom_level_index=VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX,_via_canvas_scale_without_zoom=1,_via_is_all_region_selected=!1,_via_is_canvas_zoomed=!1,_via_is_loading_current_image=!1,_via_is_region_id_visible=!0,_via_is_region_boundary_visible=!0,_via_is_region_info_visible=!1,_via_is_ctrl_pressed=!1,_via_is_debug_mode=!1,_via_is_message_visible=!0,_via_region_selected_flag=new Set,_via_copied_image_regions=[],_via_paste_to_multiple_images_input,_via_message_clear_timer,_via_attribute_being_updated="region",_via_attributes={region:{Type:{default_options:{Slice:!0},description:"",options:{Slice:"Slice",Hole:"Hole",Risk:"Risk",Infarct:"Infarct"},type:"dropdown"}},file:{ID:{type:"text",description:"",default_value:""}}},_via_current_attribute_id="",_via_user_input_ok_handler=null,_via_user_input_cancel_handler=null,_via_user_input_data={},_via_metadata_being_updated="region",_via_annotation_editor_mode=VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION,_via_image_id_list=[],_via_image_filename_list=[],_via_image_load_error=[],_via_reload_img_fn_list_table=!0,_via_img_fn_list_img_index_list=[],_via_img_fn_list_html=[],image_grid_panel=document.getElementById("image_grid_panel"),_via_display_area_content_name="",_via_display_area_content_name_prev="",_via_image_grid_load_ongoing=!1,_via_image_grid_page_first_index=0,_via_image_grid_page_last_index=-1,_via_image_grid_selected_img_index_list=[],_via_image_grid_page_img_index_list=[],_via_image_grid_visible_img_index_list=[],_via_image_grid_mousedown_img_index=-1,_via_image_grid_mouseup_img_index=-1,_via_image_grid_img_index_list=[],_via_image_grid_group={},_via_image_grid_group_var=[],_via_image_grid_stack_prev_page=[],VIA_IMG_PRELOAD_INDICES=[1,-1,2,3,-2,4],VIA_IMG_PRELOAD_COUNT=4,_via_settings={ui:{}},fileInput=(_via_settings.ui.annotation_editor_fontsize=.8,_via_settings.ui.leftsidebar_width=18,_via_settings.ui.image_grid={},_via_settings.ui.image_grid.img_height=80,_via_settings.ui.image_grid.rshape_fill="none",_via_settings.ui.image_grid.rshape_fill_opacity=.3,_via_settings.ui.image_grid.rshape_stroke="yellow",_via_settings.ui.image_grid.rshape_stroke_width=2,_via_settings.ui.image_grid.show_region_shape=!0,_via_settings.ui.image_grid.show_image_policy="all",_via_settings.ui.image={},_via_settings.ui.image.region_label="__via_region_id__",_via_settings.ui.image.region_color="Type",_via_settings.ui.image.region_label_font="20px Sans",_via_settings.ui.image.on_image_annotation_editor_placement=VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION,_via_settings.core={},_via_settings.core.buffer_size=4*VIA_IMG_PRELOAD_COUNT+2,_via_settings.core.filepath={},_via_settings.core.default_filepath="",document.getElementById("fileInput")),projectFileInput=document.getElementById("projectHelperFileInput"),ui_top_panel=document.getElementById("ui_top_panel"),image_panel=document.getElementById("image_panel"),img_fn_list_panel=document.getElementById("img_fn_list_panel"),img_fn_list=document.getElementById("img_fn_list"),attributes_panel=document.getElementById("attributes_panel"),leftsidebar=document.getElementById("leftsidebar"),VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE=.1,VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE=20,VIA_LEFTSIDEBAR_WIDTH_CHANGE=1,VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE=5,VIA_FLOAT_PRECISION=3,VIA_COCO_EXPORT_RSHAPE=["rect","circle","ellipse","polygon","point"],VIA_COCO_EXPORT_ATTRIBUTE_TYPE=[VIA_ATTRIBUTE_TYPE.DROPDOWN,VIA_ATTRIBUTE_TYPE.RADIO],tooltipTriggerList=document.querySelectorAll('[data-bs-toggle="tooltip"]'),tooltipList=[...tooltipTriggerList].map(e=>new bootstrap.Tooltip(e)),annotation_editor=document.getElementById("annotation_editor_panel"),locked_regions=[],SegemntaitonServerIP="192.168.1.129:5000/",is_alt_pressed=!1,current_shape="rect",img_buffer=$("#image_buffer"),Shapes={EDIT:"edit",RECTANGLE:"rect",CIRCLE:"circle",ELLIPSE:"ellipse",POLYGON:"polygon",POINT:"point",PEN:"pen",REMOVE:"remove",TRIM:"trim",DRAG:"drag"},img_locked_regions={},Scores={};class Message{static toast=$("#liveToast");static address=$("#toastAddress");static body=$("#toastBody");static show(e){if(""!==e.address&&(_via_is_message_visible||"Error"===e.address)){switch(e.address){case"Error":Message.address.text("Error"),Message.address.attr("class","badge text-bg-danger me-auto");break;case"Warning":Message.address.text("Warning"),Message.address.attr("class","badge text-bg-warning me-auto");break;case"Info":Message.address.text("Info"),Message.address.attr("class","badge text-bg-info me-auto");break;case"Success":Message.address.text("Success"),Message.address.attr("class","badge text-bg-success me-auto");break;default:Message.address.text(e.address),Message.address.attr("class","badge text-bg-primary me-auto")}Message.body.text(e.body),Message.toast.toast("show")}}static test(){Message.show({address:"test",body:"test"})}static showError(e){""!==e&&Message.show({address:"Error",body:e})}static showInfo(e){""!==e&&_via_is_message_visible&&Message.show({address:"Info",body:e})}}let message=new Message;class Settings{constructor(){this.panel=$("#settings_panel"),this.projectName=this.projectGetDefaultProjectName(),this.defaultPath="",this.searchPath="",this.bufferSize=20,this.projectSave=!1,this.is_highlight_region=$("#settings_highlightRegion").is(":checked"),this.is_scrolling=$("#settings_scrollToRow").is(":checked"),this.attributeShowScore=!1,this.attributeShowScoreColor=!1,this.attributeShowPixelArea=!1,this.attributeShowFile=!1,this.serverAddress="",$("#settings_serverReduceRatio").val(this.reduction),this.scoreThreshold="0.85",this.scoreHigherColor=$("#settings_scoreHighColor").val(),this.scoreLowerColor=$("#settings_scoreLowColor").val(),this.reduction=0,this.quickbuttons={},this.image_grid_content="all",this.autoSave=!1,this.project={},this.load(),this.initQuickButtons(),this.initProject(),this.initRegion(),this.initScore(),this.initOther()}toggleSettings(){this.panel.hasClass("d-none")?(clear_display_area(),this.panel.removeClass("d-none"),$("#selection_panel").hide()):(clear_display_area(),show_single_image_view(),this.panel.addClass("d-none"))}load(){var e=JSON.parse(localStorage.getItem("saa_settings"));e?(this.defaultPath=e.defaultPath,this.searchPath=e.searchPath,this.bufferSize=e.bufferSize,this.projectSave=e.projectSave,this.is_highlight_region=e.is_highlight_region,this.is_scrolling=e.is_scrolling,this.attributeShowScore=e.attributeShowScore,this.attributeShowScoreColor=e.attributeShowScoreColor,this.attributeShowPixelArea=e.attributeShowPixelArea,this.scoreThreshold=e.scoreThreshold,this.scoreHigherColor=e.scoreHigherColor,this.scoreLowerColor=e.scoreLowerColor,this.reduction=e.reduction,this.quickbuttons=e.quickbuttons,this.image_grid_content=e.image_grid_content,this.autoSave=e.autoSave):(console.log("No settings found"),this.save(!1))}save(e=!0){try{var t={defaultPath:this.defaultPath,searchPath:this.searchPath,bufferSize:this.bufferSize,projectSave:this.projectSave,is_highlight_region:this.is_highlight_region,is_scrolling:this.is_scrolling,attributeShowScore:this.attributeShowScore,attributeShowScoreColor:this.attributeShowScoreColor,attributeShowPixelArea:this.attributeShowPixelArea,attributeShowFile:this.attributeShowFile,scoreThreshold:this.scoreThreshold,scoreHigherColor:this.scoreHigherColor,scoreLowerColor:this.scoreLowerColor,reduction:this.reduction,quickbuttons:this.quickbuttons,image_grid_content:this.image_grid_content,autoSave:this.autoSave};localStorage.setItem("saa_settings",JSON.stringify(t))}catch(e){console.log(e),Message.show({address:"Error",body:"There was an error saving settings. Please try again.",color:"#cc1100"})}e&&Message.show({address:"Save",body:"Settings saved successfully",color:"#4ADEDE"})}initProject(){$("#settings_projectName").val(this.projectName).on("input",()=>{this.projectName=$("#settings_projectName").val(),$("#project_name").val(this.projectName),this.save()}),$("#project_name").val(this.projectName).on("input",()=>{this.projectName=$("#project_name").val(),$("#settings_projectName").val(this.projectName)}),$("#settings_defaultPath").val(this.defaultPath).on("input",()=>{this.defaultPath=$("#settings_defaultPath").val()+"/",this.save(),console.log("Default path changed to "+this.defaultPath)}),$("#settings_searchPath").val(this.searchPath).on("input",()=>{this.searchPath=$("#settings_searchPath").val(),this.save()})}initRegion(){$("#settings_regionLabel").on("change",()=>{this.regionLabel=$("#settings_regionLabel").find("option:selected").text(),this.save()}),$("#settings_regionColorStyle").on("change",()=>{this.regionColorStyle=$("#settings_regionColorStyle").find("option:selected").text(),this.save()}),$("#settings_regionColor").val(this.regionColor).on("input",()=>{this.regionColor=$("#settings_regionColor").val(),this.save()}),$("#settings_fontStyle").val(this.fontStyle).on("input",()=>{this.fontStyle=$("#settings_fontStyle").val(),this.save()}),this.toggleHighlightRegionCheckbox(),this.toggleScrollingCheckbox()}enableHighlightRegion(){this.is_highlight_region=!this.is_highlight_region,this.save(),this.toggleHighlightRegionCheckbox()}toggleHighlightRegionCheckbox(){this.is_highlight_region?$("#settings_highlightRegion").attr("checked",!0):$("#settings_highlightRegion").attr("checked",!1)}enableScrollingCheckbox(){this.is_scrolling=!this.is_scrolling,this.save(),this.toggleScrollingCheckbox()}toggleScrollingCheckbox(){console.log(this.is_scrolling),this.is_scrolling?$("#settings_scrollToRow").attr("checked",!0):$("#settings_scrollToRow").attr("checked",!1)}toggleScoreCheckbox(){this.attributeShowScore?$("#settings_showScore").attr("checked",!0):$("#settings_showScore").attr("checked",!1)}toggleScoreColorCheckbox(){this.attributeShowScoreColor?$("#settings_showScoreColor").attr("checked",!0):$("#settings_showScoreColor").attr("checked",!1)}togglePixelAreaCheckbox(){this.attributeShowPixelArea?$("#settings_showArea").attr("checked",!0):$("#settings_showArea").attr("checked",!1)}toggleFileAttributesCheckbox(){this.attributeShowFile?$("#settings_showFileAttributes").attr("checked",!0):$("#settings_showFileAttributes").attr("checked",!1)}initScore(){$("#settings_scoring").val(this.scoreThreshold).on("input",()=>{this.scoreThreshold=$("#settings_scoring").val(),this.save()}),$("#settings_scoreHighColor").val(this.scoreHigherColor).on("input",()=>{this.scoreHigherColor=$("#settings_scoreHighColor").val(),console.log(this.scoreHigherColor),this.save()}),$("#settings_scoreLowColor").val(this.scoreLowerColor).on("input",()=>{this.scoreLowerColor=$("#settings_scoreLowColor").val(),this.save()}),this.toggleScoreCheckbox(),this.toggleScoreColorCheckbox(),this.togglePixelAreaCheckbox()}projectAutoSave(){this.projectSave=!this.projectSave}showScore(){this.attributeShowScore=!this.attributeShowScore,this.save()}showScoreColor(){this.attributeShowScoreColor=!this.attributeShowScoreColor,this.save()}showPixelArea(){this.attributeShowPixelArea=!this.attributeShowPixelArea,this.save()}showFileAttributes(){this.attributeShowFile=!this.attributeShowFile,this.save()}initQuickButtons(){null!=this.quickbuttons&&0!==Object.keys(this.quickbuttons).length||(this.quickbuttons={openProject:!0,saveProject:!0,settings:!0,imgGrid:!0,sidePanel:!1,autoAnn:!0,calcAreas:!0,prev:!1,next:!1,undo:!0,redo:!0,zoomIn:!1,zoomOut:!1,selAllReg:!1,copySelReg:!1,pasteReg:!1,delSelReg:!1},this.save(!1))}toggleQuickButtonsCheckbox(){if(this.quickbuttons)for(var e in this.quickbuttons)this.quickbuttons[e]?$("#btn-"+e).attr("checked",!0):$("#btn-"+e).attr("checked",!1)}toggleQuickButtons(){if(this.quickbuttons)for(var e in this.quickbuttons)this.quickbuttons[e]?$("#nav_"+e).show():$("#nav_"+e).hide()}initOther(){this.toggleQuickButtonsCheckbox(),this.toggleQuickButtons(),$("#settings_serverReduceRatio").val(this.reduction).on("input",()=>{this.reduction=$("#settings_serverReduceRatio").val(),this.reduction=parseInt(this.reduction),this.save()}),$("#settings_preloadBuffer").val(this.bufferSize).on("input",()=>{this.bufferSize=$("#settings_preloadBuffer").val(),this.bufferSize=parseInt(this.bufferSize),this.save()}),$("#btn-openProject").val(this.quickbuttons.openProject).on("change",()=>{this.quickbuttons.openProject=$("#btn-openProject").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-saveProject").val(this.quickbuttons.saveProject).on("change",()=>{this.quickbuttons.saveProject=$("#btn-saveProject").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-settings").val(this.quickbuttons.settings).on("change",()=>{this.quickbuttons.settings=$("#btn-settings").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-imgGrid").val(this.quickbuttons.imgGrid).on("change",()=>{this.quickbuttons.imgGrid=$("#btn-imgGrid").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-sidePanel").val(this.quickbuttons.sidePanel).on("change",()=>{this.quickbuttons.sidePanel=$("#btn-sidePanel").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-autoAnn").val(this.quickbuttons.autoAnn).on("change",()=>{this.quickbuttons.autoAnn=$("#btn-autoAnn").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-calcAreas").val(this.quickbuttons.calcAreas).on("change",()=>{this.quickbuttons.calcAreas=$("#btn-calcAreas").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-prev").val(this.quickbuttons.prev).on("change",()=>{this.quickbuttons.prev=$("#btn-prev").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-next").val(this.quickbuttons.next).on("change",()=>{this.quickbuttons.next=$("#btn-next").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-undo").val(this.quickbuttons.undo).on("change",()=>{this.quickbuttons.undo=$("#btn-undo").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-redo").val(this.quickbuttons.redo).on("change",()=>{this.quickbuttons.redo=$("#btn-redo").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-settings").val(this.quickbuttons.settings).on("change",()=>{this.quickbuttons.settings=$("#btn-settings").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-zoomIn").val(this.quickbuttons.zoomIn).on("change",()=>{this.quickbuttons.zoomIn=$("#btn-zoomIn").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-zoomOut").val(this.quickbuttons.zoomOut).on("change",()=>{this.quickbuttons.zoomOut=$("#btn-zoomOut").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-selAllReg").val(this.quickbuttons.selAllReg).on("change",()=>{this.quickbuttons.selAllReg=$("#btn-selAllReg").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-copySelReg").val(this.quickbuttons.copySelReg).on("change",()=>{this.quickbuttons.copySelReg=$("#btn-copySelReg").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-pasteReg").val(this.quickbuttons.pasteReg).on("change",()=>{this.quickbuttons.pasteReg=$("#btn-pasteReg").is(":checked"),this.toggleQuickButtons(),this.save()}),$("#btn-delSelReg").val(this.quickbuttons.delSelReg).on("change",()=>{this.quickbuttons.delSelReg=$("#btn-delSelReg").is(":checked"),this.toggleQuickButtons(),this.save()})}projectGetDefaultProjectName(){var e=new Date;return"ISAI_"+(e.getDate()+["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][e.getMonth()]+e.getFullYear()+"_"+e.getHours()+":"+e.getMinutes())}autoSaveProject(){this.autoSave&&this.save(!1)}updateProjectData(e={}){this.project=e}getProjectData(){return this.project}isThereAProject(){return 0<Object.keys(this.project).length}getSettings(){return{defaultPath:this.defaultPath,searchPath:this.searchPath,bufferSize:this.bufferSize,projectSave:this.projectSave,projectName:this.projectName,is_highlight_region:this.is_highlight_region,is_scrolling:this.is_scrolling,attributeShowScore:this.attributeShowScore,attributeShowScoreColor:this.attributeShowScoreColor,attributeShowPixelArea:this.attributeShowPixelArea,scoreThreshold:this.scoreThreshold,scoreHigherColor:this.scoreHigherColor,scoreLowerColor:this.scoreLowerColor,reduction:this.reduction,quickbuttons:this.quickbuttons,image_grid_content:this.image_grid_content,autoSave:this.autoSave}}importSettings(e){this.defaultPath=e.defaultPath,this.searchPath=e.searchPath,this.bufferSize=e.bufferSize,this.projectSave=e.projectSave,this.projectName=e.projectName,this.is_highlight_region=e.is_highlight_region,this.is_scrolling=e.is_scrolling,this.attributeShowScore=e.attributeShowScore,this.attributeShowScoreColor=e.attributeShowScoreColor,this.attributeShowPixelArea=e.attributeShowPixelArea,this.scoreThreshold=e.scoreThreshold,this.scoreHigherColor=e.scoreHigherColor,this.scoreLowerColor=e.scoreLowerColor,this.reduction=e.reduction,this.quickbuttons=e.quickbuttons,this.image_grid_content=e.image_grid_content,this.autoSave=e.autoSave,this.save(!1)}}let settings=new Settings;class Project{constructor(){this.name=settings.projectGetDefaultProjectName(),this.attributes=this.initAttributes(),this.images={},this.metadata={},this.files={},this.idlist=[]}initAttributes(){return{region:{Type:{default_options:{Slice:!0},description:"",options:{Slice:"Slice",Hole:"Hole",Risk:"Risk",Infarct:"Infarct"},type:"dropdown"}},file:{ID:{type:"text",description:"",default_value:""}}}}setName(e){this.name=e}initDefaultProject(){settings.isThereAProject()||(this.project={}),this.setName(settings.projectGetDefaultProjectName())}onNameUpdate(e){this.setName(e.value)}getProjectName(){return this.name}saveWithConfirm(){var e=this.getProjectName(),t='<div class="mb-3">\n  <label for="projectNameModal" class="form-label">Project name</label>\n  <input type="email" class="form-control" id="projectNameModal" placeholder="'+e+'">\n</div>\n',e='<div class="mb-3">\n  <label for="fileNameModal" class="form-label">Project file name</label>\n  <input type="email" class="form-control" id="fileNameModal" placeholder="'+e+'">\n</div>\n';modal.show("Save Project",t+e,"<button type='button' class='btn btn-primary' onclick='project.saveConfirmed(document.getElementById(\"projectNameModal\").value, document.getElementById(\"fileNameModal\").value)'>Save</button>",!1)}static replacer(e,t){return t instanceof Map?{dataType:"Map",value:Array.from(t.entries())}:t instanceof Set?{dataType:"Set",value:Array.from(t)}:t}saveConfirmed(e,t){""===e&&(e=this.getProjectName()),""===t&&(t=this.getProjectName());e={name:e,settings:settings.getSettings(),img_metadata:_via_img_metadata,attributes:this.attributes,image_ids:_via_image_id_list,img_src:_via_img_src},e=JSON.stringify(e,this.replacer),e=new Blob([e],{type:"application/json"});fileManager.saveDataToLocalFile(e,t+".json"),modal.hide(),modal.clear()}openSelectProjectFile(){modal.show("Open Project","Select a project file to open","",!1),fileInput&&(fileInput.accept=".json",fileInput.onchange=this.projectOpen,fileInput.removeAttribute("multiple"),fileInput.click())}projectOpen(e){e=e.target.files[0];FileManager.loadTextFile(e,project.parseJsonFile.bind(project))}parseJsonFile(e){var t,i=JSON.parse(e);if(this.checkProjectFileValidity(i)){for(var a in settings.importSettings(i.settings),clearViaData(),i.img_metadata)this.checkMetadataValidity(i.img_metadata[a])?(_via_img_metadata[a]=new FileMetadata(i.img_metadata[a].filename,i.img_metadata[a].size),_via_img_metadata[a].loadFromJSON(i.img_metadata[a]),_via_image_id_list.push(a),_via_image_filename_list.push(i.img_metadata[a].filename),_via_img_count+=1,sidebar.set_file_annotations_to_default_value(a)):Message.showError("Invalid metadata for image "+a);i.image_ids&&(_via_image_id_list=i.image_ids),i.attributes&&(this.attributes=i.attributes,this.parseAttributesFromMetadata(),e=Object.keys(this.attributes.file),(t=Object.keys(this.attributes.region)).length?(_via_attribute_being_updated="region",_via_current_attribute_id=t[0]):e.length&&(_via_attribute_being_updated="file",_via_current_attribute_id=e[0])),i.img_src&&(_via_img_src=i.img_src),""!==settings.defaultPath?(_via_file_resolve_all_to_default_filepath(),modal.hide(),modal.clear()):modal.update("Project Loaded","Project loaded successfully, but images are not loaded. Do you want to load all project images?","<div class='btn-group' data-toggle='buttons'><button type='button' class='btn btn-primary' onclick='sel_local_images();'>Select images</button><button type='button' class='btn btn-primary' onclick='project.loadAllImages()'>Select image folder</button></div>"),Message.show("Project loaded successfully"),0<_via_image_id_list.length&&(_via_image_index=0,buffer.showImage(_via_image_index),sidebar.update_img_fn_list(),_via_reload_img_fn_list_table=!0)}else modal.hide(),modal.clear(),Message.showError("Invalid project file")}loadAllImages(){projectFileInput&&(projectFileInput.accept="image/*",projectFileInput.onchange=this.solveImageLoading,projectFileInput.click())}solveImageLoading(e){var t,i;for(t of e.target.files)"image"===t.type.substring(0,5)&&-1!==_via_image_filename_list.indexOf(t.name)&&(i=_via_image_filename_list.indexOf(t.name),i=_via_image_id_list[i],_via_img_fileref[i]=t,_via_img_metadata[i].size=t.size);0<_via_image_id_list.length&&(_via_image_index=0,buffer.showImage(_via_image_index),sidebar.update_img_fn_list(),_via_reload_img_fn_list_table=!0),modal.hide(),modal.clear(),projectFileInput.value=""}checkProjectFileValidity(e){return!!(e.settings&&e.img_metadata&&e.attributes&&e.image_ids)}checkMetadataValidity(e){return!!(e.filename&&e.size&&e.file_attributes&&e.regions)}parseAttributesFromMetadata(){for(var t in this.attributes.hasOwnProperty("file")||(this.attributes.file={}),this.attributes.hasOwnProperty("region")||(this.attributes.region={}),_via_img_metadata){for(var e in _via_img_metadata[t].file_attributes)this.attributes.file.hasOwnProperty(e)||(this.attributes.file[e]={},this.attributes.file[e].type="text");for(let e=0;e<_via_img_metadata[t].regions.length;++e)for(var i in _via_img_metadata[t].regions[e].region_attributes)this.attributes.region.hasOwnProperty(i)||(this.attributes.region[i]={},this.attributes.region[i].type="text")}}fileRemoveWithConfirm(){var e=_via_image_id_list[_via_image_index],t=_via_img_metadata[e].filename,e=_via_img_metadata[e].regions.length,t={img_index:{type:"text",name:"File Id",value:_via_image_index+1,disabled:!0,size:8},filename:{type:"text",name:"Filename",value:t,disabled:!0,size:30},region_count:{type:"text",name:"Number of regions",disabled:!0,value:e,size:8}};invoke_with_user_inputs(this.fileRemoveConfirmed,t,{title:"Remove File"})}fileRemoveConfirmed(e){var t=e.img_index-1;this.removeFile(t),t===_via_img_count?0===_via_img_count?(buffer.imgLoaded=!1,show_home_panel()):buffer.showImage(t-1):buffer.showImage(t),_via_reload_img_fn_list_table=!0,sidebar.update_img_fn_list(),Message.show("Removed file: "+e.filename),user_input_default_cancel_handler()}removeFile(e){var t;e<0||e>=_via_img_count?console.log("project_remove_file(): invalid img_index "+e):(t=_via_image_id_list[e],_via_image_id_list.splice(e,1),_via_image_filename_list.splice(e,1),-1!==(e=_via_img_fn_list_img_index_list.indexOf(e))&&_via_img_fn_list_img_index_list.splice(e,1),buffer.emptyBuffer(),sidebar.img_fn_list_add_css_class("text-muted"),drawing.clearCanvas(),delete _via_img_metadata[t],delete _via_img_src[t],delete _via_img_fileref[t],--_via_img_count)}addFile(e,t,i){let a=i;return void 0===i&&(void 0===t&&(t=-1),a=_via_get_image_id(e,t)),_via_img_metadata.hasOwnProperty(a)||(_via_img_metadata[a]=new FileMetadata(e,t),_via_image_id_list.push(a),_via_image_filename_list.push(e),_via_img_count+=1),a}addLocalFile(e){var t,i,a,_=[];let s=0;for(t of e.target.files)"image"===t.type.substring(0,5)?-1===(i=_via_image_filename_list.indexOf(t.name))?(a=project.addFile(t.name,t.size),_via_img_fileref[a]=t,sidebar.set_file_annotations_to_default_value(a),_.push(_via_image_id_list.indexOf(a))):(a=_via_image_id_list[i],_via_img_fileref[a]=t,_via_img_metadata[a].size=t.size):s+=1;if(_via_img_metadata){let e="Loaded "+_.length+" images.";s&&(e+=" ( Discarded "+s+" non-image files! )"),show_message(e),_.length?buffer.showImage(_[0]):buffer.showImage(_via_image_index),sidebar.update_img_fn_list()}else show_message("Please upload some image files!")}addAbsPathFileWithInput(){invoke_with_user_inputs(this.addAbsPathFileConfirmed,{abs_path:{type:"text",name:"Absolute Path",value:"",disabled:!1,size:30}},{title:"Add File"})}addAbsPathFileConfirmed(e){var t,i=e.abs_path;""!==i?(t=i.split("/").pop(),i=this.addUrlFile(i),i=_via_image_id_list.indexOf(i),buffer.showImage(i),sidebar.update_img_fn_list(),user_input_default_cancel_handler(),Message.showInfo("Added file: "+t)):""!==e.absolute_path_list.value?import_files_url_from_csv(e.absolute_path_list.value).then(()=>console.log("csv")):Message.showError("Please enter a valid path")}addUrlFileWithInput(){invoke_with_user_inputs(this.addUrlFileConfirmed,{url:{type:"text",name:"add one URL",placeholder:"https://example.com/image.jpg",disabled:!1,size:50},url_list:{type:"textarea",name:"or, add multiple URL (one url per line)",placeholder:"https://example.com/image1.jpg\nhttps://example.com/image2.jpg\nhttps://example.com/image3.png",disabled:!1,rows:5,cols:80}},{title:"Add File using URL"})}addUrlFileConfirmed(e){var t,i="";e&&e.url&&e.url.value?(i=e.url.value.trim(),t=this.addUrlFile(i),t=_via_image_id_list.indexOf(t),show_message("Added file at url ["+i+"]"),sidebar.update_img_fn_list(),buffer.showImage(t),user_input_default_cancel_handler()):""!==e.url_list.value&&import_files_url_from_csv(e.url_list.value).then(()=>console.log("csv"))}addUrlFile(e){if(""!==e){var t=_via_get_image_id(e,-1);if(!_via_img_metadata.hasOwnProperty(t))return t=project.addFile(e),_via_img_src[t]=_via_img_metadata[t].filename,sidebar.set_file_annotations_to_default_value(t),t}}fileLoadOnFail(e){var t=_via_image_id_list[e];_via_img_src[t]="",_via_image_load_error[e]=!0,sidebar.img_fn_list_ith_entry_error(e,!0)}fileLoadOnSuccess(e){_via_image_load_error[e]=!1,sidebar.img_fn_list_ith_entry_error(e,!1)}importAttributesFromFile(e){var t=e.target.files;for(let e=0;e<t.length;++e){var i=t[e];FileManager.loadTextFile(i,this.importAttributesFromJson)}}importAttributesFromJson(a){try{var _,s,n=JSON.parse(a);let e,t=0,i=0;for(e in n.file)this.attributes.file[e]=JSON.parse(JSON.stringify(n.file[e])),t+=1;for(e in n.region)this.attributes.region[e]=JSON.parse(JSON.stringify(n.region[e])),i+=1;(0<t||0<i)&&(_=Object.keys(this.attributes.file),(s=Object.keys(this.attributes.region)).length?(_via_attribute_being_updated="region",_via_current_attribute_id=s[0]):_.length&&(_via_attribute_being_updated="file",_via_current_attribute_id=_[0]),attribute_update_panel_set_active_button(),update_attributes_update_panel(),sidebar.annotation_editor_update_content()),Message.showInfo("Imported "+t+" file attributes and "+i+" region attributes")}catch(e){Message.showError("Failed to import attributes: ["+e+"]")}}}function clearViaData(){_via_image_id_list=[],_via_image_filename_list=[],_via_img_count=0,_via_img_metadata={},_via_img_fileref={},_via_img_src={},project.attributes={region:{},file:{}},buffer.emptyBuffer()}function import_img_metadata(e){for(var t in e)_via_img_metadata.hasOwnProperty(t)||(_via_img_metadata[t]=e[t],_via_image_id_list.push(t),_via_image_filename_list.push(e[t].filename),_via_img_count+=1)}let project=new Project;class Sidebar{constructor(){this.img_fn_list=$("#img_fn_list"),this.img_fn_list_regex=$("#img_fn_list_regex"),this.img_fn_list_regex.val(""),this.img_fn_list_regex.keyup(function(){this.img_fn_list_onregex()}.bind(this)),this.img_fn_list_preset_filters=$("#filelist_preset_filters_list"),this.img_fn_list_preset_filters.change(function(){this.img_fn_list_onpresetfilter_select()}.bind(this)),this.img_fn_list_html=[],this.init_img_fn_list(),this.aep=$("#annotation_editor_panel"),this.ae=$("#annotation_editor"),this.aec=$("#annotation_editor_content"),this.display_area=$("#display_area"),this.descending_order=!0,this.addEventlisteners()}addEventlisteners(){this.img_fn_list.on("contextmenu","li",function(e){e.preventDefault(),this.file_manager_contextmenu(e)}.bind(this)),this.display_area.on("contextmenu","div",function(e){e.preventDefault(),this.descending_order=!this.descending_order,this.annotation_editor_contextmenu(e)}.bind(this))}file_manager_contextmenu(i){let e=i.pageX,t=i.pageY;var a=this.aep.width(),_=this.aep.height(),s=this.display_area.width(),n=this.display_area.height(),r=this.display_area.offset().left,o=this.display_area.offset().top;e+a>r+s&&(e=r+s-a),t+_>o+n&&(t=o+n-_);let l=$('<ul class="list-group" id="contextmenu_delete_root" style="position: absolute; z-index: 100"></ul>');0<l.length&&$("#contextmenu_delete_root").remove(),l.css("left",e),l.css("top",t),l.append('<li class="list-group-item" id="contextmenu_delete">Delete</li>'),l.find("li").hover(function(){$(this).addClass("active")}),l.find("li").mouseleave(function(){$(this).removeClass("active")}),l.find("#contextmenu_delete").click(function(){var e=$(i.target).attr("title"),t=_via_image_filename_list.indexOf(e);project.removeFile(t),t===_via_img_count?0===_via_img_count?(buffer.imgLoaded=!1,show_home_panel()):buffer.showImage(t-1):buffer.showImage(t),_via_reload_img_fn_list_table=!0,sidebar.update_img_fn_list(),show_message("Removed file ["+e+"] from project"),user_input_default_cancel_handler(),l.remove(),this.update_img_fn_list()}.bind(this)),$(document).click(function(e){"contextmenu_delete"!==e.target.id&&l.remove()}),this.display_area.append(l)}annotation_editor_contextmenu(e){if(!$("#image_panel_container").hasClass("d-none")){var t=e.pageX,i=e.pageY,a=$("<table>",{style:"position: absolute; z-index: 100",class:"table table-sm table-borderless",id:"annotation_editor_contextmenu"}),_=(a.length&&$("#annotation_editor_contextmenu").remove(),a.css("width","auto"),a.css("left",t),a.css("top",i),a.addClass("position-absolute card p-1 m"),a.css({display:"inline-block",width:"auto",padding:"8px!important"}),$("<thead>"),$("<tbody>"));if(a.append(_),Object.keys(project.attributes.region).length&&project.attributes.region.constructor===Object){var s=drawing.isInsideAllRegion({x:e.offsetX,y:e.offsetY},this.descending_order,!0);if(0===s.length||void 0===s[0]||-1===s[0])return;for(let e=0;e<s.length;e++){var n=s[e];void 0!==n&&(n=this.annotation_editor_get_metadata_row_html(n,!0),_.append(n))}this.display_area.append(a),this.annotation_editor_update_content()}$(document).click(function(t){var e=$("#annotation_editor_contextmenu"),i=e.find("*");let a=!1;for(let e=0;e<i.length;e++)t.target===i[e]&&(a=!0);a||e.remove()}),drawing.updateCheckedLockHtml()}}init_img_fn_list(){this.img_fn_list_html=[],this.img_fn_list_html.push('<ul class="list-group">');for(let e=0;e<_via_image_filename_list.length;++e)this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(e));this.img_fn_list_html.push("</ul>"),this.img_fn_list.innerHTML=this.img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file()}img_fn_list_onregex(){var e=this.img_fn_list_regex.val(),t=this.img_fn_list_preset_filters;6===t.selectedIndex?this.filter_selected_region_attribute(e):(this.img_fn_list_generate_html(e),img_fn_list.innerHTML=this.img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file(),""===e&&(t.selectedIndex=0))}update_img_fn_list(){var e=this.img_fn_list_regex.val(),t=this.img_fn_list_preset_filters;if(""===e||null===e)if(0===t.selectedIndex){this.img_fn_list_html=[],_via_img_fn_list_img_index_list=[],this.img_fn_list_html.push('<ul class="list-group">');for(let e=0;e<_via_image_filename_list.length;++e)this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(e)),_via_img_fn_list_img_index_list.push(e);this.img_fn_list_html.push("</ul>"),this.img_fn_list.innerHTML=this.img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file()}else this.img_fn_list_onpresetfilter_select();else 6===t.selectedIndex?this.filter_selected_region_attribute(e):(this.img_fn_list_generate_html(e),img_fn_list.innerHTML=_via_img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file())}img_fn_list_onpresetfilter_select(){var i=this.img_fn_list_preset_filters.find(":selected").val();switch(i){case"all":document.getElementById("img_fn_list_regex").value="",this.img_fn_list_generate_html(),img_fn_list.innerHTML=this.img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file();break;case"regex":case"files_region_attribute":this.img_fn_list_onregex(),this.img_fn_list_regex.focus();break;default:this.img_fn_list_html=[],_via_img_fn_list_img_index_list=[],this.img_fn_list_html.push('<ul class="list-group">');let t;for(t=0;t<_via_image_filename_list.length;++t){var a=_via_image_id_list[t];let e=!1;switch(i){case"files_without_region":0===_via_img_metadata[a].regions.length&&(e=!0);break;case"files_missing_region_annotations":this.is_region_annotation_missing(a)&&(e=!0);break;case"files_missing_file_annotations":this.is_file_annotation_missing(a)&&(e=!0);break;case"files_error_loading":!0===_via_image_load_error[t]&&(e=!0)}e&&(this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(t)),_via_img_fn_list_img_index_list.push(t))}this.img_fn_list_html.push("</ul>"),img_fn_list.innerHTML=this.img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file()}}is_region_annotation_missing(e){let t,i;for(i=0;i<_via_img_metadata[e].regions.length;++i)for(t in project.attributes.region){if(!_via_img_metadata[e].regions[i].region_attributes.hasOwnProperty(t))return!0;if(""===_via_img_metadata[e].regions[i].region_attributes[t])return!0}return!1}is_file_annotation_missing(e){let t;for(t in project.attributes.file){if(!_via_img_metadata[e].file_attributes.hasOwnProperty(t))return!0;if(""===_via_img_metadata[e].file_attributes[t])return!0}return!1}img_fn_list_ith_entry_selected(e,t){t?this.img_fn_list_ith_entry_add_css_class(e,"active"):this.img_fn_list_ith_entry_remove_css_class(e,"active")}img_fn_list_ith_entry_error(e,t){t?this.img_fn_list_ith_entry_add_css_class(e,"error"):this.img_fn_list_ith_entry_remove_css_class(e,"error")}img_fn_list_ith_entry_add_css_class(e,t){e=document.getElementById("fl"+e);e&&!e.classList.contains(t)&&e.classList.add(t)}img_fn_list_ith_entry_remove_css_class(e,t){e=document.getElementById("fl"+e);e&&e.classList.contains(t)&&e.classList.remove(t)}img_fn_list_clear_all_style(){$("#img_fn_list li").removeClass("active")}img_fn_list_add_css_class(e){$("#img_fn_list li").removeClass(),$("#img_fn_list li").addClass(e)}img_fn_list_ith_entry_html(e){let t="",i=_via_image_filename_list[e],a=(is_url(i)&&(i=i.substring(0,4)+"..."+get_filename_from_url(i)),!1);return t+='<li  id="fl'+e+'"',_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?_via_image_grid_page_img_index_list.includes(e)&&(a=!0):e===_via_image_index&&(a=!0),a?t+=' class="list-group-item active"':t+=' class="list-group-item text-muted"',t+=' style="height:40px; overflow: hidden; white-space: nowrap; cursor: default;" onclick="jump_to_image('+e+')" title="'+_via_image_filename_list[e]+'">['+(e+1)+"] "+decodeURIComponent(i)+"</li>"}img_fn_list_generate_html(t){this.img_fn_list_html=[],_via_img_fn_list_img_index_list=[],this.img_fn_list_html.push('<ul class="list-group">');for(let e=0;e<_via_image_filename_list.length;++e)null!==_via_image_filename_list[e].match(t)&&(this.img_fn_list_html.push(this.img_fn_list_ith_entry_html(e)),_via_img_fn_list_img_index_list.push(e));this.img_fn_list_html.push("</ul>")}img_fn_list_scroll_to_current_file(){this.img_fn_list_scroll_to_file(_via_image_index)}img_fn_list_scroll_to_file(e){var t,i,a;_via_img_fn_list_img_index_list.includes(e)&&(e=document.getElementById("fl"+e),t=img_fn_list.clientHeight-20,i=img_fn_list.scrollTop,a=img_fn_list.scrollTop+t,e.offsetTop>i?e.offsetTop>a&&(img_fn_list.scrollTop=e.offsetTop):img_fn_list.scrollTop=e.offsetTop-t)}filter_selected_region_attribute(e){e=e.split("=");if(2===e.length&&""!==e[0]&&""!==e[1]){var a=e[0];let i=e[1];if('""'===i&&(i=""),_via_img_fn_list_html=[],_via_img_fn_list_img_index_list=[],_via_img_fn_list_html.push("<ul>"),project.attributes.region.hasOwnProperty(a)){let t;for(t=0;t<_via_image_filename_list.length;++t){var _=_via_image_id_list[t];let e;for(e=0;e<_via_img_metadata[_].regions.length;++e)if(_via_img_metadata[_].regions[e].region_attributes.hasOwnProperty(a)&&JSON.stringify(_via_img_metadata[_].regions[e].region_attributes[a])===JSON.stringify(i)){_via_img_fn_list_html.push(this.img_fn_list_ith_entry_html(t)),_via_img_fn_list_img_index_list.push(t);break}}}_via_img_fn_list_html.push("</ul>"),img_fn_list.innerHTML=_via_img_fn_list_html.join(""),this.img_fn_list_scroll_to_current_file()}}annotation_editor_show(){this.annotation_editor_remove(),this.aep.empty();var e,t=$("<table>",{id:"annotation_editor",class:"table table-sm"});_via_annotation_editor_mode===VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION?_via_settings.ui.image.on_image_annotation_editor_placement!==VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE&&drawing.isRegionSelected()&&Object.keys(project.attributes.region).length&&project.attributes.region.constructor===Object&&(t.addClass("position-absolute card p-1 m"),t.css({display:"inline-block",width:"auto"}),_via_settings.ui.image.on_image_annotation_editor_placement===VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION&&(e=this.annotation_editor_get_placement(drawing.userSelRegionId()),t.css({top:e.top,left:e.left})),this.display_area.append(t),this.annotation_editor_update_content()):(this.aep.append(t),this.ae=$("#annotation_editor"),this.annotation_editor_update_content(),drawing.isRegionSelected()&&(this.annotation_editor_scroll_to_row(drawing.userSelRegionId()),this.annotation_editor_highlight_row(drawing.userSelRegionId())))}annotation_editor_contextmenu_handler(e){e.stopPropagation(),e.preventDefault();let t=parseInt(e.target.dataset.regionId);var e=_via_img_metadata[_via_image_id].regions[t].region_attributes,i=new Menu;i.append(new MenuItem({label:"Delete",click:()=>{drawing.deleteRegion(t),this.annotation_editor_remove()}})),e.hasOwnProperty("type")&&"text"===e.type&&i.append(new MenuItem({label:"Edit Text",click:()=>{drawing.editRegionText(t),this.annotation_editor_remove()}})),i.popup(remote.getCurrentWindow())}annotation_editor_hide(){_via_annotation_editor_mode===VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION?this.annotation_editor_remove():this.annotation_editor_clear_row_highlight()}annotation_editor_toggle_on_image_editor(){_via_settings.ui.image.on_image_annotation_editor_placement===VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE?(_via_annotation_editor_mode=VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION,_via_settings.ui.image.on_image_annotation_editor_placement=VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION,this.annotation_editor_show(),show_message("Enabled on image annotation editor")):(_via_settings.ui.image.on_image_annotation_editor_placement=VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE,_via_annotation_editor_mode=VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS,this.annotation_editor_hide(),show_message("Disabled on image annotation editor"))}annotation_editor_update_content(){try{void 0!==this.ae&&(this.ae.empty(),this.annotation_editor_update_header_html(),this.annotation_editor_update_metadata_html(),drawing.updateCheckedLockHtml())}catch(e){console.log(e),Message.show({address:"Warning",body:"Cannot update annotation editor content",color:"#e8f800"})}}annotation_editor_get_placement(e){var t={};let i=_via_canvas_regions[e].shape_attributes;switch(i.name){case"rect":t.top=i.y+i.height,t.left=i.x+i.width;break;case"circle":t.top=i.cy+i.r,t.left=i.cx;break;case"ellipse":t.top=i.cy+i.ry*Math.cos(i.theta),t.left=i.cx-i.ry*Math.sin(i.theta);break;case"polygon":case"polyline":var a=Object.keys(i.all_points_x).reduce(function(e,t){return i.all_points_x[e]>i.all_points_x[t]?e:t});t.top=Math.max(i.all_points_y[a]),t.left=Math.max(i.all_points_x[a]);break;case"point":t.top=i.cy,t.left=i.cx}return t.top=t.top+image_panel.offsetTop,t.left=t.left+image_panel.offsetLeft,t}annotation_editor_remove(){void 0!==this.ae&&this.ae.remove()}is_annotation_editor_visible(){return void 0!==this.aep}annotation_editor_toggle_all_regions_editor(){_via_annotation_editor_mode=VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS,this.aep.css("font-size",_via_settings.ui.annotation_editor_fontsize+"rem"),this.annotation_editor_show()}annotation_editor_set_active_button(){let e;for(e in project.attributes){var t="button_edit_"+e+"_metadata";$("#"+t).removeClass("active")}var i="button_edit_"+_via_metadata_being_updated+"_metadata";$("#"+i).addClass("active")}edit_region_metadata_in_annotation_editor(){_via_metadata_being_updated="region",this.annotation_editor_set_active_button(),this.annotation_editor_update_content()}edit_file_metadata_in_annotation_editor(){_via_metadata_being_updated="file",this.annotation_editor_set_active_button(),this.annotation_editor_update_content()}annotation_editor_update_header_html(e=!1){var t,i=$("<thead>",{id:"annotation_editor_header"}),a=$("<tr>");"region"===_via_metadata_being_updated&&(t=$("<th>",{scope:"col",html:"#"}),a.append(t)),"file"===_via_metadata_being_updated&&(t=$("<th>",{scope:"col"}),_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?t.html("group"):t.html("file"),a.append(t));let _;for(_ in project.attributes[_via_metadata_being_updated])$("<th>",{scope:"col",html:_}).appendTo(a);if("region"===_via_metadata_being_updated&&($("<th>",{scope:"col",html:'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lock-fill" viewBox="0 0 16 16">\n  <path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2m3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2"/>\n</svg>'}).appendTo(a),settings.attributeShowScore&&$("<th>",{scope:"col",html:'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calculator-fill" viewBox="0 0 16 16">\n  <path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm2 .5v2a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5h-7a.5.5 0 0 0-.5.5m0 4v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5M4.5 9a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM4 12.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5M7.5 6a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM7 9.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5m.5 2.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM10 6.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5m.5 2.5a.5.5 0 0 0-.5.5v4a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 0-.5-.5z"/>\n</svg>'}).appendTo(a),settings.attributeShowPixelArea)&&$("<th>",{scope:"col",html:'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrows-angle-expand" viewBox="0 0 16 16">\n  <path fill-rule="evenodd" d="M5.828 10.172a.5.5 0 0 0-.707 0l-4.096 4.096V11.5a.5.5 0 0 0-1 0v3.975a.5.5 0 0 0 .5.5H4.5a.5.5 0 0 0 0-1H1.732l4.096-4.096a.5.5 0 0 0 0-.707m4.344-4.344a.5.5 0 0 0 .707 0l4.096-4.096V4.5a.5.5 0 1 0 1 0V.525a.5.5 0 0 0-.5-.5H11.5a.5.5 0 0 0 0 1h2.768l-4.096 4.096a.5.5 0 0 0 0 .707"/>\n</svg>'}).appendTo(a),i.append(a),e)return i;0===this.ae.length?this.ae.append(i):"annotation_editor_header"===this.ae.first().attr("id")?this.ae.first().replaceWith(i):this.ae.prepend(i)}annotation_editor_update_metadata_html(){if(_via_img_count)switch(this.ae.has("tbody").length||(this.ae.append('<tbody id="annotation_editor_content"></tbody>'),this.aec=$("#annotation_editor_content")),_via_metadata_being_updated){case"region":let e;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_img_metadata[_via_image_id].isGroupable()){var t=[];for(e=0;e<_via_img_metadata[_via_image_id].groupedRegions.groupBy.length;++e)if(_via_img_metadata[_via_image_id].isGroupedKey(_via_img_metadata[_via_image_id].groupedRegions.groupBy[e])){$("<tr>",{id:"ae_row_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e],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[e]+"_rid",html:"O"}).appendTo("#ae_row_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]),(_via_img_metadata[_via_image_id].groupedRegions.groupIDs.has(_via_img_metadata[_via_image_id].groupedRegions.groupBy[e])?$("<td> ",{id:"ae_row_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]+"_group",colspan:4,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[e])+"' aria-label='group identifier' id='group_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]+"'>"}):$("<td> ",{id:"ae_row_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]+"_group",colspan:4,html:"<input class='form-control form-control-sm' onchange='plugin.changeGroupIdentifier(this)' type='text' placeholder='Group: "+(e+1)+"' aria-label='group identifier' id='group_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]+"'>"})).appendTo("#ae_row_"+_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]),this.aec.append(this.annotation_editor_get_metadata_row_html(_via_img_metadata[_via_image_id].groupedRegions.groupBy[e])),t.push(_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]);for(var i of _via_img_metadata[_via_image_id].getGroupedRegion(_via_img_metadata[_via_image_id].groupedRegions.groupBy[e]))this.aec.append(this.annotation_editor_get_metadata_row_html(i)),t.push(i)}for($("<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:4,html:"Other"}).appendTo("#ae_no_group"),e=0;e<_via_img_metadata[_via_image_id].regions.length;++e)t.includes(e)||this.aec.append(this.annotation_editor_get_metadata_row_html(e))}else for(e=0;e<_via_img_metadata[_via_image_id].regions.length;++e)this.aec.append(this.annotation_editor_get_metadata_row_html(e));break;case"file":this.aec.append(this.annotation_editor_get_metadata_row_html(0))}}annotation_editor_update_row(e){this.ae.has("tbody").length||(this.ae.append('<tbody id="annotation_editor_content"></tbody>'),this.aec=$("#annotation_editor_content"));var t=this.annotation_editor_get_metadata_row_html(e).attr("id");this.aec.find(t).replaceWith(this.annotation_editor_get_metadata_row_html(e))}annotation_editor_add_row(e){var t;this.is_annotation_editor_visible()&&(this.ae.has("tbody").length||(this.ae.append('<tbody id="annotation_editor_content"></tbody>'),this.aec=$("#annotation_editor_content")),t=this.annotation_editor_get_metadata_row_html(e),0<=(e=parseInt(e)-1)?(e="ae_"+_via_metadata_being_updated+"_"+e,$("#"+e).after(t)):this.aec.append(t))}annotation_editor_get_metadata_row_html(i,t=!1,a=!1){let e="ae_"+_via_metadata_being_updated+"_"+i;t&&(e="ae_cm_"+_via_metadata_being_updated+"_"+i),a&&(e="ae_example_"+_via_metadata_being_updated+"_"+i);var _,s,n,r,o=$("<tr>",{id:e,class:"align-middle"});if("region"===_via_metadata_being_updated&&_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE&&settings.attributeShowScoreColor&&void 0!==_via_img_metadata[_via_image_id].regions[i]&&_via_img_metadata[_via_image_id].regions[i].hasOwnProperty("score")&&((_=_via_img_metadata[_via_image_id].regions[i].score)>settings.scoreThreshold?o.css("background-color",settings.scoreHigherColor):_<0?o.removeAttr("style"):o.css("background-color",settings.scoreLowerColor)),"region"===_via_metadata_being_updated){var l=$("<th>",{scope:"row"});switch(_via_display_area_content_name){case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:l.html("Grouped regions in "+_via_image_grid_selected_img_index_list.length+" files");break;case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:l.html(i+1),l.attr("id","0__"+i),l.attr("onclick","sidebar.annotation_editor_on_metadata_focus(this)");break;default:l.html(i+1),l.attr("id","0__"+i)}o.append(l)}if("file"===_via_metadata_being_updated){var g=$("<th>",{scope:"row"});switch(_via_display_area_content_name){case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:g.html("Group of "+_via_image_grid_selected_img_index_list.length+" files");break;case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:g.html(_via_image_filename_list[_via_image_index]);break;default:g.html("file_example.png")}o.append(g)}let d;for(d in project.attributes[_via_metadata_being_updated]){var c=$("<td>"),u=project.attributes[_via_metadata_being_updated][d].type;let e="";project.attributes[_via_metadata_being_updated][d].hasOwnProperty("desc")&&(e=project.attributes[_via_metadata_being_updated][d].desc);var h,m=d+"__"+i;let _="",t="";if(_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE)switch(_via_metadata_being_updated){case"region":_via_img_metadata[_via_image_id].regions[i].region_attributes.hasOwnProperty(d)?_=_via_img_metadata[_via_image_id].regions[i].region_attributes[d]:t="not defined yet!";break;case"file":_via_img_metadata[_via_image_id].file_attributes.hasOwnProperty(d)?_=_via_img_metadata[_via_image_id].file_attributes[d]:t="not defined yet!"}if(_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID){let a;switch(_via_metadata_being_updated){case"region":a=this._via_get_region_metadata_stat(_via_image_grid_selected_img_index_list,d);break;case"file":a=this._via_get_file_metadata_stat(_via_image_grid_selected_img_index_list,d)}switch(u){case"text":if(a.hasOwnProperty(d)){var p=Object.keys(a[d]);if(p.includes("undefined"))_="",t="includes "+a[d][void 0]+" undefined values";else switch(p.length){case 0:_="",t="not applicable";break;case 1:_=p[0],t="";break;default:_="",t=p.length+" different values: "+JSON.stringify(p).replace(/"/g,"'")}}else _="",t="not defined yet!";break;case"radio":case"dropdown":case"image":_=a.hasOwnProperty(d)&&1===(h=Object.keys(a[d])).length?h[0]:"";break;case"checkbox":if(_={},a.hasOwnProperty(d)){var v,f,b=Object.keys(a[d]);let e=!0,t,i;for(i=b.length,t=0;t<i-1;++t)if(v=b[t],f=b[t+1],a[d][v]!==a[d][f]){e=!1;break}if(e){let e;for(e in a[d])_[e]=!0}}}}switch(u){case"text":c.attr("id",m),c.html('<textarea title="'+e+'" placeholder="'+t+'" onchange="sidebar.annotation_editor_on_metadata_update(this); return false;" onfocus="sidebar.annotation_editor_on_metadata_focus(this); return false;">'+_+"</textarea>"),a||c.attr("id",m);break;case"checkbox":{let t;for(t in project.attributes[_via_metadata_being_updated][d].options){var y=m+"__"+t,w=$("<input>",{value:t,class:"form-check-input",type:"checkbox"});a||(w.attr("id",y),w.attr("onfocus","sidebar.annotation_editor_on_metadata_focus(this); return false;"),w.attr("onchange","sidebar.annotation_editor_on_metadata_update(this); return false;"));let e=project.attributes[_via_metadata_being_updated][d].options[t];""!==e&&void 0!==e||(e=t),void 0!==_&&_.hasOwnProperty(t)&&w.prop("checked",_[t]);var I=$("<label>"),y=(I.attr("for",y),I.html(e),$("<div>",{class:"row"})),A=$("<div>",{class:"col"});A.append(w),A.append(I),y.append(A),c.append(y)}break}case"radio":{let t;for(t in project.attributes[_via_metadata_being_updated][d].options){var x=m+"__"+t,E=$("<input>",{type:"radio",name:m,class:"form-check-input",value:t});a||(E.attr("id",x),E.attr("onchange","sidebar.annotation_editor_on_metadata_update(this); return false;"),E.attr("oncfocus","sidebar.annotation_editor_on_metadata_focus(this); return false;"));let e=project.attributes[_via_metadata_being_updated][d].options[t];""!==e&&void 0!==e||(e=t),_===t&&E.prop("checked",!0);var x=$("<label>",{for:x,html:e}),k=$("<div>",{class:"row"}),R=$("<div>",{class:"col"});R.append(E),R.append(x),k.append(R),c.append(k)}break}case"image":{let t,e=0;for(t in project.attributes[_via_metadata_being_updated][d].options)e+=1;var j=$("<div>",{class:"image_options"});for(t in c.append(j),project.attributes[_via_metadata_being_updated][d].options){var P=m+"__"+t,O=$("<input>",{type:"radio",name:m,class:"form-check-input",value:t});a||(O.attr("id",P),O.attr("onchange","sidebar.annotation_editor_on_metadata_update(this); return false;"),O.attr("onfocus","sidebar.annotation_editor_on_metadata_focus(this); return false;"));let e=project.attributes[_via_metadata_being_updated][d].options[t];""!==e&&void 0!==e||(e=t),_===t&&O.prop("checked",!0);var P=$("<label>",{for:P}),G=(P.html('<img src="'+e+'" alt=""><p>'+t+"</p>"),$("<div>",{class:"row"})),S=$("<div>",{class:"col"});S.append(O),S.append(P),G.append(S),j.append(G)}break}case"dropdown":{var M=$("<select>");a||(M.attr("id",m),M.attr("onfocus","sidebar.annotation_editor_on_metadata_focus(this); return false;"),M.attr("onchange","sidebar.annotation_editor_on_metadata_update(this); return false;")),M.addClass("form-select form-select-sm");let t,i=!1;for(t in project.attributes[_via_metadata_being_updated][d].options){var N=$("<option>",{value:t});let e=project.attributes[_via_metadata_being_updated][d].options[t];""!==e&&void 0!==e||(e=t),t===_&&(N.attr("selected","selected"),i=!0),N.html(e),M.append(N)}i||(M.selectedIndex=-1),c.append(M);break}}o.append(c)}try{if(a)return s=$("<td>").html($("<input>",{type:"checkbox",class:"form-check-input"})),o.append(s),settings.attributeShowScore&&(n=$("<td>").html("n/a"),o.append(n)),settings.attributeShowPixelArea&&(r=$("<td>").html("100"),o.append(r)),o;if("region"===_via_metadata_being_updated&&void 0!==_via_img_metadata[_via_image_id].regions[i]){var D=$("<td>");let e="lock_"+i;t&&(e="lock_"+i+"_context");var T,L=$("<input>",{type:"checkbox",class:"form-check-input"}),C=(L.attr("id",e),L.attr("onchange","drawing.lockRegionHandler(this); return false;"),D.append(L),o.append(D),_via_img_metadata[_via_image_id].regions[i]);if(settings.attributeShowScore&&(T=$("<td>").html("n/a"),C.hasOwnProperty("score")&&!isNaN(C.score)&&T.html(Math.round(100*C.score)/100),o.append(T)),settings.attributeShowPixelArea){let e;e="polygon"===C.shape_attributes.name?plugin.calcPolygonArea(C.shape_attributes.all_points_x,C.shape_attributes.all_points_y):'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.46457 14.1213C8.07404 14.5118 8.07404 15.145 8.46457 15.5355C8.85509 15.926 9.48825 15.926 9.87878 15.5355L15.5356 9.87862C15.9262 9.4881 15.9262 8.85493 15.5356 8.46441C15.1451 8.07388 14.5119 8.07388 14.1214 8.46441L8.46457 14.1213Z" fill="currentColor" /><path fill-rule="evenodd" clip-rule="evenodd" d="M6.34315 17.6569C9.46734 20.781 14.5327 20.781 17.6569 17.6569C20.781 14.5327 20.781 9.46734 17.6569 6.34315C14.5327 3.21895 9.46734 3.21895 6.34315 6.34315C3.21895 9.46734 3.21895 14.5327 6.34315 17.6569ZM16.2426 16.2426C13.8995 18.5858 10.1005 18.5858 7.75736 16.2426C5.41421 13.8995 5.41421 10.1005 7.75736 7.75736C10.1005 5.41421 13.8995 5.41421 16.2426 7.75736C18.5858 10.1005 18.5858 13.8995 16.2426 16.2426Z" fill="currentColor" /></svg>';var V=$("<td>").html(e);o.append(V)}}drawing.updateCheckedLockHtml()}catch(e){console.log("Ok, no problem, just no lock for this region :D")}return o}annotation_editor_scroll_to_row(e){this.is_annotation_editor_visible()&&settings.is_scrolling&&("file"===_via_metadata_being_updated&&(e=0),e="ae_"+_via_metadata_being_updated+"_"+e,document.getElementById(e).scrollIntoView(!1))}annotation_editor_highlight_row(e){this.is_annotation_editor_visible()&&(e="ae_"+_via_metadata_being_updated+"_"+e,$("#"+e).addClass("table-active"))}annotation_editor_clear_row_highlight(){var e;this.is_annotation_editor_visible()&&(e=$("#annotation_editor_content"),$(".table-active",e).removeClass("table-active"))}annotation_editor_extract_html_id_components(e){var t=e.split("__"),i={};switch(t.length){case 3:i.attr_id=t[0],i.row_id=t[1],i.option_id=t[2];break;case 2:i.attr_id=t[0],i.row_id=t[1]}return i}_via_get_file_metadata_stat(e,t){var i={};i[t]={};let a,_,s,n,r;for(_=e.length,a=0;a<_;++a)if(n=e[a],s=_via_image_id_list[n],_via_img_metadata[s].file_attributes.hasOwnProperty(t))if("object"==typeof(r=_via_img_metadata[s].file_attributes[t])){let e;for(e in r)i[t].hasOwnProperty(e)?i[t][e]+=1:i[t][e]=1}else i[t].hasOwnProperty(r)?i[t][r]+=1:i[t][r]=1;return i}_via_get_region_metadata_stat(e,t){var i={};i[t]={};let a,_,s,n,r,o,l;for(_=e.length,a=0;a<_;++a)for(n=e[a],s=_via_image_id_list[n],l=_via_img_metadata[s].regions.length,o=0;o<l;++o)if(image_grid_is_region_in_current_group(_via_img_metadata[s].regions[o].region_attributes))if("object"==typeof(r=_via_img_metadata[s].regions[o].region_attributes[t])){let e;for(e in r)i[t].hasOwnProperty(e)?i[t][e]+=1:i[t][e]=1}else i[t].hasOwnProperty(r)?i[t][r]+=1:i[t][r]=1;return i}annotation_editor_on_metadata_focus(e){_via_annotation_editor_mode===VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS&&(e=this.annotation_editor_extract_html_id_components(e.id).row_id,toggle_all_regions_selection(!1),this.annotation_editor_clear_row_highlight(),set_region_select_state(e,!0),this.annotation_editor_highlight_row(e),drawing.setIsRegionSelected(!0),drawing.setUserSelRegionId(e),drawing.redrawRegCanvas())}annotation_editor_on_metadata_update(e){var t,i=this.annotation_editor_extract_html_id_components(e.id);let a=[_via_image_index],_=i.row_id;_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&(a=_via_image_grid_selected_img_index_list.slice(0),_=-1),"file"===_via_metadata_being_updated?(t=this.annotation_editor_update_file_metadata(a,i.attr_id,e.value,e.checked),this.annotation_editor_on_metadata_update_done("file",i.attr_id,t)):"region"===_via_metadata_being_updated&&(t=this.annotation_editor_update_region_metadata(a,_,i.attr_id,e.value,e.checked),this.annotation_editor_on_metadata_update_done("region",i.attr_id,t))}annotation_editor_on_metadata_update_done(e,t,i){show_message("Updated "+e+" attributes of "+i+" "+e+"s");let a,_,s=(_=_via_image_grid_group_var.length,!1);for(a=0;a<_;++a)if(_via_image_grid_group_var[a].type===e&&_via_image_grid_group_var[a].name===t){s=!0;break}drawing.regionsGroupColorInit(),drawing.redrawRegCanvas(),_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&s&&image_grid_show_all_project_images()}annotation_editor_update_file_metadata(s,n,r,o){try{let e,t,i,a,_=(t=s.length,0);for(e=0;e<t;++e)switch(a=s[e],i=_via_image_id_list[a],project.attributes.file[n].type){case"text":case"radio":case"dropdown":case"image":_via_img_metadata[i].file_attributes[n]=r,_+=1;break;case"checkbox":var l,g=r;_via_img_metadata[i].file_attributes.hasOwnProperty(n)?"object"!=typeof _via_img_metadata[i].file_attributes[n]&&(l=_via_img_metadata[i].file_attributes[n],_via_img_metadata[i].file_attributes[n]={},Object.keys(project.attributes.file[n].options).includes(l))&&(_via_img_metadata[i].file_attributes[n]={},_via_img_metadata[i].file_attributes[n][l]=!0):_via_img_metadata[i].file_attributes[n]={},o?_via_img_metadata[i].file_attributes[n][g]=!0:delete _via_img_metadata[i].file_attributes[n][g],_+=1}return _}catch(e){console.log(e),Message.show({address:"Error",body:"Problem with update file metadata",color:"#cc1100"})}}annotation_editor_update_region_metadata(r,o,l,g,d){try{let e,t,i,a,_=(t=r.length,0),s,n;if(-1===o){for(e=0;e<t;++e)for(a=r[e],i=_via_image_id_list[a],n=_via_img_metadata[i].regions.length,s=0;s<n;++s)if(image_grid_is_region_in_current_group(_via_img_metadata[i].regions[s].region_attributes))switch(project.attributes.region[l].type){case"text":case"dropdown":case"radio":case"image":_via_img_metadata[i].regions[s].region_attributes[l]=g,_+=1;break;case"checkbox":var c,u=g;_via_img_metadata[i].regions[s].region_attributes.hasOwnProperty(l)?"object"!=typeof _via_img_metadata[i].regions[s].region_attributes[l]&&(c=_via_img_metadata[i].regions[s].region_attributes[l],_via_img_metadata[i].regions[s].region_attributes[l]={},Object.keys(project.attributes.region[l].options).includes(c))&&(_via_img_metadata[i].regions[s].region_attributes[l][c]=!0):_via_img_metadata[i].regions[s].region_attributes[l]={},d?_via_img_metadata[i].regions[s].region_attributes[l][u]=!0:delete _via_img_metadata[i].regions[s].region_attributes[l][u],_+=1}}else for(e=0;e<t;++e)switch(a=r[e],i=_via_image_id_list[a],project.attributes.region[l].type){case"text":case"dropdown":case"radio":case"image":_via_img_metadata[i].regions[o].region_attributes[l]=g,_+=1;break;case"checkbox":var h,m=g;_via_img_metadata[i].regions[o].region_attributes.hasOwnProperty(l)?"object"!=typeof _via_img_metadata[i].regions[o].region_attributes[l]&&(h=_via_img_metadata[i].regions[o].region_attributes[l],_via_img_metadata[i].regions[o].region_attributes[l]={},Object.keys(project.attributes.region[l].options).includes(h))&&(_via_img_metadata[i].regions[o].region_attributes[l][h]=!0):_via_img_metadata[i].regions[o].region_attributes[l]={},d?_via_img_metadata[i].regions[o].region_attributes[l][m]=!0:delete _via_img_metadata[i].regions[o].region_attributes[l][m],_+=1}return _}catch(e){console.log(e),Message.show({address:"Error",body:"Problem with update region metadata",color:"#cc1100"})}}set_region_annotations_to_default_value(t){let i;for(i in project.attributes.region)switch(project.attributes.region[i].type){case"text":var e=project.attributes.region[i].default_value;void 0!==e&&(_via_img_metadata[_via_image_id].regions[t].region_attributes[i]=e);break;case"image":case"dropdown":case"radio":_via_img_metadata[_via_image_id].regions[t].region_attributes[i]="";e=project.attributes.region[i].default_options;void 0!==e&&(_via_img_metadata[_via_image_id].regions[t].region_attributes[i]=Object.keys(e)[0]);break;case"checkbox":_via_img_metadata[_via_image_id].regions[t].region_attributes[i]={};var a=project.attributes.region[i].default_options;if(void 0!==a){let e;for(e in a){var _=a[e];void 0!==_&&(_via_img_metadata[_via_image_id].regions[t].region_attributes[i][e]=_)}}}}set_file_annotations_to_default_value(t){let i;for(i in project.attributes.file)switch(project.attributes.file[i].type){case"text":_via_img_metadata[t].file_attributes[i]=project.attributes.file[i].default_value;break;case"image":case"dropdown":case"radio":_via_img_metadata[t].file_attributes[i]="";var e=project.attributes.file[i].default_options;_via_img_metadata[t].file_attributes[i]=Object.keys(e)[0];break;case"checkbox":{_via_img_metadata[t].file_attributes[i]={};var a=project.attributes.file[i].default_options;let e;for(e in a)_via_img_metadata[t].file_attributes[i][e]=a[e];break}}}toggleAEMode(e=!1){"region"===_via_metadata_being_updated?(this.edit_file_metadata_in_annotation_editor(),$("#sidebar_ToggleAEModes").prop("disabled",e),$("#sidebar_ToggleAEModes").html("Files")):(this.edit_region_metadata_in_annotation_editor(),e?$("#sidebar_ToggleAEModes").prop("disabled",!0):$("#sidebar_ToggleAEModes").prop("disabled",!1),$("#sidebar_ToggleAEModes").html("Regions"))}}let sidebar=new Sidebar;class Drawer{constructor(){this.ctx=_via_reg_canvas.getContext("2d"),this.initMouseHandlers(),this.click0={x:0,y:0},this.click1={x:0,y:0},this.current_point={x:0,y:0},this.region_click={x:0,y:0},this.theme_control_point_color="#ff0000",this.selected_region_boundary_line_color="#ffc300",this.selected_region_fill_color="#808080",this.region_boundary_line_color="#000000",this.region_fill_color="#ffc300",this.region_color_list=["#e69f00","#56b4e9","#009e73","#d55e00","#cc79a7","#f0e442","#ffffff"],this.canvas_regions_group_color={},this.region_edge=[-1,-1],this.polygon_vertex_match_tol=10,this.ellipse_edge_tol=.2,this.theta_tol=Math.PI,this.region_edge_tol=5,this.mouse_click_tol=2,this.current_shape=VIA_REGION_SHAPE.EDIT,this.trim_points={},this.trim_region_id=-1,this.trim_line={},this.trim_choose_points=[],this.trim_choose_points_id=[],this.trim_phase_id=0,this.is_user_moving_region=!1,this.is_user_drawing_region=!1,this.is_user_triming_region=!1,this.is_region_selected=!1,this.is_user_drawing_polygon=!1,this.is_user_resizing_region=!1,this.is_window_resized=!1,this.user_sel_region_id=-1,this.current_polygon_region_id=-1,this.polygon_resize_vertex_offset=100,this.theme_region_boundary_width=this.setBoundarySize(),this.region_shapes_points_radius=3,this.theme_sel_region_opacity=.5,this.region_point_radius=3,this.region_point_radius_default=3,this.current_browser=this.getBrowser()}setFontSize(e){var t=parseInt(window.getComputedStyle(document.body).getPropertyValue("font-size"));this.ctx.font="bold "+(t+=e)+"px Arial"}setBoundarySize(e=3){this.theme_region_boundary_width=e}getBrowser(){let e=navigator.userAgent,t,i=e.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i)||[];return/trident/i.test(i[1])?"IE "+((t=/\brv[ :]+(\d+)/g.exec(e)||[])[1]||""):"Chrome"===i[1]&&null!=(t=e.match(/\bOPR\/(\d+)/))?"Opera "+t[1]:(i=i[2]?[i[1],i[2]]:[navigator.appName,navigator.appVersion,"-?"],null!=(t=e.match(/version\/(\d+)/i))&&i.splice(1,1,t[1]),i[0])}setCurrentShape(e){this.current_shape=e}currentShape(){return this.current_shape}setIsUserDrawingPolygon(e){this.is_user_drawing_polygon=e}isUserDrawingPolygon(){return this.is_user_drawing_polygon}setCurrentPolygonRegionId(e){this.current_polygon_region_id=e}currentPolygonRegionId(){return this.current_polygon_region_id}setIsRegionSelected(e){this.is_region_selected=e}isRegionSelected(){return this.is_region_selected}setIsUserResizingRegion(e){this.is_user_resizing_region=e}isUserResizingRegion(){return this.is_user_resizing_region}userSelRegionId(){return this.user_sel_region_id}setUserSelRegionId(e){this.user_sel_region_id=e}isUserDrawingRegion(){return this.is_user_drawing_region}setIsUserDrawingRegion(e){this.is_user_drawing_region=e}isUserMovingRegion(){return this.is_user_moving_region}setIsUserMovingRegion(e){this.is_user_moving_region=e}regionEdgeTol(){return this.region_edge_tol}lineStyle(e=this.theme_region_boundary_width/2,t=this.region_fill_color){this.ctx.strokeStyle=t,this.ctx.lineWidth=e}beginFill(e=this.selected_region_fill_color,t=this.theme_sel_region_opacity){this.ctx.fillStyle=e,this.ctx.globalAlpha=t}regionsGroupColorInit(){this.canvas_regions_group_color={};var i=_via_settings.ui.image.region_color;if("__via_default_region_color__"!==i){let e;for(var a of _via_img_metadata[_via_image_id].regions)a.region_attributes.hasOwnProperty(i)&&(e=a.region_attributes[i],this.canvas_regions_group_color.hasOwnProperty(e)||(this.canvas_regions_group_color[e]=this.region_color_list[Object.keys(this.canvas_regions_group_color).length]));let t=0;for(e in this.canvas_regions_group_color)this.canvas_regions_group_color[e]=this.region_color_list[t%this.region_color_list.length],t+=1}}initMouseHandlers(){_via_reg_canvas.addEventListener("pointerdown",this.canvasMouseDownHandler.bind(this),!1),_via_reg_canvas.addEventListener("pointerup",this.canvasMouseUpHandler.bind(this),!1),_via_reg_canvas.addEventListener("pointermove",this.canvasMouseMoveHandler.bind(this),!1),_via_reg_canvas.addEventListener("pointerover",this.canvasMouseOverHandler.bind(this),!1),_via_reg_canvas.addEventListener("contextmenu",this.canvasContextMenuHandler.bind(this),!1)}canvasMouseDownHandler(e){e.stopPropagation(),zoom.handleMoseDown(e),this.current_shape===VIA_REGION_SHAPE.DRAG||is_alt_pressed||2===e.button||(this.current_shape===VIA_REGION_SHAPE.TRIM?this.is_user_triming_region=!0:(this.is_user_triming_region=!1,this.destroyTrim()),this.click0.x=e.offsetX,this.click0.y=e.offsetY,1===this.trim_phase_id?(this.trim_line.x0=e.offsetX,this.trim_line.y0=e.offsetY):4!==this.trim_phase_id&&(this.region_edge=this.isOnRegionCorner(this.click0),e=this.isInsideRegion(this.click0),this.is_region_selected?0<this.region_edge[1]?this.is_user_resizing_region||(this.region_edge[0]!==this.user_sel_region_id&&(this.user_sel_region_id=this.region_edge[0]),this.is_user_resizing_region=!0):(this.isInsideThisRegion(this.click0,this.user_sel_region_id)&&!this.is_user_moving_region&&(this.is_user_moving_region=!0,this.region_click.x=this.click0.x,this.region_click.y=this.click0.y),-1===e&&(this.is_user_drawing_region=!0,this.is_region_selected=!1,toggle_all_regions_selection(!(this.user_sel_region_id=-1)))):(-1!==e||this.current_shape!==VIA_REGION_SHAPE.POLYGON&&this.current_shape!==VIA_REGION_SHAPE.POLYLINE&&this.current_shape!==VIA_REGION_SHAPE.POINT)&&(this.is_user_drawing_region=!0)))}canvasMouseUpHandler(e){if(e.stopPropagation(),zoom.handleMoseUp(e),this.current_shape!==VIA_REGION_SHAPE.DRAG&&!is_alt_pressed&&2!==e.button)if(this.click1.x=e.offsetX,this.click1.y=e.offsetY,1===this.trim_phase_id)this.trim_line.x1=e.offsetX,this.trim_line.y1=e.offsetY,this.trim_phase_id=2,this.calculateTrimPoints();else{4===this.trim_phase_id&&(this.isInsideTrimPolygon(this.click1,this.trim_points.polygon1)&&this.trimFromMetadata(this.trim_points.polygon1),this.isInsideTrimPolygon(this.click1,this.trim_points.polygon2))&&this.trimFromMetadata(this.trim_points.polygon2);var a=Math.abs(this.click1.x-this.click0.x),t=Math.abs(this.click1.y-this.click0.y);if(this.is_user_moving_region){this.is_user_moving_region=!1,_via_reg_canvas.style.cursor="default";var _=Math.round(this.click1.x-this.region_click.x),s=Math.round(this.click1.y-this.region_click.y);if(Math.abs(_)>this.mouse_click_tol||Math.abs(s)>this.mouse_click_tol)this.moveSelectedRegions(_,s);else{_=this.isInsideRegion(this.click0,!0);if(0<=_&&_!==this.user_sel_region_id)this.user_sel_region_id=_,this.is_region_selected=!0,this.is_user_moving_region=!1,this.is_user_triming_region&&this.trimRegionSection(_),e.shiftKey||toggle_all_regions_selection(!1),set_region_select_state(_,!0),sidebar.annotation_editor_show();else{switch(toggle_all_regions_selection(!1),this.is_region_selected=!1,this.current_shape){case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:this.is_user_drawing_polygon=!0;var i=new File_Region,i=(i.shape_attributes.name=this.current_shape,i.shape_attributes.all_points_x=[Math.round(this.click0.x)],i.shape_attributes.all_points_y=[Math.round(this.click0.y)],_via_canvas_regions.push(i));this.current_polygon_region_id=i-1;break;case VIA_REGION_SHAPE.POINT:i=new File_Region,i=(i.shape_attributes.name=VIA_REGION_SHAPE.POINT,i.shape_attributes.cx=Math.round(this.click0.x*_via_canvas_scale),i.shape_attributes.cy=Math.round(this.click0.y*_via_canvas_scale),_via_img_metadata[_via_image_id].regions.push(i),new File_Region);i.shape_attributes.name=VIA_REGION_SHAPE.POINT,i.shape_attributes.cx=Math.round(this.click0.x),i.shape_attributes.cy=Math.round(this.click0.y),_via_canvas_regions.push(i)}sidebar.annotation_editor_update_content()}}this.redrawRegCanvas(),_via_reg_canvas.focus()}else if(this.is_user_resizing_region){this.is_user_resizing_region=!1,_via_reg_canvas.style.cursor="default";var n=this.region_edge[0],r=_via_img_metadata[_via_image_id].regions[n].shape_attributes,o=_via_canvas_regions[n].shape_attributes;if(_via_img_metadata[_via_image_id].regions[n].hasOwnProperty("score")&&(_via_img_metadata[_via_image_id].regions[n].score="n/a",plugin.updateScoresBasedOnGroup()),!this.is_user_triming_region)switch(o.name){case VIA_REGION_SHAPE.RECT:{var l=[o.x,o.y,0,0],g=(l[2]=l[0]+o.width,l[3]=l[1]+o.height,this.current_point.x),d=this.current_point.y;let e=!1;_via_is_ctrl_pressed&&(e=!0),this.rectUpdateCorner(this.region_edge[1],l,g,d,e),this.rectStandardizeCoordinates(l);g=Math.abs(l[2]-l[0]),d=Math.abs(l[3]-l[1]);r.x=Math.round(l[0]*_via_canvas_scale),r.y=Math.round(l[1]*_via_canvas_scale),r.width=Math.round(g*_via_canvas_scale),r.height=Math.round(d*_via_canvas_scale),o.x=Math.round(r.x/_via_canvas_scale),o.y=Math.round(r.y/_via_canvas_scale),o.width=Math.round(r.width/_via_canvas_scale),o.height=Math.round(r.height/_via_canvas_scale);break}case VIA_REGION_SHAPE.CIRCLE:l=Math.abs(o.cx-this.current_point.x),g=Math.abs(o.cy-this.current_point.y),d=Math.sqrt(l*l+g*g);r.r=fixfloat(d*_via_canvas_scale),o.r=Math.round(r.r/_via_canvas_scale);break;case VIA_REGION_SHAPE.ELLIPSE:{let e=o.rx,t=o.ry,i=o.theta;var c=Math.abs(o.cx-this.current_point.x),u=Math.abs(o.cy-this.current_point.y);switch(this.region_edge[1]){case 5:t=Math.sqrt(c*c+u*u),i=Math.atan2(-(this.current_point.x-o.cx),this.current_point.y-o.cy);break;case 6:e=Math.sqrt(c*c+u*u),i=Math.atan2(this.current_point.y-o.cy,this.current_point.x-o.cx);break;default:e=c,t=u,i=0}r.rx=fixfloat(e*_via_canvas_scale),r.ry=fixfloat(t*_via_canvas_scale),r.theta=fixfloat(i),o.rx=Math.round(r.rx/_via_canvas_scale),o.ry=Math.round(r.ry/_via_canvas_scale),o.theta=fixfloat(i);break}case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:var h,m,p,v,l=this.region_edge[1]-this.polygon_resize_vertex_offset;e.ctrlKey||e.metaKey?(d=(g=_via_canvas_regions[this.user_sel_region_id].shape_attributes).name,this.isOnPolygonVertex(g.all_points_x,g.all_points_y,this.current_point)===this.region_edge[1]?this.polygonDelVertex(n,l)&&show_message("Deleted vertex "+l+" from region"):(g=this.is_on_polygon_edge(g.all_points_x,g.all_points_y,this.current_point.x,this.current_point.y)-this.polygon_resize_vertex_offset,p=Math.round(this.click1.x),v=Math.round(this.click1.y),h=Math.round(p*_via_canvas_scale),m=Math.round(v*_via_canvas_scale),p=Math.round(h/_via_canvas_scale),v=Math.round(m/_via_canvas_scale),_via_canvas_regions[n].shape_attributes.all_points_x.splice(1+g,0,p),_via_canvas_regions[n].shape_attributes.all_points_y.splice(1+g,0,v),_via_img_metadata[_via_image_id].regions[n].shape_attributes.all_points_x.splice(1+g,0,h),_via_img_metadata[_via_image_id].regions[n].shape_attributes.all_points_y.splice(1+g,0,m),show_message("Added 1 new vertex to "+d+" region"))):(p=Math.round(this.current_point.x*_via_canvas_scale),v=Math.round(this.current_point.y*_via_canvas_scale),r.all_points_x[l]=p,r.all_points_y[l]=v,o.all_points_x[l]=Math.round(p/_via_canvas_scale),o.all_points_y[l]=Math.round(v/_via_canvas_scale))}this.redrawRegCanvas(),_via_reg_canvas.focus()}else if(a<this.mouse_click_tol||t<this.mouse_click_tol){if(this.is_user_drawing_polygon){s=Math.round(this.click1.x),_=Math.round(this.click1.y),a=_via_canvas_regions[this.current_polygon_region_id].shape_attributes.all_points_x.length,t=_via_canvas_regions[this.current_polygon_region_id].shape_attributes.all_points_x[a-1],a=_via_canvas_regions[this.current_polygon_region_id].shape_attributes.all_points_y[a-1];s===t&&_===a||(_via_canvas_regions[this.current_polygon_region_id].shape_attributes.all_points_x.push(s),_via_canvas_regions[this.current_polygon_region_id].shape_attributes.all_points_y.push(_))}else{t=this.isInsideRegion(this.click0);if(0<=t){if(this.user_sel_region_id=t,this.is_region_selected=!0,this.is_user_moving_region=!1,this.is_user_drawing_region=!1,e.shiftKey||(sidebar.annotation_editor_highlight_row(),toggle_all_regions_selection(!1)),set_region_select_state(t,!0),e.shiftKey?sidebar.annotation_editor_hide():sidebar.annotation_editor_show(),_via_is_region_info_visible)switch(_via_canvas_regions[t].shape_attributes.name){case VIA_REGION_SHAPE.RECT:break;case VIA_REGION_SHAPE.CIRCLE:var f=document.getElementById("region_info"),b=_via_canvas_regions[this.user_sel_region_id].shape_attributes;f.innerHTML+=", Radius:"+b.r;break;case VIA_REGION_SHAPE.ELLIPSE:f=document.getElementById("region_info"),b=_via_canvas_regions[this.user_sel_region_id].shape_attributes;f.innerHTML+=", X-radius:"+b.rx+", Y-radius:"+b.ry;break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:}this.is_user_triming_region&&this.trimRegionSection(t),show_message("Region selected. If you intended to draw a region, click again inside the selected region to start drawing a region.")}else if(this.is_user_drawing_region)this.is_user_drawing_region=!1,toggle_all_regions_selection(this.is_region_selected=!1),sidebar.annotation_editor_hide();else switch(this.current_shape){case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:this.is_user_drawing_polygon=!0;var y=new File_Region,y=(y.shape_attributes.name=this.current_shape,y.shape_attributes.all_points_x=[Math.round(this.click0.x)],y.shape_attributes.all_points_y=[Math.round(this.click0.y)],_via_canvas_regions.push(y));this.current_polygon_region_id=y-1;break;case VIA_REGION_SHAPE.POINT:y=new File_Region,y=(y.shape_attributes.name=VIA_REGION_SHAPE.POINT,y.shape_attributes.cx=Math.round(this.click0.x*_via_canvas_scale),y.shape_attributes.cy=Math.round(this.click0.y*_via_canvas_scale),_via_img_metadata[_via_image_id].regions.push(y)),y=y-1,y=(sidebar.set_region_annotations_to_default_value(y),new File_Region);y.shape_attributes.name=VIA_REGION_SHAPE.POINT,y.shape_attributes.cx=Math.round(this.click0.x),y.shape_attributes.cy=Math.round(this.click0.y),_via_canvas_regions.push(y),sidebar.annotation_editor_update_content()}}this.redrawRegCanvas(),_via_reg_canvas.focus()}else if(this.is_user_drawing_region){this.is_user_drawing_region=!1;let e=this.click0.x,t=this.click0.y;var w=new File_Region,I=new File_Region,A=Math.abs(this.click1.x-e),x=Math.abs(this.click1.y-t);let i=!1;if(A>VIA_REGION_MIN_DIM&&x>VIA_REGION_MIN_DIM){switch(this.current_shape){case VIA_REGION_SHAPE.RECT:e=(this.click0.x<this.click1.x?this.click0:this.click1).x,t=(this.click0.y<this.click1.y?this.click0:this.click1).y;var E=Math.round(e*_via_canvas_scale),k=Math.round(t*_via_canvas_scale),R=Math.round(A*_via_canvas_scale),P=Math.round(x*_via_canvas_scale);w.shape_attributes.name="rect",w.shape_attributes.x=E,w.shape_attributes.y=k,w.shape_attributes.width=R,w.shape_attributes.height=P,I.shape_attributes.name="rect",I.shape_attributes.x=Math.round(E/_via_canvas_scale),I.shape_attributes.y=Math.round(k/_via_canvas_scale),I.shape_attributes.width=Math.round(R/_via_canvas_scale),I.shape_attributes.height=Math.round(P/_via_canvas_scale),i=!0;break;case VIA_REGION_SHAPE.CIRCLE:E=Math.round(e*_via_canvas_scale),k=Math.round(t*_via_canvas_scale),R=Math.round(Math.sqrt(A*A+x*x)*_via_canvas_scale);w.shape_attributes.name="circle",w.shape_attributes.cx=E,w.shape_attributes.cy=k,w.shape_attributes.r=R,I.shape_attributes.name="circle",I.shape_attributes.cx=Math.round(E/_via_canvas_scale),I.shape_attributes.cy=Math.round(k/_via_canvas_scale),I.shape_attributes.r=Math.round(R/_via_canvas_scale),i=!0;break;case VIA_REGION_SHAPE.ELLIPSE:P=Math.round(e*_via_canvas_scale),E=Math.round(t*_via_canvas_scale),k=Math.round(A*_via_canvas_scale),R=Math.round(x*_via_canvas_scale);w.shape_attributes.name="ellipse",w.shape_attributes.cx=P,w.shape_attributes.cy=E,w.shape_attributes.rx=k,w.shape_attributes.ry=R,w.shape_attributes.theta=0,I.shape_attributes.name="ellipse",I.shape_attributes.cx=Math.round(P/_via_canvas_scale),I.shape_attributes.cy=Math.round(E/_via_canvas_scale),I.shape_attributes.rx=Math.round(k/_via_canvas_scale),I.shape_attributes.ry=Math.round(R/_via_canvas_scale),I.shape_attributes.theta=0,i=!0;break;case VIA_REGION_SHAPE.REMOVE:e=(this.click0.x<this.click1.x?this.click0:this.click1).x,t=(this.click0.y<this.click1.y?this.click0:this.click1).y;P=Math.round(e*_via_canvas_scale),E=Math.round(t*_via_canvas_scale),k=Math.round(A*_via_canvas_scale),R=Math.round(x*_via_canvas_scale),P=Math.round(P/_via_canvas_scale),E=Math.round(E/_via_canvas_scale),k=Math.round(k/_via_canvas_scale),R=Math.round(R/_via_canvas_scale);this.drawSelector(P,E,k,R);break;case VIA_REGION_SHAPE.POINT:case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:}i&&((a=_via_img_metadata[_via_image_id].regions.push(w))!==(s=_via_canvas_regions.push(I))&&(console.log("_via_img_metadata.regions["+a+"] and _via_canvas_regions["+s+"] count mismatch"),Message.show({address:"Error",body:"Region count mismatch",color:"#cc1100"})),_=a-1,sidebar.set_region_annotations_to_default_value(_),select_only_region(_),_via_annotation_editor_mode===VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS&&"region"===_via_metadata_being_updated&&(sidebar.annotation_editor_add_row(_),sidebar.annotation_editor_scroll_to_row(_),sidebar.annotation_editor_clear_row_highlight(),sidebar.annotation_editor_highlight_row(_)),sidebar.annotation_editor_show()),this.redrawRegCanvas(),_via_reg_canvas.focus()}else show_message("Prevented accidental addition of a very small region.")}}}canvasMouseOverHandler(){this.is_user_drawing_polygon?_via_reg_canvas.style.cursor="crosshair":_via_reg_canvas.style.cursor="default",this.redrawRegCanvas(),_via_reg_canvas.focus()}canvasMouseMoveHandler(e){if(buffer.imgLoaded&&(zoom.handleMoseMove(e),this.current_shape!==VIA_REGION_SHAPE.DRAG)&&!is_alt_pressed)if(this.current_point.x=e.offsetX,this.current_point.y=e.offsetY,1===this.trim_phase_id&&this.trim_line.hasOwnProperty("x0"))this.redrawRegCanvas(),this.drawLineRegion(this.trim_line.x0,this.trim_line.y0,this.current_point.x,this.current_point.y,!1);else{var t,i,a=document.getElementById("region_info");if(null!=a&&_via_is_region_info_visible&&(e=Math.round(this.current_point.x*_via_canvas_scale),l=Math.round(this.current_point.y*_via_canvas_scale),a.innerHTML="X:"+e+", Y:"+l),this.is_region_selected){if(null!=a&&_via_is_region_info_visible&&-1!==this.user_sel_region_id)switch(_via_canvas_regions[this.user_sel_region_id].shape_attributes.name){case VIA_REGION_SHAPE.RECT:break;case VIA_REGION_SHAPE.CIRCLE:var _=document.getElementById("region_info"),s=_via_canvas_regions[this.user_sel_region_id].shape_attributes;_.innerHTML+=", Radius:"+s.r;break;case VIA_REGION_SHAPE.ELLIPSE:_=document.getElementById("region_info"),s=_via_canvas_regions[this.user_sel_region_id].shape_attributes;_.innerHTML+=", X-radius:"+s.rx+", Y-radius:"+s.ry;break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:}if(this.is_user_resizing_region)sidebar.annotation_editor_hide();else if(this.region_edge=this.isOnRegionCorner(this.current_point),this.region_edge[0]===this.user_sel_region_id){switch(this.region_edge[1]){case 1:case 3:_via_reg_canvas.style.cursor="nwse-resize";break;case 2:case 4:_via_reg_canvas.style.cursor="nesw-resize";break;case 5:case 7:_via_reg_canvas.style.cursor="ns-resize";break;case 6:case 8:_via_reg_canvas.style.cursor="ew-resize";break;case 5:_via_reg_canvas.style.cursor="n-resize";break;case 6:_via_reg_canvas.style.cursor="e-resize";break;default:_via_reg_canvas.style.cursor="default"}this.region_edge[1]>=this.polygon_resize_vertex_offset&&(_via_reg_canvas.style.cursor="crosshair",show_message("To move vertex, simply drag the vertex. To add vertex, press [Ctrl] key and click on the edge. To delete vertex, press [Ctrl] (or [Command]) key and click on vertex."))}else{var e=this.isInsideThisRegion(this.current_point,this.user_sel_region_id);_via_reg_canvas.style.cursor=e?"move":"default"}}if(this.is_user_drawing_region){_via_canvas_regions.length?this.redrawRegCanvas():this.clearCanvas();let e=this.click0.x,t=this.click0.y;var n=Math.round(Math.abs(this.current_point.x-this.click0.x)),r=Math.round(Math.abs(this.current_point.y-this.click0.y));switch(this.lineStyle(),this.current_shape){case VIA_REGION_SHAPE.RECT:t=(this.click0.x<this.current_point.x?this.click0.y<this.current_point.y?(e=this.click0.x,this.click0):(e=this.click0.x,this.current_point):this.click0.y<this.current_point.y?(e=this.current_point.x,this.click0):(e=this.current_point.x,this.current_point)).y,this.drawRectRegion(e,t,n,r,!1),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", W:"+n+", H:"+r);break;case VIA_REGION_SHAPE.CIRCLE:var o=Math.round(Math.sqrt(n*n+r*r));this.drawCircleRegion(e,t,o,!1),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", Radius:"+o);break;case VIA_REGION_SHAPE.ELLIPSE:this.drawEllipseRegion(e,t,n,r,0,!1),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", X-radius:"+fixfloat(n)+", Y-radius:"+fixfloat(r));break;case VIA_REGION_SHAPE.REMOVE:t=(this.click0.x<this.current_point.x?this.click0.y<this.current_point.y?(e=this.click0.x,this.click0):(e=this.click0.x,this.current_point):this.click0.y<this.current_point.y?(e=this.current_point.x,this.click0):(e=this.current_point.x,this.current_point)).y,this.lineStyle("","#dc143c"),this.ctx.setLineDash([5,3]),this.drawRectRegion(e,t,n,r,!1),this.ctx.setLineDash([]),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", W:"+n+", H:"+r);break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:}_via_reg_canvas.focus()}if(this.is_user_resizing_region){_via_canvas_regions.length?this.redrawRegCanvas():this.clearCanvas();var l=this.region_edge[0],g=_via_canvas_regions[l].shape_attributes;if(!this.is_user_triming_region)switch(g.name){case VIA_REGION_SHAPE.RECT:{var d=[g.x,g.y,0,0],c=(d[2]=d[0]+g.width,d[3]=d[1]+g.height,this.current_point.x),u=this.current_point.y;let e=!1;_via_is_ctrl_pressed&&(e=!0),this.rectUpdateCorner(this.region_edge[1],d,c,u,e),this.rectStandardizeCoordinates(d);c=Math.abs(d[2]-d[0]),u=Math.abs(d[3]-d[1]);this.drawRectRegion(d[0],d[1],c,u,!0),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", W:"+c+", H:"+u);break}case VIA_REGION_SHAPE.CIRCLE:var d=Math.abs(g.cx-this.current_point.x),c=Math.abs(g.cy-this.current_point.y),u=Math.sqrt(d*d+c*c);this.drawCircleRegion(g.cx,g.cy,u,!0),null!=a&&_via_is_region_info_visible&&(d=a.innerHTML.split(","),a.innerHTML="",a.innerHTML+=d[0]+","+d[1]+", Radius:"+Math.round(u));break;case VIA_REGION_SHAPE.ELLIPSE:{let e=g.rx,t=g.ry,i=g.theta;var h=Math.abs(g.cx-this.current_point.x),m=Math.abs(g.cy-this.current_point.y);switch(this.region_edge[1]){case 5:t=Math.sqrt(h*h+m*m),i=Math.atan2(-(this.current_point.x-g.cx),this.current_point.y-g.cy);break;case 6:e=Math.sqrt(h*h+m*m),i=Math.atan2(this.current_point.y-g.cy,this.current_point.x-g.cx);break;default:e=h,t=m,i=0}this.drawEllipseRegion(g.cx,g.cy,e,t,i,!0),null!=a&&_via_is_region_info_visible&&(c=a.innerHTML.split(","),a.innerHTML="",a.innerHTML=c[0]+","+c[1]+", X-radius:"+fixfloat(e)+", Y-radius:"+fixfloat(t));break}case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:d=g.all_points_x.slice(0),u=g.all_points_y.slice(0),c=this.region_edge[1]-this.polygon_resize_vertex_offset;d[c]=this.current_point.x,u[c]=this.current_point.y,this.drawPolygonRegion(d,u,!0,g.name),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", Vertices:"+g.all_points_x.length)}_via_reg_canvas.focus()}if(this.is_user_moving_region){_via_canvas_regions.length?this.redrawRegCanvas():this.clearCanvas();var p=this.current_point.x-this.region_click.x,v=this.current_point.y-this.region_click.y,f=_via_canvas_regions[this.user_sel_region_id].shape_attributes;switch(f.name){case VIA_REGION_SHAPE.RECT:this.drawRectRegion(f.x+p,f.y+v,f.width,f.height,!0),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", W:"+f.width+", H:"+f.height);break;case VIA_REGION_SHAPE.CIRCLE:this.drawCircleRegion(f.cx+p,f.cy+v,f.r,!0);break;case VIA_REGION_SHAPE.ELLIPSE:void 0===f.theta&&(f.theta=0),this.drawEllipseRegion(f.cx+p,f.cy+v,f.rx,f.ry,f.theta,!0);break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:var b=f.all_points_x.slice(0),y=f.all_points_y.slice(0);for(let e=0;e<b.length;++e)b[e]+=p,y[e]+=v;this.drawPolygonRegion(b,y,!0,f.name),null!=a&&_via_is_region_info_visible&&(a.innerHTML+=", Vertices:"+f.all_points_x.length);break;case VIA_REGION_SHAPE.POINT:this.drawPointRegion(f.cx+p,f.cy+v,!0)}_via_reg_canvas.focus(),sidebar.annotation_editor_hide()}else this.is_user_drawing_polygon&&(_via_canvas_regions.length?this.redrawRegCanvas():this.clearCanvas(),l=(e=_via_canvas_regions[this.current_polygon_region_id].shape_attributes).all_points_x,i=e.all_points_y,0<(t=l.length)&&(l=[l.slice(t-1),this.current_point.x],i=[i.slice(t-1),this.current_point.y],this.drawPolygonRegion(l,i,!1,e.name)),null!=a)&&_via_is_region_info_visible&&(a.innerHTML+=", Vertices:"+t)}}canvasContextMenuHandler(e){e.preventDefault(),this.is_user_drawing_polygon&&_via_polyshape_finish_drawing()}moveSelectedRegions(e,t){let i,a;for(a=_via_canvas_regions.length,i=0;i<a;++i)_via_region_selected_flag.has(i)&&this.moveRegion(i,e,t)}validateMoveRegion(e,t,i){switch(i.name){case VIA_REGION_SHAPE.RECT:if(e<0||t<0)return show_message("Region moved beyond image boundary. Resetting."),!1;if(t+i.height>_via_current_image_height||e+i.width>_via_current_image_width)return show_message("Region moved beyond image boundary. Resetting."),!1;case VIA_REGION_SHAPE.CIRCLE:case VIA_REGION_SHAPE.ELLIPSE:case VIA_REGION_SHAPE.POINT:case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:if(e<0||t<0||e>_via_current_image_width||t>_via_current_image_height)return show_message("Region moved beyond image boundary. Resetting."),!1}return!0}moveRegion(e,a,_){var s=_via_img_metadata[_via_image_id].regions[e].shape_attributes,n=_via_canvas_regions[e].shape_attributes;switch(n.name){case VIA_REGION_SHAPE.RECT:var t=s.x+Math.round(a*_via_canvas_scale),i=s.y+Math.round(_*_via_canvas_scale);this.validateMoveRegion(t,i,s)&&(s.x=t,s.y=i,n.x=Math.round(s.x/_via_canvas_scale),n.y=Math.round(s.y/_via_canvas_scale));break;case VIA_REGION_SHAPE.CIRCLE:case VIA_REGION_SHAPE.ELLIPSE:case VIA_REGION_SHAPE.POINT:t=s.cx+Math.round(a*_via_canvas_scale),i=s.cy+Math.round(_*_via_canvas_scale);this.validateMoveRegion(t,i,s)&&(s.cx=t,s.cy=i,n.cx=Math.round(s.cx/_via_canvas_scale),n.cy=Math.round(s.cy/_via_canvas_scale));break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:{let t=s.all_points_x,i=s.all_points_y;var r=n.all_points_x,o=n.all_points_y,l=Object.assign({},t),g=Object.assign({},i);for(let e=0;e<t.length;++e){var d=t[e]+Math.round(a*_via_canvas_scale),c=i[e]+Math.round(_*_via_canvas_scale);if(!this.validateMoveRegion(d,c,s)){t=l,i=g;break}}for(let e=0;e<t.length;++e)t[e]=t[e]+Math.round(a*_via_canvas_scale),i[e]=i[e]+Math.round(_*_via_canvas_scale);for(let e=0;e<r.length;++e)r[e]=Math.round(t[e]/_via_canvas_scale),o[e]=Math.round(i[e]/_via_canvas_scale);break}}}redrawRegCanvas(){buffer.imgLoaded&&(this.clearCanvas(),0<_via_canvas_regions.length)&&(_via_is_region_boundary_visible&&this.drawAllRegions(),_via_is_region_id_visible)&&this.drawAllRegionId()}clearCanvas(){this.ctx.clearRect(0,0,_via_reg_canvas.width,_via_reg_canvas.height)}drawAllRegions(){var t,i,a,_=_via_settings.ui.image.region_color;this.is_user_triming_region&&null!==this.trim_points&&(this.lineStyle(this.theme_region_boundary_width+1,"#ff5733"),this.ctx.setLineDash([5,3]),this.updateTrimPoints(),this.drawPolygonRegion(this.trim_points.x,this.trim_points.y,!1),this.ctx.setLineDash([]));for(let e=0;e<_via_canvas_regions.length;++e)switch(t=_via_canvas_regions[e].shape_attributes,i=_via_region_selected_flag.has(e),this.lineStyle(),this.is_user_drawing_polygon||"__via_default_region_color__"===_||void 0!==_via_img_metadata[_via_image_id].regions[e].region_attributes&&(a=_via_img_metadata[_via_image_id].regions[e].region_attributes[_],this.canvas_regions_group_color.hasOwnProperty(a))&&this.lineStyle(this.theme_region_boundary_width/2,this.canvas_regions_group_color[a]),t.name){case VIA_REGION_SHAPE.RECT:this.drawRectRegion(t.x,t.y,t.width,t.height,i);break;case VIA_REGION_SHAPE.CIRCLE:this.drawCircleRegion(t.cx,t.cy,t.r,i);break;case VIA_REGION_SHAPE.ELLIPSE:void 0===t.theta&&(t.theta=0),this.drawEllipseRegion(t.cx,t.cy,t.rx,t.ry,t.theta,i);break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:this.drawPolygonRegion(t.all_points_x,t.all_points_y,i,t.name);break;case VIA_REGION_SHAPE.POINT:this.drawPointRegion(t.cx,t.cy,i)}}drawControlPoint(e,t){this.ctx.beginPath(),this.ctx.arc(e,t,this.region_shapes_points_radius,0,2*Math.PI,!1),this.ctx.closePath(),this.ctx.fillStyle=this.theme_control_point_color,this.ctx.fill(),this.ctx.globalAlpha=1}drawRectRegion(e,t,i,a,_){_?(this.lineStyle(this.theme_region_boundary_width/2,this.selected_region_boundary_line_color),this.beginFill(),this.ctx.beginPath(),settings.is_highlight_region&&this.ctx.fillRect(e,t,i,a),this.ctx.globalAlpha=1,this.ctx.strokeRect(e,t,i,a),this.ctx.closePath(),this.drawControlPoint(e,t),this.drawControlPoint(e+i,t),this.drawControlPoint(e,t+a),this.drawControlPoint(e+i,t+a),this.drawControlPoint(e+i/2,t),this.drawControlPoint(e+i/2,t+a),this.drawControlPoint(e,t+a/2),this.drawControlPoint(e+i,t+a/2)):(this.ctx.beginPath(),this.ctx.strokeRect(e,t,i,a),this.ctx.closePath())}drawCircleRegion(e,t,i,a){a?(this.ctx.beginPath(),this.ctx.arc(e,t,i,0,2*Math.PI,!1),this.ctx.closePath(),this.lineStyle(this.theme_region_boundary_width/2,this.selected_region_boundary_line_color),this.beginFill(),this.ctx.stroke(),settings.is_highlight_region&&this.ctx.fill(),this.drawControlPoint(e+i,t)):(this.ctx.beginPath(),this.ctx.arc(e,t,i,0,2*Math.PI,!1),this.ctx.closePath(),this.ctx.stroke())}drawEllipseRegion(e,t,i,a,_,s){s?(this.lineStyle(this.theme_region_boundary_width/2,this.selected_region_boundary_line_color),this.beginFill(),this.ctx.beginPath(),this.ctx.ellipse(e,t,i,a,_,0,2*Math.PI,!1),this.ctx.closePath(),this.ctx.stroke(),settings.is_highlight_region&&this.ctx.fill(),this.drawControlPoint(e+i*Math.cos(_),t+i*Math.sin(_)),this.drawControlPoint(e-i*Math.cos(_),t-i*Math.sin(_)),this.drawControlPoint(e+a*Math.sin(_),t-a*Math.cos(_)),this.drawControlPoint(e-a*Math.sin(_),t+a*Math.cos(_))):(this.ctx.beginPath(),this.ctx.ellipse(e,t,i,a,_,0,2*Math.PI,!1),this.ctx.closePath(),this.ctx.stroke())}drawPolygonRegion(t,i,e){if(void 0!==t&&void 0!==i)if(e){this.lineStyle(this.theme_region_boundary_width/2,this.selected_region_boundary_line_color),this.beginFill(),this.ctx.beginPath(),this.ctx.moveTo(t[0],i[0]);for(let e=1;e<t.length;e++)this.ctx.lineTo(t[e],i[e]);this.is_user_drawing_polygon&&this.ctx.lineTo(t[0],i[0]),this.ctx.closePath(),this.ctx.stroke(),settings.is_highlight_region&&this.ctx.fill();for(let e=0;e<t.length;e++)this.drawControlPoint(t[e],i[e])}else{this.ctx.beginPath(),this.ctx.moveTo(t[0],i[0]);for(let e=1;e<t.length;e++)this.ctx.lineTo(t[e],i[e]);this.is_user_drawing_polygon&&this.ctx.lineTo(t[0],i[0]),this.ctx.closePath(),this.ctx.stroke()}}drawPointRegion(e,t,i){i?(this.ctx.beginPath(),this.ctx.arc(e,t,this.region_point_radius,0,2*Math.PI,!1),this.ctx.closePath(),this.lineStyle(this.theme_region_boundary_width/2,this.selected_region_boundary_line_color),this.beginFill(),this.ctx.stroke(),settings.is_highlight_region&&this.ctx.fill(),this.drawControlPoint(e,t)):(this.ctx.beginPath(),this.ctx.arc(e,t,this.region_point_radius,0,2*Math.PI,!1),this.ctx.closePath(),this.ctx.stroke())}drawLineRegion(e,t,i,a,_){_?(this.lineStyle(this.theme_region_boundary_width/2,this.selected_region_boundary_line_color),this.beginFill(),this.ctx.beginPath(),this.ctx.moveTo(e,t),this.ctx.lineTo(i,a),this.ctx.closePath(),this.ctx.stroke(),settings.is_highlight_region&&this.ctx.fill(),this.drawControlPoint(e,t),this.drawControlPoint(i,a)):(this.ctx.beginPath(),this.ctx.moveTo(e,t),this.ctx.lineTo(i,a),this.ctx.closePath(),this.ctx.stroke())}drawAllRegionId(){var s=_via_settings.ui.image.region_color;for(let _=0;_<_via_img_metadata[_via_image_id].regions.length;++_){var n=_via_canvas_regions[_],r=this.getRegionBoundingBox(n);let e=r[0],t=r[1];var r=Math.abs(r[2]-r[0]),o=Math.floor(this.ctx.measureText("M").width),l=1.8*o;let i=(_+1).toString();var g=_via_img_metadata[_via_image_id].regions[_].region_attributes[_via_settings.ui.image.region_label];if("__via_region_id__"!==_via_settings.ui.image.region_label)if(void 0!==g)switch(typeof g){case"string":i=g;break;case"object":i=Object.keys(g).join(",");break;default:i=g}else i="undefined";let a;var d=this.ctx.measureText(i).width,n=(a=!(r<d)||"__via_region_id__"===_via_settings.ui.image.region_label?d+o:1<(d=Math.floor(r*i.length/d))?(i=i.substring(0,d-1)+".",r):(i=i.substring(0,1)+".",2*o),n.shape_attributes.name===VIA_REGION_SHAPE.POLYGON||n.shape_attributes.name===VIA_REGION_SHAPE.POLYLINE?void 0!==n.shape_attributes.id_x&&n.shape_attributes.all_points_x.includes(n.shape_attributes.id_x)?(e=n.shape_attributes.id_x,t=n.shape_attributes.id_y):(d=Math.floor(Math.random()*n.shape_attributes.all_points_x.length),e=n.shape_attributes.all_points_x[d],t=n.shape_attributes.all_points_y[d],_via_canvas_regions[_].shape_attributes.id_x=e,_via_canvas_regions[_].shape_attributes.id_y=t):e-=a/2-r/2,t<l&&(t=l),_via_img_metadata[_via_image_id].regions[_].region_attributes[s]);this.canvas_regions_group_color.hasOwnProperty(n)?this.beginFill(this.canvas_regions_group_color[n],.8):this.beginFill("#0d6efd",.8),this.ctx.beginPath(),"Firefox"===this.current_browser?this.ctx.rect(Math.floor(e),Math.floor(t-1.1*l),Math.floor(a),Math.floor(l)):this.ctx.roundRect(Math.floor(e),Math.floor(t-1.1*l),Math.floor(a),Math.floor(l),5),this.ctx.fill(),this.ctx.closePath(),this.ctx.globalAlpha=1,this.ctx.fillStyle="white",this.ctx.fillText(i,Math.floor(e+.5*o),Math.floor(t-.35*l))}}getRegionBoundingBox(e){var s=e.shape_attributes,n=new Array(4);switch(s.name){case"rect":n[0]=s.x,n[1]=s.y,n[2]=s.x+s.width,n[3]=s.y+s.height;break;case"circle":n[0]=s.cx-s.r,n[1]=s.cy-s.r,n[2]=s.cx+s.r,n[3]=s.cy+s.r;break;case"ellipse":var t=s.theta,i=t+Math.PI/2,a=s.rx*Math.cos(t),t=s.rx*Math.sin(t),_=s.ry*Math.cos(i),i=s.ry*Math.sin(i),a=2*Math.sqrt(a*a+_*_),_=2*Math.sqrt(t*t+i*i);n[0]=s.cx-a/2,n[1]=s.cy-_/2,n[2]=s.cx+a/2,n[3]=s.cy+_/2;break;case"polyline":case"polygon":{var r=s.all_points_x,o=s.all_points_y;let t=Number.MAX_SAFE_INTEGER,i=Number.MAX_SAFE_INTEGER,a=0,_=0;for(let e=0;e<r.length;++e)r[e]<t&&(t=r[e]),r[e]>a&&(a=r[e]),o[e]<i&&(i=o[e]),o[e]>_&&(_=o[e]);n[0]=t,n[1]=i,n[2]=a,n[3]=_;break}case"point":n[0]=s.cx-this.region_point_radius,n[1]=s.cy-this.region_point_radius,n[2]=s.cx+this.region_point_radius,n[3]=s.cy+this.region_point_radius}return n}isInsideRegion(_,s=!1,n=!1){var r=_via_canvas_regions.length;if(0!==r){let e,t,i,a=(i=s?(e=r-1,t=-1):(e=0,t=r,1),e);for(;a!==t;){if(this.isInsideThisRegion(_,a,n))return a;a+=i}}return-1}isInsideAllRegion(e,t=!1,i=!1){var a=_via_canvas_regions.length;if(0===a)return-1;let _,s,n,r=(n=t?(_=a-1,s=-1):(_=0,s=a,1),_);for(var o=[];r!==s;)this.isInsideThisRegion(e,r,i)&&o.push(r),r+=n;return o}isInsideThisRegion(e,t,i=!1){var a=_via_canvas_regions[t].shape_attributes;let _=!1;if(_via_img_metadata[_via_image_id].isRegionLocked(t)&&!i)return!1;switch(a.name){case VIA_REGION_SHAPE.RECT:_=this.isInsideRect(a.x,a.y,a.width,a.height,e.x,e.y);break;case VIA_REGION_SHAPE.CIRCLE:_=this.isInsideCircle(a.cx,a.cy,a.r,e.x,e.y);break;case VIA_REGION_SHAPE.ELLIPSE:_=this.isInsideEllipse(a.cx,a.cy,a.rx,a.ry,a.theta,e.x,e.y);break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:_=this.isInsidePolygon(a.all_points_x,a.all_points_y,e.x,e.y);break;case VIA_REGION_SHAPE.POINT:_=this.isInsidePoint(a.cx,a.cy,e.x,e.y)}return _}isInsideCircle(e,t,i,a,_){a-=e,e=_-t;return a*a+e*e<i*i}isInsideRect(e,t,i,a,_,s){return e<_&&_<e+i&&t<s&&s<t+a}isInsideEllipse(e,t,i,a,_,s,n){var r=Math.cos(-_)*(e-s)-Math.sin(-_)*(t-n),e=Math.sin(-_)*(e-s)+Math.cos(-_)*(t-n);return r*r/(i*i)+e*e/(a*a)<1}isInsidePolygon(e,t,i,a){if(0===e.length||0===t.length)return 0;let _=0;var s=e.length;let n;for(n=0;n<s-1;++n){var r=this.isLeft(e[n],t[n],e[n+1],t[n+1],i,a);t[n]<=a?t[n+1]>a&&0<r&&++_:t[n+1]<=a&&r<0&&--_}var o=this.isLeft(e[s-1],t[s-1],e[0],t[0],i,a);return t[s-1]<=a?t[0]>a&&0<o&&++_:t[0]<=a&&o<0&&--_,0===_?0:1}isInsidePoint(e,t,i,a){i-=e,e=a-t;return i*i+e*e<this.polygon_vertex_match_tol*this.polygon_vertex_match_tol}isLeft(e,t,i,a,_,s){return(i-e)*(s-t)-(_-e)*(a-t)}isOnRegionCorner(i){var a=[-1,-1];for(let t=0;t<_via_canvas_regions.length;++t){var _=_via_canvas_regions[t].shape_attributes;let e=!1;if(a[0]=t,!_via_img_metadata[_via_image_id].isRegionLocked(t)){switch(_.name){case VIA_REGION_SHAPE.RECT:e=this.isOnRectEdge(_.x,_.y,_.width,_.height,i.x,i.y);break;case VIA_REGION_SHAPE.CIRCLE:e=this.isOnCircleEdge(_.cx,_.cy,_.r,i.x,i.y);break;case VIA_REGION_SHAPE.ELLIPSE:e=this.isOnEllipseEdge(_.cx,_.cy,_.rx,_.ry,_.theta,i.x,i.y);break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:0===(e=this.isOnPolygonVertex(_.all_points_x,_.all_points_y,i))&&(e=this.isOnPolygonEdge(_.all_points_x,_.all_points_y,i.x,i.y));break;case VIA_REGION_SHAPE.POINT:e=0}if(0<e)return a[1]=e,a}}return a[0]=-1,a}isOnRectEdge(e,t,i,a,_,s){var n=Math.abs(e-_),r=Math.abs(t-s),o=Math.abs(e+i-_),l=Math.abs(t+a-s);return n<this.region_edge_tol&&r<this.region_edge_tol?1:o<this.region_edge_tol&&r<this.region_edge_tol?2:o<this.region_edge_tol&&l<this.region_edge_tol?3:n<this.region_edge_tol&&l<this.region_edge_tol?4:(e=Math.abs(e+i/2-_),i=Math.abs(t+a/2-s),e<this.region_edge_tol&&r<this.region_edge_tol?5:o<this.region_edge_tol&&i<this.region_edge_tol?6:e<this.region_edge_tol&&l<this.region_edge_tol?7:n<this.region_edge_tol&&i<this.region_edge_tol?8:0)}isOnCircleEdge(e,t,i,a,_){var s=e-a,n=t-_;return Math.abs(Math.sqrt(s*s+n*n)-i)<this.region_edge_tol?(s=Math.atan2(_-t,a-e),Math.abs(s-Math.PI/2)<this.theta_tol||Math.abs(s+Math.PI/2)<this.theta_tol?5:Math.abs(s)<this.theta_tol||Math.abs(Math.abs(s)-Math.PI)<this.theta_tol?6:0<s&&s<Math.PI/2?1:s>Math.PI/2&&s<Math.PI?4:s<0&&s>-Math.PI/2?2:s<-Math.PI/2&&s>-Math.PI?3:void 0):0}isOnEllipseEdge(e,t,i,a,_,s,n){s-=e,n-=t;var r=Math.cos(-_)*s-Math.sin(-_)*n,_=Math.sin(-_)*s+Math.cos(-_)*n,r=(e-(s=r+e))/i,i=(t-(n=_+t))/a;return Math.abs(Math.sqrt(r*r+i*i)-1)<this.ellipse_edge_tol?(_=Math.atan2(n-t,s-e),Math.abs(_-Math.PI/2)<this.theta_tol||Math.abs(_+Math.PI/2)<this.theta_tol?5:Math.abs(_)<this.theta_tol||Math.abs(Math.abs(_)-Math.PI)<this.theta_tol?6:void 0):0}isOnPolygonVertex(e,t,i){let a,_;for(_=e.length,a=0;a<_;++a)if(Math.abs(e[a]-i.x)<this.polygon_vertex_match_tol&&Math.abs(t[a]-i.y)<this.polygon_vertex_match_tol)return this.polygon_resize_vertex_offset+a;return 0}isOnPolygonEdge(e,t,i,a){let _,s,n,r;for(s=e.length,r=[],_=0;_<s-1;++_)n=this.distToLine(i,a,e[_],t[_],e[_+1],t[_+1]),r.push(n);n=this.distToLine(i,a,e[s-1],t[s-1],e[0],t[0]),r.push(n);let o=r[0],l=0;for(s=r.length,_=1;_<s;++_)r[_]<o&&(o=r[_],l=_);let g=l+1;var d=e[g=g>=s?0:g],c=t[g],u=e[l],h=t[l];return Math.sqrt((i-d)*(i-d)+(a-c)*(a-c))<Math.sqrt((i-u)*(i-u)+(a-h)*(a-h))&&(l=g),o<this.polygon_vertex_match_tol?this.polygon_resize_vertex_offset+l:0}is_on_polygon_edge(e,t,i,a){for(var _,s=e.length,n=[],r=0;r<s-1;++r)_=this.distToLine(i,a,e[r],t[r],e[r+1],t[r+1]),n.push(_);_=this.distToLine(i,a,e[s-1],t[s-1],e[0],t[0]),n.push(_);var o=n[0],l=0;for(s=n.length,r=1;r<s;++r)n[r]<o&&(o=n[r],l=r);return o<this.polygon_vertex_match_tol?this.polygon_resize_vertex_offset+l:0}isPointInsideBoundingBox(e,t,i,a,_,s){var n={};return i<_?(n.x1=i,n.x2=_):(n.x1=_,n.x2=i),a<s?(n.y1=a,n.y2=s):(n.y1=s,n.y2=a),!!(n.x1<=e&&e<=n.x2&&n.y1<=t&&t<=n.y2)}distToLine(e,t,i,a,_,s){var n,r;return this.isPointInsideBoundingBox(e,t,i,a,_,s)?(n=s-a,r=_-i,e=Math.abs(n*e-r*t+_*a-s*i),t=Math.sqrt(r*r+n*n),Math.round(e/t)):Number.MAX_SAFE_INTEGER}rectStandardizeCoordinates(e){var t;e[0]>e[2]&&(t=e[0],e[0]=e[2],e[2]=t),e[3]<e[1]&&(t=e[1],e[1]=e[3],e[3]=t)}rectUpdateCorner(e,t,i,a,_){if(_)switch(e){case 1:case 3:var s=t[2]-t[0],n=t[3]-t[1],r=Math.sqrt(s*s+n*n),s=s/r,n=n/r,r=(i-t[0])*s+(a-t[1])*n,n=n*r;i=Math.round(t[0]+s*r),a=Math.round(t[1]+n);break;case 2:case 4:s=t[2]-t[0],r=t[1]-t[3],n=Math.sqrt(s*s+r*r),s=s/n,r=r/n,n=(i-t[0])*s+(a-t[3])*r,r=r*n;i=Math.round(t[0]+s*n),a=Math.round(t[3]+r)}switch(e){case 1:t[0]=i,t[1]=a;break;case 3:t[2]=i,t[3]=a;break;case 2:t[2]=i,t[1]=a;break;case 4:t[0]=i,t[3]=a;break;case 5:t[1]=a;break;case 6:t[2]=i;break;case 7:t[3]=a;break;case 8:t[0]=i}}setAllCanvasSize(e,t){_via_reg_canvas.height=t,_via_reg_canvas.width=e}setCanvasScale(e){this.ctx.scale(e,e)}trimRegionSection(e){var t=_via_canvas_regions[e].shape_attributes;"polygon"===t.name&&(this.trim_region_id=e,this.trim_phase_id=1,this.trim_points={x:t.all_points_x,y:t.all_points_y})}updateTrimPoints(){var e;-1!==this.trim_phase_id&&_via_canvas_regions.hasOwnProperty(this.trim_region_id)&&(e=_via_canvas_regions[this.trim_region_id].shape_attributes,this.trim_points={x:e.all_points_x,y:e.all_points_y})}calculateTrimPoints(){if(2===this.trim_phase_id){var t=[];for(let e=0;e<this.trim_points.x.length;e++){var i=this.trim_points.x[e],a=this.trim_points.y[e],_=this.trim_points.x[(e+1)%this.trim_points.x.length],s=this.trim_points.y[(e+1)%this.trim_points.x.length],n=this.trim_line.x0,r=this.trim_line.y0,o=this.trim_line.x1,l=this.trim_line.y1;t.push(this.lineLineIntersection(i,a,_,s,n,r,o,l))}for(let e=0;e<t.length;e++)-1!==t[e][0]&&-1!==t[e][1]&&(this.trim_choose_points.push(t[e][0]),this.trim_choose_points.push(t[e][1]));!(this.trim_choose_points.length<4)&&4<=this.trim_choose_points.length?(this.trim_phase_id=3,this.drawTrimemdRegions()):this.destroyTrim()}}lineLineIntersection(e,t,i,a,_,s,n,r){var o,l,g=(e-i)*(s-r)-(t-a)*(_-n);return 0!=g&&(l=(e*a-t*i)*(s-r)-(t-a)*(_*r-s*n),o=Math.round(((e*a-t*i)*(_-n)-(e-i)*(_*r-s*n))/g),l=Math.round(l/g),o>=Math.min(e,i))&&o<=Math.max(e,i)&&o>=Math.min(_,n)&&o<=Math.max(_,n)&&l>=Math.min(t,a)&&l<=Math.max(t,a)&&l>=Math.min(s,r)&&l<=Math.max(s,r)?[o,l]:[-1,-1]}drawTrimemdRegions(){if(!(this.trim_phase_id<3)){var i={x:[],y:[]},a={x:[],y:[]};let t=!0;for(let e=0;e<this.trim_points.x.length;e++){var _=this.trim_points.x[e],s=this.trim_points.y[e],n=this.trim_points.x[(e+1)%this.trim_points.x.length],r=this.trim_points.y[(e+1)%this.trim_points.x.length],o=this.trim_line.x0,l=this.trim_line.y0,g=this.trim_line.x1,d=this.trim_line.y1,n=this.lineLineIntersection(_,s,n,r,o,l,g,d);-1!==n[0]&&-1!==n[1]?t=t?(i.x.push(_),i.y.push(s),i.x.push(n[0]),i.y.push(n[1]),a.x.push(n[0]),a.y.push(n[1]),!1):(a.x.push(_),a.y.push(s),a.x.push(n[0]),a.y.push(n[1]),i.x.push(n[0]),i.y.push(n[1]),!0):(t?(i.x.push(_),i):(a.x.push(_),a)).y.push(s)}this.trim_points.polygon1=i,this.trim_points.polygon2=a,this.ctx.beginPath(),this.ctx.moveTo(i.x[0],i.y[0]);for(let e=1;e<i.x.length;e++)this.ctx.lineTo(i.x[e],i.y[e]);this.ctx.closePath(),this.ctx.fillStyle="rgba(255, 0, 0, 0.5)",this.ctx.fill(),this.ctx.beginPath(),this.ctx.moveTo(a.x[0],a.y[0]);for(let e=1;e<a.x.length;e++)this.ctx.lineTo(a.x[e],a.y[e]);this.ctx.closePath(),this.ctx.fillStyle="rgba(0, 255, 0, 0.5)",this.ctx.fill(),this.trim_phase_id=4}}isInsideTrimPolygon(i,a){if(void 0===a)return!1;let _=!1;for(let e=0,t=a.x.length-1;e<a.x.length;t=e++){var s=a.x[e],n=a.y[e],r=a.x[t],o=a.y[t];n>i.y!=i.y<o&&i.x<(r-s)*(i.y-n)/(o-n)+s&&(_=!_)}return _}trimFromMetadata(e){var t;this.trim_phase_id<4||(t=_via_img_metadata[_via_image_id].regions[this.trim_region_id].shape_attributes,t=this.thisPointIsThePolygonPoint(e,t),_via_img_metadata[_via_image_id].regions[this.trim_region_id].shape_attributes=t,drawing.setIsRegionSelected(!1),drawing.setUserSelRegionId(-1),drawing.clearCanvas(),_via_load_canvas_regions(),0===_via_canvas_regions.length?drawing.clearCanvas():drawing.redrawRegCanvas(),this.destroyTrim())}thisPointIsThePolygonPoint(i,e){var a,_=[],s=[],n=e.all_points_x,r=e.all_points_y;Math.round(n[0]/_via_canvas_scale),Math.round(r[0]/_via_canvas_scale);for(let t=0;t<i.x.length;t++){for(let e=0;e<n.length;e++)if(a={x:Math.round(n[e]/_via_canvas_scale),y:Math.round(r[e]/_via_canvas_scale)},i.x[t]===a.x&&i.y[t]===a.y){_.push(a.x*_via_canvas_scale),s.push(a.y*_via_canvas_scale);break}for(let e=0;e<this.trim_choose_points.length;e+=2)if(i.x[t]===this.trim_choose_points[e]&&i.y[t]===this.trim_choose_points[e+1]){_.push(i.x[t]*_via_canvas_scale),s.push(i.y[t]*_via_canvas_scale);break}}return e.all_points_x=_,e.all_points_y=s,e}destroyTrim(){this.trim_points={},this.trim_choose_points=[],this.trim_choose_points_id=[],this.trim_phase_id=0,this.trim_region_id=-1,this.trim_line={}}is_inside_points(e,t,i,a,_,s){return e<_&&_<e+i&&t<s&&s<t+a}polygonDelVertex(e,t){var i=_via_canvas_regions[e].shape_attributes,a=i.all_points_x.length,i=i.name;return i!==VIA_REGION_SHAPE.POLYGON&&i!==VIA_REGION_SHAPE.POLYLINE?(show_message("Vertices can only be deleted from polygon/polyline."),!1):a<=3&&i===VIA_REGION_SHAPE.POLYGON?(show_message("Failed to delete vertex because a polygon must have at least 3 vertices."),!1):a<=2&&i===VIA_REGION_SHAPE.POLYLINE?(show_message("Failed to delete vertex because a polyline must have at least 2 vertices."),!1):(_via_canvas_regions[e].shape_attributes.all_points_x.splice(t,1),_via_canvas_regions[e].shape_attributes.all_points_y.splice(t,1),_via_img_metadata[_via_image_id].regions[e].shape_attributes.all_points_x.splice(t,1),_via_img_metadata[_via_image_id].regions[e].shape_attributes.all_points_y.splice(t,1),!0)}drawSelector(i,a,_,s){var e=_via_canvas_regions.length;if(0!==e)for(let t=0;t<e;++t)if(!_via_img_metadata[_via_image_id].isRegionLocked(t)){var n=_via_canvas_regions[t].shape_attributes;if("polygon"===n.name)for(let e=0;e<n.all_points_x.length;++e)this.is_inside_points(i,a,_,s,n.all_points_x[e],n.all_points_y[e])&&this.polygonDelVertex(t,e)}}lockRegionHandler(e){let t=parseInt(e.id.slice(5));var i=e.id.slice(e.id.length-8);"_context"===i&&(t=parseInt(e.id.slice(5,e.id.length-8))),_via_img_metadata[_via_image_id].lockedRegions.has(t)||_via_img_metadata[_via_image_id].lockedRegions.delete(t),"_context"===i?$("#lock_"+t+"_context").is(":checked")?_via_img_metadata[_via_image_id].addLockedRegion(t):_via_img_metadata[_via_image_id].clearRegionLock(t):$("#lock_"+t).is(":checked")?_via_img_metadata[_via_image_id].addLockedRegion(t):_via_img_metadata[_via_image_id].clearRegionLock(t),_via_img_metadata[_via_image_id].isRegionLocked(t)?($("#lock_"+t).prop("checked",!0),$("#lock_"+t+"_context")&&$("#lock_"+t+"_context").prop("checked",!0)):($("#lock_"+t).prop("checked",!1),$("#lock_"+t+"_context")&&$("#lock_"+t+"_context").prop("checked",!1))}updateCheckedLockHtml(){if(_via_img_metadata.hasOwnProperty(_via_image_id))for(let e=0;e<_via_img_metadata[_via_image_id].regions.length;e++)_via_img_metadata[_via_image_id].isRegionLocked(e)&&($("#lock_"+e).prop("checked",!0),$("#lock_"+e+"_context"))&&$("#lock_"+e+"_context").prop("checked",!0)}updateUiComponents(){if(buffer.imgLoaded)switch(show_message("Updating user interface components."),_via_display_area_content_name){case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:image_grid_set_content_panel_height_fixed(),image_grid_set_content_to_current_group();break;case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:!this.is_window_resized&&buffer.imgLoaded&&(this.is_window_resized=!0,buffer.showImage(_via_image_index))}}drawingSetVariables(){this.click0.x=0,this.click0.y=0,this.click1.x=0,this.click1.y=0,this.is_user_drawing_region=!1,this.is_window_resized=!1,this.is_user_resizing_region=!1,this.is_user_moving_region=!1,this.is_user_drawing_polygon=!1,this.is_region_selected=!1,this.user_sel_region_id=-1}toString(){return"Canvas controller -> "+_via_reg_canvas}}let drawing=new Drawer;class Modal{constructor(){this.modal=$("#staticBackdropModal"),this.modalBody=$("#staticBackdropModal .modal-body"),this.modalTitle=$("#staticBackdropModal .modal-title"),this.modalFooter=$("#staticBackdropModal .modal-footer"),this.modalClose=$("#staticBackdropModal .btn-close"),this.modalClose.click(()=>{this.modal.modal("hide")})}show(e,t,i,a=!1){this.modalTitle.html(e),this.modalBody.html(t),this.modalFooter.html(i),this.modal.modal("show"),a?this.modalClose.hide():this.modalClose.show()}update(e,t,i){this.modalTitle.html(e),this.modalBody.html(t),this.modalFooter.html(i)}hide(){this.modal.modal("hide")}clear(){this.modalTitle.html(""),this.modalBody.html(""),this.modalFooter.html("")}}let modal=new Modal;class FileManager{constructor(){this.modal=new Modal}downloadAllRegionDataModal(){this.modal=new Modal,this.modal.show("Download all region data",'<div class="form-check form-switch"><input class="form-check-input" type="checkbox" id="download_all_region_data_csv"><label class="form-check-label" for="download_all_region_data_csv">Download as CSV</label></div><div class="form-check form-switch"><input class="form-check-input" type="checkbox" id="download_all_region_data_json"><label class="form-check-label" for="download_all_region_data_json">Download as JSON</label></div><div class="form-check form-switch"><input class="form-check-input" type="checkbox" id="download_all_region_data_coco"><label class="form-check-label" for="download_all_region_data_coco">Download as COCO</label></div>','<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button><button type="button" class="btn btn-primary" onclick="fileManager.downloadAllRegionDataModalDownload()">Download</button>')}downloadAllRegionDataModalDownload(){var e=document.getElementById("download_all_region_data_csv").checked,t=document.getElementById("download_all_region_data_json").checked,i=document.getElementById("download_all_region_data_coco").checked;e&&this.downloadAllRegionData("csv"),t&&this.downloadAllRegionData("json"),i&&this.downloadAllRegionData("coco"),this.modal.hide(),delete this.modal}downloadAllRegionData(i,a=i){this.packMetadata(i).then(e=>{e=new Blob(e,{type:`text/${a};charset=utf-8`});let t=settings.projectName||"InfarctSizeExport";"csv"===a&&"json"===a||(a="json",t+=`_${i}.`+a),this.saveDataToLocalFile(e,t)},e=>{show_message(`Failed to download data: [${e}]`)})}packMetadata(e){let i=e;let s=["filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes"];return new Promise((e,t)=>{e("csv"===i?(()=>{for(var e in _via_img_metadata){var t=fileManager.escapeForCsv(map_to_json(_via_img_metadata[e].file_attributes));let a=`
+${_via_img_metadata[e].filename},${_via_img_metadata[e].size},"${t}"`,_=_via_img_metadata[e].regions;0!==_.length?s.push(..._.map((e,t)=>{var i='"'+fileManager.escapeForCsv(map_to_json(e.shape_attributes))+'"',e='"'+fileManager.escapeForCsv(map_to_json(e.region_attributes))+'"';return a+`,${_.length},${t},${i},`+e})):s.push(a+',0,0,"{}","{}"')}return s})():"coco"===i?img_stat_set_all().then(()=>[fileManager.exportProjectToCocoFormat()],e=>{throw e}):[JSON.stringify(_via_img_metadata)])})}saveDataToLocalFile(e,t){var i=document.createElement("a"),e=URL.createObjectURL(e),t=(i.href=e,i.download=t,new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}));i.dispatchEvent(t),URL.revokeObjectURL(e)}escapeForCsv(e){return e.replace(/["]/g,'""')}incrementSkippedAnnotationCount(){this.skipped_annotation_count++,show_message("Skipped "+this.skipped_annotation_count+" annotations. COCO format only supports the following attribute types: "+JSON.stringify(VIA_COCO_EXPORT_ATTRIBUTE_TYPE)+" and region shapes: "+JSON.stringify(VIA_COCO_EXPORT_RSHAPE))}shouldAssignUniqueId(){for(var e in _via_img_metadata)if(Number.isNaN(parseInt(e)))return!0;for(var t in project.attributes)if(VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(project.attributes[t].type))for(var i in project.attributes[t].options){if(this.attribute_option_id_list.includes(i)||Number.isNaN(parseInt(i)))return!0;this.attribute_option_id_list.push(i)}return!1}exportProjectToCocoFormat(){var e,t,i={info:{},images:[],annotations:[],licenses:[],categories:[]},a=(i.info={year:(new Date).getFullYear(),version:"1.0",description:"Annotations exported to COCO format using InfarctSize",contributor:"",url:"https://infarctsize.com/",date_created:(new Date).toString()},i.licenses=[{id:0,name:"Unknown License",url:""}],this.skipped_annotation_count=0,this.attribute_option_id_list=[],this.shouldAssignUniqueId()),_={};let s=1;for(e in project.attributes.region)if(VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(project.attributes.region[e].type))for(var n in project.attributes.region[e].options){var r=a?s++:parseInt(n);i.categories.push({supercategory:e,id:r,name:project.attributes.region[e].options[n]}),_[n]=r}let o=1,l=1;for(t in _via_image_id_list){var g=_via_image_id_list[t];let e=_via_settings.core.default_filepath+_via_img_metadata[g].filename;_via_img_fileref[g]instanceof File&&(e=_via_img_fileref[g].filename);var d,c=a?l++:parseInt(g);for(d in i.images.push({id:c,width:_via_img_stat[t][0],height:_via_img_stat[t][1],file_name:_via_img_metadata[g].filename,license:0,flickr_url:e,coco_url:e,date_captured:""}),_via_img_metadata[g].regions){var u=_via_img_metadata[g].regions[d];if(VIA_COCO_EXPORT_RSHAPE.includes(u.shape_attributes.name)){var h,m=via_region_shape_to_coco_annotation(u.shape_attributes);for(h in m.id=o,m.image_id=c,u.region_attributes){var p=u.region_attributes[h];_.hasOwnProperty(p)?(m.category_id=_[p],i.annotations.push(m),o++):this.incrementSkippedAnnotationCount()}}else this.incrementSkippedAnnotationCount()}}return[JSON.stringify(i)]}importAnnotationsFromFile(){var e=document.createElement("input");e.type="file",e.accept=".csv,.json,.coco",e.addEventListener("change",this.importAnnotationsFromFileOnchange),e.click()}importAnnotationsFromFileOnchange(e){var t=e.target.files;for(let e=0;e<t.length;e++){var i=t[e],a=i.type;"application/json"===a||"text/json"===a?this.importAnnotationsFromJson(i):"text/csv"===a&&FileManager.loadTextFile(i,import_annotations_from_csv)}}importAnnotationsFromJson(t){var e=new FileReader;t.name.split(".").pop();e.readAsText(t),e.onload=function(e){e=e.target.result,e=JSON.parse(e);e.hasOwnProperty("images")&&e.hasOwnProperty("annotations")?FileManager.loadTextFile(t,import_coco_annotations_from_json):FileManager.loadTextFile(t,import_annotations_from_json)}}static loadTextFile(t,i){if(t){let e=new FileReader;e.addEventListener("progress",function(e){Message.showInfo("Loading data from file : "+t.name+" ... ")},!1),e.addEventListener("error",function(){Message.showError("Error loading data text file :  "+t.name+" !"),i("")},!1),e.addEventListener("load",function(){i(e.result)},!1),e.readAsText(t,"utf-8")}}}let fileManager=new FileManager;function infarctSizeAiWorker(){function d(i,a,_,s){if(void 0!==i&&void 0!==a&&0!==i.length&&0!==a.length){let e=0;var n,r=i.length;let t;for(t=0;t<r;++t){var o=(t+1)%r,l=(l=i[t],n=a[t],(i[o]-l)*(s-n)-(_-l)*(a[o]-n));a[t]<=s?a[o]>s&&0<l&&++e:a[o]<=s&&l<0&&--e}return 0!==e}}onmessage=function(e){var i,a,t,_=JSON.parse(e.data.slice_regions_id),s=JSON.parse(e.data.regions),e=e.data.update,n={},r=s.map(e=>{return e={x:e.shape_attributes.all_points_x,y:e.shape_attributes.all_points_y},t=e.x.length,i=e.x.reduce((e,t)=>e+t,0),e=e.y.reduce((e,t)=>e+t,0),{x:Math.floor(i/t),y:Math.floor(e/t)};var t,i});for(let t=0;t<_.length;++t)for(let e=0;e<_.length;++e)t!==e&&(i=s[_[t]].shape_attributes,a=r[_[e]],d(i.all_points_x,i.all_points_y,a.x,a.y))&&(_.splice(e,1),--e<t)&&t--;for(t of _){var o,l=s[t].shape_attributes,g=[];for(let e=0;e<s.length;e++)e!==t&&"polygon"===s[e].shape_attributes.name&&(o=r[e],d(l.all_points_x,l.all_points_y,o.x,o.y))&&g.push(e);n[t]=g}postMessage({slice_regions_id:JSON.stringify(_),grouped_regions:JSON.stringify(n),update:e})}}function Science_plugins(){this.grouped_regions={},this.slice_regions_id=[],this.GroupingWorker=new Worker(URL.createObjectURL(new Blob(["("+infarctSizeAiWorker.toString()+")()"],{type:"text/javascript"}))),this.GroupingWorker.onmessage=this.groupingWorkerOnMessage.bind(this)}Science_plugins.prototype.updateSliceRegion=function(t=_via_image_id,e=!0){var i=[];for(let e=0;e<_via_img_metadata[t].regions.length;++e)"Slice"===_via_img_metadata[t].regions[e].region_attributes.Type&&i.push(e);return 0===i.length?(console.log("No slice regions found"),!1):i===_via_img_metadata[t].groupedRegions.groupBy?(console.log("No change in slice regions"),!1):(_via_img_metadata[t].clearGroups(),void this.GroupingWorker.postMessage({slice_regions_id:JSON.stringify(i),regions:JSON.stringify(_via_img_metadata[t].regions),grouped_regions:JSON.stringify(_via_img_metadata[_via_image_id].groupedRegions.groups,Project.replacer),update:e}))},Science_plugins.prototype.groupingWorkerOnMessage=function(e){var t,i=JSON.parse(e.data.grouped_regions);for(t in i)_via_img_metadata[_via_image_id].addGroupedRegion(parseInt(t),i[t]);_via_img_metadata[_via_image_id].groupedRegions.groupBy=JSON.parse(e.data.slice_regions_id),e.data.update&&sidebar.annotation_editor_update_content()},Science_plugins.prototype.changeGroupIdentifier=function(e){var t=parseInt(e.id.split("_")[1]),e=e.value;_via_img_metadata[_via_image_id].groupedRegions.groupIDs.set(t,e)},Science_plugins.prototype.selectAllRegionsInGroup=function(e){var t,e=parseInt(e.id.split("_")[2]),i=(toggle_all_regions_selection(!1),sidebar.annotation_editor_clear_row_highlight(),_via_region_selected_flag.add(e),_via_img_metadata[_via_image_id].getGroupedRegion(e));for(t in i)_via_region_selected_flag.add(i[t]);sidebar.annotation_editor_highlight_row(e),drawing.setIsRegionSelected(!0),drawing.setUserSelRegionId(e),drawing.redrawRegCanvas()},Science_plugins.prototype.getScores=function(e){return _via_img_metadata[_via_image_id].regions[e].hasOwnProperty("score")?_via_img_metadata[_via_image_id].regions[e].score:"n/a"},Science_plugins.prototype.setScores=function(e,t){_via_img_metadata[_via_image_id].regions[e].score=t,sidebar.annotation_editor_update_content()},Science_plugins.prototype.updateScoresBasedOnGroup=function(){for(var t in _via_img_metadata[_via_image_id].groupedRegions.groups){var i={Infarct:!1,Risk:!1};for(let e=0;e<_via_img_metadata[_via_image_id].groupedRegions.groups[t].length;e++){var a=_via_img_metadata[_via_image_id].regions[_via_img_metadata[_via_image_id].groupedRegions.groups[t][e]];if(a.hasOwnProperty("score")&&"n/a"===a.score)switch(a.region_attributes.Type){case"Infarct":i.Infarct=!0;break;case"Risk":i.Risk=!0}}if(i.Infarct||i.Risk)for(let e=0;e<_via_img_metadata[_via_image_id].groupedRegions.groups[t].length;e++){var _=_via_img_metadata[_via_image_id].regions[_via_img_metadata[_via_image_id].groupedRegions.groups[t][e]];i.Infarct&&"Infarct"===_.region_attributes.Type&&(_.score="n/a"),i.Risk&&"Risk"===_.region_attributes.Type&&(_.score="n/a")}}},Science_plugins.prototype.calcPolygonArea=function(t,i){let a=0;var _=t.length;for(let e=0;e<_;e++){var s=(e+1)%_,n=t[e]*i[s],s=i[e]*t[s];a+=n-s}return.5*Math.abs(a)},Science_plugins.prototype.ExportArea=async function(){let g=[["Filename","File_ID","Treatment_ID","Slice_ID","Slice_area","Infract_area","Risk_area","Slice_score","Infract_score","Risk_score"]],d=0;for(let e in _via_image_id_list){let l=_via_image_id_list[e];0!==_via_img_metadata[l].regions.length&&(0<!_via_img_metadata[l].groupedRegions.groups.size&&!this.updateSliceRegion(l,!1)?console.log("Slice regions updated but not grouped yet. Skipping image: "+l):await new Promise(e=>{let t=setInterval(()=>{0<_via_img_metadata[l].groupedRegions.groups.size&&(clearInterval(t),e())},100)}).then(()=>{var t=[];if(0!==_via_img_metadata[l].groupedRegions.groups){for(let e=0;e<_via_img_metadata[l].groupedRegions.groupBy.length;e++){var _=_via_img_metadata[l].groupedRegions.groupBy[e],s={ID:e+1,Area:0,InfarctArea:0,RiskArea:0,SliceScore:"n/a",InfarctScore:"n/a",RiskScore:"n/a"},n={score_exists:!1,Slice:0,Slice_no:0,Infarct:0,Infarct_no:0,Risk:0,Risk_no:0};_via_img_metadata[l].groupedRegions.groupIDs.has(_)&&(s.ID=_via_img_metadata[l].groupedRegions.groupIDs.get(_)),s.Area=this.calcPolygonArea(_via_img_metadata[l].regions[_].shape_attributes.all_points_x,_via_img_metadata[l].regions[_].shape_attributes.all_points_y),_via_img_metadata[l].regions[_].hasOwnProperty("score")&&(n.score_exists=!0,n.Slice+=_via_img_metadata[l].regions[_].score,n.Slice_no++);for(let e=0;e<_via_img_metadata[l].getGroupedRegion(_).length;e++){var r=_via_img_metadata[l].regions[_via_img_metadata[l].getGroupedRegion(_)[e]],o=this.calcPolygonArea(r.shape_attributes.all_points_x,r.shape_attributes.all_points_y);if(r.hasOwnProperty("score"))switch(n.score_exists=!0,r.region_attributes.Type){case"Slice":"n/a"!==r.score&&(n.Slice+=r.score,n.Slice_no++);break;case"Risk":"n/a"!==r.score&&(n.Risk+=r.score,n.Risk_no++);break;case"Infarct":"n/a"!==r.score&&(n.Infarct+=r.score,n.Infarct_no++)}switch(r.region_attributes.Type){case"Slice":s.Area-=o;break;case"Risk":s.RiskArea+=o;break;case"Infarct":s.InfarctArea+=o}}n.score_exists&&(s.SliceScore=0<n.Slice_no&&0<n.Slice?n.Slice/n.Slice_no:"n/a",s.InfarctScore=0<n.Infarct_no&&0<n.Infarct?n.Infarct/n.Infarct_no:"n/a",s.RiskScore=0<n.Risk_no&&0<n.Risk?n.Risk/n.Risk_no:"n/a"),t.push(s)}let i=_via_image_filename_list[e],a=_via_img_metadata[l].file_attributes.ID||"";t.forEach((e,t)=>{g.push([i,a,e.ID,e.Area,e.InfarctArea,e.RiskArea,e.SliceScore,e.InfarctScore,e.RiskScore])}),d++}}))}var e="data:text/csv;charset=utf-8,"+g.map(e=>e.join(",")).join("\n"),e=encodeURI(e),t=document.createElement("a");t.setAttribute("href",e),t.setAttribute("download","RegionSizes.csv"),document.body.appendChild(t),t.click()};let plugin=new Science_plugins;class ImgManipulation{constructor(){this.image=null,this.contrast=100,this.brightness=100,this.hue=0,this.saturation=100,this.brightnessSlider=$("#brightnessRange"),this.contrastSlider=$("#contrastRange"),this.hueSlider=$("#hueRange"),this.saturationSlider=$("#saturationRange"),this.setSliderValues(),this.addEventListeners()}addEventListeners(){this.brightnessSlider.on("input",()=>{this.brightness=this.brightnessSlider.val(),this.changeImgSettings()}),this.contrastSlider.on("input",()=>{this.contrast=this.contrastSlider.val(),this.changeImgSettings()}),this.hueSlider.on("input",()=>{this.hue=this.hueSlider.val(),this.changeImgSettings()}),this.saturationSlider.on("input",()=>{this.saturation=this.saturationSlider.val(),this.changeImgSettings()})}hook(e=_via_current_image){this.image=document.getElementById(e.id),this.image.style.transformOrigin="top left"}setSliderValues(){this.brightnessSlider.val(this.brightness),this.contrastSlider.val(this.contrast),this.hueSlider.val(this.hue),this.saturationSlider.val(this.saturation)}backToSidebar(){$("#image_man_container").addClass("d-none"),$("#sidebar_container").removeClass("d-none")}async show(){this.image.classList.add("position-absolute"),this.image.classList.remove("d-none")}changeImgSettings(){this.image.style.filter=`brightness(${this.brightnessSlider.val()}%) contrast(${this.contrastSlider.val()}%) hue-rotate(${this.hueSlider.val()}deg) saturate(${this.saturationSlider.val()}%)`}resetFilters(){this.brightness=100,this.contrast=100,this.hue=0,this.saturation=100,this.setSliderValues(),this.changeImgSettings()}resize(e=_via_current_image_width,t=_via_current_image_height){this.image.width=e,this.image.height=t}scaleImage(e){this.image.style.transform=`scale(${e})`}}let image=new ImgManipulation;class Zoom{constructor(){this.panzoom=Panzoom(image_panel,{maxScale:12,minScale:.1,disablePan:!0}),this.addEventListeners(),this.isZooming=!1,this.init=!0}setStartScale(e){let t=0;t=_via_current_image_width>_via_current_image_height?_via_current_image_width/2-_via_current_image_width*e/2:_via_current_image_height/2-_via_current_image_height*e/2,this.panzoom.setOptions({startScale:e,startX:-t})}addEventListeners(){image_panel.addEventListener("wheel",e=>{e.preventDefault(),this.zoomWithWheel(e)}),image_panel.addEventListener("panzoomzoom",e=>{this.init?this.init=!1:this.startPanzoomOperation()}),image_panel.addEventListener("panzoomend",e=>{this.endPanzoomOperation()})}startPanzoomOperation(){this.isZooming=!0}endPanzoomOperation(){this.isZooming&&(this.fixCanvasBlur(),this.isZooming=!1)}showFixCanvasBlurButton(){let e=document.createElement("button");e.innerHTML="Fix canvas blur",e.style.position="absolute",e.style.color="white",e.style.top="8px",e.style.right="80px",e.style.zIndex="100",e.classList.add("btn"),e.classList.add("btn-primary"),e.onclick=()=>{this.fixCanvasBlur(),document.body.removeChild(e)},setTimeout(()=>{document.body.removeChild(e)},5e3),document.body.appendChild(e)}zoomWithWheel(e){this.panzoom.zoomWithWheel(e)}handleMoseDown(e){this.panzoom.handleDown(e)}handleMoseUp(e){this.panzoom.handleUp(e)}handleMoseMove(e){this.panzoom.handleMove(e)}fixCanvasBlur(){buffer.imgLoaded&&requestAnimationFrame(()=>{let e=this.panzoom.getScale();1<e&&(e=1),drawing.setBoundarySize(Math.round(4/e)),drawing.setFontSize(Math.round(6/e)),drawing.redrawRegCanvas(),_via_reg_canvas.focus()})}zoomIn(){this.panzoom.zoomIn()}zoomOut(){this.panzoom.zoomOut()}resetZoom(){this.panzoom.reset(),image.scaleImage(1),_via_reg_canvas.style.transform="scale(1)",_via_canvas_scale=1}enablePan(){this.panzoom.setOptions({disablePan:!1})}disablePan(){this.panzoom.setOptions({disablePan:!0})}}let zoom=new Zoom;class FileMetadata{constructor(e,t){this.filename=e,this.size=t,this.regions=[],this.file_attributes={ID:{type:"text",description:"",default_value:""},Treatment:{type:"text",description:"",default_value:""}},this.fileAttributes=new Map([["ID",{type:"text",description:"",default_value:""}],["Treatment",{type:"text",description:"",default_value:""}]]),this.lockedRegions=new Set,this.groupedRegions={groupBy:new Array,groups:new Map,groupIDs:new Map},this.autoAnnotated=!1}setFilename(e){this.filename=e}setFileAttributes(e){this.file_attributes=e}addRegion(e){this.regions.push(e)}addLockedRegion(e){this.lockedRegions.add(e)}isRegionLocked(e){return this.lockedRegions.has(e)}clearLockedRegions(){this.lockedRegions.clear()}clearRegionLock(e){this.lockedRegions.delete(e)}clearRegions(){this.regions=[]}loadFromJSON(e){e.regions&&(this.regions=e.regions),e.file_attributes&&(this.file_attributes=e.file_attributes),"Set"===e.lockedRegions.dataType?this.lockedRegions=new Set(e.lockedRegions.value):this.lockedRegions=new Set,e.groupedRegions.groupBy&&(this.groupedRegions.groupBy=new Set(e.groupedRegions.groupBy.value)),e.groupedRegions.groups&&(this.groupedRegions.groups=new Map(e.groupedRegions.groups.value)),e.groupedRegions.groupIDs&&(this.groupedRegions.groupIDs=new Map(e.groupedRegions.groupIDs.value)),e.autoAnnotated&&(this.autoAnnotated=e.autoAnnotated)}addGroupedRegion(e,t){this.groupedRegions.groups.set(e,t)}getGroupedRegion(e){return this.groupedRegions.groups.get(e)}clearGroupedRegions(){this.groupedRegions.groups=new Map,this.groupedRegions.groupBy=[]}clearGroups(){this.groupedRegions.groups=new Map}clearGroupedRegion(e){this.groupedRegions.groups.delete(e)}isGroupable(){return 0<this.groupedRegions.groupBy.length}isGroupedKey(e){return this.groupedRegions.groups.has(e)}}class ImageBuffer{constructor(){this.loading=$("#loading"),this.imgLoading=!1,this.imgLoaded=!1,this.bufferIdList=[],this.bufferTimestamp=[],this.preLoadedId=-1,this.preLoadPromiseList=[]}stopLoading(){this.imgLoading=!1}showImage(i){if(this.loading.removeClass("d-none"),!this.imgLoading){var e=_via_image_id_list[i];if(_via_img_metadata.hasOwnProperty(e)){if(!(void 0!==_via_img_fileref[e]&&_via_img_fileref[e]instanceof File||void 0!==_via_img_src[e]&&""!==_via_img_src[e])){if(is_url(_via_img_metadata[e].filename))return _via_img_src[e]=_via_img_metadata[e].filename,void this.showImage(i);this.searchPathSearch(i)}this.bufferIdList.includes(i)?(this.imgLoaded=!1,this.showImageFromBuffer(i).then(function(e){Promise.all(this.preLoadPromiseList).then(function(e){this.preLoadPromiseList=[];var t=this.startPreload(i,0);this.preLoadPromiseList.push(t)}.bind(this)),undoredo_worker.postMessage({commands:"reset"})}.bind(this),function(e){console.log("showImageFromBuffer() failed for file: "+_via_image_filename_list[e]),this.loading.addClass("d-none"),this.imgLoading=!1}.bind(this))):(this.imgLoading=!0,this.addImageToBuffer(i).then(function(e){this.imgLoading=!1,this.showImage(i)}.bind(this),function(e){this.imgLoading=!1,this.loading.addClass("d-none"),Message.showError("The requested image does not exist!"),show_page_404(i)}.bind(this)))}else this.loading.addClass("d-none"),Message.showError("The requested image does not exist!")}}searchPathSearch(t){var e=_via_file_get_search_path_list();0===e.length&&e.push(""),_via_file_resolve(t,e).then(function(e){this.showImage(t)}.bind(this),function(e){this.loading.addClass("d-none"),Message.showError("The requested image does not exist!"),show_page_404(t)}.bind(this))}showImageFromBuffer(a){return new Promise(function(e,t){this.hideCurrentImage();var i="bim"+a;((_via_current_image=document.getElementById(i))?(_via_image_index=a,_via_image_id=_via_image_id_list[_via_image_index],_via_current_image_filename=_via_img_metadata[_via_image_id].filename,this.imgLoaded=!0,image.hook(),this.postImageLoad(a),e):t)(a)}.bind(this))}postImageLoad(e){_via_img_metadata[_via_image_id].clearGroupedRegions();e=this.bufferIdList.indexOf(e),this.bufferTimestamp[e]=Date.now(),drawing.drawingSetVariables(),_via_current_image_width=_via_current_image.naturalWidth,_via_current_image_height=_via_current_image.naturalHeight,0!==_via_current_image_width&&0!==_via_current_image_height||(_via_current_image_width=640,_via_current_image_height=480),e=document.documentElement;let t=e.clientWidth-leftsidebar.clientWidth-20;t<50&&(t=e.clientWidth),"none"===leftsidebar.style.display&&(t=e.clientWidth);var i,e=e.clientHeight-2*ui_top_panel.offsetHeight;_via_canvas_width=_via_current_image_width,_via_canvas_height=_via_current_image_height,_via_canvas_width>t&&(i=t/_via_current_image.naturalWidth,_via_canvas_width=t,_via_canvas_height=_via_current_image.naturalHeight*i),_via_canvas_height>e&&(i=e/_via_canvas_height,_via_canvas_height=e,_via_canvas_width*=i),_via_canvas_width=Math.round(_via_canvas_width),_via_canvas_height=Math.round(_via_canvas_height),_via_canvas_scale=_via_current_image.naturalWidth/_via_canvas_width,zoom.setStartScale(1/_via_canvas_scale),_via_canvas_scale_without_zoom=_via_canvas_scale,_via_canvas_scale=1,drawing.setAllCanvasSize(_via_current_image_width,_via_current_image_height),toggle_all_regions_selection(!1),set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE),sidebar.img_fn_list_ith_entry_selected(_via_image_index,!0),sidebar.img_fn_list_scroll_to_current_file(),sidebar.annotation_editor_update_content(),_via_reg_canvas.focus(),zoom.resetZoom(),_via_load_canvas_regions(),drawing.redrawRegCanvas(),image.resize(),zoom.fixCanvasBlur(),image.show().then(e=>this.loading.addClass("d-none")),void 0===_via_img_metadata[_via_image_id].autoAnnotated&&_via_img_metadata[_via_image_id].autoAnnotated&&plugin.updateSliceRegion()}addImageToBuffer(a){return new Promise(function(t,i){var e;this.bufferIdList.includes(a)?t(a):(e=_via_image_id_list[a],_via_img_metadata.hasOwnProperty(e)?_via_img_fileref[e]instanceof File?this.loadFromFileRef(a,e).then(function(e){t(e)},function(e){i(e),this.loading.addClass("d-none")}):void 0===_via_img_src[e]||""===_via_img_src[e]?(i(a),this.loading.addClass("d-none")):this.loadFromSrc(a,e).then(function(e){t(e)},function(e){i(e),this.loading.addClass("d-none")}):i(a))}.bind(this),!1)}loadFromFileRef(_,s){return new Promise(function(t,e){let i=URL.createObjectURL(_via_img_fileref[s]),a=new Image;a.id="bim"+_,a.src=i,a.alt="Image loaded from base64 data of a local file selected by user.",a.classList.add("d-none"),image_panel.insertBefore(a,_via_reg_canvas),a.addEventListener("error",function(){URL.revokeObjectURL(i),project.fileLoadOnFail(_),e(_),this.loading.addClass("d-none")}),a.addEventListener("load",function(){URL.revokeObjectURL(i),img_stat_set(_,[a.naturalWidth,a.naturalHeight]),image_panel.insertBefore(a,_via_reg_canvas),project.fileLoadOnSuccess(_),sidebar.img_fn_list_ith_entry_remove_css_class(_,"text-muted");var e=this.bufferIdList.length;this.bufferIdList.push(_),this.bufferTimestamp[e]=Date.now(),t(_)}.bind(this))}.bind(this),!1)}loadFromSrc(a,_){return new Promise(function(t,e){let i=new Image;i.id="bim"+a,_via_img_src[_]=_via_img_src[_].replace("#","%23"),i.src=_via_img_src[_],_via_img_src[_].startsWith("data:image")?i.alt="Source: image data in base64 format":i.alt="Source: "+_via_img_src[_],image_panel.insertBefore(i,_via_reg_canvas),i.addEventListener("abort",function(){project.fileLoadOnFail(a),e(a)}),i.addEventListener("error",function(){project.fileLoadOnFail(a),e(a),this.loading.addClass("d-none")}),i.addEventListener("load",function(){img_stat_set(a,[i.naturalWidth,i.naturalHeight]),image_panel.insertBefore(i,_via_reg_canvas),project.fileLoadOnSuccess(a),sidebar.img_fn_list_ith_entry_remove_css_class(a,"text-muted");var e=this.bufferIdList.length;this.bufferIdList.push(a),this.bufferTimestamp[e]=Date.now(),t(a)}.bind(this),!1)}.bind(this),!1)}hideCurrentImage(){sidebar.img_fn_list_ith_entry_selected(_via_image_index,!1),drawing.clearCanvas(),_via_current_image&&_via_current_image.classList.add("d-none")}startPreload(i,e){return new Promise(function(t,e){this.preLoadedId=i,this.preloadImage(this.preLoadedId,0).then(function(e){t(e)})}.bind(this))}preloadImage(_,s){return new Promise(function(i,e){let a=this.getPreloadImgIndex(_,s);if(this.preLoadedId!==_via_image_index)i([]);else{if(this.bufferIdList.length>settings.bufferSize)for(;this.bufferIdList.length>settings.bufferSize;)if(console.log("Buffer full. Removing least useful image ..."),console.log("Buffer size: ",this.bufferIdList.length),console.log("Buffer: ",settings.bufferSize),this.removeLeastUsefulImage(),_via_image_index!==this.preLoadedId)return void i([]);this.addImageToBuffer(a).then(function(t){var e;_via_image_index===this.preLoadedId&&(e=s+1)!==VIA_IMG_PRELOAD_COUNT?this.preloadImage(_,e).then(function(e){e.push(t),i(e)}):i([t])}.bind(this),function(e){var t=s+1;t!==VIA_IMG_PRELOAD_COUNT?this.preloadImage(a,t).then(function(e){i(e)}):i([])}.bind(this))}}.bind(this))}getPreloadImgIndex(e,t){let i=e+VIA_IMG_PRELOAD_INDICES[t];return(i<0||i>=_via_img_count)&&(i<0?i=_via_img_count+i:i-=_via_img_count),i}removeLeastUsefulImage(){var e=this.getNotInPreloadList(),e=this.getOldestInList(e);this.bufferIdList[e]!==_via_image_index?this.removeImageFromBuffer(e):(e=this.getFurthestFromCurrentImg(),this.removeImageFromBuffer(e))}removeImageFromBuffer(e){var t=this.bufferIdList[e],i=document.getElementById("bim"+t);i&&(this.bufferIdList.splice(e,1),this.bufferTimestamp.splice(e,1),i.parentNode.removeChild(i),sidebar.img_fn_list_ith_entry_add_css_class(t,"text-muted"))}emptyBuffer(){let e,t;for(t=this.bufferIdList.length,e=0;e<t;++e){var i=this.bufferIdList[e],a=document.getElementById("bim"+i);a&&(a.parentNode.removeChild(a),sidebar.img_fn_list_ith_entry_add_css_class(i,"text-muted"))}this.bufferIdList=[],this.bufferTimestamp=[]}getOldestInList(e){let t;var i=e.length;let a=-1,_=Date.now();for(t=0;t<i;++t){var s=e[t];this.bufferTimestamp[s]<_&&(_=this.bufferTimestamp[t],a=t)}return a}getFurthestFromCurrentImg(){let e,t,i,a;var _=this.bufferIdList.length;let s=0,n=(t=Math.abs(this.bufferIdList[0]-_via_image_index),i=_via_img_count-t,Math.min(t,i));for(e=1;e<_;++e)t=Math.abs(this.bufferIdList[e]-_via_image_index),i=_via_img_count-t,(a=Math.min(t,i))>n&&(n=a,s=e);return s}getNotInPreloadList(){var t=this.getPreloadList(),i=[];for(let e=0;e<this.bufferIdList.length;e++)t.includes(this.bufferIdList[e])||i.push(e);return i}getPreloadList(){var i=[_via_image_index];for(let t=0;t<VIA_IMG_PRELOAD_COUNT;++t){let e=_via_image_index+VIA_IMG_PRELOAD_INDICES[t];(e=e<0?_via_img_count+e:e)>=_via_img_count&&(e-=_via_img_count),i.push(e)}return i}}let buffer=new ImageBuffer;function file_metadata(e,t){this.filename=e,this.size=t,this.regions=[],this.file_attributes={ID:{type:"text",description:"",default_value:""},Treatment:{type:"text",description:"",default_value:""}},this.fileAttributes=new Map([["ID",{type:"text",description:"",default_value:""}],["Treatment",{type:"text",description:"",default_value:""}]]),this.lockedRegions=new Set,this.groupedRegions=new Map,this.autoAnnotated=!1}function _via_init(){console.log(SAA_NAME),show_message(SAA_NAME+" ("+SAA_SHORT_NAME+") version "+SAA_VERSION+". Ready !"),_via_is_debug_mode&&(document.getElementById("ui_top_panel").innerHTML+="<span>DEBUG MODE</span>"),project.initDefaultProject(),_via_init_keyboard_handlers(),image_grid_init(),show_single_image_view(),update_attributes_update_panel(),attribute_update_panel_set_active_button(),sidebar.annotation_editor_set_active_button(),sidebar.annotation_editor_toggle_all_regions_editor(),"function"==typeof _via_load_submodules&&(console.log("Loading VIA submodule"),setTimeout(async function(){await _via_load_submodules()},100))}function _via_init_keyboard_handlers(){window.addEventListener("keydown",_via_window_keydown_handler,!1),_via_reg_canvas.addEventListener("keydown",_via_reg_canvas_keydown_handler,!1),_via_reg_canvas.addEventListener("keyup",_via_reg_canvas_keyup_handler,!1)}function download_as_image(){var e,t,i,a;_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE?show_message("This functionality is only available in single image view mode"):((a=document.createElement("canvas")).width=_via_reg_canvas.width,a.height=_via_reg_canvas.height,(e=a.getContext("2d")).drawImage(_via_current_image,0,0,_via_reg_canvas.width,_via_reg_canvas.height),e.drawImage(_via_reg_canvas,0,0),e="image/jpeg",_via_current_image.src.startsWith("data:")&&(t=_via_current_image.src.indexOf(":",0),i=_via_current_image.src.indexOf(";",t),e=_via_current_image.src.substring(t+1,i)),(t=a.toDataURL(e)).replace(e,"image/octet-stream"),(i=document.createElement("a")).href=t,i.target="_blank",i.download=_via_current_image_filename,a=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}),i.dispatchEvent(a))}async function download_grid_selected_images(){for(var e of _via_image_grid_selected_img_index_list){var s=_via_image_id_list[e],t=_via_img_metadata[s].filename;let i=$("#bim"+e)[0];void 0===i&&(await buffer.addImageToBuffer(e),i=$("#bim"+e)[0]);e=document.createElement("canvas");e.width=i.naturalWidth,e.height=i.naturalHeight;let a=e.getContext("2d");a.drawImage(i,0,0);let _;var n=_via_img_metadata[s].regions.length;for(_=0;_<n;++_)if(image_grid_is_region_in_current_group(_via_img_metadata[s].regions[_].region_attributes)){var r=_via_img_metadata[s].regions[_].shape_attributes;let t;switch(r.name){case VIA_REGION_SHAPE.RECT:t=[r.x,r.y,r.x+r.width,r.y+r.height];break;case VIA_REGION_SHAPE.CIRCLE:t=[r.cx,r.cy,r.cx+r.r,r.cy+r.r];break;case VIA_REGION_SHAPE.ELLIPSE:t=[r.cx,r.cy,r.cx+r.rx,r.cy+r.ry];break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:let e;for(t=[],e=0;e<r.all_points_x.length;++e)t.push(r.all_points_x[e]),t.push(r.all_points_y[e]);break;case VIA_REGION_SHAPE.POINT:t=[r.cx,r.cy]}var o=i.height/i.naturalHeight,o=new _via_region(r.name,_,t,o,0,0).get_svg_element(),o=(o.setAttribute("class","region"),o.setAttribute("fill","none"),o.setAttribute("stroke","red"),o.setAttribute("stroke-width","2"),(new XMLSerializer).serializeToString(o));let e=new Image;e.src="data:image/svg+xml;base64,"+btoa(o),e.onload=function(){a.drawImage(e,0,0)}}var e=e.toDataURL("image/jpeg"),l=(e.replace("image/jpeg","image/octet-stream"),document.createElement("a")),e=(l.href=e,l.target="_blank",l.download=t,new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}));l.dispatchEvent(e)}}function clear_display_area(){var e=document.getElementsByClassName("display_area_content");let t;for(t=0;t<e.length;++t)e[t].classList.add("d-none");$("#selection_panel").hide()}function is_content_name_valid(e){for(var t in VIA_DISPLAY_AREA_CONTENT_NAME)if(VIA_DISPLAY_AREA_CONTENT_NAME[t]===e)return!0;return!1}function show_home_panel(){show_single_image_view()}function set_display_area_content(e){is_content_name_valid(e)&&(_via_display_area_content_name_prev=_via_display_area_content_name,clear_display_area(),document.getElementById(e).classList.remove("d-none"),(_via_display_area_content_name=e)===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE)&&$("#selection_panel").show()}function show_single_image_view(){buffer.imgLoaded?(sidebar.img_fn_list_clear_all_style(),buffer.showImage(_via_image_index),set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE),$("#selection_panel").show(),sidebar.annotation_editor_update_content()):(set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO),$("#selection_panel").hide())}function show_image_grid_view(){buffer.imgLoaded?(sidebar.img_fn_list_clear_all_style(),set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID),$("#selection_panel").hide(),image_grid_toolbar_update_group_by_select(),0===_via_image_grid_group_var.length&&image_grid_show_all_project_images(),sidebar.annotation_editor_update_content(),sidebar.edit_file_metadata_in_annotation_editor()):(set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO),$("#selection_panel").hide())}function sel_local_images(){fileInput&&(fileInput.setAttribute("multiple","multiple"),fileInput.accept=".jpg,.jpeg,.png,.bmp",fileInput.onchange=project.addLocalFile,fileInput.click())}function download_all_region_data(i,a){void 0===a&&(a=i),pack_via_metadata(i).then(function(e){var e=new Blob(e,{type:"text/"+a+";charset=utf-8"}),t="via_export";void 0!==_via_settings&&_via_settings.hasOwnProperty("project")&&""!==_via_settings.project.name&&(t=_via_settings.project.name),"csv"===a&&"json"===a||(t+="_"+i+"."+a),save_data_to_local_file(e,t)}.bind(this),function(e){show_message("Failed to download data: ["+e+"]")}.bind(this))}function sel_local_data_file(e){if(fileInput){switch(e){case"annotations":fileInput.accept=".csv,.json",fileInput.onchange=import_annotations_from_file;break;case"annotations_coco":fileInput.accept=".json",fileInput.onchange=load_coco_annotations_json_file;break;case"files_url":fileInput.accept="",fileInput.onchange=import_files_url_from_file;break;case"attributes":fileInput.accept="json",fileInput.onchange=project.importAttributesFromFile;break;default:return void console.log("sel_local_data_file() : unknown type "+e)}fileInput.removeAttribute("multiple"),fileInput.click()}}function import_files_url_from_file(e){for(var t,i=e.target.files,a=0;a<i.length;++a)t=i[a],FileManager.loadTextFile(t,import_files_url_from_csv)}function import_annotations_from_file(e){for(var t,i=e.target.files,a=0;a<i.length;++a)switch((t=i[a]).type){case"":show_message("File type for "+t.name+" cannot be determined! Assuming text/plain.");case"text/plain":case"application/vnd.ms-excel":case"text/csv":FileManager.loadTextFile(t,import_annotations_from_csv);break;case"text/json":case"application/json":FileManager.loadTextFile(t,import_annotations_from_json);break;default:show_message("Annotations cannot be imported from file of type "+t.type)}}function load_coco_annotations_json_file(e){FileManager.loadTextFile(e.target.files[0],import_coco_annotations_from_json)}function import_annotations_from_csv(f){return new Promise(function(e,t){""!==f&&void 0!==f||t();var i=0,a=0,_=0,s=new RegExp("\n|\r|\r\n","g"),n=f.split(s),r=parse_csv_header_line(n[0]);if(r.is_header){for(var o=n.length,l="",g=1;g<o;++g)if("\n"!==n[g].charAt(0)&&""!==n[g].charAt(0)){var d=parse_csv_line(n[g]);if(d.length!==r.csv_column_count)a+=1;else{var c=d[r.filename_index],u=d[r.size_index],h=_via_get_image_id(c,u);if(_via_img_metadata.hasOwnProperty(h)||(h=project.addFile(c,u),""===_via_settings.core.default_filepath?_via_img_src[h]=c:_via_file_resolve_file_to_default_filepath(h),_+=1,""===l&&(l=h)),'"{}"'!==d[r.file_attr_index])for(p in m=json_str_to_map(unescape_from_csv(remove_prefix_suffix_quotes(d[r.file_attr_index]))))_via_img_metadata[h].file_attributes[p]=m[p],project.attributes.file.hasOwnProperty(p)||(project.attributes.file[p]={type:"text"});var m,p,v=new File_Region;if('"{}"'!==d[r.region_shape_attr_index])for(p in m=json_str_to_map(unescape_from_csv(remove_prefix_suffix_quotes(d[r.region_shape_attr_index]))))v.shape_attributes[p]=m[p];if('"{}"'!==d[r.region_attr_index])for(p in m=json_str_to_map(unescape_from_csv(remove_prefix_suffix_quotes(d[r.region_attr_index]))))v.region_attributes[p]=m[p],project.attributes.region.hasOwnProperty(p)||(project.attributes.region[p]={type:"text"});(0<Object.keys(v.shape_attributes).length||0<Object.keys(v.region_attributes).length)&&(_via_img_metadata[h].regions.push(v),i+=1)}}show_message("Import Summary : ["+_+"] new files, ["+i+"] regions, ["+a+"] malformed csv lines."),_&&sidebar.update_img_fn_list(),buffer.imgLoaded?i&&(update_attributes_update_panel(),sidebar.annotation_editor_update_content(),_via_load_canvas_regions(),drawing.redrawRegCanvas(),_via_reg_canvas.focus()):_&&(s=_via_image_id_list.indexOf(l),buffer.showImage(s)),e([_,i,a])}else show_message("Header line missing in the CSV file"),t()})}function parse_csv_header_line(e){return"#filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes"===e||"filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes"===e?{is_header:!0,filename_index:0,size_index:1,file_attr_index:2,region_shape_attr_index:5,region_attr_index:6,csv_column_count:7}:{is_header:!1}}function import_coco_annotations_from_json(A){return new Promise(function(e,t){if(""===A||void 0===A)show_message("Empty file");else{var i=JSON.parse(A);if(i.hasOwnProperty("info")&&i.hasOwnProperty("categories")&&i.hasOwnProperty("annotations")&&i.hasOwnProperty("images")){var a,_={};for(m in i.categories){var s=i.categories[m].supercategory,n=i.categories[m].id,r=i.categories[m].name;project.attributes.region.hasOwnProperty(s)||(project.attributes.region[s]={type:VIA_ATTRIBUTE_TYPE.RADIO,description:'coco["categories"]['+m+"]="+JSON.stringify(i.categories[m]),options:{},default_options:{}}),project.attributes.region[s].options[n]=r,_[n]=s}for(a in project.attributes.region)5<Object.keys(project.attributes.region[a].options).length&&(project.attributes.region[a].type=VIA_ATTRIBUTE_TYPE.DROPDOWN);var o,l={};for(o in i.annotations){var g=i.annotations[o].image_id;l.hasOwnProperty(g)||(l[g]=[]),l[g].push(o)}_via_img_metadata={},_via_image_id_list=[],_via_image_filename_list=[];_via_img_count=0;var d,c=0;for(d in i.images){var u=i.images[d].id,h=i.images[d].hasOwnProperty("coco_url")&&""!==i.images[d].coco_url?i.images[d].coco_url:i.images[d].file_name;if(_via_img_metadata[u]={filename:h,size:-1,regions:[],file_attributes:{width:i.images[d].width,height:i.images[d].height}},_via_image_id_list.push(u),_via_image_filename_list.push(h),_via_img_count+=1,l.hasOwnProperty(u))for(var m in l[u]){for(var p=i.annotations[l[u][m]],v=polygon_to_bbox(p.segmentation[0]),f=!0,b=0;b<p.bbox.length;++b)if(p.bbox[b]!==v[b]){f=!1;break}var y={shape_attributes:{},region_attributes:{}},w=_[p.category_id],I=p.category_id.toString();if(y.region_attributes[w]=I,8===p.segmentation[0].length&&f)y.shape_attributes={name:"rect",x:p.bbox[0],y:p.bbox[1],width:p.bbox[2],height:p.bbox[3]};else{y.shape_attributes={name:"polygon",all_points_x:[],all_points_y:[]};for(b=0;b<p.segmentation[0].length;b+=2)y.shape_attributes.all_points_x.push(p.segmentation[0][b]),y.shape_attributes.all_points_y.push(p.segmentation[0][b+1])}_via_img_metadata[u].regions.push(y),c+=1}}show_message("Import Summary : ["+_via_img_count+"] new files, ["+c+"] regions."),_via_img_count&&sidebar.update_img_fn_list(),buffer.imgLoaded?c&&(update_attributes_update_panel(),sidebar.annotation_editor_update_content(),_via_load_canvas_regions(),drawing.redrawRegCanvas(),_via_reg_canvas.focus()):_via_img_count&&buffer.showImage(0),e([_via_img_count,c,0])}else show_message("File does not contain valid annotations in COCO format.")}})}function import_annotations_from_json(c){return new Promise(function(e,t){if(""!==c&&void 0!==c){var i,a=JSON.parse(c),_=0,s=0;for(i in a){for(var n in _via_img_metadata.hasOwnProperty(i)||(project.addFile(a[i].filename,a[i].size,i),""===settings.defaultPath?_via_img_src[i]=a[i].filename:_via_file_resolve_file_to_default_filepath(i),s+=1),a[i].file_attributes)""===n||(_via_img_metadata[i].file_attributes[n]=a[i].file_attributes[n],project.attributes.file.hasOwnProperty(n))||(project.attributes.file[n]={type:"text"});var r,o=a[i].regions;for(r in o){var l,g,d=new File_Region;for(l in o[r].shape_attributes)d.shape_attributes[l]=o[r].shape_attributes[l];for(g in o[r].region_attributes)""===g||(d.region_attributes[g]=o[r].region_attributes[g],project.attributes.region.hasOwnProperty(g))||(project.attributes.region[g]={type:"text"});(0<Object.keys(d.shape_attributes).length||0<Object.keys(d.region_attributes).length)&&(_via_img_metadata[i].regions.push(d),_+=1)}}show_message("Import Summary : ["+s+"] new files, ["+_+"] regions, [0] malformed entries."),s&&sidebar.update_img_fn_list(),buffer.imgLoaded?_&&(update_attributes_update_panel(),sidebar.annotation_editor_update_content(),_via_load_canvas_regions(),drawing.redrawRegCanvas(),_via_reg_canvas.focus()):s&&buffer.showImage(0),e([s,_,0])}})}function parse_csv_line(e,t){if(void 0===e||0===e.length)return[];void 0===t&&(t=",");for(var i=!1,a=0,_=[],s=0;s<e.length;)e.charAt(s)===t?(i||(_.push(e.substr(a,s-a)),a=s+1),s+=1):'"'===e.charAt(s)?i?'"'===e.charAt(s+1)?s+=2:(i=!1,s+=1):(i=!0,a=s,s+=1):s+=1;return _.push(e.substr(a)),_}function json_str_to_map(e){return void 0===e||0===e.length?{}:JSON.parse(e)}function map_to_json(e){var t,i=[];for(t in e){var a=e[t],_=JSON.stringify(t),_=(_+=VIA_CSV_KEYVAL_SEP)+JSON.stringify(a);i.push(_)}return"{"+i.join(VIA_CSV_SEP)+"}"}function escape_for_csv(e){return e.replace(/["]/g,'""')}function unescape_from_csv(e){return e.replace(/""/g,'"')}function remove_prefix_suffix_quotes(e){return'"'===e.charAt(0)&&'"'===e.charAt(e.length-1)?e.substring(1,e.length-1):e}function clone_image_region(e){var t,i=new File_Region;for(t in e.shape_attributes)i.shape_attributes[t]=clone_value(e.shape_attributes[t]);for(t in e.region_attributes)i.region_attributes[t]=clone_value(e.region_attributes[t]);return i}function clone_value(e){if("object"!=typeof e)return e;if(Array.isArray(e))return e.slice(0);var t,i={};for(t in e)e.hasOwnProperty(t)&&(i[t]=clone_value(e[t]));return i}function _via_get_image_id(e,t){return void 0===t?e:e+t}function load_text_file(t,e){var i;t&&((i=new FileReader).addEventListener("progress",function(e){show_message("Loading data from file : "+t.name+" ... ")},!1),i.addEventListener("error",function(){show_message("Error loading data text file :  "+t.name+" !"),e("")},!1),i.addEventListener("load",function(){e(i.result)},!1),i.readAsText(t,"utf-8"))}function import_files_url_from_csv(o){return new Promise(function(e,t){""!==o&&void 0!==o||t();for(var i,a=0,t=new RegExp("\n|\r|\r\n","g"),_=o.split(t),s=_.length,n="",r=0;r<s;++r)"\n"===_[r].charAt(0)||""===_[r].charAt(0)?0:(i=project.addUrlFile(_[r]),""===n&&(n=i),a+=1);show_message("Added "+a+" files to project"),a&&(t=_via_image_id_list.indexOf(n),buffer.showImage(t),sidebar.update_img_fn_list())})}function pack_via_metadata(g){return new Promise(function(i,t){if("csv"===g){var e,a=[];for(e in a.push("filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes"),_via_img_metadata){var _=escape_for_csv(map_to_json(_via_img_metadata[e].file_attributes)),s="\n"+_via_img_metadata[e].filename,n=(s=s+(","+_via_img_metadata[e].size)+(',"'+_+'"'),_via_img_metadata[e].regions);if(0!==n.length)for(var r=0;r<n.length;++r){var o=[];o.push(s),o.push(n.length),o.push(r);var l='"'+escape_for_csv(map_to_json(n[r].shape_attributes))+'"';o.push(l);l='"'+escape_for_csv(map_to_json(n[r].region_attributes))+'"';o.push(l),a.push(o.join(VIA_CSV_SEP))}else a.push(s+',0,0,"{}","{}"')}i(a)}"coco"===g?img_stat_set_all().then(function(e){var t=export_project_to_coco_format();i([t])}.bind(this),function(e){t(e)}.bind(this)):i([JSON.stringify(_via_img_metadata)])}.bind(this))}function export_project_to_coco_format(){var e={info:{},images:[],annotations:[],licenses:[],categories:[]},t=(e.info={year:(new Date).getFullYear(),version:"1.0",description:"VIA project exported to COCO format using VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via/)",contributor:"",url:"http://www.robots.ox.ac.uk/~vgg/software/via/",date_created:(new Date).toString()},0),i=!(e.licenses=[{id:0,name:"Unknown License",url:""}]);for(h in _via_img_metadata)if(Number.isNaN(parseInt(h))){i=!0;break}if(i){var a=[];for(s in project.attributes)if(VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(project.attributes[s].type))for(var _ in project.attributes[s].options){if(a.includes(_)||Number.isNaN(parseInt(_))){i=!0;break}a.push(i)}}var s,n,r={},o=1;for(s in project.attributes.region)if(VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(project.region[s].type))for(var _ in project.project.attributes.region[s].options)i?(n=o,o+=1):n=parseInt(_),e.categories.push({supercategory:s,id:n,name:project.attributes.region[s].options[_]}),r[_]=n;var l,g=1,d=1;for(l in _via_image_id_list){var c,u,h=_via_image_id_list[l],m=_via_settings.core.default_filepath+_via_img_metadata[h].filename;for(u in _via_img_fileref[h]instanceof File&&(m=_via_img_fileref[h].filename),i?(c=d,d+=1):c=parseInt(h),e.images.push({id:c,width:_via_img_stat[l][0],height:_via_img_stat[l][1],file_name:_via_img_metadata[h].filename,license:0,flickr_url:m,coco_url:m,date_captured:""}),_via_img_metadata[h].regions){var p=_via_img_metadata[h].regions[u];if(VIA_COCO_EXPORT_RSHAPE.includes(p.shape_attributes.name)){var v,f=via_region_shape_to_coco_annotation(p.shape_attributes);f.id=g,f.image_id=c,Object.keys(p.region_attributes);for(v in p.region_attributes){var b=p.region_attributes[v];r.hasOwnProperty(b)?(f.category_id=r[b],e.annotations.push(f),g+=1):t+=1}}else t+=1}}return show_message("Skipped "+t+" annotations. COCO format only supports the following attribute types: "+JSON.stringify(VIA_COCO_EXPORT_ATTRIBUTE_TYPE)+" and region shapes: "+JSON.stringify(VIA_COCO_EXPORT_RSHAPE)),[JSON.stringify(e)]}function via_region_shape_to_coco_annotation(e){var t={segmentation:[[]],area:[],bbox:[],iscrowd:0};switch(e.name){case"rect":var i=e.x,a=e.y,_=i+(p=parseInt(e.width)),s=a+(v=parseInt(e.height));t.segmentation[0]=[i,a,_,a,_,s,i,s],t.area=p*v,t.bbox=[i,a,p,v];break;case"point":var n=e.cx;t.keypoints=[n,e.cy,2],t.num_keypoints=1;break;case"circle":c=e.r,u=e.r;for(var r=Math.PI/180,o=0;o<360;o+=VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE){var l=o*r,g=e.cx+c*Math.cos(l),d=e.cy+u*Math.sin(l);t.segmentation[0].push(fixfloat(g),fixfloat(d))}t.bbox=polygon_to_bbox(t.segmentation[0]),t.area=t.bbox[2]*t.bbox[3];break;case"ellipse":c=e.rx,u=e.ry;for(var c,u,h=0,r=(e.hasOwnProperty("theta")&&(h=e.theta),Math.PI/180),o=0;o<360;o+=VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE){l=o*r,g=e.cx+c*Math.cos(l)*Math.cos(h)-u*Math.sin(l)*Math.sin(h),d=e.cy+c*Math.cos(l)*Math.sin(h)+u*Math.sin(l)*Math.cos(h);t.segmentation[0].push(fixfloat(g),fixfloat(d))}t.bbox=polygon_to_bbox(t.segmentation[0]),t.area=t.bbox[2]*t.bbox[3];break;case"polygon":t.segmentation[0]=[];var m,i=1/0,a=1/0,_=-1/0,s=-1/0;for(m in e.all_points_x)t.segmentation[0].push(e.all_points_x[m]),t.segmentation[0].push(e.all_points_y[m]),e.all_points_x[m]<i&&(i=e.all_points_x[m]),e.all_points_y[m]<a&&(a=e.all_points_y[m]),e.all_points_x[m]>_&&(_=e.all_points_x[m]),e.all_points_y[m]>s&&(s=e.all_points_y[m]);var p=_-i,v=s-a;t.bbox=[i,a,p,v],t.area=p*v}return t}function save_data_to_local_file(e,t){var i=document.createElement("a"),e=(i.href=URL.createObjectURL(e),i.download=t,new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}));i.dispatchEvent(e)}function init_message_panel(){var e=document.getElementById("message_panel");e.addEventListener("mousedown",function(){this.style.display="none"},!1),e.addEventListener("mouseover",function(){clearTimeout(_via_message_clear_timer)},!1)}function toggle_message_visibility(){_via_is_message_visible?(show_message("Disabled status messages"),_via_is_message_visible=!1):(_via_is_message_visible=!0,show_message("Status messages are now visible"))}function show_message(e,t){_via_is_message_visible&&Message.show({address:"Info",body:e,color:"#4ADEDE"})}function _via_load_canvas_regions(){drawing.regionsGroupColorInit();var e=_via_img_metadata[_via_image_id].regions;_via_canvas_regions=[];for(var t=0;t<e.length;++t){var i,a=new File_Region;for(i in e[t].shape_attributes)a.shape_attributes[i]=e[t].shape_attributes[i];switch(_via_canvas_regions.push(a),_via_canvas_regions[t].shape_attributes.name){case VIA_REGION_SHAPE.RECT:var _=e[t].shape_attributes.x/_via_canvas_scale,s=e[t].shape_attributes.y/_via_canvas_scale,n=e[t].shape_attributes.width/_via_canvas_scale,r=e[t].shape_attributes.height/_via_canvas_scale;_via_canvas_regions[t].shape_attributes.x=Math.round(_),_via_canvas_regions[t].shape_attributes.y=Math.round(s),_via_canvas_regions[t].shape_attributes.width=Math.round(n),_via_canvas_regions[t].shape_attributes.height=Math.round(r);break;case VIA_REGION_SHAPE.CIRCLE:var o=e[t].shape_attributes.cx/_via_canvas_scale,l=e[t].shape_attributes.cy/_via_canvas_scale,_=e[t].shape_attributes.r/_via_canvas_scale;_via_canvas_regions[t].shape_attributes.cx=Math.round(o),_via_canvas_regions[t].shape_attributes.cy=Math.round(l),_via_canvas_regions[t].shape_attributes.r=Math.round(_);break;case VIA_REGION_SHAPE.ELLIPSE:var o=e[t].shape_attributes.cx/_via_canvas_scale,l=e[t].shape_attributes.cy/_via_canvas_scale,s=e[t].shape_attributes.rx/_via_canvas_scale,n=e[t].shape_attributes.ry/_via_canvas_scale,r=e[t].shape_attributes.theta;_via_canvas_regions[t].shape_attributes.cx=Math.round(o),_via_canvas_regions[t].shape_attributes.cy=Math.round(l),_via_canvas_regions[t].shape_attributes.rx=Math.round(s),_via_canvas_regions[t].shape_attributes.ry=Math.round(n),_via_canvas_regions[t].shape_attributes.theta=r;break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:for(var g=e[t].shape_attributes.all_points_x.slice(0),d=e[t].shape_attributes.all_points_y.slice(0),c=0;c<g.length;++c)g[c]=Math.round(g[c]/_via_canvas_scale),d[c]=Math.round(d[c]/_via_canvas_scale);_via_canvas_regions[t].shape_attributes.all_points_x=g,_via_canvas_regions[t].shape_attributes.all_points_y=d;break;case VIA_REGION_SHAPE.POINT:o=e[t].shape_attributes.cx/_via_canvas_scale,l=e[t].shape_attributes.cy/_via_canvas_scale;_via_canvas_regions[t].shape_attributes.cx=Math.round(o),_via_canvas_regions[t].shape_attributes.cy=Math.round(l)}}}function select_region_shape(e){if(drawing.currentShape()!==e&&!drawing.is_user_drawing_polygon)switch($("#selection_panel button").removeClass("active"),"img_set"===e&&($('#selection_panel button[id="image_settings"]').addClass("active"),$("#image_man_container").removeClass("d-none"),$("#sidebar_container").addClass("d-none")),"drag"===e?(zoom.enablePan(),_via_reg_canvas.cursor="move"):(zoom.disablePan(),_via_reg_canvas.cursor="crosshair"),$('#selection_panel button[id="shape_'+e+'"]').addClass("active"),drawing.setCurrentShape(e),drawing.currentShape()){case VIA_REGION_SHAPE.RECT:case VIA_REGION_SHAPE.CIRCLE:case VIA_REGION_SHAPE.ELLIPSE:Message.show({address:"Info",body:"Press single click and drag mouse to draw "+drawing.currentShape()+" region",color:"#1d3557"});break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:drawing.setIsUserDrawingPolygon(!1),drawing.setCurrentPolygonRegionId(-1),Message.show({address:"Info",body:"[Single Click] to define polygon/polyline vertices, [Backspace] to delete last vertex, [Enter] to finish, [Esc] to cancel drawing."});break;case VIA_REGION_SHAPE.POINT:Message.show({address:"Info",body:"Press single click to define points (or landmarks)"});break;case VIA_REGION_SHAPE.REMOVE:Message.show({address:"Info",body:"Draw a rectangle with this tool to remove some points from the regions (poly and pen regions) within the rectangle"});break;case VIA_REGION_SHAPE.TRIM:Message.show({address:"Info",body:"Select a polygon to trim"});break;case VIA_REGION_SHAPE.DRAG:Message.show({address:"Info",body:"Now you can pan the image"});break;default:Message.show({address:"Info",body:"Editor mode, drawing is disabled"})}}function set_all_canvas_size(e,t){image_panel.style.height=t+"px",image_panel.style.width=e+"px"}function set_all_canvas_scale(e){}function show_all_canvas(){image_panel.style.display="inline-block"}function hide_all_canvas(){image_panel.style.display="none"}function jump_to_image(e){_via_img_count<=0||(undoredo_worker.postMessage({commands:"reset"}),undoredo_worker.postMessage({commands:"add"}),_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?0<=e&&e<_via_img_count&&(show_single_image_view(),buffer.showImage(e)):0<=e&&e<_via_img_count&&buffer.showImage(e))}function count_missing_region_attr(e){for(var t=0,i=Object.keys(_via_region_attributes).length,a=0;a<_via_img_metadata[e].regions.length;++a)t+=i-Object.keys(_via_img_metadata[e].regions[a].region_attributes).length;return t}function count_missing_file_attr(e){return Object.keys(_via_file_attributes).length-Object.keys(_via_img_metadata[e].file_attributes).length}function toggle_all_regions_selection(e){var t,i=_via_img_metadata[_via_image_id].regions.length;if(_via_region_selected_flag.clear(),e)for(t=0;t<i;++t)_via_region_selected_flag.add(t);_via_is_all_region_selected=e,sidebar.annotation_editor_hide(),_via_annotation_editor_mode===VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS&&sidebar.annotation_editor_clear_row_highlight()}function select_only_region(e){toggle_all_regions_selection(!1),set_region_select_state(e,!0),drawing.setIsRegionSelected(!0),_via_is_all_region_selected=!1,drawing.setUserSelRegionId(e)}function set_region_select_state(e,t){t?_via_region_selected_flag.add(parseInt(e)):_via_region_selected_flag.delete(parseInt(e))}function show_annotation_data(){pack_via_metadata("csv").then(function(e){e="<pre>"+e.join("")+"</pre>";window.open("","Annotations (preview) ","toolbar=no,menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,width=800,height=600").document.body.innerHTML=e}.bind(this),function(e){show_message("Failed to collect annotation data!")}.bind(this))}function _via_window_keydown_handler(e){e.target===document.body&&_via_handle_global_keydown_event(e)}function _via_handle_global_keydown_event(e){if(buffer.imgLoaded){if("+"===e.key)return void zoom.zoomIn();if("="===e.key)return void zoom.resetZoom();if("-"===e.key)return void zoom.zoomOut()}"Delete"===e.key?((drawing.isRegionSelected()||_via_is_all_region_selected)&&del_sel_regions(),e.preventDefault()):"ArrowRight"===e.key||"n"===e.key?(move_to_next_image(),e.preventDefault()):"ArrowLeft"===e.key||"p"===e.key?(move_to_prev_image(),e.preventDefault()):"ArrowUp"===e.key?(region_visualisation_update("region_label","__via_region_id__",1),e.preventDefault()):"ArrowDown"===e.key?(region_visualisation_update("region_color","__via_default_region_color__",-1),e.preventDefault()):"s"===e.key?(AutoAnnotator.run_annotation(),e.preventDefault()):"Home"===e.key?(show_first_image(),e.preventDefault()):"End"===e.key?(show_last_image(),e.preventDefault()):"PageDown"===e.key?(jump_to_next_image_block(),e.preventDefault()):"PageUp"===e.key?(jump_to_prev_image_block(),e.preventDefault()):("a"===e.key&&_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&image_grid_group_toggle_select_all(),"Escape"===e.key?(e.preventDefault(),_via_is_loading_current_image&&buffer.stopLoading(),drawing.isUserResizingRegion()&&drawing.setIsUserResizingRegion(!1),drawing.isRegionSelected()&&(drawing.setIsRegionSelected(!1),drawing.setUserSelRegionId(-1),toggle_all_regions_selection(!1)),drawing.isUserDrawingPolygon()&&(drawing.setIsUserDrawingPolygon(!1),_via_canvas_regions.splice(drawing.currentPolygonRegionId(),1)),drawing.isUserDrawingRegion()&&drawing.setIsUserDrawingRegion(!1),drawing.isUserResizingRegion()&&drawing.setIsUserResizingRegion(!1),drawing.isUserMovingRegion()&&drawing.setIsUserMovingRegion(!1),drawing.redrawRegCanvas()):" "===e.key?(e.ctrlKey?sidebar.annotation_editor_toggle_on_image_editor():(leftsidebar_toggle(),_via_reg_canvas.focus()),e.preventDefault()):"F1"===e.key?(set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED),$("#selection_panel").hide(),e.preventDefault()):"F2"===e.key&&(set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_ABOUT),$("#selection_panel").hide(),e.preventDefault()))}function _via_reg_canvas_keyup_handler(e){"Control"===e.key&&(_via_is_ctrl_pressed=!1),"Alt"===e.key&&(select_region_shape(current_shape),is_alt_pressed=!1,zoom.disablePan())}function _via_reg_canvas_keydown_handler(e){if("Control"===e.key&&(_via_is_ctrl_pressed=!0),"Alt"===e.key&&(is_alt_pressed||(current_shape=drawing.currentShape(),select_region_shape("edit"),zoom.enablePan()),is_alt_pressed=!0),buffer.imgLoaded){if("Enter"!==e.key||drawing.currentShape()!==VIA_REGION_SHAPE.POLYLINE&&drawing.currentShape()!==VIA_REGION_SHAPE.POLYGON||_via_polyshape_finish_drawing(),"Backspace"!==e.key||drawing.currentShape()!==VIA_REGION_SHAPE.POLYLINE&&drawing.currentShape()!==VIA_REGION_SHAPE.POLYGON||_via_polyshape_delete_last_vertex(),"a"===e.key)return sel_all_regions(),void e.preventDefault();if("c"===e.key)return(drawing.isRegionSelected()||_via_is_all_region_selected)&&copy_sel_regions(),void e.preventDefault();if("v"===e.key)return paste_sel_regions_in_current_image(),void e.preventDefault();if("b"===e.key)return toggle_region_boundary_visibility(),void e.preventDefault();if("l"===e.key)return toggle_region_id_visibility(),void e.preventDefault();if("r"===e.key)return(drawing.isRegionSelected()||_via_is_all_region_selected)&&reducePointsSelRegion(),void e.preventDefault();if(drawing.isRegionSelected()&&("ArrowRight"===e.key||"ArrowLeft"===e.key||"ArrowDown"===e.key||"ArrowUp"===e.key)){var t=1,i=(e.shiftKey&&(t=10),0),a=0;switch(e.key){case"ArrowLeft":i=-t;break;case"ArrowUp":a=-t;break;case"ArrowRight":i=t;break;case"ArrowDown":a=t}return drawing.moveSelectedRegions(i,a),drawing.redrawRegCanvas(),void e.preventDefault()}"d"===e.key&&_via_is_ctrl_pressed&&del_sel_regions(),"z"===e.key&&_via_is_ctrl_pressed&&undoredo_worker.postMessage({commands:"undo"}),"v"===e.key&&_via_is_ctrl_pressed&&undoredo_worker.postMessage({commands:"redo"})}_via_handle_global_keydown_event(e)}function _via_polyshape_finish_drawing(){var e,t,i;drawing.isUserDrawingPolygon()&&(e=drawing.currentPolygonRegionId(),t=drawing.currentShape(),(i=_via_canvas_regions[e].shape_attributes.all_points_x.length)<=2&&t===VIA_REGION_SHAPE.POLYGON?show_message("For a polygon, you must define at least 3 points. Press [Esc] to cancel drawing operation.!"):i<=1&&t===VIA_REGION_SHAPE.POLYLINE?show_message("A polyline must have at least 2 points. Press [Esc] to cancel drawing operation.!"):(i=_via_image_id,drawing.setCurrentPolygonRegionId(-1),drawing.setIsUserDrawingPolygon(!1),drawing.setIsUserDrawingRegion(!1),_via_img_metadata[i].regions[e]={},_via_polyshape_add_new_polyshape(i,t,e),select_only_region(e),sidebar.set_region_annotations_to_default_value(e),sidebar.annotation_editor_add_row(e),sidebar.annotation_editor_scroll_to_row(e),drawing.redrawRegCanvas(),_via_reg_canvas.focus()))}function _via_polyshape_delete_last_vertex(){var e;drawing.isUserDrawingPolygon()&&0<(e=_via_canvas_regions[drawing.currentPolygonRegionId()].shape_attributes.all_points_x.length)&&(_via_canvas_regions[drawing.currentPolygonRegionId()].shape_attributes.all_points_x.splice(e-1,1),_via_canvas_regions[drawing.currentPolygonRegionId()].shape_attributes.all_points_y.splice(e-1,1),drawing.redrawRegCanvas(),_via_reg_canvas.focus())}function _via_polyshape_add_new_polyshape(e,t,i){for(var a=_via_canvas_regions[i].shape_attributes.all_points_x.slice(0),_=_via_canvas_regions[i].shape_attributes.all_points_y.slice(0),s=[],n=[],r=a.length,o=0;o<r;++o)a[o]=Math.round(a[o]*_via_canvas_scale),_[o]=Math.round(_[o]*_via_canvas_scale),s[o]=Math.round(a[o]/_via_canvas_scale),n[o]=Math.round(_[o]/_via_canvas_scale);var l=new File_Region;l.shape_attributes.name=t,l.shape_attributes.all_points_x=a,l.shape_attributes.all_points_y=_,_via_img_metadata[e].regions[i]=l,e===_via_image_id&&(_via_canvas_regions[i].shape_attributes.name=t,_via_canvas_regions[i].shape_attributes.all_points_x=s,_via_canvas_regions[i].shape_attributes.all_points_y=n)}function del_sel_regions(){if(_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID)if(buffer.imgLoaded){var e=0;if(_via_is_all_region_selected)e=_via_canvas_regions.length,_via_canvas_regions.splice(0),_via_img_metadata[_via_image_id].clearRegions(),_via_img_metadata[_via_image_id].clearLockedRegions();else{for(var t,i=[],a=0;a<_via_canvas_regions.length;++a)_via_region_selected_flag.has(a)&&(i.push(a),_via_region_selected_flag.delete(a));i.sort(function(e,t){return t-e});for(t of i)_via_canvas_regions.splice(t,1),_via_img_metadata[_via_image_id].regions.splice(t,1),_via_img_metadata[_via_image_id].isRegionLocked(t)&&_via_img_metadata[_via_image_id].clearRegionLock(t),e+=1;i.length&&(_via_reg_canvas.style.cursor="default")}_via_is_all_region_selected=!1,drawing.setIsRegionSelected(!1),drawing.setUserSelRegionId(-1),drawing.updateCheckedLockHtml(),0===_via_canvas_regions.length?drawing.clearCanvas():drawing.redrawRegCanvas(),_via_reg_canvas.focus(),sidebar.annotation_editor_show(),show_message("Deleted "+e+" selected regions")}else show_message("First load some images!")}function reducePointsSelRegion(){if(_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID)if(buffer.imgLoaded){let e=0;if(_via_is_all_region_selected){var i=_via_canvas_regions.length;if(0===i)return;for(let t=0;t<i;++t)if(!_via_img_metadata[_via_image_id].isRegionLocked(t)){var a=_via_canvas_regions[t].shape_attributes;if("polygon"===a.name)for(let e=0;e<a.all_points_x.length;++e)drawing.polygonDelVertex(t,e)}}else{var t,_=[];for(let e=0;e<_via_canvas_regions.length;++e)_via_region_selected_flag.has(e)&&(_.push(e),_via_region_selected_flag.delete(e));_.sort(function(e,t){return t-e});for(t of _){var s=_via_canvas_regions[t].shape_attributes;if("polygon"===s.name)for(let e=0;e<s.all_points_x.length;++e)_via_img_metadata[_via_image_id].isRegionLocked(t)||drawing.polygonDelVertex(t,e);e+=1}_.length&&(_via_reg_canvas.style.cursor="default")}_via_is_all_region_selected=!1,drawing.setIsRegionSelected(!1),drawing.setUserSelRegionId(-1),0===_via_canvas_regions.length?drawing.clearCanvas():drawing.redrawRegCanvas(),_via_reg_canvas.focus(),sidebar.annotation_editor_show(),show_message("Reduced points for"+e+" selected regions")}else show_message("First load some images!")}function sel_all_regions(){_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?image_grid_group_toggle_select_all():buffer.imgLoaded?(toggle_all_regions_selection(!0),_via_is_all_region_selected=!0,drawing.redrawRegCanvas()):show_message("First load some images!")}function copy_sel_regions(){if(_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID)if(buffer.imgLoaded)if(drawing.isRegionSelected()||_via_is_all_region_selected){_via_copied_image_regions.splice(0);for(var e=0;e<_via_img_metadata[_via_image_id].regions.length;++e){var t=_via_img_metadata[_via_image_id].regions[e];_via_canvas_regions[e];_via_region_selected_flag.has(e)&&_via_copied_image_regions.push(clone_image_region(t))}show_message("Copied "+_via_copied_image_regions.length+" selected regions. Press Ctrl + v to paste")}else show_message("Select a region first!");else show_message("First load some images!")}function paste_sel_regions_in_current_image(){if(_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID)if(buffer.imgLoaded)if(_via_copied_image_regions.length){for(var e=0,t=0;t<_via_copied_image_regions.length;++t){var i=drawing.getRegionBoundingBox(_via_copied_image_regions[t]);i[2]<_via_current_image_width&&i[3]<_via_current_image_height&&(i=clone_image_region(_via_copied_image_regions[t]),_via_img_metadata[_via_image_id].regions.push(i),e+=1)}_via_load_canvas_regions(),show_message("Pasted "+e+" regions. Discarded "+(_via_copied_image_regions.length-e)+" regions exceeding image boundary."),drawing.redrawRegCanvas(),_via_reg_canvas.focus()}else show_message("To paste a region, you first need to select a region and copy it!");else show_message("First load some images!")}function paste_to_multiple_images_with_confirm(){_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&(0===_via_copied_image_regions.length?show_message("First copy some regions!"):invoke_with_user_inputs(paste_to_multiple_images_confirmed,{region_count:{type:"text",name:"Number of copied regions",value:_via_copied_image_regions.length,disabled:!0},prev_next_count:{type:"text",name:'Copy to (count format)<br><span style="font-size:0.8rem">For example: to paste copied regions to the <i>previous 2 images</i> and <i>next 3 images</i>, type <strong>2,3</strong> in the textbox and to paste only in <i>next 5 images</i>, type <strong>0,5</strong></span>',placeholder:"2,3",disabled:!1,size:30},img_index_list:{type:"text",name:'Copy to (image index list)<br><span style="font-size:0.8rem">For example: <strong>2-5,7,9</strong> pastes the copied regions to the images with the following id <i>2,3,4,5,7,9</i> and <strong>3,8,141</strong> pastes to the images with id <i>3,8 and 141</i></span>',placeholder:"2-5,7,9",disabled:!1,size:30},regex:{type:"text",name:'Copy to filenames matching a regular expression<br><span style="font-size:0.8rem">For example: <strong>_large</strong> pastes the copied regions to all images whose filename contain the keyword <i>_large</i></span>',placeholder:"regular expression",disabled:!1,size:30},include_region_attributes:{type:"checkbox",name:"Paste also the region annotations",checked:!0}},{title:"Paste Regions to Multiple Images"}))}function paste_to_multiple_images_confirmed(e){for(var t=generate_img_index_list(_via_paste_to_multiple_images_input=e),i=0,a=0;a<t.length;a++)i+=paste_regions(t[a]);show_message("Pasted ["+i+"] regions in "+t.length+" images"),t.includes(_via_image_index)&&(_via_load_canvas_regions(),drawing.redrawRegCanvas(),_via_reg_canvas.focus()),user_input_default_cancel_handler()}function paste_regions(e){var t=0;if(_via_copied_image_regions.length)for(var i=_via_image_id_list[e],a=0;a<_via_copied_image_regions.length;++a){var _=clone_image_region(_via_copied_image_regions[a]);_via_img_metadata[i].regions.push(_),t+=1}return t}function del_sel_regions_with_confirm(){var e,t,i;_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&(0===_via_copied_image_regions.length?show_message("First copy some regions!"):(_via_paste_to_multiple_images_input&&(e=_via_paste_to_multiple_images_input.prev_next_count.value,t=_via_paste_to_multiple_images_input.img_index_list.value,i=_via_paste_to_multiple_images_input.regex.value),invoke_with_user_inputs(del_sel_regions_confirmed,{region_count:{type:"text",name:"Number of regions selected",value:_via_copied_image_regions.length,disabled:!0},prev_next_count:{type:"text",name:'Delete from (count format)<br><span style="font-size:0.8rem">For example: to delete copied regions from the <i>previous 2 images</i> and <i>next 3 images</i>, type <strong>2,3</strong> in the textbox and to delete regions only in <i>next 5 images</i>, type <strong>0,5</strong></span>',placeholder:"2,3",disabled:!1,size:30,value:e},img_index_list:{type:"text",name:'Delete from (image index list)<br><span style="font-size:0.8rem">For example: <strong>2-5,7,9</strong> deletes the copied regions to the images with the following id <i>2,3,4,5,7,9</i> and <strong>3,8,141</strong> deletes regions from the images with id <i>3,8 and 141</i></span>',placeholder:"2-5,7,9",disabled:!1,size:30,value:t},regex:{type:"text",name:'Delete from filenames matching a regular expression<br><span style="font-size:0.8rem">For example: <strong>_large</strong> deletes the copied regions from all images whose filename contain the keyword <i>_large</i></span>',placeholder:"regular expression",disabled:!1,size:30,value:i}},{title:"Undo Regions Pasted to Multiple Images"})))}function del_sel_regions_confirmed(e){user_input_default_cancel_handler();for(var t=generate_img_index_list(e),i=0,a=0;a<t.length;a++)i+=delete_regions(t[a]);show_message("Deleted ["+i+"] regions in "+t.length+" images"),t.includes(_via_image_index)&&(_via_load_canvas_regions(),drawing.redrawRegCanvas(),_via_reg_canvas.focus())}function delete_regions(e){var t=0;if(_via_copied_image_regions.length)for(var i=_via_image_id_list[e],a=0;a<_via_copied_image_regions.length;++a)for(var _=JSON.stringify(_via_copied_image_regions[a].shape_attributes),s=_via_img_metadata[i].regions.length-1;0<=s;--s)if(JSON.stringify(_via_img_metadata[i].regions[s].shape_attributes)===_){_via_img_metadata[i].regions.splice(s,1),t+=1;break}return t}function show_first_image(){_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?_via_image_grid_group_var.length?image_grid_group_prev({value:0}):show_message('First, create groups by selecting items from "Group by" dropdown list'):0<_via_img_count&&buffer.showImage(_via_img_fn_list_img_index_list[0])}function show_last_image(){var e;_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?_via_image_grid_group_var.length?image_grid_group_prev({value:_via_image_grid_group_var.length-1}):show_message('First, create groups by selecting items from "Group by" dropdown list'):0<_via_img_count&&(e=_via_img_fn_list_img_index_list.length-1,buffer.showImage(_via_img_fn_list_img_index_list[e]))}function jump_image_block_get_count(){var e=_via_img_fn_list_img_index_list.length;return e<20?2:e<100?10:e<1e3?25:e<5e3?50:e<1e4?100:e<5e4?500:Math.round(e/50)}function jump_to_next_image_block(){var e,t;_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&(1<(t=jump_image_block_get_count())?(e=_via_image_index,_via_img_fn_list_img_index_list.includes(e)&&((e=_via_img_fn_list_img_index_list.indexOf(e)+t)+1>_via_img_fn_list_img_index_list.length&&(e=0),t=_via_img_fn_list_img_index_list[e],buffer.showImage(t))):move_to_next_image())}function jump_to_prev_image_block(){var e,t;_via_display_area_content_name!==VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&(1<(t=jump_image_block_get_count())?(e=_via_image_index,_via_img_fn_list_img_index_list.includes(e)&&((e=_via_img_fn_list_img_index_list.indexOf(e)-t)<0&&(e=_via_img_fn_list_img_index_list.length-1),t=_via_img_fn_list_img_index_list[e],buffer.showImage(t))):move_to_prev_image())}function move_to_prev_image(){var e,t;_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?_via_image_grid_group_var.length?image_grid_group_prev({value:_via_image_grid_group_var.length-1}):show_message('First, create groups by selecting items from "Group by" dropdown list'):0<_via_img_count&&(e=_via_image_index,_via_img_fn_list_img_index_list.includes(e)?(-1===(t=_via_img_fn_list_img_index_list.indexOf(e)-1)&&(t=_via_img_fn_list_img_index_list.length-1),t=_via_img_fn_list_img_index_list[t],buffer.showImage(t)):0===_via_img_fn_list_img_index_list.length?show_message("Filtered file list does not any files!"):buffer.showImage(_via_img_fn_list_img_index_list[0]),"function"==typeof _via_hook_prev_image)&&_via_hook_prev_image(e)}function move_to_next_image(){var e,t;_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?_via_image_grid_group_var.length?image_grid_group_next({value:_via_image_grid_group_var.length-1}):show_message('First, create groups by selecting items from "Group by" dropdown list'):0<_via_img_count&&(e=_via_image_index,_via_img_fn_list_img_index_list.includes(e)?((t=_via_img_fn_list_img_index_list.indexOf(e)+1)===_via_img_fn_list_img_index_list.length&&(t=0),t=_via_img_fn_list_img_index_list[t],buffer.showImage(t)):0===_via_img_fn_list_img_index_list.length?show_message("Filtered file list does not contain any files!"):buffer.showImage(_via_img_fn_list_img_index_list[0]),"function"==typeof _via_hook_next_image)&&_via_hook_next_image(e)}function toggle_region_boundary_visibility(){_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE&&(_via_is_region_boundary_visible=!_via_is_region_boundary_visible,drawing.redrawRegCanvas(),_via_reg_canvas.focus()),_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&(_via_settings.ui.image_grid.show_region_shape?(_via_settings.ui.image_grid.show_region_shape=!1,document.getElementById("image_grid_content_rshape").innerHTML=""):(_via_settings.ui.image_grid.show_region_shape=!0,image_grid_page_show_all_regions()))}function toggle_region_id_visibility(){_via_is_region_id_visible=!_via_is_region_id_visible,drawing.redrawRegCanvas(),_via_reg_canvas.focus()}function toggle_region_info_visibility(){var e=document.getElementById("region_info");_via_is_region_info_visible=e.classList.contains("d-none")?(e.classList.remove("d-none"),!0):(e.classList.add("d-none"),!1)}function region_visualisation_update(e,t,i){var a,_=[t],t=(_=_.concat(Object.keys(project.attributes.region))).length,s=_.indexOf(_via_settings.ui.image[e]);if(-1!==s){switch(t<=(a=(a=s+i)<0?t+a:a)&&(a-=t),e){case"region_label":_via_settings.ui.image.region_label=_[a],drawing.redrawRegCanvas();break;case"region_color":_via_settings.ui.image.region_color=_[a],drawing.regionsGroupColorInit(),drawing.redrawRegCanvas()}s=e.replace("_"," ");_via_settings.ui.image[e].startsWith("__via")?show_message(s+" cleared"):show_message(s+" set to region attribute ["+_via_settings.ui.image[e]+"]")}}function leftsidebar_toggle(){$("#sidebar_container").toggle("fast"),drawing.updateUiComponents()}function attribute_update_panel_set_active_button(){for(var e in project.attributes){var t="button_show_"+e+"_attributes";document.getElementById(t).classList.remove("active")}t="button_show_"+_via_attribute_being_updated+"_attributes";document.getElementById(t).classList.add("active")}function show_region_attributes_update_panel(){_via_attribute_being_updated="region";var e=Object.keys(project.attributes.region);_via_current_attribute_id=e.length?e[0]:"",update_attributes_update_panel(),attribute_update_panel_set_active_button()}function show_file_attributes_update_panel(){_via_attribute_being_updated="file";var e=Object.keys(project.attributes.file);_via_current_attribute_id=e.length?e[0]:"",update_attributes_update_panel(),attribute_update_panel_set_active_button()}function update_attributes_name_list(){var e,t=document.getElementById("attributes_name_list");for(e in t.innerHTML="",project.attributes[_via_attribute_being_updated]){var i=document.createElement("option");i.setAttribute("value",e),(i.innerHTML=e)===_via_current_attribute_id&&i.setAttribute("selected","selected"),t.appendChild(i)}}function update_attributes_update_panel(){update_attributes_name_list(),show_attribute_properties(),show_attribute_options(),show_attribute_example()}function update_attribute_properties_panel(){document.getElementById("settings_panel").classList.contains("d-none")||(show_attribute_properties(),show_attribute_options(),show_attribute_example())}function show_attribute_example(){var e=$("#attributes_example"),t=(e.addClass("d-none"),e.html(""),$("<table>",{class:"table table-sm table-borderless",id:"annotation_editor_example_table"})),i=$("<tbody>");i.append(sidebar.annotation_editor_get_metadata_row_html(100,!1,!0)),t.append(sidebar.annotation_editor_update_header_html(!0)),t.append(i),e.append(t),e.removeClass("d-none")}function show_attribute_properties(){var t=document.getElementById("attributes_name_list"),i=document.getElementById("attribute_properties");if(i.classList.add("d-none"),i.innerHTML="",0!==t.options.length){var t=_via_current_attribute_id=void 0!==_via_current_attribute_id&&""!==_via_current_attribute_id?_via_current_attribute_id:t.options[0].value,a=_via_attribute_being_updated,_=project.attributes[a][t].type,s=project.attributes[a][t].description,s=(attribute_property_add_input_property("Name of attribute (appears in exported annotations)","Name",t,"attribute_name",i),attribute_property_add_input_property("Description of attribute (shown to user during annotation session)","Desc.",s,"attribute_description",i),"text"===_&&attribute_property_add_input_property("Default value of this attribute","Def.",project.attributes[a][t].default_value,"attribute_default_value",i),document.createElement("div")),a=(s.setAttribute("class","input-group mb-1"),document.createElement("span")),n=(a.setAttribute("class","input-group-text"),a.setAttribute("title","Attribute type (e.g. text, checkbox, radio, etc)"),a.innerHTML="Type",document.createElement("select"));n.setAttribute("class","form-select"),n.setAttribute("aria-label","Attribute type"),n.setAttribute("onchange","attribute_property_on_update(this)"),n.setAttribute("id","attribute_type");let e;for(e in VIA_ATTRIBUTE_TYPE){var r=VIA_ATTRIBUTE_TYPE[e],o=document.createElement("option");o.setAttribute("value",r),_==(o.innerHTML=r)&&o.setAttribute("selected","selected"),n.appendChild(o)}s.appendChild(a),s.appendChild(n),document.getElementById("attribute_properties").appendChild(s),i.classList.remove("d-none")}}function show_attribute_options(){var e=document.getElementById("attributes_name_list"),t=document.getElementById("attribute_options");if(t.classList.add("d-none"),t.innerHTML="",0!==e.options.length){var i,a,_,s,n,r,o=e.value,l=project.attributes[_via_attribute_being_updated][o].type;switch(l){case VIA_ATTRIBUTE_TYPE.TEXT:break;case VIA_ATTRIBUTE_TYPE.IMAGE:for(r in(i=document.createElement("div")).setAttribute("class","row mt-2"),(a=document.createElement("span")).setAttribute("class","col"),a.setAttribute("title","When selected, this is the value that appears in exported annotations"),a.innerHTML="id",(_=document.createElement("span")).setAttribute("class","col"),_.setAttribute("title","URL or base64 (see https://www.base64-image.de/) encoded image data that corresponds to the image shown as an option to the annotator"),_.innerHTML="image url or b64",(s=document.createElement("span")).setAttribute("class","col"),s.setAttribute("title","The default value of this attribute"),s.innerHTML="def.",i.appendChild(a),i.appendChild(_),i.appendChild(s),document.getElementById("attribute_options").appendChild(i),n=project.attributes[_via_attribute_being_updated][o].options)attribute_property_add_option(o,r,n[r],project.attributes[_via_attribute_being_updated][o].default_options[r],l);attribute_property_add_new_entry_option(o,l);break;case VIA_ATTRIBUTE_TYPE.CHECKBOX:case VIA_ATTRIBUTE_TYPE.DROPDOWN:case VIA_ATTRIBUTE_TYPE.RADIO:for(r in(i=document.createElement("div")).setAttribute("class","row mt-2"),(a=document.createElement("span")).setAttribute("class","col"),a.setAttribute("title","When selected, this is the value that appears in exported annotations"),a.innerHTML="id",(_=document.createElement("span")).setAttribute("class","col"),_.setAttribute("title","This is the text shown as an option to the annotator"),_.innerHTML="description",(s=document.createElement("span")).setAttribute("title","The default value of this attribute"),s.setAttribute("class","col"),s.innerHTML="def.",i.appendChild(a),i.appendChild(_),i.appendChild(s),document.getElementById("attribute_options").appendChild(i),n=project.attributes[_via_attribute_being_updated][o].options)attribute_property_add_option(o,r,n[r],project.attributes[_via_attribute_being_updated][o].default_options[r],l);attribute_property_add_new_entry_option(o,l);break;default:console.log("Attribute type "+l+" is unavailable")}t.classList.remove("d-none")}}function attribute_property_add_input_property(e,t,i,a,_){var s=document.createElement("div"),n=(s.setAttribute("class","input-group mb-1"),document.createElement("span")),e=(n.setAttribute("title",e),n.setAttribute("class","input-group-text"),n.innerHTML=t,document.createElement("input"));e.setAttribute("onchange","attribute_property_on_update(this)"),e.setAttribute("type","text"),e.setAttribute("class","form-control"),void 0!==i&&e.setAttribute("value",i),e.setAttribute("id",a),s.appendChild(n),s.appendChild(e),_.appendChild(s)}function attribute_property_add_option(e,t,i,a,_){var s=document.createElement("div"),n=(s.setAttribute("class","row mb-1"),document.createElement("div")),r=(n.setAttribute("class","col"),document.createElement("input")),o=(r.setAttribute("type","text"),r.setAttribute("value",t),r.setAttribute("title",t),r.setAttribute("onchange","attribute_property_on_option_update(this)"),r.setAttribute("id","_via_attribute_option_id_"+t),r.setAttribute("class","col form-control"),document.createElement("div")),l=(o.setAttribute("class","col"),document.createElement("input")),g=(l.setAttribute("type","text"),l.setAttribute("class","form-control"),_===VIA_ATTRIBUTE_TYPE.IMAGE?(g=i.length+" bytes of base64 image data",l.setAttribute("value",g),l.setAttribute("title","To update, copy and paste base64 image data in this text box")):(l.setAttribute("value",i),l.setAttribute("title",i)),l.setAttribute("onchange","attribute_property_on_option_update(this)"),l.setAttribute("id","_via_attribute_option_description_"+t),document.createElement("div")),i=(g.setAttribute("class","col"),document.createElement("input"));i.setAttribute("type",_),i.setAttribute("class","form-check-input"),void 0!==a&&(i.checked=a),"radio"!==_&&"image"!==_&&"dropdown"!==_||(i.setAttribute("type","radio"),i.setAttribute("name",e)),i.setAttribute("onchange","attribute_property_on_option_update(this)"),i.setAttribute("id","_via_attribute_option_default_"+t),n.appendChild(r),o.appendChild(l),g.appendChild(i),s.appendChild(n),s.appendChild(o),s.appendChild(g),document.getElementById("attribute_options").appendChild(s)}function attribute_property_add_new_entry_option(e,t){var i=document.createElement("div"),a=(i.setAttribute("class","row"),document.createElement("div")),_=(a.setAttribute("class","col mt-2 mb-3"),document.createElement("input"));_.setAttribute("type","text"),_.setAttribute("onchange","attribute_property_on_option_add(this)"),_.setAttribute("id","_via_attribute_new_option_id"),_.setAttribute("placeholder","Add new option id"),_.setAttribute("class","form-control"),a.appendChild(_),i.appendChild(a),document.getElementById("attribute_options").appendChild(i)}function attribute_property_on_update(e){var t=get_current_attribute_id(),i=_via_attribute_being_updated,a=e.value;switch(e.id){case"attribute_name":a!==t&&(Object.defineProperty(project.attributes[i],a,Object.getOwnPropertyDescriptor(project.attributes[i],t)),delete project.attributes[i][t],update_attributes_update_panel(),sidebar.annotation_editor_update_content());break;case"attribute_description":project.attributes[i][t].description=a,update_attributes_update_panel(),sidebar.annotation_editor_update_content();break;case"attribute_default_value":project.attributes[i][t].default_value=a,update_attributes_update_panel(),sidebar.annotation_editor_update_content();break;case"attribute_type":var _=project.attributes[i][t].type;if((project.attributes[i][t].type=a)===VIA_ATTRIBUTE_TYPE.TEXT)project.attributes[i][t].default_value="",delete project.attributes[i][t].options,delete project.attributes[i][t].default_options;else{project.attributes[i][t].hasOwnProperty("options")||(project.attributes[i][t].options={},project.attributes[i][t].default_options={}),project.attributes[i][t].hasOwnProperty("default_value")&&delete project.attributes[i][t].default_value;var s,n,r=attribute_get_unique_values(i,t);for(g in project.attributes[i][t].options)r.includes(g)||(project.attributes[i][t].options[g]=g);for(s in _via_img_metadata)for(var o in _via_img_metadata[s].regions)if(_via_img_metadata[s].regions[o].region_attributes.hasOwnProperty(t)){if(_===VIA_ATTRIBUTE_TYPE.CHECKBOX&&(a===VIA_ATTRIBUTE_TYPE.RADIO||a===VIA_ATTRIBUTE_TYPE.DROPDOWN)){var l,g,d=0;for(g in _via_img_metadata[s].regions[o].region_attributes[t])_via_img_metadata[s].regions[o].region_attributes[t][g]&&(d+=1,l=g);1===d?_via_img_metadata[s].regions[o].region_attributes[t]=l:delete _via_img_metadata[s].regions[o].region_attributes[t]}_!==VIA_ATTRIBUTE_TYPE.RADIO&&_!==VIA_ATTRIBUTE_TYPE.DROPDOWN||a!==VIA_ATTRIBUTE_TYPE.CHECKBOX||(n=_via_img_metadata[s].regions[o].region_attributes[t],_via_img_metadata[s].regions[o].region_attributes[t]={},_via_img_metadata[s].regions[o].region_attributes[t][n]=!0)}}show_attribute_properties(),show_attribute_options(),show_attribute_example(),sidebar.annotation_editor_update_content()}}function attribute_get_unique_values(e,t){var i,a,_,s=[];switch(e){case"file":for(i in _via_img_metadata)_via_img_metadata[i].file_attributes.hasOwnProperty(t)&&(a=_via_img_metadata[i].file_attributes[t],s.includes(a)||s.push(a));break;case"region":for(i in _via_img_metadata)for(_=0;_<_via_img_metadata[i].regions.length;++_)if(_via_img_metadata[i].regions[_].region_attributes.hasOwnProperty(t))if("object"==typeof(a=_via_img_metadata[i].regions[_].region_attributes[t]))for(var n in _via_img_metadata[i].regions[_].region_attributes[t])s.includes(n)||s.push(n);else s.includes(a)||s.push(a)}return s}function attribute_property_on_option_update(e){var t=get_current_attribute_id();if(e.id.startsWith("_via_attribute_option_id_")){var i,a=e.id.substring("_via_attribute_option_id_".length),_=e.value;if(a!==_)return void((i=attribute_property_option_id_is_valid(t,_)).is_valid?update_attribute_option_id_with_confirm(_via_attribute_being_updated,t,a,_):(e.value=a,show_message(i.message),show_attribute_properties()))}if(e.id.startsWith("_via_attribute_option_description_")&&(_=e.id.substring("_via_attribute_option_description_".length),a=project.attributes[_via_attribute_being_updated][t].options[_],(i=e.value)!==a)&&(project.attributes[_via_attribute_being_updated][t].options[_]=i,show_attribute_properties(),sidebar.annotation_editor_update_content()),e.id.startsWith("_via_attribute_option_default_")){var s=e.id.substring("_via_attribute_option_default_".length);if(0===Object.keys(project.attributes[_via_attribute_being_updated][t].default_options).length)project.attributes[_via_attribute_being_updated][t].default_options[s]=e.checked;else switch(project.attributes[_via_attribute_being_updated][t].type){case"image":case"dropdown":case"radio":project.attributes[_via_attribute_being_updated][t].default_options={},project.attributes[_via_attribute_being_updated][t].default_options[s]=e.checked;break;case"checkbox":project.attributes[_via_attribute_being_updated][t].default_options[s]=e.checked}attribute_property_on_option_default_update(_via_attribute_being_updated,t,s).then(function(){show_attribute_properties(),sidebar.annotation_editor_update_content()})}}function attribute_property_on_option_default_update(s,n,r){return new Promise(function(e,t){var i,a,_;project.attributes[s][n].type;switch(s){case"file":for(i in _via_img_metadata)_via_img_metadata[i].file_attributes.hasOwnProperty(n)||(_via_img_metadata[i].file_attributes[n]=r);break;case"region":for(i in _via_img_metadata)for(a=_via_img_metadata[i].regions.length,_=0;_<a;++_)_via_img_metadata[i].regions[_].region_attributes.hasOwnProperty(n)||(_via_img_metadata[i].regions[_].region_attributes[n]=r)}e()})}function attribute_property_on_option_add(e){var t,i;""!==e.value&&null!==e.value&&"_via_attribute_new_option_id"===e.id&&((i=attribute_property_option_id_is_valid(t=get_current_attribute_id(),e=e.value)).is_valid?(project.attributes[_via_attribute_being_updated][t].options[e]="",show_attribute_options(),sidebar.annotation_editor_update_content()):(show_message(i.message),attribute_property_reset_new_entry_inputs()))}function attribute_property_reset_new_entry_inputs(){var e=document.getElementById("attribute_options").lastChild;e.childNodes[0]&&(e.childNodes[0].value=""),e.childNodes[1]&&(e.childNodes[1].value="")}function attribute_property_show_new_entry_inputs(e,t){var i=document.createElement("div"),a=(i.classList.add("property"),document.createElement("span")),_=document.createElement("input"),_=(_.setAttribute("onchange","attribute_property_on_option_add(this)"),_.setAttribute("placeholder","Add new id"),_.setAttribute("value",""),_.setAttribute("id","_via_attribute_new_option_id"),a.appendChild(_),document.createElement("span")),s=document.createElement("input"),s=(s.setAttribute("onchange","attribute_property_on_option_add(this)"),s.setAttribute("placeholder","Optional description"),s.setAttribute("value",""),s.setAttribute("id","_via_attribute_new_option_description"),_.appendChild(s),document.createElement("span")),n=document.createElement("input");n.setAttribute("type",t),"radio"===t&&n.setAttribute("name",e),n.setAttribute("onchange","attribute_property_on_option_add(this)"),n.setAttribute("id","_via_attribute_new_option_default"),s.appendChild(n),i.appendChild(a),i.appendChild(_),i.appendChild(s),document.getElementById("attribute_options").appendChild(i)}function attribute_property_option_id_is_valid(e,t){for(var i in project.attributes[_via_attribute_being_updated][e].options)if(i===t)return{is_valid:!1,message:"Option id ["+e+"] already exists"};return t.includes("__")?{is_valid:!1,message:"Option id cannot contain two consecutive underscores"}:{is_valid:!0}}function attribute_property_id_exists(e){for(var t in project.attributes[_via_attribute_being_updated])if(t===e)return!0;return!1}function delete_existing_attribute_with_confirm(){var e,t=document.getElementById("user_input_attribute_id").value;""===t?show_message("Enter the name of attribute that you wish to delete"):attribute_property_id_exists(t)?(e={title:"Delete "+_via_attribute_being_updated+" attribute ["+t+"]",warning:"Warning: Deleting an attribute will lead to the attribute being deleted in all the annotations. Please click OK only if you are sure."},invoke_with_user_inputs(delete_existing_attribute_confirmed,{attr_type:{type:"text",name:"Attribute Type",value:_via_attribute_being_updated,disabled:!0},attr_id:{type:"text",name:"Attribute Id",value:t,disabled:!0}},e)):show_message("Attribute ["+t+"] does not exist!")}function delete_existing_attribute_confirmed(e){var t=e.attr_type.value,e=e.attr_id.value;delete_existing_attribute(t,e),document.getElementById("user_input_attribute_id").value="",show_message("Deleted "+t+" attribute ["+e+"]"),user_input_default_cancel_handler()}function delete_existing_attribute(e,t){var i,a,_;project.attributes[e].hasOwnProperty(t)&&(i=Object.keys(project.attributes[e]),_via_current_attribute_id=1===i.length?"":((_=(a=i.indexOf(t))+1)===i.length&&(_=a-1),i[_]),delete project.attributes[e][t],delete_region_attribute_in_all_metadata(t),update_attributes_update_panel(),sidebar.annotation_editor_update_content())}function add_new_attribute_from_user_input(){var e=document.getElementById("user_input_attribute_id").value;""===e?show_message("Enter the name of attribute that you wish to delete"):attribute_property_id_exists(e)?show_message("The "+_via_attribute_being_updated+" attribute ["+e+"] already exists."):(add_new_attribute(_via_current_attribute_id=e),update_attributes_update_panel(),sidebar.annotation_editor_update_content(),show_message("Added "+_via_attribute_being_updated+" attribute ["+e+"]."))}function add_new_attribute(e){project.attributes[_via_attribute_being_updated][e]={},project.attributes[_via_attribute_being_updated][e].type="text",project.attributes[_via_attribute_being_updated][e].description="",project.attributes[_via_attribute_being_updated][e].default_value=""}function update_current_attribute_id(e){_via_current_attribute_id=e.options[e.selectedIndex].value,update_attribute_properties_panel()}function get_current_attribute_id(){return document.getElementById("attributes_name_list").value}function update_attribute_option_id_with_confirm(e,t,i,a){var _,s=!1,e=(""===a||void 0===a?(_={title:"Delete an option for "+e+" attribute"},s=!0):_={title:"Rename an option for "+e+" attribute"},{attr_type:{type:"text",name:"Attribute Type",value:e,disabled:!0},attr_id:{type:"text",name:"Attribute Id",value:t,disabled:!0}});s?e.option_id={type:"text",name:"Attribute Option",value:i,disabled:!0}:(e.option_id={type:"text",name:"Attribute Option (old)",value:i,disabled:!0},e.new_option_id={type:"text",name:"Attribute Option (new)",value:a,disabled:!0}),invoke_with_user_inputs(update_attribute_option_id_confirmed,e,_,update_attribute_option_id_cancel)}function update_attribute_option_id_cancel(e){update_attribute_properties_panel()}function update_attribute_option_id_confirmed(e){var t,i=e.attr_type.value,a=e.attr_id.value,_=e.option_id.value,e=void 0===e.new_option_id||""===e.new_option_id?(t=!0,""):(t=!1,e.new_option_id.value);update_attribute_option(t,i,a,_,e),show_message(t?"Deleted option ["+_+"] for "+i+" attribute ["+a+"].":"Renamed option ["+_+"] to ["+e+"] for "+i+" attribute ["+a+"]."),update_attribute_properties_panel(),sidebar.annotation_editor_update_content(),user_input_default_cancel_handler()}function update_attribute_option(e,t,i,a,_){switch(t){case"region":update_region_attribute_option_in_all_metadata(e,i,a,_),e||Object.defineProperty(project.attributes[t][i].options,_,Object.getOwnPropertyDescriptor(project.attributes[_via_attribute_being_updated][i].options,a)),delete project.attributes.region[i].options[a];break;case"file":update_file_attribute_option_in_all_metadata(i,a),e||Object.defineProperty(project.attributes[t][i].options,_,Object.getOwnPropertyDescriptor(project.attributes[_via_attribute_being_updated][i].options,a)),delete project.attributes.file[i].options[a]}}function update_file_attribute_option_in_all_metadata(e,t,i,a){for(var _ in _via_img_metadata)_via_img_metadata[_].file_attributes.hasOwnProperty(t)&&_via_img_metadata[_].file_attributes[t].hasOwnProperty(i)&&(Object.defineProperty(_via_img_metadata[_].file_attributes[t],a,Object.getOwnPropertyDescriptor(_via_img_metadata[_].file_attributes[t],i)),delete _via_img_metadata[_].file_attributes[t][i])}function update_region_attribute_option_in_all_metadata(e,t,i,a){for(var _ in _via_img_metadata)for(var s=0;s<_via_img_metadata[_].regions.length;++s)_via_img_metadata[_].regions[s].region_attributes.hasOwnProperty(t)&&_via_img_metadata[_].regions[s].region_attributes[t].hasOwnProperty(i)&&(Object.defineProperty(_via_img_metadata[_].regions[s].region_attributes[t],a,Object.getOwnPropertyDescriptor(_via_img_metadata[_].regions[s].region_attributes[t],i)),delete _via_img_metadata[_].regions[s].region_attributes[t][i])}function delete_region_attribute_in_all_metadata(e){for(var t in _via_img_metadata)for(var i=0;i<_via_img_metadata[t].regions.length;++i)_via_img_metadata[t].regions[i].region_attributes.hasOwnProperty(e)&&delete _via_img_metadata[t].regions[i].region_attributes[e]}function delete_file_attribute_option_from_all_metadata(e,t){for(var i in _via_img_metadata)_via_img_metadata.hasOwnProperty(i)&&delete_file_attribute_option_from_metadata(i,e,t)}function delete_file_attribute_option_from_metadata(e,t,i){_via_img_metadata[e].file_attributes.hasOwnProperty(t)&&_via_img_metadata[e].file_attributes[t].hasOwnProperty(i)&&delete _via_img_metadata[e].file_attributes[t][i]}function delete_file_attribute_from_all_metadata(e,t){for(e in _via_img_metadata)_via_img_metadata.hasOwnProperty(e)&&_via_img_metadata[e].file_attributes.hasOwnProperty(t)&&delete _via_img_metadata[e].file_attributes[t]}function invoke_with_user_inputs(e,t,i,a){setup_user_input_panel(e,t,i,a),show_user_input_panel()}function setup_user_input_panel(e,t,i,a,_=0){_via_user_input_ok_handler=e,_via_user_input_cancel_handler=a,_via_user_input_data=t;var s,e=document.createElement("div"),n=(e.setAttribute("class","content"),[]);for(s in n.push('<div class="container">'),_via_user_input_data){n.push('<div class="row">'),n.push('<div class="col">'+_via_user_input_data[s].name+"</div>");var r="",o=(_via_user_input_data[s].disabled&&(r='disabled="disabled"'),"");switch(_via_user_input_data[s].value&&(o='value="'+_via_user_input_data[s].value+'"'),_via_user_input_data[s].type){case"checkbox":o=_via_user_input_data[s].checked?'checked="checked"':"",n.push('<div class="col"><input class="form-check-input" '+o+" "+r+' type="checkbox" id="'+s+'"></div>');break;case"text":var l="50",g=(_via_user_input_data[s].size&&(l=_via_user_input_data[s].size),"");_via_user_input_data[s].placeholder&&(g=_via_user_input_data[s].placeholder),n.push('<div class="col"><input class="form-control" '+o+" "+r+' size="'+l+'" placeholder="'+g+'" type="text" id="'+s+'"></div>');break;case"textarea":var l="5",d="50",g=(_via_user_input_data[s].rows&&(l=_via_user_input_data[s].rows),_via_user_input_data[s].cols&&(d=_via_user_input_data[s].cols),"");_via_user_input_data[s].placeholder&&(g=_via_user_input_data[s].placeholder),n.push('<div class="col"><textarea class="form-control" '+r+' rows="'+l+'" cols="'+d+'" placeholder="'+g+'" id="'+s+'">'+o+"</textarea></div>")}n.push("</div>")}n.push("</div>"),i.hasOwnProperty("warning")&&n.push('<div class="alert alert-warning">'+i.warning+"</div>"),e.innerHTML=n.join("");a=n.join("");modal.show(i.title,a,'<button type="button" class="btn btn-primary" onclick="user_input_parse_and_invoke_handler()" id="user_input_ok_button">Ok</button><button type="button" class="btn btn-secondary" onclick="user_input_cancel_handler()" id="user_input_cancel_button">Cancel</button>')}function user_input_default_cancel_handler(){hide_user_input_panel(),_via_user_input_data={},_via_user_input_ok_handler=null,_via_user_input_cancel_handler=null}function user_input_cancel_handler(){_via_user_input_cancel_handler&&_via_user_input_cancel_handler(),user_input_default_cancel_handler()}function user_input_parse_and_invoke_handler(){for(var e=document.getElementsByClassName("_via_user_input_variable"),t=0;t<e.length;++t){var i=e[t].id;_via_user_input_data.hasOwnProperty(i)&&("checkbox"===_via_user_input_data[i].type?_via_user_input_data[i].value=e[t].checked:_via_user_input_data[i].value=e[t].value)}void 0===_via_user_input_data.confirm||_via_user_input_data.confirm.value?_via_user_input_ok_handler(_via_user_input_data):_via_user_input_cancel_handler&&_via_user_input_cancel_handler(),user_input_default_cancel_handler()}function show_user_input_panel(){document.getElementById("user_input_panel").style.display="block"}function hide_user_input_panel(){$("#staticBackdropModal").modal("hide"),document.getElementById("user_input_panel").style.display="none"}function image_grid_init(){var e=document.getElementById("image_grid_content"),e=(e.focus(),e.addEventListener("mousedown",image_grid_mousedown_handler,!1),e.addEventListener("mouseup",image_grid_mouseup_handler,!1),e.addEventListener("dblclick",image_grid_dblclick_handler,!1),image_grid_set_content_panel_height_fixed(),settings.image_grid_content);image_grid_set_policy_btn_text(e),$("#image_grid_show_image_policy_dropdown").on("click","a",function(){var e=$(this).data("value");image_grid_set_policy_btn_text(e),image_grid_onchange_show_image_policy(e)})}function image_grid_set_policy_btn_text(e){switch(e){case"all":$("#image_grid_show_image_policy").text("all");break;case"first_mid_last":$("#image_grid_show_image_policy").text("first, mid, last");break;case"even_indexed":$("#image_grid_show_image_policy").text("even");break;case"odd_indexed":$("#image_grid_show_image_policy").text("odd");break;case"gap5":$("#image_grid_show_image_policy").text("gap 5");break;case"gap25":$("#image_grid_show_image_policy").text("gap 25");break;case"gap50":$("#image_grid_show_image_policy").text("gap 50");break;default:$("#image_grid_show_image_policy").text("all")}}function image_grid_update(){_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID&&image_grid_set_content(_via_image_grid_img_index_list)}function image_grid_toggle(){document.getElementById("toolbar_image_grid_toggle");(_via_display_area_content_name===VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID?(image_grid_clear_all_groups(),show_single_image_view):show_image_grid_view)()}function image_grid_show_all_project_images(){for(var e=[],t=_via_img_fn_list_img_index_list.length,i=0;i<t;++i)e.push(_via_img_fn_list_img_index_list[i]);image_grid_clear_all_groups(),document.getElementById("image_grid_toolbar_group_by_select").selectedIndex=0,image_grid_set_content(e)}function image_grid_clear_all_groups(){for(var e=_via_image_grid_group_var.length,t=0;t<e;++t)image_grid_remove_html_group_panel(_via_image_grid_group_var[t]),image_grid_group_by_select_set_disabled(_via_image_grid_group_var[t].type,_via_image_grid_group_var[t].name,!1);_via_image_grid_group={},_via_image_grid_group_var=[]}function image_grid_set_content(e){if(0!==e.length&&!_via_image_grid_load_ongoing){_via_image_grid_img_index_list=e.slice(0),_via_image_grid_selected_img_index_list=e.slice(0),document.getElementById("image_grid_group_by_img_count").innerHTML=_via_image_grid_img_index_list.length.toString(),_via_image_grid_page_first_index=0,_via_image_grid_page_last_index=null,_via_image_grid_stack_prev_page=[],_via_image_grid_page_img_index_list=[],image_grid_clear_content(),image_grid_set_content_panel_height_fixed(),_via_image_grid_load_ongoing=!0;var t=_via_image_grid_img_index_list.length;switch(settings.image_grid_content){case"all":_via_image_grid_page_img_index_list=_via_image_grid_img_index_list.slice(0);break;case"first_mid_last":if(t<3)for(i=0;i<t;++i)_via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[i]);else _via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[0]),_via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[Math.floor(t/2)]),_via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[t-1]);break;case"even_indexed":for(i=0;i<t;++i)i%2!=0&&_via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[i]);break;case"odd_indexed":for(i=0;i<t;++i)i%2==0&&_via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[i]);break;case"gap5":case"gap25":case"gap50":var i,a=parseInt(_via_settings.ui.image_grid.show_image_policy.substring("gap".length));for(i=0;i<t;i+=a)_via_image_grid_page_img_index_list.push(_via_image_grid_img_index_list[i]);break;default:_via_image_grid_page_img_index_list=_via_image_grid_img_index_list.slice(0)}_via_image_grid_visible_img_index_list=[],image_grid_update_sel_count_html(),sidebar.annotation_editor_update_content(),image_grid_content_append_img(_via_image_grid_page_first_index),show_message("[Click] toggles selection, [Shift + Click] selects everything a image, [Click] or [Ctrl + Click] removes selection of all subsequent or preceeding images.")}}function image_grid_clear_content(){var e=document.getElementById("image_grid_content_img"),t=document.getElementById("image_grid_content_rshape");e.innerHTML="",t.innerHTML="",_via_image_grid_visible_img_index_list=[]}function image_grid_set_content_panel_height_fixed(){var e=document.getElementById("image_grid_content"),t=document.documentElement;e.style.height=t.clientHeight-3*ui_top_panel.offsetHeight+"px"}function image_grid_content_append_img(e){var t,e=_via_image_grid_page_img_index_list[e],i=image_grid_get_html_img_id(e),a=_via_image_id_list[e];let _=document.createElement("img");_via_img_fileref[a]instanceof File?((t=new FileReader).addEventListener("error",function(){},!1),t.addEventListener("load",function(){_.src=t.result.toString()},!1),t.readAsDataURL(_via_img_fileref[a])):_.src=_via_img_src[a],_.setAttribute("id",i),_.style.height=_via_settings.ui.image_grid.img_height+"px",_.setAttribute("title","["+(e+1)+"] "+_via_img_metadata[a].filename),_.addEventListener("load",image_grid_on_img_load,!1),_.addEventListener("error",image_grid_on_img_error,!1),_.classList.add("img-thumbnail"),document.getElementById("image_grid_content_img").appendChild(_)}function image_grid_on_img_load(e){var e=e.target,t=image_grid_parse_html_img_id(e.id);project.fileLoadOnSuccess(t),image_grid_add_img_if_possible(e)}function image_grid_on_img_error(e){var e=e.target,t=image_grid_parse_html_img_id(e.id);project.fileLoadOnFail(t),image_grid_add_img_if_possible(e)}function image_grid_add_img_if_possible(e){var t,i,a,_,s,n=image_grid_parse_html_img_id(e.id),r=document.getElementById("image_grid_content_img"),o=parseInt(e.offsetTop)+parseInt(e.height);r.clientHeight<o?(document.getElementById("image_grid_content_img").removeChild(e),_via_settings.ui.image_grid.show_region_shape&&image_grid_page_show_all_regions(),_via_image_grid_load_ongoing=!1,t=_via_image_grid_page_img_index_list.indexOf(n),_via_image_grid_page_last_index=t,i=document.getElementById("image_grid_nav"),a=[],_=_via_image_grid_page_first_index,s=_via_image_grid_page_last_index-1,a.push('<span class="input-group-text">Showing&nbsp;'+(_+1)+" to "+(s+1)+"&nbsp;:</span>"),_via_image_grid_stack_prev_page.length?a.push('<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_prev()">Prev</button>'):a.push('<button type="button" class="btn btn-sm btn-outline-secondary disabled">Prev</button>'),a.push('<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_next()">Next</button>'),i.innerHTML=a.join("")):(o=(r=_via_image_grid_page_img_index_list.indexOf(n))+1,_via_image_grid_visible_img_index_list.push(n),-1===_via_image_grid_selected_img_index_list.indexOf(n)&&image_grid_update_img_select(n,"unselect"),o!==_via_image_grid_page_img_index_list.length?_via_image_grid_load_ongoing?image_grid_content_append_img(r+1):(_via_image_grid_page_last_index=_via_image_grid_page_first_index,i=document.getElementById("image_grid_nav"),(a=[]).push('<span class="input-group-text">Cancelled&nbsp;:</span>'),_via_image_grid_stack_prev_page.length?a.push('<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_prev()">Prev</button>'):a.push('<button type="button" class="btn btn-sm btn-outline-secondary disabled">Prev</button>'),a.push('<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_next()">Next</button>'),i.innerHTML=a.join("")):(t=_via_image_grid_page_img_index_list.indexOf(n),_via_image_grid_page_last_index=t,_via_settings.ui.image_grid.show_region_shape&&image_grid_page_show_all_regions(),_via_image_grid_load_ongoing=!1,i=document.getElementById("image_grid_nav"),a=[],_=_via_image_grid_page_first_index,s=_via_image_grid_page_last_index,a.push('<span class="input-group-text">Showing&nbsp;'+(_+1)+" to "+(s+1)+" (end)&nbsp;</span>"),_via_image_grid_stack_prev_page.length?a.push('<button type="button" class="btn btn-sm btn-outline-secondary" onclick="image_grid_page_prev()">Prev</button>'):a.push('<button type="button" class="btn btn-sm btn-outline-secondary disabled">Prev</button>'),a.push('<button type="button" class="btn btn-sm btn-outline-secondary disabled">Next</button>'),i.innerHTML=a.join("")))}function image_grid_onchange_show_image_policy(e){settings.image_grid_content=e,settings.save(),image_grid_set_content(_via_image_grid_img_index_list)}function image_grid_page_show_all_regions(){var e=[];if(_via_settings.ui.image_grid.show_region_shape)for(var t=document.getElementById("image_grid_content_img"),i=t.childNodes.length,a=0;a<i;++a){var _=image_grid_parse_html_img_id(t.childNodes[a].id),s=[],_=(s.push(parseInt(t.childNodes[a].width)),s.push(parseInt(t.childNodes[a].height)),s.push(parseInt(t.childNodes[a].naturalWidth)),s.push(parseInt(t.childNodes[a].naturalHeight)),s.push(parseInt(t.childNodes[a].offsetLeft)+parseInt(t.childNodes[a].clientLeft)),s.push(parseInt(t.childNodes[a].offsetTop)+parseInt(t.childNodes[a].clientTop)),image_grid_show_region_shape(_,s));e.push(_)}}function image_grid_is_region_in_current_group(e){var t,i=_via_image_grid_group_var.length;if(0!==i)for(t=0;t<i;++t)if("region"===_via_image_grid_group_var[t].type){var a=_via_image_grid_group_var[t].values[_via_image_grid_group_var[t].current_value_index];if(e[_via_image_grid_group_var[t].name]!=a)return!1}return!0}function image_grid_show_region_shape(g,d){return new Promise(function(e,t){for(var i=_via_image_id_list[g],a=image_grid_get_html_img_id(g),_=_via_img_metadata[i].regions.length,s=0;s<_;++s)if(image_grid_is_region_in_current_group(_via_img_metadata[i].regions[s].region_attributes)){var n=_via_img_metadata[i].regions[s].shape_attributes;switch(n.name){case VIA_REGION_SHAPE.RECT:r=[n.x,n.y,n.x+n.width,n.y+n.height];break;case VIA_REGION_SHAPE.CIRCLE:r=[n.cx,n.cy,n.cx+n.r,n.cy+n.r];break;case VIA_REGION_SHAPE.ELLIPSE:r=[n.cx,n.cy,n.cx+n.rx,n.cy+n.ry];break;case VIA_REGION_SHAPE.POLYLINE:case VIA_REGION_SHAPE.POLYGON:for(var r=[],o=0;o<n.all_points_x.length;++o)r.push(n.all_points_x[o]),r.push(n.all_points_y[o]);break;case VIA_REGION_SHAPE.POINT:r=[n.cx,n.cy]}var l=d[1]/d[3],l=new _via_region(n.name,s,r,l,d[4],d[5]).get_svg_element();l.setAttribute("id",image_grid_get_html_region_id(g,s)),l.setAttribute("class",a),l.setAttribute("fill",_via_settings.ui.image_grid.rshape_fill),l.setAttribute("stroke",_via_settings.ui.image_grid.rshape_stroke),l.setAttribute("stroke-width",_via_settings.ui.image_grid.rshape_stroke_width),document.getElementById("image_grid_content_rshape").appendChild(l)}})}function image_grid_image_size_increase(){var e=_via_settings.ui.image_grid.img_height+VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE;_via_settings.ui.image_grid.img_height=e,_via_image_grid_page_last_index=null,image_grid_update()}function image_grid_image_size_decrease(){var e=_via_settings.ui.image_grid.img_height-VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE;1<e&&(_via_settings.ui.image_grid.img_height=e,_via_image_grid_page_last_index=null,image_grid_update())}function image_grid_image_size_reset(){var e=_via_settings.ui.image_grid.img_height;1<e&&(_via_settings.ui.image_grid.img_height=e,_via_image_grid_page_last_index=null,image_grid_update())}function image_grid_mousedown_handler(e){e.preventDefault(),_via_image_grid_mousedown_img_index=image_grid_parse_html_img_id(e.target.id)}function image_grid_mouseup_handler(e){e.preventDefault();var t=_via_image_grid_mouseup_img_index;if(_via_image_grid_mouseup_img_index=image_grid_parse_html_img_id(e.target.id),isNaN(_via_image_grid_mousedown_img_index)||isNaN(_via_image_grid_mouseup_img_index))t=_via_image_grid_img_index_list[0],image_grid_group_select_none();else{var i=_via_image_grid_img_index_list.indexOf(_via_image_grid_mousedown_img_index),a=_via_image_grid_img_index_list.indexOf(_via_image_grid_mouseup_img_index),_=-1,s=-1,n="select";if(i===a?e.shiftKey?(_=_via_image_grid_img_index_list.indexOf(t)+1,s=a+1):(s=(_=i)+1,n="toggle"):(s=i<a?(_=i,a+1):(_=a+1,i),n="toggle"),!(s<_)){for(var r=_;r<s;++r)image_grid_update_img_select(_via_image_grid_img_index_list[r],n);image_grid_update_sel_count_html(),sidebar.annotation_editor_update_content()}}}function image_grid_update_sel_count_html(){document.getElementById("image_grid_group_by_sel_img_count").innerHTML=_via_image_grid_selected_img_index_list.length.toString()}function image_grid_update_img_select(e,t){var i,a=image_grid_get_html_img_id(e),_=-1!==_via_image_grid_selected_img_index_list.indexOf(e);switch(t="toggle"===t?_?"unselect":"select":t){case"select":_||_via_image_grid_selected_img_index_list.push(e),-1!==_via_image_grid_visible_img_index_list.indexOf(e)&&document.getElementById(a).classList.remove("not_sel");break;case"unselect":_&&(i=_via_image_grid_selected_img_index_list.indexOf(e),_via_image_grid_selected_img_index_list.splice(i,1)),-1!==_via_image_grid_visible_img_index_list.indexOf(e)&&document.getElementById(a).classList.add("not_sel")}}function image_grid_group_select_all(){image_grid_group_set_all_selection_state("select"),image_grid_update_sel_count_html(),sidebar.annotation_editor_update_content(),show_message("Selected all images in the current group")}function image_grid_group_select_none(){image_grid_group_set_all_selection_state("unselect"),image_grid_update_sel_count_html(),sidebar.annotation_editor_update_content(),show_message("Removed selection of all images in the current group")}function image_grid_group_set_all_selection_state(e){for(var t=0;t<_via_image_grid_img_index_list.length;++t)image_grid_update_img_select(_via_image_grid_img_index_list[t],e)}function image_grid_group_toggle_select_all(){(_via_image_grid_selected_img_index_list.length===_via_image_grid_img_index_list.length?image_grid_group_select_none:image_grid_group_select_all)()}function image_grid_parse_html_img_id(e){e=e.substring(2);return parseInt(e)}function image_grid_get_html_img_id(e){return"im"+e}function image_grid_parse_html_region_id(e){e=e.split("_");return 2===e.length?{img_index:parseInt(e[0].substring(2)),region_id:parseInt(e[1].substring(2))}:(console.log("image_grid_parse_html_region_id(): invalid html_region_id"),{})}function image_grid_get_html_region_id(e,t){return image_grid_get_html_img_id(e)+"_rs"+t}function image_grid_dblclick_handler(e){_via_image_index=image_grid_parse_html_img_id(e.target.id),show_single_image_view()}function image_grid_toolbar_update_group_by_select(){var e,t,i=document.getElementById("image_grid_toolbar_group_by_select"),a=(i.innerHTML="",document.createElement("option"));for(e in a.setAttribute("value",""),a.setAttribute("selected","selected"),a.innerHTML="All Images",i.appendChild(a),project.attributes.file)(a=document.createElement("option")).setAttribute("value",image_grid_toolbar_group_by_select_get_html_id("file",e)),a.innerHTML="[file] "+e,i.appendChild(a);for(t in project.attributes.region)(a=document.createElement("option")).setAttribute("value",image_grid_toolbar_group_by_select_get_html_id("region",t)),a.innerHTML="[region] "+t,i.appendChild(a)}function image_grid_toolbar_group_by_select_get_html_id(e,t){return"file"===e?"f_"+t:"region"===e?"r_"+t:void 0}function image_grid_toolbar_group_by_select_parse_html_id(e){return e.startsWith("f_")?{attr_type:"file",attr_name:e.substring(2)}:e.startsWith("r_")?{attr_type:"region",attr_name:e.substring(2)}:void 0}function image_grid_toolbar_onchange_group_by_select(e){var t,i;""===e.options[e.selectedIndex].value?image_grid_show_all_project_images():(image_grid_group_by(t=(i=image_grid_toolbar_group_by_select_parse_html_id(e.options[e.selectedIndex].value)).attr_type,i=i.attr_name),image_grid_group_by_select_set_disabled(t,i,!0),e.blur())}function image_grid_remove_html_group_panel(e){e=document.getElementById("group_toolbar_"+e.group_index);document.getElementById("image_grid_group_panel").removeChild(e),0===$("#image_grid_group_panel").children().length&&$("#image_grid_group_panel_card").addClass("d-none")}function image_grid_add_html_group_panel(e){var t=$('<div class="input-group input-group-sm" id="group_toolbar_'+e.group_index+'"></div>'),i=$('<button class="btn btn-outline-secondary" onclick="image_grid_remove_group_by(this)"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">\n  <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>\n</svg></button>'),i=(t.append(i),$('<button class="btn btn-outline-secondary" value="'+e.group_index+'" onclick="image_grid_group_prev(this)"><</button>')),a=(t.append(i),$('<select class="form-select form-select-sm" id="'+image_grid_group_select_get_html_id(e.group_index)+'" onchange="image_grid_group_value_onchange(this)"></select>'));let _,s;var n=e.values.length,r=e.values[e.current_value_index];for(_=0;_<n;++_){s=e.values[_];var o=$('<option value="'+s+'">'+(_+1)+"/"+n+": "+e.name+" = "+s+"</option>");s===r&&o.attr("selected","selected"),a.append(o)}t.append(a);i=$('<button class="btn btn-outline-secondary" value="'+e.group_index+'" onclick="image_grid_group_next(this)">></button>');t.append(i),$("#image_grid_group_panel").append(t),$("#image_grid_group_panel_card").removeClass("d-none")}function image_grid_group_panel_set_selected_value(e){document.getElementById(image_grid_group_select_get_html_id(e)).selectedIndex=_via_image_grid_group_var[e].current_value_index}function image_grid_group_panel_set_options(e){var t=document.getElementById(image_grid_group_select_get_html_id(e));if(t.innerHTML="",void 0!==_via_image_grid_group_var[e])for(var i=_via_image_grid_group_var[e].values.length,a=_via_image_grid_group_var[e].name,_=_via_image_grid_group_var[e].values[_via_image_grid_group_var[e].current_value_index],s=0;s<i;++s){var n=_via_image_grid_group_var[e].values[s],r=document.createElement("option");r.setAttribute("value",n),r.innerHTML=s+1+"/"+i+": "+a+" = "+n,n===_&&r.setAttribute("selected","selected"),t.appendChild(r)}}function image_grid_group_select_get_html_id(e){return"gi_"+e}function image_grid_group_select_parse_html_id(e){return parseInt(e.substring(3))}function image_grid_group_by_select_set_disabled(e,t,i){for(var a=document.getElementById("image_grid_toolbar_group_by_select"),_=image_grid_toolbar_group_by_select_get_html_id(e,t),s=a.options.length,n=0;n<s;++n)if(_===a.options[n].value){i?a.options[n].setAttribute("disabled","disabled"):a.options[n].removeAttribute("disabled");break}}function image_grid_remove_group_by(e){var t=parseInt(e.parentNode.id.substring("group_toolbar_".length));if(0===t)image_grid_show_all_project_images();else{image_grid_group_by_merge(_via_image_grid_group,0,t);for(var i=_via_image_grid_group_var.length,e=document.getElementById("image_grid_group_panel"),a=t;a<i;++a)image_grid_remove_html_group_panel(_via_image_grid_group_var[a]),image_grid_group_by_select_set_disabled(_via_image_grid_group_var[a].type,_via_image_grid_group_var[a].name,!1);_via_image_grid_group_var.splice(t),image_grid_set_content_to_current_group()}}function image_grid_group_by(e,t){if(0===Object.keys(_via_image_grid_group).length){var i=[],a=_via_img_fn_list_img_index_list.length;for(s=0;s<a;++s)i.push(_via_img_fn_list_img_index_list[s]);_via_image_grid_group=image_grid_split_array_to_group(i,e,t);var _=Object.keys(_via_image_grid_group);(_via_image_grid_group_var=[]).push({type:e,name:t,current_value_index:0,values:_,group_index:0}),image_grid_add_html_group_panel(_via_image_grid_group_var[0])}else{image_grid_group_split_all_arrays(_via_image_grid_group,e,t);var s,n=_via_image_grid_group;for(a=_via_image_grid_group_var.length,s=0;s<a;++s)n=n[_via_image_grid_group_var[s].values[_via_image_grid_group_var[s].current_value_index]];var _=Object.keys(n),r=_via_image_grid_group_var.length;_via_image_grid_group_var.push({type:e,name:t,current_value_index:0,values:_,group_index:r}),image_grid_add_html_group_panel(_via_image_grid_group_var[r])}image_grid_set_content_to_current_group()}function image_grid_group_by_merge(e,t,i){if(t===i)return image_grid_group_by_collapse(e);for(var a in e)e[a]=image_grid_group_by_merge(e[a],t+1,i)}function image_grid_group_by_collapse(e){var t,i=[];for(t in e)i=Array.isArray(e[t])?i.concat(e[t]):i.concat(image_grid_group_by_collapse(e[t]));return i}function image_grid_group_split_all_arrays(e,t,i){if(Array.isArray(e))return image_grid_split_array_to_group(e,t,i);for(var a in e)Array.isArray(e[a])?e[a]=image_grid_split_array_to_group(e[a],t,i):image_grid_group_split_all_arrays(e[a],t,i)}function image_grid_split_array_to_group(e,t,i){var a,_,s,n={},r=e.length;switch(t){case"file":for(g=0;g<r;++g)a=e[g],_=_via_image_id_list[a],_via_img_metadata[_].file_attributes.hasOwnProperty(i)&&(s=_via_img_metadata[_].file_attributes[i],n.hasOwnProperty(s)||(n[s]=[]),n[s].push(a));break;case"region":for(var o,l,g=0;g<r;++g)for(a=e[g],_=_via_image_id_list[a],l=_via_img_metadata[_].regions.length,o=0;o<l;++o)_via_img_metadata[_].regions[o].region_attributes.hasOwnProperty(i)&&(s=_via_img_metadata[_].regions[o].region_attributes[i],n.hasOwnProperty(s)||(n[s]=[]),n[s].includes(a)||n[s].push(a))}return n}function image_grid_group_next(e){var t=parseInt(e.value);if(void 0!==_via_image_grid_group_var[t]){var e=_via_image_grid_group_var[t].values.length,i=_via_image_grid_group_var[t].current_value_index+1;if(e<=i)if(0===t)image_grid_jump_to_group(t,i-=e);else{for(var a=t-1,_=_via_image_grid_group_var[a].current_value_index+1;0!==a&&_>=_via_image_grid_group_var[a].values.length;)a=t-1,_=_via_image_grid_group_var[a].current_value_index+1;image_grid_jump_to_group(a,_=_>=_via_image_grid_group_var[a].values.length?0:_)}else image_grid_jump_to_group(t,i);image_grid_set_content_to_current_group()}}function image_grid_group_prev(e){var t=parseInt(e.value),e=_via_image_grid_group_var[t].values.length,i=_via_image_grid_group_var[t].current_value_index-1;if(i<0)if(0===t)image_grid_jump_to_group(t,i=e+i);else{for(var a=t-1,_=_via_image_grid_group_var[a].current_value_index-1;0!==a&&_<0;)a=t-1,_=_via_image_grid_group_var[a].current_value_index-1;image_grid_jump_to_group(a,_=_<0?_via_image_grid_group_var[a].values.length-1:_)}else image_grid_jump_to_group(t,i);image_grid_set_content_to_current_group()}function image_grid_group_value_onchange(e){image_grid_jump_to_group(image_grid_group_select_parse_html_id(e.id),e.selectedIndex),image_grid_set_content_to_current_group()}function image_grid_jump_to_group(e,t){var i=_via_image_grid_group_var[e].values.length;if(!(i<=t||t<0)&&(_via_image_grid_group_var[e].current_value_index=t,image_grid_group_panel_set_selected_value(e),e+1<_via_image_grid_group_var.length)){for(var a=_via_image_grid_group,_=0;_<=e;++_)a=a[_via_image_grid_group_var[_].values[_via_image_grid_group_var[_].current_value_index]];for(_=e+1;_<_via_image_grid_group_var.length;++_){if(_via_image_grid_group_var[_].values=Object.keys(a),0===_via_image_grid_group_var[_].values.length){_via_image_grid_group_var[_].current_value_index=-1,_via_image_grid_group_var.splice(_),image_grid_group_panel_set_options(_);break}_via_image_grid_group_var[_].current_value_index=0,a=a[_via_image_grid_group_var[_].values[0]],image_grid_group_panel_set_options(_)}}}function image_grid_set_content_to_current_group(){if(0===(e=_via_image_grid_group_var.length))image_grid_show_all_project_images();else{for(var e,t=_via_image_grid_group,i=0;i<e;++i)t=t[_via_image_grid_group_var[i].values[_via_image_grid_group_var[i].current_value_index]];Array.isArray(t)?image_grid_set_content(t):console.log("Error: image_grid_set_content_to_current_group(): expected array while got "+typeof t)}}function image_grid_page_next(){_via_image_grid_stack_prev_page.push(_via_image_grid_page_first_index),_via_image_grid_page_first_index=_via_image_grid_page_last_index,image_grid_clear_content(),_via_image_grid_load_ongoing=!0,image_grid_page_nav_show_cancel(),image_grid_content_append_img(_via_image_grid_page_first_index)}function image_grid_page_prev(){_via_image_grid_page_first_index=_via_image_grid_stack_prev_page.pop(),_via_image_grid_page_last_index=-1,image_grid_clear_content(),_via_image_grid_load_ongoing=!0,image_grid_page_nav_show_cancel(),image_grid_content_append_img(_via_image_grid_page_first_index)}function image_grid_page_nav_show_cancel(){var e=document.getElementById("image_grid_nav"),t=[];t.push("<span>Loading images ... </span>"),t.push('<span class="text_button" onclick="image_grid_cancel_load_ongoing()">Cancel</span>'),e.innerHTML=t.join("")}function image_grid_cancel_load_ongoing(){_via_image_grid_load_ongoing=!1}function _via_region(e,t,i,a,_,s){this.shape=e,this.id=t,this.scale_factor=a,this.offset_x=_,this.offset_y=s,this.recompute_svg=!1,this.attributes={};var n,r=i.length;if(this.dview=new Array(r),this.dimg=new Array(r),0!==r)for(n=0;n<r;n++){this.dimg[n]=i[n];var o=this.offset_x;n%2!=0&&(o=this.offset_y),this.dview[n]=Math.round(this.dimg[n]*this.scale_factor)+o}switch(this.shape){case"rect":_via_region_rect.call(this),this.svg_attributes=["x","y","width","height"];break;case"circle":_via_region_circle.call(this),this.svg_attributes=["cx","cy","r"];break;case"ellipse":_via_region_ellipse.call(this),this.svg_attributes=["cx","cy","rx","ry","transform"];break;case"line":_via_region_line.call(this),this.svg_attributes=["x1","y1","x2","y2"];break;case"polyline":_via_region_polyline.call(this),this.svg_attributes=["points"];break;case"polygon":_via_region_polygon.call(this),this.svg_attributes=["points"];break;case"point":_via_region_point.call(this),this.shape="circle",this.svg_attributes=["cx","cy","r"]}this.initialize()}function _via_region_rect(){this.is_inside=_via_region_rect.prototype.is_inside,this.is_on_edge=_via_region_rect.prototype.is_on_edge,this.move=_via_region_rect.prototype.move,this.resize=_via_region_rect.prototype.resize,this.initialize=_via_region_rect.prototype.initialize,this.dist_to_nearest_edge=_via_region_rect.prototype.dist_to_nearest_edge}function _via_region_circle(){this.is_inside=_via_region_circle.prototype.is_inside,this.is_on_edge=_via_region_circle.prototype.is_on_edge,this.move=_via_region_circle.prototype.move,this.resize=_via_region_circle.prototype.resize,this.initialize=_via_region_circle.prototype.initialize,this.dist_to_nearest_edge=_via_region_circle.prototype.dist_to_nearest_edge}function _via_region_ellipse(){this.is_inside=_via_region_ellipse.prototype.is_inside,this.is_on_edge=_via_region_ellipse.prototype.is_on_edge,this.move=_via_region_ellipse.prototype.move,this.resize=_via_region_ellipse.prototype.resize,this.initialize=_via_region_ellipse.prototype.initialize,this.dist_to_nearest_edge=_via_region_ellipse.prototype.dist_to_nearest_edge}function _via_region_line(){this.is_inside=_via_region_line.prototype.is_inside,this.is_on_edge=_via_region_line.prototype.is_on_edge,this.move=_via_region_line.prototype.move,this.resize=_via_region_line.prototype.resize,this.initialize=_via_region_line.prototype.initialize,this.dist_to_nearest_edge=_via_region_line.prototype.dist_to_nearest_edge}function _via_region_polyline(){this.is_inside=_via_region_polyline.prototype.is_inside,this.is_on_edge=_via_region_polyline.prototype.is_on_edge,this.move=_via_region_polyline.prototype.move,this.resize=_via_region_polyline.prototype.resize,this.initialize=_via_region_polyline.prototype.initialize,this.dist_to_nearest_edge=_via_region_polyline.prototype.dist_to_nearest_edge}function _via_region_polygon(){this.is_inside=_via_region_polygon.prototype.is_inside,this.is_on_edge=_via_region_polygon.prototype.is_on_edge,this.move=_via_region_polygon.prototype.move,this.resize=_via_region_polygon.prototype.resize,this.initialize=_via_region_polygon.prototype.initialize,this.dist_to_nearest_edge=_via_region_polygon.prototype.dist_to_nearest_edge}function _via_region_point(){this.is_inside=_via_region_point.prototype.is_inside,this.is_on_edge=_via_region_point.prototype.is_on_edge,this.move=_via_region_point.prototype.move,this.resize=_via_region_point.prototype.resize,this.initialize=_via_region_point.prototype.initialize,this.dist_to_nearest_edge=_via_region_point.prototype.dist_to_nearest_edge}function _via_file_resolve_all_to_default_filepath(){for(var e in _via_img_metadata)_via_img_metadata.hasOwnProperty(e)&&_via_file_resolve_file_to_default_filepath(e)}function _via_file_resolve_file_to_default_filepath(e){var t;_via_img_metadata.hasOwnProperty(e)&&(void 0===_via_img_fileref[e]||!_via_img_fileref[e]instanceof File)&&(is_url(_via_img_metadata[e].filename)?_via_img_src[e]=_via_img_metadata[e].filename:(t="file:/"+settings.defaultPath+_via_img_metadata[e].filename,_via_img_src[e]=t))}function _via_file_resolve_all(){return new Promise(function(t,i){for(var e=[],a=_via_file_get_search_path_list(),_=0;_<_via_img_count;++_){var s=_via_image_id_list[_];void 0!==_via_img_src[s]&&""!==_via_img_src[s]||(s=_via_file_resolve(_,a),e.push(s))}Promise.all(e).then(function(e){console.log(e),t()},function(e){console.log(e),i()})})}function _via_file_get_search_path_list(){var e,t=[];for(e in _via_settings.core.filepath)0!==_via_settings.core.filepath[e]&&t.push(e);return t}function _via_file_resolve(e,a){return new Promise(function(t,i){_via_file_resolve_check_path(e,0,a).then(function(e){t(e)},function(e){i(e)})},!1)}function _via_file_resolve_check_path(s,n,r){return new Promise(function(t,i){var e=_via_image_id_list[s],a=new Image(0,0),_=r[n]+_via_img_metadata[e].filename;is_url(_via_img_metadata[e].filename)&&""!==r[n]&&(_=r[n]+get_filename_from_url(_via_img_metadata[e].filename)),a.setAttribute("src",_),a.addEventListener("load",function(){_via_img_src[e]=_,t(s)},!1),a.addEventListener("abort",function(){i(s)}),a.addEventListener("error",function(){var e=n+1;e<r.length?_via_file_resolve_check_path(s,e,r).then(function(e){t(s)},function(e){i(s)}):i(s)},!1)},!1)}function show_page_404(e){$("#loading").addClass("d-none"),buffer.hideCurrentImage(),set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_404),$("#selection_panel").hide(),_via_image_index=e,_via_image_id=_via_image_id_list[_via_image_index],buffer.imgLoaded=!1,sidebar.img_fn_list_ith_entry_selected(_via_image_index,!0),document.getElementById("page_404_filename").innerHTML="["+(_via_image_index+1)+"]"+_via_img_metadata[_via_image_id].filename}function is_url(e){return!!(e.startsWith("http://")||e.startsWith("https://")||e.startsWith("www."))}function get_filename_from_url(e){return e.substring(e.lastIndexOf("/")+1)}function fixfloat(e){return parseFloat(e.toFixed(VIA_FLOAT_PRECISION))}function shape_attribute_fixfloat(e){for(var t in e)switch(t){case"x":case"y":case"width":case"height":case"r":case"rx":case"ry":e[t]=fixfloat(e[t]);break;case"all_points_x":case"all_points_y":for(var i in e[t])e[t][i]=fixfloat(e[t][i])}}function array_intersect(e){if(0===e.length)return[];if(1===e.length)return e[0];for(var t=e[0],i=0,a=1;a<e.length;++a)e[a].length<t.length&&(t=e[a],i=a);var _=[],s={};for(a=0;a<e.length;++a)for(var n=0===a?i:a,r=0;r<e[n].length;++r)s[e[n][r]]===a-1?a===e.length-1?(_.push(e[n][r]),s[e[n][r]]=0):s[e[n][r]]=a:s[e[n][r]]=0;return _}function generate_img_index_list(e){var t=[],i=[];if(""!==e.prev_next_count.value){var a=e.prev_next_count.value.split(",");if(2===a.length){var _=parseInt(a[0]),s=parseInt(a[1]);for(h=_via_image_index-_;h<=_via_image_index+s;h++)i.push(h)}}0!==i.length&&t.push(i);var n=[];if(""!==e.img_index_list.value){var r=e.img_index_list.value.split(",");if(0!==r.length)for(h=0;h<r.length;++h)if(r[h].includes("-"))for(var o=r[h].split("-"),l=parseInt(o[0])-1,g=parseInt(o[1])-1,d=l;d<=g;++d)n.push(d);else n.push(parseInt(r[h])-1)}0!==n.length&&t.push(n);var c=[];if(""!==e.regex.value)for(var u=e.regex.value,h=0;h<_via_image_filename_list.length;++h)null!==_via_image_filename_list[h].match(u)&&c.push(h);return 0!==c.length&&t.push(c),array_intersect(t)}function img_stat_set(e,t){t.length?_via_img_stat[e]=t:delete _via_img_stat[e]}function img_stat_set_all(){return new Promise(function(t,i){var e,a,_=[];for(a in _via_image_id_list)_via_img_stat.hasOwnProperty(a)||(e=_via_image_id_list[a],_via_img_metadata[e].file_attributes.hasOwnProperty("width")&&_via_img_metadata[e].file_attributes.hasOwnProperty("height")?_via_img_stat[a]=[_via_img_metadata[e].file_attributes.width,_via_img_metadata[e].file_attributes.height]:_.push(img_stat_get(a)));_.length?Promise.all(_).then(function(e){t()}.bind(this),function(e){console.warn("Failed to read statistics of all images!"),i()}):t()}.bind(this))}function img_stat_get(s){return new Promise(function(e,t){var i=_via_image_id_list[s],a=document.createElement("img"),_=null;a.addEventListener("load",function(){_via_img_stat[s]=[a.naturalWidth,a.naturalHeight],null!==_&&URL.revokeObjectURL(_),e()}.bind(this)),a.addEventListener("error",function(){_via_img_stat[s]=[-1,-1],null!==_&&URL.revokeObjectURL(_),e()}.bind(this)),_via_img_fileref[i]instanceof File?(_=URL.createObjectURL(_via_img_fileref[i]),a.src=_):a.src=_via_img_src[i]}.bind(this))}function polygon_to_bbox(e){for(var t=1/0,i=-1/0,a=1/0,_=-1/0,s=0;s<e.length;s+=2)i<e[s]&&(i=e[s]),e[s]<t&&(t=e[s]),_<e[s+1]&&(_=e[s+1]),e[s+1]<a&&(a=e[s+1]);return[t,a,i-t,_-a]}function Annotate(){this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.modal=$("#staticBackdropProgress"),this.progress=$("#annotationProgress"),this.progressNum=0,this.reAnnotateCommand=""}_via_region.prototype.prepare_svg_element=function(){this.svg_element=document.createElementNS("http://www.w3.org/2000/svg",this.shape),this.svg_string="<"+this.shape,this.svg_element.setAttributeNS(null,"id",this.id);for(var e=this.svg_attributes.length,t=0;t<e;t++)this.svg_element.setAttributeNS(null,this.svg_attributes[t],this[this.svg_attributes[t]]),this.svg_string+=" "+this.svg_attributes[t]+'="'+this[this.svg_attributes[t]]+'"';this.svg_string+="/>"},_via_region.prototype.get_svg_element=function(){return this.recompute_svg&&(this.prepare_svg_element(),this.recompute_svg=!1),this.svg_element},_via_region.prototype.get_svg_string=function(){return this.recompute_svg&&(this.prepare_svg_element(),this.recompute_svg=!1),this.svg_string},_via_region_rect.prototype.initialize=function(){this.dview[0]<this.dview[2]?(this.x=this.dview[0],this.x2=this.dview[2]):(this.x=this.dview[2],this.x2=this.dview[0]),this.dview[1]<this.dview[3]?(this.y=this.dview[1],this.y2=this.dview[3]):(this.y=this.dview[3],this.y2=this.dview[1]),this.width=this.x2-this.x,this.height=this.y2-this.y,this.recompute_svg=!0},_via_region_circle.prototype.initialize=function(){this.cx=this.dview[0],this.cy=this.dview[1];var e=this.dview[2]-this.dview[0],t=this.dview[3]-this.dview[1];this.r=Math.round(Math.sqrt(e*e+t*t)),this.r2=this.r*this.r,this.recompute_svg=!0},_via_region_ellipse.prototype.initialize=function(){this.cx=this.dview[0],this.cy=this.dview[1],this.rx=Math.abs(this.dview[2]-this.dview[0]),this.ry=Math.abs(this.dview[3]-this.dview[1]),this.inv_rx2=1/(this.rx*this.rx),this.inv_ry2=1/(this.ry*this.ry),this.recompute_svg=!0},_via_region_line.prototype.initialize=function(){this.x1=this.dview[0],this.y1=this.dview[1],this.x2=this.dview[2],this.y2=this.dview[3],this.dx=this.x1-this.x2,this.dy=this.y1-this.y2,this.mconst=this.x1*this.y2-this.x2*this.y1,this.recompute_svg=!0},_via_region_polyline.prototype.initialize=function(){for(var e=this.dview.length,t=new Array(e/2),i=0,a=0;a<e;a+=2)t[i]=this.dview[a]+" "+this.dview[a+1],i++;this.points=t.join(","),this.recompute_svg=!0},_via_region_polygon.prototype.initialize=function(){for(var e=this.dview.length,t=new Array(e/2),i=0,a=0;a<e;a+=2)t[i]=this.dview[a]+" "+this.dview[a+1],i++;this.points=t.join(","),this.recompute_svg=!0},_via_region_point.prototype.initialize=function(){this.cx=this.dview[0],this.cy=this.dview[1],this.r=2,this.r2=this.r*this.r,this.recompute_svg=!0},_via_is_debug_mode||(window.onbeforeunload=function(e){return(e=e||window.event)&&(e.returnValue="Did you save your data?"),"Did you save your data?"});let AutoAnnotator=new Annotate;function undoRedoWorker(){let i="INCREMENT";let a={};let _={INCREMENT:e=>{let t=e.count;return{execute(){e.count+=1},undo(){e.count=t}}},DECREMENT:e=>{let t=e.count;return{execute(){--e.count},undo(){e.count=t}}}};let s={name:"via_metadata",count:0},n=(t=>{let i=[null],a=0;return{doCommand(e){a<i.length-1&&(i=i.slice(0,a+1)),_[e]&&(e=_[e](t),i.push(e),a+=1,e.execute())},undo(){0<a&&(i[a].undo(),--a),e()},redo(){a<i.length-1&&(a+=1,i[a].execute()),e()}}})(s),r=[];function e(){s.count<r.length?(a=JSON.parse(r[s.count]),0===s.count&&1<r.length?self.postMessage({undo_enabled:!1,redo_enabled:!0,region_metadata:a,is_update:!0}):s.count===r.length-1&&1<r.length?self.postMessage({undo_enabled:!0,redo_enabled:!1,region_metadata:a,is_update:!0}):0===s.count&&1===r.length?self.postMessage({undo_enabled:!1,redo_enabled:!1,region_metadata:a,is_update:!0}):self.postMessage({undo_enabled:!0,redo_enabled:!0,region_metadata:a,is_update:!0})):n.undo()}self.onmessage=function(e){switch(e.data.commands){case"undo":n.undo(),a=e.data.region_metadata;break;case"redo":n.redo(),a=e.data.region_metadata;break;case"add":a=e.data.region_metadata,(()=>{s.count!==r.length&&(s.count>r.length?r.splice(0,r.length):r.splice(s.count,r.length),s.count=r.length);var e,t=JSON.stringify(a);for(e of r)if(e===t)return;r.push(t),1<r.length&&self.postMessage({undo_enabled:!0,redo_enabled:!1}),n.doCommand(i)})();break;case"reset":r=[],s.count=0,self.postMessage({undo_enabled:!1,redo_enabled:!1})}}}Annotate.prototype.run_annotation=function(){switch(_via_display_area_content_name){case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:this.modal.modal("show"),this.annotate_all_imgs();break;case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:this.modal.modal("show"),this.annotate_single_img().then(()=>{this.setProgressBar(100),"cancel"===this.reAnnotateCommand?this.reAnnotateCommand="":($("#annotation_spinner").removeClass("d-none"),Message.show({address:"Success",body:"The image uploaded successfully."}))});break;default:Message.show({address:"Error",body:"Please load an image first."})}},Annotate.prototype.annotate_all_imgs=function(){try{this.progressNum=10,this.setProgressBar(this.progressNum),this.sendImageAll().then(()=>{this.setProgressBar(100),$("#annotation_spinner").removeClass("d-none")})}catch(e){this.progressNum=0,setTimeout(function(){$("#staticBackdropProgress").modal("hide")},1e3),this.progress.css("width","0%"),console.log(e),Message.show({address:"Error",body:"Please check your network connection or load the image again."})}},Annotate.prototype.annotate_single_img=async function(){try{this.progressNum=10,this.setProgressBar(this.progressNum);var e=_via_image_index,t=_via_image_id_list[e],i=$("#bim"+e)[0],a=await this.getImageBlobFromElement(i);switch(await this.isAnnotated(t).then(e=>{}),this.reAnnotateCommand){case"reAnnotate":_via_img_metadata[t].autoAnnotated=!1;break;case"cancel":return this.setProgressBar(100),void Message.show({address:"Info",body:"The image was not annotated."})}await this.sendImage(a,_via_img_metadata[_via_image_id_list[e]].filename)}catch(e){console.log(e),this.progressNum=0,setTimeout(function(){$("#staticBackdropProgress").modal("hide")},1e3),this.progress.css("width","0%"),Message.show({address:"Error",body:"Please check your network connection or load the image again."})}},Annotate.prototype.setProgressBar=function(e){this.progress.animate({width:e+"%"},100),100===e&&(this.progress.css("width","100%"),setTimeout(function(){this.modal.modal("hide"),this.progress.css("width","0%")}.bind(this),1e3))},Annotate.prototype.setModal=function(e,t,i){$("#staticBackdropModal h1").html(e),$("#modalBody").html(t),$("#modalFooter").html(i),setTimeout(function(){$("#staticBackdropModal").modal("show")},1e3)},Annotate.prototype.clearModal=function(){$("#staticBackdropModal").modal("hide"),$("#staticBackdropModal h1").html(""),$("#modalBody").html(""),$("#modalFooter").html("")},Annotate.prototype.awaitUserInput=function(t=!1){return new Promise(e=>{$("#modalFooter").on("click","#reAnnotate",function(){e("reAnnotate")}),$("#modalFooter").on("click","#cancel",function(){e("cancel")}),t&&$("#modalFooter").on("click","#reAnnotateAll",function(){e("reAnnotateAll")})})},Annotate.prototype.addScores=function(e,t,i,a){if(t.hasOwnProperty("slice_scores")&&t.hasOwnProperty("infarct_scores")&&t.hasOwnProperty("risk_scores")&&void 0!==i)switch(i.region_attributes.Type){case"Slice":i.score=t.slice_scores[a];break;case"Infarct":i.score=t.infarct_scores[a];break;case"Risk":i.score=t.risk_scores[a]}},Annotate.prototype.getImageBlobFromElement=async function(_){return new Promise((t,e)=>{var i=document.createElement("canvas"),a=i.getContext("2d");i.width=_.width,i.height=_.height,a.drawImage(_,0,0,_.width,_.height),i.toBlob(e=>{t(e)},"image/png")})},Annotate.prototype.isAnnotated=async function(e){_via_img_metadata[e].autoAnnotated&&(this.setModal("Notice",'<p class ="fw-bold" >This image is already annotated. Do you want to re-annotate them?</p><p class ="fw-light">If you click "Re-Annotate", the old annotations will be deleted.</p><p class ="fw-light">If you click "Cancel", the old annotations will be kept.</p>','<button type="button" class="btn btn-primary" id="reAnnotate">Re-Annotate</button><button type="button" class="btn btn-secondary" id="cancel">Cancel</button>'),this.reAnnotateCommand=await this.awaitUserInput(),this.clearModal())},Annotate.prototype.isAnnotatedAll=async function(e){_via_img_metadata[e].autoAnnotated&&"reAnnotateAll"!==this.reAnnotateCommand&&($("#staticBackdropModal h1").html("Notice"),this.setModal("Notice",'<p class ="fw-bold" >This image is already annotated. Do you want to re-annotate them?</p><p class ="fw-light">If you click "Re-Annotate", the old annotations will be deleted.</p><p class ="fw-light">If you click "Cancel", the old annotations will be kept.</p>','<button type="button" class="btn btn-primary" id="reAnnotate">Re-Annotate</button><button type="button" class="btn btn-primary" id="reAnnotateAll">Re-Annotate all</button><button type="button" class="btn btn-secondary" id="cancel">Cancel</button>'),this.reAnnotateCommand=await this.awaitUserInput(!0),this.clearModal())},Annotate.prototype.addAnnotations=async function(a,_){let s=!0,n=-1;"reAnnotate"!==this.reAnnotateCommand&&"reAnnotateAll"!==this.reAnnotateCommand||(_via_img_metadata[a].regions=[],_via_img_metadata[a].autoAnnotated=!1,_via_img_metadata[a].lockedRegions.clear());for(let i=0;i<_.masks.length;i++){var r={shape_attributes:{},region_attributes:{}};let e="Risk";0===_.class_ids[i]?e="Slice":1===_.class_ids[i]&&(e="Infarct"),"Slice"===(r.region_attributes.Type=e)&&s&&(n++,s=!1),"Slice"!==e&&(s=s||!0),this.addScores(a,_,r,n);var o=[],l=[];let t=1;if(1<_.masks[i].length){for(let e=0;e<_.masks[i].length;e+=2)15<_.masks[i].length&&t%(settings.reduction+1)!=0||(o.push(_.masks[i][e]),l.push(_.masks[i][e+1])),t++;r.shape_attributes={name:"polygon",all_points_x:o,all_points_y:l},2<o.length&&(_via_img_metadata[a].regions.push(r),_via_img_metadata[a].addLockedRegion(_via_img_metadata[a].regions.length-1))}}_via_img_metadata[a].autoAnnotated=!0},Annotate.prototype.sendImage=async function(e,t){try{var i=new FormData;i.append("image",e,t),$.ajax({type:"POST",url:settings.serverAddress+"/upload",data:i,contentType:!1,processData:!1,xhr:function(){var e=new window.XMLHttpRequest;return e.upload.addEventListener("progress",function(e){e.lengthComputable&&(e=e.loaded/e.total*100,AutoAnnotator.setProgressBar(e))}),e},success:function(e){if($("#annotation_spinner").addClass("d-none"),null==e)Message.show({address:"Error",body:"Error sending images!"});else if("login error"===e)Message.show({address:"Error",body:"Please login again."}),setTimeout(function(){window.location.href="/login"},2e3);else{for(var t in zoom.resetZoom(),e){var i=_via_image_id_list[_via_image_filename_list.indexOf(t)];AutoAnnotator.addAnnotations(i,e[t])}AutoAnnotator.setAllItemSuccess(),Message.show({address:"Success",body:"the image annotated successfully."})}},error:function(e){console.error(e),AutoAnnotator.setProgressBar(0),$("#annotation_spinner").addClass("d-none"),setTimeout(function(){$("#staticBackdropProgress").modal("hide")},1e3),Message.show({address:"Error",body:"Error sending images!"})}})}catch(e){return console.log(e),!1}},Annotate.prototype.sendImageAll=async function(){try{var e,t=new FormData;let a=!1;for(e of _via_image_grid_selected_img_index_list){var i=e,_=_via_image_id_list[i];if(await this.isAnnotatedAll(_),"cancel"!==this.reAnnotateCommand){"reAnnotateAll"!==this.reAnnotateCommand&&"reAnnotate"!==this.reAnnotateCommand||(a=!0);let e=$("#bim"+i)[0];void 0===e&&(await buffer.addImageToBuffer(i),e=$("#bim"+i)[0]);var s=await this.getImageBlobFromElement(e),n=_via_img_metadata[_].filename;t.append("image",s,n)}}a&&(this.reAnnotateCommand="reAnnotate"),"cancel"===this.reAnnotateCommand?(this.progressNum=100,this.setProgressBar(this.progressNum)):$.ajax({type:"POST",url:settings.serverAddress+"/upload",data:t,contentType:!1,processData:!1,xhr:function(){var e=new window.XMLHttpRequest;return e.upload.addEventListener("progress",function(e){e.lengthComputable&&(e=e.loaded/e.total*100,AutoAnnotator.setProgressBar(e))}),e},success:function(e){if($("#annotation_spinner").addClass("d-none"),null==e)Message.show({address:"Error",body:"Error sending images!"});else if("login error"===e)Message.show({address:"Error",body:"Please login again."}),setTimeout(function(){window.location.href="/login"},2e3);else{for(var t in zoom.resetZoom(),e){var i=_via_image_id_list[_via_image_filename_list.indexOf(t)];a&&(_via_img_metadata[i].regions=[]),AutoAnnotator.addAnnotations(i,e[t])}AutoAnnotator.setAllItemSuccess()}}.bind(this),error:function(e){console.error(e),$("#annotation_spinner").addClass("d-none"),AutoAnnotator.setProgressBar(0),setTimeout(function(){$("#staticBackdropProgress").modal("hide")},1e3),Message.show({address:"Error",body:"Error sending images!"})}})}catch(e){return console.log(e),!1}},Annotate.prototype.setAllItemSuccess=function(){_via_is_all_region_selected=!1,drawing.setIsRegionSelected(!1),drawing.setUserSelRegionId(-1),zoom.resetZoom(),_via_load_canvas_regions(),0===_via_canvas_regions.length?drawing.clearCanvas():drawing.redrawRegCanvas(),zoom.fixCanvasBlur(),_via_reg_canvas.focus(),setTimeout(function(){plugin.updateSliceRegion()},1e3),this.progressNum=100,this.setProgressBar(this.progressNum),this.reAnnotateCommand=""};let undoredo_worker=new Worker(URL.createObjectURL(new Blob(["("+undoRedoWorker.toString()+")()"],{type:"text/javascript"}))),File_Region=function(e={},t={}){this.shape_attributes=new Proxy(e,validator),this.region_attributes=new Proxy(t,validator)};function trueTypeOf(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}let validator={get:function(e,t){return"_isProxy"===t||(["object","array"].includes(trueTypeOf(e[t]))&&!e[t]._isProxy&&(e[t]=new Proxy(e[t],validator)),e[t])},set:function(e,t,i){return e[t]=i,undo_redo_enabled.enabled&&(enableUndoRedo(),setTimeout(function(){return enableUndoRedo(),!0},250)),!0},deleteProperty:function(e,t){return delete e[t],!0}};function enableUndoRedo(){undo_redo_enabled.enabled=!undo_redo_enabled.enabled}let validator2={get:function(e,t){return e[t]},set:function(e,t,i){return e[t]=i,undo_redo_enabled.enabled&&(!1===now_update?undoredo_worker.postMessage({commands:"add",region_metadata:JSON.stringify(_via_img_metadata[_via_image_id].regions)}):now_update=!1),!0}},undo_redo_enabled=new Proxy({enabled:!0},validator2),now_update=!1;undoredo_worker.addEventListener("message",function(e){if(e.data.undo_enabled?$("#nav_undo").removeClass("disabled"):$("#nav_undo").addClass("disabled"),e.data.redo_enabled?$("#nav_redo").removeClass("disabled"):$("#nav_redo").addClass("disabled"),e.data.hasOwnProperty("region_metadata")){undo_redo_enabled.enabled=!1,now_update=!0;var t,e=JSON.parse(e.data.region_metadata);_via_img_metadata[_via_image_id].regions=[];for(t of e)_via_img_metadata[_via_image_id].regions.push(new File_Region(t.shape_attributes,t.region_attributes));_via_is_all_region_selected=!1,_via_load_canvas_regions(),drawing.redrawRegCanvas(),drawing.setIsRegionSelected(!1),drawing.setUserSelRegionId(-1),_via_reg_canvas.focus(),undo_redo_enabled.enabled=!0}}),$("#nav_brand").click(function(){return show_home_panel(),!1}),$("#nav_home").click(function(){return show_home_panel(),!1}),$("#nav_project_load").click(function(){return project.openSelectProjectFile(),!1}),$("#nav_project_save").click(function(){return project.saveWithConfirm(),!1}),$("#nav_project_setting").click(function(){return settings.toggleSettings(),!1}),$("#nav_project_addLocFiles").click(function(){return sel_local_images(),!1}),$("#nav_project_addFilUrl").click(function(){return project.addUrlFileWithInput(),!1}),$("#nav_project_addFilAbs").click(function(){return project.addAbsPathFileWithInput(),!1}),$("#nav_annotation_export").click(function(){return fileManager.downloadAllRegionDataModal(),!1}),$("#nav_annotation_import").click(function(){return fileManager.importAnnotationsFromFile(),!1}),$("#nav_annotation_prevAnn").click(function(){return show_annotation_data(),!1}),$("#nav_annotation_downImage").click(function(){return download_as_image(),!1}),$("#nav_annotation_autoAnn").click(function(){return AutoAnnotator.run_annotation(),!1}),$("#nav_view_imgGrid").click(function(){return image_grid_toggle(),!1}),$("#nav_view_sidebar").click(function(){return leftsidebar_toggle(),!1}),$("#nav_view_status").click(function(){return toggle_message_visibility(),!1}),$("#nav_view_regionBound").click(function(){return toggle_region_boundary_visibility(),!1}),$("#nav_view_regionLabel").click(function(){return toggle_region_id_visibility(),!1}),$("#nav_openProject").click(function(){return project.openSelectProjectFile(),!1}),$("#nav_saveProject").click(function(){return project.saveWithConfirm(),!1}),$("#nav_settings").click(function(){return settings.toggleSettings(),!1}),$("#nav_imgGrid").click(function(){return image_grid_toggle(),!1}),$("#nav_sidePanel").click(function(){return leftsidebar_toggle(),!1}),$("#nav_autoAnn").click(function(){return AutoAnnotator.run_annotation(),!1}),$("#nav_calcAreas").click(function(){return plugin.ExportArea(),!1}),$("#nav_prev").click(function(){return move_to_prev_image(),!1}),$("#nav_next").click(function(){return move_to_next_image(),!1}),$("#nav_undo").click(function(){return undoredo_worker.postMessage({commands:"undo"}),!1}),$("#nav_redo").click(function(){return undoredo_worker.postMessage({commands:"redo"}),!1}),$("#nav_zoomIn").click(function(){return zoom.zoomIn(),!1}),$("#nav_zoomOut").click(function(){return zoom.zoomOut(),!1}),$("#nav_selAllReg").click(function(){return sel_all_regions(),!1}),$("#nav_copySelReg").click(function(){return copy_sel_regions(),!1}),$("#nav_pasteReg").click(function(){return paste_sel_regions_in_current_image(),!1}),$("#nav_delSelReg").click(function(){return del_sel_regions(),!1}),$("#shape_drag").click(function(){return select_region_shape("drag"),!1}),$("#shape_circle").click(function(){return select_region_shape("circle"),!1}),$("#shape_ellipse").click(function(){return select_region_shape("ellipse"),!1}),$("#shape_pen").click(function(){return select_region_shape("pen"),!1}),$("#shape_polygon").click(function(){return select_region_shape("polygon"),!1}),$("#shape_edit").click(function(){return select_region_shape("edit"),!1}),$("#shape_remove").click(function(){return select_region_shape("remove"),!1}),$("#shape_rect").click(function(){return select_region_shape("rect"),!1}),$("#shape_trim").click(function(){return select_region_shape("trim"),!1}),$("#image_settings").click(function(){return select_region_shape("img_set"),!1}),$("#project_name").change(function(){return project.onNameUpdate(this),!1}),$("#sidebar_addFiles").click(function(){return sel_local_images(),!1}),$("#sidebar_addUrl").click(function(){return project.addUrlFileWithInput(),!1}),$("#sidebar_remove").click(function(){return project.fileRemoveWithConfirm(),!1}),$("#sidebar_ToggleAEModes").click(function(){return sidebar.toggleAEMode(),!1}),$("#sidebar_UpdateSlices").click(function(){return plugin.updateSliceRegion(),!1}),$("#sidebar_reset").click(function(){return image.backToSidebar(),select_region_shape("edit"),!1}),$("#image_reset").click(function(){return image.resetFilters(),!1}),$("#sidebar_onImageEditor").click(function(){return sidebar.annotation_editor_toggle_on_image_editor(),!1}),$("#start_selectImages").click(function(){return sel_local_images(),!1}),$("#start_addUrlImages").click(function(){return project.addUrlFileWithInput(),!1}),$("#start_settings").click(function(){return settings.toggleSettings(),!1}),$("#start_saveProject").click(function(){return project.saveWithConfirm(),!1}),$("#start_loadProject").click(function(){return project.openSelectProjectFile(),!1}),$("#start_gettingStarted").click(function(){return set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED),!1}),$("#404_settings").click(function(){return settings.toggleSettings(),!1}),$("#404_selectImages").click(function(){return sel_local_images(),!1}),$("#404_selectFolder").click(function(){return project.loadAllImages(),!1}),$("#404_gettingStarted").click(function(){return set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED),!1}),$("#nav_help_getStart").click(function(){return set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED),!1}),$("#image_grid_toolbar_group_by_select").change(function(){return image_grid_toolbar_onchange_group_by_select(this),!1});
\ No newline at end of file
diff --git a/templates/app.html b/templates/app.html
new file mode 100644
index 0000000000000000000000000000000000000000..46339d0bc38509577ce77a46ab484a4c7a7170de
--- /dev/null
+++ b/templates/app.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>InfarctSize</title><link href="assets/css/main.min.css" rel="stylesheet"><link rel="shortcut icon" href="assets/favicon.ico"></head><body onload="_via_init()" onresize="drawing.updateUiComponents()"><input id="fileInput" name="files" style="display: none" type="file"> <input type="file" id="projectHelperFileInput" name="fileList" webkitdirectory style="display: none"><div id="user_input_panel"></div><nav aria-label="the main toolbar" id="ui_top_panel" class="navbar navbar-pharma py-0 navbar-expand-lg navbar-dark mb-1"><div class="container-fluid"><a class="navbar-brand" href="https://www.pharmahungary.com/" target="_blank"><img src="https://www.pharmahungary.com/wp-content/themes/pharma/assets/img/logo.svg" alt="" width="140" height="40" class="d-inline-block align-text-top"> </a><button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-bs-target="#navbarSupportedContent" data-bs-toggle="collapse" type="button"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarSupportedContent"><ul class="navbar-nav me-auto mb-2 mb-lg-0"><li class="nav-item"><a aria-current="page" class="nav-link" href="#" id="nav_home">Home</a></li><li class="nav-item dropdown"><a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="projectDropdown" role="button">Files</a><ul aria-labelledby="navbarDropdown" class="dropdown-menu"><li><a class="dropdown-item" href="#" id="nav_project_load">Load project</a></li><li><a class="dropdown-item" href="#" id="nav_project_save">Save project</a></li><li><a class="dropdown-item" href="#" id="nav_annotation_downImage">Save image with annotations</a></li><li><hr class="dropdown-divider"></li><li><a class="dropdown-item" href="#" id="nav_project_addLocFiles">Add local files</a></li><li><a class="dropdown-item" href="#" id="nav_project_addFilUrl">Add files from URL</a></li><li><a class="dropdown-item" href="#" id="nav_project_addFilAbs">Load image folder</a></li><li><hr class="dropdown-divider"></li><li><a class="dropdown-item" href="#" id="nav_project_setting">Settings</a></li></ul></li><li class="nav-item dropdown"><a aria-expanded="false" class="nav-link" href="#" id="nav_annotation_autoAnn" role="button">Auto annotation <i class="bi bi-stars"></i></a></li><li class="nav-item dropdown"><a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="viewrDropdown" role="button">View</a><ul aria-labelledby="navbarDropdown" class="dropdown-menu"><li><a class="dropdown-item" href="#" id="nav_view_imgGrid">Image grid view</a></li><li><a class="dropdown-item" href="#" id="nav_view_sidebar">Show/hide sidebar</a></li><li><a class="dropdown-item" href="#" id="nav_view_status">Show/hide message</a></li><li><hr class="dropdown-divider"></li><li><a class="dropdown-item" href="#" id="nav_view_regionBound">Show/hider region boundaries (b)</a></li><li><a class="dropdown-item" href="#" id="nav_view_regionLabel">Show/hide region labels (l)</a></li></ul></li><li class="nav-item dropdown"><a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="helpDropdown" role="button">Help</a><ul aria-labelledby="navbarDropdown" class="dropdown-menu"><li><a class="dropdown-item" href="#" id="nav_help_getStart">Getting started</a></li><li><a class="dropdown-item" href="#" data-bs-target="#Report" data-bs-toggle="offcanvas" id="nav_help_reportIssue">Report issue</a></li><li><hr class="dropdown-divider"></li><li><a class="dropdown-item" data-bs-target="#About" data-bs-toggle="offcanvas" href="#" id="nav_help_about">About</a></li></ul></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_openProject" title="Open project"><i class="bi bi-archive"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_saveProject" title="Save project"><i class="bi bi-save"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_settings" title="Settings"><i class="bi bi-gear"></i></a></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_imgGrid" title="Image grid view"><i class="bi bi-grid"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_sidePanel" title="Toggle side panel"><i class="bi bi-layout-sidebar"></i></a></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_autoAnn" title="Auto annotation"><i class="bi bi-stars"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_calcAreas" title="Export results"><i class="bi bi-file-earmark-arrow-down"></i></a></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_prev" title="Previous"><i class="bi bi-caret-left"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_next" title="Next"><i class="bi bi-caret-right"></i></a></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link disabled" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_undo" title="Undo"><i class="bi bi-arrow-counterclockwise"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link disabled" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_redo" title="Redo"><i class="bi bi-arrow-clockwise"></i></a></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_zoomIn" title="Zoom in"><i class="bi bi-zoom-in"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_zoomOut" title="Zoom out"><i class="bi bi-zoom-out"></i></a></li><li class="nav-item d-none d-lg-block" style="margin-left: 1em"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_selAllReg" title="Select all regions"><i class="bi bi-check2-all"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_copySelReg" title="Copy selected regions"><i class="bi bi-clipboard-check"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_pasteReg" title="Paste regions"><i class="bi bi-clipboard"></i></a></li><li class="nav-item d-none d-lg-block"><a class="nav-link" data-bs-placement="bottom" data-bs-toggle="tooltip" href="#" id="nav_delSelReg" title="Delete selected regions"><i class="bi bi-trash3"></i></a></li><li class="nav-item"><a aria-current="page" class="nav-link" href="/profile" id="nav_profile">Profile <i class="bi bi-person"></i></a></li><li class="nav-item"><a class="nav-link" href="/logout" id="nav_logout">Logout <i class="bi bi-box-arrow-right"></i></a></li></ul></div></div></nav><div id="image_buffer" style="display: none"></div><div class="container-fluid"><div class="row mb-2"><div class="col-lg-3 g-0" id="sidebar_container"><div class="accordion" id="leftsidebar"><div class="accordion-item"><h2 class="accordion-header" id="panelsStayOpen-headingOne"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapseOne" aria-expanded="true" aria-controls="panelsStayOpen-collapseOne">Project</button></h2><div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show" aria-labelledby="panelsStayOpen-headingOne"><div class="accordion-body"><div class="form-floating mb-3"><input class="form-control" id="project_name" placeholder="Name of project" type="text"> <label for="project_name">Project name</label></div><div class="input-group-sm mb-3"><select aria-label="Fn list search options" class="form-control" id="filelist_preset_filters_list"><option value="all">All files</option><option value="files_without_region">Show files without regions</option><option value="files_error_loading">Files that could not be loaded</option></select> <input aria-label="Fn list search text box" class="form-control" id="img_fn_list_regex" placeholder="Search files" type="text"></div><div aria-hidden="true" class="card mb-3 position-relative overflow-auto" style="height: 150px"><div class="card-body position-relative overflow-auto h-25" id="img_fn_list"><h5 class="card-title placeholder-glow"><span class="placeholder col-6"></span></h5><p class="card-text placeholder-glow"><span class="placeholder col-7"></span> <span class="placeholder col-4"></span> <span class="placeholder col-4"></span> <span class="placeholder col-6"></span> <span class="placeholder col-8"></span></p></div></div><div aria-label="Sidebar fn list buttons" class="btn-group w-100" role="group"><button class="btn btn-primary" id="sidebar_addFiles" type="button">Add files</button> <button class="btn btn-primary" id="sidebar_addUrl" type="button">Add URL</button></div></div></div></div><div class="accordion-item"><h2 class="accordion-header" id="panelsStayOpen-headingTwo"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapseTwo" aria-expanded="false" aria-controls="panelsStayOpen-collapseTwo">Annotations</button></h2><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" style="height: 250px"><div class="card-body" id="annotation_editor_panel"><h5 class="card-title placeholder-glow"><span class="placeholder col-6"></span></h5><p class="card-text placeholder-glow"><span class="placeholder col-7"></span> <span class="placeholder col-4"></span> <span class="placeholder col-4"></span> <span class="placeholder col-6"></span> <span class="placeholder col-8"></span></p></div></div><div aria-label="Sidebar ae list buttons" class="btn-group w-100" role="group"><button class="btn btn-outline-primary" id="sidebar_ToggleAEModes" type="button" data-bs-toggle="button">Regions</button> <button class="btn btn-primary" id="sidebar_UpdateSlices" type="button" data-bs-toggle="tooltip" data-bs-original-title="Update the groups based on the slices">Group slices</button></div></div></div></div></div></div><div class="col-lg-3 g-0 d-none" id="image_man_container"><div class="d-flex flex-column flex-shrink-0 p-3 rounded-3 shadow"><a class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none" href="/"><span class="fs-4">Quick image settings</span></a><hr><div class="container"><a><label for="brightnessRange" class="form-label">Brightness</label> <input type="range" class="form-range" min="0" max="200.0" step="2" id="brightnessRange"> </a><a><label for="contrastRange" class="form-label">Contrast</label> <input type="range" class="form-range" min="0" max="200" step="2" id="contrastRange"></a></div><hr><a class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none" href="/"><span class="fs-4">Color settings</span></a><hr><div class="container"><a><label for="saturationRange" class="form-label">Saturation</label> <input type="range" class="form-range" min="0" max="200" step="2" id="saturationRange"> </a><a><label for="hueRange" class="form-label">Hue</label> <input type="range" class="form-range" min="0" max="360" step="2" id="hueRange"></a></div><hr><div class="container"></div><div aria-label="Sidebar ae list buttons" class="btn-group w-100" role="group"><button class="btn btn-primary" id="sidebar_reset" type="button">Back</button> <button class="btn btn-primary" id="image_reset" type="button">Reset</button></div></div></div><div class="col-lg"><div class="d-flex align-items-center position-absolute d-none" style="z-index: 150; right: 10px" id="annotation_spinner"><div class="container px-1"><div class="spinner-grow spinner-grow-sm text-info" role="status"><span class="visually-hidden">Loading...</span></div></div><span class="badge rounded-pill text-bg-info">Annotating... &nbsp;</span></div><div id="selection_panel" class="container-fluid shadow p-0 bg-body"><div class="list-group position-absolute" style="z-index: 40"><button aria-current="true" class="list-group-item list-group-item-action active" data-bs-placement="right" data-bs-toggle="tooltip" id="shape_edit" title="Editor mode" type="button"><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"><path d="M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" fill="currentColor"/></svg></button> <button class="list-group-item list-group-item-action" data-bs-placement="right" data-bs-toggle="tooltip" id="shape_drag" title="Pan tool" type="button"><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"><path fill="currentColor" d="M3 16C3 20.42 6.58 24 11 24C14.43 24 17.5 21.91 18.77 18.73L21.33 12.3C21.58 11.66 21.56 10.92 21.18 10.35C20.69 9.61 19.82 9.29 19 9.5L18.22 9.73C17.76 9.85 17.34 10.08 17 10.39V4.5C17 3.12 15.88 2 14.5 2C14.31 2 14.13 2 13.96 2.06C13.75 .89 12.73 0 11.5 0C10.44 0 9.54 .66 9.17 1.59C8.96 1.53 8.73 1.5 8.5 1.5C7.12 1.5 6 2.62 6 4V4.55C5.84 4.5 5.67 4.5 5.5 4.5C4.12 4.5 3 5.62 3 7V16M5 7C5 6.72 5.22 6.5 5.5 6.5S6 6.72 6 7V12H8V4C8 3.72 8.22 3.5 8.5 3.5S9 3.72 9 4V12H11V2.5C11 2.22 11.22 2 11.5 2S12 2.22 12 2.5V12H14V4.5C14 4.22 14.22 4 14.5 4S15 4.22 15 4.5V15H17L18 12.5C18.15 12.05 18.5 11.71 19 11.59L19.5 11.45L16.91 18C15.95 20.41 13.61 22 11 22C7.69 22 5 19.31 5 16V7Z"/></svg></button> <button class="list-group-item list-group-item-action" data-bs-placement="right" data-bs-toggle="tooltip" id="shape_polygon" title="Draw polygon" type="button"><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"><path d="M2,2V8H4.28L5.57,16H4V22H10V20.06L15,20.05V22H21V16H19.17L20,9H22V3H16V6.53L14.8,8H9.59L8,5.82V2M4,4H6V6H4M18,5H20V7H18M6.31,8H7.11L9,10.59V14H15V10.91L16.57,9H18L17.16,16H15V18.06H10V16H7.6M11,10H13V12H11M6,18H8V20H6M17,18H19V20H17" fill="currentColor"/></svg></button> <button class="list-group-item list-group-item-action" data-bs-placement="right" data-bs-toggle="tooltip" id="shape_remove" title="Reduce points tool (for poly and pencil elements). Also you can select a region and press R to apply for the selected mask." type="button"><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"><path d="M20 20V17H22V20C22 21.11 21.1 22 20 22H17V20H20M2 20V17H4V20H7V22H4C2.9 22 2 21.1 2 20M10 20H14V22H10V20M14.59 8L12 10.59L9.41 8L8 9.41L10.59 12L8 14.59L9.41 16L12 13.41L14.59 16L16 14.59L13.41 12L16 9.41L14.59 8M20 10H22V14H20V10M2 10H4V14H2V10M2 4C2 2.89 2.9 2 4 2H7V4H4V7H2V4M22 4V7H20V4H17V2H20C21.1 2 22 2.9 22 4M10 2H14V4H10V2Z" fill="currentColor"/></svg></button> <button class="list-group-item list-group-item-action" data-bs-placement="right" data-bs-toggle="tooltip" id="shape_trim" title="Trim (for poly and pencil elements)" type="button"><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7" fill="currentColor"/></svg></button> <button class="list-group-item list-group-item-action" data-bs-placement="right" data-bs-toggle="tooltip" id="image_settings" title="Image settings" type="button"><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"><path fill="currentColor" d="M3,17V19H9V17H3M3,5V7H13V5H3M13,21V19H21V17H13V15H11V21H13M7,9V11H3V13H7V15H9V9H7M21,13V11H11V13H21M15,9H17V7H21V5H17V3H15V9Z"/></svg></button></div></div><div class="container-fluid" id="display_area"><div class="container-fluid overflow-hidden rounded display_area_content d-none" style="height: 90vh; background-color: #fff;" id="image_panel_container"><div class="container container-fluid" id="image_panel"><canvas class="position-absolute" height="1" id="region_canvas" tabindex="1" width="1" style="z-index: 3; transform-origin: top left"></canvas></div></div><div id="image_grid_panel" class="display_area_content display_none"><div class="card mb-2"><div class="card-header" style="padding: 0 0.5rem"><div class="row g-2 align-items-center"><div class="col-auto"><label for="image_grid_toolbar_group_by_select" class="col-form-label">Group by&nbsp;</label></div><div class="col-auto"><select aria-label="Grid panel selector 1" class="form-select form-select-sm" id="image_grid_toolbar_group_by_select"></select></div></div></div><div class="card-body d-none" id="image_grid_group_panel_card" style="padding: 0.2rem 0.5rem"><div id="image_grid_group_panel"></div></div></div><div class="container mb-1"><div class="row align-items-center" id="image_grid_toolbar"><div class="input-group input-group-sm"><span class="input-group-text">Selected</span> <span id="image_grid_group_by_sel_img_count" class="input-group-text">0</span> <span class="input-group-text">of</span> <span id="image_grid_group_by_img_count" class="input-group-text">0</span> <span class="input-group-text">images in current group, show</span> <button class="btn btn-secondary dropdown-toggle" type="button" id="image_grid_show_image_policy" data-bs-toggle="dropdown" aria-expanded="false">Select option</button><ul class="dropdown-menu" id="image_grid_show_image_policy_dropdown" aria-labelledby="image_grid_show_image_policy"><li><a class="dropdown-item" href="#" data-value="all">all images (paginated)</a></li><li><a class="dropdown-item" href="#" data-value="first_mid_last">only first, middle and last image</a></li><li><a class="dropdown-item" href="#" data-value="even_indexed">even indexed images (i.e. 0,2,4,...)</a></li><li><a class="dropdown-item" href="#" data-value="odd_indexed">odd indexed images (i.e. 1,3,5,...)</a></li><li><a class="dropdown-item" href="#" data-value="gap5">images 1, 5, 10, 15,...</a></li><li><a class="dropdown-item" href="#" data-value="gap25">images 1, 25, 50, 75, ...</a></li><li><a class="dropdown-item" href="#" data-value="gap50">images 1, 50, 100, 150, ...</a></li></ul></div></div><div class="row align-items-center"><div class="input-group input-group-sm" id="image_grid_nav"></div></div></div><div id="image_grid_content"><div id="image_grid_content_img"></div><svg xmlns:xlink="http://www.w3.org/2000/svg" id="image_grid_content_rshape"></svg></div><div id="image_grid_info"></div></div><div id="settings_panel" class="display_area_content container-fluid d-none"><div class="row"><div class="col"><div class="card"><div class="card-body"><h5 class="card-title">Settings</h5><div class="row mb-3 p-3"><div class="card"><div class="card-body"><h5 class="card-title">Top panel</h5><div class="col-auto"><label for="settings_quickButtons" class="form-label">Quick buttons <small>(enable or disable quick buttons on the top bar)</small></label><div class="container my-4" id="settings_quickButtons"><div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-3"><div class="col"><input type="checkbox" class="btn-check" id="btn-openProject" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-openProject">Open project</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-saveProject" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-saveProject">Save project</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-settings" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-settings">Settings</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-imgGrid" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-imgGrid">Image grid view</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-sidePanel" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-sidePanel">Toggle side panel</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-autoAnn" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-autoAnn">Auto annotation</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-calcAreas" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-calcAreas">Export results</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-prev" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-prev">Previous</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-next" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-next">Next</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-undo" autocomplete="off"> <label class="btn btn-outline-secondary" for="btn-undo">Undo</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-redo" autocomplete="off"> <label class="btn btn-outline-secondary" for="btn-redo">Redo</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-zoomIn" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-zoomIn">Zoom in</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-zoomOut" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-zoomOut">Zoom out</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-selAllReg" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-selAllReg">Select all regions</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-copySelReg" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-copySelReg">Copy selected regions</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-pasteReg" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-pasteReg">Paste regions</label></div><div class="col"><input type="checkbox" class="btn-check" id="btn-delSelReg" autocomplete="off"> <label class="btn btn-outline-primary" for="btn-delSelReg">Delete selected regions</label></div></div></div></div></div></div></div><div class="row mb-3 p-3"><div class="card"><div class="card-body"><h5 class="card-title">Optimization</h5><div class="col-auto"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="settings_autoSave" onchange="settings.projectAutoSave()" disabled="disabled"> <label class="form-check-label" for="settings_autoSave">Project auto save</label></div></div><div class="col-auto"><label for="settings_preloadBuffer" class="form-label">Preload Buffer Size <small>(how many images to preload in memory; more images will consume more memory)</small></label> <input type="range" class="form-range" min="2" max="50" id="settings_preloadBuffer"></div><div class="col-auto"><label for="settings_serverReduceRatio" class="form-label">Vertex reduction ratio <small>(reduce the number of vertices in the region shape by this ratio on AI processed regions)</small></label> <input type="range" class="form-range" min="0" max="5" id="settings_serverReduceRatio"></div></div></div></div><div class="row mb-3 p-3"><div class="card"><div class="card-body"><h5 class="card-title">Annotation panel</h5><div class="col-auto"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="settings_highlightRegion" onchange="settings.enableHighlightRegion()"> <label class="form-check-label" for="settings_highlightRegion">Highlight the selected region</label></div></div><div class="col-auto"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="settings_scrollToRow" onchange="settings.enableScrollingCheckbox()"> <label class="form-check-label" for="settings_scrollToRow">Scroll to annotation list row, when select a region</label></div></div><div class="col-auto"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="settings_showScore" onchange="settings.showScore()"> <label class="form-check-label" for="settings_showScore">Show score (AI) in annotation list <small>(if the region is AI processed)</small></label></div></div><div class="col-auto"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="settings_showArea" onchange="settings.showPixelArea()"> <label class="form-check-label" for="settings_showArea">Show pixel area in annotation list for each region</label></div></div></div></div></div><div class="row mb-3 p-3"><div class="card"><div class="card-body"><h5 class="card-title">Scoring (AI)</h5><div class="col-auto"><label for="settings_scoring" class="col-form-label">Score threshold</label> <input type="text" class="form-control" id="settings_scoring" placeholder="E.g. 0.1"></div><div class="col-auto mt-2"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="settings_showScoreColor" onchange="settings.showScoreColor()"> <label class="form-check-label" for="settings_showScoreColor">Show score color in annotation list <small>(if the region is AI processed)</small></label></div></div><div class="row mb-3 py-3"><label for="settings_scoreHighColor" class="col-sm-2 col-form-label">Higher than threshold (row color)</label><div class="col-auto"><input type="color" class="form-control form-control-color" id="settings_scoreHighColor" value="#20c997" title="Choose your color"></div><label for="settings_scoreLowColor" class="col-sm-2 col-form-label">Lower than threshold (row color)</label><div class="col-auto"><input type="color" class="form-control form-control-color" id="settings_scoreLowColor" value="#c56771" title="Choose your color"></div></div></div></div></div><div class="row mb-3 p-3"><div class="card"><div class="card-body"><h5 class="card-title">Annotation editor</h5></div><ul class="nav nav-pills mb-3"><li class="nav-item"><a class="nav-link active" aria-current="page" href="#" onclick="show_region_attributes_update_panel()" id="button_show_region_attributes" title="Show region attribute editor">Region attributes</a></li><li class="nav-item"><a class="nav-link" href="#" onclick="show_file_attributes_update_panel()" id="button_show_file_attributes" title="Show file attribute editor">File attributes</a></li></ul><div class="row"><div class="col-auto" id="attributes_update_panel"><div class="row"><div class="col"><div class="input-group mb-3"><input type="text" class="form-control" placeholder="attribute name" id="user_input_attribute_id" aria-label="Attribute editor things"> <button class="btn btn-outline-secondary" type="button" onclick="add_new_attribute_from_user_input()" id="button_add_new_attribute"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-circle" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/></svg></button> <button class="btn btn-outline-secondary" type="button" onclick="delete_existing_attribute_with_confirm()" id="button_del_attribute"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dash-circle" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/><path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8"/></svg></button></div></div></div><div class="row"><div class="col mb-3"><select class="form-select" aria-label="List of existing attributes" title="List of existing attributes" onchange="update_current_attribute_id(this)" id="attributes_name_list"></select></div></div><div class="row"><div id="attribute_properties" class="col"></div></div><div class="row"><div id="attribute_options" class="col"></div></div></div><div class="col-auto"><label for="attributes_example" class="form-label">Preview</label><div class="card" id="attributes_example"></div></div></div></div></div></div></div></div></div></div><div class="display_area_content container-fluid" id="page_start_info"><div class="card"><div class="card-body"><h5 class="card-title">Welcome</h5><h6 class="card-subtitle mb-2 text-muted">InfarctSize-AI</h6><p class="card-text">To start using InfarctSize, select images and draw regions.<br>Use settings to fine-tune the application.<br>Save your project to continue later. You can also load a saved project to continue annotation.<br>Also remember to save your project before closing this application so that you can load it later to continue annotation.<br>For help, see the help page.</p><a href="#" class="card-link" id="start_selectImages">Select images</a> <a href="#" class="card-link" id="start_addUrlImages">Add images from folder</a> <a href="#" class="card-link" id="start_settings">Settings</a> <a href="#" class="card-link" id="start_loadProject">Load project</a> <a href="#" class="card-link" id="start_gettingStarted">Help</a></div></div></div><div class="display_area_content container-fluid" id="page_404"><div class="card"><div class="card-body"><h5 class="card-title">404 - File not found</h5><h6 class="card-subtitle mb-2 text-muted" id="page_404_filename"></h6><p class="card-text">We recommend that you to select the folder which contains the project images. <small>(Select folder)</small> Another approach is to select manually and add this file. <small>(Select images)</small></p><a href="#" class="card-link" id="404_selectImages">Select images</a> <a href="#" class="card-link" id="404_selectFolder">Select folder</a> <a href="#" class="card-link" id="404_gettingStarted">Help</a> <a href="#" class="card-link" id="404_settings">Settings</a></div></div></div><div class="display_area_content container overflow-y-scroll" style="height: 30em" id="page_getting_started"><div class="row"><div class="col-4"><nav aria-label="Getting started panel sections" id="navbar-gettingstarted" class="h-100 flex-column align-items-stretch pe-4 border-end"><nav aria-label="Load section" class="nav nav-pills flex-column"><a class="nav-link" href="#item-1">Load images</a><nav aria-label="Load section in section" class="nav nav-pills flex-column"><a class="nav-link ms-3 my-1" href="#item-1-1">Method 1</a> <a class="nav-link ms-3 my-1" href="#item-1-2">Method 2</a> <a class="nav-link ms-3 my-1" href="#item-1-3">Method 3</a></nav><a class="nav-link" href="#item-2">Draw regions</a><nav aria-label="Drawing section" class="nav nav-pills flex-column"><a class="nav-link ms-3 my-1" href="#item-2-1">Rectangle, Circle and Ellipse</a> <a class="nav-link ms-3 my-1" href="#item-2-2">Point</a> <a class="nav-link ms-3 my-1" href="#item-2-3">Polygon</a></nav><a class="nav-link" href="#item-3">Create Annotations</a> <a class="nav-link" href="#item-4">Export Annotations</a> <a class="nav-link" href="#item-5">Save Project</a></nav></nav></div><div class="col-8"><div data-bs-spy="scroll" data-bs-target="#navbar-gettingstarted" data-bs-smooth-scroll="true" tabindex="0"><div id="item-1"><h4>Load Images</h4><p>The first step is to load all the images that you wish to annotate. There are multiple ways to add images to a VIA project. Choose the method that suits your use case.</p></div><div id="item-1-1"><h5>Selecting local files using browser's file selector</h5><p>Click Project -> Add local files.</p><p>Select desired images and click Open</p></div><div id="item-1-2"><h5>Adding files from URL or absolute path</h5><p>Click Project -> Add files from URL</p><p>Enter URL and click OK</p></div><div id="item-1-3"><h5>Adding files from list of url or absolute path stored in text file</h5><p>Create a text file containing URL and absolute path (one per line)</p><p>Click Project -> Add url or path from text file</p></div><div id="item-2"><h4>Draw Regions</h4><p>Select a region shape (rectangle, circle, ellipse, polygon, point) from the shapes panel and draw regions as follows</p></div><div id="item-2-1"><h5>Rectangle, Circle and Ellipse</h5><p>Press left mouse button, drag mouse cursor and release mouse button.</p><p>To define a point inside an existing region, click inside the region to select it (if not already selected), now press left mouse button, drag and release to draw region inside existing region.</p><p>To select, click inside the region. If the click point contains multiple regions, then clicking multiple times at that location shuffles selection through those regions.</p></div><div id="item-2-2"><h5>Point</h5><p>Click to define points.</p><p>To draw a region inside existing region, click inside the region to select it (if not already selected), now click again to define the point.</p><p>To select, click on (or near) the existing point.</p></div><div id="item-2-3"><h5>Polygon and Polyline</h5><p>Click to define vertices.</p><p>Press [Enter] to finish drawing the region or press [Esc] to cancel.</p><p>If the first vertex needs to be defined inside an existing region, click inside the region to select it (if not already selected), now click again to define the vertex.</p><p>To select, click inside the region. If the click point contains multiple regions, then clicking multiple times at that location shuffles selection through those regions.</p></div><div id="item-3"><h4>Create Annotations</h4><p>For a more detailed description of this step, see Creating Annotations : VIA User Guide. Click the Settings icon to show attributes editor panel and add the desired file or region attributes (e.g. name). Use Ctrl + space to toggle the list of annotations.</p></div><div id="item-4"><h5>Export Annotations</h5><p>To export the annotations in json or csv format, click Annotation -> Export annotations in top menubar.</p></div><div id="item-5"><h5>Save Project</h5><p>To save the project, click Project -> Save in top menubar.</p></div></div></div></div></div><div class="display_area_content container-fluid" id="page_load_ongoing"><div style="text-align: center"><a href="https://www.robots.ox.ac.uk/~vgg/software/via/"><svg height="160" style="background-color: #212121" viewbox="0 0 400 160"><use xlink:href="#via_logo"></use></svg></a><div style="margin-top: 4rem">Loading ...</div></div></div><div class="offcanvas offcanvas-start" id="About" tabindex="-1"><div class="offcanvas-header"><h5 class="offcanvas-title" id="offcanvasAbout">About InfarctSize-AI</h5><button aria-label="Close" class="btn-close" data-bs-dismiss="offcanvas" type="button"></button></div><div class="offcanvas-body"><div></div><p><button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">License</button></p><div class="collapse" id="collapseExample"><div class="card card-body"></div></div></div></div><div class="offcanvas offcanvas-start" id="Report" tabindex="-1"><div class="offcanvas-header"><h5 class="offcanvas-title" id="offcanvasReport">Report issue</h5><button aria-label="Close" class="btn-close" data-bs-dismiss="offcanvas" type="button"></button></div><div class="offcanvas-body"><div><h4>Do you have a problem?</h4><p>If you encounter any problems or have feedback about our product or service, please let us know by sending an email to <a href="mailto:infarctsize@pharmahungary.com">infarctsize@pharmahungary.com</a>. Our team will review your report and take appropriate action to resolve the issue as soon as possible.</p><h4>Contact</h4><p><em>Pharmahungary Group - Infarctsize</em><br><strong>Email:</strong> <a href="mailto:infarctsize@pharmahungary.com">infarctsize@pharmahungary.com</a><br></p><h5>Versions</h5><ul><li>InfarctSize-AI 1.0.0 (beta)</li></ul></div></div></div></div></div></div></div><div class="modal fade" id="staticBackdropProgress" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true"><div class="modal-dialog modal-dialog-centered"><div class="modal-content"><div class="modal-header"><h1 class="modal-title fs-5" id="staticBackdropLabel">Please wait...</h1></div><div class="modal-body"><div class="progress"><div id="annotationProgress" class="progress-bar" role="progressbar" aria-label="Progressbar for annotations" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div></div></div></div><div class="modal fade" id="staticBackdropModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="modalTitle" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h1 class="modal-title fs-5" id="modalTitle"></h1><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body" id="modalBody"></div><div class="modal-footer" id="modalFooter"></div></div></div></div><div aria-atomic="true" aria-live="polite" class="toast" id="liveToast" role="alert" style="position: absolute; bottom: 10px; right: 10px"><div class="toast-header"><span class="badge text-bg-primary me-auto" id="toastAddress">Hello</span> <small id="toastInfo">now</small> <button aria-label="Close" class="btn-close" data-bs-dismiss="toast" type="button"></button></div><div class="toast-body" id="toastBody">Hello!</div></div><div id="loading" class="d-flex justify-content-center align-items-center" style="height: 100vh;
+        width: 100vw;
+        position: fixed;
+        top: 0;
+        left: 0;
+        background-color: rgba(0, 0, 0, 0.5);
+        z-index: 9999;"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha256-CDOy6cOibCWEdsRiZuaHf8dSGGJRYuBGC+mjoJimHGw=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script><script>$(window).on("load", function () {
+      $("#loading").fadeOut(500).addClass("d-none");
+    });</script><script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js" integrity="sha256-9PI4Hcqdhk6UDuIArOUvzRL9S/uJRmDpkA2o4gIigDs=" crossorigin="anonymous"></script><script src="assets/js/main.js"></script><div style="display:none" class="d-none" id="identifier">InfarctSize-AI</div></body></html>
\ No newline at end of file
diff --git a/train/example_run.py b/train/example_run.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d279327a75318660ab319e2020a8e5a89a384a2
--- /dev/null
+++ b/train/example_run.py
@@ -0,0 +1,81 @@
+import random
+
+import numpy as np
+import segmentation_models_pytorch as smp
+import torch.nn as nn
+from torch.utils.data import DataLoader
+
+from models.load_models import device
+from models.unet_impl import UNet2
+from train.train import train_for_epochs
+from train.utils import MyDataset, MyDataset_test
+from utils.constants import num_classes
+
+poland_imgs = np.float32(
+    (np.load('/path/to/train/data/Poland_images.npy')))
+poland_masks = np.float32(
+    (np.load('/path/to/train/data/Poland_masks.npy')))
+hungary_imgs = np.float32(
+    (np.load('/path/to/train/data/Hungary_images.npy')))
+hungary_masks = np.float32(
+    (np.load('/path/to/train/data/Hungary_masks.npy')))
+train_masks1 = np.float32((np.load(
+    '/path/to/train/data/SE_masks_sh_merged.npy')))
+train_imgs = np.float32(
+    (np.load('/path/to/train/data/SE_imgs.npy')))
+
+test_imgs = np.float32(
+    (np.load('/path/to/train/data/new_test_for_masks.npy')))
+test_masks1 = np.float32((np.load(
+    '/path/to/train/data/SE_test_masks_sh_merged.npy')))
+test_imgs = np.append(test_imgs, np.float32((np.load(
+    '/path/to/train/data/Vilskerst_selected_images.npy'))), axis=0)
+test_masks1 = np.append(test_masks1, np.float32((np.load(
+    '/path/to/train/data/Vilskerst_selected_masks_sh_merged.npy'))), axis=0)
+test_imgs = np.append(test_imgs, np.float32((np.load(
+    '/path/to/train/data/Hausenloy_images.npy'))), axis=0)
+test_masks1 = np.append(test_masks1, np.float32((np.load(
+    '/path/to/train/data/Hausenloy_selected_masks_sh_merged.npy'))), axis=0)
+test_imgs = np.append(test_imgs, np.float32((np.load(
+    '/path/to/train/data/Sauri_rat_images.npy'))), axis=0)
+test_masks1 = np.append(test_masks1, np.float32((np.load(
+    '/path/to/train/data/Sauri_rat_masks_sh_merged.npy'))), axis=0)
+
+train_indeces = []
+for t in range(int(float(poland_imgs.shape[0]+hungary_imgs.shape[0])/2.0)):
+    rand_num = random.randint(0, train_imgs.shape[0]-1)
+    while rand_num in train_indeces:
+        rand_num = random.randint(0, train_imgs.shape[0]-1)
+    train_indeces.append(rand_num)
+
+train_imgs = train_imgs[train_indeces, :, :, :]
+train_masks1 = train_masks1[train_indeces, :, :, :]
+train_imgs = np.append(train_imgs, poland_imgs, axis=0)
+train_imgs = np.append(train_imgs, hungary_imgs, axis=0)
+train_masks1 = np.append(train_masks1, poland_masks, axis=0)
+train_masks1 = np.append(train_masks1, hungary_masks, axis=0)
+
+train_set1 = MyDataset(train_imgs, train_masks1)
+test_set1 = MyDataset_test(test_imgs, test_masks1)
+
+train_loader_batch8 = DataLoader(dataset=train_set1,
+                                 batch_size=16, shuffle=True)
+test_loader1 = DataLoader(dataset=test_set1,
+                          batch_size=8, shuffle=True)
+
+Net1 = UNet2(3, num_classes).to(device)
+Net2 = smp.UnetPlusPlus(encoder_name='resnet34', encoder_weights='imagenet',
+                        classes=num_classes).to(device)
+Net3 = smp.DeepLabV3Plus(encoder_name='resnet34', encoder_weights='imagenet',
+                         classes=num_classes).to(device)
+
+name = "test"
+print("Traning Net1")
+Net1 = train_for_epochs(Net1, train_loader_batch8, test_loader1,
+                        nn.BCEWithLogitsLoss(), "ownUNet_" + name + ".pt", 50, 0.0)
+print("Traning Net2")
+Net2 = train_for_epochs(Net2, train_loader_batch8, test_loader1,
+                        nn.BCEWithLogitsLoss(), "unet++_" + name + ".pt", 50, 0.0)
+print("Traning Net3")
+Net3 = train_for_epochs(Net3, train_loader_batch8, test_loader1,
+                        nn.BCEWithLogitsLoss(), "deeplabv3+_" + name + ".pt", 50, 0.0)
diff --git a/train/train.py b/train/train.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e393aa25dac2eb6956d95fd57c8820b5f55e9de
--- /dev/null
+++ b/train/train.py
@@ -0,0 +1,75 @@
+import copy
+
+from torch import no_grad, save
+from torch.optim import Adam
+
+from train.utils import device
+
+learning_rate = 0.006
+
+def train(Net, DataLoader, optimizer,  criterion, AvgLoss):
+    Net.train()
+    IterNum = 0.0
+    for batch_idx, (data_x, data_y) in enumerate(DataLoader):
+        datax, datay = data_x.to(device), data_y.to(device)
+        print(" No. of batch: " + str(batch_idx))
+        datax = datax/127.5-1.0
+        optimizer.zero_grad()
+        response = Net(datax)
+        loss = criterion(response, datay.float())
+        loss.backward()
+        AvgLoss += float(loss.item())
+        IterNum += 1.0
+        optimizer.step()
+    AvgLoss = AvgLoss/IterNum
+    return AvgLoss
+
+
+def test(Net, test_loader,  criterion):
+    Net.eval()
+    AvgLoss = 0.0
+    IterNum = 0.0
+    with no_grad():
+        for _, (data_x, data_y) in enumerate(test_loader):
+            datax, datay = data_x.to(device), data_y.to(device)
+            datax = datax/127.5-1.0
+            response = Net(datax)
+
+            loss = criterion(response, datay.float())
+            AvgLoss += float(loss.item())
+            IterNum += 1.0
+    AvgLoss = AvgLoss/IterNum
+    return AvgLoss
+
+
+def train_for_epochs(Net, train_loader, test_loader, criterion, save_name, epoch_numb, best_loss):
+    Net.train()
+    optimizer = Adam(Net.parameters(), lr=learning_rate)
+    best_weights = copy.deepcopy(Net.state_dict())
+    save(Net.state_dict(), F"{save_name}")
+    current_best_epoch = 0
+    for epoch in range(epoch_numb):
+        AvgLoss = 0.0
+        print("Epoch: "+str(epoch))
+        AvgLoss = train(Net, train_loader, optimizer, criterion, AvgLoss)
+        TestLoss = test(Net, test_loader, criterion)
+        print(" Avg Loss: " + str(AvgLoss))
+        print(" Test Loss: " + str(TestLoss))
+        if epoch == 0 and best_loss == 0:
+            best_loss = TestLoss
+        else:
+            if TestLoss < best_loss:
+                best_loss = TestLoss
+                current_best_epoch = epoch
+                print("Better parameters found...")
+                best_weights = copy.deepcopy(Net.state_dict())
+                save(Net.state_dict(), F"{save_name}")
+                print("Parameters saved after epoch " + str(epoch) + ".")
+        if epoch-9 > current_best_epoch:
+            print("Loss hasn't improved in the last ten epochs, quitting...")
+            print("Parameters saved last after epoch " +
+                  str(current_best_epoch) + ".")
+            break
+
+    Net.load_state_dict(best_weights)
+    return Net
diff --git a/train/utils.py b/train/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..4514df415bd331e704c0e44191e568f64ce6dbaa
--- /dev/null
+++ b/train/utils.py
@@ -0,0 +1,52 @@
+import random
+
+import torchvision.transforms.functional as TF
+from torch.cuda import is_available as cuda_is_available
+from torch.utils.data import Dataset
+
+device = 'cuda' if cuda_is_available() else 'cpu'
+
+class MyDataset(Dataset):
+
+    def __init__(self, imgs, masks):
+        self.imgs = imgs
+        self.masks = masks
+
+    def __len__(self):
+        return self.imgs.shape[0]
+
+    def __getitem__(self, idx):
+        image = self.imgs[idx]
+        mask = self.masks[idx]
+
+        image = TF.to_tensor(image)
+        mask = TF.to_tensor(mask)
+
+        if random.random() > 0.5:
+            image = TF.hflip(image)
+            mask = TF.hflip(mask)
+
+        if random.random() > 0.5:
+            image = TF.vflip(image)
+            mask = TF.vflip(mask)
+
+        return (image, mask)
+
+
+class MyDataset_test(Dataset):
+
+    def __init__(self, imgs, masks):
+        self.imgs = imgs
+        self.masks = masks
+
+    def __len__(self):
+        return self.imgs.shape[0]
+
+    def __getitem__(self, idx):
+        image = self.imgs[idx]
+        mask = self.masks[idx]
+
+        image = TF.to_tensor(image)
+        mask = TF.to_tensor(mask)
+
+        return (image, mask)
diff --git a/train_test.py b/train_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..759546336f6daf25b083a04f1c53840cdea8fca2
--- /dev/null
+++ b/train_test.py
@@ -0,0 +1,72 @@
+import unittest
+
+import numpy as np
+import segmentation_models_pytorch as smp
+import torch
+import torch.nn as nn
+from torch.utils.data import DataLoader
+
+from models.unet_impl import UNet2
+from train.train import train_for_epochs
+from train.utils import MyDataset, MyDataset_test, device
+from utils.constants import num_classes
+
+
+class TestModelGeneration(unittest.TestCase):
+    def setUp(self):
+        # Generate random data for training and testing
+        self.train_imgs = np.random.rand(100, 128, 128, 3).astype(np.float32)
+        self.train_masks = np.random.randint(
+            0, 2, (100, 128, 128, num_classes)).astype(np.float32)  # Binary masks
+        self.test_imgs = np.random.rand(20, 128, 128, 3).astype(np.float32)
+        self.test_masks = np.random.randint(
+            0, 2, (20, 128, 128, num_classes)).astype(np.float32)  # Binary masks
+
+        # Create datasets and dataloaders
+        self.train_set = MyDataset(self.train_imgs, self.train_masks)
+        self.test_set = MyDataset_test(self.test_imgs, self.test_masks)
+        self.train_loader = DataLoader(
+            dataset=self.train_set, batch_size=10, shuffle=True)
+        self.test_loader = DataLoader(
+            dataset=self.test_set, batch_size=4, shuffle=False)
+
+        # Initialize models
+        self.net1 = UNet2(3, num_classes).to(device)
+        self.net2 = smp.UnetPlusPlus(
+            encoder_name='resnet34', encoder_weights=None, classes=num_classes).to(device)
+        self.net3 = smp.DeepLabV3Plus(
+            encoder_name='resnet34', encoder_weights=None, classes=num_classes).to(device)
+
+    def test_generate_and_save_models(self):
+        # Define loss function
+        loss_fn = nn.BCEWithLogitsLoss()
+
+        # Train and save Net1
+        print("Training Net1")
+        self.net1 = train_for_epochs(
+            self.net1, self.train_loader, self.test_loader, loss_fn, "models/random_net1.pt", 1, 0.0)
+        torch.save(self.net1.state_dict(), "models/random_net1.pt")
+        print("Net1 saved as 'models/random_net1.pt'")
+
+        # Train and save Net2
+        print("Training Net2")
+        self.net2 = train_for_epochs(
+            self.net2, self.train_loader, self.test_loader, loss_fn, "models/random_net2.pt", 1, 0.0)
+        torch.save(self.net2.state_dict(), "models/random_net2.pt")
+        print("Net2 saved as 'models/random_net2.pt'")
+
+        # Train and save Net3
+        print("Training Net3")
+        self.net3 = train_for_epochs(
+            self.net3, self.train_loader, self.test_loader, loss_fn, "models/random_net3.pt", 1, 0.0)
+        torch.save(self.net3.state_dict(), "models/random_net3.pt")
+        print("Net3 saved as 'models/random_net3.pt'")
+
+        # Assertions to check if files are created
+        self.assertTrue(torch.load("models/random_net1.pt"))
+        self.assertTrue(torch.load("models/random_net2.pt"))
+        self.assertTrue(torch.load("models/random_net3.pt"))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/utils/constants.py b/utils/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..80c7525aa07cc03950da286396e0844df4ba782c
--- /dev/null
+++ b/utils/constants.py
@@ -0,0 +1,34 @@
+
+import json
+import os
+
+# Constants
+# Read config file
+try:
+    config = open("config.json", "r")
+    config = json.load(config)
+    print("Config file loaded")
+except:
+    print("Config file not found")
+    config = {}
+
+# UPLOAD_FOLDER stores the images while they are being processed
+os.makedirs('./upload', exist_ok=True)
+UPLOAD_FOLDER = './upload'
+# ALLOWED_EXTENSIONS is a set of allowed file extensions for the uploaded images
+ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
+# Path to your model
+if config != {}:
+    model_unet_impl = config['model_unet_impl']
+    model_smp_unet = config['model_smp_unet']
+    model_smp_deeplab = config['model_smp_deeplab']
+    port = config['port']
+    debug = config['debug']
+else:
+    model_unet_impl = "./models/random_net1.pt"
+    model_smp_unet = './models/random_net2.pt'
+    model_smp_deeplab = './models/random_net3.pt'
+    port = 5000
+    debug = False
+# Set the supported classes
+num_classes = 3
diff --git a/utils/helpers.py b/utils/helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d310a4d2f5170eef788be9853709a6972d6149c
--- /dev/null
+++ b/utils/helpers.py
@@ -0,0 +1,48 @@
+
+import cv2
+import os
+from utils.constants import UPLOAD_FOLDER
+import numpy as np
+
+# Method to calculate the score of the segmentation
+
+
+def calculate_score(layer1, layer2, layer3, comb):
+    if np.sum(comb >= 0.5) != 0:
+        belongs_to_union = ((layer1 >= 0.5) | (
+            layer2 >= 0.5)) | (layer3 >= 0.5)
+        element_number = np.sum(belongs_to_union)
+        probabilities = np.multiply(
+            np.mean([layer1, layer2, layer3], axis=0), belongs_to_union)
+        if element_number > 0:
+            return np.sum(probabilities) / element_number
+    return 0
+
+# Method to reshape and threshold the mask
+
+
+def reshape_and_threshold(mask):
+    mask = np.transpose(mask, (3, 2, 1, 0))
+    mask = np.where(mask < 0.0, 0.0, 1.0)
+    mask = np.transpose(mask, (3, 0, 1, 2))
+    return mask
+
+# Method to convert RGBA to RGB
+
+
+def cv2_RGBA2RGB(img, bg=(255, 255, 255)):
+    b, g, r, a = cv2.split(img)
+    alpha = a / 255
+    r = (bg[0] * (1 - alpha) + r * alpha).astype(np.uint8)
+    g = (bg[1] * (1 - alpha) + g * alpha).astype(np.uint8)
+    b = (bg[2] * (1 - alpha) + b * alpha).astype(np.uint8)
+    new_img = cv2.merge((b, g, r))
+    return new_img
+
+
+def delete_buff_image(file):
+    try:
+        os.remove(os.path.join(UPLOAD_FOLDER, file))
+        print(f"{file} deleted")
+    except Exception as e:
+        print(f"Error while deleting file: {file}. Error message: {e}")