├── .gitignore ├── Image labeller notebook.ipynb ├── LICENSE.txt ├── README.md ├── convert_json_to_label.py ├── examples ├── simple │ ├── images │ │ ├── uea_grounds_1.jpg │ │ ├── uea_grounds_2.jpg │ │ └── uea_grounds_2__labels.json │ ├── label_names_example.yml │ └── screenshot.png └── ssd │ ├── README.md │ ├── demo.py │ ├── original_detection_dataset.py │ ├── randomly_split_directory.py │ ├── train.py │ └── train_utils.py ├── ext_static └── jquery │ ├── images │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png │ ├── ui-bg_diagonals-thick_20_666666_40x40.png │ ├── ui-bg_flat_10_000000_40x100.png │ ├── ui-bg_glass_100_f6f6f6_1x400.png │ ├── ui-bg_glass_100_fdf5ce_1x400.png │ ├── ui-bg_glass_65_ffffff_1x400.png │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png │ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png │ ├── ui-icons_222222_256x240.png │ ├── ui-icons_228ef1_256x240.png │ ├── ui-icons_ef8c08_256x240.png │ ├── ui-icons_ffd27a_256x240.png │ └── ui-icons_ffffff_256x240.png │ ├── jquery-2.1.1.min.js │ ├── jquery-ui.min.css │ ├── jquery-ui.min.js │ └── jquery-ui.theme.min.css ├── flask_app.py ├── image_labelling_tool ├── __init__.py ├── admin.py ├── labelling_tool.py ├── labelling_tool_jupyter.py ├── labelling_tool_views.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_labels_edit_time_elapsed.py │ └── __init__.py ├── models.py ├── static │ ├── d3.d.ts │ ├── d3.min.js │ ├── jquery.d.ts │ ├── js.cookie.js │ ├── json2.js │ ├── labelling_tool │ │ ├── abstract_label.js │ │ ├── abstract_label.ts │ │ ├── abstract_tool.js │ │ ├── abstract_tool.ts │ │ ├── box_label.js │ │ ├── box_label.ts │ │ ├── composite_label.js │ │ ├── composite_label.ts │ │ ├── group_label.js │ │ ├── group_label.ts │ │ ├── label_class.js │ │ ├── label_class.ts │ │ ├── main_tool.js │ │ ├── main_tool.ts │ │ ├── math_primitives.js │ │ ├── math_primitives.ts │ │ ├── object_id_table.js │ │ ├── object_id_table.ts │ │ ├── point_label.js │ │ ├── point_label.ts │ │ ├── polygonal_label.js │ │ ├── polygonal_label.ts │ │ ├── root_label_view.js │ │ ├── root_label_view.ts │ │ ├── select_tools.js │ │ └── select_tools.ts │ ├── polyk.d.ts │ └── polyk.js ├── templates │ └── inline │ │ ├── instructions.html │ │ └── labelling_tool.html ├── templatetags │ ├── __init__.py │ └── labelling_tool_tags.py └── tests.py ├── requirements.txt ├── setup.py ├── templates └── labeller_page.jinja2 └── tests ├── example_labeller ├── __init__.py ├── admin.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── populate.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── templates │ └── index.html ├── tests.py ├── urls.py └── views.py ├── example_labeller_app ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py └── manage.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.pyc 3 | 4 | examples/ssd/data/ 5 | examples/ssd/models/ 6 | examples/ssd/result/ 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 University of East Anglia, Norwich, UK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Labelling Tool 2 | This repository is a fork of a project released [here](https://bitbucket.org/ueacomputervision/image-labelling-tool). 3 | The original originals are Geoffrey French, Dr. M. Fisher and Dr. M. Mackiewicz at University of East Anglia. 4 | 5 | The code is modified to work better with SLIC initialization. 6 | Also, this repository provides a complete example to train an object detection model with annotated data. 7 | 8 | ## Installation 9 | ``` 10 | $ pip install -e . 11 | ``` 12 | 13 | ## Usage 14 | ```bash 15 | # This starts up interactive annotator. 16 | # If slic option is supplied, it starts with precomputed segmentations based on SLIC. 17 | $ python flask_app.py [--slic] [--image_dir PATH_TO_DIR] [--label_names label_names.yml] 18 | ``` 19 | SLIC option should not be set for those who only want to annotate by bounding boxes. 20 | The option can be helpful for segmenting an image. Please be aware the superpixel segmentation is not always perfect. 21 | 22 | The annotations will be stored in the same directory where the corresponding images exist. 23 | The annotaions are stored by `.json` files containing all the parameters. 24 | 25 | I provided a convenient script to translate raw `.json` into a label image. 26 | This can be helpful for annotating semantig segmentation datasets. 27 | ```bash 28 | # Convert annotated json to labels 29 | $ python convert_json_to_label.py [--image_dir PATH_TO_DIR] [--label_names label_names.yml] 30 | ``` 31 | 32 | ## Example 33 | 34 | #### Annotation 35 | 36 | ```bash 37 | $ python flask_app.py --image_dir examples/simple/images --label_names examples/simple/label_names_example.yml --file_ext jpg 38 | $ python convert_json_to_label.py --image_dir examples/simple/images --label_names examples/simple/label_names_example.yml 39 | ``` 40 | 41 | If you want to initialize an image with superpixel initialization, use `--slic` option. 42 | Please use `--file_ext` to specify the file extension of images. 43 | 44 | ![](https://github.com/yuyu2172/image-labelling-tool/blob/master/examples/simple/screenshot.png) 45 | 46 | 47 | #### Full example to train an object detection model with annotated dataset 48 | In the examples, there is a training script for SSD that uses dataset that is created by this annotation tool. 49 | SSD is one of the state of the art deep learning based object detection model. 50 | 51 | The implementation uses ChainerCV, a computer vision library. 52 | The library is created to lower the barrier of entry to use deep learning based computer vision models. 53 | 54 | The more details can be found in [`examples/ssd`](https://github.com/yuyu2172/image-labelling-tool/tree/master/examples/ssd). 55 | 56 | 57 | # (Original README starts here) UEA Computer Vision - Image Labelling Tool 58 | 59 | #### A light-weight image labelling tool for Python designed for creating segmentation datasets. 60 | 61 | Operates as a browser-based application, either embedded as a widget within [IPython Notebook](http://ipython.org) 62 | or embedded within a web page as part of a web application. 63 | 64 | Currently supports simple polygonal labels. 65 | 66 | 67 | ### IPython Notebook widget example 68 | 69 | The supplied IPython notebook example creates a labelling tool widget and displays it within the notebook. 70 | API usage is demonstrated further down. 71 | 72 | ### Flask web app example 73 | 74 | An example Flask-based web app is provided that displays the labelling tool within a web page. To start it, 75 | run `python flask_app.py` and open `127.0.0.1:5000` within a browser. 76 | 77 | 78 | ### Libraries, Credits and License 79 | 80 | Incorporates the public domain [json2.js](https://github.com/douglascrockford/JSON-js) library. 81 | Uses [d3.js](http://d3js.org/), [jQuery](https://jquery.com/), [jQuery UI](https://jqueryui.com/) 82 | and [PolyK](http://polyk.ivank.net/). 83 | 84 | This software was developed by Geoffrey French in collaboration with Dr. M. Fisher and 85 | Dr. M. Mackiewicz at the [School of Computing Sciences](http://www.uea.ac.uk/computing) 86 | at the [University of East Anglia](http://www.uea.ac.uk) as part of a project funded by 87 | [Marine Scotland](http://www.gov.scot/Topics/marine). 88 | 89 | It is licensed under the MIT license. 90 | -------------------------------------------------------------------------------- /convert_json_to_label.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | from matplotlib import pyplot as plt 4 | import numpy as np 5 | import os 6 | import yaml 7 | 8 | from flask import Flask, render_template, request, make_response, send_from_directory 9 | 10 | from image_labelling_tool import labelling_tool 11 | 12 | 13 | if __name__ == '__main__': 14 | parser = argparse.ArgumentParser(description='Image labelling tool - Convert Json to Image') 15 | parser.add_argument('--image_dir') 16 | parser.add_argument('--label_names') 17 | parser.add_argument('--file_ext', type=str, default='png') 18 | args = parser.parse_args() 19 | file_ext = '.{}'.format(args.file_ext) 20 | img_dir = args.image_dir 21 | with open(args.label_names, 'r') as f: 22 | label_names = yaml.load(f) 23 | 24 | labelled_images = labelling_tool.PersistentLabelledImage.for_directory( 25 | img_dir, image_filename_pattern='*{}'.format(file_ext)) 26 | 27 | for labelled_image in labelled_images: 28 | if labelled_image.labels is not None: 29 | labels_2d = labelled_image.render_labels( 30 | label_classes=label_names, 31 | pixels_as_vectors=False) 32 | # Unlabeled should be -1 33 | labels_2d -= 1 34 | name = os.path.splitext(labelled_image.image_path)[0] 35 | np.save(os.path.join(name + '.npy'), labels_2d) 36 | 37 | plt.imshow(labels_2d) 38 | plt.savefig(os.path.join(name + '_label.png')) 39 | print('{} converted'.format(labelled_image.image_path)) 40 | else: 41 | print('{} there is no labels'.format(labelled_image.image_path)) 42 | -------------------------------------------------------------------------------- /examples/simple/images/uea_grounds_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/examples/simple/images/uea_grounds_1.jpg -------------------------------------------------------------------------------- /examples/simple/images/uea_grounds_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/examples/simple/images/uea_grounds_2.jpg -------------------------------------------------------------------------------- /examples/simple/label_names_example.yml: -------------------------------------------------------------------------------- 1 | - tree 2 | - building 3 | - lake 4 | -------------------------------------------------------------------------------- /examples/simple/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/examples/simple/screenshot.png -------------------------------------------------------------------------------- /examples/ssd/README.md: -------------------------------------------------------------------------------- 1 | # SSD Example 2 | 3 | 4 | This example provides following functionalities. 5 | 6 | + (Annotating dataset for detection task. This is the functionality supported by image-labelling-tool.) 7 | + A data loader object that wraps annotated data, and conveniently access data using Python. 8 | + A SSD training script that works with the data loader. 9 | 10 | 11 | ### Dependency 12 | 13 | + `chainer` 14 | + `chainercv>=0.6` 15 | 16 | 17 | ### Process 18 | 1. Annotate dataset (please read the [README](https://github.com/yuyu2172/image-labelling-tool) in the top of this repo) 19 | 20 | Please store all image data below `DATA_DIR`. The label names should be writtein in a YAML file locating at `LABEL_NAME`. 21 | The file extension of the images can be selected by `EXT` (e.g. jpg). 22 | 23 | After finish annotating data, 24 | the annotation files are stored umder `DATA_DIR` together with the images. 25 | 26 | ```bash 27 | $ python flask_app.py --image_dir DATA_DIR --label_names LABEL_FILE --file_ext EXT 28 | ``` 29 | 30 | 2. Train SSD 31 | 32 | This script assumes data stored in the annotation style of this annotation tool. 33 | Thanks to that, you do not need to write any data loader code by yourself. 34 | 35 | ```bash 36 | $ python train.py --train DATA_DIR --label_names LABEL_NAMES --gpu GPU 37 | ``` 38 | 39 | More on `train.py`. 40 | ```bash 41 | $ python train.py -h 42 | ``` 43 | 44 | 45 | ### Dividing a dataset into train/val 46 | When calling `train.py` without supplying `--val`, the dataset is split into two with ratio 8:2. 47 | The larger one is used as the training dataset and the smaller one is used as the validation dataset. 48 | 49 | There can be a situation when the train/val split should be fixed. 50 | You can use fixed split during training by supplying both `--train` and `--val` when calling `train.py`. 51 | 52 | In order to split data in fixed manner, there is a convenient script `randomly_split_directory.py`. 53 | This script divides all data in `DATA_DIR` into `TRAIN_DIR` and `VAL_DIR`. 54 | 55 | ```bash 56 | $ python randomly_split_directory.py TRAIN_DIR VAL_DIR DATA_DIR 57 | ``` 58 | 59 | 60 | ### Example 61 | 62 | In order to try these scripts without annotating images, sample annotations are provided. 63 | Each annotation contains a bounding box around an orange or an apple. 64 | It can be downloaded from here. 65 | https://drive.google.com/open?id=0BzBTxmVQJTrGek9ISlNmU2RkTk0 66 | 67 | For annotating your own dataset, please read https://github.com/yuyu2172/image-labelling-tool 68 | 69 | ##### Unzip the compressed file. 70 | ```bash 71 | $ git clone https://github.com/yuyu2172/image-labelling-tool 72 | $ cd image-labelling-tool/examples/ssd 73 | # Download the file in the current directory. 74 | $ unzip apple_orange_annotations.zip 75 | ``` 76 | 77 | ##### Run train code 78 | ```bash 79 | $ python train.py --train apple_orange_annotations --label_names apple_orange_annotations/apple_orange_label_names.yml --val_iteration 100 --gpu GPU 80 | ``` 81 | 82 | For environments with a less powerful GPU, please reduce computational demand by setting `--batchsize` to a lower value than the default one. 83 | For those who are worried that nothing appears, please pass `--batchsize 1` and `--log_iteration 1` to the command. 84 | This will make the script to report log for every image. 85 | 86 | Python's `multiprocessing` works badly with OpenCV in some environments ([dicussion](https://github.com/chainer/chainercv/issues/386#issuecomment-321485827)). 87 | In that case, please set `--loaderjob 0`. 88 | This will make the script to use `SerialIterator` instead of `MultiprocessingIterator`. 89 | 90 | ##### Alternatively, fix data used for validation 91 | [description](https://github.com/yuyu2172/image-labelling-tool/tree/master/examples/ssd#dividing-dataset-into-trainval) 92 | ```bash 93 | $ python randomly_split_directory.py train val apple_orange_annotations 94 | $ python train.py --train train --val val --label_names apple_orange_annotations/apple_orange_label_names.yml --val_iteration 100 --gpu GPU 95 | ``` 96 | 97 | ##### Demo code to visualize output 98 | 99 | ```bash 100 | $ python demo.py --pretrained_model result/model_iter_400 --label_names apple_orange_annotations/apple_orange_label_names.yml apple_orange_annotations/Orange/frame0017.jpg 101 | ``` 102 | 103 | Here is a link to a weight trained for 400 iterations. 104 | [model_iter_400](https://drive.google.com/file/d/0B8QTag5ixHD3NWVGN3FVZHVHdFU/view?usp=sharing) 105 | -------------------------------------------------------------------------------- /examples/ssd/demo.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import matplotlib.pyplot as plot 3 | import yaml 4 | 5 | import chainer 6 | 7 | from chainercv.datasets import voc_detection_label_names 8 | from chainercv.links import SSD300 9 | from chainercv import utils 10 | from chainercv.visualizations import vis_bbox 11 | 12 | from original_detection_dataset import OriginalDetectionDataset 13 | 14 | 15 | def main(): 16 | chainer.config.train = False 17 | 18 | parser = argparse.ArgumentParser() 19 | parser.add_argument('--gpu', type=int, default=-1) 20 | parser.add_argument('--pretrained_model') 21 | parser.add_argument('image') 22 | parser.add_argument( 23 | '--label_names', help='The path to the yaml file with label names') 24 | args = parser.parse_args() 25 | 26 | with open(args.label_names, 'r') as f: 27 | label_names = tuple(yaml.load(f)) 28 | model = SSD300( 29 | n_fg_class=len(label_names), 30 | pretrained_model=args.pretrained_model) 31 | 32 | if args.gpu >= 0: 33 | chainer.cuda.get_device_from_id(args.gpu).use() 34 | model.to_gpu() 35 | 36 | img = utils.read_image(args.image, color=True) 37 | bboxes, labels, scores = model.predict([img]) 38 | bbox, label, score = bboxes[0], labels[0], scores[0] 39 | 40 | vis_bbox( 41 | img, bbox, label, score, label_names=label_names) 42 | plot.show() 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /examples/ssd/original_detection_dataset.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | import os 4 | 5 | import chainer 6 | from chainercv.utils import read_image 7 | 8 | 9 | class OriginalDetectionDataset(chainer.dataset.DatasetMixin): 10 | 11 | def __init__(self, data_dir, label_names): 12 | self.data_dir = data_dir 13 | self.label_names = label_names 14 | 15 | self.img_filenames = [] 16 | self.anno_filenames = [] 17 | # for name in sorted(os.listdir(data_dir)): 18 | for root, dirs, files in os.walk(data_dir): 19 | for name in sorted(files): 20 | # If the file is not an image, ignore the file. 21 | if os.path.splitext(name)[1] != '.jpg': 22 | continue 23 | img_filename = os.path.join(root, name) 24 | anno_filename = os.path.splitext(img_filename)[0] + '__labels.json' 25 | if not os.path.exists(anno_filename): 26 | continue 27 | self.img_filenames.append(img_filename) 28 | self.anno_filenames.append(anno_filename) 29 | 30 | def __len__(self): 31 | return len(self.img_filenames) 32 | 33 | def get_example(self, i): 34 | img_filename = self.img_filenames[i] 35 | anno_filename = self.anno_filenames[i] 36 | img = read_image(img_filename) 37 | 38 | with open(anno_filename, 'r') as f: 39 | anno = json.load(f) 40 | anno = anno['labels'] 41 | 42 | bbox = [] 43 | label = [] 44 | for anno_i in anno: 45 | h = anno_i['size']['y'] 46 | w = anno_i['size']['x'] 47 | center_y = anno_i['centre']['y'] 48 | center_x = anno_i['centre']['x'] 49 | 50 | if anno_i['label_class'] not in self.label_names: 51 | raise ValueError( 52 | 'The class does not exist {}'.format(anno_i['label_class'])) 53 | l = self.label_names.index(anno_i['label_class']) 54 | bbox.append( 55 | [center_y - h / 2, center_x - w / 2, 56 | center_y + h / 2, center_x + w / 2]) 57 | label.append(l) 58 | 59 | bbox = np.array(bbox, dtype=np.float32) 60 | label = np.array(label, dtype=np.int32) 61 | return img, bbox, label 62 | -------------------------------------------------------------------------------- /examples/ssd/randomly_split_directory.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import warnings 4 | import shutil 5 | 6 | import numpy as np 7 | 8 | 9 | def main(): 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument('train_dir', default='train') 12 | parser.add_argument('val_dir', default='val') 13 | parser.add_argument('data_dir') 14 | parser.add_argument('--seed', type=int, default=0) 15 | args = parser.parse_args() 16 | 17 | np.random.seed(args.seed) 18 | 19 | filename_tuples = [] 20 | for root, dirs, names in os.walk(args.data_dir): 21 | for name in names: 22 | if os.path.splitext(name)[1] == '.jpg': 23 | name_head = os.path.splitext(name)[0] 24 | tup = (os.path.join(root, name), 25 | os.path.join(root, name_head + '__labels.json'), 26 | '/'.join(os.path.split(root)[1:])) 27 | filename_tuples.append(tup) 28 | 29 | 30 | if os.path.exists(args.train_dir): 31 | warnings.warn('directory train already exists') 32 | else: 33 | os.makedirs(args.train_dir) 34 | 35 | if os.path.exists(args.val_dir): 36 | warnings.warn('directory val already exists') 37 | else: 38 | os.makedirs(args.val_dir) 39 | 40 | order = np.arange(len(filename_tuples)) 41 | np.random.shuffle(order) 42 | first_size = int(len(order) * 0.8) 43 | 44 | for i in order[:first_size]: 45 | tup = filename_tuples[i] 46 | out_dir = os.path.join(args.train_dir, tup[2]) 47 | if not os.path.exists(out_dir): 48 | os.makedirs(out_dir) 49 | 50 | name = os.path.split(tup[0])[1] 51 | shutil.copyfile(tup[0], os.path.join(out_dir, name)) 52 | name = os.path.split(tup[1])[1] 53 | shutil.copyfile(tup[1], os.path.join(out_dir, name)) 54 | 55 | for i in order[first_size:]: 56 | tup = filename_tuples[i] 57 | tup = filename_tuples[i] 58 | out_dir = os.path.join(args.val_dir, tup[2]) 59 | if not os.path.exists(out_dir): 60 | os.makedirs(out_dir) 61 | 62 | name = os.path.split(tup[0])[1] 63 | shutil.copyfile(tup[0], os.path.join(out_dir, name)) 64 | name = os.path.split(tup[1])[1] 65 | shutil.copyfile(tup[1], os.path.join(out_dir, name)) 66 | 67 | 68 | if __name__ == '__main__': 69 | main() 70 | -------------------------------------------------------------------------------- /examples/ssd/train.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import yaml 3 | 4 | import chainer 5 | 6 | from original_detection_dataset import OriginalDetectionDataset 7 | from train_utils import train 8 | 9 | 10 | if __name__ == '__main__': 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument( 13 | '--train', help='The root directory of the training dataset') 14 | parser.add_argument( 15 | '--val', 16 | help='The root directory of the validation dataset. If this is not ' 17 | 'supplied, the data for train dataset is split into two with ratio 8:2.') 18 | parser.add_argument( 19 | '--label_names', help='The path to the yaml file with label names') 20 | parser.add_argument( 21 | '--iteration', type=int, default=120000, 22 | help='The number of iterations to run until finishing the train loop') 23 | parser.add_argument( 24 | '--lr', type=float, default=1e-4, help='Initial learning rate') 25 | parser.add_argument( 26 | '--step_size', type=int, default=-1, 27 | help='The number of iterations to run before ' 28 | 'dropping the learning rate by 0.1') 29 | parser.add_argument( 30 | '--batchsize', type=int, default=8, 31 | help='The size of batch') 32 | parser.add_argument( 33 | '--gpu', type=int, default=-1, help='GPU ID') 34 | parser.add_argument('--out', default='result', 35 | help='The directory in which logs are saved') 36 | parser.add_argument( 37 | '--val_iteration', type=int, default=10000, 38 | help='The number of iterations between every validation.') 39 | parser.add_argument( 40 | '--log_iteration', type=int, default=10, 41 | help='The number of iterations between every logging.') 42 | parser.add_argument( 43 | '--loaderjob', type=int, default=4, 44 | help='The number of processes to launch for MultiprocessIterator.') 45 | parser.add_argument( 46 | '--resume', 47 | help='The path to the trainer snapshot to resume from. ' 48 | 'If unspecified, no snapshot will be resumed') 49 | args = parser.parse_args() 50 | 51 | with open(args.label_names, 'r') as f: 52 | label_names = tuple(yaml.load(f)) 53 | 54 | if args.val is not None: 55 | train_data = OriginalDetectionDataset(args.train, label_names) 56 | val_data = OriginalDetectionDataset(args.val, label_names) 57 | else: 58 | # If --val is not supplied, the train data is split into two 59 | # with ratio 8:2. 60 | dataset = OriginalDetectionDataset(args.train, label_names) 61 | train_data, val_data = chainer.datasets.split_dataset_random( 62 | dataset, int(len(dataset) * 0.8)) 63 | 64 | step_points = [args.step_size] 65 | train( 66 | train_data, 67 | val_data, 68 | label_names, 69 | args.iteration, 70 | args.lr, 71 | step_points, 72 | args.batchsize, 73 | args.gpu, 74 | args.out, 75 | args.val_iteration, 76 | args.log_iteration, 77 | args.loaderjob, 78 | args.resume) 79 | -------------------------------------------------------------------------------- /examples/ssd/train_utils.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import numpy as np 3 | 4 | import chainer 5 | from chainer.datasets import TransformDataset 6 | from chainer.optimizer import WeightDecay 7 | from chainer import serializers 8 | from chainer import training 9 | from chainer.training import extensions 10 | from chainer.training import triggers 11 | 12 | from chainercv.extensions import DetectionVOCEvaluator 13 | from chainercv.links.model.ssd import GradientScaling 14 | from chainercv.links.model.ssd import multibox_loss 15 | from chainercv.links import SSD300 16 | from chainercv import transforms 17 | 18 | from chainercv.links.model.ssd import random_crop_with_bbox_constraints 19 | from chainercv.links.model.ssd import random_distort 20 | from chainercv.links.model.ssd import resize_with_random_interpolation 21 | 22 | 23 | class MultiboxTrainChain(chainer.Chain): 24 | 25 | def __init__(self, model, alpha=1, k=3): 26 | super(MultiboxTrainChain, self).__init__() 27 | with self.init_scope(): 28 | self.model = model 29 | self.alpha = alpha 30 | self.k = k 31 | 32 | def __call__(self, imgs, gt_mb_locs, gt_mb_labels): 33 | mb_locs, mb_confs = self.model(imgs) 34 | loc_loss, conf_loss = multibox_loss( 35 | mb_locs, mb_confs, gt_mb_locs, gt_mb_labels, self.k) 36 | loss = loc_loss * self.alpha + conf_loss 37 | 38 | chainer.reporter.report( 39 | {'loss': loss, 'loss/loc': loc_loss, 'loss/conf': conf_loss}, 40 | self) 41 | 42 | return loss 43 | 44 | 45 | class Transform(object): 46 | 47 | def __init__(self, coder, size, mean): 48 | # to send cpu, make a copy 49 | self.coder = copy.copy(coder) 50 | self.coder.to_cpu() 51 | 52 | self.size = size 53 | self.mean = mean 54 | 55 | def __call__(self, in_data): 56 | # There are five data augmentation steps 57 | # 1. Color augmentation 58 | # 2. Random expansion 59 | # 3. Random cropping 60 | # 4. Resizing with random interpolation 61 | # 5. Random horizontal flipping 62 | 63 | img, bbox, label = in_data 64 | 65 | # 1. Color augmentation 66 | img = random_distort(img) 67 | 68 | # 2. Random expansion 69 | if np.random.randint(2): 70 | img, param = transforms.random_expand( 71 | img, fill=self.mean, return_param=True) 72 | bbox = transforms.translate_bbox( 73 | bbox, y_offset=param['y_offset'], x_offset=param['x_offset']) 74 | 75 | # 3. Random cropping 76 | img, param = random_crop_with_bbox_constraints( 77 | img, bbox, return_param=True) 78 | bbox, param = transforms.crop_bbox( 79 | bbox, y_slice=param['y_slice'], x_slice=param['x_slice'], 80 | allow_outside_center=False, return_param=True) 81 | label = label[param['index']] 82 | 83 | # 4. Resizing with random interpolatation 84 | _, H, W = img.shape 85 | img = resize_with_random_interpolation(img, (self.size, self.size)) 86 | bbox = transforms.resize_bbox(bbox, (H, W), (self.size, self.size)) 87 | 88 | # 5. Random horizontal flipping 89 | img, params = transforms.random_flip( 90 | img, x_random=True, return_param=True) 91 | bbox = transforms.flip_bbox( 92 | bbox, (self.size, self.size), x_flip=params['x_flip']) 93 | 94 | # Preparation for SSD network 95 | img -= self.mean 96 | mb_loc, mb_label = self.coder.encode(bbox, label) 97 | 98 | return img, mb_loc, mb_label 99 | 100 | 101 | def train(train_data, val_data, label_names, 102 | iteration, lr, step_points, 103 | batchsize, gpu, out, val_iteration, 104 | log_iteration, loaderjob, 105 | resume): 106 | """Train SSD 107 | 108 | """ 109 | pretrained_model = SSD300( 110 | pretrained_model='voc0712') 111 | model = SSD300(n_fg_class=len(label_names)) 112 | model.extractor.copyparams(pretrained_model.extractor) 113 | model.multibox.loc.copyparams(pretrained_model.multibox.loc) 114 | model.use_preset('evaluate') 115 | train_chain = MultiboxTrainChain(model) 116 | if gpu >= 0: 117 | chainer.cuda.get_device(gpu).use() 118 | model.to_gpu() 119 | 120 | train_data = TransformDataset( 121 | train_data, 122 | Transform(model.coder, model.insize, model.mean)) 123 | if loaderjob <= 0: 124 | train_iter = chainer.iterators.SerialIterator(train_data, batchsize) 125 | else: 126 | train_iter = chainer.iterators.MultiprocessIterator( 127 | train_data, batchsize, n_processes=min((loaderjob, batchsize))) 128 | 129 | val_iter = chainer.iterators.SerialIterator( 130 | val_data, batchsize, repeat=False, shuffle=False) 131 | 132 | # initial lr is set by ExponentialShift 133 | optimizer = chainer.optimizers.MomentumSGD() 134 | optimizer.setup(train_chain) 135 | for param in train_chain.params(): 136 | if param.name == 'b': 137 | param.update_rule.add_hook(GradientScaling(2)) 138 | else: 139 | param.update_rule.add_hook(WeightDecay(0.0005)) 140 | 141 | updater = training.StandardUpdater(train_iter, optimizer, device=gpu) 142 | trainer = training.Trainer(updater, (iteration, 'iteration'), out) 143 | trainer.extend( 144 | extensions.ExponentialShift('lr', 0.1, init=lr), 145 | trigger=triggers.ManualScheduleTrigger(step_points, 'iteration')) 146 | 147 | val_interval = (val_iteration, 'iteration') 148 | trainer.extend( 149 | DetectionVOCEvaluator( 150 | val_iter, model, use_07_metric=True, 151 | label_names=label_names), 152 | trigger=val_interval) 153 | 154 | log_interval = log_iteration, 'iteration' 155 | trainer.extend(extensions.LogReport(trigger=log_interval)) 156 | trainer.extend(extensions.observe_lr(), trigger=log_interval) 157 | trainer.extend(extensions.PrintReport( 158 | ['epoch', 'iteration', 'lr', 159 | 'main/loss', 'main/loss/loc', 'main/loss/conf', 160 | 'validation/main/map']), 161 | trigger=log_interval) 162 | trainer.extend(extensions.ProgressBar(update_interval=10)) 163 | 164 | trainer.extend(extensions.snapshot(), trigger=val_interval) 165 | trainer.extend( 166 | extensions.snapshot_object(model, 'model_iter_{.updater.iteration}'), 167 | trigger=val_interval) 168 | 169 | if resume: 170 | serializers.load_npz(resume, trainer) 171 | 172 | trainer.run() 173 | -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_diagonals-thick_18_b81900_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_diagonals-thick_18_b81900_40x40.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_diagonals-thick_20_666666_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_diagonals-thick_20_666666_40x40.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_flat_10_000000_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_flat_10_000000_40x100.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_glass_100_f6f6f6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_glass_100_f6f6f6_1x400.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_glass_100_fdf5ce_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_glass_100_fdf5ce_1x400.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_gloss-wave_35_f6a828_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_gloss-wave_35_f6a828_500x100.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_highlight-soft_100_eeeeee_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_highlight-soft_100_eeeeee_1x100.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-bg_highlight-soft_75_ffe45c_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-bg_highlight-soft_75_ffe45c_1x100.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-icons_228ef1_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-icons_228ef1_256x240.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-icons_ef8c08_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-icons_ef8c08_256x240.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-icons_ffd27a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-icons_ffd27a_256x240.png -------------------------------------------------------------------------------- /ext_static/jquery/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/ext_static/jquery/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /flask_app.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2015 University of East Anglia, Norwich, UK 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | # Developed by Geoffrey French in collaboration with Dr. M. Fisher and 24 | # Dr. M. Mackiewicz. 25 | import argparse 26 | import json 27 | from matplotlib import pyplot as plt 28 | import numpy as np 29 | import os 30 | import yaml 31 | 32 | from flask import Flask, render_template, request, make_response, send_from_directory 33 | 34 | from image_labelling_tool import labelling_tool 35 | 36 | 37 | if __name__ == '__main__': 38 | parser = argparse.ArgumentParser(description='Image labelling tool - Flask app') 39 | parser.add_argument('--slic', action='store_true', help='Use SLIC segmentation to generate initial labels') 40 | parser.add_argument('--readonly', action='store_true', help='Don\'t persist changes to disk') 41 | parser.add_argument('--image_dir') 42 | parser.add_argument('--label_names') 43 | parser.add_argument('--file_ext', type=str, default='png') 44 | args = parser.parse_args() 45 | 46 | file_ext = '.{}'.format(args.file_ext) 47 | 48 | # `LabelClass` parameters are: symbolic name, human readable name for UI, and RGB colour as list 49 | with open(args.label_names, 'r') as f: 50 | label_names = yaml.load(f) 51 | 52 | cmap = plt.get_cmap('Spectral') 53 | colors = [(np.array(cmap(i)[:3]) * 255).astype(np.int32).tolist() 54 | for i in range(1, len(label_names) + 1)] 55 | label_classes = [labelling_tool.LabelClass(name, name, color) 56 | for color, name in zip(colors, label_names)] 57 | 58 | img_dir = args.image_dir 59 | if args.slic: 60 | import glob 61 | from skimage.segmentation import slic 62 | 63 | for path in glob.glob(os.path.join(img_dir, '*{}'.format(file_ext))): 64 | name = os.path.splitext(path)[0] 65 | out_name = name + '__labels.json' 66 | if os.path.exists(out_name): 67 | print('Label already exits at {}'.format(out_name)) 68 | # raise ValueError 69 | continue 70 | 71 | print('Segmenting {0}'.format(path)) 72 | img = plt.imread(path) 73 | # slic_labels = slic(img, 1000, compactness=20.0) 74 | # slic_labels = slic(img, 1000, slic_zero=True) + 1 75 | slic_labels = slic(img, 1500, slic_zero=True) + 1 76 | 77 | print('Converting SLIC labels to vector labels...') 78 | labels = labelling_tool.ImageLabels.from_label_image(slic_labels) 79 | 80 | with open(out_name, 'w') as f: 81 | json.dump(labels.labels_json, f) 82 | 83 | readonly = args.readonly 84 | # Load in .JPG images from the 'images' directory. 85 | labelled_images = labelling_tool.PersistentLabelledImage.for_directory( 86 | img_dir, image_filename_pattern='*{}'.format(file_ext), 87 | readonly=readonly) 88 | print('Loaded {0} images'.format(len(labelled_images))) 89 | 90 | # Generate image IDs list 91 | image_ids = [str(i) for i in range(len(labelled_images))] 92 | # Generate images table mapping image ID to image so we can get an image by ID 93 | images_table = {image_id: img for image_id, img in zip(image_ids, labelled_images)} 94 | # Generate image descriptors list to hand over to the labelling tool 95 | # Each descriptor provides the image ID, the URL and the size 96 | image_descriptors = [] 97 | for image_id, img in zip(image_ids, labelled_images): 98 | data, mimetype, width, height = img.data_and_mime_type_and_size() 99 | image_descriptors.append(labelling_tool.image_descriptor( 100 | image_id=image_id, url='/image/{}'.format(image_id), 101 | width=width, height=height 102 | )) 103 | 104 | app = Flask(__name__, static_folder='image_labelling_tool/static') 105 | config = { 106 | 'tools': { 107 | 'imageSelector': True, 108 | 'labelClassSelector': True, 109 | 'drawPolyLabel': True, 110 | 'compositeLabel': True, 111 | 'deleteLabel': True, 112 | } 113 | } 114 | 115 | 116 | @app.route('/') 117 | def index(): 118 | label_classes_json = [{'name': cls.name, 'human_name': cls.human_name, 'colour': cls.colour} for cls in label_classes] 119 | return render_template('labeller_page.jinja2', 120 | tool_js_urls=labelling_tool.js_file_urls('/static/labelling_tool/'), 121 | label_classes=json.dumps(label_classes_json), 122 | image_descriptors=json.dumps(image_descriptors), 123 | initial_image_index=0, 124 | config=json.dumps(config)) 125 | 126 | 127 | @app.route('/labelling/get_labels/') 128 | def get_labels(image_id): 129 | image = images_table[image_id] 130 | 131 | labels = image.labels_json 132 | complete = False 133 | 134 | 135 | label_header = { 136 | 'labels': labels, 137 | 'image_id': image_id, 138 | 'complete': complete 139 | } 140 | 141 | r = make_response(json.dumps(label_header)) 142 | r.mimetype = 'application/json' 143 | return r 144 | 145 | 146 | @app.route('/labelling/set_labels', methods=['POST']) 147 | def set_labels(): 148 | label_header = json.loads(request.form['labels']) 149 | image_id = label_header['image_id'] 150 | complete = label_header['complete'] 151 | labels = label_header['labels'] 152 | 153 | image = images_table[image_id] 154 | image.labels_json = labels 155 | 156 | return make_response('') 157 | 158 | 159 | @app.route('/image/') 160 | def get_image(image_id): 161 | image = images_table[image_id] 162 | data, mimetype, width, height = image.data_and_mime_type_and_size() 163 | r = make_response(data) 164 | r.mimetype = mimetype 165 | return r 166 | 167 | 168 | 169 | @app.route('/ext_static/') 170 | def base_static(filename): 171 | return send_from_directory(app.root_path + '/ext_static/', filename) 172 | 173 | 174 | # app.run(debug=True) 175 | app.run(debug=False) 176 | -------------------------------------------------------------------------------- /image_labelling_tool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/image_labelling_tool/__init__.py -------------------------------------------------------------------------------- /image_labelling_tool/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | import models 3 | 4 | # Register your models here. 5 | admin.site.register(models.Labels) 6 | -------------------------------------------------------------------------------- /image_labelling_tool/labelling_tool_jupyter.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2015 University of East Anglia, Norwich, UK 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | # Developed by Geoffrey French in collaboration with Dr. M. Fisher and 24 | # Dr. M. Mackiewicz. 25 | 26 | 27 | 28 | import base64, json, sys 29 | 30 | from ipywidgets import widgets 31 | 32 | from IPython.utils.traitlets import Unicode, Integer, List, Dict 33 | 34 | from . import labelling_tool 35 | 36 | 37 | 38 | 39 | 40 | class ImageLabellingTool (widgets.DOMWidget): 41 | _view_name = Unicode('ImageLabellingToolView', sync=True) 42 | _view_module = Unicode('image-labelling-tool', sync=True) 43 | 44 | label_classes = List(sync=True) 45 | 46 | tool_width_ = Integer(sync=True) 47 | tool_height_ = Integer(sync=True) 48 | 49 | images_ = List(sync=True) 50 | initial_image_index_ = Integer(sync=True) 51 | 52 | labelling_tool_config_ = Dict(sync=True) 53 | 54 | 55 | 56 | def __init__(self, labelled_images=None, label_classes=None, tool_width=1040, tool_height=585, 57 | labelling_tool_config=None, **kwargs): 58 | """ 59 | 60 | :type labelled_images: AbstractLabelledImage 61 | :param labelled_images: a list of images to label 62 | 63 | :type label_classes: [LabelClass] 64 | :param label_classes: list of label classes available to the user 65 | 66 | :type tool_width: int 67 | :param tool_width: width of tool in pixels 68 | 69 | :type tool_height: int 70 | :param tool_height: height of tool in pixels 71 | 72 | :param kwargs: kwargs passed to DOMWidget constructor 73 | """ 74 | if label_classes is None: 75 | label_classes = [] 76 | 77 | label_classes = [{'name': cls.name, 'human_name': cls.human_name, 'colour': cls.colour} for cls in label_classes] 78 | 79 | if labelled_images is None: 80 | labelled_images = [] 81 | 82 | if labelling_tool_config is None: 83 | labelling_tool_config = {} 84 | 85 | image_ids = [str(i) for i in range(len(labelled_images))] 86 | self.__images = {image_id: img for image_id, img in zip(image_ids, labelled_images)} 87 | self.__changing = False 88 | 89 | image_descriptors = [] 90 | for image_id, img in zip(image_ids, labelled_images): 91 | image_descriptors.append(labelling_tool.image_descriptor(image_id=image_id)) 92 | 93 | 94 | super(ImageLabellingTool, self).__init__(tool_width_=tool_width, tool_height_=tool_height, 95 | images_=image_descriptors, 96 | initial_image_index_=0, 97 | label_classes=label_classes, 98 | labelling_tool_config_=labelling_tool_config, **kwargs) 99 | 100 | self.on_msg(self._on_msg_recv) 101 | 102 | self.label_data = labelled_images[0].labels_json 103 | 104 | 105 | def _on_msg_recv(self, _, msg, *args): 106 | msg_type = msg.get('msg_type', '') 107 | if msg_type == 'get_labels': 108 | try: 109 | image_id = str(msg.get('image_id', '0')) 110 | except ValueError: 111 | image_id = '0' 112 | 113 | load_labels_msg = {} 114 | 115 | image = self.__images[image_id] 116 | data, mimetype, width, height = image.data_and_mime_type_and_size() 117 | 118 | data_b64 = base64.b64encode(data) 119 | 120 | self.label_data = image.labels_json 121 | 122 | msg_label_header = { 123 | 'image_id': image_id, 124 | 'labels': image.labels_json, 125 | 'complete': image.complete 126 | } 127 | msg_image = { 128 | 'image_id': image_id, 129 | 'img_url': 'data:{0};base64,'.format(mimetype) + data_b64, 130 | 'width': width, 131 | 'height': height, 132 | } 133 | self.send({ 134 | 'msg_type': 'load_labels', 135 | 'label_header': msg_label_header, 136 | 'image': msg_image, 137 | }) 138 | elif msg_type == 'update_labels': 139 | label_header = msg.get('label_header') 140 | if label_header is not None: 141 | image_id = label_header['image_id'] 142 | complete = label_header['complete'] 143 | labels = label_header['labels'] 144 | self.__images[image_id].labels_json = labels 145 | self.__images[image_id].complete = complete 146 | print('Received changes for image {0}; {1} labels'.format(image_id, len(labels))) 147 | 148 | 149 | 150 | _LABELLING_TOOL_JS_URLS = labelling_tool.js_file_urls("image_labelling_tool/static/labelling_tool") 151 | _LABELLING_TOOL_JS_REFS = ', '.join(['"{}"'.format(url) for url in _LABELLING_TOOL_JS_URLS]) 152 | 153 | def _lt_deps_shim(): 154 | # Regrettably Jupyter uses require.js which burdens us with defining dependencies. 155 | # The files are in the order they should be imported, so just make sure each one 156 | # depends on the previous file. 157 | # Duplicating all the dependencies that are defined in the Typescript files takes time 158 | # and I have better things to do with my life. I hate web development. I hate Javascript. 159 | shim = {} 160 | # files = [f.replace('.js', '') for f in labelling_tool.LABELLING_TOOL_JS_FILES] 161 | files = _LABELLING_TOOL_JS_URLS 162 | shim[files[0]] = dict(exports='labelling_tool') 163 | for prev, cur in zip(files[:-1], files[1:]): 164 | shim[cur] = dict(deps=[prev], exports='labelling_tool') 165 | return json.dumps(shim) 166 | 167 | 168 | LABELLING_TOOL_JUPYTER_JS = """ 169 | console.log("Run me"); 170 | requirejs.config({ 171 | shim: {{<>}} 172 | }); 173 | 174 | define('image-labelling-tool', 175 | ["jupyter-js-widgets", 176 | "image_labelling_tool/static/d3.min.js", 177 | "image_labelling_tool/static/json2.js", 178 | "image_labelling_tool/static/polyk.js", 179 | {{<>}}], 180 | function(widget, manager){ 181 | /* 182 | Labeling tool view; links to the server side data structures 183 | */ 184 | var ImageLabellingToolView = widget.DOMWidgetView.extend({ 185 | render: function() { 186 | var self = this; 187 | 188 | // Register a custom IPython widget message handler for receiving messages from the Kernel 189 | this.model.on('msg:custom', this._on_custom_msg, this); 190 | 191 | 192 | // Get label classes, tool dimensions, and image ID set and initial image ID from the kernel 193 | var label_classes = self.model.get("label_classes"); 194 | var tool_width = self.model.get("tool_width_"); 195 | var tool_height = self.model.get("tool_height_"); 196 | var images = self.model.get('images_'); 197 | var initial_image_index = self.model.get('initial_image_index_'); 198 | var config = self.model.get('labelling_tool_config_'); 199 | 200 | console.log("Labelling tool config:"); 201 | console.log(config); 202 | 203 | 204 | // Callback function to allow the labelling tool to request an image 205 | var get_labels = function(image_id_str) { 206 | // Send a 'request_image_descriptor' message to the kernel requesting the 207 | // image identified by `image_id_str` 208 | self.send({msg_type: 'get_labels', image_id: image_id_str}); 209 | }; 210 | 211 | // Callback function to allow the labelling tool to send modified label data to the kernel 212 | var update_labels = function(label_header) { 213 | // Send a 'label_header' message to the kernel, along with modified label data 214 | self.send({msg_type: 'update_labels', label_header: label_header}); 215 | }; 216 | 217 | // Create the labelling tool 218 | // Place it into the widget element (`this.$el`). 219 | // Also give it the label classes, tool dimensions, image ID set, initial image ID and the callbacks above 220 | self._labeling_tool = new labelling_tool.LabellingTool(this.$el, label_classes, tool_width, tool_height, 221 | images, initial_image_index, 222 | get_labels, update_labels, null, 223 | config); 224 | }, 225 | 226 | 227 | _on_custom_msg: function(msg) { 228 | // Received a custom message from the kernel 229 | if (msg.msg_type === "load_labels") { 230 | // 'load_labels' message 231 | var label_header = msg.label_header; 232 | var image = msg.image; 233 | // Send labels to labelling tool 234 | this._labeling_tool.loadLabels(label_header, image); 235 | } 236 | } 237 | }); 238 | 239 | // Register the ImageLabelingToolView with the IPython widget manager. 240 | // manager.WidgetManager.register_widget_view('ImageLabellingToolView', ImageLabellingToolView); 241 | console.log("Defined ImageLabellingToolView"); 242 | 243 | return { 244 | 'ImageLabellingToolView': ImageLabellingToolView 245 | }; 246 | }); 247 | """.replace('{{<>}}', _LABELLING_TOOL_JS_REFS)\ 248 | .replace('{{<>}}', _lt_deps_shim()) 249 | 250 | -------------------------------------------------------------------------------- /image_labelling_tool/labelling_tool_views.py: -------------------------------------------------------------------------------- 1 | import json, datetime 2 | 3 | from django.http import HttpResponse, JsonResponse 4 | from django.views.decorators.cache import never_cache 5 | from django.views import View 6 | from django.utils.decorators import method_decorator 7 | 8 | from django.conf import settings 9 | 10 | from . import models 11 | 12 | 13 | class LabellingToolView (View): 14 | """ 15 | Labelling tool class based view 16 | 17 | Subclass and override the `get_labels` method (mandatory) and optionally 18 | the `update_labels` method to customise how label data is accessed and updated. 19 | 20 | `get_labels` should return either a `models.Labels` instance or a dictionary of the form: 21 | `{ 22 | 'complete': boolean indicating if labelling is finished for this image, 23 | 'labels': label data as JSON 24 | 'state': [optional] 'editable' if editing should be permitted, 'locked' if the UI should 25 | warn the user that the labels are being edited by someone else 26 | }` 27 | 28 | Example: 29 | >>> class MyLabelView (LabellingToolView): 30 | ... def get_labels(self, request, image_id_str, *args, **kwargs): 31 | ... image = models.Image.get(id=int(image_id_string)) 32 | ... # Assume `image.labels` is a field that refers to the `Labels` instance 33 | ... return image.labels 34 | 35 | Or: 36 | >>> class MyLabelView (LabellingToolView): 37 | ... def get_labels(self, request, image_id_str, *args, **kwargs): 38 | ... image = models.Image.get(id=int(image_id_string)) 39 | ... # Lets assume that the label data has been incorporated into the `Image` class: 40 | ... labels_metadata = { 41 | ... 'complete': image.complete, 42 | ... 'timeElapsed': image.edit_time_elapsed, 43 | ... 'labels': image.labels_json, 44 | ... 'state': ('locked' if image.in_use else 'editable') 45 | ... } 46 | ... return labels_metadata 47 | ... 48 | ... def update_labels(self, request, image_id_str, labels, complete, time_elapsed, *args, **kwargs): 49 | ... image = models.Image.get(id=int(image_id_string)) 50 | ... image.complete = complete 51 | ... image.edit_time_elapsed = time_elapsed 52 | ... image.labels_json = labels 53 | ... image.save() 54 | """ 55 | def get_labels(self, request, image_id_str, *args, **kwargs): 56 | raise NotImplementedError('Abstract for type {}'.format(type(self))) 57 | 58 | def update_labels(self, request, image_id_str, labels, complete, time_elapsed, *args, **kwargs): 59 | labels = self.get_labels(request, image_id_str, *args, **kwargs) 60 | labels.update_labels(labels, complete, time_elapsed, request.user, save=True, check_lock=False) 61 | 62 | @method_decorator(never_cache) 63 | def get(self, request, *args, **kwargs): 64 | if 'labels_for_image_id' in request.GET: 65 | image_id_str = request.GET['labels_for_image_id'] 66 | 67 | labels = self.get_labels(request, image_id_str, *args, **kwargs) 68 | if labels is None: 69 | # No labels for this image 70 | labels_header = { 71 | 'image_id': image_id_str, 72 | 'complete': False, 73 | 'timeElapsed': 0.0, 74 | 'state': 'editable', 75 | 'labels': [], 76 | } 77 | elif isinstance(labels, models.Labels): 78 | # Remove existing lock 79 | labels_header = { 80 | 'image_id': image_id_str, 81 | 'complete': labels.complete, 82 | 'timeElapsed': labels.edit_time_elapsed, 83 | 'state': 'editable', 84 | 'labels': labels.labels_json, 85 | } 86 | elif isinstance(labels, dict): 87 | labels_header = { 88 | 'image_id': image_id_str, 89 | 'complete': labels['complete'], 90 | 'timeElapsed': labels.get('edit_time_elapsed', 0.0), 91 | 'state': labels.get('state', 'editable'), 92 | 'labels': labels['labels'], 93 | } 94 | else: 95 | raise TypeError('labels returned by get_labels metod should be None, a Labels model ' 96 | 'or a dictionary; not a {}'.format(type(labels))) 97 | 98 | return JsonResponse(labels_header) 99 | elif 'next_unlocked_image_id_after' in request.GET: 100 | return JsonResponse({'error': 'operation_not_supported'}) 101 | else: 102 | return JsonResponse({'error': 'unknown_operation'}) 103 | 104 | def post(self, request, *args, **kwargs): 105 | labels = json.loads(request.POST['labels']) 106 | image_id = labels['image_id'] 107 | complete = labels['complete'] 108 | time_elapsed = labels['timeElapsed'] 109 | label_data = labels['labels'] 110 | 111 | try: 112 | self.update_labels(request, str(image_id), label_data, complete, time_elapsed, *args, **kwargs) 113 | except models.LabelsLockedError: 114 | return JsonResponse({'error': 'locked'}) 115 | else: 116 | return JsonResponse({'response': 'success'}) 117 | 118 | 119 | 120 | class LabellingToolViewWithLocking (LabellingToolView): 121 | """ 122 | Labelling tool class based view with label locking 123 | 124 | Subclass and override the `get_labels` method (mandatory), the 125 | `get_next_unlocked_image_id_after` method (mandatory) and optionally 126 | the `update_labels` method to customise how label data is accessed and updated. 127 | 128 | `get_labels` should return a `models.Labels` instance; it should NOT return anything else 129 | in the way that the `get_labels` method of a subclass of `LabellingToolView` can. 130 | 131 | The `LABELLING_TOOL_LOCK_TIME` attribute in settings can be used to set the amount of time 132 | that a lock lasts for in seconds; default is 10 minutes (600s). 133 | 134 | Example: 135 | >>> class MyLabelView (LabellingToolViewWithLocking): 136 | ... def get_labels(self, request, image_id_str, *args, **kwargs): 137 | ... image = models.Image.get(id=int(image_id_string)) 138 | ... # Assume `image.labels` is a field that refers to the `Labels` instance 139 | ... return image.labels 140 | ... 141 | ... def get_next_unlocked_image_id_after(self, request, current_image_id_str, *args, **kwargs): 142 | ... unlocked_labels = image_labelling_tool.models.Labels.objects.unlocked() 143 | ... unlocked_imgs = models.Image.objects.filter(labels__in=unlocked_labels) 144 | ... unlocked_img_ids = [img.id for img in unlocked_imgs] 145 | ... try: 146 | ... index = unlocked_img_ids.index(int(current_image_id_str)) 147 | ... except ValueError: 148 | ... return None 149 | ... index += 1 150 | ... if index < len(unlocked_img_ids): 151 | ... return unlocked_img_ids[index] 152 | ... else: 153 | ... return None 154 | """ 155 | def get_next_unlocked_image_id_after(self, request, current_image_id_str, *args, **kwargs): 156 | raise NotImplementedError('Abstract for type {}'.format(type(self))) 157 | 158 | def update_labels(self, request, image_id_str, labels_js, complete, time_elapsed, *args, **kwargs): 159 | expire_after = getattr(settings, 'LABELLING_TOOL_LOCK_TIME', 600) 160 | labels = self.get_labels(request, image_id_str, *args, **kwargs) 161 | labels.update_labels(labels_js, complete, time_elapsed, request.user, check_lock=True, save=False) 162 | if request.user.is_authenticated(): 163 | labels.refresh_lock(request.user, datetime.timedelta(seconds=expire_after), save=False) 164 | labels.save() 165 | 166 | @method_decorator(never_cache) 167 | def get(self, request, *args, **kwargs): 168 | if 'labels_for_image_id' in request.GET: 169 | image_id_str = request.GET['labels_for_image_id'] 170 | 171 | labels = self.get_labels(request, image_id_str) 172 | 173 | if not isinstance(labels, models.Labels): 174 | raise TypeError('labels returned by get_labels metod should be a Labels ' 175 | 'model, not a {}'.format(type(labels))) 176 | 177 | # Remove existing lock 178 | if request.user.is_authenticated(): 179 | already_locked = models.Labels.objects.locked_by_user(request.user) 180 | for locked_labels in already_locked: 181 | locked_labels.unlock(from_user=request.user, save=True) 182 | 183 | if labels.is_locked_to(request.user): 184 | state = 'locked' 185 | attempt_lock = False 186 | else: 187 | state = 'editable' 188 | attempt_lock = True 189 | labels_header = { 190 | 'image_id': image_id_str, 191 | 'complete': labels.complete, 192 | 'timeElapsed': labels.edit_time_elapsed, 193 | 'state': state, 194 | 'labels': labels.labels_json, 195 | } 196 | 197 | if attempt_lock and request.user.is_authenticated(): 198 | expire_after = getattr(settings, 'LABELLING_TOOL_LOCK_TIME', 600) 199 | labels.lock(request.user, datetime.timedelta(seconds=expire_after), save=True) 200 | 201 | return JsonResponse(labels_header) 202 | elif 'next_unlocked_image_id_after' in request.GET: 203 | current_image_id_str = request.GET['next_unlocked_image_id_after'] 204 | next_image_id = self.get_next_unlocked_image_id_after( 205 | request, current_image_id_str, *args, **kwargs) 206 | 207 | return JsonResponse({'next_unlocked_image_id': str(next_image_id)}) 208 | else: 209 | return JsonResponse({'error': 'unknown_operation'}) 210 | -------------------------------------------------------------------------------- /image_labelling_tool/managers.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db import models 3 | from django.utils import timezone 4 | 5 | 6 | class LabelsManager (models.Manager): 7 | def empty(self): 8 | return self.filter(labels_json_str='[]') 9 | 10 | def not_empty(self): 11 | return self.exclude(labels_json_str='[]') 12 | 13 | def modified_by_user(self, user): 14 | return self.filter(last_modified_by=user) 15 | 16 | def locked_by_user(self, user): 17 | return self.filter(locked_by=user) 18 | 19 | def unlocked(self): 20 | now = timezone.now() 21 | return self.filter(locked_by=None) | self.filter(lock_expiry_datetime__gte=now) 22 | 23 | -------------------------------------------------------------------------------- /image_labelling_tool/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-30 09:03 3 | from __future__ import unicode_literals 4 | 5 | import datetime 6 | from django.conf import settings 7 | from django.db import migrations, models 8 | import django.db.models.deletion 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Labels', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('labels_json_str', models.TextField(default=b'[]')), 25 | ('complete', models.BooleanField(default=False)), 26 | ('creation_date', models.DateField()), 27 | ('last_modified_datetime', models.DateTimeField(default=datetime.datetime.now)), 28 | ('lock_expiry_datetime', models.DateTimeField(default=datetime.datetime.now)), 29 | ('last_modified_by', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='modified_labels', to=settings.AUTH_USER_MODEL)), 30 | ('locked_by', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='locked_labels', to=settings.AUTH_USER_MODEL)), 31 | ], 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /image_labelling_tool/migrations/0002_labels_edit_time_elapsed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2017-04-11 07:40 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('image_labelling_tool', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='labels', 17 | name='edit_time_elapsed', 18 | field=models.FloatField(blank=True, default=0.0), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /image_labelling_tool/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/image_labelling_tool/migrations/__init__.py -------------------------------------------------------------------------------- /image_labelling_tool/models.py: -------------------------------------------------------------------------------- 1 | import json, datetime 2 | from django.db import models 3 | from django.conf import settings 4 | from django.utils import timezone 5 | from . import managers 6 | 7 | class LabelsLockedError (Exception): 8 | pass 9 | 10 | # Create your models here. 11 | class Labels (models.Model): 12 | # Label data 13 | labels_json_str = models.TextField(default='[]') 14 | complete = models.BooleanField(default=False) 15 | 16 | # Creation date 17 | creation_date = models.DateField() 18 | 19 | # Time elapsed during editing, in seconds 20 | edit_time_elapsed = models.FloatField(default=0.0, blank=True) 21 | 22 | # Last modification user and datetime 23 | last_modified_by = models.ForeignKey( 24 | settings.AUTH_USER_MODEL, related_name='modified_labels', null=True, default=None) 25 | last_modified_datetime = models.DateTimeField(default=datetime.datetime.now) 26 | 27 | # Locked by user and expiry datetime 28 | locked_by = models.ForeignKey( 29 | settings.AUTH_USER_MODEL, related_name='locked_labels', null=True, default=None) 30 | lock_expiry_datetime = models.DateTimeField(default=datetime.datetime.now) 31 | 32 | # Manager 33 | objects = managers.LabelsManager() 34 | 35 | @property 36 | def labels_json(self): 37 | return json.loads(self.labels_json_str) 38 | 39 | @labels_json.setter 40 | def labels_json(self, label_js): 41 | self.labels_json_str = json.dumps(label_js) 42 | 43 | @property 44 | def is_empty(self): 45 | return self.labels_json_str == '[]' 46 | 47 | @property 48 | def label_classes(self): 49 | label_classes = [x['label_class'] for x in self.labels_json] 50 | return set(label_classes) 51 | 52 | def update_labels(self, labels_json, complete, time_elapsed, user, save=False, check_lock=False): 53 | # Verify time elapsed is within the bounds of possibility 54 | current_time = timezone.now() 55 | dt_since_last_mod = (current_time - self.last_modified_datetime).total_seconds() 56 | # Allow to either double the time since last modification or time since last modification plus 1 minute 57 | # to account for potential latency in delivery of last edit 58 | permitted_dt = max(dt_since_last_mod * 2.0, dt_since_last_mod + 60.0) 59 | permitted_time = self.edit_time_elapsed + permitted_dt 60 | if time_elapsed > permitted_time: 61 | print('WARNING: rejecting time_elapsed: ' 62 | 'self.edit_time_elapsed={}, time_elapsed={}, permitted_time={}'.format( 63 | self.edit_time_elapsed, time_elapsed, permitted_time 64 | )) 65 | elif time_elapsed >= self.edit_time_elapsed: 66 | self.edit_time_elapsed = time_elapsed 67 | 68 | if check_lock: 69 | if self.is_locked_to(user): 70 | raise LabelsLockedError 71 | self.labels_json = labels_json 72 | self.complete = complete 73 | if user.is_authenticated(): 74 | self.last_modified_by = user 75 | else: 76 | self.last_modified_by = None 77 | self.last_modified_datetime = timezone.now() 78 | if save: 79 | self.save() 80 | 81 | def is_lock_active(self): 82 | return timezone.now() < self.lock_expiry_datetime and self.locked_by is not None 83 | 84 | def is_locked_to(self, user=None): 85 | lock_active = self.is_lock_active() 86 | if user is not None and not user.is_authenticated(): 87 | user = None 88 | if user is None: 89 | return lock_active 90 | else: 91 | return lock_active and user != self.locked_by 92 | 93 | def lock(self, to_user, expire_after, save=False): 94 | if self.is_locked_to(to_user): 95 | raise ValueError('Cannot lock Labels(id={}) to user {}; is already locked'.format( 96 | self.id, to_user.username 97 | )) 98 | self.locked_by = to_user 99 | expiry = timezone.now() + expire_after 100 | self.lock_expiry_datetime = expiry 101 | if save: 102 | self.save() 103 | 104 | def refresh_lock(self, to_user, expire_after, save=False): 105 | if self.is_lock_active(): 106 | if self.locked_by != to_user: 107 | raise ValueError('Cannot refresh lock Labels(id={}) for user {}; is already locked by {}'.format( 108 | self.id, to_user.username, self.locked_by.username 109 | )) 110 | expiry = timezone.now() + expire_after 111 | self.lock_expiry_datetime = expiry 112 | if save: 113 | self.save() 114 | 115 | def unlock(self, from_user, save=False): 116 | if self.is_lock_active(): 117 | if from_user != self.locked_by: 118 | raise ValueError('Cannot unlock Labels(id={}) from user {}, it is locked by {}'.format( 119 | self.id, from_user.username, self.locked_by.username 120 | )) 121 | self.locked_by = None 122 | self.lock_expiry_datetime = timezone.now() 123 | if save: 124 | self.save() 125 | 126 | def __unicode__(self): 127 | if self.last_modified_by is not None: 128 | return 'Labels {} (last modified by {} at {})'.format( 129 | self.id, self.last_modified_by.username, self.last_modified_datetime) 130 | else: 131 | return 'Labels {}'.format(self.id) 132 | -------------------------------------------------------------------------------- /image_labelling_tool/static/js.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * JavaScript Cookie v2.0.3 3 | * https://github.com/js-cookie/js-cookie 4 | * 5 | * Copyright 2006, 2015 Klaus Hartl & Fagner Brack 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | define(factory); 11 | } else if (typeof exports === 'object') { 12 | module.exports = factory(); 13 | } else { 14 | var _OldCookies = window.Cookies; 15 | var api = window.Cookies = factory(); 16 | api.noConflict = function () { 17 | window.Cookies = _OldCookies; 18 | return api; 19 | }; 20 | } 21 | }(function () { 22 | function extend () { 23 | var i = 0; 24 | var result = {}; 25 | for (; i < arguments.length; i++) { 26 | var attributes = arguments[ i ]; 27 | for (var key in attributes) { 28 | result[key] = attributes[key]; 29 | } 30 | } 31 | return result; 32 | } 33 | 34 | function init (converter) { 35 | function api (key, value, attributes) { 36 | var result; 37 | 38 | // Write 39 | 40 | if (arguments.length > 1) { 41 | attributes = extend({ 42 | path: '/' 43 | }, api.defaults, attributes); 44 | 45 | if (typeof attributes.expires === 'number') { 46 | var expires = new Date(); 47 | expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); 48 | attributes.expires = expires; 49 | } 50 | 51 | try { 52 | result = JSON.stringify(value); 53 | if (/^[\{\[]/.test(result)) { 54 | value = result; 55 | } 56 | } catch (e) {} 57 | 58 | value = encodeURIComponent(String(value)); 59 | value = value.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); 60 | 61 | key = encodeURIComponent(String(key)); 62 | key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); 63 | key = key.replace(/[\(\)]/g, escape); 64 | 65 | return (document.cookie = [ 66 | key, '=', value, 67 | attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE 68 | attributes.path && '; path=' + attributes.path, 69 | attributes.domain && '; domain=' + attributes.domain, 70 | attributes.secure ? '; secure' : '' 71 | ].join('')); 72 | } 73 | 74 | // Read 75 | 76 | if (!key) { 77 | result = {}; 78 | } 79 | 80 | // To prevent the for loop in the first place assign an empty array 81 | // in case there are no cookies at all. Also prevents odd result when 82 | // calling "get()" 83 | var cookies = document.cookie ? document.cookie.split('; ') : []; 84 | var rdecode = /(%[0-9A-Z]{2})+/g; 85 | var i = 0; 86 | 87 | for (; i < cookies.length; i++) { 88 | var parts = cookies[i].split('='); 89 | var name = parts[0].replace(rdecode, decodeURIComponent); 90 | var cookie = parts.slice(1).join('='); 91 | 92 | if (cookie.charAt(0) === '"') { 93 | cookie = cookie.slice(1, -1); 94 | } 95 | 96 | try { 97 | cookie = converter && converter(cookie, name) || cookie.replace(rdecode, decodeURIComponent); 98 | 99 | if (this.json) { 100 | try { 101 | cookie = JSON.parse(cookie); 102 | } catch (e) {} 103 | } 104 | 105 | if (key === name) { 106 | result = cookie; 107 | break; 108 | } 109 | 110 | if (!key) { 111 | result[name] = cookie; 112 | } 113 | } catch (e) {} 114 | } 115 | 116 | return result; 117 | } 118 | 119 | api.get = api.set = api; 120 | api.getJSON = function () { 121 | return api.apply({ 122 | json: true 123 | }, [].slice.call(arguments)); 124 | }; 125 | api.defaults = {}; 126 | 127 | api.remove = function (key, attributes) { 128 | api(key, '', extend(attributes, { 129 | expires: -1 130 | })); 131 | }; 132 | 133 | api.withConverter = init; 134 | 135 | return api; 136 | } 137 | 138 | return init(); 139 | })); 140 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/abstract_label.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | /// 28 | /// 29 | /// 30 | var labelling_tool; 31 | (function (labelling_tool) { 32 | /* 33 | Label visibility 34 | */ 35 | (function (LabelVisibility) { 36 | LabelVisibility[LabelVisibility["HIDDEN"] = 0] = "HIDDEN"; 37 | LabelVisibility[LabelVisibility["FAINT"] = 1] = "FAINT"; 38 | LabelVisibility[LabelVisibility["FULL"] = 2] = "FULL"; 39 | })(labelling_tool.LabelVisibility || (labelling_tool.LabelVisibility = {})); 40 | var LabelVisibility = labelling_tool.LabelVisibility; 41 | /* 42 | Abstract label entity 43 | */ 44 | var AbstractLabelEntity = (function () { 45 | function AbstractLabelEntity(view, model) { 46 | this.root_view = view; 47 | this.model = model; 48 | this._attached = this._hover = this._selected = false; 49 | this._event_listeners = []; 50 | this.parent_entity = null; 51 | this.entity_id = AbstractLabelEntity.entity_id_counter++; 52 | } 53 | AbstractLabelEntity.prototype.add_event_listener = function (listener) { 54 | this._event_listeners.push(listener); 55 | }; 56 | AbstractLabelEntity.prototype.remove_event_listener = function (listener) { 57 | var i = this._event_listeners.indexOf(listener); 58 | if (i !== -1) { 59 | this._event_listeners.splice(i, 1); 60 | } 61 | }; 62 | AbstractLabelEntity.prototype.set_parent = function (parent) { 63 | this.parent_entity = parent; 64 | }; 65 | AbstractLabelEntity.prototype.get_entity_id = function () { 66 | return this.entity_id; 67 | }; 68 | AbstractLabelEntity.prototype.attach = function () { 69 | this.root_view._register_entity(this); 70 | this._attached = true; 71 | }; 72 | AbstractLabelEntity.prototype.detach = function () { 73 | this._attached = false; 74 | this.root_view._unregister_entity(this); 75 | }; 76 | AbstractLabelEntity.prototype.destroy = function () { 77 | if (this.parent_entity !== null) { 78 | this.parent_entity.remove_child(this); 79 | } 80 | this.root_view.shutdown_entity(this); 81 | }; 82 | AbstractLabelEntity.prototype.update = function () { 83 | }; 84 | AbstractLabelEntity.prototype.commit = function () { 85 | }; 86 | AbstractLabelEntity.prototype.hover = function (state) { 87 | this._hover = state; 88 | this._update_style(); 89 | }; 90 | AbstractLabelEntity.prototype.select = function (state) { 91 | this._selected = state; 92 | this._update_style(); 93 | }; 94 | AbstractLabelEntity.prototype.notify_hide_labels_change = function () { 95 | this._update_style(); 96 | }; 97 | AbstractLabelEntity.prototype.get_label_class = function () { 98 | return this.model.label_class; 99 | }; 100 | AbstractLabelEntity.prototype.set_label_class = function (label_class) { 101 | this.model.label_class = label_class; 102 | this._update_style(); 103 | this.commit(); 104 | }; 105 | AbstractLabelEntity.prototype._update_style = function () { 106 | }; 107 | ; 108 | AbstractLabelEntity.prototype._outline_colour = function () { 109 | if (this._selected) { 110 | if (this._hover) { 111 | return new labelling_tool.Colour4(255, 0, 128, 1.0); 112 | } 113 | else { 114 | return new labelling_tool.Colour4(255, 0, 0, 1.0); 115 | } 116 | } 117 | else { 118 | if (this._hover) { 119 | return new labelling_tool.Colour4(0, 255, 128, 1.0); 120 | } 121 | else { 122 | return new labelling_tool.Colour4(255, 255, 0, 1.0); 123 | } 124 | } 125 | }; 126 | AbstractLabelEntity.prototype.compute_centroid = function () { 127 | return null; 128 | }; 129 | AbstractLabelEntity.prototype.compute_bounding_box = function () { 130 | return null; 131 | }; 132 | ; 133 | AbstractLabelEntity.prototype.contains_pointer_position = function (point) { 134 | return false; 135 | }; 136 | AbstractLabelEntity.prototype.distance_to_point = function (point) { 137 | return null; 138 | }; 139 | ; 140 | AbstractLabelEntity.prototype.notify_model_destroyed = function (model_id) { 141 | }; 142 | ; 143 | AbstractLabelEntity.entity_id_counter = 0; 144 | return AbstractLabelEntity; 145 | })(); 146 | labelling_tool.AbstractLabelEntity = AbstractLabelEntity; 147 | /* 148 | Map label type to entity constructor 149 | */ 150 | var label_type_to_entity_factory = {}; 151 | /* 152 | Register label entity factory 153 | */ 154 | function register_entity_factory(label_type_name, factory) { 155 | label_type_to_entity_factory[label_type_name] = factory; 156 | } 157 | labelling_tool.register_entity_factory = register_entity_factory; 158 | /* 159 | Construct entity for given label model. 160 | Uses the map above to choose the appropriate constructor 161 | */ 162 | function new_entity_for_model(root_view, label_model) { 163 | var factory = label_type_to_entity_factory[label_model.label_type]; 164 | return factory(root_view, label_model); 165 | } 166 | labelling_tool.new_entity_for_model = new_entity_for_model; 167 | })(labelling_tool || (labelling_tool = {})); 168 | //# sourceMappingURL=abstract_label.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/abstract_label.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | /// 29 | /// 30 | /// 31 | 32 | module labelling_tool { 33 | /* 34 | Abstract label model 35 | */ 36 | export interface AbstractLabelModel { 37 | label_type: string; 38 | label_class: string; 39 | } 40 | 41 | 42 | 43 | /* 44 | Label visibility 45 | */ 46 | export enum LabelVisibility { 47 | HIDDEN, 48 | FAINT, 49 | FULL 50 | } 51 | 52 | 53 | export interface LabelEntityEventListener { 54 | on_mouse_in: (entity: any) => void; 55 | on_mouse_out: (entity: any) => void; 56 | } 57 | 58 | 59 | /* 60 | Abstract label entity 61 | */ 62 | export class AbstractLabelEntity { 63 | private static entity_id_counter: number = 0; 64 | model: ModelType; 65 | protected root_view: RootLabelView; 66 | private entity_id: number; 67 | _attached: boolean; 68 | _hover: boolean; 69 | _selected: boolean; 70 | _event_listeners: LabelEntityEventListener[]; 71 | parent_entity: ContainerEntity; 72 | 73 | 74 | 75 | constructor(view: RootLabelView, model: ModelType) { 76 | this.root_view = view; 77 | this.model = model; 78 | this._attached = this._hover = this._selected = false; 79 | this._event_listeners = []; 80 | this.parent_entity = null; 81 | this.entity_id = AbstractLabelEntity.entity_id_counter++; 82 | 83 | } 84 | 85 | 86 | add_event_listener(listener: LabelEntityEventListener) { 87 | this._event_listeners.push(listener) 88 | } 89 | 90 | remove_event_listener(listener: LabelEntityEventListener) { 91 | var i = this._event_listeners.indexOf(listener); 92 | if (i !== -1) { 93 | this._event_listeners.splice(i, 1); 94 | } 95 | } 96 | 97 | set_parent(parent: ContainerEntity) { 98 | this.parent_entity = parent; 99 | } 100 | 101 | get_entity_id(): number { 102 | return this.entity_id; 103 | } 104 | 105 | attach() { 106 | this.root_view._register_entity(this); 107 | this._attached = true; 108 | } 109 | 110 | detach() { 111 | this._attached = false; 112 | this.root_view._unregister_entity(this); 113 | } 114 | 115 | destroy() { 116 | if (this.parent_entity !== null) { 117 | this.parent_entity.remove_child(this); 118 | } 119 | this.root_view.shutdown_entity(this); 120 | } 121 | 122 | update() { 123 | } 124 | 125 | commit() { 126 | } 127 | 128 | hover(state: boolean) { 129 | this._hover = state; 130 | this._update_style(); 131 | } 132 | 133 | select(state: boolean) { 134 | this._selected = state; 135 | this._update_style(); 136 | } 137 | 138 | notify_hide_labels_change() { 139 | this._update_style(); 140 | } 141 | 142 | get_label_class(): string { 143 | return this.model.label_class; 144 | } 145 | 146 | set_label_class(label_class: string) { 147 | this.model.label_class = label_class; 148 | this._update_style(); 149 | this.commit(); 150 | } 151 | 152 | _update_style() { 153 | }; 154 | 155 | _outline_colour(): Colour4 { 156 | if (this._selected) { 157 | if (this._hover) { 158 | return new Colour4(255, 0, 128, 1.0); 159 | } 160 | else { 161 | return new Colour4(255, 0, 0, 1.0); 162 | } 163 | } 164 | else { 165 | if (this._hover) { 166 | return new Colour4(0, 255, 128, 1.0); 167 | } 168 | else { 169 | return new Colour4(255, 255, 0, 1.0); 170 | } 171 | } 172 | } 173 | 174 | compute_centroid(): Vector2 { 175 | return null; 176 | } 177 | 178 | compute_bounding_box(): AABox { 179 | return null; 180 | }; 181 | 182 | contains_pointer_position(point: Vector2): boolean { 183 | return false; 184 | } 185 | 186 | distance_to_point(point: Vector2): number { 187 | return null; 188 | }; 189 | 190 | notify_model_destroyed(model_id: number) { 191 | }; 192 | } 193 | 194 | 195 | /* 196 | Container entity 197 | */ 198 | export interface ContainerEntity { 199 | add_child(child: AbstractLabelEntity): void; 200 | remove_child(child: AbstractLabelEntity): void; 201 | } 202 | 203 | 204 | 205 | /* 206 | Map label type to entity constructor 207 | */ 208 | var label_type_to_entity_factory: any = {}; 209 | 210 | 211 | /* 212 | Register label entity factory 213 | */ 214 | export function register_entity_factory(label_type_name: string, 215 | factory: (root_view:RootLabelView, model:AbstractLabelModel) => any) { 216 | label_type_to_entity_factory[label_type_name] = factory; 217 | } 218 | 219 | /* 220 | Construct entity for given label model. 221 | Uses the map above to choose the appropriate constructor 222 | */ 223 | export function new_entity_for_model(root_view: RootLabelView, label_model: AbstractLabelModel) { 224 | var factory = label_type_to_entity_factory[label_model.label_type]; 225 | return factory(root_view, label_model); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/abstract_tool.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | /// 28 | /// 29 | var labelling_tool; 30 | (function (labelling_tool) { 31 | /* 32 | Abstract tool 33 | */ 34 | var AbstractTool = (function () { 35 | function AbstractTool(view) { 36 | this._view = view; 37 | } 38 | AbstractTool.prototype.on_init = function () { 39 | }; 40 | ; 41 | AbstractTool.prototype.on_shutdown = function () { 42 | }; 43 | ; 44 | AbstractTool.prototype.on_switch_in = function (pos) { 45 | }; 46 | ; 47 | AbstractTool.prototype.on_switch_out = function (pos) { 48 | }; 49 | ; 50 | AbstractTool.prototype.on_left_click = function (pos, event) { 51 | }; 52 | ; 53 | AbstractTool.prototype.on_cancel = function (pos) { 54 | return false; 55 | }; 56 | ; 57 | AbstractTool.prototype.on_button_down = function (pos, event) { 58 | }; 59 | ; 60 | AbstractTool.prototype.on_button_up = function (pos, event) { 61 | }; 62 | ; 63 | AbstractTool.prototype.on_move = function (pos) { 64 | }; 65 | ; 66 | AbstractTool.prototype.on_drag = function (pos) { 67 | return false; 68 | }; 69 | ; 70 | AbstractTool.prototype.on_wheel = function (pos, wheelDeltaX, wheelDeltaY) { 71 | return false; 72 | }; 73 | ; 74 | AbstractTool.prototype.on_key_down = function (event) { 75 | return false; 76 | }; 77 | ; 78 | AbstractTool.prototype.on_entity_mouse_in = function (entity) { 79 | }; 80 | ; 81 | AbstractTool.prototype.on_entity_mouse_out = function (entity) { 82 | }; 83 | ; 84 | return AbstractTool; 85 | })(); 86 | labelling_tool.AbstractTool = AbstractTool; 87 | })(labelling_tool || (labelling_tool = {})); 88 | //# sourceMappingURL=abstract_tool.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/abstract_tool.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | /// 29 | /// 30 | 31 | module labelling_tool { 32 | /* 33 | Abstract tool 34 | */ 35 | export class AbstractTool{ 36 | _view: RootLabelView; 37 | 38 | constructor(view: RootLabelView) { 39 | this._view = view; 40 | 41 | } 42 | 43 | on_init() { 44 | }; 45 | 46 | on_shutdown() { 47 | }; 48 | 49 | on_switch_in(pos: Vector2) { 50 | }; 51 | 52 | on_switch_out(pos: Vector2) { 53 | }; 54 | 55 | on_left_click(pos: Vector2, event: any) { 56 | }; 57 | 58 | on_cancel(pos: Vector2): boolean { 59 | return false; 60 | }; 61 | 62 | on_button_down(pos: Vector2, event: any) { 63 | }; 64 | 65 | on_button_up(pos: Vector2, event: any) { 66 | }; 67 | 68 | on_move(pos: Vector2) { 69 | }; 70 | 71 | on_drag(pos: Vector2): boolean { 72 | return false; 73 | }; 74 | 75 | on_wheel(pos: Vector2, wheelDeltaX: number, wheelDeltaY: number): boolean { 76 | return false; 77 | }; 78 | 79 | on_key_down(event: any): boolean { 80 | return false; 81 | }; 82 | 83 | on_entity_mouse_in(entity: AbstractLabelEntity) { 84 | }; 85 | 86 | on_entity_mouse_out(entity: AbstractLabelEntity) { 87 | }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/box_label.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | var __extends = (this && this.__extends) || function (d, b) { 28 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 29 | function __() { this.constructor = d; } 30 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 31 | }; 32 | /// 33 | /// 34 | /// 35 | /// 36 | var labelling_tool; 37 | (function (labelling_tool) { 38 | function new_BoxLabelModel(centre, size) { 39 | return { label_type: 'box', label_class: null, centre: centre, size: size }; 40 | } 41 | function BoxLabel_box(label) { 42 | var lower = { x: label.centre.x - label.size.x * 0.5, y: label.centre.y - label.size.y * 0.5 }; 43 | var upper = { x: label.centre.x + label.size.x * 0.5, y: label.centre.y + label.size.y * 0.5 }; 44 | return new labelling_tool.AABox(lower, upper); 45 | } 46 | /* 47 | Box label entity 48 | */ 49 | var BoxLabelEntity = (function (_super) { 50 | __extends(BoxLabelEntity, _super); 51 | function BoxLabelEntity(view, model) { 52 | _super.call(this, view, model); 53 | } 54 | BoxLabelEntity.prototype.attach = function () { 55 | _super.prototype.attach.call(this); 56 | this._rect = this.root_view.world.append("rect") 57 | .attr("x", 0).attr("y", 0) 58 | .attr("width", 0).attr("height", 0); 59 | this.update(); 60 | var self = this; 61 | this._rect.on("mouseover", function () { 62 | self._on_mouse_over_event(); 63 | }).on("mouseout", function () { 64 | self._on_mouse_out_event(); 65 | }); 66 | this._update_style(); 67 | }; 68 | ; 69 | BoxLabelEntity.prototype.detach = function () { 70 | this._rect.remove(); 71 | this._rect = null; 72 | _super.prototype.detach.call(this); 73 | }; 74 | BoxLabelEntity.prototype._on_mouse_over_event = function () { 75 | for (var i = 0; i < this._event_listeners.length; i++) { 76 | this._event_listeners[i].on_mouse_in(this); 77 | } 78 | }; 79 | BoxLabelEntity.prototype._on_mouse_out_event = function () { 80 | for (var i = 0; i < this._event_listeners.length; i++) { 81 | this._event_listeners[i].on_mouse_out(this); 82 | } 83 | }; 84 | BoxLabelEntity.prototype.update = function () { 85 | var box = BoxLabel_box(this.model); 86 | var size = box.size(); 87 | this._rect 88 | .attr('x', box.lower.x).attr('y', box.lower.y) 89 | .attr('width', size.x).attr('height', size.y); 90 | }; 91 | BoxLabelEntity.prototype.commit = function () { 92 | this.root_view.commit_model(this.model); 93 | }; 94 | BoxLabelEntity.prototype._update_style = function () { 95 | if (this._attached) { 96 | var stroke_colour = this._outline_colour(); 97 | if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.HIDDEN) { 98 | this._rect.attr("visibility", "hidden"); 99 | } 100 | else if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.FAINT) { 101 | stroke_colour = stroke_colour.with_alpha(0.2); 102 | this._rect.attr("style", "fill:none;stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 103 | this._rect.attr("visibility", "visible"); 104 | } 105 | else if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.FULL) { 106 | var circle_fill_colour = this.root_view.view.colour_for_label_class(this.model.label_class); 107 | if (this._hover) { 108 | circle_fill_colour = circle_fill_colour.lighten(0.4); 109 | } 110 | circle_fill_colour = circle_fill_colour.with_alpha(0.35); 111 | stroke_colour = stroke_colour.with_alpha(0.5); 112 | this._rect.attr("style", "fill:" + circle_fill_colour.to_rgba_string() + ";stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 113 | this._rect.attr("visibility", "visible"); 114 | } 115 | } 116 | }; 117 | BoxLabelEntity.prototype.compute_centroid = function () { 118 | return this.model.centre; 119 | }; 120 | ; 121 | BoxLabelEntity.prototype.compute_bounding_box = function () { 122 | return BoxLabel_box(this.model); 123 | }; 124 | ; 125 | BoxLabelEntity.prototype.contains_pointer_position = function (point) { 126 | return this.compute_bounding_box().contains_point(point); 127 | }; 128 | BoxLabelEntity.prototype.distance_to_point = function (point) { 129 | return BoxLabel_box(this.model).distance_to(point); 130 | }; 131 | return BoxLabelEntity; 132 | })(labelling_tool.AbstractLabelEntity); 133 | labelling_tool.BoxLabelEntity = BoxLabelEntity; 134 | labelling_tool.register_entity_factory('box', function (root_view, model) { 135 | return new BoxLabelEntity(root_view, model); 136 | }); 137 | /* 138 | Draw box tool 139 | */ 140 | var DrawBoxTool = (function (_super) { 141 | __extends(DrawBoxTool, _super); 142 | function DrawBoxTool(view, entity) { 143 | _super.call(this, view); 144 | this.entity = entity; 145 | this._start_point = null; 146 | this._current_point = null; 147 | } 148 | DrawBoxTool.prototype.on_init = function () { 149 | }; 150 | ; 151 | DrawBoxTool.prototype.on_shutdown = function () { 152 | }; 153 | ; 154 | DrawBoxTool.prototype.on_switch_in = function (pos) { 155 | if (this._start_point !== null) { 156 | this._current_point = pos; 157 | this.update_box(); 158 | } 159 | }; 160 | ; 161 | DrawBoxTool.prototype.on_switch_out = function (pos) { 162 | if (this._start_point !== null) { 163 | this._current_point = null; 164 | this.update_box(); 165 | } 166 | }; 167 | ; 168 | DrawBoxTool.prototype.on_cancel = function (pos) { 169 | if (this.entity !== null) { 170 | if (this._start_point !== null) { 171 | this.destroy_entity(); 172 | this._start_point = null; 173 | } 174 | } 175 | else { 176 | this._view.unselect_all_entities(); 177 | this._view.view.set_current_tool(new labelling_tool.SelectEntityTool(this._view)); 178 | } 179 | return true; 180 | }; 181 | ; 182 | DrawBoxTool.prototype.on_left_click = function (pos, event) { 183 | if (this._start_point === null) { 184 | this._start_point = pos; 185 | this._current_point = pos; 186 | this.create_entity(pos); 187 | } 188 | else { 189 | this._current_point = pos; 190 | this.update_box(); 191 | this.entity.commit(); 192 | this._start_point = null; 193 | this._current_point = null; 194 | } 195 | }; 196 | ; 197 | DrawBoxTool.prototype.on_move = function (pos) { 198 | if (this._start_point !== null) { 199 | this._current_point = pos; 200 | this.update_box(); 201 | } 202 | }; 203 | ; 204 | DrawBoxTool.prototype.create_entity = function (pos) { 205 | var model = new_BoxLabelModel(pos, { x: 0.0, y: 0.0 }); 206 | var entity = this._view.get_or_create_entity_for_model(model); 207 | this.entity = entity; 208 | // Freeze to prevent this temporary change from being sent to the backend 209 | this._view.view.freeze(); 210 | this._view.add_child(entity); 211 | this._view.select_entity(entity, false, false); 212 | this._view.view.thaw(); 213 | }; 214 | ; 215 | DrawBoxTool.prototype.destroy_entity = function () { 216 | // Freeze to prevent this temporary change from being sent to the backend 217 | this._view.view.freeze(); 218 | this.entity.destroy(); 219 | this.entity = null; 220 | this._view.view.thaw(); 221 | }; 222 | ; 223 | DrawBoxTool.prototype.update_box = function () { 224 | if (this.entity !== null) { 225 | var box = null; 226 | if (this._start_point !== null) { 227 | if (this._current_point !== null) { 228 | box = labelling_tool.AABox_from_points([this._start_point, this._current_point]); 229 | } 230 | else { 231 | box = new labelling_tool.AABox(this._start_point, this._start_point); 232 | } 233 | } 234 | this.entity.model.centre = box.centre(); 235 | this.entity.model.size = box.size(); 236 | this.entity.update(); 237 | } 238 | }; 239 | ; 240 | return DrawBoxTool; 241 | })(labelling_tool.AbstractTool); 242 | labelling_tool.DrawBoxTool = DrawBoxTool; 243 | })(labelling_tool || (labelling_tool = {})); 244 | //# sourceMappingURL=box_label.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/box_label.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | 33 | module labelling_tool { 34 | /* 35 | Box label model 36 | */ 37 | interface BoxLabelModel extends AbstractLabelModel { 38 | centre: Vector2; 39 | size: Vector2; 40 | } 41 | 42 | function new_BoxLabelModel(centre: Vector2, size: Vector2): BoxLabelModel { 43 | return {label_type: 'box', label_class: null, centre: centre, size: size}; 44 | } 45 | 46 | function BoxLabel_box(label: BoxLabelModel): AABox { 47 | var lower = {x: label.centre.x - label.size.x*0.5, y: label.centre.y - label.size.y*0.5}; 48 | var upper = {x: label.centre.x + label.size.x*0.5, y: label.centre.y + label.size.y*0.5}; 49 | return new AABox(lower, upper); 50 | } 51 | 52 | 53 | /* 54 | Box label entity 55 | */ 56 | export class BoxLabelEntity extends AbstractLabelEntity { 57 | _rect: any; 58 | 59 | 60 | constructor(view: RootLabelView, model: BoxLabelModel) { 61 | super(view, model); 62 | } 63 | 64 | 65 | attach() { 66 | super.attach(); 67 | 68 | this._rect = this.root_view.world.append("rect") 69 | .attr("x", 0).attr("y", 0) 70 | .attr("width", 0).attr("height", 0); 71 | 72 | this.update(); 73 | 74 | var self = this; 75 | this._rect.on("mouseover", function() { 76 | self._on_mouse_over_event(); 77 | }).on("mouseout", function() { 78 | self._on_mouse_out_event(); 79 | }); 80 | 81 | 82 | this._update_style(); 83 | }; 84 | 85 | detach() { 86 | this._rect.remove(); 87 | this._rect = null; 88 | super.detach(); 89 | } 90 | 91 | 92 | _on_mouse_over_event() { 93 | for (var i = 0; i < this._event_listeners.length; i++) { 94 | this._event_listeners[i].on_mouse_in(this); 95 | } 96 | } 97 | 98 | _on_mouse_out_event() { 99 | for (var i = 0; i < this._event_listeners.length; i++) { 100 | this._event_listeners[i].on_mouse_out(this); 101 | } 102 | } 103 | 104 | 105 | 106 | update() { 107 | var box = BoxLabel_box(this.model); 108 | var size = box.size(); 109 | 110 | this._rect 111 | .attr('x', box.lower.x).attr('y', box.lower.y) 112 | .attr('width', size.x).attr('height', size.y); 113 | } 114 | 115 | commit() { 116 | this.root_view.commit_model(this.model); 117 | } 118 | 119 | _update_style() { 120 | if (this._attached) { 121 | var stroke_colour: Colour4 = this._outline_colour(); 122 | 123 | if (this.root_view.view.label_visibility == LabelVisibility.HIDDEN) { 124 | this._rect.attr("visibility", "hidden"); 125 | } 126 | else if (this.root_view.view.label_visibility == LabelVisibility.FAINT) { 127 | stroke_colour = stroke_colour.with_alpha(0.2); 128 | this._rect.attr("style", "fill:none;stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 129 | this._rect.attr("visibility", "visible"); 130 | } 131 | else if (this.root_view.view.label_visibility == LabelVisibility.FULL) { 132 | var circle_fill_colour = this.root_view.view.colour_for_label_class(this.model.label_class); 133 | if (this._hover) { 134 | circle_fill_colour = circle_fill_colour.lighten(0.4); 135 | } 136 | circle_fill_colour = circle_fill_colour.with_alpha(0.35); 137 | 138 | stroke_colour = stroke_colour.with_alpha(0.5); 139 | 140 | this._rect.attr("style", "fill:" + circle_fill_colour.to_rgba_string() + ";stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 141 | this._rect.attr("visibility", "visible"); 142 | } 143 | } 144 | } 145 | 146 | compute_centroid(): Vector2 { 147 | return this.model.centre; 148 | }; 149 | 150 | compute_bounding_box(): AABox { 151 | return BoxLabel_box(this.model); 152 | }; 153 | 154 | contains_pointer_position(point: Vector2): boolean { 155 | return this.compute_bounding_box().contains_point(point); 156 | } 157 | 158 | distance_to_point(point: Vector2): number { 159 | return BoxLabel_box(this.model).distance_to(point); 160 | } 161 | } 162 | 163 | 164 | register_entity_factory('box', (root_view: RootLabelView, model: AbstractLabelModel) => { 165 | return new BoxLabelEntity(root_view, model as BoxLabelModel); 166 | }); 167 | 168 | 169 | /* 170 | Draw box tool 171 | */ 172 | export class DrawBoxTool extends AbstractTool { 173 | entity: BoxLabelEntity; 174 | _start_point: Vector2; 175 | _current_point: Vector2; 176 | 177 | constructor(view: RootLabelView, entity: BoxLabelEntity) { 178 | super(view); 179 | this.entity = entity; 180 | this._start_point = null; 181 | this._current_point = null; 182 | } 183 | 184 | on_init() { 185 | }; 186 | 187 | on_shutdown() { 188 | }; 189 | 190 | on_switch_in(pos: Vector2) { 191 | if (this._start_point !== null) { 192 | this._current_point = pos; 193 | this.update_box(); 194 | } 195 | }; 196 | 197 | on_switch_out(pos: Vector2) { 198 | if (this._start_point !== null) { 199 | this._current_point = null; 200 | this.update_box(); 201 | } 202 | }; 203 | 204 | on_cancel(pos: Vector2): boolean { 205 | if (this.entity !== null) { 206 | if (this._start_point !== null) { 207 | this.destroy_entity(); 208 | this._start_point = null; 209 | } 210 | } 211 | else { 212 | this._view.unselect_all_entities(); 213 | this._view.view.set_current_tool(new SelectEntityTool(this._view)); 214 | } 215 | return true; 216 | }; 217 | 218 | on_left_click(pos: Vector2, event: any) { 219 | if (this._start_point === null) { 220 | this._start_point = pos; 221 | this._current_point = pos; 222 | this.create_entity(pos); 223 | } 224 | else { 225 | this._current_point = pos; 226 | this.update_box(); 227 | this.entity.commit(); 228 | this._start_point = null; 229 | this._current_point = null; 230 | } 231 | }; 232 | 233 | on_move(pos: Vector2) { 234 | if (this._start_point !== null) { 235 | this._current_point = pos; 236 | this.update_box(); 237 | } 238 | }; 239 | 240 | 241 | 242 | create_entity(pos: Vector2) { 243 | var model = new_BoxLabelModel(pos, {x: 0.0, y: 0.0}); 244 | var entity = this._view.get_or_create_entity_for_model(model); 245 | this.entity = entity; 246 | // Freeze to prevent this temporary change from being sent to the backend 247 | this._view.view.freeze(); 248 | this._view.add_child(entity); 249 | this._view.select_entity(entity, false, false); 250 | this._view.view.thaw(); 251 | }; 252 | 253 | destroy_entity() { 254 | // Freeze to prevent this temporary change from being sent to the backend 255 | this._view.view.freeze(); 256 | this.entity.destroy(); 257 | this.entity = null; 258 | this._view.view.thaw(); 259 | }; 260 | 261 | update_box() { 262 | if (this.entity !== null) { 263 | var box: AABox = null; 264 | if (this._start_point !== null) { 265 | if (this._current_point !== null) { 266 | box = AABox_from_points([this._start_point, this._current_point]); 267 | } 268 | else { 269 | box = new AABox(this._start_point, this._start_point); 270 | } 271 | } 272 | this.entity.model.centre = box.centre(); 273 | this.entity.model.size = box.size(); 274 | this.entity.update(); 275 | } 276 | }; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/composite_label.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | var __extends = (this && this.__extends) || function (d, b) { 28 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 29 | function __() { this.constructor = d; } 30 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 31 | }; 32 | /// 33 | /// 34 | var labelling_tool; 35 | (function (labelling_tool) { 36 | function new_CompositeLabelModel() { 37 | return { label_type: 'composite', label_class: null, components: [] }; 38 | } 39 | labelling_tool.new_CompositeLabelModel = new_CompositeLabelModel; 40 | /* 41 | Composite label entity 42 | */ 43 | var CompositeLabelEntity = (function (_super) { 44 | __extends(CompositeLabelEntity, _super); 45 | function CompositeLabelEntity(view, model) { 46 | _super.call(this, view, model); 47 | this._centroid = null; 48 | } 49 | CompositeLabelEntity.prototype.attach = function () { 50 | _super.prototype.attach.call(this); 51 | this.circle = this.root_view.world.append("circle") 52 | .attr('r', 8.0); 53 | this.central_circle = this.root_view.world.append("circle") 54 | .attr("pointer-events", "none") 55 | .attr('r', 4.0); 56 | this.shape_line = d3.svg.line() 57 | .x(function (d) { return d.x; }) 58 | .y(function (d) { return d.y; }) 59 | .interpolate("linear-closed"); 60 | this.connections_group = null; 61 | this.update(); 62 | var self = this; 63 | this.circle.on("mouseover", function () { 64 | self._on_mouse_over_event(); 65 | }).on("mouseout", function () { 66 | self._on_mouse_out_event(); 67 | }); 68 | this._update_style(); 69 | }; 70 | CompositeLabelEntity.prototype.detach = function () { 71 | this.circle.remove(); 72 | this.central_circle.remove(); 73 | this.connections_group.remove(); 74 | this.circle = null; 75 | this.central_circle = null; 76 | this.shape_line = null; 77 | this.connections_group = null; 78 | _super.prototype.detach.call(this); 79 | }; 80 | CompositeLabelEntity.prototype._on_mouse_over_event = function () { 81 | for (var i = 0; i < this._event_listeners.length; i++) { 82 | this._event_listeners[i].on_mouse_in(this); 83 | } 84 | }; 85 | CompositeLabelEntity.prototype._on_mouse_out_event = function () { 86 | for (var i = 0; i < this._event_listeners.length; i++) { 87 | this._event_listeners[i].on_mouse_out(this); 88 | } 89 | }; 90 | CompositeLabelEntity.prototype.update = function () { 91 | var component_centroids = this._compute_component_centroids(); 92 | this._centroid = labelling_tool.compute_centroid_of_points(component_centroids); 93 | this.circle 94 | .attr('cx', this._centroid.x) 95 | .attr('cy', this._centroid.y); 96 | this.central_circle 97 | .attr('cx', this._centroid.x) 98 | .attr('cy', this._centroid.y); 99 | if (this.connections_group !== null) { 100 | this.connections_group.remove(); 101 | this.connections_group = null; 102 | } 103 | this.connections_group = this.root_view.world.append("g"); 104 | for (var i = 0; i < component_centroids.length; i++) { 105 | this.connections_group.append("path") 106 | .attr("d", this.shape_line([this._centroid, component_centroids[i]])) 107 | .attr("stroke-width", 1) 108 | .attr("stroke-dasharray", "3, 3") 109 | .attr("style", "stroke:rgba(255,0,255,0.6);"); 110 | this.connections_group.append("circle") 111 | .attr("cx", component_centroids[i].x) 112 | .attr("cy", component_centroids[i].y) 113 | .attr("r", 3) 114 | .attr("stroke-width", 1) 115 | .attr("style", "stroke:rgba(255,0,255,0.6);fill: rgba(255,0,255,0.25);"); 116 | } 117 | }; 118 | CompositeLabelEntity.prototype.commit = function () { 119 | this.root_view.commit_model(this.model); 120 | }; 121 | CompositeLabelEntity.prototype._update_style = function () { 122 | if (this._attached) { 123 | var stroke_colour = this._outline_colour(); 124 | if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.FAINT) { 125 | stroke_colour = stroke_colour.with_alpha(0.2); 126 | this.circle.attr("style", "fill:none;stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 127 | this.connections_group.selectAll("path") 128 | .attr("style", "stroke:rgba(255,0,255,0.2);"); 129 | this.connections_group.selectAll("circle") 130 | .attr("style", "stroke:rgba(255,0,255,0.2);fill: none;"); 131 | } 132 | else if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.FULL) { 133 | var circle_fill_colour = new labelling_tool.Colour4(255, 128, 255, 1.0); 134 | var central_circle_fill_colour = this.root_view.view.colour_for_label_class(this.model.label_class); 135 | var connection_fill_colour = new labelling_tool.Colour4(255, 0, 255, 1.0); 136 | var connection_stroke_colour = new labelling_tool.Colour4(255, 0, 255, 1.0); 137 | if (this._hover) { 138 | circle_fill_colour = circle_fill_colour.lighten(0.4); 139 | central_circle_fill_colour = central_circle_fill_colour.lighten(0.4); 140 | connection_fill_colour = connection_fill_colour.lighten(0.4); 141 | connection_stroke_colour = connection_stroke_colour.lighten(0.4); 142 | } 143 | circle_fill_colour = circle_fill_colour.with_alpha(0.35); 144 | central_circle_fill_colour = central_circle_fill_colour.with_alpha(0.35); 145 | connection_fill_colour = connection_fill_colour.with_alpha(0.25); 146 | connection_stroke_colour = connection_stroke_colour.with_alpha(0.6); 147 | stroke_colour = stroke_colour.with_alpha(0.5); 148 | this.circle.attr("style", "fill:" + circle_fill_colour.to_rgba_string() + ";stroke:" + connection_stroke_colour.to_rgba_string() + ";stroke-width:1"); 149 | this.central_circle.attr("style", "fill:" + central_circle_fill_colour.to_rgba_string() + ";stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 150 | this.connections_group.selectAll("path") 151 | .attr("style", "stroke:rgba(255,0,255,0.6);"); 152 | this.connections_group.selectAll("circle") 153 | .attr("style", "stroke:" + connection_stroke_colour.to_rgba_string() + ";fill:" + connection_fill_colour.to_rgba_string() + ";"); 154 | } 155 | } 156 | }; 157 | CompositeLabelEntity.prototype._compute_component_centroids = function () { 158 | var component_centroids = []; 159 | for (var i = 0; i < this.model.components.length; i++) { 160 | var model_id = this.model.components[i]; 161 | var entity = this.root_view.get_entity_for_model_id(model_id); 162 | var centroid = entity.compute_centroid(); 163 | component_centroids.push(centroid); 164 | } 165 | return component_centroids; 166 | }; 167 | CompositeLabelEntity.prototype.compute_centroid = function () { 168 | return this._centroid; 169 | }; 170 | ; 171 | CompositeLabelEntity.prototype.compute_bounding_box = function () { 172 | var centre = this.compute_centroid(); 173 | return new labelling_tool.AABox({ x: centre.x - 1, y: centre.y - 1 }, { x: centre.x + 1, y: centre.y + 1 }); 174 | }; 175 | CompositeLabelEntity.prototype.contains_pointer_position = function (point) { 176 | return labelling_tool.compute_sqr_dist(point, this._centroid) <= (8.0 * 8.0); 177 | }; 178 | CompositeLabelEntity.prototype.notify_model_destroyed = function (model_id) { 179 | var index = this.model.components.indexOf(model_id); 180 | if (index !== -1) { 181 | // Remove the model ID from the components array 182 | this.model.components = this.model.components.slice(0, index).concat(this.model.components.slice(index + 1)); 183 | this.update(); 184 | } 185 | }; 186 | return CompositeLabelEntity; 187 | })(labelling_tool.AbstractLabelEntity); 188 | labelling_tool.CompositeLabelEntity = CompositeLabelEntity; 189 | labelling_tool.register_entity_factory('composite', function (root_view, model) { 190 | return new CompositeLabelEntity(root_view, model); 191 | }); 192 | })(labelling_tool || (labelling_tool = {})); 193 | //# sourceMappingURL=composite_label.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/composite_label.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | /// 29 | /// 30 | 31 | module labelling_tool { 32 | /* 33 | Composite label model 34 | */ 35 | export interface CompositeLabelModel extends AbstractLabelModel { 36 | components: number[]; 37 | } 38 | 39 | export function new_CompositeLabelModel(): CompositeLabelModel { 40 | return {label_type: 'composite', label_class: null, components: []}; 41 | } 42 | 43 | 44 | 45 | /* 46 | Composite label entity 47 | */ 48 | export class CompositeLabelEntity extends AbstractLabelEntity { 49 | circle: any; 50 | central_circle: any; 51 | shape_line: any; 52 | connections_group: any; 53 | _centroid: Vector2; 54 | 55 | constructor(view: RootLabelView, model: CompositeLabelModel) { 56 | super(view, model); 57 | this._centroid = null; 58 | } 59 | 60 | attach() { 61 | super.attach(); 62 | this.circle = this.root_view.world.append("circle") 63 | .attr('r', 8.0); 64 | 65 | this.central_circle = this.root_view.world.append("circle") 66 | .attr("pointer-events", "none") 67 | .attr('r', 4.0); 68 | 69 | this.shape_line = d3.svg.line() 70 | .x(function (d: any) { return d.x; }) 71 | .y(function (d: any) { return d.y; }) 72 | .interpolate("linear-closed"); 73 | 74 | this.connections_group = null; 75 | 76 | this.update(); 77 | 78 | var self = this; 79 | this.circle.on("mouseover", function() { 80 | self._on_mouse_over_event(); 81 | }).on("mouseout", function() { 82 | self._on_mouse_out_event(); 83 | }); 84 | 85 | 86 | this._update_style(); 87 | } 88 | 89 | detach() { 90 | this.circle.remove(); 91 | this.central_circle.remove(); 92 | this.connections_group.remove(); 93 | this.circle = null; 94 | this.central_circle = null; 95 | this.shape_line = null; 96 | this.connections_group = null; 97 | super.detach(); 98 | } 99 | 100 | 101 | _on_mouse_over_event() { 102 | for (var i = 0; i < this._event_listeners.length; i++) { 103 | this._event_listeners[i].on_mouse_in(this); 104 | } 105 | } 106 | 107 | _on_mouse_out_event() { 108 | for (var i = 0; i < this._event_listeners.length; i++) { 109 | this._event_listeners[i].on_mouse_out(this); 110 | } 111 | } 112 | 113 | 114 | update() { 115 | var component_centroids = this._compute_component_centroids(); 116 | this._centroid = compute_centroid_of_points(component_centroids); 117 | 118 | this.circle 119 | .attr('cx', this._centroid.x) 120 | .attr('cy', this._centroid.y); 121 | 122 | this.central_circle 123 | .attr('cx', this._centroid.x) 124 | .attr('cy', this._centroid.y); 125 | 126 | if (this.connections_group !== null) { 127 | this.connections_group.remove(); 128 | this.connections_group = null; 129 | } 130 | 131 | this.connections_group = this.root_view.world.append("g"); 132 | for (var i = 0; i < component_centroids.length; i++) { 133 | this.connections_group.append("path") 134 | .attr("d", this.shape_line([this._centroid, component_centroids[i]])) 135 | .attr("stroke-width", 1) 136 | .attr("stroke-dasharray", "3, 3") 137 | .attr("style", "stroke:rgba(255,0,255,0.6);"); 138 | this.connections_group.append("circle") 139 | .attr("cx", component_centroids[i].x) 140 | .attr("cy", component_centroids[i].y) 141 | .attr("r", 3) 142 | .attr("stroke-width", 1) 143 | .attr("style", "stroke:rgba(255,0,255,0.6);fill: rgba(255,0,255,0.25);"); 144 | } 145 | } 146 | 147 | commit() { 148 | this.root_view.commit_model(this.model); 149 | } 150 | 151 | 152 | _update_style() { 153 | if (this._attached) { 154 | var stroke_colour: Colour4 = this._outline_colour(); 155 | 156 | if (this.root_view.view.label_visibility == LabelVisibility.FAINT) { 157 | stroke_colour = stroke_colour.with_alpha(0.2); 158 | this.circle.attr("style", "fill:none;stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 159 | 160 | this.connections_group.selectAll("path") 161 | .attr("style", "stroke:rgba(255,0,255,0.2);"); 162 | this.connections_group.selectAll("circle") 163 | .attr("style", "stroke:rgba(255,0,255,0.2);fill: none;"); 164 | } 165 | else if (this.root_view.view.label_visibility == LabelVisibility.FULL) { 166 | var circle_fill_colour = new Colour4(255, 128, 255, 1.0); 167 | var central_circle_fill_colour = this.root_view.view.colour_for_label_class(this.model.label_class); 168 | var connection_fill_colour = new Colour4(255, 0, 255, 1.0); 169 | var connection_stroke_colour = new Colour4(255, 0, 255, 1.0); 170 | if (this._hover) { 171 | circle_fill_colour = circle_fill_colour.lighten(0.4); 172 | central_circle_fill_colour = central_circle_fill_colour.lighten(0.4); 173 | connection_fill_colour = connection_fill_colour.lighten(0.4); 174 | connection_stroke_colour = connection_stroke_colour.lighten(0.4); 175 | } 176 | circle_fill_colour = circle_fill_colour.with_alpha(0.35); 177 | central_circle_fill_colour = central_circle_fill_colour.with_alpha(0.35); 178 | connection_fill_colour = connection_fill_colour.with_alpha(0.25); 179 | connection_stroke_colour = connection_stroke_colour.with_alpha(0.6); 180 | 181 | stroke_colour = stroke_colour.with_alpha(0.5); 182 | 183 | this.circle.attr("style", "fill:" + circle_fill_colour.to_rgba_string() + ";stroke:" + connection_stroke_colour.to_rgba_string() + ";stroke-width:1"); 184 | this.central_circle.attr("style", "fill:" + central_circle_fill_colour.to_rgba_string() + ";stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 185 | 186 | this.connections_group.selectAll("path") 187 | .attr("style", "stroke:rgba(255,0,255,0.6);"); 188 | this.connections_group.selectAll("circle") 189 | .attr("style", "stroke:"+connection_stroke_colour.to_rgba_string()+";fill:"+connection_fill_colour.to_rgba_string()+";"); 190 | } 191 | } 192 | } 193 | 194 | _compute_component_centroids(): Vector2[] { 195 | var component_centroids: Vector2[] = []; 196 | for (var i = 0; i < this.model.components.length; i++) { 197 | var model_id = this.model.components[i]; 198 | var entity = this.root_view.get_entity_for_model_id(model_id); 199 | var centroid = entity.compute_centroid(); 200 | component_centroids.push(centroid); 201 | } 202 | return component_centroids; 203 | } 204 | 205 | compute_centroid(): Vector2 { 206 | return this._centroid; 207 | }; 208 | 209 | compute_bounding_box(): AABox { 210 | var centre = this.compute_centroid(); 211 | return new AABox({x: centre.x - 1, y: centre.y - 1}, {x: centre.x + 1, y: centre.y + 1}); 212 | } 213 | 214 | contains_pointer_position(point: Vector2): boolean { 215 | return compute_sqr_dist(point, this._centroid) <= (8.0 * 8.0); 216 | } 217 | 218 | notify_model_destroyed(model_id: number) { 219 | var index = this.model.components.indexOf(model_id); 220 | 221 | if (index !== -1) { 222 | // Remove the model ID from the components array 223 | this.model.components = this.model.components.slice(0, index).concat(this.model.components.slice(index+1)); 224 | this.update(); 225 | } 226 | } 227 | } 228 | 229 | 230 | register_entity_factory('composite', (root_view: RootLabelView, model: AbstractLabelModel) => { 231 | return new CompositeLabelEntity(root_view, model as CompositeLabelModel); 232 | }); 233 | } 234 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/label_class.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | /// 28 | var labelling_tool; 29 | (function (labelling_tool) { 30 | var LabelClass = (function () { 31 | function LabelClass(j) { 32 | this.name = j.name; 33 | this.human_name = j.human_name; 34 | this.colour = labelling_tool.Colour4.from_rgb_a(j.colour, 1.0); 35 | } 36 | return LabelClass; 37 | })(); 38 | labelling_tool.LabelClass = LabelClass; 39 | })(labelling_tool || (labelling_tool = {})); 40 | //# sourceMappingURL=label_class.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/label_class.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | /// 29 | 30 | module labelling_tool { 31 | /* 32 | Label class 33 | */ 34 | export interface LabelClassJSON { 35 | name: string; 36 | human_name: string; 37 | colour: number[]; 38 | } 39 | 40 | 41 | export class LabelClass { 42 | name: string; 43 | human_name: string; 44 | colour: Colour4; 45 | 46 | constructor(j: LabelClassJSON) { 47 | this.name = j.name; 48 | this.human_name = j.human_name; 49 | this.colour = Colour4.from_rgb_a(j.colour, 1.0); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/math_primitives.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | var labelling_tool; 28 | (function (labelling_tool) { 29 | function ensure_config_option_exists(x, flag_name, default_value) { 30 | var v = x[flag_name]; 31 | if (v === undefined) { 32 | x[flag_name] = default_value; 33 | } 34 | return x[flag_name]; 35 | } 36 | labelling_tool.ensure_config_option_exists = ensure_config_option_exists; 37 | function compute_centroid_of_points(vertices) { 38 | var sum = [0.0, 0.0]; 39 | var N = vertices.length; 40 | if (N === 0) { 41 | return { x: 0, y: 0 }; 42 | } 43 | else { 44 | for (var i = 0; i < N; i++) { 45 | var vtx = vertices[i]; 46 | sum[0] += vtx.x; 47 | sum[1] += vtx.y; 48 | } 49 | var scale = 1.0 / N; 50 | return { x: sum[0] * scale, y: sum[1] * scale }; 51 | } 52 | } 53 | labelling_tool.compute_centroid_of_points = compute_centroid_of_points; 54 | function compute_sqr_length(v) { 55 | return v.x * v.x + v.y * v.y; 56 | } 57 | labelling_tool.compute_sqr_length = compute_sqr_length; 58 | function compute_sqr_dist(a, b) { 59 | var dx = b.x - a.x, dy = b.y - a.y; 60 | return dx * dx + dy * dy; 61 | } 62 | labelling_tool.compute_sqr_dist = compute_sqr_dist; 63 | /* 64 | RGBA colour 65 | */ 66 | var Colour4 = (function () { 67 | function Colour4(r, g, b, a) { 68 | this.r = r; 69 | this.g = g; 70 | this.b = b; 71 | this.a = a; 72 | } 73 | Colour4.prototype.lerp = function (x, t) { 74 | var s = 1.0 - t; 75 | return new Colour4(Math.round(this.r * s + x.r * t), Math.round(this.g * s + x.g * t), Math.round(this.b * s + x.b * t), this.a * s + x.a * t); 76 | }; 77 | Colour4.prototype.lighten = function (amount) { 78 | return this.lerp(Colour4.WHITE, amount); 79 | }; 80 | Colour4.prototype.with_alpha = function (alpha) { 81 | return new Colour4(this.r, this.g, this.b, alpha); 82 | }; 83 | Colour4.prototype.to_rgba_string = function () { 84 | return 'rgba(' + this.r + ',' + this.g + ',' + this.b + ',' + this.a + ')'; 85 | }; 86 | Colour4.from_rgb_a = function (rgb, alpha) { 87 | return new Colour4(rgb[0], rgb[1], rgb[2], alpha); 88 | }; 89 | Colour4.BLACK = new Colour4(0, 0, 0, 1.0); 90 | Colour4.WHITE = new Colour4(255, 255, 255, 1.0); 91 | return Colour4; 92 | })(); 93 | labelling_tool.Colour4 = Colour4; 94 | /* 95 | Axis-aligned box 96 | */ 97 | var AABox = (function () { 98 | function AABox(lower, upper) { 99 | this.lower = lower; 100 | this.upper = upper; 101 | } 102 | AABox.prototype.contains_point = function (point) { 103 | return point.x >= this.lower.x && point.x <= this.upper.x && 104 | point.y >= this.lower.y && point.y <= this.upper.y; 105 | }; 106 | AABox.prototype.centre = function () { 107 | return { x: (this.lower.x + this.upper.x) * 0.5, 108 | y: (this.lower.y + this.upper.y) * 0.5 }; 109 | }; 110 | AABox.prototype.size = function () { 111 | return { x: this.upper.x - this.lower.x, 112 | y: this.upper.y - this.lower.y }; 113 | }; 114 | AABox.prototype.closest_point_to = function (p) { 115 | var x = Math.max(this.lower.x, Math.min(this.upper.x, p.x)); 116 | var y = Math.max(this.lower.y, Math.min(this.upper.y, p.y)); 117 | return { x: x, y: y }; 118 | }; 119 | AABox.prototype.sqr_distance_to = function (p) { 120 | var c = this.closest_point_to(p); 121 | var dx = c.x - p.x, dy = c.y - p.y; 122 | return dx * dx + dy * dy; 123 | }; 124 | AABox.prototype.distance_to = function (p) { 125 | return Math.sqrt(this.sqr_distance_to(p)); 126 | }; 127 | return AABox; 128 | })(); 129 | labelling_tool.AABox = AABox; 130 | function AABox_from_points(array_of_points) { 131 | if (array_of_points.length > 0) { 132 | var first = array_of_points[0]; 133 | var lower = { x: first.x, y: first.y }; 134 | var upper = { x: first.x, y: first.y }; 135 | for (var i = 1; i < array_of_points.length; i++) { 136 | var p = array_of_points[i]; 137 | lower.x = Math.min(lower.x, p.x); 138 | lower.y = Math.min(lower.y, p.y); 139 | upper.x = Math.max(upper.x, p.x); 140 | upper.y = Math.max(upper.y, p.y); 141 | } 142 | return new AABox(lower, upper); 143 | } 144 | else { 145 | return new AABox({ x: 0, y: 0 }, { x: 0, y: 0 }); 146 | } 147 | } 148 | labelling_tool.AABox_from_points = AABox_from_points; 149 | function AABox_from_aaboxes(array_of_boxes) { 150 | if (array_of_boxes.length > 0) { 151 | var first = array_of_boxes[0]; 152 | var result = new AABox({ x: first.lower.x, y: first.lower.y }, { x: first.upper.x, y: first.upper.y }); 153 | for (var i = 1; i < array_of_boxes.length; i++) { 154 | var box = array_of_boxes[i]; 155 | result.lower.x = Math.min(result.lower.x, box.lower.x); 156 | result.lower.y = Math.min(result.lower.y, box.lower.y); 157 | result.upper.x = Math.max(result.upper.x, box.upper.x); 158 | result.upper.y = Math.max(result.upper.y, box.upper.y); 159 | } 160 | return result; 161 | } 162 | else { 163 | return new AABox({ x: 1, y: 1 }, { x: -1, y: -1 }); 164 | } 165 | } 166 | labelling_tool.AABox_from_aaboxes = AABox_from_aaboxes; 167 | })(labelling_tool || (labelling_tool = {})); 168 | //# sourceMappingURL=math_primitives.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/math_primitives.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | module labelling_tool { 29 | /* 30 | 2D Vector 31 | */ 32 | export interface Vector2 { 33 | x: number; 34 | y: number; 35 | } 36 | 37 | export function ensure_config_option_exists(x: any, flag_name: string, default_value: any) { 38 | var v = x[flag_name]; 39 | if (v === undefined) { 40 | x[flag_name] = default_value; 41 | } 42 | return x[flag_name]; 43 | } 44 | 45 | export function compute_centroid_of_points(vertices: Vector2[]): Vector2 { 46 | var sum = [0.0, 0.0]; 47 | var N = vertices.length; 48 | if (N === 0) { 49 | return {x: 0, y: 0}; 50 | } 51 | else { 52 | for (var i = 0; i < N; i++) { 53 | var vtx = vertices[i]; 54 | sum[0] += vtx.x; 55 | sum[1] += vtx.y; 56 | } 57 | var scale = 1.0 / N; 58 | return {x: sum[0] * scale, y: sum[1] * scale}; 59 | } 60 | } 61 | 62 | export function compute_sqr_length(v: Vector2) { 63 | return v.x * v.x + v.y * v.y; 64 | } 65 | 66 | export function compute_sqr_dist(a: Vector2, b: Vector2) { 67 | var dx = b.x - a.x, dy = b.y - a.y; 68 | return dx * dx + dy * dy; 69 | } 70 | 71 | 72 | /* 73 | RGBA colour 74 | */ 75 | export class Colour4 { 76 | r: number; 77 | g: number; 78 | b: number; 79 | a: number; 80 | 81 | constructor(r: number, g: number, b: number, a: number) { 82 | this.r = r; 83 | this.g = g; 84 | this.b = b; 85 | this.a = a; 86 | } 87 | 88 | lerp(x: Colour4, t: number) { 89 | let s = 1.0 - t; 90 | return new Colour4(Math.round(this.r*s + x.r*t), 91 | Math.round(this.g*s + x.g*t), 92 | Math.round(this.b*s + x.b*t), 93 | this.a*s + x.a*t); 94 | } 95 | 96 | lighten(amount: number): Colour4 { 97 | return this.lerp(Colour4.WHITE, amount); 98 | } 99 | 100 | with_alpha(alpha: number): Colour4 { 101 | return new Colour4(this.r, this.g, this.b, alpha); 102 | } 103 | 104 | to_rgba_string(): string { 105 | return 'rgba(' + this.r + ',' + this.g + ',' + this.b + ',' + this.a + ')'; 106 | } 107 | 108 | static from_rgb_a(rgb: number[], alpha: number): Colour4 { 109 | return new Colour4(rgb[0], rgb[1], rgb[2], alpha); 110 | } 111 | 112 | static BLACK: Colour4 = new Colour4(0, 0, 0, 1.0); 113 | static WHITE: Colour4 = new Colour4(255, 255, 255, 1.0); 114 | } 115 | 116 | 117 | 118 | /* 119 | Axis-aligned box 120 | */ 121 | export class AABox { 122 | lower: Vector2; 123 | upper: Vector2; 124 | 125 | constructor(lower: Vector2, upper: Vector2) { 126 | this.lower = lower; 127 | this.upper = upper; 128 | } 129 | 130 | contains_point(point: Vector2): boolean { 131 | return point.x >= this.lower.x && point.x <= this.upper.x && 132 | point.y >= this.lower.y && point.y <= this.upper.y; 133 | } 134 | 135 | centre(): Vector2 { 136 | return {x: (this.lower.x + this.upper.x) * 0.5, 137 | y: (this.lower.y + this.upper.y) * 0.5}; 138 | } 139 | 140 | size(): Vector2 { 141 | return {x: this.upper.x - this.lower.x, 142 | y: this.upper.y - this.lower.y}; 143 | } 144 | 145 | closest_point_to(p: Vector2): Vector2 { 146 | var x = Math.max(this.lower.x, Math.min(this.upper.x, p.x)); 147 | var y = Math.max(this.lower.y, Math.min(this.upper.y, p.y)); 148 | return {x: x, y: y}; 149 | } 150 | 151 | sqr_distance_to(p: Vector2): number { 152 | var c = this.closest_point_to(p); 153 | var dx: number = c.x - p.x, dy: number = c.y - p.y; 154 | return dx * dx + dy * dy; 155 | } 156 | 157 | distance_to(p: Vector2): number { 158 | return Math.sqrt(this.sqr_distance_to(p)); 159 | } 160 | } 161 | 162 | export function AABox_from_points(array_of_points: Vector2[]): AABox { 163 | if (array_of_points.length > 0) { 164 | var first = array_of_points[0]; 165 | var lower = {x: first.x, y: first.y}; 166 | var upper = {x: first.x, y: first.y}; 167 | for (var i = 1; i < array_of_points.length; i++) { 168 | var p = array_of_points[i]; 169 | lower.x = Math.min(lower.x, p.x); 170 | lower.y = Math.min(lower.y, p.y); 171 | upper.x = Math.max(upper.x, p.x); 172 | upper.y = Math.max(upper.y, p.y); 173 | } 174 | return new AABox(lower, upper); 175 | } 176 | else { 177 | return new AABox({x: 0, y: 0}, {x: 0, y: 0}); 178 | } 179 | } 180 | 181 | export function AABox_from_aaboxes(array_of_boxes: AABox[]): AABox { 182 | if (array_of_boxes.length > 0) { 183 | var first = array_of_boxes[0]; 184 | var result = new AABox({x: first.lower.x, y: first.lower.y}, 185 | {x: first.upper.x, y: first.upper.y}); 186 | for (var i = 1; i < array_of_boxes.length; i++) { 187 | var box = array_of_boxes[i]; 188 | result.lower.x = Math.min(result.lower.x, box.lower.x); 189 | result.lower.y = Math.min(result.lower.y, box.lower.y); 190 | result.upper.x = Math.max(result.upper.x, box.upper.x); 191 | result.upper.y = Math.max(result.upper.y, box.upper.y); 192 | } 193 | return result; 194 | } 195 | else { 196 | return new AABox({x: 1, y: 1}, {x: -1, y: -1}); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/object_id_table.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | var labelling_tool; 28 | (function (labelling_tool) { 29 | /* 30 | Object ID table 31 | */ 32 | var ObjectIDTable = (function () { 33 | function ObjectIDTable() { 34 | this._id_counter = 1; 35 | this._id_to_object = {}; 36 | } 37 | ObjectIDTable.prototype.get = function (id) { 38 | return this._id_to_object[id]; 39 | }; 40 | ObjectIDTable.prototype.register = function (obj) { 41 | var id; 42 | if ('object_id' in obj && obj.object_id !== null) { 43 | id = obj.object_id; 44 | this._id_counter = Math.max(this._id_counter, id + 1); 45 | this._id_to_object[id] = obj; 46 | } 47 | else { 48 | id = this._id_counter; 49 | this._id_counter += 1; 50 | this._id_to_object[id] = obj; 51 | obj.object_id = id; 52 | } 53 | }; 54 | ObjectIDTable.prototype.unregister = function (obj) { 55 | delete this._id_to_object[obj.object_id]; 56 | obj.object_id = null; 57 | }; 58 | ObjectIDTable.prototype.register_objects = function (object_array) { 59 | var obj, id, i; 60 | for (i = 0; i < object_array.length; i++) { 61 | obj = object_array[i]; 62 | if ('object_id' in obj && obj.object_id !== null) { 63 | id = obj.object_id; 64 | this._id_counter = Math.max(this._id_counter, id + 1); 65 | this._id_to_object[id] = obj; 66 | } 67 | } 68 | for (i = 0; i < object_array.length; i++) { 69 | obj = object_array[i]; 70 | if ('object_id' in obj && obj.object_id !== null) { 71 | } 72 | else { 73 | id = this._id_counter; 74 | this._id_counter += 1; 75 | this._id_to_object[id] = obj; 76 | obj.object_id = id; 77 | } 78 | } 79 | }; 80 | ObjectIDTable.get_id = function (x) { 81 | if ('object_id' in x && x.object_id !== null) { 82 | return x.object_id; 83 | } 84 | else { 85 | return null; 86 | } 87 | }; 88 | return ObjectIDTable; 89 | })(); 90 | labelling_tool.ObjectIDTable = ObjectIDTable; 91 | })(labelling_tool || (labelling_tool = {})); 92 | //# sourceMappingURL=object_id_table.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/object_id_table.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | module labelling_tool { 29 | /* 30 | Object ID table 31 | */ 32 | export class ObjectIDTable { 33 | _id_counter:number; 34 | _id_to_object: any; 35 | 36 | constructor() { 37 | this._id_counter = 1; 38 | this._id_to_object = {}; 39 | } 40 | 41 | get(id:number):any { 42 | return this._id_to_object[id]; 43 | } 44 | 45 | register(obj:any):void { 46 | var id:number; 47 | if ('object_id' in obj && obj.object_id !== null) { 48 | id = obj.object_id; 49 | this._id_counter = Math.max(this._id_counter, id + 1); 50 | this._id_to_object[id] = obj; 51 | } 52 | else { 53 | id = this._id_counter; 54 | this._id_counter += 1; 55 | this._id_to_object[id] = obj; 56 | obj.object_id = id; 57 | } 58 | } 59 | 60 | unregister(obj:any) { 61 | delete this._id_to_object[obj.object_id]; 62 | obj.object_id = null; 63 | } 64 | 65 | 66 | register_objects(object_array:any[]) { 67 | var obj:any, id:number, i:number; 68 | 69 | for (i = 0; i < object_array.length; i++) { 70 | obj = object_array[i]; 71 | if ('object_id' in obj && obj.object_id !== null) { 72 | id = obj.object_id; 73 | this._id_counter = Math.max(this._id_counter, id + 1); 74 | this._id_to_object[id] = obj; 75 | } 76 | } 77 | 78 | for (i = 0; i < object_array.length; i++) { 79 | obj = object_array[i]; 80 | 81 | if ('object_id' in obj && obj.object_id !== null) { 82 | 83 | } 84 | else { 85 | id = this._id_counter; 86 | this._id_counter += 1; 87 | this._id_to_object[id] = obj; 88 | obj.object_id = id; 89 | } 90 | } 91 | } 92 | 93 | static get_id(x: any) { 94 | if ('object_id' in x && x.object_id !== null) { 95 | return x.object_id; 96 | } 97 | else { 98 | return null; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/point_label.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | var __extends = (this && this.__extends) || function (d, b) { 28 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 29 | function __() { this.constructor = d; } 30 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 31 | }; 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | var labelling_tool; 38 | (function (labelling_tool) { 39 | function new_PointLabelModel(position) { 40 | return { label_type: 'point', label_class: null, position: position }; 41 | } 42 | /* 43 | Point label entity 44 | */ 45 | var PointLabelEntity = (function (_super) { 46 | __extends(PointLabelEntity, _super); 47 | function PointLabelEntity(view, model) { 48 | _super.call(this, view, model); 49 | } 50 | PointLabelEntity.prototype.attach = function () { 51 | _super.prototype.attach.call(this); 52 | this.circle = this.root_view.world.append("circle") 53 | .attr('r', 4.0); 54 | this.update(); 55 | var self = this; 56 | this.circle.on("mouseover", function () { 57 | self._on_mouse_over_event(); 58 | }).on("mouseout", function () { 59 | self._on_mouse_out_event(); 60 | }); 61 | this._update_style(); 62 | }; 63 | PointLabelEntity.prototype.detach = function () { 64 | this.circle.remove(); 65 | this.circle = null; 66 | _super.prototype.detach.call(this); 67 | }; 68 | PointLabelEntity.prototype._on_mouse_over_event = function () { 69 | for (var i = 0; i < this._event_listeners.length; i++) { 70 | this._event_listeners[i].on_mouse_in(this); 71 | } 72 | }; 73 | PointLabelEntity.prototype._on_mouse_out_event = function () { 74 | for (var i = 0; i < this._event_listeners.length; i++) { 75 | this._event_listeners[i].on_mouse_out(this); 76 | } 77 | }; 78 | PointLabelEntity.prototype.update = function () { 79 | var position = this.model.position; 80 | this.circle 81 | .attr('cx', position.x) 82 | .attr('cy', position.y); 83 | }; 84 | PointLabelEntity.prototype.commit = function () { 85 | this.root_view.commit_model(this.model); 86 | }; 87 | PointLabelEntity.prototype._update_style = function () { 88 | if (this._attached) { 89 | var stroke_colour = this._outline_colour(); 90 | if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.HIDDEN) { 91 | this.circle.attr("visibility", "hidden"); 92 | } 93 | else if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.FAINT) { 94 | stroke_colour = stroke_colour.with_alpha(0.2); 95 | this.circle.attr("style", "fill:none;stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 96 | this.circle.attr("visibility", "visible"); 97 | } 98 | else if (this.root_view.view.label_visibility == labelling_tool.LabelVisibility.FULL) { 99 | var circle_fill_colour = this.root_view.view.colour_for_label_class(this.model.label_class); 100 | if (this._hover) { 101 | circle_fill_colour = circle_fill_colour.lighten(0.4); 102 | } 103 | circle_fill_colour = circle_fill_colour.with_alpha(0.35); 104 | stroke_colour = stroke_colour.with_alpha(0.5); 105 | this.circle.attr("style", "fill:" + circle_fill_colour.to_rgba_string() + ";stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 106 | this.circle.attr("visibility", "visible"); 107 | } 108 | } 109 | }; 110 | PointLabelEntity.prototype.compute_centroid = function () { 111 | return this.model.position; 112 | }; 113 | PointLabelEntity.prototype.compute_bounding_box = function () { 114 | var centre = this.compute_centroid(); 115 | return new labelling_tool.AABox({ x: centre.x - 1, y: centre.y - 1 }, { x: centre.x + 1, y: centre.y + 1 }); 116 | }; 117 | PointLabelEntity.prototype.contains_pointer_position = function (point) { 118 | return labelling_tool.compute_sqr_dist(point, this.model.position) <= (4.0 * 4.0); 119 | }; 120 | return PointLabelEntity; 121 | })(labelling_tool.AbstractLabelEntity); 122 | labelling_tool.PointLabelEntity = PointLabelEntity; 123 | labelling_tool.register_entity_factory('point', function (root_view, model) { 124 | return new PointLabelEntity(root_view, model); 125 | }); 126 | /* 127 | Draw point tool 128 | */ 129 | var DrawPointTool = (function (_super) { 130 | __extends(DrawPointTool, _super); 131 | function DrawPointTool(view, entity) { 132 | _super.call(this, view); 133 | this.entity = entity; 134 | } 135 | DrawPointTool.prototype.on_init = function () { 136 | }; 137 | ; 138 | DrawPointTool.prototype.on_shutdown = function () { 139 | }; 140 | ; 141 | // on_switch_in(pos: Vector2) { 142 | // if (this.entity !== null) { 143 | // this.add_point(pos); 144 | // } 145 | // }; 146 | // 147 | // on_switch_out(pos: Vector2) { 148 | // if (this.entity !== null) { 149 | // this.remove_last_point(); 150 | // } 151 | // }; 152 | // 153 | DrawPointTool.prototype.on_cancel = function (pos) { 154 | this._view.unselect_all_entities(); 155 | this._view.view.set_current_tool(new labelling_tool.SelectEntityTool(this._view)); 156 | return true; 157 | }; 158 | ; 159 | DrawPointTool.prototype.on_left_click = function (pos, event) { 160 | this.create_entity(pos); 161 | this.entity.update(); 162 | this.entity.commit(); 163 | }; 164 | ; 165 | DrawPointTool.prototype.create_entity = function (position) { 166 | var model = new_PointLabelModel(position); 167 | var entity = this._view.get_or_create_entity_for_model(model); 168 | this.entity = entity; 169 | // Freeze to prevent this temporary change from being sent to the backend 170 | this._view.view.freeze(); 171 | this._view.add_child(entity); 172 | this._view.select_entity(entity, false, false); 173 | this._view.view.thaw(); 174 | }; 175 | ; 176 | DrawPointTool.prototype.destroy_entity = function () { 177 | // Freeze to prevent this temporary change from being sent to the backend 178 | this._view.view.freeze(); 179 | this.entity.destroy(); 180 | this.entity = null; 181 | this._view.view.thaw(); 182 | }; 183 | ; 184 | return DrawPointTool; 185 | })(labelling_tool.AbstractTool); 186 | labelling_tool.DrawPointTool = DrawPointTool; 187 | })(labelling_tool || (labelling_tool = {})); 188 | //# sourceMappingURL=point_label.js.map -------------------------------------------------------------------------------- /image_labelling_tool/static/labelling_tool/point_label.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 University of East Anglia, Norwich, UK 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Developed by Geoffrey French in collaboration with Dr. M. Fisher and 25 | Dr. M. Mackiewicz. 26 | */ 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | 34 | module labelling_tool { 35 | /* 36 | Point label model 37 | */ 38 | interface PointLabelModel extends AbstractLabelModel { 39 | position: Vector2; 40 | } 41 | 42 | function new_PointLabelModel(position: Vector2): PointLabelModel { 43 | return {label_type: 'point', label_class: null, position: position}; 44 | } 45 | 46 | 47 | /* 48 | Point label entity 49 | */ 50 | export class PointLabelEntity extends AbstractLabelEntity { 51 | circle: any; 52 | connections_group: any; 53 | 54 | constructor(view: RootLabelView, model: PointLabelModel) { 55 | super(view, model); 56 | } 57 | 58 | attach() { 59 | super.attach(); 60 | this.circle = this.root_view.world.append("circle") 61 | .attr('r', 4.0); 62 | 63 | this.update(); 64 | 65 | var self = this; 66 | this.circle.on("mouseover", function() { 67 | self._on_mouse_over_event(); 68 | }).on("mouseout", function() { 69 | self._on_mouse_out_event(); 70 | }); 71 | 72 | 73 | this._update_style(); 74 | } 75 | 76 | detach() { 77 | this.circle.remove(); 78 | this.circle = null; 79 | super.detach(); 80 | } 81 | 82 | 83 | _on_mouse_over_event() { 84 | for (var i = 0; i < this._event_listeners.length; i++) { 85 | this._event_listeners[i].on_mouse_in(this); 86 | } 87 | } 88 | 89 | _on_mouse_out_event() { 90 | for (var i = 0; i < this._event_listeners.length; i++) { 91 | this._event_listeners[i].on_mouse_out(this); 92 | } 93 | } 94 | 95 | 96 | update() { 97 | var position = this.model.position; 98 | 99 | this.circle 100 | .attr('cx', position.x) 101 | .attr('cy', position.y); 102 | } 103 | 104 | commit() { 105 | this.root_view.commit_model(this.model); 106 | } 107 | 108 | 109 | _update_style() { 110 | if (this._attached) { 111 | var stroke_colour: Colour4 = this._outline_colour(); 112 | 113 | if (this.root_view.view.label_visibility == LabelVisibility.HIDDEN) { 114 | this.circle.attr("visibility", "hidden"); 115 | } 116 | else if (this.root_view.view.label_visibility == LabelVisibility.FAINT) { 117 | stroke_colour = stroke_colour.with_alpha(0.2); 118 | this.circle.attr("style", "fill:none;stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 119 | this.circle.attr("visibility", "visible"); 120 | } 121 | else if (this.root_view.view.label_visibility == LabelVisibility.FULL) { 122 | var circle_fill_colour = this.root_view.view.colour_for_label_class(this.model.label_class); 123 | if (this._hover) { 124 | circle_fill_colour = circle_fill_colour.lighten(0.4); 125 | } 126 | circle_fill_colour = circle_fill_colour.with_alpha(0.35); 127 | 128 | stroke_colour = stroke_colour.with_alpha(0.5); 129 | 130 | this.circle.attr("style", "fill:" + circle_fill_colour.to_rgba_string() + ";stroke:" + stroke_colour.to_rgba_string() + ";stroke-width:1"); 131 | this.circle.attr("visibility", "visible"); 132 | } 133 | } 134 | } 135 | 136 | compute_centroid(): Vector2 { 137 | return this.model.position; 138 | } 139 | 140 | compute_bounding_box(): AABox { 141 | var centre = this.compute_centroid(); 142 | return new AABox({x: centre.x - 1, y: centre.y - 1}, {x: centre.x + 1, y: centre.y + 1}); 143 | } 144 | 145 | contains_pointer_position(point: Vector2): boolean { 146 | return compute_sqr_dist(point, this.model.position) <= (4.0 * 4.0); 147 | } 148 | } 149 | 150 | 151 | register_entity_factory('point', (root_view: RootLabelView, model: AbstractLabelModel) => { 152 | return new PointLabelEntity(root_view, model as PointLabelModel); 153 | }); 154 | 155 | 156 | /* 157 | Draw point tool 158 | */ 159 | export class DrawPointTool extends AbstractTool { 160 | entity: PointLabelEntity; 161 | 162 | constructor(view: RootLabelView, entity: PointLabelEntity) { 163 | super(view); 164 | this.entity = entity; 165 | } 166 | 167 | on_init() { 168 | }; 169 | 170 | on_shutdown() { 171 | }; 172 | 173 | // on_switch_in(pos: Vector2) { 174 | // if (this.entity !== null) { 175 | // this.add_point(pos); 176 | // } 177 | // }; 178 | // 179 | // on_switch_out(pos: Vector2) { 180 | // if (this.entity !== null) { 181 | // this.remove_last_point(); 182 | // } 183 | // }; 184 | // 185 | on_cancel(pos: Vector2): boolean { 186 | this._view.unselect_all_entities(); 187 | this._view.view.set_current_tool(new SelectEntityTool(this._view)); 188 | return true; 189 | }; 190 | 191 | on_left_click(pos: Vector2, event: any) { 192 | this.create_entity(pos); 193 | this.entity.update(); 194 | this.entity.commit(); 195 | }; 196 | 197 | 198 | create_entity(position: Vector2) { 199 | var model = new_PointLabelModel(position); 200 | var entity = this._view.get_or_create_entity_for_model(model); 201 | this.entity = entity; 202 | // Freeze to prevent this temporary change from being sent to the backend 203 | this._view.view.freeze(); 204 | this._view.add_child(entity); 205 | this._view.select_entity(entity, false, false); 206 | this._view.view.thaw(); 207 | }; 208 | 209 | destroy_entity() { 210 | // Freeze to prevent this temporary change from being sent to the backend 211 | this._view.view.freeze(); 212 | this.entity.destroy(); 213 | this.entity = null; 214 | this._view.view.thaw(); 215 | }; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /image_labelling_tool/static/polyk.d.ts: -------------------------------------------------------------------------------- 1 | declare module PolyK { 2 | export interface AABB { 3 | x: number; 4 | y: number; 5 | width: number; 6 | height: number; 7 | } 8 | 9 | export interface Intersection { 10 | dist: number; 11 | edge: number; 12 | norm: any; 13 | refl: any; 14 | } 15 | 16 | export function IsSimple(p: number[]): boolean; 17 | export function IsConvex(p: number[]): boolean; 18 | export function GetArea(p: number[]): number; 19 | export function GetAABB(p: number[]): AABB; 20 | export function Reverse(p: number[]): number[]; 21 | export function Triangulate(p: number[]): number[]; 22 | export function ContainsPoint(p: number[], px: number, py: number): boolean; 23 | export function Slice(p: number[], ax: number, ay: number, bx: number, by: number): number[]; 24 | export function Raycast(p: number[], x: number, y: number, dx: number, dy: number, isc?: Intersection): Intersection; 25 | export function ClosestEdge(p: number[], x: number, y: number, isc?: Intersection): Intersection; 26 | } -------------------------------------------------------------------------------- /image_labelling_tool/templates/inline/instructions.html: -------------------------------------------------------------------------------- 1 |

Panning and zooming:

2 |
    3 |
  • Use the scroll wheel (or scroll gesture on touch pad) to zoon in and out
  • 4 |
  • Right-click drag to pan the image
  • 5 |
6 | 7 |

To navigate between images:

8 |
    9 |
  • Using the left and right arrows to navigate the images one by one
  • 10 |
  • Enter a number in the box at the top to navigate to a specific image
  • 11 |
12 | 13 |

To label regions of the image:

14 |
    15 | {% if drawPolyLabel %} 16 |
  • Drawing polygonal regions onto the image: 17 |
      18 |
    • Click the Draw poly button
    • 19 |
    • Within the image pane, left-click to draw polygonal corners of your region
    • 20 |
    • When you have finished the region, right-click to stop
    • 21 |
    • You are still in draw poly mode, so you can start left-clicking again to draw the next region
    • 22 |
    • To exit draw poly mode, right-click a second time.
    • 23 |
    • Fixing mistakes while in drawing mode: 24 |
        25 |
      • The previous vertex is highlighted with a small pink circle. To delete it, press the 26 | '/' key. 27 |
      • 28 |
      • To insert vertices at a different point in the polygon, press the '[' 29 | and ']' keys to cycle through. This also cycles the vertex that is 30 | highlighted as the previous vertex (above), allowing you to select vertices to 31 | delete.
      • 32 |
      33 |
    • 34 |
    35 |
  • 36 | {% endif %} 37 | 38 | {% if drawBoxLabel %} 39 |
  • Drawing rectangular regions onto the image: 40 |
      41 |
    • Click the Draw box button
    • 42 |
    • Within the image pane, left-click to place one corner and left click again to place another 43 | corner; the tool will create a box between the two
    • 44 |
    • Repeat the above for each box that you want to create
    • 45 |
    46 |
  • 47 | {% endif %} 48 | 49 | {% if drawPointLabel %} 50 |
  • Highlighting points of interest in the image: 51 |
      52 |
    • Click the Add point button
    • 53 |
    • Within the image pane, left-click to place a point marker in the image
    • 54 |
    • Repeat the above for each point that you want to create
    • 55 |
    56 |
  • 57 | {% endif %} 58 | 59 |
  • Selecting regions: 60 |
      61 |
    • Selected regions have a red outline, yellow otherwise
    • 62 |
    • If only one region is selected, clicking the Draw poly button will allow you to modify it; you will go back to draw poly mode
    • 63 |
    • To select a different region, click the Select button and choose a different region by clicking on it. Multiple regions can be selected by holding SHIFT while clicking.
    • 64 |
    • If multiple regions overlap and you wish to select a region that is obscured, move the 65 | pointer over the region that you wish to select; the front-most region will be highlighted. 66 | Press the ',' and '.' keys to cycle through the regions under the 67 | pointer until the one that you need is highlighted. Left-click to select it. 68 |
    • 69 |
    70 |
  • 71 | 72 | {% if deleteLabel %} 73 |
  • Deleting regions: 74 |
      75 |
    • Select regions using the select tool (see above)
    • 76 |
    • Click the wastebin button to delete them; you will be asked for confirmation
    • 77 |
    78 |
  • 79 | {% endif %} 80 | 81 | {% if labelClassSelector %} 82 |
  • Changing the label of a region: 83 |
      84 |
    • Select regions using the select tool (see above)
    • 85 |
    • Use the drop-down (normally reads UNCLASSIFIED) within the Labels section to change the label
    • 86 |
    • Please note: for regions for which you cannot identify the content, use the Excluded label, this indicates to the system that it should not consider these parts of the image
    • 87 |
    88 |
  • 89 | {% endif %} 90 | 91 |
  • If the coloured regions are obscuring parts of the image that you need to see: 92 |
      93 |
    • Within the Labels section, click the Hide labels checkbox to hide the labels
    • 94 |
    • Uncheck it to show them afterwards
    • 95 |
    96 |
  • 97 | 98 |
  • When you are done: 99 |
      100 |
    • When you are satisfied that you have marked out all of the regions of interest and that they are correctly labelled, click the Finished checkbox within the Current image section. 101 | This will mark the image as finished within the system.
    • 102 |
    103 |
  • 104 |
105 | -------------------------------------------------------------------------------- /image_labelling_tool/templates/inline/labelling_tool.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | -------------------------------------------------------------------------------- /image_labelling_tool/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gfrench' 2 | -------------------------------------------------------------------------------- /image_labelling_tool/templatetags/labelling_tool_tags.py: -------------------------------------------------------------------------------- 1 | import uuid, json 2 | 3 | from django import template 4 | from django.utils.html import format_html 5 | 6 | from image_labelling_tool import labelling_tool as lt 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.simple_tag 12 | def labelling_tool_scripts(): 13 | script_urls = lt.js_file_urls('/static/labelling_tool/') 14 | script_tags = ''.join(['\n'.format(url) for url in script_urls]) 15 | return format_html(script_tags) 16 | 17 | @register.inclusion_tag('inline/labelling_tool.html') 18 | def labelling_tool(width, height, label_classes, image_descriptors, initial_image_index, 19 | labelling_tool_url, enable_locking, config=None): 20 | tool_id = uuid.uuid4() 21 | if config is None: 22 | config = {} 23 | return {'tool_id': str(tool_id), 24 | 'width': width, 25 | 'height': height, 26 | 'label_classes': json.dumps(label_classes), 27 | 'image_descriptors': json.dumps(image_descriptors), 28 | 'initial_image_index': str(initial_image_index), 29 | 'labelling_tool_url': labelling_tool_url, 30 | 'enable_locking': enable_locking, 31 | 'config': json.dumps(config), 32 | } 33 | 34 | @register.inclusion_tag('inline/instructions.html') 35 | def labelling_tool_instructions(config=None): 36 | tools = { 37 | 'imageSelector': True, 38 | 'labelClassSelector': True, 39 | 'brushSelect': True, 40 | 'drawPointLabel': True, 41 | 'drawBoxLabel': True, 42 | 'drawPolyLabel': True, 43 | 'compositeLabel': True, 44 | 'groupLabel': True, 45 | 'deleteLabel': True, 46 | } 47 | if config is not None: 48 | tools.update(config.get('tools')) 49 | return tools 50 | -------------------------------------------------------------------------------- /image_labelling_tool/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.9 2 | scikit-image>=0.10 3 | pillow>=2.7 4 | flask>=0.10 5 | jupyter>=1.0 6 | notebook>=4.2 7 | widgetsnbextension>=1.2 8 | pyyaml 9 | flask 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import find_packages 3 | from setuptools import setup 4 | 5 | version = '0.1.dev1' 6 | 7 | here = os.path.abspath(os.path.dirname(__file__)) 8 | try: 9 | README = open(os.path.join(here, 'README.md')).read() 10 | except IOError: 11 | README = '' 12 | 13 | install_requires = [ 14 | 'numpy', 15 | 'Pillow', 16 | 'scikit-image' 17 | ] 18 | 19 | tests_require = [ 20 | ] 21 | 22 | setup( 23 | name="Image labelling tool", 24 | version=version, 25 | description="A web-based labelling tool for Djano, Flask and Jupyter notebook", 26 | long_description="\n\n".join([README]), 27 | classifiers=[ 28 | "Development Status :: 1 - Alpha", 29 | "Intended Audience :: Developers", 30 | "Intended Audience :: Science/Research", 31 | "License :: OSI Approved :: MIT License", 32 | "Programming Language :: Python :: 2.7", 33 | # "Programming Language :: Python :: 3", 34 | # "Programming Language :: Python :: 3.4", 35 | "Topic :: Software Development :: User Interfaces", 36 | ], 37 | keywords="", 38 | author="Geoffrey French", 39 | # author_email="brittix1023 at gmail dot com", 40 | url="https://bitbucket.org/ueacomputervision/image-labelling-tool", 41 | license="MIT", 42 | packages=find_packages(), 43 | include_package_data=False, 44 | zip_safe=False, 45 | install_requires=install_requires, 46 | extras_require={ 47 | 'testing': tests_require, 48 | }, 49 | ) 50 | -------------------------------------------------------------------------------- /templates/labeller_page.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | UEA Computer Vision - Labelling Tool 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for js_url in tool_js_urls %} 14 | 15 | {% endfor %} 16 | 17 | 18 | 19 | 20 |

UEA Computer Vision - Labelling Tool example Flask App

21 | 22 |
23 | 24 | 25 |

Instructions for use

26 | 27 |

To navigate between images:

28 |
    29 |
  • Using the left and right arrows to navigate the images one by one
  • 30 |
  • Enter a number in the box at the top to navigate to a specific image
  • 31 |
32 | 33 |

To label regions of the image:

34 |
    35 |
  • Drawing polygonal regions onto the image: 36 |
      37 |
    • Click the Draw poly button
    • 38 |
    • Within the image pane, left-click to draw polygonal corners of your region
    • 39 |
    • When you have finished the region, right-click to stop
    • 40 |
    • You are still in draw poly mode, so you can start left-clicking again to draw the next region
    • 41 |
    • To exit draw poly mode, right-click a second time.
    • 42 |
    • Fixing mistakes while in drawing mode: 43 |
        44 |
      • The previous vertex is highlighted with a small pink circle. To delete it, press the 45 | '/' key. 46 |
      • 47 |
      • To insert vertices at a different point in the polygon, press the ',' 48 | and '.' keys to cycle through. This also cycles the vertex that is 49 | highlighted as the previous vertex (above), allowing you to select vertices to 50 | delete.
      • 51 |
      52 |
    • 53 |
    54 |
  • 55 | 56 |
  • Drawing rectangular regions onto the image: 57 |
      58 |
    • Click the Draw box button
    • 59 |
    • Within the image pane, left-click to place one corner and left click again to place another 60 | corner; the tool will create a box between the two
    • 61 |
    • Repeat the above for each box that you want to create
    • 62 |
    63 |
  • 64 | 65 |
  • Highlighting points of interest in the image: 66 |
      67 |
    • Click the Add point button
    • 68 |
    • Within the image pane, left-click to place a point marker in the image
    • 69 |
    • Repeat the above for each point that you want to create
    • 70 |
    71 |
  • 72 | 73 |
  • Selecting regions: 74 |
      75 |
    • Selected regions have a red outline, yellow otherwise
    • 76 |
    • If only one region is selected, clicking the Draw poly button will allow you to modify it; you will go back to draw poly mode
    • 77 |
    • To select a different region, click the Select button and choose a different region by clicking on it. Multiple regions can be selected by holding SHIFT while clicking.
    • 78 |
    • If multiple regions overlap and you wish to select a region that is obscured, move the 79 | pointer over the region that you wish to select; the front-most region will be highlighted. 80 | Press the '[' and ']' keys to cycle through the regions under the 81 | pointer until the one that you need is highlighted. Left-click to select it. 82 |
    • 83 |
    84 |
  • 85 | 86 |
  • Deleting regions: 87 |
      88 |
    • Select regions using the select tool (see above)
    • 89 |
    • Click the wastebin button to delete them; you will be asked for confirmation
    • 90 |
    91 |
  • 92 | 93 |
  • Changing the label of a region: 94 |
      95 |
    • Select regions using the select tool (see above)
    • 96 |
    • Use the drop-down (normally reads UNCLASSIFIED) within the Labels section to change the label
    • 97 |
    • Please note: for regions for which you cannot identify the content, use the Excluded label, this indicates to the system that it should not consider these parts of the image
    • 98 |
    99 |
  • 100 | 101 |
  • If the coloured regions are obscuring parts of the image that you need to see: 102 |
      103 |
    • Within the Labels section, click the Hide labels checkbox to hide the labels
    • 104 |
    • Uncheck it to show them afterwards
    • 105 |
    106 |
  • 107 | 108 |
  • When you are done: 109 |
      110 |
    • When you are satisfied that you have marked out all of the regions of interest and that they are correctly labelled, click the Finished checkbox within the Current image section. 111 | This will mark the image as finished within the system.
    • 112 |
    113 |
  • 114 |
115 | 116 | 117 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /tests/example_labeller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/tests/example_labeller/__init__.py -------------------------------------------------------------------------------- /tests/example_labeller/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | import models 3 | 4 | # Register your models here. 5 | admin.site.register(models.ImageWithLabels) 6 | -------------------------------------------------------------------------------- /tests/example_labeller/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/tests/example_labeller/management/__init__.py -------------------------------------------------------------------------------- /tests/example_labeller/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/tests/example_labeller/management/commands/__init__.py -------------------------------------------------------------------------------- /tests/example_labeller/management/commands/populate.py: -------------------------------------------------------------------------------- 1 | import os, mimetypes, json, datetime 2 | from django.core.management.base import BaseCommand, CommandError 3 | from django.core.files import File 4 | from image_labelling_tool import labelling_tool 5 | from image_labelling_tool import models as lt_models 6 | from ... import models 7 | 8 | class Command(BaseCommand): 9 | help = 'Populates the image database from a directory' 10 | 11 | def add_arguments(self, parser): 12 | parser.add_argument('dir', type=str) 13 | 14 | def handle(self, *args, **options): 15 | images_dir = options['dir'] 16 | image_and_label_files = [] 17 | for filename in os.listdir(images_dir): 18 | path = os.path.join(images_dir, filename) 19 | if os.path.isfile(path): 20 | mt, encoding = mimetypes.guess_type(path) 21 | if mt is not None and mt.startswith('image/'): 22 | image_path = path 23 | labels_path = os.path.splitext(path)[0] + '__labels.json' 24 | if os.path.exists(labels_path) and os.path.isfile(labels_path): 25 | image_and_label_files.append((image_path, labels_path)) 26 | else: 27 | image_and_label_files.append((image_path, None)) 28 | 29 | for image_path, labels_path in image_and_label_files: 30 | if labels_path is not None: 31 | self.stdout.write('Adding image {} with labels from {}'.format(image_path, labels_path)) 32 | wrapped_labels = json.load(open(labels_path, 'r')) 33 | labels, complete = labelling_tool.PersistentLabelledImage._unwrap_labels( 34 | image_path, wrapped_labels) 35 | complete = complete if isinstance(complete, bool) else False 36 | labels_model = lt_models.Labels( 37 | labels_json_str=json.dumps(labels.labels_json), complete=complete, 38 | creation_date=datetime.date.today()) 39 | labels_model.save() 40 | else: 41 | self.stdout.write('Adding image {}'.format(image_path)) 42 | labels_model = lt_models.Labels(creation_date=datetime.date.today()) 43 | labels_model.save() 44 | 45 | image_model = models.ImageWithLabels(labels=labels_model) 46 | image_model.image.save(os.path.basename(image_path), 47 | File(open(image_path, 'rb'))) 48 | image_model.save() 49 | -------------------------------------------------------------------------------- /tests/example_labeller/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-30 09:03 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('image_labelling_tool', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='ImageWithLabels', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('image', models.ImageField(blank=True, upload_to=b'')), 23 | ('labels', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='image', to='image_labelling_tool.Labels')), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /tests/example_labeller/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/tests/example_labeller/migrations/__init__.py -------------------------------------------------------------------------------- /tests/example_labeller/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from image_labelling_tool import models as lt_models 4 | 5 | # Create your models here. 6 | class ImageWithLabels (models.Model): 7 | # image 8 | image = models.ImageField(blank=True) 9 | 10 | # labels 11 | labels = models.OneToOneField(lt_models.Labels, related_name='image') 12 | -------------------------------------------------------------------------------- /tests/example_labeller/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load labelling_tool_tags %} 2 | 3 | 4 | 5 | UEA Computer Vision - Labelling Tool 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% labelling_tool_scripts %} 17 | 18 | 19 | 20 | 21 |

UEA Computer Vision - Labelling Tool example Django App

22 | 23 | {% url 'example_labeller:labelling_tool_api' as ltapi_url %} 24 | 25 | {% labelling_tool 1280 720 label_classes image_descriptors initial_image_index ltapi_url True labelling_tool_config %} 26 | 27 | 28 |

Admin

29 |

The Django admin system can be found here.

30 | 31 | 32 |

Instructions for use

33 | 34 | {% labelling_tool_instructions %} 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/example_labeller/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tests/example_labeller/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | # Examples: 7 | # url(r'^$', 'labelling_aas.views.home', name='home'), 8 | # url(r'^blog/', include('blog.urls')), 9 | 10 | url(r'^$', views.home, name='home'), 11 | url(r'^labelling_tool_api', views.LabellingToolAPI.as_view(), name='labelling_tool_api'), 12 | ] 13 | -------------------------------------------------------------------------------- /tests/example_labeller/views.py: -------------------------------------------------------------------------------- 1 | import os, datetime 2 | 3 | from django.shortcuts import render, get_object_or_404 4 | 5 | from django.conf import settings 6 | 7 | from image_labelling_tool import labelling_tool 8 | from image_labelling_tool import models as lt_models 9 | from image_labelling_tool import labelling_tool_views 10 | 11 | from . import models 12 | 13 | def home(request): 14 | image_descriptors = [labelling_tool.image_descriptor( 15 | image_id=img.id, url=img.image.url, 16 | width=img.image.width, height=img.image.height) for img in models.ImageWithLabels.objects.all()] 17 | 18 | # Convert the label class tuples in `settings` to `labelling_tool.LabelClass` instances 19 | label_classes = [labelling_tool.LabelClass(*c) for c in settings.LABEL_CLASSES] 20 | 21 | context = { 22 | 'label_classes': [c.to_json() for c in label_classes], 23 | 'image_descriptors': image_descriptors, 24 | 'initial_image_index': 0, 25 | 'labelling_tool_config': settings.LABELLING_TOOL_CONFIG, 26 | } 27 | return render(request, 'index.html', context) 28 | 29 | 30 | class LabellingToolAPI (labelling_tool_views.LabellingToolViewWithLocking): 31 | def get_labels(self, request, image_id_str, *args, **kwargs): 32 | image = get_object_or_404(models.ImageWithLabels, id=image_id_str) 33 | return image.labels 34 | 35 | def get_next_unlocked_image_id_after(self, request, current_image_id_str, *args, **kwargs): 36 | unlocked_labels = lt_models.Labels.objects.unlocked() 37 | unlocked_imgs = models.ImageWithLabels.objects.filter(labels__in=unlocked_labels) 38 | unlocked_img_ids = [img.id for img in unlocked_imgs] 39 | try: 40 | index = unlocked_img_ids.index(int(current_image_id_str)) 41 | except ValueError: 42 | return None 43 | index += 1 44 | if index < len(unlocked_img_ids): 45 | return unlocked_img_ids[index] 46 | else: 47 | return None 48 | -------------------------------------------------------------------------------- /tests/example_labeller_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyu2172/image-labelling-tool/4a8f046d729f68a2fb214104a7522111a88c100a/tests/example_labeller_app/__init__.py -------------------------------------------------------------------------------- /tests/example_labeller_app/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example_labeller_app project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '3+p2$qln6o1ws1c)6o!+o+p%ql1n!+tt@wp)g5!pfgliqld)yo' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'image_labelling_tool', 41 | 'example_labeller', 42 | ) 43 | 44 | MIDDLEWARE_CLASSES = ( 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | 'django.middleware.security.SecurityMiddleware', 53 | ) 54 | 55 | ROOT_URLCONF = 'example_labeller_app.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | 'django.template.context_processors.static', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'example_labeller_app.wsgi.application' 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 84 | } 85 | } 86 | 87 | 88 | # Internationalization 89 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 90 | 91 | LANGUAGE_CODE = 'en-us' 92 | 93 | TIME_ZONE = 'UTC' 94 | 95 | USE_I18N = True 96 | 97 | USE_L10N = True 98 | 99 | USE_TZ = True 100 | 101 | 102 | # Static files (CSS, JavaScript, Images) 103 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 104 | 105 | STATIC_URL = '/static/' 106 | 107 | STATICFILES_DIRS = ( 108 | os.path.join(BASE_DIR, '..', 'ext_static'), 109 | ) 110 | 111 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 112 | MEDIA_URL = '/media/' 113 | 114 | CSRF_COOKIE_SECURE = False 115 | 116 | 117 | 118 | # Labelling tool configuration, used in `example_labeller.views` 119 | 120 | # Label classes 121 | # Tuple entries arameters are: symbolic name, human readable name for UI, and RGB colour as list 122 | LABEL_CLASSES = [ 123 | ('tree', 'Trees', [0, 255, 192]), 124 | ('building', 'Buldings', [255, 128, 0]), 125 | ('lake', 'Lake', [0, 128, 255]), 126 | ] 127 | 128 | 129 | # Configuration 130 | LABELLING_TOOL_CONFIG = { 131 | 'tools': { 132 | 'imageSelector': True, 133 | 'labelClassSelector': True, 134 | 'brushSelect': True, 135 | 'drawPointLabel': True, 136 | 'drawBoxLabel': True, 137 | 'drawPolyLabel': True, 138 | 'compositeLabel': True, 139 | 'groupLabel': True, 140 | 'deleteLabel': True, 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /tests/example_labeller_app/urls.py: -------------------------------------------------------------------------------- 1 | """example_labeller_app URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.conf import settings 18 | from django.contrib import admin 19 | from django.views.generic import RedirectView 20 | from django.views.static import serve 21 | 22 | urlpatterns = [ 23 | url(r'^admin/', include(admin.site.urls)), 24 | 25 | url(r'^example_labeller/', include('example_labeller.urls', namespace='example_labeller', 26 | app_name='example_labeller')), 27 | 28 | url(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}), 29 | 30 | url(r'^$', RedirectView.as_view(url='/example_labeller')), 31 | ] 32 | -------------------------------------------------------------------------------- /tests/example_labeller_app/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example_labeller_app project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_labeller_app.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /tests/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_labeller_app.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | --------------------------------------------------------------------------------