diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..72151b8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git + +**/credentials.json +**/robot_tuner.dat +**/GoogleNews-vectors-negative300.bin.gz +**/model.vec +**/classify_image_graph_def.pb +**/classifier_candy_sorter* +**/logs/* +**/tmp diff --git a/.gitignore b/.gitignore index d79ec25..4fec06e 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,9 @@ ENV/ ### Application +# data +data/ + # Log rotation *.log.* diff --git a/README.md b/README.md index 1abc853..4c82f26 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,24 @@ In the training mode, the system uses Inception model with transfer learning on - Controller PC (Linux) and camera: recognizes the candies with Cloud ML, controls the robot arm - Robot arm: picks the candy, takes it to the certain location, and drops. +### Candy type +The robot can handle two types of candy. Small candy that can be picked up, for which it will correcly adjust the gripper to account for rotation etc. It also supports boxed/smooth candy which it picks up using a suction cup. + ## Learning mode - Android tablet: shows UI for training process updates - Controller PC: runs Inception-v3 + transfer learning on Cloud ML to train a model from scratch, with the camera image +- More on [learning mode](/setup/demo_script.md) ## Setting things up - Prepare [hardware components](./setup/requirements.md) - Prepare [candies](./setup/candidate_candies.md) -- Set up as directed by [setup/README.md](./setup/README.md) +- Set up as directed by [setup/README.md](./setup/SETUP_GUIDE.md) + +## Troublehooting +- See [this page](/setup/troubleshooting.md) in case you encounter any errors or unexpected behaviour. + +## Troublehooting +- See [this page](/setup/troubleshooting.md) in case you encounter any errors or unexpected behaviour. ## Note -- Currently, the system is not using Cloud Speech API. It uses Web Speech API that shares the same voice recognition backend. - For Learning mode it is using Cloud ML training. For Serving mode it is not using Cloud ML prediction. diff --git a/robot-arm/README.md b/robot-arm/README.md index 6ce3e4b..5203314 100644 --- a/robot-arm/README.md +++ b/robot-arm/README.md @@ -3,10 +3,8 @@ robot-arm (api) === ## Note -This software is design to work with Python2.7 under following condition. +This software is designed to work with Python3.6 under following conditions: - This software depends on the following libraries: - - OpenCV3.2 (need to be compiled from source.) - - Refer to [setup/README.md](../setup/README.md) for installation. - Environment variables ``` export FLASK_ENV='prd' # choices are 'prd', 'stg' or 'dev' @@ -28,7 +26,7 @@ $ sudo systemctl start uwsgi-robot.service In this setup senario, the next command is not used. We use nginx+uWSGI instead. ``` # Run app. This requres environment variables. -$ python2 run_api.py # Be sure to use python2.7 +$ python3 run_api.py # Be sure to use python2.7 ``` ## API example diff --git a/robot-arm/calibration/converter.py b/robot-arm/calibration/converter.py index 3f04ab8..a00b4fb 100644 --- a/robot-arm/calibration/converter.py +++ b/robot-arm/calibration/converter.py @@ -22,7 +22,7 @@ class CoordinateConverter(object): - def __init__(self, from_points, to_points, z_low=-69): + def __init__(self, from_points, to_points, z_low=-31, r=0): """ find T that meets x_robot = T x_logical """ @@ -39,18 +39,23 @@ def __init__(self, from_points, to_points, z_low=-69): self.mat_transform = np.dot(self.mat_x_to.T, inv_x) self.z_low = z_low + self.r_offset = r - def convert(self, x, y): + def convert(self, x, y, r = None): xy_trans = AdjustForPictureToRobot() x, y = xy_trans.adjust(x, y) from_vector = np.array([x, y, 1]) transformed = np.dot(from_vector, self.mat_transform.T) - return transformed[0], transformed[1] + if r is None: + return transformed[0], transformed[1] + else: + adjusted_r = r + self.r_offset + return transformed[0], transformed[1], adjusted_r @classmethod - def from_tuning_file(cls, file_='/tmp/robot_tuner.dat', z_low_pad=5): + def from_tuning_file(cls, file_, z_low_pad=1): tuner_data = [] with open(file_, 'r') as readfile: for line in readfile: @@ -59,11 +64,16 @@ def from_tuning_file(cls, file_='/tmp/robot_tuner.dat', z_low_pad=5): data = json.loads(line) tuner_data.append(data) - z_low = sum([d['z'] for d in tuner_data])/3 + z_low_pad + # Find z low by averaging the measured z's + z_low = sum([d['z'] for d in tuner_data[:3]])/3 + z_low_pad + # If there is a fourth line, it contains the r-offset. + r = 0 if len(tuner_data) < 4 else tuner_data[3]['r'] + return cls( [(-0.3, 1.5), (-0.3, -1.5), (0.3, 0)], [(tuner_data[0]['x'], tuner_data[0]['y']), (tuner_data[1]['x'], tuner_data[1]['y']), (tuner_data[2]['x'], tuner_data[2]['y'])], - z_low=z_low + z_low=z_low, + r=r ) diff --git a/robot-arm/calibration/example_tune.dat b/robot-arm/calibration/example_tune.dat index c84f9dd..c382230 100644 --- a/robot-arm/calibration/example_tune.dat +++ b/robot-arm/calibration/example_tune.dat @@ -1,3 +1,5 @@ -{"x":78.83, "y": 132.45, "z":-70.82} -{"x":74.41, "y":-145.70, "z":-70.06} -{"x":175.01, "y": -7.77, "z":-69.91} \ No newline at end of file +{"x": 172.98753356933594, "y": 176.05148315429688, "z": -35.271888732910156} +{"x": 181.35813903808594, "y": -169.5199432373047, "z": -30.119842529296875} +{"x": 246.0970458984375, "y": 6.5959792137146, "z": -33.612144470214844} +{"r": 7.0} +{"x": 24.846025466918945, "y": 303.1612243652344, "z": 60.07351303100586} diff --git a/robot-arm/dobot/alarms.py b/robot-arm/dobot/alarms.py new file mode 100644 index 0000000..58f6868 --- /dev/null +++ b/robot-arm/dobot/alarms.py @@ -0,0 +1,74 @@ +alarms = { + "0x00": { + "Category": "Public Alarm", + "Name": "Reset Alarm", + "SetCondition": " After the system reset, the reset alarm will be set automatically", + "ResetCondition": " Protocol instruction is cleared" + }, + "0x01": { + "Category": "Public Alarm", + "Name": "Undefined Instruction", + "SetCondition": "Undifined instruction is received", + "ResetCondition": "The protocol instruction is cleared." + }, + "0x02": { + "Category": "Public Alarm", + "Name": "File System Error", + "SetCondition": "The file system errors;", + "ResetCondition": "Reset, if the file system initialization is successful, then reset the alarm automatically." + }, + "0x03": { + "Category": "Public Alarm", + "Name": "Failed Communication between MCU and FPGA", + "SetCondition": "The communication between MCU and FPGA is failed when initialization;", + "ResetCondition": "Reset, if the communication is successful, then reset the alarm automatically" + }, + "0x04": { + "Category": "Public Alarm", + "Name": "Angle Sensor Reading Error", + "SetCondition": "The angle sensor value can not be read correctly", + "ResetCondition": "Power off, power on again, if the angle sensor value can be read correctly, then reset alarm." + }, + "0x11": { + "Category": "Planning Alarm", + "Name": "Inverse Resolve Alarm", + "SetCondition": "The planning target point is not in the robot work space, resulting in the reverse solution error", + "ResetCondition": "The protocol instruction is cleared" + }, + "0x12": { + "Category": "Planning Alarm", + "Name": "Inverse Resolve Limit", + "SetCondition": "Inverse resolve of the target point beyond the joint limit value", + "ResetCondition": "The protocol instruction is cleared" + }, + "0x13": { + "Category": "Planning Alarm", + "Name": "Arc Input Parameter Alarm", + "SetCondition": "Enter the midpoint of the arc, and the target point can not form an arc", + "ResetCondition": "The protocol instruction is cleared" + }, + "0x15": { + "Category": "Planning Alarm", + "Name": "JUMP Parameter Error", + "SetCondition": "The planning starting point or the target point height above the set maximum JUMP height", + "ResetCondition": "The protocol instruction is cleared" + }, + "0x21": { + "Category": "Kinematic Alarm", + "Name": "Inverse Resolve Alarm", + "SetCondition": "The movement process beyond the robot work space resulting in the inverse solution error", + "ResetCondition": "The protocol instruction is cleared" + }, + "0x22": { + "Category": "Kinematic Alarm", + "Name": "Inverse Resolve Limit", + "SetCondition": "The inverse solution of the motion exceeds the Joint limit value", + "ResetCondition": "The protocol instruction is cleared" + }, + "0x44": { + "Category": "Limit Alarm", + "Name": "Joint 3 Positive Limit Alarm", + "SetCondition": "Joint 3 moves in the positive direction to the limit area", + "ResetCondition": "Protocol command reset the alarm manually; reset the alarm in reverse exit limit area automatically" + } +} \ No newline at end of file diff --git a/robot-arm/dobot/client.py b/robot-arm/dobot/client.py index ed7e919..8e2adde 100644 --- a/robot-arm/dobot/client.py +++ b/robot-arm/dobot/client.py @@ -18,7 +18,9 @@ import logging import time + from dobot import command +from dobot import alarms from dobot.errors import TimeoutError from dobot.serial import SerialCommunicator @@ -32,6 +34,7 @@ def __init__(self, port, baudrate, read_timeout_sec=10, serial_timeout_sec=5): port, baudrate, read_timeout_sec=read_timeout_sec, serial_timeout_sec=serial_timeout_sec ) + def get_pose(self): return self.serial.call(command.GetPose()) @@ -51,40 +54,78 @@ def count_queued_command(self): result = self.serial.call(command.GetQueuedCmdLeftSpace()) return DOBOT_QUEUE_SIZE - result['leftSpace'] + def get_alarms_state(self, discard_reset_alarm=True): + # Get the alarms from the Dobot + result = self.serial.call(command.GetAlarmsState()) + # Process alarm data into a string with bit flags + alarm_string = "".join(["{0:0>8b}".format(x)[::-1] for x in list(result)[4:-1]]) # Get only the alarm values + # Traverse the bit flags and extract the alarm IDs + alarm_ids = ["0x" + "{0:0>2X}".format(index) for index, value in + enumerate(alarm_string[int(discard_reset_alarm):]) if value == "1"] + # Lookup alarm ids to get the information about the alarms + dobot_alarms = [alarms.alarms.get(alarm_id) if alarms.alarms.get(alarm_id) else alarm_id + for alarm_id in alarm_ids] + return dobot_alarms + + def clear_alarms_state(self): + return self.serial.call(command.ClearAllAlarmsState()) + def initialize(self): self.serial.call(command.ClearAllAlarmsState()) self.serial.call(command.SetQueuedCmdClear()) self.adjust_z(0) self.serial.call(command.SetHomeCmd()) - def move(self, x, y, z, velocity=200, accel=200, jump=True): + def move(self, x, y, z, r=0, velocity=200, accel=200, jump=True): self.serial.call(command.SetPTPJointParams(velocity, velocity, velocity, velocity, accel, accel, accel, accel)) mode = 0 if not jump: mode = 1 - self.serial.call(command.SetPTPCmd(mode, x, y, z, 0)) + self.serial.call(command.SetPTPCmd(mode, x, y, z, r)) - def linear_move(self, x, y, z, velocity=200, accel=200): + def linear_move(self, x, y, z, r=0, velocity=200, accel=200): self.serial.call(command.SetPTPCoordinateParams(velocity, velocity, accel, accel)) - self.serial.call(command.SetPTPCmd(2, x, y, z, 0)) + self.serial.call(command.SetPTPCmd(2, x, y, z, r)) def pickup(self, x, y, z_low=0, z_high=100, sleep_sec=1, velocity=200, accel=100, num_trials=1): - self.move(x, y, z_high, velocity, accel) + self.move(x, y, z_high, 0, velocity, accel) self.pump(1) for i in range(num_trials): - self.linear_move(x, y, z_low, velocity, accel) + self.linear_move(x, y, z_low, 0, velocity, accel) + time.sleep(sleep_sec) + self.linear_move(x, y, z_high, 0, velocity, accel) + + def pickup_gripper(self, x, y, r, z_low=0, z_high=100, sleep_sec=0.5, velocity=200, accel=100, num_trials=1): + self.grip(0) + self.move(x, y, z_high, r, velocity, accel) + for i in range(num_trials): + self.linear_move(x, y, z_low, r, velocity, accel) + time.sleep(1) + self.grip(1) + self.wait(120) time.sleep(sleep_sec) - self.linear_move(x, y, z_high, velocity, accel) + self.linear_move(x, y, z_high, r, velocity, accel) def adjust_z(self, z): self.wait() pose = self.serial.call(command.GetPose()) - self.linear_move(pose['x'], pose['y'], z) + self.linear_move(pose['x'], pose['y'], z, pose['r']) + + def adjust_r(self, r): + self.wait() + pose = self.serial.call(command.GetPose()) + self.linear_move(pose['x'], pose['y'], pose['z'], r) def pump(self, on): self.serial.call(command.SetEndEffectorSuctionCup(1, on)) + def grip(self, grip): + self.serial.call(command.SetEndEffectorGripper(1, grip)) + + def stepper_motor(self, index, enabled, reverse=False): + self.serial.call(command.SetEMotor(index, enabled, -10000 if reverse else 10000)) + def close(self): self.serial.close() diff --git a/robot-arm/dobot/command.py b/robot-arm/dobot/command.py index 85da4df..5e9bd09 100644 --- a/robot-arm/dobot/command.py +++ b/robot-arm/dobot/command.py @@ -102,7 +102,7 @@ def parse_to_dict(self, packet): def checksum(payload): - return 0xFF & (0x100 - (sum([ord(c) for c in payload]))) + return 0xFF & (0x100 - (sum([c for c in payload]))) def ctrl(rw, is_queued): @@ -129,7 +129,9 @@ def __init__(self): class GetAlarmsState(Command): def __init__(self): - super(GetAlarmsState, self).__init__(20, 0, 0) + super(GetAlarmsState, self).__init__( + 20, 0, 0, '16B' + ) class ClearAllAlarmsState(Command): @@ -199,6 +201,29 @@ def __init__(self, is_ctrl_enabled, is_sucked): self.params = struct.pack(' 0 else -140, z=z_high, r=xy_conv[2], jump=False) + + logging.info('Starting pickup.') + dobot.pickup_gripper(xy_conv[0], xy_conv[1], xy_conv[2], z_low=z_low, z_high=z_high, velocity=v, accel=a) + logging.info('Serving to {}.'.format(dest)) + dobot.move(dest[0], dest[1], dest[2], 0, velocity=v, accel=a) + dobot.wait() + logging.info('Releasing the candy') + dobot.grip(0) + dobot.wait() logging.info('Turning pump off.') dobot.pump(0) dobot.close() @@ -86,6 +133,7 @@ def get_state(): logging.info('Getting pose of dobot.') pose = dobot.get_pose() status["queue_count"] = dobot.count_queued_command() + status["alarms"] = dobot.get_alarms_state() dobot.close() logging.info('Dobot pose: (x, y, z, r)=({}, {}, {}, {}) (base, fore, rear, end)=({}, {}, {}, {})'.format( @@ -96,3 +144,12 @@ def get_state(): status[key] = pose[key] return jsonify(status) + + + +@api.route("/test") +def test(): + dobot = get_dobot(current_app.config['DOBOT_SERIAL_PORT']) + pose = dobot.get_pose() + dobot.close() + return jsonify({}) diff --git a/robot-arm/web/app.py b/robot-arm/web/app.py index 2ef4399..e4d370f 100644 --- a/robot-arm/web/app.py +++ b/robot-arm/web/app.py @@ -26,18 +26,40 @@ from web.config import get_config -def create_app(dobot_port, tuner_file): - config = get_config(os.getenv('FLASK_ENV', 'dev'), dobot_port) +def create_app(dobot_port, tuner_file, instance_path): + app = Flask(__name__, instance_path=instance_path, instance_relative_config=True) - app = Flask(__name__) - - app.config.from_object(config) - _configure_logging(app, config) + app.config.from_object(get_config(os.getenv('FLASK_ENV', 'dev'), dobot_port, tuner_file)) + app.config.from_pyfile('config.py', silent=True) + _configure_logging(app) app.register_blueprint(api, url_prefix='/api') + return app -def _configure_logging(app, config): +""" +Removed file logging as this seems to cause issues with supervisor and docker +'file_app': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'formatter': 'default', + 'filename': os.path.join(app.config['LOG_DIR'], 'app.log'), + 'when': 'd', + 'interval': 1, + 'backupCount': 14, + }, + 'file_access': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'formatter': 'access', + 'filename': os.path.join(app.config['LOG_DIR'], 'access.log'), + 'when': 'd', + 'interval': 1, + 'backupCount': 14, + }, + +""" + + +def _configure_logging(app): app.logger logging.config.dictConfig({ 'version': 1, @@ -59,38 +81,21 @@ def _configure_logging(app, config): 'class': 'logging.StreamHandler', 'formatter': 'access', }, - 'file_app': { - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'formatter': 'default', - 'filename': os.path.join(config.LOG_DIR, 'app.log'), - 'when': 'd', - 'interval': 1, - 'backupCount': 14, - }, - 'file_access': { - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'formatter': 'access', - 'filename': os.path.join(config.LOG_DIR, 'access.log'), - 'when': 'd', - 'interval': 1, - 'backupCount': 14, - }, - }, 'loggers': { '': { - 'level': config.LOG_LEVEL, - 'handlers': ['file_app'] if not app.debug else ['console_app'], + 'level': app.config['LOG_LEVEL'], + 'handlers': ['console_app'] if not app.debug else ['console_app'], 'propagate': False, }, 'werkzeug': { - 'level': config.LOG_LEVEL, - 'handlers': ['file_access'] if not app.debug else ['console_access'], + 'level': app.config['LOG_LEVEL'], + 'handlers': ['console_access'] if not app.debug else ['console_access'], 'propagate': False, }, }, 'root': { - 'level': config.LOG_LEVEL, - 'handlers': ['file_app'] if not app.debug else ['console_app'], + 'level': app.config['LOG_LEVEL'], + 'handlers': ['console_app'] if not app.debug else ['console_app'], }, }) diff --git a/robot-arm/web/config.py b/robot-arm/web/config.py index f149163..a26a95f 100644 --- a/robot-arm/web/config.py +++ b/robot-arm/web/config.py @@ -17,6 +17,7 @@ import logging import os +import json from calibration.converter import CoordinateConverter @@ -32,10 +33,10 @@ class DefaultConfig(object): DOBOT_SERIAL_PORT = None DOBOT_DEFAULT_BAUDRATE = 115200 - DOBOT_Z_HIGH = 50. - DOBOT_SERVE_XY = (0., -150.) - DOBOT_MAX_VELOCITY = 300 - DOBOT_MAX_ACCERALATION = 300 + DOBOT_Z_HIGH = 60. + DOBOT_SERVE_XY = (0., -200., 20.) + DOBOT_MAX_VELOCITY = 200 + DOBOT_MAX_ACCERALATION = 100 DOBOT_COORDINATE_CONVERTER = None @@ -58,14 +59,29 @@ class ProductionConfig(DefaultConfig): 'prd': ProductionConfig, } -Config = None +def get_config(env, dobot_port, tuner_file): + config = _ENV_TO_CONFIG[env]() -def get_config(env, dobot_port): - global Config - Config = _ENV_TO_CONFIG[env]() + config.DOBOT_SERIAL_PORT = dobot_port + config.DOBOT_COORDINATE_CONVERTER = CoordinateConverter.from_tuning_file(tuner_file) - Config.DOBOT_SERIAL_PORT = dobot_port - Config.DOBOT_COORDINATE_CONVERTER = CoordinateConverter.from_tuning_file() + serve_position = read_serve_location(tuner_file) + config.DOBOT_SERVE_XY = config.DOBOT_SERVE_XY if not serve_position else serve_position - return Config + return config + + +def read_serve_location(tuner_file): + tuner_data = [] + with open(tuner_file, 'r') as readfile: + for line in readfile: + if not line: + break + data = json.loads(line) + tuner_data.append(data) + + if len(tuner_data) < 5: + return None + else: + return tuner_data[4]['x'], tuner_data[4]['y'], tuner_data[4]['z'] diff --git a/setup/README.md b/setup/README.md index b7c8dc2..969a5b7 100644 --- a/setup/README.md +++ b/setup/README.md @@ -1,39 +1,5 @@ -Instruction -===== - -## Space Requirement -- More than 50cm x 55cm ( recommend 80cmx80cm ) space required. -- See [requirements.md](./requirements.md) for layouts. - - -## Hardware Components -- Robot Arm - - [Dobot Magician](http://dobot.cc/store/buy-dobot-magician.html) - - Update to the latest firmware. -- Document camera - - [Ipevo Ziggi-HD](https://www.amazon.com/dp/B01530XGMA) - - [Ipevo Height Extension Stand](https://www.amazon.com/dp/B00CTIF2O0) -- [A3 marker sheet](./image/marker_paper.pdf) - - for camera and robot arm to adjust their position -- Labeling tag - - [Post-it 75 x 25mm](https://www.amazon.com/dp/B0013MW3PO/) - - Yellow ones are easier to make good contrast with your pen color. -- Linux Box - - [Intel NUC kit](https://www.amazon.com/dp/B01DG1SEES) - - Core-i3 or i5 (included in Intel NUC kit) - - HDMI or mini HDMI cable is needed accordingly. - - 2 x [8GB Memory](https://www.amazon.com/dp/B00CQ35HBQ)) - - [SSD 250GB](https://www.amazon.com/dp/B0194MV5U8)(One of Sandisk’s long life (5 yrs) series is strongly recommended for price and stability. not samsung or other.) -- Tablet UI - - Nexus9 - - Microphone (internal or external) - - Chrome browser (to access linux box) -- See [requirements.md](./requirements.md) for the full list. - - - -## Step1 : Place hardware components and Marker sheet -1. Print out [the marker sheet](./image/marker_paper.pdf) in A3 paper and stick it on the center +# Step 1: Hardware setup +1. Print out [the marker sheet](./setup/image/marker_paper.pdf) in A3 paper and stick it on the center (This sheet will be used during both setup and demo.) 2. Build the robot arm by following manuals. 3. Place the robot arm to attach the A3 paper on A-D side. @@ -41,97 +7,173 @@ Instruction 5. Place the camera(CDVU-06IP) as shown below. In this case, camera should built with joint extender, CDVU-04IP-A1. Note: Due to unavailability of 'CDVU-04IP-A1' in some regions including japan, a small box of 27-32cm in height can be used instead. -![](./image/arrangement.png) -![](./image/robot_and_camera.png) +# Step 2: Setup of pc -## Step2 : Setup Linux box as controller PC. -1. Build your linux box by following direction of each manufacture. - - You might need to mount ssd and memory inside the linux box unless they are already built in. -2. Connect both the robot arm and the camera to linux box. - - The suction cup should be attached on arm end. - - In some cases, we experienced the robot's firmware beeing outdated. In such a case, DobotStudio is required to upgrade its firmware(See manuals). -3. Connect the linux box to the internet using LAN cable. -4. During setup you need a LCD display, a keyboard and a mouse. please prepare. -5. Install linux and softwares - - Ubuntu 16.04.1 Server 64bit (You may also try Desktop 64bit, if your PC is well supported by Ubuntu.) - - See [linux box.md](./linux_box.md) +## Basic setup +* Ubuntu 18.01 -#### Getting API credential. +### Update your installation +``` +$ sudo apt-get update && sudo apt-get upgrade -y && sudo reboot +``` +### Install Git, Python and curl +``` +$ sudo apt install git python2.7 python-pip curl ansible +``` + +### Clone this repository +``` +$ cd ~ +$ git clone {repo_url} +``` + +### Create the installation files folder +``` +$ mkdir -p _installationfiles/models +``` + +### Setting up the Google Cloud Platform access + + +#### Install gsutil +To install gsutil (python application for managing google could storage) it is easier to just install the google cloud SKD + +``` +$ cd ~ +$ curl https://sdk.cloud.google.com | bash +``` + +Close and reopen the shell for the changes to take effect. + +#### Setup account and APIs +#### Install gsutil +To install gsutil (python application for managing google could storage) it is easier to just install the google cloud SKD + +``` +$ cd ~ +$ curl https://sdk.cloud.google.com | bash +``` + +Close and reopen the shell for the changes to take effect. + +#### Setup account and APIs This demo requires API credential for Google Cloud Platform(GCP). If this is your first project to use GCP, you can get an account from [cloud.google.com](https://cloud.google.com/). 1. Create a new GCP project + 2. Enable the following APIs and services on [API Manager](https://support.google.com/cloud/answer/6158841) - Google Cloud Storage and Google Cloud Storage JSON API - Vision API - Speech API - Natural Language API - Cloud ML API -3. Create a service account key file -4. See [this doc](https://cloud.google.com/vision/docs/common/auth#set_up_a_service_account) to create a service account key + +3. Create a Storage Bucket - For all future steps, the name of this will be your bucket id. + +4. Create a service account key file + + See [this doc](https://cloud.google.com/vision/docs/common/auth#set_up_a_service_account) to create a service account key - Service account: Compute Engine default service account - Key type: JSON - - Save the JSON as /home/brainpad/FindYourCandy/credential.json + - Save the JSON as /home/{user}/FindYourCandy/credentials.json and a copy in /home as well - (* Saving to different path or filename may require editing webapp.ini later) -5. Set env variable + + + +5. Give the service account previously created access to the storage bucket. The required permissions are: + - Storage Object Admin (only this might suffice) + - Storage Object Creator + - Storage Object Viewer + +6. Set env variable - Add the following line (replace the path_to_your_own_credential_file with the actual JSON file path) to the last of `~/.bashrc` file. ``` export GOOGLE_APPLICATION_CREDENTIALS="path_to_your_own_credential_file" ``` -6. Reopen the shell so that it takes effect +7. Reopen the shell so that it takes effect -## Step3: Camera Calibation -The following instructions illustrates how to adjust the camera position. -1. Boot up linux box and login to the desktop. -2. Execute [(cd script ; python2 camera_tune.py)](./script/camera_tune.py) that is included in this demo software. And see the camera view in the window. -3. If you cannot see 'OK' sign in the window, tweak the camera or its extension and have whole image of A3 paper. - - You may also try to get better focus by switching the AF slide between S and C on the camera. - - In most cases, keeping the slide to S and pressing `[・] (focus)` button a few times does good in our environment. -4. You may click on left mouse button to exit this software. +### Models -![](./image/camera_calibration.png) +### English word vector +1. Download https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM +2. Copy the file to _installationfiles/models -## Step4: Robot Arm Caribration -(* Read the safety manuals of your Robot Arm , befor proceeding this section.) +### Inception v3 +``` +$ wget http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz +$ tar xvzf inception-2015-12-05.tgz +``` +Copy the .pb file to _installationfiles/models -1. Start uwsgi-robot - ``` - sudo systemctl stop nginx.service # just make sure - sudo systemctl start uwsgi-robot.service - ``` -2. Execute [(cd script ; python2 robot_tune.py)](./script/robot_tune.py) to start tuning the coordinates of arm. -3. Hit `Enter` key to initialize the robot arm. -4. Push the `release` button (which has symbol of `unlock` ) while you holding the robot arm by the other hand. Please be aware when the button is pressed, the robot arm looses friction and will start falling instantly. To avoid damaging your robot or desk, you should always assist robot arm when you press the `release` button. -5. Slowly land the arm edge to the center of `Maker A`. (still pressing the button.) Not on the letter 'A' but the target sign printed by the letter -6. Hit `Enter` key. -7. Repeat above 3,4 and 5 for Marker D and E. -8. The program saves those coordinates and ends automatically. -9. (You can see there are 3 lines of jsonl in `/tmp/robot_tuner.dat`.) -10. Restart to reload `/tmp/robot_tuner.dat`. - ``` - sudo systemctl stop nginx.service # just make sure - sudo systemctl restart uwsgi-robot.service - sudo systemctl start nginx.service # finally nginx starts here - ``` -![](./image/robot_calibration.png) +## Using Ansible to install requrements and move scripts + +### Copy hosts file +``` +$ cd setup/docker/host +$ cp hosts.example hosts.file +``` + +### Edit the hosts.file and replace the required values +``` +$ nano hosts.file + +some_user={the current users username} +fyc_home={root directory of Find your candy} +cloud_ml_bucket={the name of the Cloud Storage Bucket you created previously} +``` + +``` +$ ./setup.sh hosts.file +``` + + +### Execute the ansible script +``` +$ ./setup.sh hosts.file +``` + +This script will install Chrome, create required folders and move requried scripts. It will also build the docker images from source. + + + +### Upload ML model to Google Cloud storage +``` +$ cd /train +$ gcloud auth activate-service-account --key-file=../_installationfiles/credentials.json +$ ./build_package.sh gs://{bucket-id}/package/ +``` +Feel free to log on to [GCP Console](https://cloud.google.com), navigate to the storage bucket and ensure that the model has been uploaded. + + +## Success + +### Calibrate + +#### Calibrate camera +``` +$ bin/tune_camera.sh +``` -## Step5: Tablet -1. Bootup Nexus9 and login with a google account for demo. -2. Update firmware. -3. Connect the [Ethernet Adapter for Chromecast](https://store.google.com/product/ethernet_adapter_for_chromecast)('GL0402') to Nexus9’s OTG connector. This is a hack to provide power and ether connection through USB cable when wifi is not proper to use. -4. Follow the “Set up Chromecast” on the [google support page]( -https://support.google.com/chromecast/answer/2998456?hl=en) -\(there is instruction for Android 4.1 and higher\). -(* If you have a trouble with voice recognition of Nexus9, consider for external microphone.) +#### Calibrate robot arm +``` +$ bin/tune_robot.sh +``` -## Step6: Demo Application +### Run +``` +$ bin/start_all.sh +``` +Note that even though the command returns immediately, the webapp will take some time to start the first time. +To see the log output of the services, use the command +``` +$ bin/tail_logs.sh +``` -#### Configure and run demo - - See [README.md](../robot-arm) for robot-arm - - See [README.md](../webapp) for webapp +Go to settings and ensure that the correct microphone/input is selected, as this is often an issue it seems. diff --git a/setup/docker/README.md b/setup/docker/README.md new file mode 100644 index 0000000..1056b4b --- /dev/null +++ b/setup/docker/README.md @@ -0,0 +1,28 @@ +# Ansible/Docker setup + +This folder contains the required files to run the application under Docker, using Ansible to setup the machine. + +# Container +The docker images for the application is contained in the folder *container*. There are four images + +* Base +* OpenCV +* Robot +* Webapp + +The *base* image installs python, supervisor, nginx and other basic requirements to a base Ubuntu 18.04 image. The *OpenCV* image builds OpenCV 3.4.1 from source and installs it into the image. + +The dependencies of the images are as follows: +* Base -> OpenCV -> Webapp +* Base -> Robot + + +## Usage +The images can be built manually, or using the helper script *build.sh* that takes the name of the container as the argument, and automatically tags the image with the current time and *latest* tag. + + +# Host + +The host folder contains Ansible configuration files for setting up the system to use Docker. The roles folder contains configuration for installing docker, chrome and creating and moving script files and required files. The setup.sh can be used to execute the ansible script. + +NB! The script is currently only uable for local deployment. Some minor changes to the parameters passed to ansible in the script must be made to enable remote configuration. diff --git a/setup/docker/container/base/Dockerfile b/setup/docker/container/base/Dockerfile new file mode 100644 index 0000000..84d8d3a --- /dev/null +++ b/setup/docker/container/base/Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:18.04 + +RUN set -ex \ + && sed -i -e "s%http://archive.ubuntu.com/ubuntu/%http://no.archive.ubuntu.com/ubuntu/%g" /etc/apt/sources.list \ + && apt-get update && apt-get install -y \ + libblas3 \ + liblapack3 \ + nginx \ + python3-dev \ + python3-pip \ + supervisor \ + vim \ + && rm /etc/nginx/sites-enabled/default \ + && pip3 install --upgrade pip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* diff --git a/setup/docker/container/build.sh b/setup/docker/container/build.sh new file mode 100755 index 0000000..e691c6a --- /dev/null +++ b/setup/docker/container/build.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -eu + +cwd=$(dirname $0) +fyc_home=$(cd ${cwd}/../../.. && pwd) + +usage() { + echo "Usage: ${0##*/} [base|opencv|robot|webapp|speech]" +} + +if [ $# -lt 1 ]; then + usage + exit 1 +fi + +case "$1" in + base | opencv | robot | webapp | speech) + repos="computas/fyc-$1" + tag=$(date '+%Y%m%d%H%M') + + echo "${repos}:${tag}" + echo "" + + docker build -t ${repos}:${tag} -f ${cwd}/${1}/Dockerfile ${fyc_home} + docker tag ${repos}:${tag} ${repos}:latest + ;; + *) + usage + ;; +esac diff --git a/setup/docker/container/opencv/Dockerfile b/setup/docker/container/opencv/Dockerfile new file mode 100644 index 0000000..461e9d5 --- /dev/null +++ b/setup/docker/container/opencv/Dockerfile @@ -0,0 +1,56 @@ +FROM computas/fyc-base:latest + +RUN set -ex \ + && apt-get update && apt-get install -y \ + build-essential \ + cmake \ + unzip \ + pkg-config \ + libjpeg-dev \ + libpng-dev \ + libtiff-dev \ + libavcodec-dev \ + libavformat-dev \ + libswscale-dev \ + libv4l-dev \ + libxvidcore-dev \ + libx264-dev \ + libgtk-3-dev \ + libatlas-base-dev \ + gfortran \ + wget \ + && pip install numpy==1.14.5 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf ~/.cache/pip + +WORKDIR /opt +RUN set -ex \ + && wget -q -O opencv.zip https://github.com/opencv/opencv/archive/3.4.1.zip \ + && wget -q -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/3.4.1.zip \ + && unzip -q opencv.zip \ + && unzip -q opencv_contrib.zip \ + && mkdir opencv-3.4.1/build \ + && cd opencv-3.4.1/build \ + && cmake \ + -D CMAKE_BUILD_TYPE=RELEASE \ + -D CMAKE_INSTALL_PREFIX=/usr/local \ + -D OPENCV_EXTRA_MODULES_PATH=/opt/opencv_contrib-3.4.1/modules \ + -D BUILD_DOCS=OFF \ + -D BUILD_EXAMPLES=OFF \ + -D BUILD_TESTS=OFF \ + -D BUILD_PERF_TESTS=OFF \ + -D BUILD_opencv_java=OFF \ + -D WITH_TBB=ON \ + -D WITH_QT=OFF \ + -D WITH_CUDA=OFF \ + -D WITH_FFMPEG=OFF \ + .. \ + && make -j $(nproc) \ + && make install \ + && ldconfig \ + && cd /usr/local/lib/python3.6/dist-packages/ \ + && mv cv2.cpython-36m-x86_64-linux-gnu.so cv2.so \ + && cd /opt \ + && rm -f opencv.zip opencv_contrib.zip \ + && rm -rf opencv-3.4.1 opencv_contrib-3.4.1 \ diff --git a/setup/docker/container/robot/Dockerfile b/setup/docker/container/robot/Dockerfile new file mode 100644 index 0000000..c082c93 --- /dev/null +++ b/setup/docker/container/robot/Dockerfile @@ -0,0 +1,29 @@ +FROM computas/fyc-base:latest + +WORKDIR /opt/FindYourCandy +ARG container_dir="setup/docker/container/robot" + +# Python libraries +COPY robot-arm/requirements.txt robot-arm/ +COPY robot-arm/requirements robot-arm/requirements +RUN set -ex \ + && pip3 install -r robot-arm/requirements.txt \ + && rm -rf ~/.cache/pip + +# Nginx +COPY ${container_dir}/nginx-robot.conf /etc/nginx/conf.d/ + +# uWSGI +RUN set -ex && mkdir /etc/uwsgi /run/uwsgi +COPY ${container_dir}/uwsgi-robot.ini /etc/uwsgi/ + +# Supervisor +COPY ${container_dir}/supervisord-robot.conf /etc/supervisor/conf.d/ + +# Application +COPY robot-arm robot-arm +COPY setup/script/robot_tune.py setup/script/ +RUN set -ex && ln -sf /proc/$$/fd/1 robot-arm/logs/app.log + +EXPOSE 80 +CMD ["/usr/bin/supervisord"] diff --git a/setup/docker/container/robot/nginx-robot.conf b/setup/docker/container/robot/nginx-robot.conf new file mode 100644 index 0000000..bdb65c6 --- /dev/null +++ b/setup/docker/container/robot/nginx-robot.conf @@ -0,0 +1,7 @@ +server { + listen 80; + location / { + include uwsgi_params; + uwsgi_pass unix:///run/uwsgi/uwsgi.sock; + } +} diff --git a/setup/docker/container/robot/supervisord-robot.conf b/setup/docker/container/robot/supervisord-robot.conf new file mode 100644 index 0000000..9157192 --- /dev/null +++ b/setup/docker/container/robot/supervisord-robot.conf @@ -0,0 +1,16 @@ +[supervisord] +nodaemon = true + +[program:uwsgi] +command = /usr/local/bin/uwsgi --ini /etc/uwsgi/uwsgi-robot.ini +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:nginx] +command = /usr/sbin/nginx -g "daemon off;" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/setup/docker/container/robot/uwsgi-robot.ini b/setup/docker/container/robot/uwsgi-robot.ini new file mode 100644 index 0000000..780de17 --- /dev/null +++ b/setup/docker/container/robot/uwsgi-robot.ini @@ -0,0 +1,16 @@ +[uwsgi] + +chdir = /opt/FindYourCandy/robot-arm/ +module = run:app +pyargv = --tuner-file /opt/FindYourCandy/data/robot_tuner.dat --instance_path /opt/FindYourCandy/config +env = FLASK_ENV=prd + +master = true +processes = 1 +pidfile = /run/uwsgi/uwsgi.pid +die-on-term = true +disable-logging = true + +socket = /run/uwsgi/uwsgi.sock +chmod-socket = 666 +vacuum = true diff --git a/setup/docker/container/speech/Dockerfile b/setup/docker/container/speech/Dockerfile new file mode 100644 index 0000000..5e6590b --- /dev/null +++ b/setup/docker/container/speech/Dockerfile @@ -0,0 +1,30 @@ +FROM computas/fyc-base:latest + +WORKDIR /opt/FindYourCandy +ARG container_dir="setup/docker/container/speech" + +# Python libraries +COPY speech-app/requirements.txt speech-app/ +COPY speech-app/requirements speech-app/requirements +RUN set -ex \ + && pip3 install -r speech-app/requirements.txt \ + && rm -rf ~/.cache/pip + +# Nginx +#COPY ${container_dir}/nginx-speech.conf /etc/nginx/conf.d/ + +# uWSGI +#RUN set -ex && mkdir /etc/uwsgi /run/uwsgi +#COPY ${container_dir}/uwsgi-speech.ini /etc/uwsgi/ + +# Supervisor +# COPY ${container_dir}/supervisord-speech.conf /etc/supervisor/conf.d/ + +# Application +COPY speech-app speech-app +# RUN set -ex && ln -sf /proc/$$/fd/1 speech-app/logs/app.log + +ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/FindYourCandy/data/credentials.json + +EXPOSE 80 +CMD ["python3","speech-app/run.py"] diff --git a/setup/docker/container/webapp/Dockerfile b/setup/docker/container/webapp/Dockerfile new file mode 100644 index 0000000..9788e97 --- /dev/null +++ b/setup/docker/container/webapp/Dockerfile @@ -0,0 +1,30 @@ +FROM computas/fyc-opencv:latest + +WORKDIR /opt/FindYourCandy +ARG container_dir="setup/docker/container/webapp" + +# Python libraries +COPY webapp/requirements.txt webapp/ +COPY webapp/requirements webapp/requirements +RUN set -ex \ + && pip install -r webapp/requirements.txt \ + && rm -rf ~/.cache/pip + +# Nginx +COPY ${container_dir}/nginx-webapp.conf /etc/nginx/conf.d/ + +# uWSGI +RUN set -ex && mkdir /etc/uwsgi /run/uwsgi +COPY ${container_dir}/uwsgi-webapp.ini /etc/uwsgi/ + +# Supervisor +COPY ${container_dir}/supervisord-webapp.conf /etc/supervisor/conf.d/ + +# Application +COPY webapp webapp +COPY train train +COPY setup/script/camera_tune.py setup/script/ +RUN set -ex && ln -sf /proc/$$/fd/1 webapp/logs/app.log + +EXPOSE 80 +CMD ["/usr/bin/supervisord"] diff --git a/setup/docker/container/webapp/nginx-webapp.conf b/setup/docker/container/webapp/nginx-webapp.conf new file mode 100644 index 0000000..bdb65c6 --- /dev/null +++ b/setup/docker/container/webapp/nginx-webapp.conf @@ -0,0 +1,7 @@ +server { + listen 80; + location / { + include uwsgi_params; + uwsgi_pass unix:///run/uwsgi/uwsgi.sock; + } +} diff --git a/setup/docker/container/webapp/supervisord-webapp.conf b/setup/docker/container/webapp/supervisord-webapp.conf new file mode 100644 index 0000000..ae82416 --- /dev/null +++ b/setup/docker/container/webapp/supervisord-webapp.conf @@ -0,0 +1,16 @@ +[supervisord] +nodaemon = true + +[program:uwsgi] +command = /usr/local/bin/uwsgi --ini /etc/uwsgi/uwsgi-webapp.ini +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:nginx] +command = /usr/sbin/nginx -g "daemon off;" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/setup/docker/container/webapp/uwsgi-webapp.ini b/setup/docker/container/webapp/uwsgi-webapp.ini new file mode 100644 index 0000000..a448fa3 --- /dev/null +++ b/setup/docker/container/webapp/uwsgi-webapp.ini @@ -0,0 +1,17 @@ +[uwsgi] + +chdir = /opt/FindYourCandy/webapp/ +module = run:app +pyargv = --instance_path /opt/FindYourCandy/config +env = FLASK_ENV=prd +env = GOOGLE_APPLICATION_CREDENTIALS=/opt/FindYourCandy/data/credentials.json + +master = true +processes = 1 +pidfile = /run/uwsgi/uwsgi.pid +die-on-term = true +disable-logging = true + +socket = /run/uwsgi/uwsgi.sock +chmod-socket = 666 +vacuum = true diff --git a/setup/docker/host/hosts.example b/setup/docker/host/hosts.example new file mode 100644 index 0000000..f20b1bb --- /dev/null +++ b/setup/docker/host/hosts.example @@ -0,0 +1,7 @@ +[servers] +127.0.0.1 + +[servers:vars] +user=some_user +fyc_home=/home/some_user/FindYourCandy +cloud_ml_bucket=your_own_bucket_name diff --git a/setup/docker/host/requirements.txt b/setup/docker/host/requirements.txt new file mode 100644 index 0000000..ebda633 --- /dev/null +++ b/setup/docker/host/requirements.txt @@ -0,0 +1 @@ +ansible==2.3.0.0 diff --git a/setup/docker/host/roles/chrome/tasks/main.yml b/setup/docker/host/roles/chrome/tasks/main.yml new file mode 100644 index 0000000..9b82728 --- /dev/null +++ b/setup/docker/host/roles/chrome/tasks/main.yml @@ -0,0 +1,19 @@ +- name: Does the Google apt file exist? + command: test -f {{apt_file}} + register: google_apt_exists + ignore_errors: True + +- name: Add Google Chrome key + shell: wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - + when: google_apt_exists.rc == 1 + +- name: Add Google Chrome repo + copy: content="deb http://dl.google.com/linux/chrome/deb/ stable main" dest={{apt_file}} owner=root group=root mode=644 + when: google_apt_exists.rc == 1 + +- name: Update apt cache + apt: update_cache=yes + when: google_apt_exists.rc == 1 + +- name: Install Google Chrome + apt: pkg=google-chrome-stable state=installed \ No newline at end of file diff --git a/setup/docker/host/roles/docker/tasks/main.yml b/setup/docker/host/roles/docker/tasks/main.yml new file mode 100644 index 0000000..dc6411a --- /dev/null +++ b/setup/docker/host/roles/docker/tasks/main.yml @@ -0,0 +1,29 @@ +- name: Add docker group + group: name=docker + +- name: Install docker - install packages + apt: name={{item}} update_cache=yes + with_items: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + +- name: Install docker - add docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + +- name: Install docker - use stable repository + apt_repository: + repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable + +- name: Install docker - install docker + apt: name=docker-ce update_cache=yes + +- name: Install docker-compose + get_url: + url: "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-Linux-x86_64" + dest: /usr/local/bin/docker-compose + owner: root + group: root + mode: 0755 diff --git a/setup/docker/host/roles/fyc/files/docker-compose.yaml b/setup/docker/host/roles/fyc/files/docker-compose.yaml new file mode 100644 index 0000000..944a9ea --- /dev/null +++ b/setup/docker/host/roles/fyc/files/docker-compose.yaml @@ -0,0 +1,27 @@ +version: '2' +services: + webapp: + image: brainpad/fyc-webapp + devices: + - "/dev/videoziggi:/dev/video0" + ports: + - "18000:80" + volumes: + - ./config/webapp:/opt/FindYourCandy/config + - ./data:/opt/FindYourCandy/data + robot: + image: brainpad/fyc-robot + devices: + - "/dev/ttydobot:/dev/ttyUSB0" + ports: + - "18001:80" + volumes: + - ./config/robot:/opt/FindYourCandy/config + - ./data:/opt/FindYourCandy/data + speech: + image: computas/fyc-speech + ports: + - "18002:80" + volumes: + - ./config/robot:/opt/FindYourCandy/config + - ./data:/opt/FindYourCandy/data diff --git a/setup/docker/host/roles/fyc/files/udev/90-camera.rules b/setup/docker/host/roles/fyc/files/udev/90-camera.rules new file mode 100644 index 0000000..e4e24e0 --- /dev/null +++ b/setup/docker/host/roles/fyc/files/udev/90-camera.rules @@ -0,0 +1 @@ +SUBSYSTEMS=="video4linux", KERNEL=="video*", ATTRS{name}=="IPEVO Ziggi-HD Plus", MODE="0777", SYMLINK+="videoziggi", TAG+="systemd" diff --git a/setup/docker/host/roles/fyc/files/udev/90-dobot.rules b/setup/docker/host/roles/fyc/files/udev/90-dobot.rules new file mode 100644 index 0000000..0c07ece --- /dev/null +++ b/setup/docker/host/roles/fyc/files/udev/90-dobot.rules @@ -0,0 +1 @@ +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0777", SYMLINK+="ttydobot", TAG+="systemd" diff --git a/setup/docker/host/roles/fyc/meta/main.yml b/setup/docker/host/roles/fyc/meta/main.yml new file mode 100644 index 0000000..c554057 --- /dev/null +++ b/setup/docker/host/roles/fyc/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - {role: docker} diff --git a/setup/docker/host/roles/fyc/tasks/main.yml b/setup/docker/host/roles/fyc/tasks/main.yml new file mode 100644 index 0000000..692dcff --- /dev/null +++ b/setup/docker/host/roles/fyc/tasks/main.yml @@ -0,0 +1,102 @@ +- name: Add user to docker group + user: name={{user}} groups=docker append=yes + +- name: Create directories + file: + path: "{{fyc_home}}/{{item}}" + state: directory + owner: "{{user}}" + group: "{{user}}" + with_items: + - bin + - config/robot + - config/webapp + - data/models + - data/images + +- name: Copy scripts + template: + src: "bin/{{item}}.j2" + dest: "{{fyc_home}}/bin/{{item}}" + owner: "{{user}}" + group: "{{user}}" + mode: 0755 + with_items: + - restart_all.sh + - restart_robot.sh + - restart_webapp.sh + - start_all.sh + - start_robot.sh + - start_webapp.sh + - stop_all.sh + - stop_robot.sh + - stop_webapp.sh + - tail_logs.sh + - tune_camera.sh + - tune_robot.sh + - reset_model.sh + +- name: Copy config files + template: + src: "{{item.src}}" + dest: "{{item.dest}}" + owner: "{{user}}" + group: "{{user}}" + mode: 0644 + with_items: + - {src: "config-robot.py.j2", dest: "{{fyc_home}}/config/robot/config.py"} + - {src: "config-webapp.py.j2", dest: "{{fyc_home}}/config/webapp/config.py"} + +- name: Copy other files + copy: + src: "{{item.src}}" + dest: "{{item.dest}}" + owner: "{{user}}" + group: "{{user}}" + mode: 0644 + with_items: + - {src: "files/docker-compose.yaml", dest: "{{fyc_home}}/"} + - {src: "{{fyc_home}}/_installationfiles/credentials.json", dest: "{{fyc_home}}/data/"} + - {src: "{{fyc_home}}/_installationfiles/models/classify_image_graph_def.pb", dest: "{{fyc_home}}/data/models/"} + - {src: "{{fyc_home}}/_installationfiles/models/GoogleNews-vectors-negative300.bin.gz", dest: "{{fyc_home}}/data/models/"} + #- {src: "{{fyc_home}}/_installationfiles/models/model.vec", dest: "{{fyc_home}}/data/models/"} + +- name: Copy udev rules + copy: + src: "udev/{{item}}" + dest: /etc/udev/rules.d/ + owner: root + group: root + with_items: + - 90-camera.rules + - 90-dobot.rules + +- name: Copy service file + template: + src: fyc.service.j2 + dest: /etc/systemd/system/fyc.service + owner: root + group: root + +- name: Enable fyc service + systemd: name=fyc enabled=yes + +- name: Docker - Build base image + shell: ./build.sh base + args: + chdir: "{{fyc_home}}/setup/docker/container" + +- name: Docker - Build opencv image + shell: ./build.sh opencv + args: + chdir: "{{fyc_home}}/setup/docker/container" + +- name: Docker - Build robot image + shell: ./build.sh robot + args: + chdir: "{{fyc_home}}/setup/docker/container" + +- name: Docker - Build webapp image + shell: ./build.sh webapp + args: + chdir: "{{fyc_home}}/setup/docker/container" \ No newline at end of file diff --git a/setup/docker/host/roles/fyc/templates/bin/reset_model.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/reset_model.sh.j2 new file mode 100755 index 0000000..bd2a7ad --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/reset_model.sh.j2 @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu + +curl -sf -X POST http://localhost:18000/api/_reset > /dev/null 2>&1 +if [ $? != 0 ]; then + echo "Failed to reset." + exit 1 +fi +echo "Successfully reset." +exit 0 diff --git a/setup/docker/host/roles/fyc/templates/bin/restart_all.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/restart_all.sh.j2 new file mode 100755 index 0000000..4f8c0d1 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/restart_all.sh.j2 @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +systemctl restart fyc diff --git a/setup/docker/host/roles/fyc/templates/bin/restart_robot.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/restart_robot.sh.j2 new file mode 100755 index 0000000..4a597e2 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/restart_robot.sh.j2 @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +cwd=$(dirname $0) +cd $cwd + +./stop_robot.sh +./start_robot.sh diff --git a/setup/docker/host/roles/fyc/templates/bin/restart_webapp.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/restart_webapp.sh.j2 new file mode 100755 index 0000000..5a13c19 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/restart_webapp.sh.j2 @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +cwd=$(dirname $0) +cd $cwd + +./stop_webapp.sh +./start_webapp.sh diff --git a/setup/docker/host/roles/fyc/templates/bin/start_all.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/start_all.sh.j2 new file mode 100755 index 0000000..49fcbe2 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/start_all.sh.j2 @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +systemctl start fyc diff --git a/setup/docker/host/roles/fyc/templates/bin/start_robot.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/start_robot.sh.j2 new file mode 100755 index 0000000..27d8543 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/start_robot.sh.j2 @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +cd ${fyc_home} && docker-compose up -d robot diff --git a/setup/docker/host/roles/fyc/templates/bin/start_webapp.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/start_webapp.sh.j2 new file mode 100755 index 0000000..f29f8f9 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/start_webapp.sh.j2 @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +cd ${fyc_home} && docker-compose up -d webapp diff --git a/setup/docker/host/roles/fyc/templates/bin/stop_all.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/stop_all.sh.j2 new file mode 100755 index 0000000..6af9f8e --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/stop_all.sh.j2 @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +systemctl stop fyc diff --git a/setup/docker/host/roles/fyc/templates/bin/stop_robot.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/stop_robot.sh.j2 new file mode 100755 index 0000000..15aa03b --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/stop_robot.sh.j2 @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +cd ${fyc_home} && docker-compose stop robot +cd ${fyc_home} && docker-compose rm -f robot diff --git a/setup/docker/host/roles/fyc/templates/bin/stop_webapp.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/stop_webapp.sh.j2 new file mode 100755 index 0000000..e153a21 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/stop_webapp.sh.j2 @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +cd ${fyc_home} && docker-compose stop webapp +cd ${fyc_home} && docker-compose rm -f webapp diff --git a/setup/docker/host/roles/fyc/templates/bin/tail_logs.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/tail_logs.sh.j2 new file mode 100755 index 0000000..d673609 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/tail_logs.sh.j2 @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +cd ${fyc_home} && docker-compose logs -f --tail 100 diff --git a/setup/docker/host/roles/fyc/templates/bin/tune_camera.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/tune_camera.sh.j2 new file mode 100755 index 0000000..391bce6 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/tune_camera.sh.j2 @@ -0,0 +1,39 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +usage() { + echo "Usage: `basename $0` [-d camera_device]" +} + +while getopts :d:h opt; do + case $opt in + d) + camera_device=$OPTARG + ;; + h) + usage; + exit 0 + ;; + \?) + usage + exit 1 + ;; + esac +done + +: ${camera_device:=/dev/videoziggi} + +xhost +local:root +docker run -it --rm \ + -v ${fyc_home}/data:/opt/FindYourCandy/data \ + -v /tmp/.X11-unix:/tmp/.X11-unix:rw \ + --device "${camera_device}:/dev/video0" \ + --name fyc-camera-tune \ + --ipc host \ + -e DISPLAY \ + -e FLASK_ENV=prd \ + -e GOOGLE_APPLICATION_CREDENTIALS=/opt/FindYourCandy/data/credentials.json \ + computas/fyc-webapp \ + /bin/bash -c 'cd /opt/FindYourCandy/setup/script && /usr/bin/python3 camera_tune.py' diff --git a/setup/docker/host/roles/fyc/templates/bin/tune_robot.sh.j2 b/setup/docker/host/roles/fyc/templates/bin/tune_robot.sh.j2 new file mode 100755 index 0000000..36e0e83 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/bin/tune_robot.sh.j2 @@ -0,0 +1,33 @@ +#!/bin/sh +set -eu + +fyc_home={{fyc_home}} + +usage() { + echo "Usage: `basename $0` [-d dobot_device]" +} + +while getopts :d:h opt; do + case $opt in + d) + dobot_device=$OPTARG + ;; + h) + usage; + exit 0 + ;; + \?) + usage + exit 1 + ;; + esac +done + +: ${dobot_device:=/dev/ttydobot} + +docker run -it --rm \ + -v ${fyc_home}/data:/opt/FindYourCandy/data \ + --device "${dobot_device}:/dev/ttyUSB0" \ + --name fyc-robot-tune \ + computas/fyc-robot \ + /bin/bash -c 'cd /opt/FindYourCandy/setup/script && /usr/bin/python3 robot_tune.py --tuner-file /opt/FindYourCandy/data/robot_tuner.dat --mark-drop' diff --git a/webapp/tests/__init__.py b/setup/docker/host/roles/fyc/templates/config-robot.py.j2 similarity index 88% rename from webapp/tests/__init__.py rename to setup/docker/host/roles/fyc/templates/config-robot.py.j2 index 742bdaf..92efb61 100644 --- a/webapp/tests/__init__.py +++ b/setup/docker/host/roles/fyc/templates/config-robot.py.j2 @@ -12,3 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== + +from __future__ import absolute_import, division, print_function, unicode_literals diff --git a/setup/docker/host/roles/fyc/templates/config-webapp.py.j2 b/setup/docker/host/roles/fyc/templates/config-webapp.py.j2 new file mode 100644 index 0000000..28186ee --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/config-webapp.py.j2 @@ -0,0 +1,43 @@ +# Copyright 2017 BrainPad Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from __future__ import absolute_import, division, print_function, unicode_literals + +import os + +_data_dir = '/opt/FindYourCandy/data' + +DOWNLOAD_IMAGE_DIR = os.path.join(_data_dir, 'images') +MODEL_DIR = os.path.join(_data_dir, 'models') + +WORD2VEC_MODEL_FILES = { + 'en': { + 'file': os.path.join(MODEL_DIR, 'GoogleNews-vectors-negative300.bin.gz'), + 'binary': True, + }, +} +INCEPTION_MODEL_FILE = os.path.join(MODEL_DIR, 'classify_image_graph_def.pb') + +CLOUD_ML_BUCKET = 'gs://{{cloud_ml_bucket}}' +CLOUD_ML_PACKAGE_URIS = ['gs://{{cloud_ml_bucket}}/package/trainer-0.0.0.tar.gz'] +CLOUD_ML_TRAIN_DIR = 'gs://{{cloud_ml_bucket}}/{job_id}/checkpoints' +CLOUD_ML_LOG_DIR = 'gs://{{cloud_ml_bucket}}/logs/{job_id}' +CLOUD_ML_DATA_DIR = 'gs://{{cloud_ml_bucket}}/{job_id}/features' + +CLOUD_ML_BUCKET = 'gs://{{cloud_ml_bucket}}' +CLOUD_ML_PACKAGE_URIS = ['gs://{{cloud_ml_bucket}}/package/trainer-0.0.0.tar.gz'] +CLOUD_ML_TRAIN_DIR = 'gs://{{cloud_ml_bucket}}/{job_id}/checkpoints' +CLOUD_ML_LOG_DIR = 'gs://{{cloud_ml_bucket}}/logs/{job_id}' +CLOUD_ML_DATA_DIR = 'gs://{{cloud_ml_bucket}}/{job_id}/features' diff --git a/setup/docker/host/roles/fyc/templates/fyc.service.j2 b/setup/docker/host/roles/fyc/templates/fyc.service.j2 new file mode 100644 index 0000000..fcfd563 --- /dev/null +++ b/setup/docker/host/roles/fyc/templates/fyc.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=Find Your Candy +Requires=docker.service dev-videoziggi.device dev-ttydobot.device +After=docker.service dev-videoziggi.device dev-ttydobot.device + +[Service] +WorkingDirectory={{fyc_home}} +ExecStart=/usr/local/bin/docker-compose up +ExecStop=/usr/local/bin/docker-compose down +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/setup/docker/host/setup.sh b/setup/docker/host/setup.sh new file mode 100755 index 0000000..d9d2108 --- /dev/null +++ b/setup/docker/host/setup.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu + +if [ $# -lt 1 ]; then + echo "Usage: `basename $0` [host_file]" + exit 1 +fi + +cwd=$(dirname $0) + +ansible-playbook \ + -u friday \ + --ask-su-pass \ + -i $1 \ + --connection=local \ + ${cwd}/site.yml diff --git a/setup/docker/host/site.yml b/setup/docker/host/site.yml new file mode 100644 index 0000000..8038379 --- /dev/null +++ b/setup/docker/host/site.yml @@ -0,0 +1,8 @@ +- hosts: all + become: yes + become_method: sudo + vars: + - apt_file: /etc/apt/sources.list.d/google-chrome.list + roles: + - chrome + - fyc diff --git a/setup/legacy_scripts/README.md b/setup/legacy_scripts/README.md new file mode 100644 index 0000000..2528393 --- /dev/null +++ b/setup/legacy_scripts/README.md @@ -0,0 +1,3 @@ +# Description + +This folder contains scripts that can be used to simplify running the application directly on the machine. This setup is no longer recommended, as the Ansible/Docker setup is used for easy deployment. The scripts are preserved for future reference. \ No newline at end of file diff --git a/setup/legacy_scripts/restart_all.sh b/setup/legacy_scripts/restart_all.sh new file mode 100755 index 0000000..aa71842 --- /dev/null +++ b/setup/legacy_scripts/restart_all.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cwd=$(dirname $0) +cd $cwd + +./stop_all.sh +./start_all.sh diff --git a/setup/legacy_scripts/restart_nginx.sh b/setup/legacy_scripts/restart_nginx.sh new file mode 100755 index 0000000..3dea749 --- /dev/null +++ b/setup/legacy_scripts/restart_nginx.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cwd=$(dirname $0) +cd $cwd + +./stop_nginx.sh +./start_nginx.sh diff --git a/setup/legacy_scripts/restart_uwsgi_robot.sh b/setup/legacy_scripts/restart_uwsgi_robot.sh new file mode 100755 index 0000000..e98cd25 --- /dev/null +++ b/setup/legacy_scripts/restart_uwsgi_robot.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cwd=$(dirname $0) +cd $cwd + +./stop_uwsgi_robot.sh +./start_uwsgi_robot.sh diff --git a/setup/legacy_scripts/restart_uwsgi_webapp.sh b/setup/legacy_scripts/restart_uwsgi_webapp.sh new file mode 100755 index 0000000..32c988f --- /dev/null +++ b/setup/legacy_scripts/restart_uwsgi_webapp.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cwd=$(dirname $0) +cd $cwd + +./stop_uwsgi_webapp.sh +./start_uwsgi_webapp.sh diff --git a/setup/legacy_scripts/start_all.sh b/setup/legacy_scripts/start_all.sh new file mode 100755 index 0000000..eb9a3fe --- /dev/null +++ b/setup/legacy_scripts/start_all.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +cwd=$(dirname $0) +cd $cwd + +./start_nginx.sh +./start_uwsgi_robot.sh +./start_uwsgi_webapp.sh diff --git a/setup/legacy_scripts/start_nginx.sh b/setup/legacy_scripts/start_nginx.sh new file mode 100755 index 0000000..9054aaa --- /dev/null +++ b/setup/legacy_scripts/start_nginx.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo systemctl start nginx diff --git a/setup/legacy_scripts/start_uwsgi_robot.sh b/setup/legacy_scripts/start_uwsgi_robot.sh new file mode 100755 index 0000000..87ef65f --- /dev/null +++ b/setup/legacy_scripts/start_uwsgi_robot.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +sudo chmod 777 /dev/ttyUSB0 +sudo mkdir -p -m 777 /var/run/uwsgi +sudo chown friday:friday /var/run/uwsgi +sudo mkdir -p -m 777 /var/log/uwsgi +sudo chown friday:friday /var/log/uwsgi + +sudo systemctl start uwsgi-robot.service diff --git a/setup/legacy_scripts/start_uwsgi_webapp.sh b/setup/legacy_scripts/start_uwsgi_webapp.sh new file mode 100755 index 0000000..dc62ae1 --- /dev/null +++ b/setup/legacy_scripts/start_uwsgi_webapp.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +sudo chmod 777 /dev/video0 +sudo mkdir -p -m 777 /var/run/uwsgi +sudo chown brainpad:brainpad /var/run/uwsgi +sudo mkdir -p -m 777 /var/log/uwsgi +sudo chown brainpad:brainpad /var/log/uwsgi + +sudo systemctl start uwsgi-webapp.service diff --git a/setup/legacy_scripts/stop_all.sh b/setup/legacy_scripts/stop_all.sh new file mode 100755 index 0000000..0fd94f2 --- /dev/null +++ b/setup/legacy_scripts/stop_all.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +cwd=$(dirname $0) +cd $cwd + +./stop_nginx.sh +./stop_uwsgi_robot.sh +./stop_uwsgi_webapp.sh diff --git a/setup/legacy_scripts/stop_nginx.sh b/setup/legacy_scripts/stop_nginx.sh new file mode 100755 index 0000000..5840c98 --- /dev/null +++ b/setup/legacy_scripts/stop_nginx.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo systemctl stop nginx diff --git a/setup/legacy_scripts/stop_uwsgi_robot.sh b/setup/legacy_scripts/stop_uwsgi_robot.sh new file mode 100755 index 0000000..8cacd61 --- /dev/null +++ b/setup/legacy_scripts/stop_uwsgi_robot.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +sudo systemctl stop uwsgi-robot.service +pkill -f "/usr/local/bin/uwsgi --ini /etc/uwsgi/robot.ini" diff --git a/setup/legacy_scripts/stop_uwsgi_webapp.sh b/setup/legacy_scripts/stop_uwsgi_webapp.sh new file mode 100755 index 0000000..03b8016 --- /dev/null +++ b/setup/legacy_scripts/stop_uwsgi_webapp.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +sudo systemctl stop uwsgi-webapp.service +pkill -f "/usr/local/bin/uwsgi --ini /etc/uwsgi/webapp.ini" diff --git a/setup/legacy_scripts/tailf_accesslog_robot.sh b/setup/legacy_scripts/tailf_accesslog_robot.sh new file mode 100755 index 0000000..b35a8b0 --- /dev/null +++ b/setup/legacy_scripts/tailf_accesslog_robot.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +tail -f /var/log/nginx/access_robot.log diff --git a/setup/legacy_scripts/tailf_accesslog_webapp.sh b/setup/legacy_scripts/tailf_accesslog_webapp.sh new file mode 100755 index 0000000..712e392 --- /dev/null +++ b/setup/legacy_scripts/tailf_accesslog_webapp.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +tail -f /var/log/nginx/access_webapp.log diff --git a/setup/legacy_scripts/tailf_applog_robot.sh b/setup/legacy_scripts/tailf_applog_robot.sh new file mode 100755 index 0000000..5574404 --- /dev/null +++ b/setup/legacy_scripts/tailf_applog_robot.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +cwd=$(dirname $0) +fyc_home=${cwd}/.. + +tail -f ${fyc_home}/robot-arm/logs/app.log diff --git a/setup/legacy_scripts/tailf_applog_webapp.sh b/setup/legacy_scripts/tailf_applog_webapp.sh new file mode 100755 index 0000000..2f6201a --- /dev/null +++ b/setup/legacy_scripts/tailf_applog_webapp.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +cwd=$(dirname $0) +fyc_home=${cwd}/.. + +tail -f ${fyc_home}/webapp/logs/app.log diff --git a/setup/legacy_scripts/tailf_syslog.sh b/setup/legacy_scripts/tailf_syslog.sh new file mode 100755 index 0000000..9a6d650 --- /dev/null +++ b/setup/legacy_scripts/tailf_syslog.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +tail -f /var/log/syslog diff --git a/setup/legacy_scripts/tailf_uwsgilog_robot.sh b/setup/legacy_scripts/tailf_uwsgilog_robot.sh new file mode 100755 index 0000000..e6555f2 --- /dev/null +++ b/setup/legacy_scripts/tailf_uwsgilog_robot.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo tail -f /var/log/uwsgi/robot.log diff --git a/setup/legacy_scripts/tailf_uwsgilog_webapp.sh b/setup/legacy_scripts/tailf_uwsgilog_webapp.sh new file mode 100755 index 0000000..a956f30 --- /dev/null +++ b/setup/legacy_scripts/tailf_uwsgilog_webapp.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo tail -f /var/log/uwsgi/webapp.log diff --git a/setup/legacy_scripts/tune_camera.sh b/setup/legacy_scripts/tune_camera.sh new file mode 100755 index 0000000..6181223 --- /dev/null +++ b/setup/legacy_scripts/tune_camera.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cwd=$(dirname $0) +fyc_home=${cwd}/.. + +cd ${fyc_home}/setup/script +python camera_tune.py diff --git a/setup/legacy_scripts/tune_robot.sh b/setup/legacy_scripts/tune_robot.sh new file mode 100755 index 0000000..0bfd648 --- /dev/null +++ b/setup/legacy_scripts/tune_robot.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cwd=$(dirname $0) +fyc_home=${cwd}/.. + +cd ${fyc_home}/setup/script +python robot_tune.py diff --git a/setup/linux_box.md b/setup/linux_box.md index eed4b63..4d8bf6e 100644 --- a/setup/linux_box.md +++ b/setup/linux_box.md @@ -2,132 +2,103 @@ Linux Setup === ## OS installation -- Ubuntu 16.04.1 LTS Server +- Ubuntu 16.04.4 LTS Server or Desktop - Choose “standard system utilities” and “OpenSSH server” as software selection -- Create user: brainpad (group is also brainpad by default) ## Basic setup -- Install packages + +- Update packages ``` $ sudo apt-get update && sudo apt-get upgrade -y && sudo reboot -$ sudo apt-get install ubuntu-desktop -$ sudo apt-get install -y vim git build-essential -$ sudo apt-get install python-dev -$ sudo apt-get install nginx +$ sudo apt-get install ubuntu-desktop # if you have installed server edition ``` -Check what installed for python + +- Install Docker, git ``` -$ python -V -Python 2.7.12 +$ sudo apt-get install git +$ sudo apt-get install docker ``` -- Install gsutil - - You should also install `gsutil` along with [this instuction](https://cloud.google.com/sdk/docs/quickstart-linux). -- Check gsutil +- Install Google Chrome (needed for web app) ``` -$ gsutil -v -gsutil version: 4.22 +$ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - +$ sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' +$ sudo apt-get update && sudo apt-get install google-chrome-stable ``` -Also you can review what was already set. + +- Check Python version: ``` -$ gcloud info -... -core: [2017.02.21] -core-nix: [2017.02.21] -gcloud-deps: [2017.02.21] -gcloud: [] -gsutil-nix: [4.22] -gcloud-deps-linux-x86_64: [2017.02.21] -gsutil: [4.22] -bq: [2.0.24] -bq-nix: [2.0.24] -... -Cloud SDK on PATH: [True] -... -Account: [] -Project: [] -... +$ python -V +Python 2.7.12 ``` - -## pip installation +- Install python-pip ``` -$ wget https://bootstrap.pypa.io/get-pip.py -$ sudo python get-pip.py -$ sudo pip install numpy==1.12.0 +sudo apt-get install python-pip ``` -## OpenCV3.2 installation +- Install ansible (must be version 2.3.0.0) ``` -$ sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev -$ sudo apt-get install libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libgtk2.0-dev -$ mkdir ~/opencv_from_git -$ cd ~/opencv_from_git -$ git clone https://github.com/opencv/opencv.git -$ git clone https://github.com/opencv/opencv_contrib.git -$ git clone https://github.com/opencv/opencv_extra.git -$ cd ~/opencv_from_git/opencv/ -$ mkdir build -$ cd build -$ cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules/ -D BUILD_DOCS=ON -D WITH_TBB=ON .. -$ make -j7 -$ sudo make install +sudo pip install ansible==2.3.0.0 ``` -Note: Compiling opencv from source may result in a different binary. If you are suspitous about your built, check `ldd /usr/local/lib/python2.7/dist-packages/cv2.so` and compare with [this one](./cv2_dependings.txt). This might give you a clue what library is missing before compiling. - -## Permisions - -#### Permanent change -In order to keep the robot and camera accessible after reboot, you need to add user `brainpad` to group `dialout` and group `video`. +- Make sure that ~/.ansible is own by the user that will be running everything (i.e., not root) +- Check that everything is set up correctly ``` -sudo adduser brainpad dialout -sudo adduser brainpad video +ansible -i hosts.file servers -m ping -vvv ``` +- Create an SSH key, if necessary, save to default location (stting password to the key is not necessary): +``` +ssh-keygen -t rsa +ssh-add +``` -#### Temporary change -If you don't prefer making the changes permanent, you need to run the following commands. After you re-plugin usb or reboot the system, you need to repeat these steps. +- Copy SSH key to the machine: ``` -$ sudo chmod 777 /dev/ttyUSB0 - (* You may have the robot at /dev/ttyUSB1 instead of /dev/ttyUSB0, depending on your hardware.) -$ sudo chmod 777 /dev/video0 - (* If you have two or more web cameras, it may be /dev/video1 or else. ) +ssh-copy-id -i ~/.ssh/id_rsa.pub @/addq +sudo ./setup.sh hosts.file ~/.ssh/id_rsa ``` +- Create docker group and add current user to the group +``` +sudo groupadd docker +sudo gpasswd -a $USER docker +``` -## Install app +## Setup for FindYourCandy +- Copy hosts file from `hosts.example` to `hosts.file`: ``` -$ cd ~ -$ git clone https://github.com/BrainPad/FindYourCandy.git -$ cd ~/FindYourCandy -$ sudo pip install -r robot-arm/requirements.txt -$ sudo pip install -r webapp/requirements.txt +cd setup/docker/host +cp hosts.example hosts.file ``` -## Nginx setup -This is not mandatory, but this setup senario is based on nginx. -If you don't install, you have to figure out how to setup by yourself. - -Note: Do not start uWSGI yet! You need [Step4: Robot Arm Caribration](./README.md) first. +- Update `hosts.file` with IP to the machine that will be running the slution. If it is the same machine as the one you are running the commands from, use ```ifconfig```. To update the IP, make sure that you put the right IP under `[servers]` +- Create `_installationfiles` folder: +``` +$ mkdir -p ~/FindYourCandy/_installationfiles/models ``` -$ sudo cp ./setup/nginx_config_example/webapp.conf /etc/nginx/conf.d/ -$ sudo cp ./setup/nginx_config_example/robot.conf /etc/nginx/conf.d/ - -$ sudo mkdir /etc/uwsgi -$ sudo cp ./setup/nginx_config_example/webapp.ini /etc/uwsgi/ - (* You must edit webapp.ini and change the entry of GOOGLE_APPLICATION_CREDENTIALS later, after you create your credentials in following steps.) -$ sudo cp ./setup/nginx_config_example/robot.ini /etc/uwsgi/ - (* You need to modify robot.ini according to your environment.) -$ sudo mkdir -m 777 /var/run/uwsgi -$ sudo chown brainpad:brainpad /var/run/uwsgi -$ sudo mkdir -m 777 /var/log/uwsgi -$ sudo chown brainpad:brainpad /var/log/uwsgi +- Copy credentials.json to `_installationfiles` +- Copy `GoogleNews-vectors-negative300.bin.gz` to `_installationfiles/models` +- Copy inception model to `_installationfiles/models` +``` +$ cd ~/FindYourCandy/_installationfiles/models +$ wget http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz +$ tar xvzf inception-2015-12-05.tgz +``` -$ sudo cp ./setup/nginx_config_example/uwsgi-webapp.service /etc/systemd/system/ -$ sudo cp ./setup/nginx_config_example/uwsgi-robot.service /etc/systemd/system/ +``` +$ cd ~/FindYourCandy/setup/docker/container +$ ./build.sh base +$ ./build.sh opencv +$ ./build.sh robot +$ ./build.sh webapp +``` +- Tune the camera and the robot arm +``` +~/FindYourCandy/bin$ ./tune_camera.sh +~/FindYourCandy/bin$ ./tune_robot.sh ``` diff --git a/setup/nginx_config_example/robot.ini b/setup/nginx_config_example/robot.ini index 34c6d3c..a7dc67c 100644 --- a/setup/nginx_config_example/robot.ini +++ b/setup/nginx_config_example/robot.ini @@ -7,7 +7,7 @@ uid = brainpad gid = brainpad -master = false +master = true processes = 1 pidfile = /var/run/uwsgi/robot.pid daemonize = /var/log/uwsgi/robot.log diff --git a/setup/nginx_config_example/uwsgi-robot.service b/setup/nginx_config_example/uwsgi-robot.service index 348821a..0b38355 100644 --- a/setup/nginx_config_example/uwsgi-robot.service +++ b/setup/nginx_config_example/uwsgi-robot.service @@ -1,9 +1,14 @@ [Unit] Description=uWSGI service for robot -After=syslog.target +After=syslog.target dev-ttyUSB0.device +Requires=dev-ttyUSB0.device [Service] -ExecStartPre=/bin/mkdir -p -m 775 /var/run/uwsgi +ExecStartPre=/bin/chmod 777 /dev/ttyUSB0 +ExecStartPre=/bin/mkdir -p -m 777 /var/run/uwsgi +ExecStartPre=/bin/chown brainpad:brainpad /var/run/uwsgi +ExecStartPre=/bin/mkdir -p -m 777 /var/log/uwsgi +ExecStartPre=/bin/chown brainpad:brainpad /var/log/uwsgi ExecStart=/usr/local/bin/uwsgi --ini /etc/uwsgi/robot.ini RuntimeDirectory=/var/run/uwsgi Restart=always diff --git a/setup/nginx_config_example/uwsgi-webapp.service b/setup/nginx_config_example/uwsgi-webapp.service index 3c194bb..feacb6a 100644 --- a/setup/nginx_config_example/uwsgi-webapp.service +++ b/setup/nginx_config_example/uwsgi-webapp.service @@ -1,9 +1,14 @@ [Unit] Description=uWSGI service for webapp -After=syslog.target +After=syslog.target dev-video0.device +Wants=dev-video0.device [Service] -ExecStartPre=/bin/mkdir -p -m 775 /var/run/uwsgi +ExecStartPre=/bin/chmod 777 /dev/video0 +ExecStartPre=/bin/mkdir -p -m 777 /var/run/uwsgi +ExecStartPre=/bin/chown brainpad:brainpad /var/run/uwsgi +ExecStartPre=/bin/mkdir -p -m 777 /var/log/uwsgi +ExecStartPre=/bin/chown brainpad:brainpad /var/log/uwsgi ExecStart=/usr/local/bin/uwsgi --ini /etc/uwsgi/webapp.ini RuntimeDirectory=/var/run/uwsgi Restart=always @@ -11,7 +16,6 @@ KillSignal=SIGTERM StandardError=syslog Type=forking NotifyAccess=all -TimeoutSec=infinity [Install] WantedBy=multi-user.target diff --git a/setup/nginx_config_example/webapp.ini b/setup/nginx_config_example/webapp.ini index 7e14143..f710cab 100644 --- a/setup/nginx_config_example/webapp.ini +++ b/setup/nginx_config_example/webapp.ini @@ -8,7 +8,7 @@ uid = brainpad gid = brainpad -master = false +master = true processes = 1 pidfile = /var/run/uwsgi/webapp.pid daemonize = /var/log/uwsgi/webapp.log diff --git a/setup/old_setup.md b/setup/old_setup.md new file mode 100644 index 0000000..d3ff15e --- /dev/null +++ b/setup/old_setup.md @@ -0,0 +1,137 @@ +Instruction +===== + +## Space Requirement +- More than 50cm x 55cm ( recommend 80cmx80cm ) space required. +- See [requirements.md](./requirements.md) for layouts. + + +## Hardware Components +- Robot Arm + - [Dobot Magician](http://dobot.cc/store/buy-dobot-magician.html) + - Update to the latest firmware. +- Document camera + - [Ipevo Ziggi-HD](https://www.amazon.com/dp/B01530XGMA) + - [Ipevo Height Extension Stand](https://www.amazon.com/dp/B00CTIF2O0) +- [A3 marker sheet](./image/marker_paper.pdf) + - for camera and robot arm to adjust their position +- Labeling tag + - [Post-it 75 x 25mm](https://www.amazon.com/dp/B0013MW3PO/) + - Yellow ones are easier to make good contrast with your pen color. +- Linux Box + - [Intel NUC kit](https://www.amazon.com/dp/B01DG1SEES) + - Core-i3 or i5 (included in Intel NUC kit) + - HDMI or mini HDMI cable is needed accordingly. + - 2 x [8GB Memory](https://www.amazon.com/dp/B00CQ35HBQ)) + - [SSD 250GB](https://www.amazon.com/dp/B0194MV5U8)(One of Sandisk’s long life (5 yrs) series is strongly recommended for price and stability. not samsung or other.) +- Tablet UI + - Nexus9 + - Microphone (internal or external) + - Chrome browser (to access linux box) +- See [requirements.md](./requirements.md) for the full list. + + + +## Step1 : Place hardware components and Marker sheet +1. Print out [the marker sheet](./image/marker_paper.pdf) in A3 paper and stick it on the center +(This sheet will be used during both setup and demo.) +2. Build the robot arm by following manuals. +3. Place the robot arm to attach the A3 paper on A-D side. +4. Plugin the power supply unit of the robot arm to AC outlet. +5. Place the camera(CDVU-06IP) as shown below. In this case, camera should built with joint extender, CDVU-04IP-A1. +Note: Due to unavailability of 'CDVU-04IP-A1' in some regions including japan, a small box of 27-32cm in height can be used instead. + +![](./image/arrangement.png) +![](./image/robot_and_camera.png) + +## Step2 : Setup Linux box as controller PC. +1. Build your linux box by following direction of each manufacture. + - You might need to mount ssd and memory inside the linux box unless they are already built in. +2. Connect both the robot arm and the camera to linux box. + - The suction cup should be attached on arm end. + - In some cases, we experienced the robot's firmware beeing outdated. In such a case, DobotStudio is required to upgrade its firmware(See manuals). +3. Connect the linux box to the internet using LAN cable. +4. During setup you need a LCD display, a keyboard and a mouse. please prepare. +5. Install linux and softwares + - Ubuntu 16.04.1 Server 64bit (You may also try Desktop 64bit, if your PC is well supported by Ubuntu.) + - See [linux box.md](./linux_box.md) + + +#### Getting API credential. + +This demo requires API credential for Google Cloud Platform(GCP). If this is your first project to use GCP, you can get an account from [cloud.google.com](https://cloud.google.com/). + +1. Create a new GCP project +2. Enable the following APIs and services on [API Manager](https://support.google.com/cloud/answer/6158841) + - Google Cloud Storage and Google Cloud Storage JSON API + - Vision API + - Speech API + - Natural Language API + - Cloud ML API +3. Create a service account key file +4. See [this doc](https://cloud.google.com/vision/docs/common/auth#set_up_a_service_account) to create a service account key + - Service account: Compute Engine default service account + - Key type: JSON + - Save the JSON as /home/brainpad/FindYourCandy/credential.json + - (* Saving to different path or filename may require editing webapp.ini later) +5. Set env variable + - Add the following line (replace the path_to_your_own_credential_file with the actual JSON file path) to the last of `~/.bashrc` file. + + ``` + export GOOGLE_APPLICATION_CREDENTIALS="path_to_your_own_credential_file" + ``` + +6. Reopen the shell so that it takes effect + +## Step3: Camera Calibation +The following instructions illustrates how to adjust the camera position. + +1. Boot up linux box and login to the desktop. +2. Execute [(cd setup/script ; python2 camera_tune.py)](./script/camera_tune.py) that is included in this demo software. And see the camera view in the window. +3. If you cannot see 'OK' sign in the window, tweak the camera or its extension and have whole image of A3 paper. + - You may also try to get better focus by switching the AF slide between S and C on the camera. + - In most cases, keeping the slide to S and pressing `[・] (focus)` button a few times does good in our environment. +4. You may click on left mouse button to exit this software. + +![](./image/camera_calibration.png) + +## Step4: Robot Arm Caribration +(* Read the safety manuals of your Robot Arm , befor proceeding this section.) + + +1. Start uwsgi-robot + ``` + sudo systemctl stop nginx.service # just make sure + sudo systemctl start uwsgi-robot.service + ``` +2. Execute [(cd setup/script ; python2 robot_tune.py)](./script/robot_tune.py) to start tuning the coordinates of arm. +3. Hit `Enter` key to initialize the robot arm. +4. Push the `release` button (which has symbol of `unlock` ) while you holding the robot arm by the other hand. Please be aware when the button is pressed, the robot arm looses friction and will start falling instantly. To avoid damaging your robot or desk, you should always assist robot arm when you press the `release` button. +5. Slowly land the arm edge to the center of `Maker A`. (still pressing the button.) Not on the letter 'A' but the target sign printed by the letter +6. Hit `Enter` key. +7. Repeat above 3,4 and 5 for Marker D and E. +8. The program saves those coordinates and ends automatically. +9. (You can see there are 3 lines of jsonl in `/tmp/robot_tuner.dat`.) +10. Restart to reload `/tmp/robot_tuner.dat`. + ``` + sudo systemctl stop nginx.service # just make sure + sudo systemctl restart uwsgi-robot.service + sudo systemctl start nginx.service # finally nginx starts here + ``` + +![](./image/robot_calibration.png) + +## Step5: Tablet +1. Bootup Nexus9 and login with a google account for demo. +2. Update firmware. +3. Connect the [Ethernet Adapter for Chromecast](https://store.google.com/product/ethernet_adapter_for_chromecast)('GL0402') to Nexus9’s OTG connector. This is a hack to provide power and ether connection through USB cable when wifi is not proper to use. +4. Follow the “Set up Chromecast” on the [google support page]( +https://support.google.com/chromecast/answer/2998456?hl=en) +\(there is instruction for Android 4.1 and higher\). +(* If you have a trouble with voice recognition of Nexus9, consider for external microphone.) + +## Step6: Demo Application + +#### Configure and run demo + - See [README.md](../robot-arm) for robot-arm + - See [README.md](../webapp) for webapp diff --git a/setup/script/camera_tune.py b/setup/script/camera_tune.py index f9bfed8..3376745 100644 --- a/setup/script/camera_tune.py +++ b/setup/script/camera_tune.py @@ -29,7 +29,15 @@ from candysorter.config import get_config calibrator = ImageCalibrator(area=(1625, 1100), scale=550) -detector = CandyDetector.from_config(get_config(os.getenv('FLASK_ENV', 'dev'))) +config = get_config(os.getenv('FLASK_ENV', 'dev')) + +# Convert config from class object to dictionary for compatability with flask +config_dict = {} +for key in dir(config): + if key.isupper(): + config_dict[key] = getattr(config, key) + +detector = CandyDetector().from_config(config_dict) should_exit = False @@ -60,51 +68,61 @@ def detect_corners(image): return corners -def draw_detection(image): - candies = detector.detect(image) +def draw_detection(image, candies): for candy in candies: cv2.polylines(image, np.int32([np.array(candy.box_coords)]), isClosed=True, color=(0, 0, 255), lineType=cv2.LINE_AA, thickness=3) - font = cv2.FONT_HERSHEY_PLAIN -capture = cv2.VideoCapture(0) -capture.set(3, 1920) -capture.set(4, 1080) - -# Attempt to display using cv2 -cv2.namedWindow('Tuning Camera', cv2.WINDOW_KEEPRATIO | cv2.WINDOW_NORMAL) -cv2.resizeWindow('Tuning Camera', 960, 540) -cv2.setMouseCallback('Tuning Camera', mouse_event) - -w2_size = (480, 270) -cv2.namedWindow('Detection', cv2.WINDOW_KEEPRATIO | cv2.WINDOW_NORMAL) -cv2.resizeWindow('Detection', *w2_size) -cv2.setMouseCallback('Detection', mouse_event) - -while True: - time.sleep(0.3) - if capture.isOpened: - ret, frame = capture.read() - if not ret: + +def main(): + + capture = cv2.VideoCapture(0) + capture.set(3, 1920) + capture.set(4, 1080) + + # Attempt to display using cv2 + cv2.namedWindow('Tuning Camera', cv2.WINDOW_KEEPRATIO | cv2.WINDOW_NORMAL) + cv2.resizeWindow('Tuning Camera', 960, 540) + cv2.setMouseCallback('Tuning Camera', mouse_event) + + w2_size = (960, 540) + cv2.namedWindow('Detection', cv2.WINDOW_KEEPRATIO | cv2.WINDOW_NORMAL) + cv2.resizeWindow('Detection', *w2_size) + cv2.setMouseCallback('Detection', mouse_event) + + candies = None + counter = 0 + + while True: + time.sleep(0.01) + if capture.isOpened: + ret, frame = capture.read() + if not ret: + break + corners = detect_corners(frame) + corners = detect_corners(frame) + if corners is not None: + cropped = calibrator.calibrate(frame) + if (counter % 5 == 0): + candies = detector.detect(cropped) + draw_detection(cropped, candies) + cv2.imshow('Detection', cropped) + write_ok(frame) + counter += 1 + else: + blank = np.zeros((w2_size[1], w2_size[0], 3), np.uint8) + write_message(blank, 'Marker detection failed', size=1, thickness=1) + cv2.imshow('Detection', blank) + + write_message(frame, 'Click L-button of mouse to exit') + cv2.imshow('Tuning Camera', frame) + cv2.waitKey(1) + if should_exit: break - corners = detect_corners(frame) - if corners is not None: - cropped = calibrator.calibrate(frame) - draw_detection(cropped) - cv2.imshow('Detection', cropped) - write_ok(frame) - else: - blank = np.zeros((w2_size[1], w2_size[0], 3), np.uint8) - write_message(blank, 'Marker detection failed', size=1, thickness=1) - cv2.imshow('Detection', blank) - - write_message(frame, 'Click L-button of mouse to exit') - cv2.imshow('Tuning Camera', frame) - cv2.waitKey(1) - - if should_exit: - break - -print("Exit.") -cv2.destroyAllWindows() + + print("Exit.") + cv2.destroyAllWindows() + +if __name__ == '__main__': + main() diff --git a/setup/script/robot_tune.py b/setup/script/robot_tune.py index 909cf87..df87726 100644 --- a/setup/script/robot_tune.py +++ b/setup/script/robot_tune.py @@ -16,6 +16,7 @@ import argparse import json import sys +import time import requests @@ -23,6 +24,7 @@ from dobot.client import Dobot from dobot.utils import detect_dobot_port, dobot_is_on_port + DEFAULT_BAUDRATE = 115200 @@ -34,22 +36,42 @@ def get_position(self): pose = self.dobot.get_pose() return {'x': pose['x'], 'y': pose['y'], 'z': pose['z']} + def get_rotation(self): + pose = self.dobot.get_pose() + return {'r': pose['r']} + def initialize(self): self.dobot.initialize() + def wait(self): + self.dobot.wait() + + def move(self, x, y, z=0, r=0): + self.dobot.move(x, y, z, r) + + def grip(self, on): + self.dobot.grip(on) + + def pump(self, on): + self.dobot.pump(on) + + def rotate_gripper(self, delta): + pose = self.dobot.get_pose() + self.dobot.adjust_r(pose['r'] + delta) + class HTTPDobotCalibrator(object): base_url = "" def __init__(self, ipaddress): self.base_url = "http://{}".format(ipaddress) - print self.base_url + print(self.base_url) def get_position(self): r = requests.get(self.base_url + '/api/status') if 200 != r.status_code: - print "Error: unable to connect to server." + print("Error: unable to connect to server.") msg = "Error: Please check network or the 'robot api' is working on host machine." raise Exception(msg) @@ -63,28 +85,33 @@ def get_position(self): def initialize(self): requests.post(self.base_url + '/api/init') + def grip(self, on): + raise NotImplementedError + + def rotate_gripper(self, cw=True): + raise NotImplementedError + def _request(url): r = requests.get(url) if 200 != r.status_code: - print "Error: unable to connect to server." + print("Error: unable to connect to server.") msg = "Error: Please check network or the 'robot api' is working on host machine." raise Exception(msg) return r.content def wait_for_keystroke(mark_id): - raw_input( - "Push the button (marked as 'unlock') which is located in middle of arm) to release the arm and then slowly move the arm edge to slightly touch \n '{}' on marker sheet.\nAfter you finished, press Enter.".format( - mark_id)) - + input("Push the button (marked as 'unlock') which is located in middle of arm) to release the arm and then slowly move the arm edge to slightly touch \n'{}' on marker sheet.\nAfter you finished, press Enter.".format(mark_id)) if '__main__' == __name__: parser = argparse.ArgumentParser(description='Run Dobot WebAPI.') parser.add_argument('--http', dest='http', action='store_true', default=False) parser.add_argument('--api-uri', type=str, default="127.0.0.1:8000") parser.add_argument('--dobot-port', type=str, default=None) - parser.add_argument('--tuner-file', type=str, default='/tmp/robot_tuner.dat') + parser.add_argument('--tuner-file', type=str, default='/var/tmp/robot_tuner.dat') + parser.add_argument('--use-gripper', action='store_true', default=False) + parser.add_argument('--mark-drop', action='store_true', default=False) args = parser.parse_args() @@ -107,29 +134,77 @@ def wait_for_keystroke(mark_id): val_arr = [] - raw_input("PRESS Enter to start dobot arm initialization protocol.") + print("---Calibrate robot arm---") + print("When asked to place the robot on the marker, place it on the CROSS NEXT TO THE MARKER, not the letter.") + print() + print("Before you begin, please use the arm-release button located at the top of the robot arm and move the arm to \ +3'o clock. \nEnsure that the arm can move freely and that there are no obstacles to the right of the robot") + + print() + input("PRESS Enter to start dobot arm initialization protocol.") tuner.initialize() - print "" + + if args.use_gripper: + tuner.grip(True) + tuner.wait() + time.sleep(1) + tuner.pump(False) + + print("") wait_for_keystroke("Marker A") value = tuner.get_position() - print ">> Marker A(x,y,z)={}".format(value) + print(">> Marker A(x,y,z)={}".format(value)) val_arr.append(value) - print "" + print("") wait_for_keystroke("Marker D") value = tuner.get_position() - print ">> Marker D(x,y,z)={}".format(value) + print(">> Marker D(x,y,z)={}".format(value)) val_arr.append(value) - print "" + print("") wait_for_keystroke("Marker E") value = tuner.get_position() - print ">> Marker E(x,y,z)={}".format(value) + print(">> Marker E(x,y,z)={}".format(value)) val_arr.append(value) - print "" - with open('/tmp/robot_tuner.dat', 'w') as writefile: + if args.use_gripper: + print("") + # average_z = sum([v['z'] for v in val_arr]) / 3 + 4 + tuner.move(value['x'], value['y'], value['z'] + 3, 0) + tuner.grip(0) + tuner.wait() + time.sleep(1) + tuner.pump(0) + + + print("Positive values turns the gripper counter clockwise, negative clockwise.") + while True: + angle = input("Enter rotation delta in degrees or 0 to finish: ") + if angle == '0': + break + + tuner.rotate_gripper(float(angle)) + + value = tuner.get_rotation() + print(">> Rotation={}".format(value)) + val_arr.append(value) + else: + value = {'r': 0} + val_arr.append(value) + + if args.mark_drop: + print('') + print('Move the arm to the drop-off location (including height)') + input('After you finished, press Enter') + + value = tuner.get_position() + val_arr.append(value) + + + print("") + with open(args.tuner_file, 'w') as writefile: for entry in val_arr: json.dump(entry, writefile) writefile.write('\n') diff --git a/speech-app/README.md b/speech-app/README.md new file mode 100644 index 0000000..a6d2990 --- /dev/null +++ b/speech-app/README.md @@ -0,0 +1,27 @@ +# About +This folder contains a simple python applicaton that works as a relay to stream audio to Google Cloud Speech-to-Text API and return the recognized sentence. It is designed to work for single utterance. + +# Usage +This app is hosted in its own container in production, but can be used standalone. For production setup, see the setup/ readme. + +The first packet is expected to contain configuration data in the form og a json object. The supported options are + +|Name | Legal Values| +|----------------- |--------------------------------------- | +| lang | A BCP-47 language tag | +| sample_rate | The sample rate of the audio streamed | +| interim_results | true or false | + +## Installation +To run the app outisde of docker, ensure that you have python and pip installed, and activate a clean virtual environment. + +``` +$ cd speech-app +$ pip install -r requirements.txt +``` + +## Credentials +To use this you need to have valid Google Cloud credentials, and have them available for the google api to find. Refer to the google Cloud documentation for how to ensure this. If this is not present the application will return a "END_OF_SPEECH" event already in the first response. + +# Technical +This app works by utilizing the websocket library, which is based on asyncio. By using async handlers, the audio data is read and the results returned on the same thread using async/wait constructions. The communication with the Cloud Speech-to-Text API is handeled on a dedicated thread. Messages are passed between the two threads using queues. The library janus is used to provide a queue with both async and synchronous methods. diff --git a/speech-app/app/__init__.py b/speech-app/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/speech-app/app/config.json b/speech-app/app/config.json new file mode 100644 index 0000000..c0b214c --- /dev/null +++ b/speech-app/app/config.json @@ -0,0 +1,37 @@ +{ + "box-candy": { + "stems": [ + "", + "Kan jeg få", + "Jeg har lyst på", + "Jeg liker" + ], + "words": [ + "sjokolade", + "lakris", + "smurf", + "tyggis", + "tyggegummi" + ] + }, + "twist": { + "stems": [ + "", + "Kan jeg få", + "Jeg har lyst på", + "Jeg liker" + ], + "words": [ + "nøtti", + "nøtter", + "karamel", + "marsipan", + "japp", + "daim", + "cocos", + "kokkos", + "eclaires", + "toffee" + ] + } +} \ No newline at end of file diff --git a/speech-app/app/handlers.py b/speech-app/app/handlers.py new file mode 100644 index 0000000..8184aba --- /dev/null +++ b/speech-app/app/handlers.py @@ -0,0 +1,45 @@ +import asyncio +import json + +from google.cloud.speech_v1p1beta1 import types +from app.utils import loop_through_responses, audio_generator + + +async def consumer_handler(websocket, audio_buffer): + try: + async for message in websocket: + audio_buffer.put(message) + except asyncio.CancelledError: + # Task requested to terminate, just return + return + + except Exception as e: + # TODO: Log exception + return + + +async def producer_handler(websocket, response_buffer): + try: + while True: + response = await response_buffer.get() + await websocket.send(json.dumps(response)) + response_buffer.task_done() + + if response['event'] == "end_of_speech": + return + + except asyncio.CancelledError: + # Task requested to terminate, just return + return + + except Exception as e: + # TODO: Log exception + return + + +def gcp_handler(audio_buffer, response_buffer, client, config): + # Create requests generator using the audio generator, adapted from transcribe-streaming sample + requests = (types.StreamingRecognizeRequest(audio_content=content) for content in audio_generator(audio_buffer)) + responses = client.streaming_recognize(config, requests) + + loop_through_responses(responses, response_buffer) \ No newline at end of file diff --git a/speech-app/app/phrases.py b/speech-app/app/phrases.py new file mode 100644 index 0000000..412a545 --- /dev/null +++ b/speech-app/app/phrases.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import json + + +class PhraseGenerator: + @classmethod + def get_phrases(self, filename, key): + with open(filename, 'r', encoding='utf8') as config: + self.data = json.loads(config.read()) + + if key not in self.data.keys(): + return [] + + sentences = [] + for stem in self.data[key]['stems']: + for word in self.data[key]['words']: + sentences.append(stem + " " + word) + + return sentences \ No newline at end of file diff --git a/speech-app/app/utils.py b/speech-app/app/utils.py new file mode 100644 index 0000000..ca9e81c --- /dev/null +++ b/speech-app/app/utils.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import queue + +# GCP imports +from google.cloud.speech_v1p1beta1 import enums +from google.cloud.speech_v1p1beta1 import SpeechClient +from google.cloud.speech_v1p1beta1 import types + +from app.phrases import PhraseGenerator + + +def get_client(lang='en-US', sample_rate=16000, interim_results=False, + single_utterance=True, phrase_key=""): + """ + Helper to return client and config + """ + client = SpeechClient() + config = types.StreamingRecognitionConfig( + config=types.RecognitionConfig( + encoding=enums.RecognitionConfig.AudioEncoding.LINEAR16, + sample_rate_hertz=sample_rate, + language_code=lang, + # Enhanced models are only available to projects that + # opt in for audio data collection. + use_enhanced=True, + # A model must be specified to use enhanced model. + model="command_and_search", + speech_contexts=[types.SpeechContext( + phrases=PhraseGenerator.get_phrases("app/config.json", phrase_key), + )] + ), + interim_results=interim_results, + single_utterance=single_utterance + ) + print(str(config)) + return client, config + + +def loop_through_responses(responses, result_buffer): + # Go through the responses returned from the streaming client. + for response in responses: + + # TODO: Check to see if result is error or end of utterance + if not len(response.results): # Should test for single utterance in a better way + # No results, so error or end of utterance + + result_buffer.put({ + 'event': 'end_of_speech', + 'transcript': '' + }) + + # Wait for all messages put into the results buffer to be processed + result_buffer.join() + return + + else: + # We'll only use the first result, as this usually contains the result as it is building up + result = response.results[0] + + # Put the result into the result buffer + result_buffer.put({ + 'event': 'result', + 'transcript': result.alternatives[0].transcript + }) + + # Wait for all messages put into the results buffer to be processed + result_buffer.join() + + +def audio_generator(buffer): + while True: + """ + A generator that fetches the input audio data from the buffer. + Copied from google-cloud samples for speech: transcribe_streaming_mic with some alterations + """ + # Use a blocking get() to ensure there's at least one chunk of + # data, and stop iteration if the chunk is None, indicating the + # end of the audio stream. + chunk = buffer.get() + if chunk is None: + return + data = [chunk] + + # Now consume whatever other data's still buffered. + while True: + try: + chunk = buffer.get(block=False) + if chunk is None: + return + data.append(chunk) + except queue.Empty: + break + yield b''.join(data) diff --git a/speech-app/requirements.txt b/speech-app/requirements.txt new file mode 100644 index 0000000..5603c37 --- /dev/null +++ b/speech-app/requirements.txt @@ -0,0 +1 @@ +-r requirements/base.txt diff --git a/speech-app/requirements/base.txt b/speech-app/requirements/base.txt new file mode 100644 index 0000000..92090df --- /dev/null +++ b/speech-app/requirements/base.txt @@ -0,0 +1,3 @@ +google-cloud-speech +janus +websockets \ No newline at end of file diff --git a/speech-app/run.py b/speech-app/run.py new file mode 100644 index 0000000..c9b30a3 --- /dev/null +++ b/speech-app/run.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +import asyncio +import websockets + +# GCP imports +from google.cloud.speech_v1p1beta1 import types + +import queue +import janus +import json + +from app.handlers import producer_handler, consumer_handler, gcp_handler +from app.utils import get_client + + +async def handler(websocket, path): + """ + Main handler for the websocket connection. Reads the first message as this should be config information, + and then hands the connection overt to a receiver and producer handlers for sending messages. The receive + handlers will read and put messages into a queue. An independent thread reads the received + audio data and relays this into the GCP API. It puts the responses received from GCP into a queue that + is read by the send handler and returned to the client over the websocket connection. + """ + + # The first message from the browser should be json formatted config info + config_data = await websocket.recv() + config = json.loads(config_data) + + # Get config based on clients data + api_client, api_config = get_client(**config) + + # Create a thread safe buffer for audio data + audio_buffer = queue.Queue() + + # Get hold of asyncio event loop, needed for response queue and tasks + loop = asyncio.get_event_loop() + + # Create a async/sync queue for responses + response_queue = janus.Queue(loop=loop) + + # Create tasks for receiving, sending and processing the data + # Send/receive will be using asyncio, while processing will be on it's own thread + tasks = [ + loop.create_task(consumer_handler(websocket, audio_buffer)), # Handle incoming messages + loop.create_task(producer_handler(websocket, response_queue.async_q)), # Handle sending responses back + loop.run_in_executor(None, gcp_handler, audio_buffer, + response_queue.sync_q, api_client, api_config) # Process gcp requests + ] + + # Wait for one to complete. either the consumer completes because of loss of connection, + # or the final result has been received from GCP + done, pending = await asyncio.wait( + tasks, + return_when=asyncio.FIRST_COMPLETED, + ) + + # Cancel the remaining task. This might not kill the thread? + for task in pending: + task.cancel() + + # Close the socket + await websocket.close() + + +start_server = websockets.serve(handler, '', 80) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/speech-app/test/audio.raw b/speech-app/test/audio.raw new file mode 100644 index 0000000..5ebf79d Binary files /dev/null and b/speech-app/test/audio.raw differ diff --git a/speech-app/test/audio2.raw b/speech-app/test/audio2.raw new file mode 100644 index 0000000..35413b7 Binary files /dev/null and b/speech-app/test/audio2.raw differ diff --git a/speech-app/test/audio_sender.py b/speech-app/test/audio_sender.py new file mode 100644 index 0000000..ea27e0a --- /dev/null +++ b/speech-app/test/audio_sender.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + + +"""Based on Google Cloud Speech API sample application using the streaming API. + +Example usage: + python audio_sender.py resources/audio.raw +""" + +# [START import_libraries] +import argparse +import io +import sys +import json + +import asyncio +import websockets # pip install websockets + + +# [START def_transcribe_streaming] +async def transcribe_streaming(stream_file): + """Streams raw data of the given audio file.""" + # [START migration_streaming_request] + stream = [] + + for file in ['audio2.raw']: + with io.open(file, 'rb') as audio_file: + stream.append(audio_file.read()) + + async with websockets.connect('ws://localhost:8765') as websocket: + await websocket.send('{"sample_rate":16000, "interim_results":true}') + for content in stream: + await websocket.send(content) + + async for message in websocket: + data = json.loads(message) + if data['is_final']: + print(data['transcript']) + break + else: + sys.stdout.write(data['transcript'] + '\r') + sys.stdout.flush() + await asyncio.sleep(0.05) + +# [END def_transcribe_streaming] + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('stream', help='File to stream to the API') + args = parser.parse_args() + + asyncio.get_event_loop().run_until_complete(transcribe_streaming(args.stream)) diff --git a/speech-app/test/mic_sender.py b/speech-app/test/mic_sender.py new file mode 100644 index 0000000..2186453 --- /dev/null +++ b/speech-app/test/mic_sender.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python + +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Speech API sample application using the streaming API. + +NOTE: This module requires the additional dependency `pyaudio`. To install +using pip: + + pip install pyaudio + +Example usage: + python transcribe_streaming_mic.py +""" + +# [START import_libraries] +from __future__ import division + +import re +import sys +import asyncio +import websockets # pip install websockets + +from google.cloud import speech_v1p1beta1 as speech +from google.cloud.speech_v1p1beta1 import enums +from google.cloud.speech_v1p1beta1 import types +import pyaudio +from six.moves import queue +# [END import_libraries] + +# Audio recording parameters +RATE = 16000 +CHUNK = int(RATE / 10) # 100ms + + +class MicrophoneStream(object): + """Opens a recording stream as a generator yielding the audio chunks.""" + def __init__(self, rate, chunk): + self._rate = rate + self._chunk = chunk + + # Create a thread-safe buffer of audio data + self._buff = queue.Queue() + self.closed = True + + def __enter__(self): + self._audio_interface = pyaudio.PyAudio() + self._audio_stream = self._audio_interface.open( + format=pyaudio.paInt16, + # The API currently only supports 1-channel (mono) audio + # https://goo.gl/z757pE + channels=1, rate=self._rate, + input=True, frames_per_buffer=self._chunk, + # Run the audio stream asynchronously to fill the buffer object. + # This is necessary so that the input device's buffer doesn't + # overflow while the calling thread makes network requests, etc. + stream_callback=self._fill_buffer, + ) + + self.closed = False + + return self + + def __exit__(self, type, value, traceback): + self._audio_stream.stop_stream() + self._audio_stream.close() + self.closed = True + # Signal the generator to terminate so that the client's + # streaming_recognize method will not block the process termination. + self._buff.put(None) + self._audio_interface.terminate() + + def _fill_buffer(self, in_data, frame_count, time_info, status_flags): + """Continuously collect data from the audio stream, into the buffer.""" + self._buff.put(in_data) + return None, pyaudio.paContinue + + def generator(self): + while not self.closed: + # Use a blocking get() to ensure there's at least one chunk of + # data, and stop iteration if the chunk is None, indicating the + # end of the audio stream. + chunk = self._buff.get() + if chunk is None: + return + data = [chunk] + + # Now consume whatever other data's still buffered. + while True: + try: + chunk = self._buff.get(block=False) + if chunk is None: + return + data.append(chunk) + except queue.Empty: + break + + yield b''.join(data) +# [END audio_stream] + +async def send_mic(): + # See http://g.co/cloud/speech/docs/languages + # for a list of supported languages. + language_code = 'nb-NO' # a BCP-47 language tag + + + with MicrophoneStream(RATE, CHUNK) as stream: + async with websockets.connect('ws://localhost:8765') as websocket: + for content in stream.generator(): + await websocket.send(content) + + + +if __name__ == '__main__': + asyncio.get_event_loop().run_until_complete(send_mic()) diff --git a/speech-app/test/quit.raw b/speech-app/test/quit.raw new file mode 100644 index 0000000..a01dfc4 Binary files /dev/null and b/speech-app/test/quit.raw differ diff --git a/train/README.md b/train/README.md new file mode 100644 index 0000000..f33e543 --- /dev/null +++ b/train/README.md @@ -0,0 +1,79 @@ +# Model setup, training, and testing. + + + +### The following tools are used +- Tensorflow (In Windows tensorflow is not compatible with a Python version prior to 3.5) +- NumPy +- Inception v3 + + + +## Install + +- Install python 3.6 (make sure to set environment variable) +- Install PyCharm or Anaconda (Python 3.6) + +#### Windows: +- pip + + python get-pip.py (run command in C:/Python) + pip install -r /path/to/requirements.txt + + Alternatively install using PyCharm Project interpreter (Ctrl+Alt+s), or Anaconda command + + - c install conda-forge numpy + - conda install -c conda-forge tensorflow + +- Inception v3 + + downlaod inception at http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz + Copy the .pb file to _installationfiles/models + + + +#### macOS or OS X +- pip + + $ sudo easy_install pip + $ pip install -r /path/to/requirements.txt + Alternatively install using PyCharm Project interpreter (Ctrl+Alt+s), or Anaconda command + - c install conda-forge numpy + - conda install -c conda-forge tensorflow +- Inception v3 + + $ wget http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz + $ tar xvzf inception-2015-12-05.tgz + Copy the .pb file to _installationfiles/models + + +## Run + + +### Preperation +##### Set up image directory for test and training data. + You will need two separat directories separating test and training data, naming each folder inside the two directories after the label. + It is important to not have any duplicates. + + + +##### Set features and labels + After adding train and test data, run feature_extractor.py to write features and labels to json files. + + +### Start training +Once features and labels are extracted, set the following values appropriately to the amount of data used, in train.py. + +- __Epochs__ - Number of iterations the training is run +- __Batch size__ - The number of training examples (images) utilised in one iteration, the higher batch size the more general. A batch size equal to one will update the gradient and the neural network parameters after each samlpe. +- __Number of hidden__ layers'- Given the data not being linearly separable, the model will require at least one hidden layer. +- __Learning rate__ - A parameter that controls the weight adjustment. The lower the value, the slower it will adjust. + + Alternatively use the default values: 'epochs=50, batch_size=16, hidden layer size=3, learning rate = 1e-3' + **Run _train.py_** for training + + +Train builds a transfer network (model developed for a task is reused as the starting point on a second task). For each epoch training is run on shuffled samples and loss minimized. +After running train.py, the model should be trained and stored in the train directory. + + diff --git a/train/build_package.sh b/train/build_package.sh index 452d209..a21a606 100755 --- a/train/build_package.sh +++ b/train/build_package.sh @@ -8,7 +8,7 @@ fi rm -rf tmp output mkdir tmp output -python setup.py \ +python3 setup.py \ egg_info --egg-base tmp \ build --build-base tmp --build-temp tmp \ sdist --dist-dir output diff --git a/train/image/README.txt b/train/image/README.txt new file mode 100644 index 0000000..b9f5c7f --- /dev/null +++ b/train/image/README.txt @@ -0,0 +1,4 @@ +1. Tyggis +2. Lakris +3. Sjokolade +4. Smurf \ No newline at end of file diff --git a/train/image/test/chocolate/candy_02_10zjgsrj - Copy.jpg b/train/image/test/chocolate/candy_02_10zjgsrj - Copy.jpg new file mode 100644 index 0000000..a7f305d Binary files /dev/null and b/train/image/test/chocolate/candy_02_10zjgsrj - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_10zjgsrj.jpg b/train/image/test/chocolate/candy_02_10zjgsrj.jpg new file mode 100644 index 0000000..a7f305d Binary files /dev/null and b/train/image/test/chocolate/candy_02_10zjgsrj.jpg differ diff --git a/train/image/test/chocolate/candy_02_3awqhwrh - Copy.jpg b/train/image/test/chocolate/candy_02_3awqhwrh - Copy.jpg new file mode 100644 index 0000000..686b2bf Binary files /dev/null and b/train/image/test/chocolate/candy_02_3awqhwrh - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_3awqhwrh.jpg b/train/image/test/chocolate/candy_02_3awqhwrh.jpg new file mode 100644 index 0000000..686b2bf Binary files /dev/null and b/train/image/test/chocolate/candy_02_3awqhwrh.jpg differ diff --git a/train/image/test/chocolate/candy_02_41jyhoby.jpg b/train/image/test/chocolate/candy_02_41jyhoby.jpg new file mode 100644 index 0000000..e52d621 Binary files /dev/null and b/train/image/test/chocolate/candy_02_41jyhoby.jpg differ diff --git a/train/image/test/chocolate/candy_02_41odj63m.jpg b/train/image/test/chocolate/candy_02_41odj63m.jpg new file mode 100644 index 0000000..8159e80 Binary files /dev/null and b/train/image/test/chocolate/candy_02_41odj63m.jpg differ diff --git a/train/image/test/chocolate/candy_02_54sn0c7u.jpg b/train/image/test/chocolate/candy_02_54sn0c7u.jpg new file mode 100644 index 0000000..21ce633 Binary files /dev/null and b/train/image/test/chocolate/candy_02_54sn0c7u.jpg differ diff --git a/train/image/test/chocolate/candy_02_686ytblr.jpg b/train/image/test/chocolate/candy_02_686ytblr.jpg new file mode 100644 index 0000000..3095c50 Binary files /dev/null and b/train/image/test/chocolate/candy_02_686ytblr.jpg differ diff --git a/train/image/test/chocolate/candy_02_6ty47vmx - Copy.jpg b/train/image/test/chocolate/candy_02_6ty47vmx - Copy.jpg new file mode 100644 index 0000000..11d2739 Binary files /dev/null and b/train/image/test/chocolate/candy_02_6ty47vmx - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_6ty47vmx.jpg b/train/image/test/chocolate/candy_02_6ty47vmx.jpg new file mode 100644 index 0000000..11d2739 Binary files /dev/null and b/train/image/test/chocolate/candy_02_6ty47vmx.jpg differ diff --git a/train/image/test/chocolate/candy_02_7t35qa5t - Copy.jpg b/train/image/test/chocolate/candy_02_7t35qa5t - Copy.jpg new file mode 100644 index 0000000..f98a5f9 Binary files /dev/null and b/train/image/test/chocolate/candy_02_7t35qa5t - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_7t35qa5t.jpg b/train/image/test/chocolate/candy_02_7t35qa5t.jpg new file mode 100644 index 0000000..f98a5f9 Binary files /dev/null and b/train/image/test/chocolate/candy_02_7t35qa5t.jpg differ diff --git a/train/image/test/chocolate/candy_02_8s17rwkv - Copy.jpg b/train/image/test/chocolate/candy_02_8s17rwkv - Copy.jpg new file mode 100644 index 0000000..a68900b Binary files /dev/null and b/train/image/test/chocolate/candy_02_8s17rwkv - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_8s17rwkv.jpg b/train/image/test/chocolate/candy_02_8s17rwkv.jpg new file mode 100644 index 0000000..a68900b Binary files /dev/null and b/train/image/test/chocolate/candy_02_8s17rwkv.jpg differ diff --git a/train/image/test/chocolate/candy_02_9fzdvuty - Copy.jpg b/train/image/test/chocolate/candy_02_9fzdvuty - Copy.jpg new file mode 100644 index 0000000..1d50d83 Binary files /dev/null and b/train/image/test/chocolate/candy_02_9fzdvuty - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_9fzdvuty.jpg b/train/image/test/chocolate/candy_02_9fzdvuty.jpg new file mode 100644 index 0000000..1d50d83 Binary files /dev/null and b/train/image/test/chocolate/candy_02_9fzdvuty.jpg differ diff --git a/train/image/test/chocolate/candy_02_ae5l3cvn.jpg b/train/image/test/chocolate/candy_02_ae5l3cvn.jpg new file mode 100644 index 0000000..c21d410 Binary files /dev/null and b/train/image/test/chocolate/candy_02_ae5l3cvn.jpg differ diff --git a/train/image/test/chocolate/candy_02_bdipf8gx.jpg b/train/image/test/chocolate/candy_02_bdipf8gx.jpg new file mode 100644 index 0000000..62812a6 Binary files /dev/null and b/train/image/test/chocolate/candy_02_bdipf8gx.jpg differ diff --git a/train/image/test/chocolate/candy_02_dqur6rge.jpg b/train/image/test/chocolate/candy_02_dqur6rge.jpg new file mode 100644 index 0000000..af85124 Binary files /dev/null and b/train/image/test/chocolate/candy_02_dqur6rge.jpg differ diff --git a/train/image/test/chocolate/candy_02_i7jgz8wh.jpg b/train/image/test/chocolate/candy_02_i7jgz8wh.jpg new file mode 100644 index 0000000..c6617b8 Binary files /dev/null and b/train/image/test/chocolate/candy_02_i7jgz8wh.jpg differ diff --git a/train/image/test/chocolate/candy_02_j0n7zla5.jpg b/train/image/test/chocolate/candy_02_j0n7zla5.jpg new file mode 100644 index 0000000..6775031 Binary files /dev/null and b/train/image/test/chocolate/candy_02_j0n7zla5.jpg differ diff --git a/train/image/test/chocolate/candy_02_ls2wnsov.jpg b/train/image/test/chocolate/candy_02_ls2wnsov.jpg new file mode 100644 index 0000000..07ac3c2 Binary files /dev/null and b/train/image/test/chocolate/candy_02_ls2wnsov.jpg differ diff --git a/train/image/test/chocolate/candy_02_mxknm8ef.jpg b/train/image/test/chocolate/candy_02_mxknm8ef.jpg new file mode 100644 index 0000000..f77afb9 Binary files /dev/null and b/train/image/test/chocolate/candy_02_mxknm8ef.jpg differ diff --git a/train/image/test/chocolate/candy_02_otmzq9vk.jpg b/train/image/test/chocolate/candy_02_otmzq9vk.jpg new file mode 100644 index 0000000..ceead95 Binary files /dev/null and b/train/image/test/chocolate/candy_02_otmzq9vk.jpg differ diff --git a/train/image/test/chocolate/candy_02_qq42hx6y.jpg b/train/image/test/chocolate/candy_02_qq42hx6y.jpg new file mode 100644 index 0000000..86c801a Binary files /dev/null and b/train/image/test/chocolate/candy_02_qq42hx6y.jpg differ diff --git a/train/image/test/chocolate/candy_02_ta3v88t3.jpg b/train/image/test/chocolate/candy_02_ta3v88t3.jpg new file mode 100644 index 0000000..638c17b Binary files /dev/null and b/train/image/test/chocolate/candy_02_ta3v88t3.jpg differ diff --git a/train/image/test/chocolate/candy_02_u65rackx - Copy.jpg b/train/image/test/chocolate/candy_02_u65rackx - Copy.jpg new file mode 100644 index 0000000..38644e8 Binary files /dev/null and b/train/image/test/chocolate/candy_02_u65rackx - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_u65rackx.jpg b/train/image/test/chocolate/candy_02_u65rackx.jpg new file mode 100644 index 0000000..38644e8 Binary files /dev/null and b/train/image/test/chocolate/candy_02_u65rackx.jpg differ diff --git a/train/image/test/chocolate/candy_02_ukimv6r4 - Copy.jpg b/train/image/test/chocolate/candy_02_ukimv6r4 - Copy.jpg new file mode 100644 index 0000000..2211af6 Binary files /dev/null and b/train/image/test/chocolate/candy_02_ukimv6r4 - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_ukimv6r4.jpg b/train/image/test/chocolate/candy_02_ukimv6r4.jpg new file mode 100644 index 0000000..2211af6 Binary files /dev/null and b/train/image/test/chocolate/candy_02_ukimv6r4.jpg differ diff --git a/train/image/test/chocolate/candy_02_un99wary - Copy.jpg b/train/image/test/chocolate/candy_02_un99wary - Copy.jpg new file mode 100644 index 0000000..8353569 Binary files /dev/null and b/train/image/test/chocolate/candy_02_un99wary - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_un99wary.jpg b/train/image/test/chocolate/candy_02_un99wary.jpg new file mode 100644 index 0000000..8353569 Binary files /dev/null and b/train/image/test/chocolate/candy_02_un99wary.jpg differ diff --git a/train/image/test/chocolate/candy_02_zfg3vqe0 - Copy.jpg b/train/image/test/chocolate/candy_02_zfg3vqe0 - Copy.jpg new file mode 100644 index 0000000..0f06195 Binary files /dev/null and b/train/image/test/chocolate/candy_02_zfg3vqe0 - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_zfg3vqe0.jpg b/train/image/test/chocolate/candy_02_zfg3vqe0.jpg new file mode 100644 index 0000000..0f06195 Binary files /dev/null and b/train/image/test/chocolate/candy_02_zfg3vqe0.jpg differ diff --git a/train/image/test/chocolate/candy_02_zyhcf8vz - Copy.jpg b/train/image/test/chocolate/candy_02_zyhcf8vz - Copy.jpg new file mode 100644 index 0000000..105f56c Binary files /dev/null and b/train/image/test/chocolate/candy_02_zyhcf8vz - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_02_zyhcf8vz.jpg b/train/image/test/chocolate/candy_02_zyhcf8vz.jpg new file mode 100644 index 0000000..105f56c Binary files /dev/null and b/train/image/test/chocolate/candy_02_zyhcf8vz.jpg differ diff --git a/train/image/test/chocolate/candy_03_0uz63e6d - Copy.jpg b/train/image/test/chocolate/candy_03_0uz63e6d - Copy.jpg new file mode 100644 index 0000000..94b9cf5 Binary files /dev/null and b/train/image/test/chocolate/candy_03_0uz63e6d - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_0uz63e6d.jpg b/train/image/test/chocolate/candy_03_0uz63e6d.jpg new file mode 100644 index 0000000..94b9cf5 Binary files /dev/null and b/train/image/test/chocolate/candy_03_0uz63e6d.jpg differ diff --git a/train/image/test/chocolate/candy_03_1g9b31qb - Copy.jpg b/train/image/test/chocolate/candy_03_1g9b31qb - Copy.jpg new file mode 100644 index 0000000..65cd327 Binary files /dev/null and b/train/image/test/chocolate/candy_03_1g9b31qb - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_1g9b31qb.jpg b/train/image/test/chocolate/candy_03_1g9b31qb.jpg new file mode 100644 index 0000000..65cd327 Binary files /dev/null and b/train/image/test/chocolate/candy_03_1g9b31qb.jpg differ diff --git a/train/image/test/chocolate/candy_03_2kvwhqns - Copy.jpg b/train/image/test/chocolate/candy_03_2kvwhqns - Copy.jpg new file mode 100644 index 0000000..0d873d3 Binary files /dev/null and b/train/image/test/chocolate/candy_03_2kvwhqns - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_2kvwhqns.jpg b/train/image/test/chocolate/candy_03_2kvwhqns.jpg new file mode 100644 index 0000000..0d873d3 Binary files /dev/null and b/train/image/test/chocolate/candy_03_2kvwhqns.jpg differ diff --git a/train/image/test/chocolate/candy_03_9zvu0vnn - Copy.jpg b/train/image/test/chocolate/candy_03_9zvu0vnn - Copy.jpg new file mode 100644 index 0000000..bfad536 Binary files /dev/null and b/train/image/test/chocolate/candy_03_9zvu0vnn - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_9zvu0vnn.jpg b/train/image/test/chocolate/candy_03_9zvu0vnn.jpg new file mode 100644 index 0000000..bfad536 Binary files /dev/null and b/train/image/test/chocolate/candy_03_9zvu0vnn.jpg differ diff --git a/train/image/test/chocolate/candy_03_b7c2egsq - Copy.jpg b/train/image/test/chocolate/candy_03_b7c2egsq - Copy.jpg new file mode 100644 index 0000000..726a2f7 Binary files /dev/null and b/train/image/test/chocolate/candy_03_b7c2egsq - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_b7c2egsq.jpg b/train/image/test/chocolate/candy_03_b7c2egsq.jpg new file mode 100644 index 0000000..726a2f7 Binary files /dev/null and b/train/image/test/chocolate/candy_03_b7c2egsq.jpg differ diff --git a/train/image/test/chocolate/candy_03_jn47te8z - Copy.jpg b/train/image/test/chocolate/candy_03_jn47te8z - Copy.jpg new file mode 100644 index 0000000..70f0046 Binary files /dev/null and b/train/image/test/chocolate/candy_03_jn47te8z - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_jn47te8z.jpg b/train/image/test/chocolate/candy_03_jn47te8z.jpg new file mode 100644 index 0000000..70f0046 Binary files /dev/null and b/train/image/test/chocolate/candy_03_jn47te8z.jpg differ diff --git a/train/image/test/chocolate/candy_03_lr5mc62i - Copy.jpg b/train/image/test/chocolate/candy_03_lr5mc62i - Copy.jpg new file mode 100644 index 0000000..7e98a2c Binary files /dev/null and b/train/image/test/chocolate/candy_03_lr5mc62i - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_lr5mc62i.jpg b/train/image/test/chocolate/candy_03_lr5mc62i.jpg new file mode 100644 index 0000000..7e98a2c Binary files /dev/null and b/train/image/test/chocolate/candy_03_lr5mc62i.jpg differ diff --git a/train/image/test/chocolate/candy_03_mm4yky0y - Copy.jpg b/train/image/test/chocolate/candy_03_mm4yky0y - Copy.jpg new file mode 100644 index 0000000..65504d6 Binary files /dev/null and b/train/image/test/chocolate/candy_03_mm4yky0y - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_mm4yky0y.jpg b/train/image/test/chocolate/candy_03_mm4yky0y.jpg new file mode 100644 index 0000000..65504d6 Binary files /dev/null and b/train/image/test/chocolate/candy_03_mm4yky0y.jpg differ diff --git a/train/image/test/chocolate/candy_03_n53ql7mn - Copy.jpg b/train/image/test/chocolate/candy_03_n53ql7mn - Copy.jpg new file mode 100644 index 0000000..9d62463 Binary files /dev/null and b/train/image/test/chocolate/candy_03_n53ql7mn - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_n53ql7mn.jpg b/train/image/test/chocolate/candy_03_n53ql7mn.jpg new file mode 100644 index 0000000..9d62463 Binary files /dev/null and b/train/image/test/chocolate/candy_03_n53ql7mn.jpg differ diff --git a/train/image/test/chocolate/candy_03_o1elk3ez - Copy.jpg b/train/image/test/chocolate/candy_03_o1elk3ez - Copy.jpg new file mode 100644 index 0000000..7666d7f Binary files /dev/null and b/train/image/test/chocolate/candy_03_o1elk3ez - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_o1elk3ez.jpg b/train/image/test/chocolate/candy_03_o1elk3ez.jpg new file mode 100644 index 0000000..7666d7f Binary files /dev/null and b/train/image/test/chocolate/candy_03_o1elk3ez.jpg differ diff --git a/train/image/test/chocolate/candy_03_s4frwxsz - Copy.jpg b/train/image/test/chocolate/candy_03_s4frwxsz - Copy.jpg new file mode 100644 index 0000000..7ec7e50 Binary files /dev/null and b/train/image/test/chocolate/candy_03_s4frwxsz - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_s4frwxsz.jpg b/train/image/test/chocolate/candy_03_s4frwxsz.jpg new file mode 100644 index 0000000..7ec7e50 Binary files /dev/null and b/train/image/test/chocolate/candy_03_s4frwxsz.jpg differ diff --git a/train/image/test/chocolate/candy_03_sdtsoaf9 - Copy.jpg b/train/image/test/chocolate/candy_03_sdtsoaf9 - Copy.jpg new file mode 100644 index 0000000..a503de6 Binary files /dev/null and b/train/image/test/chocolate/candy_03_sdtsoaf9 - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_sdtsoaf9.jpg b/train/image/test/chocolate/candy_03_sdtsoaf9.jpg new file mode 100644 index 0000000..a503de6 Binary files /dev/null and b/train/image/test/chocolate/candy_03_sdtsoaf9.jpg differ diff --git a/train/image/test/chocolate/candy_03_tmp4xoxu - Copy.jpg b/train/image/test/chocolate/candy_03_tmp4xoxu - Copy.jpg new file mode 100644 index 0000000..4c8cc7e Binary files /dev/null and b/train/image/test/chocolate/candy_03_tmp4xoxu - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_tmp4xoxu.jpg b/train/image/test/chocolate/candy_03_tmp4xoxu.jpg new file mode 100644 index 0000000..4c8cc7e Binary files /dev/null and b/train/image/test/chocolate/candy_03_tmp4xoxu.jpg differ diff --git a/train/image/test/chocolate/candy_03_y73abwyy - Copy.jpg b/train/image/test/chocolate/candy_03_y73abwyy - Copy.jpg new file mode 100644 index 0000000..a55a041 Binary files /dev/null and b/train/image/test/chocolate/candy_03_y73abwyy - Copy.jpg differ diff --git a/train/image/test/chocolate/candy_03_y73abwyy.jpg b/train/image/test/chocolate/candy_03_y73abwyy.jpg new file mode 100644 index 0000000..a55a041 Binary files /dev/null and b/train/image/test/chocolate/candy_03_y73abwyy.jpg differ diff --git a/train/image/test/chocolate/candy_03_z5x18hgo - Copy.jpg b/train/image/test/chocolate/candy_03_z5x18hgo - Copy.jpg new file mode 100644 index 0000000..1f8e392 Binary files /dev/null and b/train/image/test/chocolate/candy_03_z5x18hgo - Copy.jpg differ diff --git a/train/image/test/gum/candy_00_3a1u01cv.jpg b/train/image/test/gum/candy_00_3a1u01cv.jpg new file mode 100644 index 0000000..88cc452 Binary files /dev/null and b/train/image/test/gum/candy_00_3a1u01cv.jpg differ diff --git a/train/image/test/gum/candy_00_3of7z61m.jpg b/train/image/test/gum/candy_00_3of7z61m.jpg new file mode 100644 index 0000000..453cfb4 Binary files /dev/null and b/train/image/test/gum/candy_00_3of7z61m.jpg differ diff --git a/train/image/test/gum/candy_00_5nwuer6x.jpg b/train/image/test/gum/candy_00_5nwuer6x.jpg new file mode 100644 index 0000000..31e1f06 Binary files /dev/null and b/train/image/test/gum/candy_00_5nwuer6x.jpg differ diff --git a/train/image/test/gum/candy_00_6ezr1la8.jpg b/train/image/test/gum/candy_00_6ezr1la8.jpg new file mode 100644 index 0000000..f74e5b1 Binary files /dev/null and b/train/image/test/gum/candy_00_6ezr1la8.jpg differ diff --git a/train/image/test/gum/candy_00_6kgvftcy.jpg b/train/image/test/gum/candy_00_6kgvftcy.jpg new file mode 100644 index 0000000..4d3003d Binary files /dev/null and b/train/image/test/gum/candy_00_6kgvftcy.jpg differ diff --git a/train/image/test/gum/candy_00_72q9vzfo.jpg b/train/image/test/gum/candy_00_72q9vzfo.jpg new file mode 100644 index 0000000..7ab5472 Binary files /dev/null and b/train/image/test/gum/candy_00_72q9vzfo.jpg differ diff --git a/train/image/test/gum/candy_00_7xn246tg.jpg b/train/image/test/gum/candy_00_7xn246tg.jpg new file mode 100644 index 0000000..958cb91 Binary files /dev/null and b/train/image/test/gum/candy_00_7xn246tg.jpg differ diff --git a/train/image/test/gum/candy_00_7ykyoppg.jpg b/train/image/test/gum/candy_00_7ykyoppg.jpg new file mode 100644 index 0000000..e6e7fbc Binary files /dev/null and b/train/image/test/gum/candy_00_7ykyoppg.jpg differ diff --git a/train/image/test/gum/candy_00_8yv6dlzt.jpg b/train/image/test/gum/candy_00_8yv6dlzt.jpg new file mode 100644 index 0000000..3a94ee8 Binary files /dev/null and b/train/image/test/gum/candy_00_8yv6dlzt.jpg differ diff --git a/train/image/test/gum/candy_00_9pljzvni.jpg b/train/image/test/gum/candy_00_9pljzvni.jpg new file mode 100644 index 0000000..4fbad62 Binary files /dev/null and b/train/image/test/gum/candy_00_9pljzvni.jpg differ diff --git a/train/image/test/gum/candy_00_b06hf57x.jpg b/train/image/test/gum/candy_00_b06hf57x.jpg new file mode 100644 index 0000000..11434f5 Binary files /dev/null and b/train/image/test/gum/candy_00_b06hf57x.jpg differ diff --git a/train/image/test/gum/candy_00_cidphmbb.jpg b/train/image/test/gum/candy_00_cidphmbb.jpg new file mode 100644 index 0000000..5638247 Binary files /dev/null and b/train/image/test/gum/candy_00_cidphmbb.jpg differ diff --git a/train/image/test/gum/candy_00_jsjwz93r.jpg b/train/image/test/gum/candy_00_jsjwz93r.jpg new file mode 100644 index 0000000..bd481c5 Binary files /dev/null and b/train/image/test/gum/candy_00_jsjwz93r.jpg differ diff --git a/train/image/test/gum/candy_00_k3ex6rha.jpg b/train/image/test/gum/candy_00_k3ex6rha.jpg new file mode 100644 index 0000000..071a14b Binary files /dev/null and b/train/image/test/gum/candy_00_k3ex6rha.jpg differ diff --git a/train/image/test/gum/candy_00_kguyaszi.jpg b/train/image/test/gum/candy_00_kguyaszi.jpg new file mode 100644 index 0000000..9f1936c Binary files /dev/null and b/train/image/test/gum/candy_00_kguyaszi.jpg differ diff --git a/train/image/test/gum/candy_00_mds8xnzm.jpg b/train/image/test/gum/candy_00_mds8xnzm.jpg new file mode 100644 index 0000000..ee4f339 Binary files /dev/null and b/train/image/test/gum/candy_00_mds8xnzm.jpg differ diff --git a/train/image/test/gum/candy_00_mooq8v1x.jpg b/train/image/test/gum/candy_00_mooq8v1x.jpg new file mode 100644 index 0000000..ce22cdd Binary files /dev/null and b/train/image/test/gum/candy_00_mooq8v1x.jpg differ diff --git a/train/image/test/gum/candy_00_ocy9gslm.jpg b/train/image/test/gum/candy_00_ocy9gslm.jpg new file mode 100644 index 0000000..1fcb03e Binary files /dev/null and b/train/image/test/gum/candy_00_ocy9gslm.jpg differ diff --git a/train/image/test/gum/candy_00_p2cnluth.jpg b/train/image/test/gum/candy_00_p2cnluth.jpg new file mode 100644 index 0000000..66ac7c0 Binary files /dev/null and b/train/image/test/gum/candy_00_p2cnluth.jpg differ diff --git a/train/image/test/gum/candy_00_qbeahxkz.jpg b/train/image/test/gum/candy_00_qbeahxkz.jpg new file mode 100644 index 0000000..994a5a0 Binary files /dev/null and b/train/image/test/gum/candy_00_qbeahxkz.jpg differ diff --git a/train/image/test/gum/candy_00_qcchqz2s.jpg b/train/image/test/gum/candy_00_qcchqz2s.jpg new file mode 100644 index 0000000..994a886 Binary files /dev/null and b/train/image/test/gum/candy_00_qcchqz2s.jpg differ diff --git a/train/image/test/gum/candy_00_qcpvtfl8.jpg b/train/image/test/gum/candy_00_qcpvtfl8.jpg new file mode 100644 index 0000000..ceda5cd Binary files /dev/null and b/train/image/test/gum/candy_00_qcpvtfl8.jpg differ diff --git a/train/image/test/gum/candy_00_qxf5mcmv.jpg b/train/image/test/gum/candy_00_qxf5mcmv.jpg new file mode 100644 index 0000000..52dba42 Binary files /dev/null and b/train/image/test/gum/candy_00_qxf5mcmv.jpg differ diff --git a/train/image/test/gum/candy_00_vzoae3wa.jpg b/train/image/test/gum/candy_00_vzoae3wa.jpg new file mode 100644 index 0000000..d2bc493 Binary files /dev/null and b/train/image/test/gum/candy_00_vzoae3wa.jpg differ diff --git a/train/image/test/gum/candy_00_xkyytd9z.jpg b/train/image/test/gum/candy_00_xkyytd9z.jpg new file mode 100644 index 0000000..85c45b3 Binary files /dev/null and b/train/image/test/gum/candy_00_xkyytd9z.jpg differ diff --git a/train/image/test/gum/candy_00_ywcr7jlz.jpg b/train/image/test/gum/candy_00_ywcr7jlz.jpg new file mode 100644 index 0000000..d42861e Binary files /dev/null and b/train/image/test/gum/candy_00_ywcr7jlz.jpg differ diff --git a/train/image/test/gum/candy_00_z2tqj5ld.jpg b/train/image/test/gum/candy_00_z2tqj5ld.jpg new file mode 100644 index 0000000..4910360 Binary files /dev/null and b/train/image/test/gum/candy_00_z2tqj5ld.jpg differ diff --git a/train/image/test/gum/candy_01_00te3i25.jpg b/train/image/test/gum/candy_01_00te3i25.jpg new file mode 100644 index 0000000..6a28b3b Binary files /dev/null and b/train/image/test/gum/candy_01_00te3i25.jpg differ diff --git a/train/image/test/gum/candy_01_2xsmcw0b.jpg b/train/image/test/gum/candy_01_2xsmcw0b.jpg new file mode 100644 index 0000000..1161ba2 Binary files /dev/null and b/train/image/test/gum/candy_01_2xsmcw0b.jpg differ diff --git a/train/image/test/gum/candy_01_56fovcp2.jpg b/train/image/test/gum/candy_01_56fovcp2.jpg new file mode 100644 index 0000000..1de7de7 Binary files /dev/null and b/train/image/test/gum/candy_01_56fovcp2.jpg differ diff --git a/train/image/test/gum/candy_01_5trn9due.jpg b/train/image/test/gum/candy_01_5trn9due.jpg new file mode 100644 index 0000000..de9c403 Binary files /dev/null and b/train/image/test/gum/candy_01_5trn9due.jpg differ diff --git a/train/image/test/gum/candy_01_5vwrkfba.jpg b/train/image/test/gum/candy_01_5vwrkfba.jpg new file mode 100644 index 0000000..2dc378c Binary files /dev/null and b/train/image/test/gum/candy_01_5vwrkfba.jpg differ diff --git a/train/image/test/gum/candy_01_9ebr3gla.jpg b/train/image/test/gum/candy_01_9ebr3gla.jpg new file mode 100644 index 0000000..2fda97d Binary files /dev/null and b/train/image/test/gum/candy_01_9ebr3gla.jpg differ diff --git a/train/image/test/gum/candy_01_b4mspe3k.jpg b/train/image/test/gum/candy_01_b4mspe3k.jpg new file mode 100644 index 0000000..58180b6 Binary files /dev/null and b/train/image/test/gum/candy_01_b4mspe3k.jpg differ diff --git a/train/image/test/gum/candy_01_c2vhmyt6.jpg b/train/image/test/gum/candy_01_c2vhmyt6.jpg new file mode 100644 index 0000000..9a0de1b Binary files /dev/null and b/train/image/test/gum/candy_01_c2vhmyt6.jpg differ diff --git a/train/image/test/gum/candy_01_d0frvo0g.jpg b/train/image/test/gum/candy_01_d0frvo0g.jpg new file mode 100644 index 0000000..f9e2d5b Binary files /dev/null and b/train/image/test/gum/candy_01_d0frvo0g.jpg differ diff --git a/train/image/test/gum/candy_01_ek7hax6h.jpg b/train/image/test/gum/candy_01_ek7hax6h.jpg new file mode 100644 index 0000000..829f2d0 Binary files /dev/null and b/train/image/test/gum/candy_01_ek7hax6h.jpg differ diff --git a/train/image/test/gum/candy_01_f08c8di2.jpg b/train/image/test/gum/candy_01_f08c8di2.jpg new file mode 100644 index 0000000..c6f74b2 Binary files /dev/null and b/train/image/test/gum/candy_01_f08c8di2.jpg differ diff --git a/train/image/test/gum/candy_01_g1dfz9n7.jpg b/train/image/test/gum/candy_01_g1dfz9n7.jpg new file mode 100644 index 0000000..dc2b0e4 Binary files /dev/null and b/train/image/test/gum/candy_01_g1dfz9n7.jpg differ diff --git a/train/image/test/gum/candy_01_h5g8du04.jpg b/train/image/test/gum/candy_01_h5g8du04.jpg new file mode 100644 index 0000000..77f0387 Binary files /dev/null and b/train/image/test/gum/candy_01_h5g8du04.jpg differ diff --git a/train/image/test/gum/candy_01_h8hgza0g.jpg b/train/image/test/gum/candy_01_h8hgza0g.jpg new file mode 100644 index 0000000..8890c4b Binary files /dev/null and b/train/image/test/gum/candy_01_h8hgza0g.jpg differ diff --git a/train/image/test/gum/candy_01_hf637dtx.jpg b/train/image/test/gum/candy_01_hf637dtx.jpg new file mode 100644 index 0000000..a5fee31 Binary files /dev/null and b/train/image/test/gum/candy_01_hf637dtx.jpg differ diff --git a/train/image/test/gum/candy_04_wzltcrd3.jpg b/train/image/test/gum/candy_04_wzltcrd3.jpg new file mode 100644 index 0000000..5b4a2f8 Binary files /dev/null and b/train/image/test/gum/candy_04_wzltcrd3.jpg differ diff --git a/train/image/test/gum/candy_04_xfc2jvcr.jpg b/train/image/test/gum/candy_04_xfc2jvcr.jpg new file mode 100644 index 0000000..53fa45c Binary files /dev/null and b/train/image/test/gum/candy_04_xfc2jvcr.jpg differ diff --git a/train/image/test/gum/candy_04_ypxzmq1g.jpg b/train/image/test/gum/candy_04_ypxzmq1g.jpg new file mode 100644 index 0000000..cd9ee93 Binary files /dev/null and b/train/image/test/gum/candy_04_ypxzmq1g.jpg differ diff --git a/train/image/test/gum/candy_04_zsgxddrc.jpg b/train/image/test/gum/candy_04_zsgxddrc.jpg new file mode 100644 index 0000000..d8fdf2a Binary files /dev/null and b/train/image/test/gum/candy_04_zsgxddrc.jpg differ diff --git a/train/image/test/gum/candy_05_3gho6qo7.jpg b/train/image/test/gum/candy_05_3gho6qo7.jpg new file mode 100644 index 0000000..cdc2b5b Binary files /dev/null and b/train/image/test/gum/candy_05_3gho6qo7.jpg differ diff --git a/train/image/test/gum/candy_05_73q1de1p.jpg b/train/image/test/gum/candy_05_73q1de1p.jpg new file mode 100644 index 0000000..d5bb5bc Binary files /dev/null and b/train/image/test/gum/candy_05_73q1de1p.jpg differ diff --git a/train/image/test/gum/candy_05_atvgrcxb.jpg b/train/image/test/gum/candy_05_atvgrcxb.jpg new file mode 100644 index 0000000..e5a51e4 Binary files /dev/null and b/train/image/test/gum/candy_05_atvgrcxb.jpg differ diff --git a/train/image/test/gum/candy_05_ccgopw74.jpg b/train/image/test/gum/candy_05_ccgopw74.jpg new file mode 100644 index 0000000..081a30d Binary files /dev/null and b/train/image/test/gum/candy_05_ccgopw74.jpg differ diff --git a/train/image/test/gum/candy_05_e4r3j581.jpg b/train/image/test/gum/candy_05_e4r3j581.jpg new file mode 100644 index 0000000..136aa76 Binary files /dev/null and b/train/image/test/gum/candy_05_e4r3j581.jpg differ diff --git a/train/image/test/gum/candy_05_f6q8buui.jpg b/train/image/test/gum/candy_05_f6q8buui.jpg new file mode 100644 index 0000000..3db4b19 Binary files /dev/null and b/train/image/test/gum/candy_05_f6q8buui.jpg differ diff --git a/train/image/test/gum/candy_06_5q5j9ile.jpg b/train/image/test/gum/candy_06_5q5j9ile.jpg new file mode 100644 index 0000000..d86ff4a Binary files /dev/null and b/train/image/test/gum/candy_06_5q5j9ile.jpg differ diff --git a/train/image/test/gum/candy_06_k8lf6w7g.jpg b/train/image/test/gum/candy_06_k8lf6w7g.jpg new file mode 100644 index 0000000..d20d81e Binary files /dev/null and b/train/image/test/gum/candy_06_k8lf6w7g.jpg differ diff --git a/train/image/test/gum/candy_06_mwrj7p80.jpg b/train/image/test/gum/candy_06_mwrj7p80.jpg new file mode 100644 index 0000000..8c1f62b Binary files /dev/null and b/train/image/test/gum/candy_06_mwrj7p80.jpg differ diff --git a/train/image/test/gum/candy_06_wojuyz64.jpg b/train/image/test/gum/candy_06_wojuyz64.jpg new file mode 100644 index 0000000..34dd438 Binary files /dev/null and b/train/image/test/gum/candy_06_wojuyz64.jpg differ diff --git a/train/image/test/gum/candy_06_z5bro46m.jpg b/train/image/test/gum/candy_06_z5bro46m.jpg new file mode 100644 index 0000000..1b6761c Binary files /dev/null and b/train/image/test/gum/candy_06_z5bro46m.jpg differ diff --git a/train/image/test/gum/candy_07_2n3terig.jpg b/train/image/test/gum/candy_07_2n3terig.jpg new file mode 100644 index 0000000..1ed42a6 Binary files /dev/null and b/train/image/test/gum/candy_07_2n3terig.jpg differ diff --git a/train/image/test/gum/candy_07_8n7eq1ct.jpg b/train/image/test/gum/candy_07_8n7eq1ct.jpg new file mode 100644 index 0000000..1a66f59 Binary files /dev/null and b/train/image/test/gum/candy_07_8n7eq1ct.jpg differ diff --git a/train/image/test/gum/candy_07_jilsk2im.jpg b/train/image/test/gum/candy_07_jilsk2im.jpg new file mode 100644 index 0000000..0200ca8 Binary files /dev/null and b/train/image/test/gum/candy_07_jilsk2im.jpg differ diff --git a/train/image/test/gum/candy_07_wv41rwxv.jpg b/train/image/test/gum/candy_07_wv41rwxv.jpg new file mode 100644 index 0000000..5bf7f7a Binary files /dev/null and b/train/image/test/gum/candy_07_wv41rwxv.jpg differ diff --git a/train/image/test/gum/candy_08_1pv3ndsr.jpg b/train/image/test/gum/candy_08_1pv3ndsr.jpg new file mode 100644 index 0000000..d857b9a Binary files /dev/null and b/train/image/test/gum/candy_08_1pv3ndsr.jpg differ diff --git a/train/image/test/gum/candy_08_aowjst7w.jpg b/train/image/test/gum/candy_08_aowjst7w.jpg new file mode 100644 index 0000000..e3d595b Binary files /dev/null and b/train/image/test/gum/candy_08_aowjst7w.jpg differ diff --git a/train/image/test/gum/candy_08_fu7hfur5.jpg b/train/image/test/gum/candy_08_fu7hfur5.jpg new file mode 100644 index 0000000..5f412bd Binary files /dev/null and b/train/image/test/gum/candy_08_fu7hfur5.jpg differ diff --git a/train/image/test/liquorice/candy_03_876gipws.jpg b/train/image/test/liquorice/candy_03_876gipws.jpg new file mode 100644 index 0000000..fe1b1c6 Binary files /dev/null and b/train/image/test/liquorice/candy_03_876gipws.jpg differ diff --git a/train/image/test/liquorice/candy_03_87gtnntr.jpg b/train/image/test/liquorice/candy_03_87gtnntr.jpg new file mode 100644 index 0000000..4f515c4 Binary files /dev/null and b/train/image/test/liquorice/candy_03_87gtnntr.jpg differ diff --git a/train/image/test/liquorice/candy_03_a64uy280.jpg b/train/image/test/liquorice/candy_03_a64uy280.jpg new file mode 100644 index 0000000..e4954bd Binary files /dev/null and b/train/image/test/liquorice/candy_03_a64uy280.jpg differ diff --git a/train/image/test/liquorice/candy_03_bbq7j932.jpg b/train/image/test/liquorice/candy_03_bbq7j932.jpg new file mode 100644 index 0000000..2934c1f Binary files /dev/null and b/train/image/test/liquorice/candy_03_bbq7j932.jpg differ diff --git a/train/image/test/liquorice/candy_03_eqwkei37.jpg b/train/image/test/liquorice/candy_03_eqwkei37.jpg new file mode 100644 index 0000000..15655c3 Binary files /dev/null and b/train/image/test/liquorice/candy_03_eqwkei37.jpg differ diff --git a/train/image/test/liquorice/candy_03_gvfjxyp0.jpg b/train/image/test/liquorice/candy_03_gvfjxyp0.jpg new file mode 100644 index 0000000..c9f8b13 Binary files /dev/null and b/train/image/test/liquorice/candy_03_gvfjxyp0.jpg differ diff --git a/train/image/test/liquorice/candy_03_h2vntke7.jpg b/train/image/test/liquorice/candy_03_h2vntke7.jpg new file mode 100644 index 0000000..1ce5bec Binary files /dev/null and b/train/image/test/liquorice/candy_03_h2vntke7.jpg differ diff --git a/train/image/test/liquorice/candy_03_hsj1xaa0.jpg b/train/image/test/liquorice/candy_03_hsj1xaa0.jpg new file mode 100644 index 0000000..cf9a5ab Binary files /dev/null and b/train/image/test/liquorice/candy_03_hsj1xaa0.jpg differ diff --git a/train/image/test/liquorice/candy_03_l47a2qgz.jpg b/train/image/test/liquorice/candy_03_l47a2qgz.jpg new file mode 100644 index 0000000..5c83700 Binary files /dev/null and b/train/image/test/liquorice/candy_03_l47a2qgz.jpg differ diff --git a/train/image/test/liquorice/candy_03_lbfcn3o8.jpg b/train/image/test/liquorice/candy_03_lbfcn3o8.jpg new file mode 100644 index 0000000..894d3b7 Binary files /dev/null and b/train/image/test/liquorice/candy_03_lbfcn3o8.jpg differ diff --git a/train/image/test/liquorice/candy_03_mmjzeegm.jpg b/train/image/test/liquorice/candy_03_mmjzeegm.jpg new file mode 100644 index 0000000..b49c4e2 Binary files /dev/null and b/train/image/test/liquorice/candy_03_mmjzeegm.jpg differ diff --git a/train/image/test/liquorice/candy_03_nem24sfn.jpg b/train/image/test/liquorice/candy_03_nem24sfn.jpg new file mode 100644 index 0000000..4afe3da Binary files /dev/null and b/train/image/test/liquorice/candy_03_nem24sfn.jpg differ diff --git a/train/image/test/liquorice/candy_03_npj4qkm1.jpg b/train/image/test/liquorice/candy_03_npj4qkm1.jpg new file mode 100644 index 0000000..bd6aa86 Binary files /dev/null and b/train/image/test/liquorice/candy_03_npj4qkm1.jpg differ diff --git a/train/image/test/liquorice/candy_03_opgip8kq.jpg b/train/image/test/liquorice/candy_03_opgip8kq.jpg new file mode 100644 index 0000000..e4e60a2 Binary files /dev/null and b/train/image/test/liquorice/candy_03_opgip8kq.jpg differ diff --git a/train/image/test/liquorice/candy_03_r6hlkm10.jpg b/train/image/test/liquorice/candy_03_r6hlkm10.jpg new file mode 100644 index 0000000..f84ff7e Binary files /dev/null and b/train/image/test/liquorice/candy_03_r6hlkm10.jpg differ diff --git a/train/image/test/liquorice/candy_03_rjx9ckm6.jpg b/train/image/test/liquorice/candy_03_rjx9ckm6.jpg new file mode 100644 index 0000000..851568f Binary files /dev/null and b/train/image/test/liquorice/candy_03_rjx9ckm6.jpg differ diff --git a/train/image/test/liquorice/candy_03_shejh4e2.jpg b/train/image/test/liquorice/candy_03_shejh4e2.jpg new file mode 100644 index 0000000..5cdb860 Binary files /dev/null and b/train/image/test/liquorice/candy_03_shejh4e2.jpg differ diff --git a/train/image/test/liquorice/candy_03_swf0g1j1.jpg b/train/image/test/liquorice/candy_03_swf0g1j1.jpg new file mode 100644 index 0000000..4a44127 Binary files /dev/null and b/train/image/test/liquorice/candy_03_swf0g1j1.jpg differ diff --git a/train/image/test/liquorice/candy_03_wb47j13t.jpg b/train/image/test/liquorice/candy_03_wb47j13t.jpg new file mode 100644 index 0000000..0a38f29 Binary files /dev/null and b/train/image/test/liquorice/candy_03_wb47j13t.jpg differ diff --git a/train/image/test/liquorice/candy_04_1achlwyn.jpg b/train/image/test/liquorice/candy_04_1achlwyn.jpg new file mode 100644 index 0000000..8a1bac5 Binary files /dev/null and b/train/image/test/liquorice/candy_04_1achlwyn.jpg differ diff --git a/train/image/test/liquorice/candy_04_2md0t0qh.jpg b/train/image/test/liquorice/candy_04_2md0t0qh.jpg new file mode 100644 index 0000000..8b71aeb Binary files /dev/null and b/train/image/test/liquorice/candy_04_2md0t0qh.jpg differ diff --git a/train/image/test/liquorice/candy_04_5dx1t33x.jpg b/train/image/test/liquorice/candy_04_5dx1t33x.jpg new file mode 100644 index 0000000..254cb71 Binary files /dev/null and b/train/image/test/liquorice/candy_04_5dx1t33x.jpg differ diff --git a/train/image/test/liquorice/candy_04_5jm319wz.jpg b/train/image/test/liquorice/candy_04_5jm319wz.jpg new file mode 100644 index 0000000..d966d35 Binary files /dev/null and b/train/image/test/liquorice/candy_04_5jm319wz.jpg differ diff --git a/train/image/test/liquorice/candy_04_8yrc7148.jpg b/train/image/test/liquorice/candy_04_8yrc7148.jpg new file mode 100644 index 0000000..dbe0f44 Binary files /dev/null and b/train/image/test/liquorice/candy_04_8yrc7148.jpg differ diff --git a/train/image/test/liquorice/candy_04_9xvbip4u.jpg b/train/image/test/liquorice/candy_04_9xvbip4u.jpg new file mode 100644 index 0000000..d450475 Binary files /dev/null and b/train/image/test/liquorice/candy_04_9xvbip4u.jpg differ diff --git a/train/image/test/liquorice/candy_04_addijo6j.jpg b/train/image/test/liquorice/candy_04_addijo6j.jpg new file mode 100644 index 0000000..df9d371 Binary files /dev/null and b/train/image/test/liquorice/candy_04_addijo6j.jpg differ diff --git a/train/image/test/liquorice/candy_04_barzl0nh.jpg b/train/image/test/liquorice/candy_04_barzl0nh.jpg new file mode 100644 index 0000000..410a8a6 Binary files /dev/null and b/train/image/test/liquorice/candy_04_barzl0nh.jpg differ diff --git a/train/image/test/liquorice/candy_04_bck4tq27.jpg b/train/image/test/liquorice/candy_04_bck4tq27.jpg new file mode 100644 index 0000000..6b8e123 Binary files /dev/null and b/train/image/test/liquorice/candy_04_bck4tq27.jpg differ diff --git a/train/image/test/liquorice/candy_04_io7n8rpo.jpg b/train/image/test/liquorice/candy_04_io7n8rpo.jpg new file mode 100644 index 0000000..e0d8939 Binary files /dev/null and b/train/image/test/liquorice/candy_04_io7n8rpo.jpg differ diff --git a/train/image/test/liquorice/candy_04_kbjj69wj.jpg b/train/image/test/liquorice/candy_04_kbjj69wj.jpg new file mode 100644 index 0000000..94bb5a4 Binary files /dev/null and b/train/image/test/liquorice/candy_04_kbjj69wj.jpg differ diff --git a/train/image/test/liquorice/candy_04_lglvyb2t.jpg b/train/image/test/liquorice/candy_04_lglvyb2t.jpg new file mode 100644 index 0000000..bac870a Binary files /dev/null and b/train/image/test/liquorice/candy_04_lglvyb2t.jpg differ diff --git a/train/image/test/liquorice/candy_04_rb019nzo.jpg b/train/image/test/liquorice/candy_04_rb019nzo.jpg new file mode 100644 index 0000000..fd51a57 Binary files /dev/null and b/train/image/test/liquorice/candy_04_rb019nzo.jpg differ diff --git a/train/image/test/liquorice/candy_04_ww7bompt.jpg b/train/image/test/liquorice/candy_04_ww7bompt.jpg new file mode 100644 index 0000000..fa3378d Binary files /dev/null and b/train/image/test/liquorice/candy_04_ww7bompt.jpg differ diff --git a/train/image/test/liquorice/candy_04_xa7lx48s.jpg b/train/image/test/liquorice/candy_04_xa7lx48s.jpg new file mode 100644 index 0000000..8c17509 Binary files /dev/null and b/train/image/test/liquorice/candy_04_xa7lx48s.jpg differ diff --git a/train/image/test/liquorice/candy_04_xggv25wh.jpg b/train/image/test/liquorice/candy_04_xggv25wh.jpg new file mode 100644 index 0000000..b6a36e7 Binary files /dev/null and b/train/image/test/liquorice/candy_04_xggv25wh.jpg differ diff --git a/train/image/test/liquorice/candy_04_y6ae2o5j.jpg b/train/image/test/liquorice/candy_04_y6ae2o5j.jpg new file mode 100644 index 0000000..703713b Binary files /dev/null and b/train/image/test/liquorice/candy_04_y6ae2o5j.jpg differ diff --git a/train/image/test/liquorice/candy_04_yqzc4ahv.jpg b/train/image/test/liquorice/candy_04_yqzc4ahv.jpg new file mode 100644 index 0000000..2fea6a2 Binary files /dev/null and b/train/image/test/liquorice/candy_04_yqzc4ahv.jpg differ diff --git a/train/image/test/liquorice/candy_05_1jc9xftb.jpg b/train/image/test/liquorice/candy_05_1jc9xftb.jpg new file mode 100644 index 0000000..23cbda2 Binary files /dev/null and b/train/image/test/liquorice/candy_05_1jc9xftb.jpg differ diff --git a/train/image/test/liquorice/candy_05_3cxb8vv3.jpg b/train/image/test/liquorice/candy_05_3cxb8vv3.jpg new file mode 100644 index 0000000..a05ce90 Binary files /dev/null and b/train/image/test/liquorice/candy_05_3cxb8vv3.jpg differ diff --git a/train/image/test/liquorice/candy_05_3u1niw5o.jpg b/train/image/test/liquorice/candy_05_3u1niw5o.jpg new file mode 100644 index 0000000..c47a1eb Binary files /dev/null and b/train/image/test/liquorice/candy_05_3u1niw5o.jpg differ diff --git a/train/image/test/liquorice/candy_05_3zci07zj.jpg b/train/image/test/liquorice/candy_05_3zci07zj.jpg new file mode 100644 index 0000000..0464363 Binary files /dev/null and b/train/image/test/liquorice/candy_05_3zci07zj.jpg differ diff --git a/train/image/test/liquorice/candy_05_5t3rerk4.jpg b/train/image/test/liquorice/candy_05_5t3rerk4.jpg new file mode 100644 index 0000000..c85e372 Binary files /dev/null and b/train/image/test/liquorice/candy_05_5t3rerk4.jpg differ diff --git a/train/image/test/liquorice/candy_05_8x92x6ut.jpg b/train/image/test/liquorice/candy_05_8x92x6ut.jpg new file mode 100644 index 0000000..9f32959 Binary files /dev/null and b/train/image/test/liquorice/candy_05_8x92x6ut.jpg differ diff --git a/train/image/test/liquorice/candy_05_918hlmx2.jpg b/train/image/test/liquorice/candy_05_918hlmx2.jpg new file mode 100644 index 0000000..805e6ca Binary files /dev/null and b/train/image/test/liquorice/candy_05_918hlmx2.jpg differ diff --git a/train/image/test/liquorice/candy_05_yqglfly2.jpg b/train/image/test/liquorice/candy_05_yqglfly2.jpg new file mode 100644 index 0000000..c962631 Binary files /dev/null and b/train/image/test/liquorice/candy_05_yqglfly2.jpg differ diff --git a/train/image/test/smurf/candy_00_536aoi7k.jpg b/train/image/test/smurf/candy_00_536aoi7k.jpg new file mode 100644 index 0000000..a3f8978 Binary files /dev/null and b/train/image/test/smurf/candy_00_536aoi7k.jpg differ diff --git a/train/image/test/smurf/candy_00_7j6etqj1.jpg b/train/image/test/smurf/candy_00_7j6etqj1.jpg new file mode 100644 index 0000000..065048f Binary files /dev/null and b/train/image/test/smurf/candy_00_7j6etqj1.jpg differ diff --git a/train/image/test/smurf/candy_00_bfmux0nn.jpg b/train/image/test/smurf/candy_00_bfmux0nn.jpg new file mode 100644 index 0000000..ecc4185 Binary files /dev/null and b/train/image/test/smurf/candy_00_bfmux0nn.jpg differ diff --git a/train/image/test/smurf/candy_00_bzxh3oon.jpg b/train/image/test/smurf/candy_00_bzxh3oon.jpg new file mode 100644 index 0000000..e2f4314 Binary files /dev/null and b/train/image/test/smurf/candy_00_bzxh3oon.jpg differ diff --git a/train/image/test/smurf/candy_00_doiffm64.jpg b/train/image/test/smurf/candy_00_doiffm64.jpg new file mode 100644 index 0000000..eaa32a8 Binary files /dev/null and b/train/image/test/smurf/candy_00_doiffm64.jpg differ diff --git a/train/image/test/smurf/candy_00_fxaxxvw2.jpg b/train/image/test/smurf/candy_00_fxaxxvw2.jpg new file mode 100644 index 0000000..219ac87 Binary files /dev/null and b/train/image/test/smurf/candy_00_fxaxxvw2.jpg differ diff --git a/train/image/test/smurf/candy_00_gdyxs35h.jpg b/train/image/test/smurf/candy_00_gdyxs35h.jpg new file mode 100644 index 0000000..36bb24d Binary files /dev/null and b/train/image/test/smurf/candy_00_gdyxs35h.jpg differ diff --git a/train/image/test/smurf/candy_00_gpha9kli.jpg b/train/image/test/smurf/candy_00_gpha9kli.jpg new file mode 100644 index 0000000..e799bf6 Binary files /dev/null and b/train/image/test/smurf/candy_00_gpha9kli.jpg differ diff --git a/train/image/test/smurf/candy_00_i42d0dqy.jpg b/train/image/test/smurf/candy_00_i42d0dqy.jpg new file mode 100644 index 0000000..8fddbbf Binary files /dev/null and b/train/image/test/smurf/candy_00_i42d0dqy.jpg differ diff --git a/train/image/test/smurf/candy_00_j1gsfnv8.jpg b/train/image/test/smurf/candy_00_j1gsfnv8.jpg new file mode 100644 index 0000000..d00ef70 Binary files /dev/null and b/train/image/test/smurf/candy_00_j1gsfnv8.jpg differ diff --git a/train/image/test/smurf/candy_00_jqlah32k.jpg b/train/image/test/smurf/candy_00_jqlah32k.jpg new file mode 100644 index 0000000..e7895d1 Binary files /dev/null and b/train/image/test/smurf/candy_00_jqlah32k.jpg differ diff --git a/train/image/test/smurf/candy_00_l8op70nt.jpg b/train/image/test/smurf/candy_00_l8op70nt.jpg new file mode 100644 index 0000000..a183e2e Binary files /dev/null and b/train/image/test/smurf/candy_00_l8op70nt.jpg differ diff --git a/train/image/test/smurf/candy_00_myvlsoys.jpg b/train/image/test/smurf/candy_00_myvlsoys.jpg new file mode 100644 index 0000000..207adb9 Binary files /dev/null and b/train/image/test/smurf/candy_00_myvlsoys.jpg differ diff --git a/train/image/test/smurf/candy_00_ntu9zzq4.jpg b/train/image/test/smurf/candy_00_ntu9zzq4.jpg new file mode 100644 index 0000000..c410a8e Binary files /dev/null and b/train/image/test/smurf/candy_00_ntu9zzq4.jpg differ diff --git a/train/image/test/smurf/candy_00_q2yyeheh.jpg b/train/image/test/smurf/candy_00_q2yyeheh.jpg new file mode 100644 index 0000000..d487131 Binary files /dev/null and b/train/image/test/smurf/candy_00_q2yyeheh.jpg differ diff --git a/train/image/test/smurf/candy_00_qbqjnzbb.jpg b/train/image/test/smurf/candy_00_qbqjnzbb.jpg new file mode 100644 index 0000000..3bf909d Binary files /dev/null and b/train/image/test/smurf/candy_00_qbqjnzbb.jpg differ diff --git a/train/image/test/smurf/candy_00_uypa64b1.jpg b/train/image/test/smurf/candy_00_uypa64b1.jpg new file mode 100644 index 0000000..61225d5 Binary files /dev/null and b/train/image/test/smurf/candy_00_uypa64b1.jpg differ diff --git a/train/image/test/smurf/candy_00_x8vlahbe.jpg b/train/image/test/smurf/candy_00_x8vlahbe.jpg new file mode 100644 index 0000000..c6e9fff Binary files /dev/null and b/train/image/test/smurf/candy_00_x8vlahbe.jpg differ diff --git a/train/image/test/smurf/candy_02_sro3tek8.jpg b/train/image/test/smurf/candy_02_sro3tek8.jpg new file mode 100644 index 0000000..3535293 Binary files /dev/null and b/train/image/test/smurf/candy_02_sro3tek8.jpg differ diff --git a/train/image/test/smurf/candy_02_x08dfcnj.jpg b/train/image/test/smurf/candy_02_x08dfcnj.jpg new file mode 100644 index 0000000..017d425 Binary files /dev/null and b/train/image/test/smurf/candy_02_x08dfcnj.jpg differ diff --git a/train/image/test/smurf/candy_02_xha2275r.jpg b/train/image/test/smurf/candy_02_xha2275r.jpg new file mode 100644 index 0000000..888c507 Binary files /dev/null and b/train/image/test/smurf/candy_02_xha2275r.jpg differ diff --git a/train/image/test/smurf/candy_02_y5c8xviq.jpg b/train/image/test/smurf/candy_02_y5c8xviq.jpg new file mode 100644 index 0000000..71468ed Binary files /dev/null and b/train/image/test/smurf/candy_02_y5c8xviq.jpg differ diff --git a/train/image/test/smurf/candy_02_z56rr4uw.jpg b/train/image/test/smurf/candy_02_z56rr4uw.jpg new file mode 100644 index 0000000..b56e560 Binary files /dev/null and b/train/image/test/smurf/candy_02_z56rr4uw.jpg differ diff --git a/train/image/test/smurf/candy_03_18fubkjb.jpg b/train/image/test/smurf/candy_03_18fubkjb.jpg new file mode 100644 index 0000000..833f732 Binary files /dev/null and b/train/image/test/smurf/candy_03_18fubkjb.jpg differ diff --git a/train/image/test/smurf/candy_03_3ubky31s.jpg b/train/image/test/smurf/candy_03_3ubky31s.jpg new file mode 100644 index 0000000..16e0538 Binary files /dev/null and b/train/image/test/smurf/candy_03_3ubky31s.jpg differ diff --git a/train/image/test/smurf/candy_03_43i9cex3.jpg b/train/image/test/smurf/candy_03_43i9cex3.jpg new file mode 100644 index 0000000..cab1ca0 Binary files /dev/null and b/train/image/test/smurf/candy_03_43i9cex3.jpg differ diff --git a/train/image/test/smurf/candy_03_9negaj99.jpg b/train/image/test/smurf/candy_03_9negaj99.jpg new file mode 100644 index 0000000..83f7c93 Binary files /dev/null and b/train/image/test/smurf/candy_03_9negaj99.jpg differ diff --git a/train/image/test/smurf/candy_04_lptsuoau.jpg b/train/image/test/smurf/candy_04_lptsuoau.jpg new file mode 100644 index 0000000..3a5d2d3 Binary files /dev/null and b/train/image/test/smurf/candy_04_lptsuoau.jpg differ diff --git a/train/image/test/smurf/candy_04_mbv6ve00.jpg b/train/image/test/smurf/candy_04_mbv6ve00.jpg new file mode 100644 index 0000000..424093c Binary files /dev/null and b/train/image/test/smurf/candy_04_mbv6ve00.jpg differ diff --git a/train/image/test/smurf/candy_04_mhaqetox.jpg b/train/image/test/smurf/candy_04_mhaqetox.jpg new file mode 100644 index 0000000..fff4463 Binary files /dev/null and b/train/image/test/smurf/candy_04_mhaqetox.jpg differ diff --git a/train/image/test/smurf/candy_05_vkiczk40.jpg b/train/image/test/smurf/candy_05_vkiczk40.jpg new file mode 100644 index 0000000..d88e4f8 Binary files /dev/null and b/train/image/test/smurf/candy_05_vkiczk40.jpg differ diff --git a/train/image/test/smurf/candy_05_xqxyto65.jpg b/train/image/test/smurf/candy_05_xqxyto65.jpg new file mode 100644 index 0000000..73d855d Binary files /dev/null and b/train/image/test/smurf/candy_05_xqxyto65.jpg differ diff --git a/train/image/test/smurf/candy_06_3u37nr5i.jpg b/train/image/test/smurf/candy_06_3u37nr5i.jpg new file mode 100644 index 0000000..7f39dd7 Binary files /dev/null and b/train/image/test/smurf/candy_06_3u37nr5i.jpg differ diff --git a/train/image/test/smurf/candy_06_58tnjn3p.jpg b/train/image/test/smurf/candy_06_58tnjn3p.jpg new file mode 100644 index 0000000..7b99c74 Binary files /dev/null and b/train/image/test/smurf/candy_06_58tnjn3p.jpg differ diff --git a/train/image/test/smurf/candy_06_7cdpdmud.jpg b/train/image/test/smurf/candy_06_7cdpdmud.jpg new file mode 100644 index 0000000..3a2ddc9 Binary files /dev/null and b/train/image/test/smurf/candy_06_7cdpdmud.jpg differ diff --git a/train/image/test/smurf/candy_06_azoj51pv.jpg b/train/image/test/smurf/candy_06_azoj51pv.jpg new file mode 100644 index 0000000..1887948 Binary files /dev/null and b/train/image/test/smurf/candy_06_azoj51pv.jpg differ diff --git a/train/image/test/smurf/candy_06_nkun1ows.jpg b/train/image/test/smurf/candy_06_nkun1ows.jpg new file mode 100644 index 0000000..0227ad8 Binary files /dev/null and b/train/image/test/smurf/candy_06_nkun1ows.jpg differ diff --git a/train/image/test/smurf/candy_06_v10bwtn8.jpg b/train/image/test/smurf/candy_06_v10bwtn8.jpg new file mode 100644 index 0000000..b6dad38 Binary files /dev/null and b/train/image/test/smurf/candy_06_v10bwtn8.jpg differ diff --git a/train/image/test/smurf/candy_07_69ucqe19.jpg b/train/image/test/smurf/candy_07_69ucqe19.jpg new file mode 100644 index 0000000..ca3e685 Binary files /dev/null and b/train/image/test/smurf/candy_07_69ucqe19.jpg differ diff --git a/train/image/test/smurf/candy_07_cldydymi.jpg b/train/image/test/smurf/candy_07_cldydymi.jpg new file mode 100644 index 0000000..ce7fbc7 Binary files /dev/null and b/train/image/test/smurf/candy_07_cldydymi.jpg differ diff --git a/train/image/test/smurf/candy_07_kezok63i.jpg b/train/image/test/smurf/candy_07_kezok63i.jpg new file mode 100644 index 0000000..db16789 Binary files /dev/null and b/train/image/test/smurf/candy_07_kezok63i.jpg differ diff --git a/train/image/test/smurf/candy_07_km18h5tt.jpg b/train/image/test/smurf/candy_07_km18h5tt.jpg new file mode 100644 index 0000000..7ce2309 Binary files /dev/null and b/train/image/test/smurf/candy_07_km18h5tt.jpg differ diff --git a/train/image/test/smurf/candy_07_on5rolhy.jpg b/train/image/test/smurf/candy_07_on5rolhy.jpg new file mode 100644 index 0000000..b8ed0a8 Binary files /dev/null and b/train/image/test/smurf/candy_07_on5rolhy.jpg differ diff --git a/train/image/test/smurf/candy_08_0lqy7ror.jpg b/train/image/test/smurf/candy_08_0lqy7ror.jpg new file mode 100644 index 0000000..560416a Binary files /dev/null and b/train/image/test/smurf/candy_08_0lqy7ror.jpg differ diff --git a/train/image/test/smurf/candy_08_aon161pq.jpg b/train/image/test/smurf/candy_08_aon161pq.jpg new file mode 100644 index 0000000..59e0ae9 Binary files /dev/null and b/train/image/test/smurf/candy_08_aon161pq.jpg differ diff --git a/train/image/test/smurf/candy_09_k97zavb2.jpg b/train/image/test/smurf/candy_09_k97zavb2.jpg new file mode 100644 index 0000000..2bd5bea Binary files /dev/null and b/train/image/test/smurf/candy_09_k97zavb2.jpg differ diff --git a/train/image/train/chocolate/candy_00_0xzl5vqf.jpg b/train/image/train/chocolate/candy_00_0xzl5vqf.jpg new file mode 100644 index 0000000..730a75d Binary files /dev/null and b/train/image/train/chocolate/candy_00_0xzl5vqf.jpg differ diff --git a/train/image/train/chocolate/candy_00_2cx9h97t.jpg b/train/image/train/chocolate/candy_00_2cx9h97t.jpg new file mode 100644 index 0000000..24748aa Binary files /dev/null and b/train/image/train/chocolate/candy_00_2cx9h97t.jpg differ diff --git a/train/image/train/chocolate/candy_00_2r0b6f9o.jpg b/train/image/train/chocolate/candy_00_2r0b6f9o.jpg new file mode 100644 index 0000000..fc854ae Binary files /dev/null and b/train/image/train/chocolate/candy_00_2r0b6f9o.jpg differ diff --git a/train/image/train/chocolate/candy_00_3qaezvqr.jpg b/train/image/train/chocolate/candy_00_3qaezvqr.jpg new file mode 100644 index 0000000..dd57823 Binary files /dev/null and b/train/image/train/chocolate/candy_00_3qaezvqr.jpg differ diff --git a/train/image/train/chocolate/candy_00_48prfo4o.jpg b/train/image/train/chocolate/candy_00_48prfo4o.jpg new file mode 100644 index 0000000..43fcc7f Binary files /dev/null and b/train/image/train/chocolate/candy_00_48prfo4o.jpg differ diff --git a/train/image/train/chocolate/candy_00_94xivcxp.jpg b/train/image/train/chocolate/candy_00_94xivcxp.jpg new file mode 100644 index 0000000..6747e18 Binary files /dev/null and b/train/image/train/chocolate/candy_00_94xivcxp.jpg differ diff --git a/train/image/train/chocolate/candy_00_9lae0tb9.jpg b/train/image/train/chocolate/candy_00_9lae0tb9.jpg new file mode 100644 index 0000000..a8f1637 Binary files /dev/null and b/train/image/train/chocolate/candy_00_9lae0tb9.jpg differ diff --git a/train/image/train/chocolate/candy_00_agglfcmr.jpg b/train/image/train/chocolate/candy_00_agglfcmr.jpg new file mode 100644 index 0000000..ca5d626 Binary files /dev/null and b/train/image/train/chocolate/candy_00_agglfcmr.jpg differ diff --git a/train/image/train/chocolate/candy_00_c1d9ylmt.jpg b/train/image/train/chocolate/candy_00_c1d9ylmt.jpg new file mode 100644 index 0000000..4a8c64f Binary files /dev/null and b/train/image/train/chocolate/candy_00_c1d9ylmt.jpg differ diff --git a/train/image/train/chocolate/candy_00_d06ykvnz.jpg b/train/image/train/chocolate/candy_00_d06ykvnz.jpg new file mode 100644 index 0000000..23900e1 Binary files /dev/null and b/train/image/train/chocolate/candy_00_d06ykvnz.jpg differ diff --git a/train/image/train/chocolate/candy_00_dbm2sxf9.jpg b/train/image/train/chocolate/candy_00_dbm2sxf9.jpg new file mode 100644 index 0000000..7df437e Binary files /dev/null and b/train/image/train/chocolate/candy_00_dbm2sxf9.jpg differ diff --git a/train/image/train/chocolate/candy_00_dbqnos0i.jpg b/train/image/train/chocolate/candy_00_dbqnos0i.jpg new file mode 100644 index 0000000..93446f6 Binary files /dev/null and b/train/image/train/chocolate/candy_00_dbqnos0i.jpg differ diff --git a/train/image/train/chocolate/candy_00_drcgurg3.jpg b/train/image/train/chocolate/candy_00_drcgurg3.jpg new file mode 100644 index 0000000..b466902 Binary files /dev/null and b/train/image/train/chocolate/candy_00_drcgurg3.jpg differ diff --git a/train/image/train/chocolate/candy_00_f0uy7l5g.jpg b/train/image/train/chocolate/candy_00_f0uy7l5g.jpg new file mode 100644 index 0000000..fd67a61 Binary files /dev/null and b/train/image/train/chocolate/candy_00_f0uy7l5g.jpg differ diff --git a/train/image/train/chocolate/candy_00_fcwef2et.jpg b/train/image/train/chocolate/candy_00_fcwef2et.jpg new file mode 100644 index 0000000..1a0ae36 Binary files /dev/null and b/train/image/train/chocolate/candy_00_fcwef2et.jpg differ diff --git a/train/image/train/chocolate/candy_00_gz8ubhup.jpg b/train/image/train/chocolate/candy_00_gz8ubhup.jpg new file mode 100644 index 0000000..4c2480e Binary files /dev/null and b/train/image/train/chocolate/candy_00_gz8ubhup.jpg differ diff --git a/train/image/train/chocolate/candy_00_iz6biioe.jpg b/train/image/train/chocolate/candy_00_iz6biioe.jpg new file mode 100644 index 0000000..7bd9f8a Binary files /dev/null and b/train/image/train/chocolate/candy_00_iz6biioe.jpg differ diff --git a/train/image/train/chocolate/candy_00_mk2gvj2y.jpg b/train/image/train/chocolate/candy_00_mk2gvj2y.jpg new file mode 100644 index 0000000..1b2ef95 Binary files /dev/null and b/train/image/train/chocolate/candy_00_mk2gvj2y.jpg differ diff --git a/train/image/train/chocolate/candy_00_mw6j3p3a.jpg b/train/image/train/chocolate/candy_00_mw6j3p3a.jpg new file mode 100644 index 0000000..1e1def1 Binary files /dev/null and b/train/image/train/chocolate/candy_00_mw6j3p3a.jpg differ diff --git a/train/image/train/chocolate/candy_00_njo32e3f.jpg b/train/image/train/chocolate/candy_00_njo32e3f.jpg new file mode 100644 index 0000000..9f9df83 Binary files /dev/null and b/train/image/train/chocolate/candy_00_njo32e3f.jpg differ diff --git a/train/image/train/chocolate/candy_00_ntub52d6.jpg b/train/image/train/chocolate/candy_00_ntub52d6.jpg new file mode 100644 index 0000000..5f99922 Binary files /dev/null and b/train/image/train/chocolate/candy_00_ntub52d6.jpg differ diff --git a/train/image/train/chocolate/candy_00_o4cs6abr.jpg b/train/image/train/chocolate/candy_00_o4cs6abr.jpg new file mode 100644 index 0000000..7ce5c5b Binary files /dev/null and b/train/image/train/chocolate/candy_00_o4cs6abr.jpg differ diff --git a/train/image/train/chocolate/candy_00_piau5rcz.jpg b/train/image/train/chocolate/candy_00_piau5rcz.jpg new file mode 100644 index 0000000..e4ffe9b Binary files /dev/null and b/train/image/train/chocolate/candy_00_piau5rcz.jpg differ diff --git a/train/image/train/chocolate/candy_00_psu7alra.jpg b/train/image/train/chocolate/candy_00_psu7alra.jpg new file mode 100644 index 0000000..8b09b00 Binary files /dev/null and b/train/image/train/chocolate/candy_00_psu7alra.jpg differ diff --git a/train/image/train/chocolate/candy_00_qv921z0e.jpg b/train/image/train/chocolate/candy_00_qv921z0e.jpg new file mode 100644 index 0000000..b98ee74 Binary files /dev/null and b/train/image/train/chocolate/candy_00_qv921z0e.jpg differ diff --git a/train/image/train/chocolate/candy_00_rd19mnk5.jpg b/train/image/train/chocolate/candy_00_rd19mnk5.jpg new file mode 100644 index 0000000..b844dca Binary files /dev/null and b/train/image/train/chocolate/candy_00_rd19mnk5.jpg differ diff --git a/train/image/train/chocolate/candy_00_s6c74dyo.jpg b/train/image/train/chocolate/candy_00_s6c74dyo.jpg new file mode 100644 index 0000000..c356733 Binary files /dev/null and b/train/image/train/chocolate/candy_00_s6c74dyo.jpg differ diff --git a/train/image/train/chocolate/candy_00_sxzdcnyb.jpg b/train/image/train/chocolate/candy_00_sxzdcnyb.jpg new file mode 100644 index 0000000..3fa5ad3 Binary files /dev/null and b/train/image/train/chocolate/candy_00_sxzdcnyb.jpg differ diff --git a/train/image/train/chocolate/candy_00_tfyywgjg.jpg b/train/image/train/chocolate/candy_00_tfyywgjg.jpg new file mode 100644 index 0000000..8c42d0f Binary files /dev/null and b/train/image/train/chocolate/candy_00_tfyywgjg.jpg differ diff --git a/train/image/train/chocolate/candy_00_tyhoi60u.jpg b/train/image/train/chocolate/candy_00_tyhoi60u.jpg new file mode 100644 index 0000000..b00230d Binary files /dev/null and b/train/image/train/chocolate/candy_00_tyhoi60u.jpg differ diff --git a/train/image/train/chocolate/candy_00_w4lu7h4p.jpg b/train/image/train/chocolate/candy_00_w4lu7h4p.jpg new file mode 100644 index 0000000..930501b Binary files /dev/null and b/train/image/train/chocolate/candy_00_w4lu7h4p.jpg differ diff --git a/train/image/train/chocolate/candy_00_wyp0255m.jpg b/train/image/train/chocolate/candy_00_wyp0255m.jpg new file mode 100644 index 0000000..3c303ed Binary files /dev/null and b/train/image/train/chocolate/candy_00_wyp0255m.jpg differ diff --git a/train/image/train/chocolate/candy_00_zakymy33.jpg b/train/image/train/chocolate/candy_00_zakymy33.jpg new file mode 100644 index 0000000..79f2493 Binary files /dev/null and b/train/image/train/chocolate/candy_00_zakymy33.jpg differ diff --git a/train/image/train/chocolate/candy_01_0zjr3d8u.jpg b/train/image/train/chocolate/candy_01_0zjr3d8u.jpg new file mode 100644 index 0000000..9d4138e Binary files /dev/null and b/train/image/train/chocolate/candy_01_0zjr3d8u.jpg differ diff --git a/train/image/train/chocolate/candy_01_46ba315w.jpg b/train/image/train/chocolate/candy_01_46ba315w.jpg new file mode 100644 index 0000000..84bcc15 Binary files /dev/null and b/train/image/train/chocolate/candy_01_46ba315w.jpg differ diff --git a/train/image/train/chocolate/candy_01_58r11n6h.jpg b/train/image/train/chocolate/candy_01_58r11n6h.jpg new file mode 100644 index 0000000..94fbe2d Binary files /dev/null and b/train/image/train/chocolate/candy_01_58r11n6h.jpg differ diff --git a/train/image/train/chocolate/candy_01_5kbvdt1x.jpg b/train/image/train/chocolate/candy_01_5kbvdt1x.jpg new file mode 100644 index 0000000..550772a Binary files /dev/null and b/train/image/train/chocolate/candy_01_5kbvdt1x.jpg differ diff --git a/train/image/train/chocolate/candy_01_69uce9f6.jpg b/train/image/train/chocolate/candy_01_69uce9f6.jpg new file mode 100644 index 0000000..9ffca79 Binary files /dev/null and b/train/image/train/chocolate/candy_01_69uce9f6.jpg differ diff --git a/train/image/train/chocolate/candy_01_6zgofiit.jpg b/train/image/train/chocolate/candy_01_6zgofiit.jpg new file mode 100644 index 0000000..e3cd092 Binary files /dev/null and b/train/image/train/chocolate/candy_01_6zgofiit.jpg differ diff --git a/train/image/train/chocolate/candy_01_8rt6osiz.jpg b/train/image/train/chocolate/candy_01_8rt6osiz.jpg new file mode 100644 index 0000000..ed622ed Binary files /dev/null and b/train/image/train/chocolate/candy_01_8rt6osiz.jpg differ diff --git a/train/image/train/chocolate/candy_01_94cm3wyz.jpg b/train/image/train/chocolate/candy_01_94cm3wyz.jpg new file mode 100644 index 0000000..62ca1a1 Binary files /dev/null and b/train/image/train/chocolate/candy_01_94cm3wyz.jpg differ diff --git a/train/image/train/chocolate/candy_01_9lbug0x0.jpg b/train/image/train/chocolate/candy_01_9lbug0x0.jpg new file mode 100644 index 0000000..c291453 Binary files /dev/null and b/train/image/train/chocolate/candy_01_9lbug0x0.jpg differ diff --git a/train/image/train/chocolate/candy_01_afrnlvhf.jpg b/train/image/train/chocolate/candy_01_afrnlvhf.jpg new file mode 100644 index 0000000..a880148 Binary files /dev/null and b/train/image/train/chocolate/candy_01_afrnlvhf.jpg differ diff --git a/train/image/train/chocolate/candy_01_avao7l7c.jpg b/train/image/train/chocolate/candy_01_avao7l7c.jpg new file mode 100644 index 0000000..90c737c Binary files /dev/null and b/train/image/train/chocolate/candy_01_avao7l7c.jpg differ diff --git a/train/image/train/chocolate/candy_01_avsvygws.jpg b/train/image/train/chocolate/candy_01_avsvygws.jpg new file mode 100644 index 0000000..e76a791 Binary files /dev/null and b/train/image/train/chocolate/candy_01_avsvygws.jpg differ diff --git a/train/image/train/chocolate/candy_01_bsnnnm0d.jpg b/train/image/train/chocolate/candy_01_bsnnnm0d.jpg new file mode 100644 index 0000000..a5d26f9 Binary files /dev/null and b/train/image/train/chocolate/candy_01_bsnnnm0d.jpg differ diff --git a/train/image/train/chocolate/candy_01_bybqsn6m.jpg b/train/image/train/chocolate/candy_01_bybqsn6m.jpg new file mode 100644 index 0000000..703cb2b Binary files /dev/null and b/train/image/train/chocolate/candy_01_bybqsn6m.jpg differ diff --git a/train/image/train/chocolate/candy_01_dfr0os91.jpg b/train/image/train/chocolate/candy_01_dfr0os91.jpg new file mode 100644 index 0000000..9e16eb5 Binary files /dev/null and b/train/image/train/chocolate/candy_01_dfr0os91.jpg differ diff --git a/train/image/train/chocolate/candy_01_i2l7jbnv.jpg b/train/image/train/chocolate/candy_01_i2l7jbnv.jpg new file mode 100644 index 0000000..f6fb000 Binary files /dev/null and b/train/image/train/chocolate/candy_01_i2l7jbnv.jpg differ diff --git a/train/image/train/chocolate/candy_01_jvse9qfz.jpg b/train/image/train/chocolate/candy_01_jvse9qfz.jpg new file mode 100644 index 0000000..51167bd Binary files /dev/null and b/train/image/train/chocolate/candy_01_jvse9qfz.jpg differ diff --git a/train/image/train/chocolate/candy_01_lem85lcw.jpg b/train/image/train/chocolate/candy_01_lem85lcw.jpg new file mode 100644 index 0000000..ce02040 Binary files /dev/null and b/train/image/train/chocolate/candy_01_lem85lcw.jpg differ diff --git a/train/image/train/chocolate/candy_01_neqrjiuh.jpg b/train/image/train/chocolate/candy_01_neqrjiuh.jpg new file mode 100644 index 0000000..4c172fb Binary files /dev/null and b/train/image/train/chocolate/candy_01_neqrjiuh.jpg differ diff --git a/train/image/train/chocolate/candy_01_nrip8rj7.jpg b/train/image/train/chocolate/candy_01_nrip8rj7.jpg new file mode 100644 index 0000000..b532aee Binary files /dev/null and b/train/image/train/chocolate/candy_01_nrip8rj7.jpg differ diff --git a/train/image/train/chocolate/candy_01_plixbiwd.jpg b/train/image/train/chocolate/candy_01_plixbiwd.jpg new file mode 100644 index 0000000..290fd34 Binary files /dev/null and b/train/image/train/chocolate/candy_01_plixbiwd.jpg differ diff --git a/train/image/train/chocolate/candy_01_s2apta5f.jpg b/train/image/train/chocolate/candy_01_s2apta5f.jpg new file mode 100644 index 0000000..c58a0a4 Binary files /dev/null and b/train/image/train/chocolate/candy_01_s2apta5f.jpg differ diff --git a/train/image/train/chocolate/candy_01_syu9xk9k.jpg b/train/image/train/chocolate/candy_01_syu9xk9k.jpg new file mode 100644 index 0000000..5c8d9ac Binary files /dev/null and b/train/image/train/chocolate/candy_01_syu9xk9k.jpg differ diff --git a/train/image/train/chocolate/candy_01_tebdw2gh.jpg b/train/image/train/chocolate/candy_01_tebdw2gh.jpg new file mode 100644 index 0000000..ea4b212 Binary files /dev/null and b/train/image/train/chocolate/candy_01_tebdw2gh.jpg differ diff --git a/train/image/train/chocolate/candy_01_tp4t7ofp.jpg b/train/image/train/chocolate/candy_01_tp4t7ofp.jpg new file mode 100644 index 0000000..b4213cc Binary files /dev/null and b/train/image/train/chocolate/candy_01_tp4t7ofp.jpg differ diff --git a/train/image/train/chocolate/candy_01_uugawlht.jpg b/train/image/train/chocolate/candy_01_uugawlht.jpg new file mode 100644 index 0000000..3079545 Binary files /dev/null and b/train/image/train/chocolate/candy_01_uugawlht.jpg differ diff --git a/train/image/train/chocolate/candy_01_ve2usf3x.jpg b/train/image/train/chocolate/candy_01_ve2usf3x.jpg new file mode 100644 index 0000000..4b8ddf8 Binary files /dev/null and b/train/image/train/chocolate/candy_01_ve2usf3x.jpg differ diff --git a/train/image/train/chocolate/candy_01_vofwmf6e.jpg b/train/image/train/chocolate/candy_01_vofwmf6e.jpg new file mode 100644 index 0000000..cd9eea0 Binary files /dev/null and b/train/image/train/chocolate/candy_01_vofwmf6e.jpg differ diff --git a/train/image/train/chocolate/candy_01_w0xhf55m.jpg b/train/image/train/chocolate/candy_01_w0xhf55m.jpg new file mode 100644 index 0000000..07858b2 Binary files /dev/null and b/train/image/train/chocolate/candy_01_w0xhf55m.jpg differ diff --git a/train/image/train/chocolate/candy_01_xhnhh684.jpg b/train/image/train/chocolate/candy_01_xhnhh684.jpg new file mode 100644 index 0000000..c24fa5d Binary files /dev/null and b/train/image/train/chocolate/candy_01_xhnhh684.jpg differ diff --git a/train/image/train/chocolate/candy_01_ydjx4wrg.jpg b/train/image/train/chocolate/candy_01_ydjx4wrg.jpg new file mode 100644 index 0000000..2f6cf26 Binary files /dev/null and b/train/image/train/chocolate/candy_01_ydjx4wrg.jpg differ diff --git a/train/image/train/chocolate/candy_01_yf9i3p8g.jpg b/train/image/train/chocolate/candy_01_yf9i3p8g.jpg new file mode 100644 index 0000000..1c97227 Binary files /dev/null and b/train/image/train/chocolate/candy_01_yf9i3p8g.jpg differ diff --git a/train/image/train/chocolate/candy_01_yg385z6e.jpg b/train/image/train/chocolate/candy_01_yg385z6e.jpg new file mode 100644 index 0000000..7c21203 Binary files /dev/null and b/train/image/train/chocolate/candy_01_yg385z6e.jpg differ diff --git a/train/image/train/chocolate/candy_01_zbsb9wt3.jpg b/train/image/train/chocolate/candy_01_zbsb9wt3.jpg new file mode 100644 index 0000000..ca698df Binary files /dev/null and b/train/image/train/chocolate/candy_01_zbsb9wt3.jpg differ diff --git a/train/image/train/chocolate/candy_02_0mbd4d0c.jpg b/train/image/train/chocolate/candy_02_0mbd4d0c.jpg new file mode 100644 index 0000000..ffdda91 Binary files /dev/null and b/train/image/train/chocolate/candy_02_0mbd4d0c.jpg differ diff --git a/train/image/train/chocolate/candy_02_2yu9r3ap.jpg b/train/image/train/chocolate/candy_02_2yu9r3ap.jpg new file mode 100644 index 0000000..ae1f54b Binary files /dev/null and b/train/image/train/chocolate/candy_02_2yu9r3ap.jpg differ diff --git a/train/image/train/chocolate/candy_02_2zv18oa0.jpg b/train/image/train/chocolate/candy_02_2zv18oa0.jpg new file mode 100644 index 0000000..737064c Binary files /dev/null and b/train/image/train/chocolate/candy_02_2zv18oa0.jpg differ diff --git a/train/image/train/chocolate/candy_02_3a8ozsrr.jpg b/train/image/train/chocolate/candy_02_3a8ozsrr.jpg new file mode 100644 index 0000000..f99d1bb Binary files /dev/null and b/train/image/train/chocolate/candy_02_3a8ozsrr.jpg differ diff --git a/train/image/train/chocolate/candy_02_5tl482je.jpg b/train/image/train/chocolate/candy_02_5tl482je.jpg new file mode 100644 index 0000000..205b15f Binary files /dev/null and b/train/image/train/chocolate/candy_02_5tl482je.jpg differ diff --git a/train/image/train/chocolate/candy_02_6k5s5r4p.jpg b/train/image/train/chocolate/candy_02_6k5s5r4p.jpg new file mode 100644 index 0000000..cbdc676 Binary files /dev/null and b/train/image/train/chocolate/candy_02_6k5s5r4p.jpg differ diff --git a/train/image/train/chocolate/candy_02_89y1laj6.jpg b/train/image/train/chocolate/candy_02_89y1laj6.jpg new file mode 100644 index 0000000..403ea3f Binary files /dev/null and b/train/image/train/chocolate/candy_02_89y1laj6.jpg differ diff --git a/train/image/train/chocolate/candy_02_mm3rzmyk.jpg b/train/image/train/chocolate/candy_02_mm3rzmyk.jpg new file mode 100644 index 0000000..2262aba Binary files /dev/null and b/train/image/train/chocolate/candy_02_mm3rzmyk.jpg differ diff --git a/train/image/train/chocolate/candy_02_qsji2j1x.jpg b/train/image/train/chocolate/candy_02_qsji2j1x.jpg new file mode 100644 index 0000000..bfb93c8 Binary files /dev/null and b/train/image/train/chocolate/candy_02_qsji2j1x.jpg differ diff --git a/train/image/train/chocolate/candy_02_ure9yu96.jpg b/train/image/train/chocolate/candy_02_ure9yu96.jpg new file mode 100644 index 0000000..87a1ccb Binary files /dev/null and b/train/image/train/chocolate/candy_02_ure9yu96.jpg differ diff --git a/train/image/train/chocolate/candy_02_ws2x097a.jpg b/train/image/train/chocolate/candy_02_ws2x097a.jpg new file mode 100644 index 0000000..a9688d4 Binary files /dev/null and b/train/image/train/chocolate/candy_02_ws2x097a.jpg differ diff --git a/train/image/train/chocolate/candy_03_2qqgeeyb.jpg b/train/image/train/chocolate/candy_03_2qqgeeyb.jpg new file mode 100644 index 0000000..23d6a5c Binary files /dev/null and b/train/image/train/chocolate/candy_03_2qqgeeyb.jpg differ diff --git a/train/image/train/chocolate/candy_03_8uet738k.jpg b/train/image/train/chocolate/candy_03_8uet738k.jpg new file mode 100644 index 0000000..259d775 Binary files /dev/null and b/train/image/train/chocolate/candy_03_8uet738k.jpg differ diff --git a/train/image/train/chocolate/candy_03_atjua8py.jpg b/train/image/train/chocolate/candy_03_atjua8py.jpg new file mode 100644 index 0000000..0b02da5 Binary files /dev/null and b/train/image/train/chocolate/candy_03_atjua8py.jpg differ diff --git a/train/image/train/chocolate/candy_03_sgj5r6xu.jpg b/train/image/train/chocolate/candy_03_sgj5r6xu.jpg new file mode 100644 index 0000000..98a3736 Binary files /dev/null and b/train/image/train/chocolate/candy_03_sgj5r6xu.jpg differ diff --git a/train/image/train/chocolate/candy_03_skbuni64.jpg b/train/image/train/chocolate/candy_03_skbuni64.jpg new file mode 100644 index 0000000..7b6277a Binary files /dev/null and b/train/image/train/chocolate/candy_03_skbuni64.jpg differ diff --git a/train/image/train/chocolate/candy_04_4rteklez.jpg b/train/image/train/chocolate/candy_04_4rteklez.jpg new file mode 100644 index 0000000..970e7ce Binary files /dev/null and b/train/image/train/chocolate/candy_04_4rteklez.jpg differ diff --git a/train/image/train/chocolate/candy_04_efaubk22.jpg b/train/image/train/chocolate/candy_04_efaubk22.jpg new file mode 100644 index 0000000..471786c Binary files /dev/null and b/train/image/train/chocolate/candy_04_efaubk22.jpg differ diff --git a/train/image/train/chocolate/candy_04_mvf8vrnf.jpg b/train/image/train/chocolate/candy_04_mvf8vrnf.jpg new file mode 100644 index 0000000..7ddc545 Binary files /dev/null and b/train/image/train/chocolate/candy_04_mvf8vrnf.jpg differ diff --git a/train/image/train/chocolate/candy_05_bb5zlllu.jpg b/train/image/train/chocolate/candy_05_bb5zlllu.jpg new file mode 100644 index 0000000..6ab9443 Binary files /dev/null and b/train/image/train/chocolate/candy_05_bb5zlllu.jpg differ diff --git a/train/image/train/chocolate/candy_05_hj1oxoa0.jpg b/train/image/train/chocolate/candy_05_hj1oxoa0.jpg new file mode 100644 index 0000000..78decb7 Binary files /dev/null and b/train/image/train/chocolate/candy_05_hj1oxoa0.jpg differ diff --git a/train/image/train/chocolate/candy_05_hutds71e.jpg b/train/image/train/chocolate/candy_05_hutds71e.jpg new file mode 100644 index 0000000..a35ae22 Binary files /dev/null and b/train/image/train/chocolate/candy_05_hutds71e.jpg differ diff --git a/train/image/train/chocolate/candy_06_ta3kunkw.jpg b/train/image/train/chocolate/candy_06_ta3kunkw.jpg new file mode 100644 index 0000000..fa39fa1 Binary files /dev/null and b/train/image/train/chocolate/candy_06_ta3kunkw.jpg differ diff --git a/train/image/train/gum/candy_00_abkviqfy.jpg b/train/image/train/gum/candy_00_abkviqfy.jpg new file mode 100644 index 0000000..ae3e6a4 Binary files /dev/null and b/train/image/train/gum/candy_00_abkviqfy.jpg differ diff --git a/train/image/train/gum/candy_01_hh204b72.jpg b/train/image/train/gum/candy_01_hh204b72.jpg new file mode 100644 index 0000000..f761e11 Binary files /dev/null and b/train/image/train/gum/candy_01_hh204b72.jpg differ diff --git a/train/image/train/gum/candy_01_hmsofn1k.jpg b/train/image/train/gum/candy_01_hmsofn1k.jpg new file mode 100644 index 0000000..a0f6e11 Binary files /dev/null and b/train/image/train/gum/candy_01_hmsofn1k.jpg differ diff --git a/train/image/train/gum/candy_01_hsf2a0i5.jpg b/train/image/train/gum/candy_01_hsf2a0i5.jpg new file mode 100644 index 0000000..97d6149 Binary files /dev/null and b/train/image/train/gum/candy_01_hsf2a0i5.jpg differ diff --git a/train/image/train/gum/candy_01_hyxcc3qz.jpg b/train/image/train/gum/candy_01_hyxcc3qz.jpg new file mode 100644 index 0000000..96f538c Binary files /dev/null and b/train/image/train/gum/candy_01_hyxcc3qz.jpg differ diff --git a/train/image/train/gum/candy_01_kgij09ss.jpg b/train/image/train/gum/candy_01_kgij09ss.jpg new file mode 100644 index 0000000..34dfd63 Binary files /dev/null and b/train/image/train/gum/candy_01_kgij09ss.jpg differ diff --git a/train/image/train/gum/candy_01_kv73hb6s.jpg b/train/image/train/gum/candy_01_kv73hb6s.jpg new file mode 100644 index 0000000..a5cac04 Binary files /dev/null and b/train/image/train/gum/candy_01_kv73hb6s.jpg differ diff --git a/train/image/train/gum/candy_01_lqy27fz5.jpg b/train/image/train/gum/candy_01_lqy27fz5.jpg new file mode 100644 index 0000000..fae42f3 Binary files /dev/null and b/train/image/train/gum/candy_01_lqy27fz5.jpg differ diff --git a/train/image/train/gum/candy_01_nqbgoy5r.jpg b/train/image/train/gum/candy_01_nqbgoy5r.jpg new file mode 100644 index 0000000..0c0c5f8 Binary files /dev/null and b/train/image/train/gum/candy_01_nqbgoy5r.jpg differ diff --git a/train/image/train/gum/candy_01_p34r94j4.jpg b/train/image/train/gum/candy_01_p34r94j4.jpg new file mode 100644 index 0000000..0e8912f Binary files /dev/null and b/train/image/train/gum/candy_01_p34r94j4.jpg differ diff --git a/train/image/train/gum/candy_01_pmvbb0gf.jpg b/train/image/train/gum/candy_01_pmvbb0gf.jpg new file mode 100644 index 0000000..e420145 Binary files /dev/null and b/train/image/train/gum/candy_01_pmvbb0gf.jpg differ diff --git a/train/image/train/gum/candy_01_poodfv4i.jpg b/train/image/train/gum/candy_01_poodfv4i.jpg new file mode 100644 index 0000000..723ab5a Binary files /dev/null and b/train/image/train/gum/candy_01_poodfv4i.jpg differ diff --git a/train/image/train/gum/candy_01_pyblpxpb.jpg b/train/image/train/gum/candy_01_pyblpxpb.jpg new file mode 100644 index 0000000..425ba7b Binary files /dev/null and b/train/image/train/gum/candy_01_pyblpxpb.jpg differ diff --git a/train/image/train/gum/candy_01_r2rnif16.jpg b/train/image/train/gum/candy_01_r2rnif16.jpg new file mode 100644 index 0000000..24c87ad Binary files /dev/null and b/train/image/train/gum/candy_01_r2rnif16.jpg differ diff --git a/train/image/train/gum/candy_01_sn9ehmif.jpg b/train/image/train/gum/candy_01_sn9ehmif.jpg new file mode 100644 index 0000000..5d16f0d Binary files /dev/null and b/train/image/train/gum/candy_01_sn9ehmif.jpg differ diff --git a/train/image/train/gum/candy_01_ti7zfoxy.jpg b/train/image/train/gum/candy_01_ti7zfoxy.jpg new file mode 100644 index 0000000..ea53252 Binary files /dev/null and b/train/image/train/gum/candy_01_ti7zfoxy.jpg differ diff --git a/train/image/train/gum/candy_01_ueoatfjn.jpg b/train/image/train/gum/candy_01_ueoatfjn.jpg new file mode 100644 index 0000000..ca8ee73 Binary files /dev/null and b/train/image/train/gum/candy_01_ueoatfjn.jpg differ diff --git a/train/image/train/gum/candy_01_wp1x0qhj.jpg b/train/image/train/gum/candy_01_wp1x0qhj.jpg new file mode 100644 index 0000000..0ca65f8 Binary files /dev/null and b/train/image/train/gum/candy_01_wp1x0qhj.jpg differ diff --git a/train/image/train/gum/candy_01_zjzl60jj.jpg b/train/image/train/gum/candy_01_zjzl60jj.jpg new file mode 100644 index 0000000..b863bc8 Binary files /dev/null and b/train/image/train/gum/candy_01_zjzl60jj.jpg differ diff --git a/train/image/train/gum/candy_01_zoxe44ve.jpg b/train/image/train/gum/candy_01_zoxe44ve.jpg new file mode 100644 index 0000000..cce3c69 Binary files /dev/null and b/train/image/train/gum/candy_01_zoxe44ve.jpg differ diff --git a/train/image/train/gum/candy_02_1ibo301h.jpg b/train/image/train/gum/candy_02_1ibo301h.jpg new file mode 100644 index 0000000..76b126e Binary files /dev/null and b/train/image/train/gum/candy_02_1ibo301h.jpg differ diff --git a/train/image/train/gum/candy_02_1pwuv85e.jpg b/train/image/train/gum/candy_02_1pwuv85e.jpg new file mode 100644 index 0000000..3faca15 Binary files /dev/null and b/train/image/train/gum/candy_02_1pwuv85e.jpg differ diff --git a/train/image/train/gum/candy_02_26loxx08.jpg b/train/image/train/gum/candy_02_26loxx08.jpg new file mode 100644 index 0000000..e307267 Binary files /dev/null and b/train/image/train/gum/candy_02_26loxx08.jpg differ diff --git a/train/image/train/gum/candy_02_2dg6okyg.jpg b/train/image/train/gum/candy_02_2dg6okyg.jpg new file mode 100644 index 0000000..0ce0092 Binary files /dev/null and b/train/image/train/gum/candy_02_2dg6okyg.jpg differ diff --git a/train/image/train/gum/candy_02_3abu59j2.jpg b/train/image/train/gum/candy_02_3abu59j2.jpg new file mode 100644 index 0000000..a79aaa0 Binary files /dev/null and b/train/image/train/gum/candy_02_3abu59j2.jpg differ diff --git a/train/image/train/gum/candy_02_3qp75b7x.jpg b/train/image/train/gum/candy_02_3qp75b7x.jpg new file mode 100644 index 0000000..f41915f Binary files /dev/null and b/train/image/train/gum/candy_02_3qp75b7x.jpg differ diff --git a/train/image/train/gum/candy_02_5jo06bav.jpg b/train/image/train/gum/candy_02_5jo06bav.jpg new file mode 100644 index 0000000..62a9c56 Binary files /dev/null and b/train/image/train/gum/candy_02_5jo06bav.jpg differ diff --git a/train/image/train/gum/candy_02_5kvh3wqn.jpg b/train/image/train/gum/candy_02_5kvh3wqn.jpg new file mode 100644 index 0000000..c1d2318 Binary files /dev/null and b/train/image/train/gum/candy_02_5kvh3wqn.jpg differ diff --git a/train/image/train/gum/candy_02_6ga74naw.jpg b/train/image/train/gum/candy_02_6ga74naw.jpg new file mode 100644 index 0000000..4e353b9 Binary files /dev/null and b/train/image/train/gum/candy_02_6ga74naw.jpg differ diff --git a/train/image/train/gum/candy_02_6kq8x3gp.jpg b/train/image/train/gum/candy_02_6kq8x3gp.jpg new file mode 100644 index 0000000..ddb4436 Binary files /dev/null and b/train/image/train/gum/candy_02_6kq8x3gp.jpg differ diff --git a/train/image/train/gum/candy_02_6pg6v3j7.jpg b/train/image/train/gum/candy_02_6pg6v3j7.jpg new file mode 100644 index 0000000..ac56361 Binary files /dev/null and b/train/image/train/gum/candy_02_6pg6v3j7.jpg differ diff --git a/train/image/train/gum/candy_02_7sl4kud6.jpg b/train/image/train/gum/candy_02_7sl4kud6.jpg new file mode 100644 index 0000000..eeb5b9f Binary files /dev/null and b/train/image/train/gum/candy_02_7sl4kud6.jpg differ diff --git a/train/image/train/gum/candy_02_7ss6me6k.jpg b/train/image/train/gum/candy_02_7ss6me6k.jpg new file mode 100644 index 0000000..3c17dcc Binary files /dev/null and b/train/image/train/gum/candy_02_7ss6me6k.jpg differ diff --git a/train/image/train/gum/candy_02_7y05b26e.jpg b/train/image/train/gum/candy_02_7y05b26e.jpg new file mode 100644 index 0000000..ab5a217 Binary files /dev/null and b/train/image/train/gum/candy_02_7y05b26e.jpg differ diff --git a/train/image/train/gum/candy_02_8c9q26jn.jpg b/train/image/train/gum/candy_02_8c9q26jn.jpg new file mode 100644 index 0000000..5e2df48 Binary files /dev/null and b/train/image/train/gum/candy_02_8c9q26jn.jpg differ diff --git a/train/image/train/gum/candy_02_8cqd1m55.jpg b/train/image/train/gum/candy_02_8cqd1m55.jpg new file mode 100644 index 0000000..2543e93 Binary files /dev/null and b/train/image/train/gum/candy_02_8cqd1m55.jpg differ diff --git a/train/image/train/gum/candy_02_9w80a7yr.jpg b/train/image/train/gum/candy_02_9w80a7yr.jpg new file mode 100644 index 0000000..e8ca040 Binary files /dev/null and b/train/image/train/gum/candy_02_9w80a7yr.jpg differ diff --git a/train/image/train/gum/candy_02_a71fw363.jpg b/train/image/train/gum/candy_02_a71fw363.jpg new file mode 100644 index 0000000..b16074b Binary files /dev/null and b/train/image/train/gum/candy_02_a71fw363.jpg differ diff --git a/train/image/train/gum/candy_02_azkx47l1.jpg b/train/image/train/gum/candy_02_azkx47l1.jpg new file mode 100644 index 0000000..948e0df Binary files /dev/null and b/train/image/train/gum/candy_02_azkx47l1.jpg differ diff --git a/train/image/train/gum/candy_02_bntq6bck.jpg b/train/image/train/gum/candy_02_bntq6bck.jpg new file mode 100644 index 0000000..28c1fad Binary files /dev/null and b/train/image/train/gum/candy_02_bntq6bck.jpg differ diff --git a/train/image/train/gum/candy_02_ckziss1y.jpg b/train/image/train/gum/candy_02_ckziss1y.jpg new file mode 100644 index 0000000..9f71958 Binary files /dev/null and b/train/image/train/gum/candy_02_ckziss1y.jpg differ diff --git a/train/image/train/gum/candy_02_dhfmvo55.jpg b/train/image/train/gum/candy_02_dhfmvo55.jpg new file mode 100644 index 0000000..3b013b1 Binary files /dev/null and b/train/image/train/gum/candy_02_dhfmvo55.jpg differ diff --git a/train/image/train/gum/candy_02_dqubhn6j.jpg b/train/image/train/gum/candy_02_dqubhn6j.jpg new file mode 100644 index 0000000..bec29eb Binary files /dev/null and b/train/image/train/gum/candy_02_dqubhn6j.jpg differ diff --git a/train/image/train/gum/candy_02_ecptfi99.jpg b/train/image/train/gum/candy_02_ecptfi99.jpg new file mode 100644 index 0000000..c0eb6a6 Binary files /dev/null and b/train/image/train/gum/candy_02_ecptfi99.jpg differ diff --git a/train/image/train/gum/candy_02_elugbm43.jpg b/train/image/train/gum/candy_02_elugbm43.jpg new file mode 100644 index 0000000..27d64a5 Binary files /dev/null and b/train/image/train/gum/candy_02_elugbm43.jpg differ diff --git a/train/image/train/gum/candy_02_hfir326h.jpg b/train/image/train/gum/candy_02_hfir326h.jpg new file mode 100644 index 0000000..e88a85f Binary files /dev/null and b/train/image/train/gum/candy_02_hfir326h.jpg differ diff --git a/train/image/train/gum/candy_02_i6us7atw.jpg b/train/image/train/gum/candy_02_i6us7atw.jpg new file mode 100644 index 0000000..0bd1a72 Binary files /dev/null and b/train/image/train/gum/candy_02_i6us7atw.jpg differ diff --git a/train/image/train/gum/candy_02_k3fijgen.jpg b/train/image/train/gum/candy_02_k3fijgen.jpg new file mode 100644 index 0000000..95ccc55 Binary files /dev/null and b/train/image/train/gum/candy_02_k3fijgen.jpg differ diff --git a/train/image/train/gum/candy_02_kdabio65.jpg b/train/image/train/gum/candy_02_kdabio65.jpg new file mode 100644 index 0000000..4ab94bc Binary files /dev/null and b/train/image/train/gum/candy_02_kdabio65.jpg differ diff --git a/train/image/train/gum/candy_02_kt6ea68b.jpg b/train/image/train/gum/candy_02_kt6ea68b.jpg new file mode 100644 index 0000000..2a3bd8c Binary files /dev/null and b/train/image/train/gum/candy_02_kt6ea68b.jpg differ diff --git a/train/image/train/gum/candy_02_m2lw5plf.jpg b/train/image/train/gum/candy_02_m2lw5plf.jpg new file mode 100644 index 0000000..c8994ce Binary files /dev/null and b/train/image/train/gum/candy_02_m2lw5plf.jpg differ diff --git a/train/image/train/gum/candy_02_ms6f5aly.jpg b/train/image/train/gum/candy_02_ms6f5aly.jpg new file mode 100644 index 0000000..6fd780b Binary files /dev/null and b/train/image/train/gum/candy_02_ms6f5aly.jpg differ diff --git a/train/image/train/gum/candy_02_nv46q95d.jpg b/train/image/train/gum/candy_02_nv46q95d.jpg new file mode 100644 index 0000000..15c1c06 Binary files /dev/null and b/train/image/train/gum/candy_02_nv46q95d.jpg differ diff --git a/train/image/train/gum/candy_02_p7vmye7v.jpg b/train/image/train/gum/candy_02_p7vmye7v.jpg new file mode 100644 index 0000000..68be25c Binary files /dev/null and b/train/image/train/gum/candy_02_p7vmye7v.jpg differ diff --git a/train/image/train/gum/candy_02_psncgjun.jpg b/train/image/train/gum/candy_02_psncgjun.jpg new file mode 100644 index 0000000..cc056ea Binary files /dev/null and b/train/image/train/gum/candy_02_psncgjun.jpg differ diff --git a/train/image/train/gum/candy_02_qsdcuxpr.jpg b/train/image/train/gum/candy_02_qsdcuxpr.jpg new file mode 100644 index 0000000..8e55c3d Binary files /dev/null and b/train/image/train/gum/candy_02_qsdcuxpr.jpg differ diff --git a/train/image/train/gum/candy_02_sm8sclwv.jpg b/train/image/train/gum/candy_02_sm8sclwv.jpg new file mode 100644 index 0000000..5f03db3 Binary files /dev/null and b/train/image/train/gum/candy_02_sm8sclwv.jpg differ diff --git a/train/image/train/gum/candy_02_u0patm1e.jpg b/train/image/train/gum/candy_02_u0patm1e.jpg new file mode 100644 index 0000000..2a2be5f Binary files /dev/null and b/train/image/train/gum/candy_02_u0patm1e.jpg differ diff --git a/train/image/train/gum/candy_02_w70ywlhe.jpg b/train/image/train/gum/candy_02_w70ywlhe.jpg new file mode 100644 index 0000000..0595c99 Binary files /dev/null and b/train/image/train/gum/candy_02_w70ywlhe.jpg differ diff --git a/train/image/train/gum/candy_02_y3fmclhk.jpg b/train/image/train/gum/candy_02_y3fmclhk.jpg new file mode 100644 index 0000000..832933e Binary files /dev/null and b/train/image/train/gum/candy_02_y3fmclhk.jpg differ diff --git a/train/image/train/gum/candy_02_y80i7r3n.jpg b/train/image/train/gum/candy_02_y80i7r3n.jpg new file mode 100644 index 0000000..ea97246 Binary files /dev/null and b/train/image/train/gum/candy_02_y80i7r3n.jpg differ diff --git a/train/image/train/gum/candy_03_06avcwal.jpg b/train/image/train/gum/candy_03_06avcwal.jpg new file mode 100644 index 0000000..f31ad33 Binary files /dev/null and b/train/image/train/gum/candy_03_06avcwal.jpg differ diff --git a/train/image/train/gum/candy_03_344e97x8.jpg b/train/image/train/gum/candy_03_344e97x8.jpg new file mode 100644 index 0000000..0fcb899 Binary files /dev/null and b/train/image/train/gum/candy_03_344e97x8.jpg differ diff --git a/train/image/train/gum/candy_03_44olker2.jpg b/train/image/train/gum/candy_03_44olker2.jpg new file mode 100644 index 0000000..17f5670 Binary files /dev/null and b/train/image/train/gum/candy_03_44olker2.jpg differ diff --git a/train/image/train/gum/candy_03_4s5schff.jpg b/train/image/train/gum/candy_03_4s5schff.jpg new file mode 100644 index 0000000..4ffabbb Binary files /dev/null and b/train/image/train/gum/candy_03_4s5schff.jpg differ diff --git a/train/image/train/gum/candy_03_655shb34.jpg b/train/image/train/gum/candy_03_655shb34.jpg new file mode 100644 index 0000000..68f6d00 Binary files /dev/null and b/train/image/train/gum/candy_03_655shb34.jpg differ diff --git a/train/image/train/gum/candy_03_7ar9qphu.jpg b/train/image/train/gum/candy_03_7ar9qphu.jpg new file mode 100644 index 0000000..c4cc080 Binary files /dev/null and b/train/image/train/gum/candy_03_7ar9qphu.jpg differ diff --git a/train/image/train/gum/candy_03_7j15w437.jpg b/train/image/train/gum/candy_03_7j15w437.jpg new file mode 100644 index 0000000..ef73021 Binary files /dev/null and b/train/image/train/gum/candy_03_7j15w437.jpg differ diff --git a/train/image/train/gum/candy_03_8rsuolp2.jpg b/train/image/train/gum/candy_03_8rsuolp2.jpg new file mode 100644 index 0000000..9e5039c Binary files /dev/null and b/train/image/train/gum/candy_03_8rsuolp2.jpg differ diff --git a/train/image/train/gum/candy_03_e0470ium.jpg b/train/image/train/gum/candy_03_e0470ium.jpg new file mode 100644 index 0000000..fcb80da Binary files /dev/null and b/train/image/train/gum/candy_03_e0470ium.jpg differ diff --git a/train/image/train/gum/candy_03_higpozr5.jpg b/train/image/train/gum/candy_03_higpozr5.jpg new file mode 100644 index 0000000..50c11ff Binary files /dev/null and b/train/image/train/gum/candy_03_higpozr5.jpg differ diff --git a/train/image/train/gum/candy_03_hpygfenl.jpg b/train/image/train/gum/candy_03_hpygfenl.jpg new file mode 100644 index 0000000..1204efa Binary files /dev/null and b/train/image/train/gum/candy_03_hpygfenl.jpg differ diff --git a/train/image/train/gum/candy_03_kt7rb3gw.jpg b/train/image/train/gum/candy_03_kt7rb3gw.jpg new file mode 100644 index 0000000..5c5a3b6 Binary files /dev/null and b/train/image/train/gum/candy_03_kt7rb3gw.jpg differ diff --git a/train/image/train/gum/candy_03_sieijxp9.jpg b/train/image/train/gum/candy_03_sieijxp9.jpg new file mode 100644 index 0000000..99cc547 Binary files /dev/null and b/train/image/train/gum/candy_03_sieijxp9.jpg differ diff --git a/train/image/train/gum/candy_03_simgxyp9.jpg b/train/image/train/gum/candy_03_simgxyp9.jpg new file mode 100644 index 0000000..348b1f1 Binary files /dev/null and b/train/image/train/gum/candy_03_simgxyp9.jpg differ diff --git a/train/image/train/gum/candy_03_ukpe5ca5.jpg b/train/image/train/gum/candy_03_ukpe5ca5.jpg new file mode 100644 index 0000000..80c01af Binary files /dev/null and b/train/image/train/gum/candy_03_ukpe5ca5.jpg differ diff --git a/train/image/train/gum/candy_03_vehnbrn1.jpg b/train/image/train/gum/candy_03_vehnbrn1.jpg new file mode 100644 index 0000000..13918a8 Binary files /dev/null and b/train/image/train/gum/candy_03_vehnbrn1.jpg differ diff --git a/train/image/train/gum/candy_03_vyzfvv5n.jpg b/train/image/train/gum/candy_03_vyzfvv5n.jpg new file mode 100644 index 0000000..a78a74e Binary files /dev/null and b/train/image/train/gum/candy_03_vyzfvv5n.jpg differ diff --git a/train/image/train/gum/candy_03_x67i3znt.jpg b/train/image/train/gum/candy_03_x67i3znt.jpg new file mode 100644 index 0000000..16a864a Binary files /dev/null and b/train/image/train/gum/candy_03_x67i3znt.jpg differ diff --git a/train/image/train/gum/candy_03_xx1ru59j.jpg b/train/image/train/gum/candy_03_xx1ru59j.jpg new file mode 100644 index 0000000..209b173 Binary files /dev/null and b/train/image/train/gum/candy_03_xx1ru59j.jpg differ diff --git a/train/image/train/gum/candy_04_1ujf836f.jpg b/train/image/train/gum/candy_04_1ujf836f.jpg new file mode 100644 index 0000000..2e956e1 Binary files /dev/null and b/train/image/train/gum/candy_04_1ujf836f.jpg differ diff --git a/train/image/train/gum/candy_04_26i98yr7.jpg b/train/image/train/gum/candy_04_26i98yr7.jpg new file mode 100644 index 0000000..616d145 Binary files /dev/null and b/train/image/train/gum/candy_04_26i98yr7.jpg differ diff --git a/train/image/train/gum/candy_04_4x8hrhdp.jpg b/train/image/train/gum/candy_04_4x8hrhdp.jpg new file mode 100644 index 0000000..726fd26 Binary files /dev/null and b/train/image/train/gum/candy_04_4x8hrhdp.jpg differ diff --git a/train/image/train/gum/candy_04_66s6ylvs.jpg b/train/image/train/gum/candy_04_66s6ylvs.jpg new file mode 100644 index 0000000..728ff12 Binary files /dev/null and b/train/image/train/gum/candy_04_66s6ylvs.jpg differ diff --git a/train/image/train/gum/candy_04_atjd4yjb.jpg b/train/image/train/gum/candy_04_atjd4yjb.jpg new file mode 100644 index 0000000..a8afe2d Binary files /dev/null and b/train/image/train/gum/candy_04_atjd4yjb.jpg differ diff --git a/train/image/train/gum/candy_04_j1x5yehc.jpg b/train/image/train/gum/candy_04_j1x5yehc.jpg new file mode 100644 index 0000000..86c2a4b Binary files /dev/null and b/train/image/train/gum/candy_04_j1x5yehc.jpg differ diff --git a/train/image/train/gum/candy_04_kl56vvqy.jpg b/train/image/train/gum/candy_04_kl56vvqy.jpg new file mode 100644 index 0000000..9739be1 Binary files /dev/null and b/train/image/train/gum/candy_04_kl56vvqy.jpg differ diff --git a/train/image/train/gum/candy_04_nlyh5c6g.jpg b/train/image/train/gum/candy_04_nlyh5c6g.jpg new file mode 100644 index 0000000..e20872e Binary files /dev/null and b/train/image/train/gum/candy_04_nlyh5c6g.jpg differ diff --git a/train/image/train/gum/candy_04_ph9ud0tm.jpg b/train/image/train/gum/candy_04_ph9ud0tm.jpg new file mode 100644 index 0000000..a65aa68 Binary files /dev/null and b/train/image/train/gum/candy_04_ph9ud0tm.jpg differ diff --git a/train/image/train/gum/candy_04_s3wssd5k.jpg b/train/image/train/gum/candy_04_s3wssd5k.jpg new file mode 100644 index 0000000..e88d30c Binary files /dev/null and b/train/image/train/gum/candy_04_s3wssd5k.jpg differ diff --git a/train/image/train/gum/candy_04_v9zvilkc.jpg b/train/image/train/gum/candy_04_v9zvilkc.jpg new file mode 100644 index 0000000..dc50141 Binary files /dev/null and b/train/image/train/gum/candy_04_v9zvilkc.jpg differ diff --git a/train/image/train/liquorice/candy_00_0l0ocxjd.jpg b/train/image/train/liquorice/candy_00_0l0ocxjd.jpg new file mode 100644 index 0000000..5ce087a Binary files /dev/null and b/train/image/train/liquorice/candy_00_0l0ocxjd.jpg differ diff --git a/train/image/train/liquorice/candy_00_0ryb528j.jpg b/train/image/train/liquorice/candy_00_0ryb528j.jpg new file mode 100644 index 0000000..fb56fde Binary files /dev/null and b/train/image/train/liquorice/candy_00_0ryb528j.jpg differ diff --git a/train/image/train/liquorice/candy_00_246lhlhf.jpg b/train/image/train/liquorice/candy_00_246lhlhf.jpg new file mode 100644 index 0000000..24cfcc3 Binary files /dev/null and b/train/image/train/liquorice/candy_00_246lhlhf.jpg differ diff --git a/train/image/train/liquorice/candy_00_287dq028.jpg b/train/image/train/liquorice/candy_00_287dq028.jpg new file mode 100644 index 0000000..78eabdb Binary files /dev/null and b/train/image/train/liquorice/candy_00_287dq028.jpg differ diff --git a/train/image/train/liquorice/candy_00_2a27erdn.jpg b/train/image/train/liquorice/candy_00_2a27erdn.jpg new file mode 100644 index 0000000..effffd6 Binary files /dev/null and b/train/image/train/liquorice/candy_00_2a27erdn.jpg differ diff --git a/train/image/train/liquorice/candy_00_4p3rvuh3.jpg b/train/image/train/liquorice/candy_00_4p3rvuh3.jpg new file mode 100644 index 0000000..f4cb53c Binary files /dev/null and b/train/image/train/liquorice/candy_00_4p3rvuh3.jpg differ diff --git a/train/image/train/liquorice/candy_00_62p40m69.jpg b/train/image/train/liquorice/candy_00_62p40m69.jpg new file mode 100644 index 0000000..bcf03fe Binary files /dev/null and b/train/image/train/liquorice/candy_00_62p40m69.jpg differ diff --git a/train/image/train/liquorice/candy_00_6gmf1jfo.jpg b/train/image/train/liquorice/candy_00_6gmf1jfo.jpg new file mode 100644 index 0000000..1727b34 Binary files /dev/null and b/train/image/train/liquorice/candy_00_6gmf1jfo.jpg differ diff --git a/train/image/train/liquorice/candy_00_8plkmzjj.jpg b/train/image/train/liquorice/candy_00_8plkmzjj.jpg new file mode 100644 index 0000000..615a2ea Binary files /dev/null and b/train/image/train/liquorice/candy_00_8plkmzjj.jpg differ diff --git a/train/image/train/liquorice/candy_00_9uw42uu6.jpg b/train/image/train/liquorice/candy_00_9uw42uu6.jpg new file mode 100644 index 0000000..5a00b2e Binary files /dev/null and b/train/image/train/liquorice/candy_00_9uw42uu6.jpg differ diff --git a/train/image/train/liquorice/candy_00_awdrfeso.jpg b/train/image/train/liquorice/candy_00_awdrfeso.jpg new file mode 100644 index 0000000..f896d39 Binary files /dev/null and b/train/image/train/liquorice/candy_00_awdrfeso.jpg differ diff --git a/train/image/train/liquorice/candy_00_cjoytbeb.jpg b/train/image/train/liquorice/candy_00_cjoytbeb.jpg new file mode 100644 index 0000000..c0d4d31 Binary files /dev/null and b/train/image/train/liquorice/candy_00_cjoytbeb.jpg differ diff --git a/train/image/train/liquorice/candy_00_eb57pmep.jpg b/train/image/train/liquorice/candy_00_eb57pmep.jpg new file mode 100644 index 0000000..8cfcbfd Binary files /dev/null and b/train/image/train/liquorice/candy_00_eb57pmep.jpg differ diff --git a/train/image/train/liquorice/candy_00_fzdju2xp.jpg b/train/image/train/liquorice/candy_00_fzdju2xp.jpg new file mode 100644 index 0000000..f737f1f Binary files /dev/null and b/train/image/train/liquorice/candy_00_fzdju2xp.jpg differ diff --git a/train/image/train/liquorice/candy_00_ghvwon66.jpg b/train/image/train/liquorice/candy_00_ghvwon66.jpg new file mode 100644 index 0000000..f19407b Binary files /dev/null and b/train/image/train/liquorice/candy_00_ghvwon66.jpg differ diff --git a/train/image/train/liquorice/candy_00_iu3dsd8l.jpg b/train/image/train/liquorice/candy_00_iu3dsd8l.jpg new file mode 100644 index 0000000..a652d27 Binary files /dev/null and b/train/image/train/liquorice/candy_00_iu3dsd8l.jpg differ diff --git a/train/image/train/liquorice/candy_00_j238onep.jpg b/train/image/train/liquorice/candy_00_j238onep.jpg new file mode 100644 index 0000000..086e267 Binary files /dev/null and b/train/image/train/liquorice/candy_00_j238onep.jpg differ diff --git a/train/image/train/liquorice/candy_00_l8iveg0m.jpg b/train/image/train/liquorice/candy_00_l8iveg0m.jpg new file mode 100644 index 0000000..1a2049a Binary files /dev/null and b/train/image/train/liquorice/candy_00_l8iveg0m.jpg differ diff --git a/train/image/train/liquorice/candy_00_mz5pgkdd.jpg b/train/image/train/liquorice/candy_00_mz5pgkdd.jpg new file mode 100644 index 0000000..9d9dfda Binary files /dev/null and b/train/image/train/liquorice/candy_00_mz5pgkdd.jpg differ diff --git a/train/image/train/liquorice/candy_00_ng1wrazv.jpg b/train/image/train/liquorice/candy_00_ng1wrazv.jpg new file mode 100644 index 0000000..e0a3147 Binary files /dev/null and b/train/image/train/liquorice/candy_00_ng1wrazv.jpg differ diff --git a/train/image/train/liquorice/candy_00_op8i9qei.jpg b/train/image/train/liquorice/candy_00_op8i9qei.jpg new file mode 100644 index 0000000..19ec496 Binary files /dev/null and b/train/image/train/liquorice/candy_00_op8i9qei.jpg differ diff --git a/train/image/train/liquorice/candy_00_ps1p9lkn.jpg b/train/image/train/liquorice/candy_00_ps1p9lkn.jpg new file mode 100644 index 0000000..863ed8b Binary files /dev/null and b/train/image/train/liquorice/candy_00_ps1p9lkn.jpg differ diff --git a/train/image/train/liquorice/candy_00_seak95hn.jpg b/train/image/train/liquorice/candy_00_seak95hn.jpg new file mode 100644 index 0000000..cd2d8d4 Binary files /dev/null and b/train/image/train/liquorice/candy_00_seak95hn.jpg differ diff --git a/train/image/train/liquorice/candy_00_sg5k61xy.jpg b/train/image/train/liquorice/candy_00_sg5k61xy.jpg new file mode 100644 index 0000000..71b489a Binary files /dev/null and b/train/image/train/liquorice/candy_00_sg5k61xy.jpg differ diff --git a/train/image/train/liquorice/candy_00_udbvur2q.jpg b/train/image/train/liquorice/candy_00_udbvur2q.jpg new file mode 100644 index 0000000..aa95684 Binary files /dev/null and b/train/image/train/liquorice/candy_00_udbvur2q.jpg differ diff --git a/train/image/train/liquorice/candy_00_vk2ewazz.jpg b/train/image/train/liquorice/candy_00_vk2ewazz.jpg new file mode 100644 index 0000000..1543e1f Binary files /dev/null and b/train/image/train/liquorice/candy_00_vk2ewazz.jpg differ diff --git a/train/image/train/liquorice/candy_00_xwd5slvt.jpg b/train/image/train/liquorice/candy_00_xwd5slvt.jpg new file mode 100644 index 0000000..f2ada25 Binary files /dev/null and b/train/image/train/liquorice/candy_00_xwd5slvt.jpg differ diff --git a/train/image/train/liquorice/candy_00_zuk32r8f.jpg b/train/image/train/liquorice/candy_00_zuk32r8f.jpg new file mode 100644 index 0000000..074b465 Binary files /dev/null and b/train/image/train/liquorice/candy_00_zuk32r8f.jpg differ diff --git a/train/image/train/liquorice/candy_01_0wgmmds3.jpg b/train/image/train/liquorice/candy_01_0wgmmds3.jpg new file mode 100644 index 0000000..8d53b0a Binary files /dev/null and b/train/image/train/liquorice/candy_01_0wgmmds3.jpg differ diff --git a/train/image/train/liquorice/candy_01_1hg51ac2.jpg b/train/image/train/liquorice/candy_01_1hg51ac2.jpg new file mode 100644 index 0000000..0a51e46 Binary files /dev/null and b/train/image/train/liquorice/candy_01_1hg51ac2.jpg differ diff --git a/train/image/train/liquorice/candy_01_3a35033j.jpg b/train/image/train/liquorice/candy_01_3a35033j.jpg new file mode 100644 index 0000000..2ce2971 Binary files /dev/null and b/train/image/train/liquorice/candy_01_3a35033j.jpg differ diff --git a/train/image/train/liquorice/candy_01_4mtxctm6.jpg b/train/image/train/liquorice/candy_01_4mtxctm6.jpg new file mode 100644 index 0000000..09f82a8 Binary files /dev/null and b/train/image/train/liquorice/candy_01_4mtxctm6.jpg differ diff --git a/train/image/train/liquorice/candy_01_56wnvg2n.jpg b/train/image/train/liquorice/candy_01_56wnvg2n.jpg new file mode 100644 index 0000000..77d6f18 Binary files /dev/null and b/train/image/train/liquorice/candy_01_56wnvg2n.jpg differ diff --git a/train/image/train/liquorice/candy_01_78wo5e0q.jpg b/train/image/train/liquorice/candy_01_78wo5e0q.jpg new file mode 100644 index 0000000..c361f55 Binary files /dev/null and b/train/image/train/liquorice/candy_01_78wo5e0q.jpg differ diff --git a/train/image/train/liquorice/candy_01_7gh5glin.jpg b/train/image/train/liquorice/candy_01_7gh5glin.jpg new file mode 100644 index 0000000..142502b Binary files /dev/null and b/train/image/train/liquorice/candy_01_7gh5glin.jpg differ diff --git a/train/image/train/liquorice/candy_01_920lz3x4.jpg b/train/image/train/liquorice/candy_01_920lz3x4.jpg new file mode 100644 index 0000000..4f75452 Binary files /dev/null and b/train/image/train/liquorice/candy_01_920lz3x4.jpg differ diff --git a/train/image/train/liquorice/candy_01_arwf981e.jpg b/train/image/train/liquorice/candy_01_arwf981e.jpg new file mode 100644 index 0000000..d0e96ea Binary files /dev/null and b/train/image/train/liquorice/candy_01_arwf981e.jpg differ diff --git a/train/image/train/liquorice/candy_01_bh0pzuqi.jpg b/train/image/train/liquorice/candy_01_bh0pzuqi.jpg new file mode 100644 index 0000000..4253c29 Binary files /dev/null and b/train/image/train/liquorice/candy_01_bh0pzuqi.jpg differ diff --git a/train/image/train/liquorice/candy_01_c4m6fykp.jpg b/train/image/train/liquorice/candy_01_c4m6fykp.jpg new file mode 100644 index 0000000..932cb53 Binary files /dev/null and b/train/image/train/liquorice/candy_01_c4m6fykp.jpg differ diff --git a/train/image/train/liquorice/candy_01_chgrwkxy.jpg b/train/image/train/liquorice/candy_01_chgrwkxy.jpg new file mode 100644 index 0000000..71c9c9b Binary files /dev/null and b/train/image/train/liquorice/candy_01_chgrwkxy.jpg differ diff --git a/train/image/train/liquorice/candy_01_d6rm9mo5.jpg b/train/image/train/liquorice/candy_01_d6rm9mo5.jpg new file mode 100644 index 0000000..57c9828 Binary files /dev/null and b/train/image/train/liquorice/candy_01_d6rm9mo5.jpg differ diff --git a/train/image/train/liquorice/candy_01_fovant62.jpg b/train/image/train/liquorice/candy_01_fovant62.jpg new file mode 100644 index 0000000..0cd5e2d Binary files /dev/null and b/train/image/train/liquorice/candy_01_fovant62.jpg differ diff --git a/train/image/train/liquorice/candy_01_g5wv86l1.jpg b/train/image/train/liquorice/candy_01_g5wv86l1.jpg new file mode 100644 index 0000000..7be38b9 Binary files /dev/null and b/train/image/train/liquorice/candy_01_g5wv86l1.jpg differ diff --git a/train/image/train/liquorice/candy_01_gamz8wks.jpg b/train/image/train/liquorice/candy_01_gamz8wks.jpg new file mode 100644 index 0000000..f12fd06 Binary files /dev/null and b/train/image/train/liquorice/candy_01_gamz8wks.jpg differ diff --git a/train/image/train/liquorice/candy_01_gsbdg04e.jpg b/train/image/train/liquorice/candy_01_gsbdg04e.jpg new file mode 100644 index 0000000..60b7c47 Binary files /dev/null and b/train/image/train/liquorice/candy_01_gsbdg04e.jpg differ diff --git a/train/image/train/liquorice/candy_01_gv5kojqb.jpg b/train/image/train/liquorice/candy_01_gv5kojqb.jpg new file mode 100644 index 0000000..cf10976 Binary files /dev/null and b/train/image/train/liquorice/candy_01_gv5kojqb.jpg differ diff --git a/train/image/train/liquorice/candy_01_hxfwen5h.jpg b/train/image/train/liquorice/candy_01_hxfwen5h.jpg new file mode 100644 index 0000000..0414fc6 Binary files /dev/null and b/train/image/train/liquorice/candy_01_hxfwen5h.jpg differ diff --git a/train/image/train/liquorice/candy_01_kasl0x8g.jpg b/train/image/train/liquorice/candy_01_kasl0x8g.jpg new file mode 100644 index 0000000..e0f35e0 Binary files /dev/null and b/train/image/train/liquorice/candy_01_kasl0x8g.jpg differ diff --git a/train/image/train/liquorice/candy_01_lt0lqnid.jpg b/train/image/train/liquorice/candy_01_lt0lqnid.jpg new file mode 100644 index 0000000..db63d65 Binary files /dev/null and b/train/image/train/liquorice/candy_01_lt0lqnid.jpg differ diff --git a/train/image/train/liquorice/candy_01_mpzauwdl.jpg b/train/image/train/liquorice/candy_01_mpzauwdl.jpg new file mode 100644 index 0000000..2f5cbd8 Binary files /dev/null and b/train/image/train/liquorice/candy_01_mpzauwdl.jpg differ diff --git a/train/image/train/liquorice/candy_01_nbw1a1tp.jpg b/train/image/train/liquorice/candy_01_nbw1a1tp.jpg new file mode 100644 index 0000000..000f823 Binary files /dev/null and b/train/image/train/liquorice/candy_01_nbw1a1tp.jpg differ diff --git a/train/image/train/liquorice/candy_01_nsjx0ija.jpg b/train/image/train/liquorice/candy_01_nsjx0ija.jpg new file mode 100644 index 0000000..9b5cd3d Binary files /dev/null and b/train/image/train/liquorice/candy_01_nsjx0ija.jpg differ diff --git a/train/image/train/liquorice/candy_01_nun57lb8.jpg b/train/image/train/liquorice/candy_01_nun57lb8.jpg new file mode 100644 index 0000000..d81a64c Binary files /dev/null and b/train/image/train/liquorice/candy_01_nun57lb8.jpg differ diff --git a/train/image/train/liquorice/candy_01_rqyvthez.jpg b/train/image/train/liquorice/candy_01_rqyvthez.jpg new file mode 100644 index 0000000..a3261b1 Binary files /dev/null and b/train/image/train/liquorice/candy_01_rqyvthez.jpg differ diff --git a/train/image/train/liquorice/candy_01_rsvj2c7t.jpg b/train/image/train/liquorice/candy_01_rsvj2c7t.jpg new file mode 100644 index 0000000..b93bd1a Binary files /dev/null and b/train/image/train/liquorice/candy_01_rsvj2c7t.jpg differ diff --git a/train/image/train/liquorice/candy_01_syn4eg1y.jpg b/train/image/train/liquorice/candy_01_syn4eg1y.jpg new file mode 100644 index 0000000..6cbf2f8 Binary files /dev/null and b/train/image/train/liquorice/candy_01_syn4eg1y.jpg differ diff --git a/train/image/train/liquorice/candy_01_u2c22ln2.jpg b/train/image/train/liquorice/candy_01_u2c22ln2.jpg new file mode 100644 index 0000000..54f9ea2 Binary files /dev/null and b/train/image/train/liquorice/candy_01_u2c22ln2.jpg differ diff --git a/train/image/train/liquorice/candy_01_w4tc9vss.jpg b/train/image/train/liquorice/candy_01_w4tc9vss.jpg new file mode 100644 index 0000000..cdc28a5 Binary files /dev/null and b/train/image/train/liquorice/candy_01_w4tc9vss.jpg differ diff --git a/train/image/train/liquorice/candy_01_xmkifq05.jpg b/train/image/train/liquorice/candy_01_xmkifq05.jpg new file mode 100644 index 0000000..08663da Binary files /dev/null and b/train/image/train/liquorice/candy_01_xmkifq05.jpg differ diff --git a/train/image/train/liquorice/candy_01_zyrdzwrn.jpg b/train/image/train/liquorice/candy_01_zyrdzwrn.jpg new file mode 100644 index 0000000..85bb423 Binary files /dev/null and b/train/image/train/liquorice/candy_01_zyrdzwrn.jpg differ diff --git a/train/image/train/liquorice/candy_02_2i0g8s6t.jpg b/train/image/train/liquorice/candy_02_2i0g8s6t.jpg new file mode 100644 index 0000000..dce9f60 Binary files /dev/null and b/train/image/train/liquorice/candy_02_2i0g8s6t.jpg differ diff --git a/train/image/train/liquorice/candy_02_3rs9ebol.jpg b/train/image/train/liquorice/candy_02_3rs9ebol.jpg new file mode 100644 index 0000000..79199e6 Binary files /dev/null and b/train/image/train/liquorice/candy_02_3rs9ebol.jpg differ diff --git a/train/image/train/liquorice/candy_02_7uglglly.jpg b/train/image/train/liquorice/candy_02_7uglglly.jpg new file mode 100644 index 0000000..b0e89b3 Binary files /dev/null and b/train/image/train/liquorice/candy_02_7uglglly.jpg differ diff --git a/train/image/train/liquorice/candy_02_9evz13kd.jpg b/train/image/train/liquorice/candy_02_9evz13kd.jpg new file mode 100644 index 0000000..e1e36b0 Binary files /dev/null and b/train/image/train/liquorice/candy_02_9evz13kd.jpg differ diff --git a/train/image/train/liquorice/candy_02_9gyp9gtd.jpg b/train/image/train/liquorice/candy_02_9gyp9gtd.jpg new file mode 100644 index 0000000..7237be6 Binary files /dev/null and b/train/image/train/liquorice/candy_02_9gyp9gtd.jpg differ diff --git a/train/image/train/liquorice/candy_02_9o7m3qly.jpg b/train/image/train/liquorice/candy_02_9o7m3qly.jpg new file mode 100644 index 0000000..97b8713 Binary files /dev/null and b/train/image/train/liquorice/candy_02_9o7m3qly.jpg differ diff --git a/train/image/train/liquorice/candy_02_cr2ziooo.jpg b/train/image/train/liquorice/candy_02_cr2ziooo.jpg new file mode 100644 index 0000000..480e857 Binary files /dev/null and b/train/image/train/liquorice/candy_02_cr2ziooo.jpg differ diff --git a/train/image/train/liquorice/candy_02_eqvxeki6.jpg b/train/image/train/liquorice/candy_02_eqvxeki6.jpg new file mode 100644 index 0000000..71c9e54 Binary files /dev/null and b/train/image/train/liquorice/candy_02_eqvxeki6.jpg differ diff --git a/train/image/train/liquorice/candy_02_ez18h40k.jpg b/train/image/train/liquorice/candy_02_ez18h40k.jpg new file mode 100644 index 0000000..300eb76 Binary files /dev/null and b/train/image/train/liquorice/candy_02_ez18h40k.jpg differ diff --git a/train/image/train/liquorice/candy_02_hptvs0cj.jpg b/train/image/train/liquorice/candy_02_hptvs0cj.jpg new file mode 100644 index 0000000..160b476 Binary files /dev/null and b/train/image/train/liquorice/candy_02_hptvs0cj.jpg differ diff --git a/train/image/train/liquorice/candy_02_jv5kh0pl.jpg b/train/image/train/liquorice/candy_02_jv5kh0pl.jpg new file mode 100644 index 0000000..4a23713 Binary files /dev/null and b/train/image/train/liquorice/candy_02_jv5kh0pl.jpg differ diff --git a/train/image/train/liquorice/candy_02_k2td6z2n.jpg b/train/image/train/liquorice/candy_02_k2td6z2n.jpg new file mode 100644 index 0000000..bc7b86f Binary files /dev/null and b/train/image/train/liquorice/candy_02_k2td6z2n.jpg differ diff --git a/train/image/train/liquorice/candy_02_n8l7uc5k.jpg b/train/image/train/liquorice/candy_02_n8l7uc5k.jpg new file mode 100644 index 0000000..08344ff Binary files /dev/null and b/train/image/train/liquorice/candy_02_n8l7uc5k.jpg differ diff --git a/train/image/train/liquorice/candy_02_p3auif65.jpg b/train/image/train/liquorice/candy_02_p3auif65.jpg new file mode 100644 index 0000000..399e420 Binary files /dev/null and b/train/image/train/liquorice/candy_02_p3auif65.jpg differ diff --git a/train/image/train/liquorice/candy_02_p7q9acky.jpg b/train/image/train/liquorice/candy_02_p7q9acky.jpg new file mode 100644 index 0000000..bf35873 Binary files /dev/null and b/train/image/train/liquorice/candy_02_p7q9acky.jpg differ diff --git a/train/image/train/liquorice/candy_02_qblnakct.jpg b/train/image/train/liquorice/candy_02_qblnakct.jpg new file mode 100644 index 0000000..df4b09f Binary files /dev/null and b/train/image/train/liquorice/candy_02_qblnakct.jpg differ diff --git a/train/image/train/liquorice/candy_02_ry8abd8r.jpg b/train/image/train/liquorice/candy_02_ry8abd8r.jpg new file mode 100644 index 0000000..60024eb Binary files /dev/null and b/train/image/train/liquorice/candy_02_ry8abd8r.jpg differ diff --git a/train/image/train/liquorice/candy_02_sp0cucwu.jpg b/train/image/train/liquorice/candy_02_sp0cucwu.jpg new file mode 100644 index 0000000..6f1a1b8 Binary files /dev/null and b/train/image/train/liquorice/candy_02_sp0cucwu.jpg differ diff --git a/train/image/train/liquorice/candy_02_trwarsoe.jpg b/train/image/train/liquorice/candy_02_trwarsoe.jpg new file mode 100644 index 0000000..a56f7a3 Binary files /dev/null and b/train/image/train/liquorice/candy_02_trwarsoe.jpg differ diff --git a/train/image/train/liquorice/candy_02_uq32b50h.jpg b/train/image/train/liquorice/candy_02_uq32b50h.jpg new file mode 100644 index 0000000..d340197 Binary files /dev/null and b/train/image/train/liquorice/candy_02_uq32b50h.jpg differ diff --git a/train/image/train/liquorice/candy_02_uvo1mysu.jpg b/train/image/train/liquorice/candy_02_uvo1mysu.jpg new file mode 100644 index 0000000..7dc1a26 Binary files /dev/null and b/train/image/train/liquorice/candy_02_uvo1mysu.jpg differ diff --git a/train/image/train/liquorice/candy_02_v9pcicaj.jpg b/train/image/train/liquorice/candy_02_v9pcicaj.jpg new file mode 100644 index 0000000..8b1045d Binary files /dev/null and b/train/image/train/liquorice/candy_02_v9pcicaj.jpg differ diff --git a/train/image/train/liquorice/candy_02_vp4n3fl8.jpg b/train/image/train/liquorice/candy_02_vp4n3fl8.jpg new file mode 100644 index 0000000..410cb2a Binary files /dev/null and b/train/image/train/liquorice/candy_02_vp4n3fl8.jpg differ diff --git a/train/image/train/liquorice/candy_02_xpsiyglz.jpg b/train/image/train/liquorice/candy_02_xpsiyglz.jpg new file mode 100644 index 0000000..8730916 Binary files /dev/null and b/train/image/train/liquorice/candy_02_xpsiyglz.jpg differ diff --git a/train/image/train/liquorice/candy_02_yx53i6l8.jpg b/train/image/train/liquorice/candy_02_yx53i6l8.jpg new file mode 100644 index 0000000..4a3c41e Binary files /dev/null and b/train/image/train/liquorice/candy_02_yx53i6l8.jpg differ diff --git a/train/image/train/liquorice/candy_03_0ek3wl38.jpg b/train/image/train/liquorice/candy_03_0ek3wl38.jpg new file mode 100644 index 0000000..72b1f6c Binary files /dev/null and b/train/image/train/liquorice/candy_03_0ek3wl38.jpg differ diff --git a/train/image/train/liquorice/candy_03_42eb4syu.jpg b/train/image/train/liquorice/candy_03_42eb4syu.jpg new file mode 100644 index 0000000..d3406dc Binary files /dev/null and b/train/image/train/liquorice/candy_03_42eb4syu.jpg differ diff --git a/train/image/train/liquorice/candy_03_4krs31xi.jpg b/train/image/train/liquorice/candy_03_4krs31xi.jpg new file mode 100644 index 0000000..0b157fc Binary files /dev/null and b/train/image/train/liquorice/candy_03_4krs31xi.jpg differ diff --git a/train/image/train/liquorice/candy_03_5oq9luqe.jpg b/train/image/train/liquorice/candy_03_5oq9luqe.jpg new file mode 100644 index 0000000..d374d38 Binary files /dev/null and b/train/image/train/liquorice/candy_03_5oq9luqe.jpg differ diff --git a/train/image/train/liquorice/candy_03_7wtmdwwx.jpg b/train/image/train/liquorice/candy_03_7wtmdwwx.jpg new file mode 100644 index 0000000..1b5a98e Binary files /dev/null and b/train/image/train/liquorice/candy_03_7wtmdwwx.jpg differ diff --git a/train/image/train/liquorice/candy_03_9rgr3rrw.jpg b/train/image/train/liquorice/candy_03_9rgr3rrw.jpg new file mode 100644 index 0000000..d8fc3f0 Binary files /dev/null and b/train/image/train/liquorice/candy_03_9rgr3rrw.jpg differ diff --git a/train/image/train/smurf/candy_00_xvxibuhl.jpg b/train/image/train/smurf/candy_00_xvxibuhl.jpg new file mode 100644 index 0000000..61337a2 Binary files /dev/null and b/train/image/train/smurf/candy_00_xvxibuhl.jpg differ diff --git a/train/image/train/smurf/candy_00_zz0sx5jc.jpg b/train/image/train/smurf/candy_00_zz0sx5jc.jpg new file mode 100644 index 0000000..0d0e529 Binary files /dev/null and b/train/image/train/smurf/candy_00_zz0sx5jc.jpg differ diff --git a/train/image/train/smurf/candy_01_19xemsfg.jpg b/train/image/train/smurf/candy_01_19xemsfg.jpg new file mode 100644 index 0000000..6ba1b11 Binary files /dev/null and b/train/image/train/smurf/candy_01_19xemsfg.jpg differ diff --git a/train/image/train/smurf/candy_01_2bxwexb5.jpg b/train/image/train/smurf/candy_01_2bxwexb5.jpg new file mode 100644 index 0000000..afb1b6e Binary files /dev/null and b/train/image/train/smurf/candy_01_2bxwexb5.jpg differ diff --git a/train/image/train/smurf/candy_01_4pypf2av.jpg b/train/image/train/smurf/candy_01_4pypf2av.jpg new file mode 100644 index 0000000..22b2b00 Binary files /dev/null and b/train/image/train/smurf/candy_01_4pypf2av.jpg differ diff --git a/train/image/train/smurf/candy_01_7cyaoh5r.jpg b/train/image/train/smurf/candy_01_7cyaoh5r.jpg new file mode 100644 index 0000000..bf44a97 Binary files /dev/null and b/train/image/train/smurf/candy_01_7cyaoh5r.jpg differ diff --git a/train/image/train/smurf/candy_01_8pprgd7e.jpg b/train/image/train/smurf/candy_01_8pprgd7e.jpg new file mode 100644 index 0000000..b2e1820 Binary files /dev/null and b/train/image/train/smurf/candy_01_8pprgd7e.jpg differ diff --git a/train/image/train/smurf/candy_01_bnukr4a1.jpg b/train/image/train/smurf/candy_01_bnukr4a1.jpg new file mode 100644 index 0000000..6eb62a5 Binary files /dev/null and b/train/image/train/smurf/candy_01_bnukr4a1.jpg differ diff --git a/train/image/train/smurf/candy_01_dkw1v18v.jpg b/train/image/train/smurf/candy_01_dkw1v18v.jpg new file mode 100644 index 0000000..d7980b5 Binary files /dev/null and b/train/image/train/smurf/candy_01_dkw1v18v.jpg differ diff --git a/train/image/train/smurf/candy_01_ebb2lwhc.jpg b/train/image/train/smurf/candy_01_ebb2lwhc.jpg new file mode 100644 index 0000000..6f62603 Binary files /dev/null and b/train/image/train/smurf/candy_01_ebb2lwhc.jpg differ diff --git a/train/image/train/smurf/candy_01_h827fgtr.jpg b/train/image/train/smurf/candy_01_h827fgtr.jpg new file mode 100644 index 0000000..5c4570e Binary files /dev/null and b/train/image/train/smurf/candy_01_h827fgtr.jpg differ diff --git a/train/image/train/smurf/candy_01_kset420f.jpg b/train/image/train/smurf/candy_01_kset420f.jpg new file mode 100644 index 0000000..633a79e Binary files /dev/null and b/train/image/train/smurf/candy_01_kset420f.jpg differ diff --git a/train/image/train/smurf/candy_01_q5uvpvew.jpg b/train/image/train/smurf/candy_01_q5uvpvew.jpg new file mode 100644 index 0000000..aed9df7 Binary files /dev/null and b/train/image/train/smurf/candy_01_q5uvpvew.jpg differ diff --git a/train/image/train/smurf/candy_01_tdknf3jz.jpg b/train/image/train/smurf/candy_01_tdknf3jz.jpg new file mode 100644 index 0000000..a09a52f Binary files /dev/null and b/train/image/train/smurf/candy_01_tdknf3jz.jpg differ diff --git a/train/image/train/smurf/candy_01_to4w6oe8.jpg b/train/image/train/smurf/candy_01_to4w6oe8.jpg new file mode 100644 index 0000000..4cc80b4 Binary files /dev/null and b/train/image/train/smurf/candy_01_to4w6oe8.jpg differ diff --git a/train/image/train/smurf/candy_01_uuy982rm.jpg b/train/image/train/smurf/candy_01_uuy982rm.jpg new file mode 100644 index 0000000..c45751b Binary files /dev/null and b/train/image/train/smurf/candy_01_uuy982rm.jpg differ diff --git a/train/image/train/smurf/candy_01_v83p6449.jpg b/train/image/train/smurf/candy_01_v83p6449.jpg new file mode 100644 index 0000000..dd3730f Binary files /dev/null and b/train/image/train/smurf/candy_01_v83p6449.jpg differ diff --git a/train/image/train/smurf/candy_01_vn9osm8s.jpg b/train/image/train/smurf/candy_01_vn9osm8s.jpg new file mode 100644 index 0000000..5cd6c9f Binary files /dev/null and b/train/image/train/smurf/candy_01_vn9osm8s.jpg differ diff --git a/train/image/train/smurf/candy_01_xvou97vp.jpg b/train/image/train/smurf/candy_01_xvou97vp.jpg new file mode 100644 index 0000000..b7c9ac5 Binary files /dev/null and b/train/image/train/smurf/candy_01_xvou97vp.jpg differ diff --git a/train/image/train/smurf/candy_01_xw0vnu2f.jpg b/train/image/train/smurf/candy_01_xw0vnu2f.jpg new file mode 100644 index 0000000..ab1fb91 Binary files /dev/null and b/train/image/train/smurf/candy_01_xw0vnu2f.jpg differ diff --git a/train/image/train/smurf/candy_01_yfrwdrjq.jpg b/train/image/train/smurf/candy_01_yfrwdrjq.jpg new file mode 100644 index 0000000..52d76fd Binary files /dev/null and b/train/image/train/smurf/candy_01_yfrwdrjq.jpg differ diff --git a/train/image/train/smurf/candy_01_zz1c6muw.jpg b/train/image/train/smurf/candy_01_zz1c6muw.jpg new file mode 100644 index 0000000..8f868be Binary files /dev/null and b/train/image/train/smurf/candy_01_zz1c6muw.jpg differ diff --git a/train/image/train/smurf/candy_02_41du25qb.jpg b/train/image/train/smurf/candy_02_41du25qb.jpg new file mode 100644 index 0000000..fc9d624 Binary files /dev/null and b/train/image/train/smurf/candy_02_41du25qb.jpg differ diff --git a/train/image/train/smurf/candy_02_47xahc8i.jpg b/train/image/train/smurf/candy_02_47xahc8i.jpg new file mode 100644 index 0000000..f096888 Binary files /dev/null and b/train/image/train/smurf/candy_02_47xahc8i.jpg differ diff --git a/train/image/train/smurf/candy_02_4gitwzwm.jpg b/train/image/train/smurf/candy_02_4gitwzwm.jpg new file mode 100644 index 0000000..72d872e Binary files /dev/null and b/train/image/train/smurf/candy_02_4gitwzwm.jpg differ diff --git a/train/image/train/smurf/candy_02_c4ssxtd7.jpg b/train/image/train/smurf/candy_02_c4ssxtd7.jpg new file mode 100644 index 0000000..01e9a59 Binary files /dev/null and b/train/image/train/smurf/candy_02_c4ssxtd7.jpg differ diff --git a/train/image/train/smurf/candy_02_e9ji4nh8.jpg b/train/image/train/smurf/candy_02_e9ji4nh8.jpg new file mode 100644 index 0000000..054c304 Binary files /dev/null and b/train/image/train/smurf/candy_02_e9ji4nh8.jpg differ diff --git a/train/image/train/smurf/candy_02_i6x2btat.jpg b/train/image/train/smurf/candy_02_i6x2btat.jpg new file mode 100644 index 0000000..cc19113 Binary files /dev/null and b/train/image/train/smurf/candy_02_i6x2btat.jpg differ diff --git a/train/image/train/smurf/candy_02_ieyz3ajq.jpg b/train/image/train/smurf/candy_02_ieyz3ajq.jpg new file mode 100644 index 0000000..59b395f Binary files /dev/null and b/train/image/train/smurf/candy_02_ieyz3ajq.jpg differ diff --git a/train/image/train/smurf/candy_02_jivdcs9l.jpg b/train/image/train/smurf/candy_02_jivdcs9l.jpg new file mode 100644 index 0000000..3a3b99e Binary files /dev/null and b/train/image/train/smurf/candy_02_jivdcs9l.jpg differ diff --git a/train/image/train/smurf/candy_02_kjop47o8.jpg b/train/image/train/smurf/candy_02_kjop47o8.jpg new file mode 100644 index 0000000..f7a2fff Binary files /dev/null and b/train/image/train/smurf/candy_02_kjop47o8.jpg differ diff --git a/train/image/train/smurf/candy_02_lqz2en6q.jpg b/train/image/train/smurf/candy_02_lqz2en6q.jpg new file mode 100644 index 0000000..26c332e Binary files /dev/null and b/train/image/train/smurf/candy_02_lqz2en6q.jpg differ diff --git a/train/image/train/smurf/candy_02_ojph1m0f.jpg b/train/image/train/smurf/candy_02_ojph1m0f.jpg new file mode 100644 index 0000000..2630051 Binary files /dev/null and b/train/image/train/smurf/candy_02_ojph1m0f.jpg differ diff --git a/train/image/train/smurf/candy_02_qadpj7g1.jpg b/train/image/train/smurf/candy_02_qadpj7g1.jpg new file mode 100644 index 0000000..832f694 Binary files /dev/null and b/train/image/train/smurf/candy_02_qadpj7g1.jpg differ diff --git a/train/image/train/smurf/candy_02_qeditk4c.jpg b/train/image/train/smurf/candy_02_qeditk4c.jpg new file mode 100644 index 0000000..ad43941 Binary files /dev/null and b/train/image/train/smurf/candy_02_qeditk4c.jpg differ diff --git a/train/image/train/smurf/candy_02_siw6md62.jpg b/train/image/train/smurf/candy_02_siw6md62.jpg new file mode 100644 index 0000000..86adf4d Binary files /dev/null and b/train/image/train/smurf/candy_02_siw6md62.jpg differ diff --git a/train/image/train/smurf/candy_03_05313dis.jpg b/train/image/train/smurf/candy_03_05313dis.jpg new file mode 100644 index 0000000..96e1068 Binary files /dev/null and b/train/image/train/smurf/candy_03_05313dis.jpg differ diff --git a/train/image/train/smurf/candy_03_2689ktl5.jpg b/train/image/train/smurf/candy_03_2689ktl5.jpg new file mode 100644 index 0000000..63d738b Binary files /dev/null and b/train/image/train/smurf/candy_03_2689ktl5.jpg differ diff --git a/train/image/train/smurf/candy_03_43pkaw40.jpg b/train/image/train/smurf/candy_03_43pkaw40.jpg new file mode 100644 index 0000000..d5029a8 Binary files /dev/null and b/train/image/train/smurf/candy_03_43pkaw40.jpg differ diff --git a/train/image/train/smurf/candy_03_4g9h3nuw.jpg b/train/image/train/smurf/candy_03_4g9h3nuw.jpg new file mode 100644 index 0000000..80e4d9c Binary files /dev/null and b/train/image/train/smurf/candy_03_4g9h3nuw.jpg differ diff --git a/train/image/train/smurf/candy_03_9d62bvqb.jpg b/train/image/train/smurf/candy_03_9d62bvqb.jpg new file mode 100644 index 0000000..8fafd33 Binary files /dev/null and b/train/image/train/smurf/candy_03_9d62bvqb.jpg differ diff --git a/train/image/train/smurf/candy_03_9ktgdwl3.jpg b/train/image/train/smurf/candy_03_9ktgdwl3.jpg new file mode 100644 index 0000000..95391e2 Binary files /dev/null and b/train/image/train/smurf/candy_03_9ktgdwl3.jpg differ diff --git a/train/image/train/smurf/candy_03_b5fx2iz6.jpg b/train/image/train/smurf/candy_03_b5fx2iz6.jpg new file mode 100644 index 0000000..baf2281 Binary files /dev/null and b/train/image/train/smurf/candy_03_b5fx2iz6.jpg differ diff --git a/train/image/train/smurf/candy_03_b738vszp.jpg b/train/image/train/smurf/candy_03_b738vszp.jpg new file mode 100644 index 0000000..6debe4e Binary files /dev/null and b/train/image/train/smurf/candy_03_b738vszp.jpg differ diff --git a/train/image/train/smurf/candy_03_brnmrpix.jpg b/train/image/train/smurf/candy_03_brnmrpix.jpg new file mode 100644 index 0000000..ee28bb5 Binary files /dev/null and b/train/image/train/smurf/candy_03_brnmrpix.jpg differ diff --git a/train/image/train/smurf/candy_03_d5ds76uj.jpg b/train/image/train/smurf/candy_03_d5ds76uj.jpg new file mode 100644 index 0000000..2dc34b4 Binary files /dev/null and b/train/image/train/smurf/candy_03_d5ds76uj.jpg differ diff --git a/train/image/train/smurf/candy_03_gveq4bqx.jpg b/train/image/train/smurf/candy_03_gveq4bqx.jpg new file mode 100644 index 0000000..228ef8d Binary files /dev/null and b/train/image/train/smurf/candy_03_gveq4bqx.jpg differ diff --git a/train/image/train/smurf/candy_03_hqppkzrc.jpg b/train/image/train/smurf/candy_03_hqppkzrc.jpg new file mode 100644 index 0000000..7ec6160 Binary files /dev/null and b/train/image/train/smurf/candy_03_hqppkzrc.jpg differ diff --git a/train/image/train/smurf/candy_03_hss5xybs.jpg b/train/image/train/smurf/candy_03_hss5xybs.jpg new file mode 100644 index 0000000..e7e0009 Binary files /dev/null and b/train/image/train/smurf/candy_03_hss5xybs.jpg differ diff --git a/train/image/train/smurf/candy_03_itikzych.jpg b/train/image/train/smurf/candy_03_itikzych.jpg new file mode 100644 index 0000000..ecba3de Binary files /dev/null and b/train/image/train/smurf/candy_03_itikzych.jpg differ diff --git a/train/image/train/smurf/candy_03_l73zakor.jpg b/train/image/train/smurf/candy_03_l73zakor.jpg new file mode 100644 index 0000000..27318bf Binary files /dev/null and b/train/image/train/smurf/candy_03_l73zakor.jpg differ diff --git a/train/image/train/smurf/candy_03_m8m9jo5h.jpg b/train/image/train/smurf/candy_03_m8m9jo5h.jpg new file mode 100644 index 0000000..e6dc4cb Binary files /dev/null and b/train/image/train/smurf/candy_03_m8m9jo5h.jpg differ diff --git a/train/image/train/smurf/candy_03_m8olbiga.jpg b/train/image/train/smurf/candy_03_m8olbiga.jpg new file mode 100644 index 0000000..760f4d1 Binary files /dev/null and b/train/image/train/smurf/candy_03_m8olbiga.jpg differ diff --git a/train/image/train/smurf/candy_03_nxfi5xf9.jpg b/train/image/train/smurf/candy_03_nxfi5xf9.jpg new file mode 100644 index 0000000..57ea597 Binary files /dev/null and b/train/image/train/smurf/candy_03_nxfi5xf9.jpg differ diff --git a/train/image/train/smurf/candy_03_olsyzn7e.jpg b/train/image/train/smurf/candy_03_olsyzn7e.jpg new file mode 100644 index 0000000..002512a Binary files /dev/null and b/train/image/train/smurf/candy_03_olsyzn7e.jpg differ diff --git a/train/image/train/smurf/candy_03_ovvmnyey.jpg b/train/image/train/smurf/candy_03_ovvmnyey.jpg new file mode 100644 index 0000000..86398fc Binary files /dev/null and b/train/image/train/smurf/candy_03_ovvmnyey.jpg differ diff --git a/train/image/train/smurf/candy_03_p9ethx97.jpg b/train/image/train/smurf/candy_03_p9ethx97.jpg new file mode 100644 index 0000000..fc7e3b4 Binary files /dev/null and b/train/image/train/smurf/candy_03_p9ethx97.jpg differ diff --git a/train/image/train/smurf/candy_03_tqsjh5qm.jpg b/train/image/train/smurf/candy_03_tqsjh5qm.jpg new file mode 100644 index 0000000..b6a87d4 Binary files /dev/null and b/train/image/train/smurf/candy_03_tqsjh5qm.jpg differ diff --git a/train/image/train/smurf/candy_03_vq1228m5.jpg b/train/image/train/smurf/candy_03_vq1228m5.jpg new file mode 100644 index 0000000..864857b Binary files /dev/null and b/train/image/train/smurf/candy_03_vq1228m5.jpg differ diff --git a/train/image/train/smurf/candy_03_zn1pjtj1.jpg b/train/image/train/smurf/candy_03_zn1pjtj1.jpg new file mode 100644 index 0000000..2f41c45 Binary files /dev/null and b/train/image/train/smurf/candy_03_zn1pjtj1.jpg differ diff --git a/train/image/train/smurf/candy_04_30byfylw.jpg b/train/image/train/smurf/candy_04_30byfylw.jpg new file mode 100644 index 0000000..b332c7d Binary files /dev/null and b/train/image/train/smurf/candy_04_30byfylw.jpg differ diff --git a/train/image/train/smurf/candy_04_3xqkdbr2.jpg b/train/image/train/smurf/candy_04_3xqkdbr2.jpg new file mode 100644 index 0000000..03426b3 Binary files /dev/null and b/train/image/train/smurf/candy_04_3xqkdbr2.jpg differ diff --git a/train/image/train/smurf/candy_04_7grdhowr.jpg b/train/image/train/smurf/candy_04_7grdhowr.jpg new file mode 100644 index 0000000..879079c Binary files /dev/null and b/train/image/train/smurf/candy_04_7grdhowr.jpg differ diff --git a/train/image/train/smurf/candy_04_7mh421u0.jpg b/train/image/train/smurf/candy_04_7mh421u0.jpg new file mode 100644 index 0000000..466acac Binary files /dev/null and b/train/image/train/smurf/candy_04_7mh421u0.jpg differ diff --git a/train/image/train/smurf/candy_04_bd8l9trf.jpg b/train/image/train/smurf/candy_04_bd8l9trf.jpg new file mode 100644 index 0000000..03adcee Binary files /dev/null and b/train/image/train/smurf/candy_04_bd8l9trf.jpg differ diff --git a/train/image/train/smurf/candy_04_c8arz29r.jpg b/train/image/train/smurf/candy_04_c8arz29r.jpg new file mode 100644 index 0000000..d38bbb0 Binary files /dev/null and b/train/image/train/smurf/candy_04_c8arz29r.jpg differ diff --git a/train/image/train/smurf/candy_04_dibxhsse.jpg b/train/image/train/smurf/candy_04_dibxhsse.jpg new file mode 100644 index 0000000..9d7c37e Binary files /dev/null and b/train/image/train/smurf/candy_04_dibxhsse.jpg differ diff --git a/train/image/train/smurf/candy_04_ehrnd3kw.jpg b/train/image/train/smurf/candy_04_ehrnd3kw.jpg new file mode 100644 index 0000000..54fffef Binary files /dev/null and b/train/image/train/smurf/candy_04_ehrnd3kw.jpg differ diff --git a/train/image/train/smurf/candy_04_hmncqtug.jpg b/train/image/train/smurf/candy_04_hmncqtug.jpg new file mode 100644 index 0000000..9e54265 Binary files /dev/null and b/train/image/train/smurf/candy_04_hmncqtug.jpg differ diff --git a/train/image/train/smurf/candy_04_nejxqd5i.jpg b/train/image/train/smurf/candy_04_nejxqd5i.jpg new file mode 100644 index 0000000..dbceb57 Binary files /dev/null and b/train/image/train/smurf/candy_04_nejxqd5i.jpg differ diff --git a/train/image/train/smurf/candy_04_nin1z5og.jpg b/train/image/train/smurf/candy_04_nin1z5og.jpg new file mode 100644 index 0000000..f32c1ef Binary files /dev/null and b/train/image/train/smurf/candy_04_nin1z5og.jpg differ diff --git a/train/image/train/smurf/candy_04_q7u3ra3g.jpg b/train/image/train/smurf/candy_04_q7u3ra3g.jpg new file mode 100644 index 0000000..d16d6fc Binary files /dev/null and b/train/image/train/smurf/candy_04_q7u3ra3g.jpg differ diff --git a/train/image/train/smurf/candy_04_q7zshpv5.jpg b/train/image/train/smurf/candy_04_q7zshpv5.jpg new file mode 100644 index 0000000..f734cb4 Binary files /dev/null and b/train/image/train/smurf/candy_04_q7zshpv5.jpg differ diff --git a/train/image/train/smurf/candy_04_rww9oqhd.jpg b/train/image/train/smurf/candy_04_rww9oqhd.jpg new file mode 100644 index 0000000..0614170 Binary files /dev/null and b/train/image/train/smurf/candy_04_rww9oqhd.jpg differ diff --git a/train/image/train/smurf/candy_04_uo3h9cbm.jpg b/train/image/train/smurf/candy_04_uo3h9cbm.jpg new file mode 100644 index 0000000..b1d12fe Binary files /dev/null and b/train/image/train/smurf/candy_04_uo3h9cbm.jpg differ diff --git a/train/image/train/smurf/candy_04_uzemkd00.jpg b/train/image/train/smurf/candy_04_uzemkd00.jpg new file mode 100644 index 0000000..4e08ef2 Binary files /dev/null and b/train/image/train/smurf/candy_04_uzemkd00.jpg differ diff --git a/train/image/train/smurf/candy_04_wihv73yk.jpg b/train/image/train/smurf/candy_04_wihv73yk.jpg new file mode 100644 index 0000000..ba049a6 Binary files /dev/null and b/train/image/train/smurf/candy_04_wihv73yk.jpg differ diff --git a/train/image/train/smurf/candy_04_xrffxl21.jpg b/train/image/train/smurf/candy_04_xrffxl21.jpg new file mode 100644 index 0000000..b47537e Binary files /dev/null and b/train/image/train/smurf/candy_04_xrffxl21.jpg differ diff --git a/train/image/train/smurf/candy_04_zxkf1u8o.jpg b/train/image/train/smurf/candy_04_zxkf1u8o.jpg new file mode 100644 index 0000000..90fcd21 Binary files /dev/null and b/train/image/train/smurf/candy_04_zxkf1u8o.jpg differ diff --git a/train/image/train/smurf/candy_05_9mj53hbl.jpg b/train/image/train/smurf/candy_05_9mj53hbl.jpg new file mode 100644 index 0000000..2748271 Binary files /dev/null and b/train/image/train/smurf/candy_05_9mj53hbl.jpg differ diff --git a/train/image/train/smurf/candy_05_b8b11ht4.jpg b/train/image/train/smurf/candy_05_b8b11ht4.jpg new file mode 100644 index 0000000..f1adc26 Binary files /dev/null and b/train/image/train/smurf/candy_05_b8b11ht4.jpg differ diff --git a/train/image/train/smurf/candy_05_bhfd10vv.jpg b/train/image/train/smurf/candy_05_bhfd10vv.jpg new file mode 100644 index 0000000..ca9f5b7 Binary files /dev/null and b/train/image/train/smurf/candy_05_bhfd10vv.jpg differ diff --git a/train/image/train/smurf/candy_05_h04rqlk0.jpg b/train/image/train/smurf/candy_05_h04rqlk0.jpg new file mode 100644 index 0000000..91de642 Binary files /dev/null and b/train/image/train/smurf/candy_05_h04rqlk0.jpg differ diff --git a/train/image/train/smurf/candy_05_jkkykreb.jpg b/train/image/train/smurf/candy_05_jkkykreb.jpg new file mode 100644 index 0000000..f9d187e Binary files /dev/null and b/train/image/train/smurf/candy_05_jkkykreb.jpg differ diff --git a/train/image/train/smurf/candy_05_k6lyhvwv.jpg b/train/image/train/smurf/candy_05_k6lyhvwv.jpg new file mode 100644 index 0000000..f581b1c Binary files /dev/null and b/train/image/train/smurf/candy_05_k6lyhvwv.jpg differ diff --git a/train/image/train/smurf/candy_05_mqf199pw.jpg b/train/image/train/smurf/candy_05_mqf199pw.jpg new file mode 100644 index 0000000..e33daec Binary files /dev/null and b/train/image/train/smurf/candy_05_mqf199pw.jpg differ diff --git a/train/image/train/smurf/candy_05_pwl6rfio.jpg b/train/image/train/smurf/candy_05_pwl6rfio.jpg new file mode 100644 index 0000000..f8c64d8 Binary files /dev/null and b/train/image/train/smurf/candy_05_pwl6rfio.jpg differ diff --git a/train/image/train/smurf/candy_05_s1qg891l.jpg b/train/image/train/smurf/candy_05_s1qg891l.jpg new file mode 100644 index 0000000..7180ca4 Binary files /dev/null and b/train/image/train/smurf/candy_05_s1qg891l.jpg differ diff --git a/train/image/train/smurf/candy_08_h2llc1dl.jpg b/train/image/train/smurf/candy_08_h2llc1dl.jpg new file mode 100644 index 0000000..c30d537 Binary files /dev/null and b/train/image/train/smurf/candy_08_h2llc1dl.jpg differ diff --git a/train/image/train/smurf/candy_08_o7ycupaz.jpg b/train/image/train/smurf/candy_08_o7ycupaz.jpg new file mode 100644 index 0000000..90cb3c6 Binary files /dev/null and b/train/image/train/smurf/candy_08_o7ycupaz.jpg differ diff --git a/train/image/train/smurf/candy_09_hqfumq1c.jpg b/train/image/train/smurf/candy_09_hqfumq1c.jpg new file mode 100644 index 0000000..1e807ea Binary files /dev/null and b/train/image/train/smurf/candy_09_hqfumq1c.jpg differ diff --git a/train/images_zip/box_candy.zip b/train/images_zip/box_candy.zip new file mode 100644 index 0000000..c7fe02d Binary files /dev/null and b/train/images_zip/box_candy.zip differ diff --git a/train/images_zip/twist.zip b/train/images_zip/twist.zip new file mode 100644 index 0000000..57ced27 Binary files /dev/null and b/train/images_zip/twist.zip differ diff --git a/train/ml-cob-command.txt b/train/ml-cob-command.txt new file mode 100644 index 0000000..8e4e5b3 --- /dev/null +++ b/train/ml-cob-command.txt @@ -0,0 +1,19 @@ +gcloud ml-engine jobs submit training {job_name} ^ +--module-name=trainer.train ^ +--scale-tier=basic ^ +--packages=gs://cx_robotas/current/package/trainer-0.0.0.tar.gz ^ +--python-version=3.5 ^ +--region=us-central1 ^ +--runtime-version=1.8 ^ +--project=cx-robotas ^ +-- ^ +--data_dir=gs://cx_robotas/current/gamma_augmented/output ^ +--test_dir=gs://cx_robotas/current/gamma_augmented/output ^ +--log_dir=gs://cx_robotas/{job_name}/logs ^ +--train_dir=gs://cx_robotas/{job_name}/train ^ +--plot_dir=gs://cx_robotas/{job_name}/plots ^ +--active_test_mode ^ +--epochs=400 ^ +--keep_prob=0.3 ^ +--hidden_size=7 ^ +--batch_size=16 \ No newline at end of file diff --git a/train/trainer/README.md b/train/trainer/README.md new file mode 100644 index 0000000..0db73c0 --- /dev/null +++ b/train/trainer/README.md @@ -0,0 +1,50 @@ +### Feature extraction +There are two feature extractors in this folder, feature_extractor and paralell_extractor. The feature +extractor is used by the application in learn mode when training a model using the interface. This way of training only supports four +categories and is not efficient. The paralell extractor does the same as the featue extractor, only it does so by spawning multiple +processes. This allows it to extract features from a greater datasat in a much smaller amout of time, especially if run on +a more powerfull machine, such as a powerfull GCP Compute Engine instance. + +#### Note +While most of the functionality of the paralell extractor, such as applying rotation to the images, are exposed through variables, +the application of several rounds of random gamma adjustments are hard-coded and must be changed though modification of the code. +The relevant code can be found on line 269. +``` +for gamma_min, gamma_max in [(0.9, 1), (0.5, 0.9), (1, 1.3)]: +``` +Where the three tuples define (min, max) values for the random gamma correction, and the numer of tuples defines how many different +gamma adjusted features will be created per input file. + + +### Training +The training script can be packaged and uploaded to GCP Cloud engine. To start a job manually, use the following command, replacing the required values. On unix, use \ instead of ^. Please the the Google Cloud ML documentation. +``` +gcloud ml-engine jobs submit training {job_name} ^ +--module-name=trainer.train ^ +--scale-tier=basic ^ +--packages=gs://path/to/trainer-0.0.0.tar.gz ^ +--python-version=3.5 ^ +--region=us-central1 ^ +--runtime-version=1.8 ^ +--project={project-name} ^ +-- ^ +--data_dir=gs://path/to/output ^ +--test_dir=gs://path/to/output ^ +--log_dir=gs://path/to/{job_name}/logs ^ +--train_dir=gs://path/to/{job_name}/train ^ +--plot_dir=gs://path/to/{job_name}/plots ^ +--active_test_mode ^ +--epochs={value} ^ +--keep_prob={value} ^ +--hidden_size={value} ^ +--batch_size={value} +``` + +The training script can also be run locally, in which case only the arguments following -- are applicable + + +### Plotter +The training script, if set to test mode, will produce a file called plot_data.sjon, which contains the required data to plot accuracy and loss using training, both overall and per label. This file can be plotted using the plotter.py. It is used by passing it the path to the plot file. +``` +> python plotter.py /path/to/plot_data.json +``` diff --git a/train/trainer/feature_extractor.py b/train/trainer/feature_extractor.py index 44709ac..ed640a1 100644 --- a/train/trainer/feature_extractor.py +++ b/train/trainer/feature_extractor.py @@ -22,7 +22,7 @@ import sys import tensorflow as tf -import cv2 +#import cv2 INPUT_DATA_TENSOR_NAME = 'DecodeJpeg:0' FEATURE_TENSOR_NAME = 'pool_3/_reshape:0' @@ -132,11 +132,15 @@ def __init__(self, path_generator, feature_extractor): def write_features(self, features_data_path): with tf.gfile.FastGFile(features_data_path, 'w') as f: + + progressCounter = 0 + for path, label_id in self.path_generator: - sys.stdout.write("\rprocessing image: {}".format(path)) + sys.stdout.write("\rprocessing image: {}".format(str(path+" "+ str(progressCounter)))) sys.stdout.flush() line = self.extract_data_for_path(path, label_id) f.write(json.dumps(line) + '\n') + progressCounter += 1 def extract_data_for_path(self, image_path, label_id): vector = self.extractor.get_feature_vectors_from_files([image_path]) @@ -156,31 +160,47 @@ def write_labels(labels, labels_data_path): def main(): parser = argparse.ArgumentParser(description='Run Dobot WebAPI.') - parser.add_argument('output_dir', nargs=1, type=str) - parser.add_argument('--image_dir', type=str) - parser.add_argument('--model_file', type=str, default=None) + parser.add_argument('--output_dir', default ='output',nargs=1, type=str) + parser.add_argument('--image_dir_train', default='../image/train', type=str) + parser.add_argument('--active_test_mode', default=False, help='Set True for testing') + parser.add_argument('--image_dir_test', default='../image/test', type=str) + parser.add_argument('--model_file', type=str, default='classify_image_graph_def.pb') parser.add_argument('--for_prediction', action='store_true') args = parser.parse_args() - output_dir = args.output_dir[0] + output_dir = args.output_dir labels_file = os.path.join(output_dir, 'labels.json') - features_file = os.path.join(output_dir, 'features.json') + features_file_train = os.path.join(output_dir, 'features.json') + features_file_test = os.path.join(output_dir, 'testfeatures.json') + + model_file = args.model_file if args.for_prediction: - path_gen = ImagePathGeneratorForPrediction(args.image_dir) + path_gen_train = ImagePathGeneratorForPrediction(args.image_dir_train) + if (args.active_test_mode): + path_gen_test = ImagePathGeneratorForPrediction(args.image_dir_test) else: - path_gen = ImagePathGeneratorForTraining(args.image_dir) + path_gen_train = ImagePathGeneratorForTraining(args.image_dir_train) + if args.active_test_mode : + path_gen_test = ImagePathGeneratorForTraining(args.image_dir_test) + logger.info("writing label file: {}".format(labels_file)) - write_labels(path_gen.get_labels(), labels_file) + write_labels(path_gen_train.get_labels(), labels_file) extractor = FeatureExtractor(model_file) - writer = FeaturesDataWriter(path_gen, extractor) + writer_train = FeaturesDataWriter(path_gen_train, extractor) + - logger.info("writing features file: {}".format(features_file)) - writer.write_features(features_file) + logger.info("writing train features file: {}".format(features_file_train)) + writer_train.write_features(features_file_train) + if (args.active_test_mode): + features_file_test = os.path.join(output_dir, 'testfeatures.json') + writer_test = FeaturesDataWriter(path_gen_test, extractor) + logger.info("writing test features file: {}".format(features_file_test)) + writer_test.write_features(features_file_test) if __name__ == "__main__": main() diff --git a/train/trainer/log/.gitkeep b/train/trainer/log/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/train/trainer/model.py b/train/trainer/model.py index a3b3699..338de51 100644 --- a/train/trainer/model.py +++ b/train/trainer/model.py @@ -85,7 +85,7 @@ def __init__(self, features_size, num_classes, for_predict=False, hidden_size=3) # add loss operation if initializing for training one_hot = tf.one_hot(self.label_ids, num_classes, name='target') self.loss_op = tf.reduce_mean( - tf.nn.softmax_cross_entropy_with_logits(logits, one_hot) + tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=one_hot) ) self.softmax_op = tf.nn.softmax(logits) @@ -98,8 +98,8 @@ def __init__(self, features_size, num_classes, for_predict=False, hidden_size=3) self.global_step = tf.Variable(0, name='global_step', trainable=False) # Summaries with tf.variable_scope('summaries'): - tf.scalar_summary('in sample loss', self.loss_op) - self.summary_op = tf.merge_all_summaries() + tf.summary.scalar('in sample loss', self.loss_op) + self.summary_op = tf.summary.merge_all() @classmethod def from_model_params(cls, model_params): diff --git a/train/trainer/output/.gitkeep b/train/trainer/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/train/trainer/parallel_extractor.py b/train/trainer/parallel_extractor.py new file mode 100644 index 0000000..2d399a4 --- /dev/null +++ b/train/trainer/parallel_extractor.py @@ -0,0 +1,370 @@ +# Copyright 2017 BrainPad Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from __future__ import absolute_import, division, print_function, unicode_literals + +import argparse +import os +import logging +import json +import sys +import tensorflow as tf +import threading +import multiprocessing +import time +import random +import math +import numpy as np +from itertools import islice + + +INPUT_DATA_TENSOR_NAME = 'DecodeJpeg:0' +FEATURE_TENSOR_NAME = 'pool_3/_reshape:0' + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s %(name)-7s %(levelname)-7s %(message)s' +) +logger = logging.getLogger(__name__) + +# Mute logging of warnings to avoid spam with deprecated use of .op_scope +tf.logging.set_verbosity(tf.logging.ERROR) + + +class FeatureExtractor(object): + """ + FeatureExtractor extracts 2048-dimension feature vectors from image files + using inception-v3. + """ + + def __init__(self, model_file): + # load inception-v3 model + self.graph = tf.Graph() + with tf.gfile.FastGFile(model_file, 'rb') as f: + graph_def = tf.GraphDef() + graph_def.ParseFromString(f.read()) + with self.graph.as_default(): + _ = tf.import_graph_def(graph_def, name='') + + feature = tf.reshape(self.graph.get_tensor_by_name(FEATURE_TENSOR_NAME), [-1]) + self.feature_op = feature + + self.saver = None + + @classmethod + def from_model_dir(cls, model_dir): + return cls(os.path.join(model_dir, 'classify_image_graph_def.pb')) + + def get_feature_vector(self, img_bgr): + with tf.Session(graph=self.graph) as sess: + feature_data = sess.run( + self.feature_op, + {INPUT_DATA_TENSOR_NAME: img_bgr} + ) + return feature_data.reshape(-1, feature_data.shape[0]) + + def get_feature_vectors_from_files(self, image_paths, turn=0, gamma_min=1, gamma_max=1): + + # Decode image + with self.graph.as_default(): + image = tf.image.decode_jpeg(tf.read_file(image_paths[0])) + + # Extract features + features = [] + with tf.Session(graph=self.graph) as sess: + if turn > 0: + rotate = tf.image.rot90(image, k=turn) + image = sess.run(rotate) + image = tf.convert_to_tensor(image) + + + if gamma_min != 1 or gamma_max != 1: + image = tf.image.convert_image_dtype(image, tf.float32) + gamma = tf.image.adjust_gamma(image, gamma=random.uniform(gamma_min, gamma_min)) + image = sess.run(gamma) + + image = tf.convert_to_tensor(image) + + + + for path in image_paths: + image_data = sess.run( + image + ) + feature_data = sess.run( + self.feature_op, + {INPUT_DATA_TENSOR_NAME: image_data} + ) + features.append(feature_data) + return features + +class ImagePathGeneratorForTraining(object): + def __init__(self, image_dir, extension='jpg'): + self.image_dir = image_dir + self.extension = extension + + def get_labels(self): + return sorted( + [sub_dir for sub_dir in tf.gfile.ListDirectory(self.image_dir) + if tf.gfile.IsDirectory('/'.join((self.image_dir, sub_dir))) + ] + ) + + def __iter__(self): + for label_id, label in enumerate(self.get_labels()): + dir_path = os.path.join(self.image_dir, label) + paths = tf.gfile.Glob('{}/*.{}'.format(dir_path, self.extension)) + for path in paths: + yield path, label_id + raise StopIteration() + + +class ImagePathGeneratorForPrediction(object): + def __init__(self, image_dir, extension='jpg'): + self.image_dir = image_dir + self.extension = extension + + def __iter__(self): + paths = tf.gfile.Glob('{}/*.{}'.format(self.image_dir, self.extension)) + for path in paths: + yield path, None + raise StopIteration() + + +def write_labels(labels, labels_data_path): + with tf.gfile.FastGFile(labels_data_path, 'w') as f: + f.write(json.dumps(labels)) + + +def main(): + args = _parse_arguments() + + labels_file = os.path.join(args.output_dir, 'labels.json') + features_file_train = os.path.join(args.output_dir, 'features.json') + features_file_test = os.path.join(args.output_dir, 'testfeatures.json') + + _ensure_dir_exists(args.output_dir) + + path_gen_train, path_gen_test = _get_path_generators(args) + + logger.info("Writing label file: {}".format(labels_file)) + write_labels(path_gen_train.get_labels(), labels_file) + + _run_multiprocess_extraction(args, path_gen_train, "train-output-", features_file_train) + + if args.active_test_mode: + _run_multiprocess_extraction(args, path_gen_test, "test-output-", features_file_test) + + logger.info("Process completed successfully") + + +def _ensure_dir_exists(directory): + if not tf.gfile.Exists(directory): + tf.gfile.MakeDirs(directory) + + +def _get_path_generators(args): + path_gen_train, path_gen_test = None, None + + if args.for_prediction: + path_gen_train = ImagePathGeneratorForPrediction(args.image_dir_train) + if args.active_test_mode: + path_gen_test = ImagePathGeneratorForPrediction(args.image_dir_test) + + else: + path_gen_train = ImagePathGeneratorForTraining(args.image_dir_train) + if args.active_test_mode: + path_gen_test = ImagePathGeneratorForTraining(args.image_dir_test) + + return path_gen_train, path_gen_test + + +def _run_multiprocess_extraction(args, path_generator, temp_files_prefix, output_feature_file): + num_of_files = len(list(path_generator)) + + progress_queue = multiprocessing.Queue() + progress_printer = threading.Thread(target=ProgressPrinter, args=(progress_queue, num_of_files)) + progress_printer.start() + + image_chunks = generate_image_chunks(path_generator, args.processes) + + test_processes = [multiprocessing.Process(target=ExtractionProcess, + args=(args, image_chunks[i], temp_files_prefix, i, progress_queue)) + for i in range(args.processes)] + + _start_and_join_processes(test_processes) + + progress_queue.put(("CLOSE", 0)) + progress_printer.join() + + _merge_files(output_feature_file, args.output_dir, temp_files_prefix) + + +def _merge_files(output_file, output_dir, prefix): + logger.info("Merging files prefixed with {} into file {}".format(prefix, output_file)) + with tf.gfile.FastGFile(output_file, 'w') as f: + for _, _, filenames in os.walk(output_dir): + for filename in filenames: + if filename.startswith(prefix): + path = os.path.join(output_dir, filename) + with tf.gfile.FastGFile(path, 'r') as g: + f.write(g.read()) + os.remove(path) + + +def _start_and_join_processes(processes): + logger.info("Starting {} processes and waiting for completion".format(len(processes))) + for t in processes: + t.start() + + for t in processes: + t.join() + + +# ------------------------------------------------- +# +# +# PROCESS +# +# +# ------------------------------------------------- +def generate_image_chunks(generator, split_into): + all_files = list(generator) + number_of_files = len(all_files) + chunk_size = math.ceil(number_of_files / float(split_into)) + + chunks = [] + for start_index in range(0, number_of_files, chunk_size): + chunks.append(all_files[start_index: start_index + chunk_size]) + + return chunks + + +class FeaturesDataWriterProcess(object): + """ + FeatureDataWriter extracts feature data from images and write to json lines + """ + + def __init__(self, image_list, feature_extractor, progress_queue): + self.image_list = image_list + self.extractor = feature_extractor + self.progress_queue = progress_queue + + def write_features(self, features_data_path, rotations): + with tf.gfile.FastGFile(features_data_path, 'w') as f: + for index, (path, label_id) in enumerate(self.image_list): + for i in range(rotations + 1): + for gamma_min, gamma_max in [(0.9, 1), (0.5, 0.9), (1, 1.3)]: + line = self.extract_data_for_path(path, label_id, i, gamma_min, gamma_max) + f.write(json.dumps(line) + '\n') + + if (index + 1) % 20 == 0: + self.progress_queue.put(("PROGRESS", 20)) + + def extract_data_for_path(self, image_path, label_id, turn, gamma_min, gamma_max): + vector = self.extractor.get_feature_vectors_from_files([image_path], turn, gamma_min, gamma_max) + line = { + 'image_uri': image_path, + 'feature_vector': vector[0].tolist() + } + if label_id is not None: + line['label_id'] = label_id + return line + + +def ExtractionProcess(args, image_list, output_prefix, index, progress_queue): + print("Extraction process " + str(index) + " started") + + extractor = FeatureExtractor(args.model_file) + writer_train = FeaturesDataWriterProcess(image_list, extractor, progress_queue) + + output_file = os.path.join(args.output_dir, output_prefix + str(index) + '.json') + + writer_train.write_features(output_file, args.rotations) + + +def ProgressPrinter(progress_queue, total): + start_time = time.time() + completed = 0 + + while True: + message_type, data = progress_queue.get() + if message_type == "CLOSE": + return + elif message_type == "PROGRESS": + completed += data + + time_per_image = (time.time() - start_time) / completed + time_left = (time_per_image * (total - completed)) / 60 + percent_complete = (completed / total) * 100 + sys.stdout.write("\rProcessing image {}/{} (Not counting rotations) - {:.1f}% " + "- Estimated time left: {:.0f} minutes" + .format(completed, total, percent_complete, math.ceil(time_left))) + sys.stdout.flush() + + +def _parse_arguments(): + parser = argparse.ArgumentParser(description='Run feature extraction') + parser.add_argument( + '--output_dir', + default='output', + nargs=1, + type=str + ) + parser.add_argument( + '--image_dir_train', + default='../image/train', + type=str + ) + parser.add_argument( + '--active_test_mode', + default=False, + action='store_true', + help='Use this flag to enable generating test features' + ) + parser.add_argument( + '--image_dir_test', + default='../image/test', + type=str + ) + parser.add_argument( + '--model_file', + type=str, + default='classify_image_graph_def.pb' + ) + parser.add_argument( + '--for_prediction', + action='store_true' + ) + parser.add_argument( + '--processes', + default=1, + type=int, + help="The number of processes to use for the extraction. More processes might help speeding up the " + "extraction process. Rule of thumb might be to use approx one process per 4 logical cores" + ) + parser.add_argument( + '--rotations', + default=0, + type=int, + help="Number of 90deg rotations of the training image to use. 0 will only use the image as is, " + "while 1 will use the image as is as well as rotating the image 1 time, doubling the number" + "of extracted features") + + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/train/trainer/plotter.py b/train/trainer/plotter.py new file mode 100644 index 0000000..c9693aa --- /dev/null +++ b/train/trainer/plotter.py @@ -0,0 +1,38 @@ +import numpy as np +import tensorflow as tf +import matplotlib.pyplot as plt +import argparse +import json + +def plot_accuracies(losses, accuracy_train, accuracy_test, accuracy_per_label, labels): + ax = plt.subplot(111) + ax.plot(losses, color='r', label="loss") + ax.plot(accuracy_test, color='b', label="test accuracy") + ax.plot(accuracy_train, color='y', label="train accuracy") + + ax.legend() + plt.show() + ax = plt.subplot(111) + for i in range(len(accuracy_per_label)): + ax.plot(accuracy_per_label[i], label=labels[i]) + ax.legend() + plt.show() + +def main(): + parser = argparse.ArgumentParser(description='Display plot outputs from training') + parser.add_argument('plotdata', + metavar='P', + type=str, + nargs='?', + default='./plots/plot_data.json', + help="The path to the plot data") + args = parser.parse_args() + + with tf.gfile.FastGFile(args.plotdata, 'r') as f: + plot_data = json.loads(f.read()) + + plot_accuracies(**plot_data) + + +if __name__ == "__main__": + main() diff --git a/train/trainer/predict.py b/train/trainer/predict.py index 16b5f44..63d91af 100644 --- a/train/trainer/predict.py +++ b/train/trainer/predict.py @@ -19,11 +19,12 @@ import json import logging +import numpy import numpy as np import tensorflow as tf -import trainer.model as model -from trainer.utils import FeaturesDataReader +import model as model +from utils import FeaturesDataReader logger = logging.getLogger(__name__) @@ -60,20 +61,23 @@ def predict(self): return label_ids, prob def predict_to_json(self): + print(self.result_to_json(*self.predict())) return self.result_to_json(*self.predict()) + def result_to_json(self, label_ids, prob): result = [] image_urls = self.data_reader.read_feature_metadata('image_uri') - for i in xrange(len(label_ids)): - lid = label_ids[i] + for i in range(len(label_ids)): + lid = int(label_ids[i]) result.append({ "url": image_urls[i], "top_lid": lid, "top_label": self.model_params.labels[lid], - "probs": prob[i].tolist() + "probs": list(np.float64(prob[i])) }) - return json.dumps(result) + print(list(result)) + return json.dumps(list(result)) def main(_): @@ -84,13 +88,12 @@ def main(_): logger.info('tf version: {}'.format(tf.__version__)) parser = argparse.ArgumentParser(description='Run Dobot WebAPI.') - parser.add_argument('--data_dir', type=str, default='data', help="Directory for training data.") + parser.add_argument('--data_dir', type=str, default='output', help="Directory for training data.") parser.add_argument('--train_dir', type=str, default='train', help="Directory for checkpoints.") args = parser.parse_args() - reader = FeaturesDataReader(args.data_dir) - + reader = FeaturesDataReader(args.data_dir, 'trainfeatures.json') predictor = Predictor(reader, args.train_dir, args.train_dir+'/params.json') print(predictor.predict_to_json()) diff --git a/train/trainer/requirements.txt b/train/trainer/requirements.txt new file mode 100644 index 0000000..6771dce --- /dev/null +++ b/train/trainer/requirements.txt @@ -0,0 +1,2 @@ +tensorflow==1.8.0 +numpy==1.14.2 \ No newline at end of file diff --git a/train/trainer/train.py b/train/trainer/train.py index 795e5fe..822eb3b 100644 --- a/train/trainer/train.py +++ b/train/trainer/train.py @@ -21,9 +21,8 @@ import os import random +import numpy as np import tensorflow as tf -import time - import trainer.model as model from trainer.utils import TrainingFeaturesDataReader @@ -44,6 +43,8 @@ def from_reader(cls, reader): data = reader.read_features() uris = reader.read_feature_metadata('image_uri') labels = reader.read_labels() + # print(data[0], data[1], uris, labels) + return cls(data[0], data[1], uris, labels) def n_samples(self): @@ -85,11 +86,13 @@ def to_json(self): class Trainer(object): - def __init__(self, train_config, model_params, train_dir, log_dir): + def __init__(self, train_config, model_params, train_dir, log_dir, plot_dir, test_dir=None): self.train_config = train_config self.model_params = model_params + self.test_dir = test_dir self.train_dir = train_dir self.log_dir = log_dir + self.plot_dir = plot_dir self.model = model.TransferModel.from_model_params(self.model_params) self.train_op = self.model.train_op(self.train_config.optimizer) @@ -105,9 +108,9 @@ def __init__(self, train_config, model_params, train_dir, log_dir): def _epoch_log_path(self, num_epoch): return os.path.join(self.log_dir, 'epochs', '{}.json'.format(str(num_epoch).zfill(6))) - def train(self, dataset): - n_samples = dataset.n_samples() + def train(self, trainingset, testingset=None): + n_samples = trainingset.n_samples() logger.info('Build transfer network.') logger.info('Start training.') @@ -119,48 +122,117 @@ def train(self, dataset): loss_log = [] with tf.Session() as sess: - summary_writer = tf.train.SummaryWriter(self.log_dir, graph=sess.graph) + summary_writer = tf.summary.FileWriter(self.log_dir, graph=sess.graph) sess.run(tf.initialize_all_variables()) + losses = [] + accuracies_test = [] + accuracies_train = [] + label_accuracy = [] + + if (testingset): + for i in range(len(testingset.labels)): + label_accuracy.append([]) + for epoch in range(self.train_config.epochs): + + num_img_per_label = [0] * len(label_accuracy) + num_errors_per_label = [0] * len(label_accuracy) + # Shuffle data for batching shuffled_idx = list(range(n_samples)) random.shuffle(shuffled_idx) for begin_idx in range(0, n_samples, self.train_config.batch_size): batch_idx = shuffled_idx[begin_idx: begin_idx + self.train_config.batch_size] - sess.run(self.train_op, self.model.feed_for_training(*dataset.get(batch_idx))) + sess.run(self.train_op, self.model.feed_for_training(*trainingset.get(batch_idx))) # Print and write summaries. in_sample_loss, summary = sess.run( [self.model.loss_op, self.model.summary_op], - self.model.feed_for_training(*dataset.all()) + self.model.feed_for_training(*trainingset.all()) ) + loss_log.append(in_sample_loss) + losses.append(in_sample_loss) summary_writer.add_summary(summary, epoch) if epoch % 100 == 0 or epoch == self.train_config.epochs - 1: logger.info('{}th epoch end with loss {}.'.format(epoch, in_sample_loss)) - if self._needs_logging(loss_log): + # -------- Accuracy for training set: + + if trainingset: features = sess.run( [self.model.softmax_op], - self.model.feed_for_training(*dataset.all()) + self.model.feed_for_training(*trainingset.all()) # feed training data ) # write loss and predicted probabilities - probs = map(lambda a: a.tolist(), features[0]) + probs = list(map(lambda a: a.tolist(), features[0])) + averageAccuracy = 0 max_l = max(loss_log) loss_norm = [float(l) / max_l for l in loss_log] + with tf.gfile.FastGFile(self._epoch_log_path(epoch + 1000), 'w') as f: + data = { + 'epoch': epoch + 1000, + 'loss': loss_norm, + } + probs_with_uri = [] + correctCount = 0 + + for i, p in enumerate(probs): + meta = trainingset.get_meta(i) # metadata for training + predicted = np.argmax(p) + if predicted == int(meta['lid']): + correctCount += 1 + item = { + 'probs': p, + 'url': meta['url'], + 'property': { + 'label': meta['label'], + 'lid': int(meta['lid']) + } + } + probs_with_uri.append(item) + + averageAccuracy += correctCount / len(probs) + accuracies_train.append(averageAccuracy) + data['probs'] = probs_with_uri + f.write(json.dumps(data)) + + # -------- Accuracy for test set: + + if (testingset): + features = sess.run( + [self.model.softmax_op], + self.model.feed_for_training(*testingset.all()) # feed testing data ---------not training + ) + + # write loss and predicted probabilities Test + probs = list(map(lambda a: a.tolist(), features[0])) + averageAccuracy = 0 + max_l = max(loss_log) + loss_norm = [float(l) / max_l for l in loss_log] + with tf.gfile.FastGFile(self._epoch_log_path(epoch), 'w') as f: data = { 'epoch': epoch, 'loss': loss_norm, } probs_with_uri = [] + correctCount = 0 for i, p in enumerate(probs): - meta = dataset.get_meta(i) + meta = testingset.get_meta(i) # metadata for testing + num_img_per_label[(meta['lid'])] += 1 + predicted = np.argmax(p) + if predicted == int(meta['lid']): + correctCount += 1 + else: + num_errors_per_label[meta['lid']] += 1 + # print('error on ', meta['url'], ' with label ', meta['label'], 'thought it was ', testingset.labels[predicted]) + item = { 'probs': p, 'url': meta['url'], @@ -171,12 +243,31 @@ def train(self, dataset): } probs_with_uri.append(item) + averageAccuracy += correctCount / len(probs) + accuracies_test.append(averageAccuracy) data['probs'] = probs_with_uri f.write(json.dumps(data)) - # FIXME: sleep to show convergence slowly on UI - if epoch < 200 and loss_log[-1] > max(loss_log) * 0.01: - time.sleep(self._sleep_sec) + for i in range(len(label_accuracy)): + print('fails on', num_errors_per_label[i], 'out of ', num_img_per_label[i], 'images of ', + testingset.labels[i]) + + if (num_errors_per_label[i] != 0): + label_accuracy[i].append(100 - (num_errors_per_label[i] / num_img_per_label[i]) * 100) + print('gives accuracy ', 100 - (num_errors_per_label[i] / num_img_per_label[i]) * 100) + elif ((num_errors_per_label[i] == 0) and (num_img_per_label != 0)): + label_accuracy[i].append(100) + print('gives accuracy ', 100) + else: + label_accuracy[i].append(0) + print('gives accuracy ', 0) + + # FIXME: sleep to show convergence slowly on UI + # if epoch < 200 and loss_log[-1] > max(loss_log) * 0.01: + # time.sleep(self._sleep_sec) + + if testingset: + self.export_plot_data(losses, accuracies_train, accuracies_test, label_accuracy, testingset.labels) self.model.saver.save(sess, checkpoint_path, global_step=self.model.global_step) summary_writer.close() @@ -192,13 +283,39 @@ def _needs_logging(self, loss_log): self._last_logged_loss = loss return True - loss_change_rate = loss/self._last_logged_loss + loss_change_rate = loss / self._last_logged_loss if 1 - loss_change_rate > self._threshold: self._last_logged_loss = loss return True return False + def export_plot_data(self, losses, accuracy_train, accuracy_test, accuracy_per_label, labels): + with tf.gfile.FastGFile(os.path.join(self.plot_dir, "plot_data.json"), 'w') as f: + f.write(json.dumps({ + 'losses': losses, + 'accuracy_train': accuracy_train, + 'accuracy_test': accuracy_test, + 'accuracy_per_label': accuracy_per_label, + 'labels': labels + }, cls=NumpyEncoder)) + + +class NumpyEncoder(json.JSONEncoder): + """ Special json encoder for numpy types """ + + def default(self, obj): + if isinstance(obj, (np.int_, np.intc, np.intp, np.int8, + np.int16, np.int32, np.int64, np.uint8, + np.uint16, np.uint32, np.uint64)): + return int(obj) + elif isinstance(obj, (np.float_, np.float16, np.float32, + np.float64)): + return float(obj) + elif isinstance(obj, (np.ndarray,)): #### This is the fix + return obj.tolist() + return json.JSONEncoder.default(self, obj) + def main(_): logging.basicConfig( @@ -209,39 +326,41 @@ def main(_): parser = argparse.ArgumentParser(description='Run Dobot WebAPI.') parser.add_argument('--batch_size', type=int, default=16) - parser.add_argument('--hidden_size', type=int, default=3, help="Number of units in hidden layer.") - parser.add_argument('--epochs', type=int, default=2000, help="Number of epochs of training") + parser.add_argument('--hidden_size', type=int, default=7, help="Number of units in hidden layer.") + parser.add_argument('--epochs', type=int, default=50, help="Number of epochs of training") parser.add_argument('--learning_rate', type=float, default=1e-3) - parser.add_argument('--data_dir', type=str, default='data', help="Directory for training data.") + parser.add_argument('--keep_prob', type=float, default=1.0) + parser.add_argument('--active_test_mode', default=False, action='store_true', help='Set True for testing') + parser.add_argument('--data_dir', type=str, default='output', help="Directory for training data.") + parser.add_argument('--test_dir', type=str, default='output', help="Directory for test data.") parser.add_argument('--log_dir', type=str, default='log', help="Directory for TensorBoard logs.") parser.add_argument('--train_dir', type=str, default='train', help="Directory for checkpoints.") + parser.add_argument('--plot_dir', type=str, default='plots', help="Directory for accuracy plot data") args = parser.parse_args() data_dir = args.data_dir - reader = TrainingFeaturesDataReader(data_dir) + readerTrain = TrainingFeaturesDataReader(data_dir, features_file_name='features.json') + trainingset = DataSet.from_reader(readerTrain) + testingset = None - dataset = DataSet.from_reader(reader) + if (args.active_test_mode): + test_dir = args.test_dir + readerTest = TrainingFeaturesDataReader(test_dir, features_file_name='testfeatures.json') + testingset = DataSet.from_reader(readerTest) train_config = TrainingConfig( - epochs=2000, - batch_size=16, + epochs=args.epochs, + batch_size=args.batch_size, optimizer_class=tf.train.RMSPropOptimizer, - optimizer_args={"learning_rate": 1e-3}, - keep_prob=1.0, + optimizer_args={"learning_rate": args.learning_rate}, + keep_prob=args.keep_prob, ) params = model.ModelParams( - labels=dataset.labels, + labels=trainingset.labels, hidden_size=args.hidden_size, - features_size=dataset.feature_size() - ) - - trainer = Trainer( - train_config=train_config, - model_params=params, - train_dir=args.train_dir, - log_dir=args.log_dir, + features_size=trainingset.feature_size() ) if not tf.gfile.Exists(args.train_dir): @@ -250,13 +369,34 @@ def main(_): if not tf.gfile.Exists(args.log_dir): tf.gfile.MakeDirs(args.log_dir) + if not tf.gfile.Exists(args.plot_dir): + tf.gfile.MakeDirs(args.plot_dir) + with tf.gfile.FastGFile(os.path.join(args.train_dir, 'params.json'), 'w') as f: f.write(params.to_json()) with tf.gfile.FastGFile(os.path.join(args.log_dir, 'training.json'), 'w') as f: f.write(train_config.to_json()) - trainer.train(dataset) + if args.active_test_mode: + trainer = Trainer( + train_config=train_config, + model_params=params, + train_dir=args.train_dir, + log_dir=args.log_dir, + plot_dir=args.plot_dir, + test_dir=args.test_dir + ) + trainer.train(trainingset, testingset) + else: + trainer = Trainer( + train_config=train_config, + model_params=params, + train_dir=args.train_dir, + log_dir=args.log_dir, + plot_dir=args.plot_dir + ) + trainer.train(trainingset) if __name__ == '__main__': diff --git a/train/trainer/train/checkpoint b/train/trainer/train/checkpoint new file mode 100644 index 0000000..9aa3203 --- /dev/null +++ b/train/trainer/train/checkpoint @@ -0,0 +1,2 @@ +model_checkpoint_path: "model.ckpt-2300" +all_model_checkpoint_paths: "model.ckpt-2300" diff --git a/train/trainer/train/model.ckpt-2300.data-00000-of-00001 b/train/trainer/train/model.ckpt-2300.data-00000-of-00001 new file mode 100644 index 0000000..b58b897 Binary files /dev/null and b/train/trainer/train/model.ckpt-2300.data-00000-of-00001 differ diff --git a/train/trainer/train/model.ckpt-2300.index b/train/trainer/train/model.ckpt-2300.index new file mode 100644 index 0000000..e08f122 Binary files /dev/null and b/train/trainer/train/model.ckpt-2300.index differ diff --git a/train/trainer/train/model.ckpt-2300.meta b/train/trainer/train/model.ckpt-2300.meta new file mode 100644 index 0000000..c1e4b6f Binary files /dev/null and b/train/trainer/train/model.ckpt-2300.meta differ diff --git a/train/trainer/train/params.json b/train/trainer/train/params.json new file mode 100644 index 0000000..392393e --- /dev/null +++ b/train/trainer/train/params.json @@ -0,0 +1 @@ +{"labels": ["chocolate", "gum", "liquorice", "smurf"], "features_size": 2048, "hidden_size": 2} \ No newline at end of file diff --git a/train/trainer/utils.py b/train/trainer/utils.py index 2de62b1..2d701b6 100644 --- a/train/trainer/utils.py +++ b/train/trainer/utils.py @@ -23,13 +23,13 @@ class FeaturesDataReader(object): - def __init__(self, data_dir, labels_file_name='labels.json', features_file_name='features.json'): + def __init__(self, data_dir, features_file_name, labels_file_name='labels.json'): self.labels_file = os.path.join(data_dir, labels_file_name) self.features_file = os.path.join(data_dir, features_file_name) def read_features(self): with tf.gfile.FastGFile(self.features_file) as i_: - features = map(lambda l: json.loads(l)['feature_vector'], i_.readlines()) + features = list(map(lambda l: json.loads(l)['feature_vector'], i_.readlines())) return np.array(features, dtype='float32') def read_labels(self): @@ -39,14 +39,15 @@ def read_labels(self): def read_feature_metadata(self, key): with tf.gfile.FastGFile(self.features_file) as i_: - features = map(lambda l: json.loads(l)[key], i_.readlines()) + features = list(map(lambda l: json.loads(l)[key], i_.readlines())) return np.array(features) class TrainingFeaturesDataReader(FeaturesDataReader): def read_features(self): + with tf.gfile.FastGFile(self.features_file) as i_: lines = i_.readlines() - features = map(lambda l: json.loads(l)['feature_vector'], lines) - label_ids = map(lambda l: json.loads(l)['label_id'], lines) + features = list(map(lambda l: json.loads(l)['feature_vector'], lines)) + label_ids = list(map(lambda l: json.loads(l)['label_id'], lines)) return np.array(features, dtype='float32'), np.array(label_ids, dtype='int32') diff --git a/webapp/README.md b/webapp/README.md index 8e53faa..671d7d8 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -3,11 +3,13 @@ webapp === ## Note -This software is design to work with Python2.7 under following condtion. +This software is designed to work with Python 3.6 under following condtions: - This software depends on the following libraries: - - OpenCV3.2 (need to be compiled from source.) - - Refer to installation_instructions.md for installation. - - Softwares you install by pip + - OpenCV 3.4.1 (need to be compiled from source) + - Refer to [installation instructions](../setup/linux_box.md), under _OpenCV3.2 installation_ for installation. + - Alternately, [this guide](https://www.pyimagesearch.com/2016/10/24/ubuntu-16-04-how-to-install-opencv/) has been used successfully. + - On Windows, use Anaconda + - Software you install with pip ``` $ pip install -r requirements.txt # ex) @@ -73,7 +75,8 @@ CLOUD_ML_DATA_DIR = 'gs://{YOUR-OWN-BUCKET-NAME}/{job_id}/features' ``` # replace {YOUR-OWN-BUCKET-NAME} to your own bukcet name $ cd ~/FindYourCandy/train -$ bash build_package.sh gs://{YOUR-OWN-BUCKET-NAME}/package +$ bash build_package.sh gs://{YOUR-OWN-BUCKET-NAME}/package/ +# make sure you have a trailing slash at the end of the command above ``` ### Configuration files diff --git a/webapp/candysorter/__init__.py b/webapp/candysorter/__init__.py index 8e797f6..742bdaf 100644 --- a/webapp/candysorter/__init__.py +++ b/webapp/candysorter/__init__.py @@ -12,5 +12,3 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== - -from app import create_app diff --git a/webapp/candysorter/app.py b/webapp/candysorter/app.py index bc6c43b..83c930d 100644 --- a/webapp/candysorter/app.py +++ b/webapp/candysorter/app.py @@ -25,13 +25,11 @@ from candysorter.config import get_config -def create_app(): - config = get_config(os.getenv('FLASK_ENV', 'dev')) +def create_app(instance_path): + app = Flask('candysorter', instance_path=instance_path, instance_relative_config=True) - app = Flask('candysorter') - - _configure_app(app, config) - _configure_logging(app, config) + _configure_app(app) + _configure_logging(app) _configure_blueprints(app) _configure_errorhandlers(app) _configure_hooks(app) @@ -39,9 +37,10 @@ def create_app(): return app -def _configure_app(app, config): +def _configure_app(app): sys.path.append(os.path.join(os.path.dirname(__file__), '../../train')) - app.config.from_object(config) + app.config.from_object(get_config(os.getenv('FLASK_ENV', 'dev'))) + app.config.from_pyfile('config.py', silent=True) def _configure_blueprints(app): @@ -52,8 +51,27 @@ def _configure_blueprints(app): app.register_blueprint(ui) -def _configure_logging(app, config): - app.logger +""" +Removed file handelers due to probable imcompatability with FileHandleers, Supervisor and Docker + 'file_app': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'formatter': 'default', + 'filename': os.path.join(app.config['LOG_DIR'], 'app.log'), + 'when': 'd', + 'interval': 1, + 'backupCount': 14, + }, + 'file_access': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'formatter': 'access', + 'filename': os.path.join(app.config['LOG_DIR'], 'access.log'), + 'when': 'd', + 'interval': 1, + 'backupCount': 14, + }, + """ + +def _configure_logging(app): logging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': True, @@ -74,50 +92,32 @@ def _configure_logging(app, config): 'class': 'logging.StreamHandler', 'formatter': 'access', }, - 'file_app': { - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'formatter': 'default', - 'filename': os.path.join(config.LOG_DIR, 'app.log'), - 'when': 'd', - 'interval': 1, - 'backupCount': 14, - }, - 'file_access': { - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'formatter': 'access', - 'filename': os.path.join(config.LOG_DIR, 'access.log'), - 'when': 'd', - 'interval': 1, - 'backupCount': 14, - }, - }, 'loggers': { 'candysorter': { - 'level': config.LOG_LEVEL, - 'handlers': ['file_app'] if not app.debug else ['console_app'], + 'level': app.config['LOG_LEVEL'], + 'handlers': ['console_app'] if not app.debug else ['console_app'], 'propagate': False, }, 'tensorflow': { 'level': logging.ERROR, - 'handlers': ['file_access'] if not app.debug else ['console_access'], + 'handlers': ['console_access'] if not app.debug else ['console_access'], 'propagate': False, }, 'werkzeug': { - 'level': config.LOG_LEVEL, - 'handlers': ['file_access'] if not app.debug else ['console_access'], + 'level': app.config['LOG_LEVEL'], + 'handlers': ['console_access'] if not app.debug else ['console_access'], 'propagate': False, }, }, 'root': { - 'level': config.LOG_LEVEL, - 'handlers': ['file_app'] if not app.debug else ['console_app'], + 'level': app.config['LOG_LEVEL'], + 'handlers': ['console_app'] if not app.debug else ['console_app'], }, }) def _configure_errorhandlers(app): - @app.errorhandler(404) @app.errorhandler(405) def handle_error(e): @@ -126,7 +126,6 @@ def handle_error(e): def _configure_hooks(app): - @app.after_request def call_after_request_callbacks(response): for callback in getattr(g, 'after_request_callbacks', ()): diff --git a/webapp/candysorter/config.py b/webapp/candysorter/config.py index 752eb22..7307584 100644 --- a/webapp/candysorter/config.py +++ b/webapp/candysorter/config.py @@ -21,7 +21,7 @@ from candysorter.ext.google.cloud import language -# flake8: noqac +# flake8: noqa class DefaultConfig(object): DEBUG = False TESTING = False @@ -48,13 +48,8 @@ class DefaultConfig(object): 'file': os.path.join(MODEL_DIR, 'GoogleNews-vectors-negative300.bin.gz'), 'binary': True, }, - 'ja': { - 'file': os.path.join(MODEL_DIR, 'model.vec'), - 'binary': False, - }, } - CLASSIFIER_MODEL_DIR = os.path.join(MODEL_DIR, 'classifier') - CLASSIFIER_MODEL_DIR_INITIAL = os.path.join(MODEL_DIR, 'classifier_initial') + INCEPTION_MODEL_FILE = os.path.join(MODEL_DIR, 'classify_image_graph_def.pb') POS_WEIGHTS = { @@ -62,40 +57,73 @@ class DefaultConfig(object): language.PartOfSpeech.NOUN: 2.8, } - CANDY_DETECTOR_HISTGRAM_BAND = (0, 255) - CANDY_DETECTOR_HISTGRAM_THRES = 2.7e-3 - CANDY_DETECTOR_BIN_THRES = 150 - CANDY_DETECTOR_EDGE3_THRES = 250 - CANDY_DETECTOR_EDGE5_THRES = 230 - CANDY_DETECTOR_MARGIN = (30, 30) - CANDY_DETECTOR_CLOSING_ITER = 2 - CANDY_DETECTOR_OPENING_ITER = 5 - CANDY_DETECTOR_ERODE_ITER = 25 - CANDY_DETECTOR_DILATE_ITER = 1 - CANDY_DETECTOR_BG_SIZE_FILTER = 2000 - CANDY_DETECTOR_SURE_FG_THRES = 10 + # This setting will determine training parameters, as well as pickup type and classifier used. + CANDY_TYPE = 0 # 0=TWIST 1=BOX CANDY + + if CANDY_TYPE == 0: + CANDY_MODEL_DIR = os.path.join(MODEL_DIR, "twist") + elif CANDY_TYPE == 1: + CANDY_MODEL_DIR = os.path.join(MODEL_DIR, "box_candy") + + CLASSIFIER_DIR_NAME = 'classifier' + CLASSIFIER_DIR_NAME_INITIAL = 'classifier_initial' + CLASSIFIER_MODEL_DIR = os.path.join(CANDY_MODEL_DIR, CLASSIFIER_DIR_NAME) + CLASSIFIER_MODEL_DIR_INITIAL = os.path.join(CANDY_MODEL_DIR, CLASSIFIER_DIR_NAME_INITIAL) + + CANDY_DETECTOR_HISTGRAM_BAND = (0, 255) + CANDY_DETECTOR_HISTGRAM_THRES = 2.7e-3 + CANDY_DETECTOR_BIN_THRES = 150 + CANDY_DETECTOR_EDGE3_THRES = 250 + CANDY_DETECTOR_EDGE5_THRES = 230 + CANDY_DETECTOR_MARGIN = (30, 30) + CANDY_DETECTOR_CLOSING_ITER = 2 + CANDY_DETECTOR_OPENING_ITER = 5 + CANDY_DETECTOR_ERODE_ITER = 25 + CANDY_DETECTOR_DILATE_ITER = 1 + CANDY_DETECTOR_BG_SIZE_FILTER = 2000 + CANDY_DETECTOR_SURE_FG_THRES = 10 CANDY_DETECTOR_RESTORE_FG_THRES = 0.0 - CANDY_DETECTOR_BOX_DIM_THRES = 50 - - IMAGE_CAPTURE_DEVICE = 0 - IMAGE_CAPTURE_WIDTH = 1920 - IMAGE_CAPTURE_HEIGHT = 1080 - IMAGE_CAPTURE_BLUR_THRESH = 100 - - IMAGE_CALIBRATOR_AREA = (1625, 1100) + CANDY_DETECTOR_BOX_DIM_THRES = 140 + + TWIST_CANDY_DETECTOR_HISTGRAM_BAND = (0, 255) + TWIST_CANDY_DETECTOR_HISTGRAM_THRES = 2.7e-3 + TWIST_CANDY_DETECTOR_BIN_THRES = 150 + TWIST_CANDY_DETECTOR_EDGE3_THRES = 250 + TWIST_CANDY_DETECTOR_EDGE5_THRES = 230 + TWIST_CANDY_DETECTOR_MARGIN = (30, 30) + TWIST_CANDY_DETECTOR_CLOSING_ITER = 2 + TWIST_CANDY_DETECTOR_OPENING_ITER = 5 + TWIST_CANDY_DETECTOR_ERODE_ITER = 1 + TWIST_CANDY_DETECTOR_DILATE_ITER = 1 + TWIST_CANDY_DETECTOR_BG_SIZE_FILTER = 2000 + TWIST_CANDY_DETECTOR_SURE_FG_THRES = 10 + TWIST_CANDY_DETECTOR_RESTORE_FG_THRES = 0.0 + TWIST_CANDY_DETECTOR_BOX_DIM_THRES = 100 + + IMAGE_CAPTURE_DEVICE = 1 + IMAGE_CAPTURE_WIDTH = 1920 + IMAGE_CAPTURE_HEIGHT = 1080 + IMAGE_CAPTURE_BLUR_THRESH = 10 + + IMAGE_CALIBRATOR_AREA = (1625, 1100) IMAGE_CALIBRATOR_SCALE = 550 - PICKUP_ENDOPOINT = 'http://localhost:18001/api/pickup' + PICKUP_TYPE = 'suction_cup' if CANDY_TYPE else "gripper" # Use 'gripper' or 'suction_cup' + ROBOT_ARM_API_URL = 'http://robot/api' + ROBOT_ARM_STATUS_ENDPOINT = ROBOT_ARM_API_URL + '/status' + PICKUP_SUCTION_CUP_ENDPOINT = ROBOT_ARM_API_URL + '/pickup' + PICKUP_GRIPPER_ENDPOINT = ROBOT_ARM_API_URL + '/pickup/gripper' TRAIN_LABEL_AREA_HEIGHT = 285 # replace "YOUR-OWN-BUCKET-NAME" to your own bucket name - CLOUD_ML_BUCKET = 'gs://{YOUR-OWN-BUCKET-NAME}' - CLOUD_ML_PACKAGE_URIS = ['gs://{YOUR-OWN-BUCKET-NAME}/package/trainer-0.0.0.tar.gz'] - CLOUD_ML_PYTHON_MODULE = 'trainer.train' - CLOUD_ML_TRAIN_DIR = 'gs://{YOUR-OWN-BUCKET-NAME}/{job_id}/checkpoints' - CLOUD_ML_LOG_DIR = 'gs://{YOUR-OWN-BUCKET-NAME}/logs/{job_id}' - CLOUD_ML_DATA_DIR = 'gs://{YOUR-OWN-BUCKET-NAME}/{job_id}/features' + CLOUD_ML_BUCKET = 'gs://YOUR-OWN-BUCKET-NAME' + CLOUD_ML_PACKAGE_URIS = ['gs://YOUR-OWN-BUCKET-NAME/package/trainer-0.0.0.tar.gz'] + CLOUD_ML_PYTHON_MODULE = 'trainer.train' + CLOUD_ML_TRAIN_DIR = 'gs://YOUR-OWN-BUCKET-NAME/{job_id}/checkpoints' + CLOUD_ML_LOG_DIR = 'gs://YOUR-OWN-BUCKET-NAME/logs/{job_id}' + CLOUD_ML_DATA_DIR = 'gs://YOUR-OWN-BUCKET-NAME/{job_id}/features' + CLOUD_ML_RUNTIME_VERSION = '1.0' class DevelopmentConfig(DefaultConfig): @@ -104,7 +132,7 @@ class DevelopmentConfig(DefaultConfig): CLASS_TEXT_ANALYZER = 'candysorter.models.texts.FakeTextAnalyzer' CLASS_IMAGE_CAPTURE = 'candysorter.models.images.capture.FakeImageCapture' - DUMMY_IMAGE_FILE = './candysorter/resources/data/candies_with_label_multi.jpg' + DUMMY_IMAGE_FILE = os.path.join(DefaultConfig.RESOURCE_DIR, 'dummy_image.jpg') class StagingConfig(DefaultConfig): @@ -121,10 +149,6 @@ class ProductionConfig(DefaultConfig): 'prd': ProductionConfig, } -Config = None - def get_config(env): - global Config - Config = _ENV_TO_CONFIG[env]() - return Config + return _ENV_TO_CONFIG[env]() diff --git a/webapp/candysorter/ext/google/cloud/auth.py b/webapp/candysorter/ext/google/cloud/auth.py new file mode 100644 index 0000000..8f8b91f --- /dev/null +++ b/webapp/candysorter/ext/google/cloud/auth.py @@ -0,0 +1,14 @@ +def test_auth(): + """ + Copied from GCP Getting started. Helper that makes an authenticated call to test credentials. + Exception should be caught by caller + """ + from google.cloud import storage + + # If you don't specify credentials when constructing the client, the + # client library will look for credentials in the environment. + storage_client = storage.Client() + + # Make an authenticated API request + buckets = list(storage_client.list_buckets()) + diff --git a/webapp/candysorter/ext/google/cloud/ml/_http.py b/webapp/candysorter/ext/google/cloud/ml/_http.py index 5811c3d..cc1045f 100644 --- a/webapp/candysorter/ext/google/cloud/ml/_http.py +++ b/webapp/candysorter/ext/google/cloud/ml/_http.py @@ -20,5 +20,5 @@ class Connection(_http.JSONConnection): API_BASE_URL = 'https://ml.googleapis.com' - API_VERSION = 'v1beta1' + API_VERSION = 'v1' API_URL_TEMPLATE = '{api_base_url}/{api_version}{path}' diff --git a/webapp/candysorter/ext/google/cloud/ml/training.py b/webapp/candysorter/ext/google/cloud/ml/training.py index 0bd772e..e3bb20b 100644 --- a/webapp/candysorter/ext/google/cloud/ml/training.py +++ b/webapp/candysorter/ext/google/cloud/ml/training.py @@ -26,11 +26,13 @@ class ScaleTier(object): class TrainingInput(object): def __init__(self, package_uris, python_module, scale_tier=ScaleTier.BASIC, - region='us-central1'): + region='us-central1', runtime_version='1.8', python_version='3.5'): self.package_uris = package_uris self.python_module = python_module self.scale_tier = scale_tier self.region = region + self.runtime_version = runtime_version + self.python_version = python_version self._properties = {} @classmethod @@ -38,7 +40,9 @@ def from_api_repr(cls, resource): training_input = cls(package_uris=resource.get('packageUris'), python_module=resource.get('pythonModule'), scale_tier=resource.get('scaleTier'), - region=resource.get('region')) + region=resource.get('region'), + runtime_version=resource.get('runtimeVersion'), + python_version=resource.get('pythonVersion')) if 'args' in resource: training_input._properties['args'] = resource['args'] return training_input @@ -49,6 +53,8 @@ def to_api_repr(self): 'packageUris': self.package_uris, 'pythonModule': self.python_module, 'region': self.region, + 'runtimeVersion': self.runtime_version, + 'pythonVersion': self.python_version, } _args = self._properties.get('args') if _args is not None: diff --git a/webapp/candysorter/ext/google/cloud/translation.py b/webapp/candysorter/ext/google/cloud/translation.py new file mode 100644 index 0000000..b9fa9f9 --- /dev/null +++ b/webapp/candysorter/ext/google/cloud/translation.py @@ -0,0 +1,6 @@ +from google.cloud import translate + + +class TranslatorClient(translate.Client): + def translate_text(self, text, source_lang, target_lang): + return self.translate([text], source_language=source_lang, target_language=target_lang) \ No newline at end of file diff --git a/webapp/candysorter/models/images/README.md b/webapp/candysorter/models/images/README.md new file mode 100644 index 0000000..0ff0384 --- /dev/null +++ b/webapp/candysorter/models/images/README.md @@ -0,0 +1,34 @@ +# Image handeling +Directory for handeling images. + + +#### Getting Started +To tune the camera run camera_tune.py. +All QR codes must be in the picture in order to proceed. The camera needs to be focused and have a high exposure. +To set the detection for twist candy, set candy_type in from candysorter.config to 0, 1 for box candy. + +#### Capture images for training data +Run capture_images_for_training.py. Make sure that the candy is well displayed for the camera, that they are fully within the black frame, and that the red edge boxes only cover one piece of candy each. To collect data, click on the space bar. Make sure to move the candy between each capture to get a variation in the pictures. Restart capture_images_for_training when starting on a new label. + + +#### Configure and detect candy +Capture_images_for_training.py and camera_tune.py uses cv2.VideoCapture() to take snapshots and show live feed camera, the snapshot taken of all candies is fed to detect.py. Variables for edge detection, margin, size etc. in detect.py are set in config.py. In order to get good images for training data or live demo, changes might have to be done to suite the given candy objects. + +The following variables dominates the success of the image detection: + +- __CANDY_DETECTOR_HISTGRAM_BAND:__ The span from black to white. Determines which +- __CANDY_DETECTOR_HISTGRAM_THRES:__ The threshold for the histogram of an image. +- __CANDY_DETECTOR_BIN_THRES:__ Partition an image using a determined threshold. +- __CANDY_DETECTOR_CLOSING_ITER:__ Number of iterations closing small holes inside the foreground objects, or small black points on the object. +- __CANDY_DETECTOR_OPENING_ITER:__ Number of iterations closing small holes in the background of the foreground objects, or small black points. +- __CANDY_DETECTOR_ERODE_ITER:__ Useful for removing small white noises or detach two connected objects etc. +- __CANDY_DETECTOR_DILATE_ITER:__ Normally, in cases like noise removal, erosion is followed by dilation. Because, erosion removes white noises, but it also shrinks our object. So we dilate it. Since noise is gone, they won’t come back, but our object area increases. It is also useful in joining broken parts of an object. +- __CANDY_DETECTOR_BOX_DIM_THRES:__ The lowest dimension on a detected item. Tested on the Twist candy, 125 will exclude marsipan, while 100 will include it. A box dimension on 50 will split the Twist candy into 2 or 3 pieces. +- __IMAGE_CAPTURE_DEVICE:__ 0 for internal camera, 1 for external + + +####### Image detection in detect.py +The image is converted to binary based on the image’s histogram. A histogram will show a graph of pixel grayscale color values between 0 and 255. +The Laplacian of an image highlights regions of rapid intensity change and is used to detect edges. An image with high variance might need 3x3 while slightly less variance will need a 5x5 gradient mask edge detector. With 5x5, the gradient will be less sensitive to local noise. +In order to remove noise, closed and opening is used to remove small white holes in the background and foreground. Erode is then used to remove shade etc. around the object and then dilate widens the obejct to restore its size. + diff --git a/webapp/candysorter/models/images/calibrate.py b/webapp/candysorter/models/images/calibrate.py index 60284a8..d6c7d2f 100644 --- a/webapp/candysorter/models/images/calibrate.py +++ b/webapp/candysorter/models/images/calibrate.py @@ -36,8 +36,8 @@ def __init__(self, thres=10, dictionary=cv2.aruco.DICT_6X6_250, area=(1000, 1519 @classmethod def from_config(cls, config): - return cls(area=config.IMAGE_CALIBRATOR_AREA, - scale=config.IMAGE_CALIBRATOR_SCALE) + return cls(area=config['IMAGE_CALIBRATOR_AREA'], + scale=config['IMAGE_CALIBRATOR_SCALE']) def calibrate(self, img): corners = self.detect_corners(img) @@ -57,6 +57,9 @@ def detect_corners(self, img): self._prev_valid_corners = corners return corners + if len(marker_coords) == 0: + raise RuntimeError('No markers detected') + # Use _prev_valid_corners if exists if self._prev_valid_corners is not None: # Check the camera is not moved diff --git a/webapp/candysorter/models/images/capture.py b/webapp/candysorter/models/images/capture.py index ff280f2..84b9f22 100644 --- a/webapp/candysorter/models/images/capture.py +++ b/webapp/candysorter/models/images/capture.py @@ -16,12 +16,11 @@ from __future__ import absolute_import, division, print_function, unicode_literals import time - import cv2 class ImageCapture(object): - def __init__(self, device, width, height, blur_thres=100): + def __init__(self, device, width, height, blur_thres=1): self.device = device self.width = width self.height = height @@ -29,21 +28,23 @@ def __init__(self, device, width, height, blur_thres=100): @classmethod def from_config(cls, config): - return cls(device=config.IMAGE_CAPTURE_DEVICE, - width=config.IMAGE_CAPTURE_WIDTH, - height=config.IMAGE_CAPTURE_HEIGHT, - blur_thres=config.IMAGE_CAPTURE_BLUR_THRESH) + return cls(device=config['IMAGE_CAPTURE_DEVICE'], + width=config['IMAGE_CAPTURE_WIDTH'], + height=config['IMAGE_CAPTURE_HEIGHT'], + blur_thres=config['IMAGE_CAPTURE_BLUR_THRESH']) def capture(self): capture = cv2.VideoCapture(self.device) capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.width) capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height) + if not capture.isOpened(): raise Exception('Failed to open camera capture.') for _ in range(0, 10): ret, img = capture.read() + if not ret or self._blur_index(img) < self.blur_thres: time.sleep(0.5) continue @@ -51,7 +52,7 @@ def capture(self): return img capture.release() - raise Exception('Failed to capture image.') + raise Exception('Failed to capture an sufficiently crisp image. No image was above blur threshold') def _blur_index(self, img): img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) @@ -64,7 +65,11 @@ def __init__(self, img_file): @classmethod def from_config(cls, config): - return cls(img_file=config.DUMMY_IMAGE_FILE) + return cls(img_file=config['DUMMY_IMAGE_FILE']) def capture(self): - return cv2.imread(self.img_file) + img = cv2.imread(self.img_file) + if img is None: + raise FileNotFoundError("Could not find the fake image") + else: + return img diff --git a/webapp/candysorter/models/images/capture_images_for_training.py b/webapp/candysorter/models/images/capture_images_for_training.py new file mode 100644 index 0000000..641edf1 --- /dev/null +++ b/webapp/candysorter/models/images/capture_images_for_training.py @@ -0,0 +1,158 @@ +from capture import ImageCapture +import argparse +import cv2 +import os +import datetime +import tensorflow as tf +import numpy as np +import time +import config as config_img + +from candysorter.models.images.calibrate import ImageCalibrator +from candysorter.models.images.detect import CandyDetector +from candysorter.config import get_config + +calibrator = ImageCalibrator(area=(1625, 1100), scale=550) +config = get_config(os.getenv('FLASK_ENV', 'dev')) + +# Convert config from class object to dictionary for compatability with flask +config_dict = {} +for key in dir(config): + if key.isupper(): + config_dict[key] = getattr(config, key) + + +should_exit = False + +class Trainingdata(): + candies = [] + path_to_label_dir = None + font = cv2.FONT_HERSHEY_PLAIN + num_images = 0 + + def mouse_event(self, event, x, y, flags, param): + global should_exit + if event == cv2.EVENT_LBUTTONUP: + print("mouse_event:L-click") + should_exit = True + elif event == cv2.EVENT_RBUTTONUP: + print("mouse_event:R-click") + self.take_pictures_and_save() + + + def write_message(self, image, msg, size=3, thickness=3): + cv2.putText(image, msg, (10, 130), self.font, size, (250, 30, 30), thickness) + + + def write_ok(self, image): + cv2.putText(image, 'OK', (900, 500), self.font, 7, (30, 250, 30), 5) + + + def detect_corners(self, image): + try: + corners = calibrator.detect_corners(image) + except Exception as e: + print(e) + return None + if len(corners) < 4: + return None + return corners + + + def draw_detection(self, image, candies): + for candy in candies: + cv2.polylines(image, np.int32([np.array(candy.box_coords)]), isClosed=True, color=(0, 0, 255), + lineType=cv2.LINE_AA, thickness=3) + + def set_variables(self, images, path): + self.candies = images + self.path_to_label_dir = path + + + def take_pictures_and_save(self): + self.num_images += len(self.candies) + for i in range(len(self.candies)): + if (self.candies == None or self.path_to_label_dir == None): + print("Images or path to label directory is not set") + return + tmp_file = os.path.join((self.path_to_label_dir), + ('{}{}.jpg'.format(datetime.datetime.now().strftime('%Y%m%d_%H%M%S'), i))) + cv2.imwrite(tmp_file, self.candies[i].cropped_img) + print(len(self.candies), 'saved to path', self.path_to_label_dir, 'which makes a total of', self.num_images) + + +def main(): + parser = argparse.ArgumentParser(description='Gather images for training.') + parser.add_argument('--image_dir', type=str, default="training_dir", help="location for new training images + /label_name") + + detector = CandyDetector.from_config(config_dict) + td = Trainingdata() + args = parser.parse_args() + image_dir = args.image_dir + candy_list = config_img.BOX_CANDIES + if config.CANDY_TYPE == 0: + candy_list = config_img.TWIST_CANDIES + + print('The following labels are available in directory: ') + for i in range(len(candy_list)): + print(i, ':', candy_list[i]) + + lid = int(input("input label number: ")) + label = "" + + if lid < 0 or lid >= len(candy_list): + raise ValueError("{} is not a valid input".format(lid)) + else : + label = candy_list[lid] + + path_to_label_dir = os.path.join(image_dir, label) + if not tf.gfile.Exists(path_to_label_dir): + tf.gfile.MakeDirs(path_to_label_dir) + + capture = cv2.VideoCapture(0) + capture.set(3, 1920) + capture.set(4, 1080) + + w2_size = (960, 540) + cv2.namedWindow('Detection', cv2.WINDOW_KEEPRATIO | cv2.WINDOW_NORMAL) + cv2.resizeWindow('Detection', *w2_size) + cv2.setMouseCallback('Detection', td.mouse_event) + candies = None + counter = 0 + + while True: + time.sleep(0.01) + if capture.isOpened: + ret, frame = capture.read() + if not ret: + break + corners = td.detect_corners(frame) + if corners is not None: + cropped = calibrator.calibrate(frame) + #write_message(cropped, label) + #td.write_message(cropped, 'click right mouse button to take image') + if (counter % 5 == 0): + candies = detector.detect(cropped) + td.set_variables(candies, os.path.join(os.path.join(image_dir, label))) + td.draw_detection(cropped, candies) + cv2.imshow('Detection', cropped) + td.write_ok(frame) + counter += 1 + else: + blank = np.zeros((w2_size[1], w2_size[0], 3), np.uint8) + td.write_message(blank, 'Marker detection failed', size=1, thickness=1) + cv2.imshow('Detection', blank) + + key = cv2.waitKey(1) + if (key == ord(' ')): + td.take_pictures_and_save() + if (key == 27): + break + + if should_exit: + break + + print("Exit.") + cv2.destroyAllWindows() +if __name__ == '__main__': + main() diff --git a/webapp/candysorter/models/images/classify.py b/webapp/candysorter/models/images/classify.py index b4da328..47278bd 100644 --- a/webapp/candysorter/models/images/classify.py +++ b/webapp/candysorter/models/images/classify.py @@ -17,6 +17,7 @@ import logging import os +from candysorter.utils import get_classifier_dir import tensorflow as tf @@ -36,11 +37,11 @@ def __init__(self, checkpoint_dir, params_file, inception_model_file): @classmethod def from_config(cls, config): - checkpoint_dir = config.CLASSIFIER_MODEL_DIR + checkpoint_dir = get_classifier_dir(config) return cls( checkpoint_dir=checkpoint_dir, params_file=os.path.join(checkpoint_dir, 'params.json'), - inception_model_file=config.INCEPTION_MODEL_FILE + inception_model_file=config['INCEPTION_MODEL_FILE'] ) def init(self): diff --git a/webapp/candysorter/models/images/config.py b/webapp/candysorter/models/images/config.py new file mode 100644 index 0000000..4f2d4f0 --- /dev/null +++ b/webapp/candysorter/models/images/config.py @@ -0,0 +1,23 @@ +# Copyright 2017 BrainPad Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + + +CAM_DEVICE = 0 +CAM_HEIGHT = 1080 +CAM_WIDTH = 1920 + + +TWIST_CANDIES = ['banan', 'caramel', 'chocolate_toffee', 'cocos', 'daim','eclairs', 'fransk_nougat', 'golden_toffee', 'japp', 'lakrids', 'marsipan', 'nougat_crisp', 'notti'] +BOX_CANDIES = ['chocolate','gum','liquorice','smurf'] \ No newline at end of file diff --git a/webapp/candysorter/models/images/detect.py b/webapp/candysorter/models/images/detect.py index c104ad6..667d3f3 100644 --- a/webapp/candysorter/models/images/detect.py +++ b/webapp/candysorter/models/images/detect.py @@ -23,14 +23,15 @@ logger = logging.getLogger(__name__) -vision_client = vision.Client() + class Candy(object): - def __init__(self, box_coords, box_dims, box_centroid, cropped_img): + def __init__(self, box_coords, box_dims, box_centroid, box_rotation, cropped_img): self.box_coords = box_coords self.box_dims = box_dims self.box_centroid = box_centroid + self.box_rotation = box_rotation self.cropped_img = cropped_img @@ -70,20 +71,36 @@ def __init__(self, @classmethod def from_config(cls, config): - return cls(histgram_band=config.CANDY_DETECTOR_HISTGRAM_BAND, - histgram_thres=config.CANDY_DETECTOR_HISTGRAM_THRES, - bin_thres=config.CANDY_DETECTOR_BIN_THRES, - edge3_thres=config.CANDY_DETECTOR_EDGE3_THRES, - edge5_thres=config.CANDY_DETECTOR_EDGE5_THRES, - margin=config.CANDY_DETECTOR_MARGIN, - closing_iter=config.CANDY_DETECTOR_CLOSING_ITER, - opening_iter=config.CANDY_DETECTOR_OPENING_ITER, - erode_iter=config.CANDY_DETECTOR_ERODE_ITER, - dilate_iter=config.CANDY_DETECTOR_DILATE_ITER, - bg_size_filter=config.CANDY_DETECTOR_BG_SIZE_FILTER, - sure_fg_thres=config.CANDY_DETECTOR_SURE_FG_THRES, - restore_fg_thres=config.CANDY_DETECTOR_RESTORE_FG_THRES, - box_dim_thres=config.CANDY_DETECTOR_BOX_DIM_THRES) + if config['CANDY_TYPE'] == 0: + return cls(histgram_band=config['TWIST_CANDY_DETECTOR_HISTGRAM_BAND'], + histgram_thres=config['TWIST_CANDY_DETECTOR_HISTGRAM_THRES'], + bin_thres=config['TWIST_CANDY_DETECTOR_BIN_THRES'], + edge3_thres=config['TWIST_CANDY_DETECTOR_EDGE3_THRES'], + edge5_thres=config['TWIST_CANDY_DETECTOR_EDGE5_THRES'], + margin=config['TWIST_CANDY_DETECTOR_MARGIN'], + closing_iter=config['TWIST_CANDY_DETECTOR_CLOSING_ITER'], + opening_iter=config['TWIST_CANDY_DETECTOR_OPENING_ITER'], + erode_iter=config['TWIST_CANDY_DETECTOR_ERODE_ITER'], + dilate_iter=config['TWIST_CANDY_DETECTOR_DILATE_ITER'], + bg_size_filter=config['TWIST_CANDY_DETECTOR_BG_SIZE_FILTER'], + sure_fg_thres=config['TWIST_CANDY_DETECTOR_SURE_FG_THRES'], + restore_fg_thres=config['TWIST_CANDY_DETECTOR_RESTORE_FG_THRES'], + box_dim_thres=config['TWIST_CANDY_DETECTOR_BOX_DIM_THRES']) + else: + return cls(histgram_band=config['CANDY_DETECTOR_HISTGRAM_BAND'], + histgram_thres=config['CANDY_DETECTOR_HISTGRAM_THRES'], + bin_thres=config['CANDY_DETECTOR_BIN_THRES'], + edge3_thres=config['CANDY_DETECTOR_EDGE3_THRES'], + edge5_thres=config['CANDY_DETECTOR_EDGE5_THRES'], + margin=config['CANDY_DETECTOR_MARGIN'], + closing_iter=config['CANDY_DETECTOR_CLOSING_ITER'], + opening_iter=config['CANDY_DETECTOR_OPENING_ITER'], + erode_iter=config['CANDY_DETECTOR_ERODE_ITER'], + dilate_iter=config['CANDY_DETECTOR_DILATE_ITER'], + bg_size_filter=config['CANDY_DETECTOR_BG_SIZE_FILTER'], + sure_fg_thres=config['CANDY_DETECTOR_SURE_FG_THRES'], + restore_fg_thres=config['CANDY_DETECTOR_RESTORE_FG_THRES'], + box_dim_thres=config['CANDY_DETECTOR_BOX_DIM_THRES']) def detect(self, img): img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) @@ -198,20 +215,22 @@ def detect(self, img): _, _contours, _ = cv2.findContours(temp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contour = _contours[0] - box_coords, box_dims, box_centroid = _bounding_box_of(contour) + box_coords, box_dims, box_centroid, box_rotation = _bounding_box_of(contour) if any([dim <= self.box_dim_thres for dim in box_dims]): continue - cropped_img = _crop_candy(img, markers != i, box_coords, box_dims, box_centroid) + cropped_img = _crop_candy(img, markers != i, box_dims, box_centroid, box_rotation) + candies.append(Candy(box_coords=box_coords, box_dims=box_dims, box_centroid=box_centroid, + box_rotation=box_rotation, cropped_img=cropped_img)) return candies -def _crop_candy(img, mask, box_coords, box_dims, box_centroid): +def _crop_candy(img, mask, box_dims, box_centroid, box_rotation): # White out other than candy _img = img.copy() @@ -221,26 +240,19 @@ def _crop_candy(img, mask, box_coords, box_dims, box_centroid): h, w, _ = _img.shape center = (w / 2, h / 2) - # Get bounding box coordinates - x1, y1 = box_coords[0] # top-left - x2, y2 = box_coords[1] # top-right - # Get bounding box dimensions pw = box_dims[0] ph = box_dims[1] - # Determine angle of rotation (in degrees) - # FIXME: x1 == x2 - angle = np.arctan((y2 - y1) / (x2 - x1)) * 180 / np.pi - # Center of bounding pox + # Center of bounding box tx, ty = box_centroid # Translation matrix to move candy to center of image trans_mat = np.float32([[1, 0, -tx + w / 2], [0, 1, -ty + h / 2], [0, 0, 1]]) # Rotation matrix - rot_mat = cv2.getRotationMatrix2D(center, angle, 1) + rot_mat = cv2.getRotationMatrix2D(center, box_rotation, 1) rot_mat = np.vstack((rot_mat, [0, 0, 1])) # Translation+Rotation matrix @@ -279,10 +291,20 @@ def _bounding_box_of(contour): box_dims = rotbox[1] box_centroid = int((left[0][0] + right[1][0]) / 2.0), int((left[0][1] + right[1][1]) / 2.0) - return box_coords, box_dims, box_centroid + # Get bounding box coordinates + x1, y1 = box_coords[0] # top-left + x2, y2 = box_coords[1] # top-right + + # Determine angle of rotation (in degrees) + box_rotation = 0 + if x1 != x2: + box_rotation = np.arctan((y2 - y1) / (x2 - x1)) * 180 / np.pi + + return box_coords, box_dims, box_centroid, box_rotation def detect_labels(img): + vision_client = vision.Client() image = vision_client.image(content=cv2.imencode('.jpg', img)[1].tostring()) try: texts = image.detect_text() diff --git a/webapp/candysorter/models/images/train.py b/webapp/candysorter/models/images/train.py index 5d7a103..9f7fcfa 100644 --- a/webapp/candysorter/models/images/train.py +++ b/webapp/candysorter/models/images/train.py @@ -17,7 +17,7 @@ import json import os -from urlparse import urlparse +from urllib.parse import urlparse from google.cloud import storage from sklearn.decomposition import PCA @@ -47,14 +47,14 @@ def __init__(self, feature_extractor, package_uris, python_module, data_dir_form @classmethod def from_config(cls, config): - return cls(feature_extractor=FeatureExtractor(config.INCEPTION_MODEL_FILE), - package_uris=config.CLOUD_ML_PACKAGE_URIS, - python_module=config.CLOUD_ML_PYTHON_MODULE, - data_dir_format=config.CLOUD_ML_DATA_DIR, - train_dir_format=config.CLOUD_ML_TRAIN_DIR, - log_dir_format=config.CLOUD_ML_LOG_DIR, - local_model_dir=config.MODEL_DIR, - local_classifier_model_dir=config.CLASSIFIER_MODEL_DIR) + return cls(feature_extractor=FeatureExtractor(config['INCEPTION_MODEL_FILE']), + package_uris=config['CLOUD_ML_PACKAGE_URIS'], + python_module=config['CLOUD_ML_PYTHON_MODULE'], + data_dir_format=config['CLOUD_ML_DATA_DIR'], + train_dir_format=config['CLOUD_ML_TRAIN_DIR'], + log_dir_format=config['CLOUD_ML_LOG_DIR'], + local_model_dir=config['MODEL_DIR'], + local_classifier_model_dir=config['CLASSIFIER_MODEL_DIR']) def data_dir(self, job_id): return self.data_dir_format.format(job_id=job_id) diff --git a/webapp/candysorter/models/texts.py b/webapp/candysorter/models/texts.py index 7fa8958..6a1a547 100644 --- a/webapp/candysorter/models/texts.py +++ b/webapp/candysorter/models/texts.py @@ -18,8 +18,9 @@ import json import logging import os +from candysorter.utils import get_classifier_dir -from gensim.models import Word2Vec +from gensim.models import KeyedVectors import numpy as np from scipy import spatial @@ -45,9 +46,9 @@ def __init__(self, params_file, model_files, pos_weights): @classmethod def from_config(cls, config): - return cls(params_file=os.path.join(config.CLASSIFIER_MODEL_DIR, 'params.json'), - model_files=config.WORD2VEC_MODEL_FILES, - pos_weights=config.POS_WEIGHTS) + return cls(params_file=os.path.join(get_classifier_dir(config), 'params.json'), + model_files=config['WORD2VEC_MODEL_FILES'], + pos_weights=config['POS_WEIGHTS']) def init(self): self._load_models() @@ -60,7 +61,7 @@ def _load_models(self): self.models = {} for l, v in self.model_files.items(): logger.info('Loading %s word2vec model...', l) - self.models[l] = Word2Vec.load_word2vec_format(v['file'], binary=v['binary']) + self.models[l] = KeyedVectors.load_word2vec_format(v['file'], binary=v['binary']) logger.info('Finished %s loading word2vec model.', l) def _load_labels(self): diff --git a/webapp/candysorter/resources/dummy_image.jpg b/webapp/candysorter/resources/dummy_image.jpg new file mode 100644 index 0000000..0da5bda Binary files /dev/null and b/webapp/candysorter/resources/dummy_image.jpg differ diff --git a/webapp/candysorter/resources/models/classifier b/webapp/candysorter/resources/models/box_candy/classifier similarity index 100% rename from webapp/candysorter/resources/models/classifier rename to webapp/candysorter/resources/models/box_candy/classifier diff --git a/webapp/candysorter/resources/models/box_candy/classifier_initial/checkpoint b/webapp/candysorter/resources/models/box_candy/classifier_initial/checkpoint new file mode 100644 index 0000000..992fe9b --- /dev/null +++ b/webapp/candysorter/resources/models/box_candy/classifier_initial/checkpoint @@ -0,0 +1,2 @@ +model_checkpoint_path: "model.ckpt-9000" +all_model_checkpoint_paths: "model.ckpt-9000" diff --git a/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.data-00000-of-00001 b/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.data-00000-of-00001 new file mode 100644 index 0000000..895e00b Binary files /dev/null and b/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.data-00000-of-00001 differ diff --git a/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.index b/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.index new file mode 100644 index 0000000..88a1c0c Binary files /dev/null and b/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.index differ diff --git a/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.meta b/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.meta new file mode 100644 index 0000000..6dcfcdd Binary files /dev/null and b/webapp/candysorter/resources/models/box_candy/classifier_initial/model.ckpt-9000.meta differ diff --git a/webapp/candysorter/resources/models/box_candy/classifier_initial/params.json b/webapp/candysorter/resources/models/box_candy/classifier_initial/params.json new file mode 100644 index 0000000..3cad4da --- /dev/null +++ b/webapp/candysorter/resources/models/box_candy/classifier_initial/params.json @@ -0,0 +1 @@ +{"labels": ["banan", "caramel", "chocolate_toffee", "cocos", "daim", "eclairs", "fransk_nougat", "golden_toffee", "japp", "lakrids", "marsipan", "notti", "nougat_crisp"], "features_size": 2048, "hidden_size": 3} \ No newline at end of file diff --git a/webapp/candysorter/resources/models/classifier_initial/checkpoint b/webapp/candysorter/resources/models/classifier_initial/checkpoint index 69a2775..9aa3203 100644 --- a/webapp/candysorter/resources/models/classifier_initial/checkpoint +++ b/webapp/candysorter/resources/models/classifier_initial/checkpoint @@ -1,2 +1,2 @@ -model_checkpoint_path: "model.ckpt-4000" -all_model_checkpoint_paths: "model.ckpt-4000" +model_checkpoint_path: "model.ckpt-2300" +all_model_checkpoint_paths: "model.ckpt-2300" diff --git a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.data-00000-of-00001 b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.data-00000-of-00001 new file mode 100644 index 0000000..b58b897 Binary files /dev/null and b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.data-00000-of-00001 differ diff --git a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.index b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.index new file mode 100644 index 0000000..e08f122 Binary files /dev/null and b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.index differ diff --git a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.meta b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.meta new file mode 100644 index 0000000..c1e4b6f Binary files /dev/null and b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-2300.meta differ diff --git a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.data-00000-of-00001 b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.data-00000-of-00001 deleted file mode 100644 index d3d1dfa..0000000 Binary files a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.data-00000-of-00001 and /dev/null differ diff --git a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.index b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.index deleted file mode 100644 index 2a37038..0000000 Binary files a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.index and /dev/null differ diff --git a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.meta b/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.meta deleted file mode 100644 index 52d5704..0000000 Binary files a/webapp/candysorter/resources/models/classifier_initial/model.ckpt-4000.meta and /dev/null differ diff --git a/webapp/candysorter/resources/models/classifier_initial/params.json b/webapp/candysorter/resources/models/classifier_initial/params.json index 27f3fc3..392393e 100644 --- a/webapp/candysorter/resources/models/classifier_initial/params.json +++ b/webapp/candysorter/resources/models/classifier_initial/params.json @@ -1 +1 @@ -{"labels": ["MINT CANDY", "CANDY CHOCOLATE", "CHEWY CANDY", "SALTY SNACKS"], "hidden_size": 3, "features_size": 2048} \ No newline at end of file +{"labels": ["chocolate", "gum", "liquorice", "smurf"], "features_size": 2048, "hidden_size": 2} \ No newline at end of file diff --git a/webapp/candysorter/resources/models/twist/classifier b/webapp/candysorter/resources/models/twist/classifier new file mode 120000 index 0000000..1370a31 --- /dev/null +++ b/webapp/candysorter/resources/models/twist/classifier @@ -0,0 +1 @@ +classifier_initial \ No newline at end of file diff --git a/webapp/candysorter/resources/models/twist/classifier_initial/checkpoint b/webapp/candysorter/resources/models/twist/classifier_initial/checkpoint new file mode 100644 index 0000000..bb6608c --- /dev/null +++ b/webapp/candysorter/resources/models/twist/classifier_initial/checkpoint @@ -0,0 +1,2 @@ +model_checkpoint_path: "model.ckpt-773600" +all_model_checkpoint_paths: "model.ckpt-773600" diff --git a/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.data-00000-of-00001 b/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.data-00000-of-00001 new file mode 100644 index 0000000..1c7e0d8 Binary files /dev/null and b/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.data-00000-of-00001 differ diff --git a/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.index b/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.index new file mode 100644 index 0000000..c8105ce Binary files /dev/null and b/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.index differ diff --git a/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.meta b/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.meta new file mode 100644 index 0000000..92a010d Binary files /dev/null and b/webapp/candysorter/resources/models/twist/classifier_initial/model.ckpt-773600.meta differ diff --git a/webapp/candysorter/resources/models/twist/classifier_initial/params.json b/webapp/candysorter/resources/models/twist/classifier_initial/params.json new file mode 100644 index 0000000..e05bc5d --- /dev/null +++ b/webapp/candysorter/resources/models/twist/classifier_initial/params.json @@ -0,0 +1 @@ +{"labels": ["banan", "caramel", "chocolate_toffee", "cocos", "daim", "eclairs", "fransk_nougat", "golden_toffee", "japp", "lakris", "marsipan", "nougat_crisp", "nutty"], "features_size": 2048, "hidden_size": 7} \ No newline at end of file diff --git a/webapp/candysorter/static/css/anim.css b/webapp/candysorter/static/css/anim.css index 0c32909..a19040e 100644 --- a/webapp/candysorter/static/css/anim.css +++ b/webapp/candysorter/static/css/anim.css @@ -25,6 +25,13 @@ opacity: 0; transform: scale(0); } } +/* tran loaded */ +.mode-tran-loaded .speech{ opacity: 0; transform: translate(-100px, 0); } +.mode-tran-finish .tran{opacity: 0; transform: translate(-100px, 0);} +.general-header {opacity: 0;} +.tran-word { opacity: 0; transform: translate(100px, 0); transition: none; } +.mode-tran-loaded .general-header {opacity: 1;} +.mode-tran-loaded .tran-word { opacity: 1; transform: translate(0, 0); transition: all .4s; } /* nl loaded */ .nl-inner { transition: none; } .nl-word { opacity: 0; transform: translate(100px, 0); transition: none; } @@ -40,30 +47,45 @@ transform: scale(0); .mode-nl-repeat .nl-inner { opacity: 0; transition: all .4s; } /* nl */ .nl { transition: all .3s; } -.mode-force-start .nl { opacity: 0; transform: scale(0.5); } +.mode-force-start .nl { opacity: 0; transform: scale(0.5); } /* Hide nl when force starts */ /* force */ .force { transition: all .3s; } -.mode-plot-end .force { opacity: 0; transform: scale(0); } +.mode-plot-end .force { opacity: 0; transform: scale(0); } /* Hide force when plot is over */ /* plot */ .plot dd { transform: scale(0); transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275); ; } .mode-plot-start .plot dd { transform: scale(1); } .mode-plot-end .plot dd { transform: scale(0); transition-delay: 0s!important; } -.mode-plot-end .plot dd.nearest { transform: scale(5); opacity: 1; transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } +/*.mode-plot-end .plot dd.nearest { transform: scale(5); opacity: 1; transition: all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } +*/ .plot dd { animation: plot 1s infinite alternate; } @keyframes plot { 0% { -opacity: 0.6; -} -50%, 100% { -opacity: 0.8; -} + opacity: 0.6; + } + 50%, 100% { + opacity: 0.8; + } } .mode-stat-end .plot dd { opacity: 0.8; animation: none; } /* cam */ .cam { opacity: 0; transform: scale(0.8); transition: all .3s; } .mode-cam-start .cam { opacity: 1; transform: scale(1); } +.mode-cam-start .delay {opacity: 0; } +.mode-cam-start text { opacity: 0; } .mode-cam-start .plot dd.nearest { opacity: 0; transform: scale(0); } -.mode-cam-start polygon { animation: cam 100s linear forwards; } +.mode-cam-start polygon { animation: cam 200s linear forwards; } + +.mode-cam-mid .delay {opacity: 1; transform: scale(1); transition-delay: 1s!important; transition-duration: 1s;} +.mode-cam-mid text { opacity: 1; transform: scale(1); transition-delay: 1s!important; transition-duration: 1s;} + +.mode-cam-end circle {opacity: 0; transform: scale(0); transition-duration: .5s;} +.mode-cam-end text { opacity: 0; transform: scale(0); transition-duration: .5s;} +.mode-cam-end polygon { opacity: 0; transform: scale(0); transition-duration: .5s;} + +.mode-cam-finished polygon { opacity: 1; transform: scale(1); transition-duration: 1s;} +.mode-cam-finished text { opacity: 1; transform: scale(1); transition-duration: 1s;} +.mode-cam-finished circle { opacity: 1; transform: scale(1); transition-duration: 1s;} + @keyframes cam { 0% { stroke-dashoffset: 10000; @@ -73,15 +95,10 @@ stroke-dashoffset: 0; } } /* thanks */ -.thanks { height: 0; } -.thanks div { opacity: 0; transform: translate(100px, 0); transition: all .3s; } +.thanks { opacity: 0; } .mode-thanks-start .thanks { height: 100%; } -.mode-thanks-start .cam { opacity: 0; } -.mode-thanks-start .thanks div:nth-child(1) { opacity: 1; transform: translate(0, 0); } -.mode-thanks-end .thanks div:nth-child(1) { opacity: 0; transform: translate(-100px, 0); } -.mode-thanks-end .thanks div:nth-child(2) { opacity: 1; transform: translate(0, 0); } -.mode-thanks-btn .thanks div:nth-child(2) { opacity: 0; transform: translate(-100px, 0); } -.mode-thanks-btn .thanks div:nth-child(3) { opacity: 1; transform: translate(0, 0); } +.mode-thanks-start .cam { opacity: 0; transform: scale(0); transition-duration: .5s;} +.mode-thanks-start .thanks { opacity: 1; transform: translate(0, 0); } /* sorry */ .sorry { height: 0; opacity: 0; } .mode-sorry-p .speech, @@ -162,3 +179,72 @@ stroke-dashoffset: 0; .mode-sorry-t-start .cap, .mode-sorry-t-start .train { height: 0; opacity: 0; } .mode-sorry-t-end .sorry { height: 100%; opacity: 1; } + + +@keyframes blinking { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes hand { + 0% { + bottom: 10%; + right: 35%; + opacity:0; + transform: translatex(350px) translatey(50px); + -webkit-animation-timing-function: ease-in; + animation-delay: 5s; + } + 45% { + transform: translatex(-100px) translatey(-120px); + opacity:.6; + -webkit-animation-timing-function: ease-out; + } + 55%{ + transform: perspective(500px) translatex(-100px) translatey(-120px) translateZ(-50px); + opacity: .8; + } + 65% { + transform: translatex(-100px) translatey(-120px); + opacity: .8; + } + 75%{ + opacity: .5; + } + 100% { + bottom:10%; + right: 35%; + opacity: 0; + transform: translatex(350px) translatey(50px); + } +} + +@keyframes blink { + + 0% { + opacity: .2; + } + 20% { + opacity: 1; + } + 100% { + opacity: .2; + } +} + +/* PROGRESS RADIAL */ +@keyframes progress { + from { + stroke-dashoffset: 109.9; + } + to { + stroke-dashoffset: 0; + } +} \ No newline at end of file diff --git a/webapp/candysorter/static/css/settings.css b/webapp/candysorter/static/css/settings.css new file mode 100644 index 0000000..5062f28 --- /dev/null +++ b/webapp/candysorter/static/css/settings.css @@ -0,0 +1,164 @@ +@charset "UTF-8"; +/* all */ +html, +body { margin:0; width: 100%; height: 100%; color: #003459; font-family: "Open Sans"; font-weight: 300; line-height: 1.5; background: #003459; -webkit-font-smoothing: antialiased; } + +input { font-size: 100%; } +h1 { color: #003459; font-size: 36px; } +p { font-size: 25px; } +a { font-size: 25px; color: #003459; text-decoration: underline; } +* { box-sizing: border-box; } + +/* Custom Scrollbar */ +::-webkit-scrollbar { width: 10px;} +::-webkit-scrollbar-track { box-shadow: inset 0 0 5px grey; border-radius: 5px;} +::-webkit-scrollbar-thumb { background: #29cff5; border-radius: 5px; } +::-webkit-scrollbar-thumb:hover { background: #2783a7; } + +/*main content container*/ +.container { padding: 50px; background-color: white; width: 50%; min-width: 750px; margin: 25px auto; border-radius: 10px; } +.header { position: absolute; right: 8px; top:10px; height: fit-content; } + +/* status icons container */ +.flex-container { display: flex; justify-content: space-around; } +.flex-container img { height: 70px; justify-content: center; } +.flex-container p { justify-content: center; height: 20px; margin: 0; font-size: 25px; } +.flex-container > div { + height: 150px; + border-radius: 20px; + display: block; + background-color: #7B7B7B; + width: 200px; + text-align: center; + font-size: 30px; + align-content: center; + padding: 20px 0; +} +/*Settings container*/ +.settings { margin-top: 50px; } +.settings-lang { font-size: 25px; } + +/*Selection container*/ +.grid-container { + display: grid; + grid-template-columns: 250px auto; + grid-template-rows: auto auto; + grid-gap: 10px; +} +.grid-container > div { + font-size: 25px; + margin: 0; +} + +.grid-container p { + margin: 0; +} + +/*Dropdown select*/ +.select { + position: relative; + display: inline-block; + margin-bottom: 15px; + width: 100%; +} +.select select { + font-family: 'Open Sans'; + font-size: 20px; + display: inline-block; + width: 300px; + cursor: pointer; + padding: 10px 15px; + outline: 0; + border: 0px solid #000000; + border-radius: 0px; + background: #E6E6E6; + color: #7B7B7B; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.select select::-ms-expand { + display: none; +} +.select select:hover, +.select select:focus { + color: #000000; + background: #CCCCCC; +} +.select select:disabled { + opacity: 0.5; + pointer-events: none; +} +.select_arrow { + position: absolute; + top: 16px; + left: 280px; /*BAD FIX, depends on select width*/ + pointer-events: none; + border-style: solid; + border-width: 8px 5px 0px 5px; + border-color: #7B7B7B transparent transparent transparent; +} + +.select select:disabled ~ .select_arrow { + border-top-color: #CCCCCC; +} + +/*Editing text*/ +.edit { margin: 0 auto; justify-content: center; } +.edit textarea {height: 100px; width: 100%; font-family: "Open Sans"; font-size: 21px; } + +.sendbutton { + display: inline-block; + padding: 5px 20px; + font-size: 24px; + cursor: pointer; + text-align: center; + text-decoration: none; + outline: none; + color: #fff; + background-color: #29cff5; + border: none; + border-radius: 10px; +} + +.sendbutton:hover {background-color: #29cff5} + +.sendbutton:active { + background-color: #29cff5; + box-shadow: 0 5px #666; + transform: translateY(4px); +} +/*Timeouts container*/ +.timeouts-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-around; +} +.timeout-setting { + justify-content: space-around; + text-align: center; + align-content: center; + align-items: center; + max-width: 30%; +} + +.timeout-setting img { + height: 150px; + border-radius: 10px; + display: block; + margin: auto; +} + +.timeout-setting input { + margin-top: 10px; + max-width: 80%; + font-family: "Open Sans"; +} + +.timeouts-container p { + margin: 10px; +} + +/*Help container*/ +.help { margin-top: 50px; } diff --git a/webapp/candysorter/static/css/style.css b/webapp/candysorter/static/css/style.css index 42c6d28..991551b 100644 --- a/webapp/candysorter/static/css/style.css +++ b/webapp/candysorter/static/css/style.css @@ -2,76 +2,152 @@ /* all */ html, body { width: 100%; height: 100%; } -body { color: #fff; font-family: "Roboto", sans-serif; font-weight: 300; line-height: 1.5; background: #222; -webkit-font-smoothing: antialiased; } +/*body { color: #fff; font-family: "Roboto", sans-serif; font-weight: 300; line-height: 1.5; background: #003459; -webkit-font-smoothing: antialiased; } */ +body { color: #fff; font-family: "Open Sans"; font-weight: 300; line-height: 1.5; background: #003459; -webkit-font-smoothing: antialiased; } + input { font-size: 100%; } p { margin: 30px 0; } a { color: #fff; text-decoration: none; } svg { vertical-align: top; } * { box-sizing: border-box; } /* parts */ -.btn { display: inline-block; font-size: 24px; font-weight: 300; width: 270px; height: 60px; line-height: 60px; border-radius: 3px; background: #ef5350; text-align: center; } -.btn-g { background: #666; } +.btn { display: inline-block; font-size: 24px; font-weight: 300; width: 270px; height: 60px; line-height: 60px; border-radius: 3px; background: #29cff5; text-align: center; } +.btn-g { background: #49bca1; } +.btn-l { background: none;} +.general-header { display:table-cell; vertical-align:middle; text-align:center; position: absolute; left:50px; top: 50px; align-content: center; } +.pauseIcon { position: absolute; bottom: 40px; right: 40px; z-index: 10;} +.pauseIcon img { position: absolute; right:50%; bottom: 50%; height: 35px; transform: translate(50%, 50%); } +/* PROGRESS RADIAL */ +.progress { + transform: rotate(-90deg); + fill: none; + stroke: #29cff5; + stroke-width: 3; +} + +.progress_value { + stroke-dasharray: 109.9; /* 2*pi*r */ + stroke-dashoffset: 109.9; + fill: none; + stroke: #29cff5; + stroke-width: 3; + +} + +.progress_complete { + stroke-dashoffset: 0; +} + + /* color */ -.label-0 circle { fill: rgba(0,0,0,0); } -.label-1 circle { fill: #ef5350; } -.label-2 circle { fill: #42A5F5; } -.label-3 circle { fill: #66BB6A; } -.label-4 circle { fill: #FFCA28; } +.label-0 circle { fill: rgba(0,0,0,0); } /* Invisible circle linking all force circles */ +.label-1 circle { fill: #ff5f63; } +.label-2 circle { fill: #FF7C00; } +.label-3 circle { fill: #49bca1; } +.label-4 circle { fill: #fed546; } .label-5 circle { fill: #AB47BC; } .label-6 circle { fill: #26A69A; } .label-7 circle { fill: #FF7043; } .label-8 circle { fill: #EC407A; } .label-9 circle { fill: #7E57C2; } .label-10 circle { fill: #FFEE58; } -dd.label-1 { background: #ef5350; } -dd.label-2 { background: #42A5F5; } -dd.label-3 { background: #66BB6A; } -dd.label-4 { background: #FFCA28; } +.label-11 circle { fill: #49D33B; } +.label-12 circle { fill: #FF7C00; } +.label-13 circle { fill: #7E57C2; } +.label-14 circle { fill: #FF577E; } +.label-15 circle { fill: #A931D8; } +dd.label-1 { background: #ff5f63; } +dd.label-2 { background: #29cff5; } +dd.label-3 { background: #49bca1; } +dd.label-4 { background: #fed546; } dd.label-5 { background: #AB47BC; } dd.label-6 { background: #26A69A; } dd.label-7 { background: #FF7043; } dd.label-8 { background: #EC407A; } dd.label-9 { background: #7E57C2; } dd.label-10 { background: #FFEE58; } -polygon.label-1 { stroke: #ef5350; } -polygon.label-2 { stroke: #42A5F5; } -polygon.label-3 { stroke: #66BB6A; } -polygon.label-4 { stroke: #FFCA28; } +polygon.label-1 { stroke: #ff5f63; } +polygon.label-2 { stroke: #29cff5; } +polygon.label-3 { stroke: #49bca1; } +polygon.label-4 { stroke: #fed546; } polygon.label-5 { stroke: #AB47BC; } polygon.label-6 { stroke: #26A69A; } polygon.label-7 { stroke: #FF7043; } polygon.label-8 { stroke: #EC407A; } polygon.label-9 { stroke: #7E57C2; } polygon.label-10 { stroke: #FFEE58; } +polygon.label-11 { stroke: #49D33B; } +polygon.label-12 { stroke: #FF7C00; } +polygon.label-13 { stroke: #33ECE7; } +polygon.label-14 { stroke: #FF577E; } +polygon.label-15 { stroke: #A931D8; } +circle.label-1 { fill: #ff5f63; } +circle.label-2 { fill: #29cff5; } +circle.label-3 { fill: #49bca1; } +circle.label-4 { fill: #fed546; } +circle.label-5 { fill: #AB47BC; } +circle.label-6 { fill: #26A69A; } +circle.label-7 { fill: #FF7043; } +circle.label-8 { fill: #EC407A; } +circle.label-9 { fill: #7E57C2; } +circle.label-10 { fill: #FFEE58; } +circle.label-11 { fill: #49D33B; } +circle.label-12 { fill: #FF7C00; } +circle.label-13 { fill: #33ECE7; } +circle.label-14 { fill: #FF577E; } +circle.label-15 { fill: #A931D8; } /* speech */ .speech { position: absolute; width: 100%; height: 100%; font-size: 36px; font-weight: 100; overflow: hidden; } -.speech-in { display: flex; justify-content: center; align-items: center; position: absolute; width: 100%; height: 100%; z-index: 1; } -.speech-mic { width: 120px; height: 120px; padding: 40px; border-radius: 100%; border: solid 0 #444; background: #333; cursor: pointer; box-sizing: content-box; -webkit-tap-highlight-color: rgba(0,0,0,0); } +.speech-in {display: flex;justify-content: center;align-items: center;position: absolute;width: 100%;height: 100%;z-index: 2;} +.speech-header { display:table-cell; vertical-align:middle; text-align:center; position: absolute; left:50px; top: 50px; align-content: center; } +.speech-mic { width: 500px; height: 500px; padding: 40px; border-radius: 100%; border: solid 0 #444; background: #29cff5; cursor: pointer; box-sizing: content-box; -webkit-tap-highlight-color: rgba(0,0,0,0); } .speech-mic img { width: 100%; } .speech-out { display: flex; justify-content: center; align-items: center; position: absolute; width: 100%; height: 100%; padding: 50px 100px; } -.speech-lang { position: absolute; right: 0; font-size: 16px; font-weight: 300; width: 50px; height: 50px; line-height: 50px; background: #333; text-align: center; z-index: 2; } -.speech-lang a { display: block; color: #999; height: 100%; } -.speech-footer { position: absolute; bottom: 30px; font-size: 16px; font-weight: 300; width: 100%; text-align: center; z-index: 2; } -.speech-footer a { color: #999; } +.speech-lang { position: absolute; right: 8px; top:15px; font-size: 16px; font-weight: 300; width: 50px; height: 50px; line-height: 50px; text-align: center; z-index: 2; } +/*SKITTEN POINTER-EVENTS WORKAROUND!!!! :O!!!*/ +.speech-footer {pointer-events: none; position: absolute; bottom: 30px; font-size: 16px; font-weight: 300; width: 100%; text-align: center; z-index: 2; } +.speech-footer a { pointer-events: all; color: #999; } +.speech-hand-animation {pointer-events: none;position: absolute;animation: hand 6S;animation-iteration-count: infinite;animation-fill-mode: forwards;fill: white;z-index: 3;} + +.speech-interim {pointer-events: none; position: absolute; bottom: 30px; font-size: 16px; font-weight: 300; width: 100%; text-align: center; z-index: 1; } + +/* translation */ +.tran { display: flex; justify-content: center; align-items: center; position: absolute; width: 100%; height: 100%; overflow: hidden; } +.tran-header{ display:table-cell; vertical-align:middle; text-align:center; position: absolute; top: 30px; align-content: center; } +.tran-word { color: #29cff5; font-size: 75px; font-weight: 100; } /*UI: font size speechTxt*/ +.tran-mask { position: absolute; right: 0; width: 100px; height: 100%; /* background: -webkit-linear-gradient( left, rgba(34,34,34,0) 0%, rgba(34,34,34,1) 100% );*/ } +.tran-footer { position: absolute; bottom: 50px; font-size: 25px; font-weight: 300; width: 100%; text-align: center; z-index: 2; } +.tran-footer img {animation: blinking 1.5s; animation-iteration-count: infinite; } + /* nl */ .nl { display: flex; justify-content: center; align-items: center; position: absolute; width: 100%; height: 100%; overflow: hidden; } .nl-depend { position: relative; height: 20px; } .nl-depend dd { position: absolute; height: 20px; background: url(/static/images/arrow-r.png) right center no-repeat, url(/static/images/arrow-bg.png) center center repeat-x; transform-origin: left; } -.nl-depend dd.left { background: url(/static/images/arrow-l.png) left center no-repeat, url(/static/images/arrow-bg.png) center center repeat-x; transform-origin: right; } +.nl-depend dd.left { background: url(/static/images/arrow-l.png) left center no-repeat, url(/static/images/arrow-bg.png) center center repeat-x; transform-origin: right; } /*UI: Remove gradient background*/ .nl-inner { max-width: 100%; } .nl-syntax { position: relative; white-space: nowrap; overflow: hidden; } -.nl-syntax dl { display: inline-block; color: #ccc; font-size: 14px; letter-spacing: 0.1em; padding: 20px; text-align: center; vertical-align: top; } +.nl-syntax dl { display: inline-block; color: #ccc; font-size: 30px; /*UI: font size word analysis*/ letter-spacing: 0.1em; padding: 20px; text-align: center; vertical-align: top; } .nl-label { text-transform: lowercase; } -.nl-word { color: #fff; font-size: 60px; font-weight: 100; } +.nl-word { color: #29cff5; font-size: 75px; } /*UI: font size speechTxt*/ .nl-tag { margin-bottom: 20px; } -.nl-mask { position: absolute; right: 0; width: 100px; height: 100%; background: -webkit-linear-gradient( left, rgba(34,34,34,0) 0%, rgba(34,34,34,1) 100% ); } +.nl-mask { position: absolute; right: 0; width: 100px; height: 100%; /* background: -webkit-linear-gradient( left, rgba(34,34,34,0) 0%, rgba(34,34,34,1) 100% );*/ } +.nl-footer { position: absolute; bottom: 50px; font-size: 25px; font-weight: 300; width: 100%; text-align: center; z-index: 2; } +/*.nl-footer img {animation: blinking 1.5s; animation-iteration-count: infinite; } */ + +.working span { animation-name: blink; animation-duration: 1.4s; animation-iteration-count: infinite; animation-fill-mode: both; } +.working span:nth-child(2) { animation-delay: .2s; } +.working span:nth-child(3) { animation-delay: .4s; } + /* force */ .force { position: absolute; width: 100%; height: 100%; overflow: hidden; } .force svg { position: absolute; } .force line { stroke: rgba(255,255,255,0.3); stroke-width: 1; } -.force text { fill: #fff; font-size: 36px; font-weight: 100; text-anchor: middle; alignment-baseline: middle; } -.force circle { opacity: 0.8; } -.force-txt { position: absolute; color: #fff; font-size: 48px; font-weight: 100; max-width: 80%; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } +.force text { fill: #003459; font-size: 20px; font-weight: 100; text-anchor: middle; alignment-baseline: middle; } +.force circle { opacity: 1; } +.force-txt { position: absolute; color: #29cff5; font-size: 75px; font-weight: 100; max-width: 80%; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; z-index: 2;} +.force-footer { position: absolute; bottom: 35%; font-size: 25px; font-weight: 300; width: 100%; text-align: center; z-index: 2; } + + /* plot */ .plot { position: absolute; width: 100%; height: 100%; overflow: hidden; } .plot dd { position: absolute; width: 80px; height: 80px; margin: -40px 0 0 -40px; border-radius: 100%; } @@ -80,11 +156,21 @@ polygon.label-10 { stroke: #FFEE58; } .cam { display: flex; justify-content: center; align-items: center; position: absolute; width: 100%; height: 100%; overflow: hidden; } .cam-img { background-size: cover; } .cam svg { width: 100%; } -.cam polygon { fill: rgba(0,0,0,0); stroke-width: 2; stroke-dasharray: 10; } +.cam polygon { fill: rgba(0,0,0,0); /*stroke: #ff5f63; */ stroke-width: 10px; stroke-dasharray: 20 10; } /*ALWAYS USE RED COLOR AS PICUKUP OUTLINE*/ +.cam line { stroke: #D12F33; stroke-width: 10px; opacity: 0.6; } +.cam text { fill: #003459; font-size: 20px; font-family: "Open Sans"; font-weight: 100; text-anchor: middle; alignment-baseline: middle; z-index: 3; } +.cam circle { opacity: 1; z-index: 3; } +.cam-footer { position: absolute; bottom: 50px; font-size: 25px; font-weight: 300; width: 100%; text-align: center; z-index: 2; } + /* thanks */ .thanks { position: absolute; width: 100%; height: 100%; font-size: 48px; font-weight: 100; overflow: hidden; } .thanks div { display: flex; justify-content: center; align-items: center; position: absolute; width: 100%; height: 100%; } .thanks .btn { margin: 15px; } +.thanks img { position: absolute; top: 75px;} +.thanks p {color: #29cff5; font-size: 70px; position: center; top: 25%; text-align: center; align-content: center; justify-content: center;width: 70%;} +.thanks .endtext { display: block;} +.thanks-header { display:table-cell; vertical-align:middle; text-align:center; position: absolute; left:50px; top: 50px; align-content: center;} + /* sorry */ .sorry { position: absolute; width: 100%; height: 100%; font-size: 48px; font-weight: 100; overflow: hidden; } .sorry-inner { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; text-align: center; } @@ -125,7 +211,7 @@ polygon.label-10 { stroke: #FFEE58; } .stat { position: absolute; width: 100%; height: 100%; overflow: hidden; } .stat-btn { position: absolute; top: 20px; right: 20px; } .stat-btn .btn { font-size: 18px; width: 160px; height: 40px; line-height: 40px; margin: 10px; } -.stat-item { display: -webkit-flex; dsiplay: flex; position: relative; width: 100%; height: 50%; overflow: hidden; } +.stat-item { display: -webkit-flex; display: flex; position: relative; width: 100%; height: 50%; overflow: hidden; } .stat-item:first-child { border-bottom: solid 1px #444; } .stat-left { display: flex; justify-content: center; align-items: center; font-size: 21px; width: 200px; height: 100%; background: #333; text-align: center; } .stat-right { -webkit-flex: 1; flex: 1; position: relative; height: 100%; } diff --git a/webapp/candysorter/static/images/branding/Computas_logo_liggende_blue.png b/webapp/candysorter/static/images/branding/Computas_logo_liggende_blue.png new file mode 100644 index 0000000..6c209ce Binary files /dev/null and b/webapp/candysorter/static/images/branding/Computas_logo_liggende_blue.png differ diff --git a/webapp/candysorter/static/images/branding/Computas_logo_liggende_hvit.png b/webapp/candysorter/static/images/branding/Computas_logo_liggende_hvit.png new file mode 100644 index 0000000..b1bf91f Binary files /dev/null and b/webapp/candysorter/static/images/branding/Computas_logo_liggende_hvit.png differ diff --git a/webapp/candysorter/static/images/branding/Computas_logoonly_liggende_blue.png b/webapp/candysorter/static/images/branding/Computas_logoonly_liggende_blue.png new file mode 100644 index 0000000..577a334 Binary files /dev/null and b/webapp/candysorter/static/images/branding/Computas_logoonly_liggende_blue.png differ diff --git a/webapp/candysorter/static/images/branding/Computas_symbol_blue.png b/webapp/candysorter/static/images/branding/Computas_symbol_blue.png new file mode 100644 index 0000000..1c793b0 Binary files /dev/null and b/webapp/candysorter/static/images/branding/Computas_symbol_blue.png differ diff --git a/webapp/candysorter/static/images/branding/Computas_symbol_hvit.png b/webapp/candysorter/static/images/branding/Computas_symbol_hvit.png new file mode 100644 index 0000000..afeab52 Binary files /dev/null and b/webapp/candysorter/static/images/branding/Computas_symbol_hvit.png differ diff --git a/webapp/candysorter/static/images/branding/Computas_textonly_liggende_blue.png b/webapp/candysorter/static/images/branding/Computas_textonly_liggende_blue.png new file mode 100644 index 0000000..4d507d2 Binary files /dev/null and b/webapp/candysorter/static/images/branding/Computas_textonly_liggende_blue.png differ diff --git a/webapp/candysorter/static/images/branding/arrow-circle-left-solid.png b/webapp/candysorter/static/images/branding/arrow-circle-left-solid.png new file mode 100644 index 0000000..ffc1250 Binary files /dev/null and b/webapp/candysorter/static/images/branding/arrow-circle-left-solid.png differ diff --git a/webapp/candysorter/static/images/branding/camera-solid.png b/webapp/candysorter/static/images/branding/camera-solid.png new file mode 100644 index 0000000..8e4cc6b Binary files /dev/null and b/webapp/candysorter/static/images/branding/camera-solid.png differ diff --git a/webapp/candysorter/static/images/branding/check-circle-green.png b/webapp/candysorter/static/images/branding/check-circle-green.png new file mode 100644 index 0000000..a00d888 Binary files /dev/null and b/webapp/candysorter/static/images/branding/check-circle-green.png differ diff --git a/webapp/candysorter/static/images/branding/cog-solid_blue.png b/webapp/candysorter/static/images/branding/cog-solid_blue.png new file mode 100644 index 0000000..6946f04 Binary files /dev/null and b/webapp/candysorter/static/images/branding/cog-solid_blue.png differ diff --git a/webapp/candysorter/static/images/branding/computas_logo_liggende_egen.png b/webapp/candysorter/static/images/branding/computas_logo_liggende_egen.png new file mode 100644 index 0000000..f9a8b32 Binary files /dev/null and b/webapp/candysorter/static/images/branding/computas_logo_liggende_egen.png differ diff --git a/webapp/candysorter/static/images/branding/mic-blue.png b/webapp/candysorter/static/images/branding/mic-blue.png new file mode 100644 index 0000000..f3f5094 Binary files /dev/null and b/webapp/candysorter/static/images/branding/mic-blue.png differ diff --git a/webapp/candysorter/static/images/branding/mic-white.png b/webapp/candysorter/static/images/branding/mic-white.png new file mode 100644 index 0000000..734b48f Binary files /dev/null and b/webapp/candysorter/static/images/branding/mic-white.png differ diff --git a/webapp/candysorter/static/images/branding/mic-white2.png b/webapp/candysorter/static/images/branding/mic-white2.png new file mode 100644 index 0000000..05e042d Binary files /dev/null and b/webapp/candysorter/static/images/branding/mic-white2.png differ diff --git a/webapp/candysorter/static/images/branding/microphone-white.png b/webapp/candysorter/static/images/branding/microphone-white.png new file mode 100644 index 0000000..28e9fbf Binary files /dev/null and b/webapp/candysorter/static/images/branding/microphone-white.png differ diff --git a/webapp/candysorter/static/images/branding/robot-solid.png b/webapp/candysorter/static/images/branding/robot-solid.png new file mode 100644 index 0000000..3cbbe7a Binary files /dev/null and b/webapp/candysorter/static/images/branding/robot-solid.png differ diff --git a/webapp/candysorter/static/images/branding/wifi-solid.png b/webapp/candysorter/static/images/branding/wifi-solid.png new file mode 100644 index 0000000..8056440 Binary files /dev/null and b/webapp/candysorter/static/images/branding/wifi-solid.png differ diff --git a/webapp/candysorter/static/images/cam.png b/webapp/candysorter/static/images/cam.png new file mode 100644 index 0000000..c33b014 Binary files /dev/null and b/webapp/candysorter/static/images/cam.png differ diff --git a/webapp/candysorter/static/images/force.png b/webapp/candysorter/static/images/force.png new file mode 100644 index 0000000..45dbb9c Binary files /dev/null and b/webapp/candysorter/static/images/force.png differ diff --git a/webapp/candysorter/static/images/fwd.png b/webapp/candysorter/static/images/fwd.png new file mode 100644 index 0000000..e866bb2 Binary files /dev/null and b/webapp/candysorter/static/images/fwd.png differ diff --git a/webapp/candysorter/static/images/fwdicon.png b/webapp/candysorter/static/images/fwdicon.png new file mode 100644 index 0000000..1288e7d Binary files /dev/null and b/webapp/candysorter/static/images/fwdicon.png differ diff --git a/webapp/candysorter/static/images/hand-point-up-regular.svg b/webapp/candysorter/static/images/hand-point-up-regular.svg new file mode 100644 index 0000000..6c6b334 --- /dev/null +++ b/webapp/candysorter/static/images/hand-point-up-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webapp/candysorter/static/images/hand-pointer-regular.svg b/webapp/candysorter/static/images/hand-pointer-regular.svg new file mode 100644 index 0000000..c9e895a --- /dev/null +++ b/webapp/candysorter/static/images/hand-pointer-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webapp/candysorter/static/images/hand-pointer-regular_g.png b/webapp/candysorter/static/images/hand-pointer-regular_g.png new file mode 100644 index 0000000..c01e3ed Binary files /dev/null and b/webapp/candysorter/static/images/hand-pointer-regular_g.png differ diff --git a/webapp/candysorter/static/images/hand-pointer-regular_y.png b/webapp/candysorter/static/images/hand-pointer-regular_y.png new file mode 100644 index 0000000..b4b336a Binary files /dev/null and b/webapp/candysorter/static/images/hand-pointer-regular_y.png differ diff --git a/webapp/candysorter/static/images/hand.png b/webapp/candysorter/static/images/hand.png new file mode 100644 index 0000000..090ee51 Binary files /dev/null and b/webapp/candysorter/static/images/hand.png differ diff --git a/webapp/candysorter/static/images/nl.png b/webapp/candysorter/static/images/nl.png new file mode 100644 index 0000000..dd7bd5e Binary files /dev/null and b/webapp/candysorter/static/images/nl.png differ diff --git a/webapp/candysorter/static/images/pause.png b/webapp/candysorter/static/images/pause.png new file mode 100644 index 0000000..063dba6 Binary files /dev/null and b/webapp/candysorter/static/images/pause.png differ diff --git a/webapp/candysorter/static/images/pauseicon.png b/webapp/candysorter/static/images/pauseicon.png new file mode 100644 index 0000000..9b8ee9b Binary files /dev/null and b/webapp/candysorter/static/images/pauseicon.png differ diff --git a/webapp/candysorter/static/images/placeholder.jpg b/webapp/candysorter/static/images/placeholder.jpg new file mode 100644 index 0000000..b9b08a4 Binary files /dev/null and b/webapp/candysorter/static/images/placeholder.jpg differ diff --git a/webapp/candysorter/static/images/select.png b/webapp/candysorter/static/images/select.png new file mode 100644 index 0000000..5f02373 Binary files /dev/null and b/webapp/candysorter/static/images/select.png differ diff --git a/webapp/candysorter/static/images/speech_cloud.png b/webapp/candysorter/static/images/speech_cloud.png new file mode 100644 index 0000000..2fc0bba Binary files /dev/null and b/webapp/candysorter/static/images/speech_cloud.png differ diff --git a/webapp/candysorter/static/images/translate.png b/webapp/candysorter/static/images/translate.png new file mode 100644 index 0000000..4ce5e8c Binary files /dev/null and b/webapp/candysorter/static/images/translate.png differ diff --git a/webapp/candysorter/static/js/config.js b/webapp/candysorter/static/js/config.js new file mode 100644 index 0000000..b121e06 --- /dev/null +++ b/webapp/candysorter/static/js/config.js @@ -0,0 +1,142 @@ +class FycConfig { + // Key names + constructor() { + // Local storage keys + this.speechLangKey = "speech_lang"; + this.uiLangKey = "ui_lang"; + this.nlLangKey = "nl_lang"; + this.endTextKey = "end_text"; + this.timeoutsKey = "timeouts"; + this.apiUrlsKey = "api_urls"; + + // Defaults + this.speechLangDefault = { + stream: 'nb-NO', + translate: 'no', + } + this.uiLangDefault= 'no'; + this.language; + this.nlLangDefault = 'en'; + this.endTextDefault = "Takk for at du kom innom!
Hold gjerne kontakten på computas.no"; + this.timeoutDefaults = { + tranSec: 5000, + nlSec: 5000, + forceSec: 5000, + camSec: 7000, + selectSec: 8000, + } + + this.apiUrlDefaults = { + morUrl: "/api/morphs", + simUrl: "/api/similarities", + pickUrl: "/api/pickup", + tranUrl: "/api/translate", + camStatusUrl: "/api/status/camera", + robStatusUrl: "/api/status/robot", + } + + this.setLanguage(); + + } + + setLanguage() { + $.ajax({ + url: '/static/lang/' + this.getUIlang() + '.json', + dataType: 'json', async: false, dataType: 'json', + success: function (lang) { + console.log(lang); + this.language = lang; + + $(".localize").each(function(i) { + let localized_text = lang[$(this).data('lang')]; + console.log("Changing text on ", this, " to ", localized_text); + $(this).text(localized_text) + }) + }, + error: function(error) { + console.log(error); + } + } + ); + } + + // Speech Language + getSpeechLang() { + return this.getJsonOrDefault(this.speechLangKey, this.speechLangDefault); + } + + setSpeechLang(lang) { + localStorage.setItem(this.speechLangKey, JSON.stringify(lang)); + } + + // UI Language + getUIlang() { + return this.getOrDefault(this.uiLangKey, this.uiLangDefault); + } + + setUIlang(lang) { + localStorage.setItem(this.uiLangKey, lang); + this.setLanguage(); + } + + + // NL Language (used by Natural Language API and Word2Vec) + getNlLang() { + return this.getOrDefault(this.nlLangKey, this.nlLangDefault); + } + + // Endtext + getEndText() { + return this.getOrDefault(this.endTextKey, this.endTextDefault); + } + + setEndText(text) { + localStorage.setItem(this.endTextKey, text); + } + + // Timeouts + getTransitionTimeouts(){ + return this.getJsonOrDefault(this.timeoutsKey, this.timeoutDefaults); + } + + setTransitionTimeouts(transKey, value) { + let currentValue = this.getTransitionTimeouts(); + let newValue = { + ...currentValue, + [transKey]: value, + + } + localStorage.setItem(this.timeoutsKey, JSON.stringify(newValue)); + } + + + // API + getApiEndpoints() { + return this.getJsonOrDefault(this.apiUrlsKey, this.apiUrlDefaults); + } + + + // Reset + reset() { + localStorage.clear(); + } + + // Helpers + getOrDefault(key, fallback) { + var value = localStorage.getItem(key); + if (value == null) { + return fallback; + } else { + return value; + } + } + + getJsonOrDefault(key, fallback) { + var value = localStorage.getItem(key); + if (value == null) { + return fallback; + } else { + return JSON.parse(value); + } + } +} \ No newline at end of file diff --git a/webapp/candysorter/static/js/he.js b/webapp/candysorter/static/js/he.js new file mode 100644 index 0000000..3cf4292 --- /dev/null +++ b/webapp/candysorter/static/js/he.js @@ -0,0 +1,342 @@ +/*! https://mths.be/he v1.1.1 by @mathias | MIT license */ +;(function(root) { + + // Detect free variables `exports`. + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module`. + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root`. + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + // All astral symbols. + var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; + // All ASCII symbols (not just printable ASCII) except those listed in the + // first column of the overrides table. + // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides + var regexAsciiWhitelist = /[\x01-\x7F]/g; + // All BMP symbols that are not ASCII newlines, printable ASCII symbols, or + // code points listed in the first column of the overrides table on + // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides. + var regexBmpWhitelist = /[\x01-\t\x0B\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g; + + var regexEncodeNonAscii = /<\u20D2|=\u20E5|>\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g; + var encodeMap = {'\xAD':'shy','\u200C':'zwnj','\u200D':'zwj','\u200E':'lrm','\u2063':'ic','\u2062':'it','\u2061':'af','\u200F':'rlm','\u200B':'ZeroWidthSpace','\u2060':'NoBreak','\u0311':'DownBreve','\u20DB':'tdot','\u20DC':'DotDot','\t':'Tab','\n':'NewLine','\u2008':'puncsp','\u205F':'MediumSpace','\u2009':'thinsp','\u200A':'hairsp','\u2004':'emsp13','\u2002':'ensp','\u2005':'emsp14','\u2003':'emsp','\u2007':'numsp','\xA0':'nbsp','\u205F\u200A':'ThickSpace','\u203E':'oline','_':'lowbar','\u2010':'dash','\u2013':'ndash','\u2014':'mdash','\u2015':'horbar',',':'comma',';':'semi','\u204F':'bsemi',':':'colon','\u2A74':'Colone','!':'excl','\xA1':'iexcl','?':'quest','\xBF':'iquest','.':'period','\u2025':'nldr','\u2026':'mldr','\xB7':'middot','\'':'apos','\u2018':'lsquo','\u2019':'rsquo','\u201A':'sbquo','\u2039':'lsaquo','\u203A':'rsaquo','"':'quot','\u201C':'ldquo','\u201D':'rdquo','\u201E':'bdquo','\xAB':'laquo','\xBB':'raquo','(':'lpar',')':'rpar','[':'lsqb',']':'rsqb','{':'lcub','}':'rcub','\u2308':'lceil','\u2309':'rceil','\u230A':'lfloor','\u230B':'rfloor','\u2985':'lopar','\u2986':'ropar','\u298B':'lbrke','\u298C':'rbrke','\u298D':'lbrkslu','\u298E':'rbrksld','\u298F':'lbrksld','\u2990':'rbrkslu','\u2991':'langd','\u2992':'rangd','\u2993':'lparlt','\u2994':'rpargt','\u2995':'gtlPar','\u2996':'ltrPar','\u27E6':'lobrk','\u27E7':'robrk','\u27E8':'lang','\u27E9':'rang','\u27EA':'Lang','\u27EB':'Rang','\u27EC':'loang','\u27ED':'roang','\u2772':'lbbrk','\u2773':'rbbrk','\u2016':'Vert','\xA7':'sect','\xB6':'para','@':'commat','*':'ast','/':'sol','undefined':null,'&':'amp','#':'num','%':'percnt','\u2030':'permil','\u2031':'pertenk','\u2020':'dagger','\u2021':'Dagger','\u2022':'bull','\u2043':'hybull','\u2032':'prime','\u2033':'Prime','\u2034':'tprime','\u2057':'qprime','\u2035':'bprime','\u2041':'caret','`':'grave','\xB4':'acute','\u02DC':'tilde','^':'Hat','\xAF':'macr','\u02D8':'breve','\u02D9':'dot','\xA8':'die','\u02DA':'ring','\u02DD':'dblac','\xB8':'cedil','\u02DB':'ogon','\u02C6':'circ','\u02C7':'caron','\xB0':'deg','\xA9':'copy','\xAE':'reg','\u2117':'copysr','\u2118':'wp','\u211E':'rx','\u2127':'mho','\u2129':'iiota','\u2190':'larr','\u219A':'nlarr','\u2192':'rarr','\u219B':'nrarr','\u2191':'uarr','\u2193':'darr','\u2194':'harr','\u21AE':'nharr','\u2195':'varr','\u2196':'nwarr','\u2197':'nearr','\u2198':'searr','\u2199':'swarr','\u219D':'rarrw','\u219D\u0338':'nrarrw','\u219E':'Larr','\u219F':'Uarr','\u21A0':'Rarr','\u21A1':'Darr','\u21A2':'larrtl','\u21A3':'rarrtl','\u21A4':'mapstoleft','\u21A5':'mapstoup','\u21A6':'map','\u21A7':'mapstodown','\u21A9':'larrhk','\u21AA':'rarrhk','\u21AB':'larrlp','\u21AC':'rarrlp','\u21AD':'harrw','\u21B0':'lsh','\u21B1':'rsh','\u21B2':'ldsh','\u21B3':'rdsh','\u21B5':'crarr','\u21B6':'cularr','\u21B7':'curarr','\u21BA':'olarr','\u21BB':'orarr','\u21BC':'lharu','\u21BD':'lhard','\u21BE':'uharr','\u21BF':'uharl','\u21C0':'rharu','\u21C1':'rhard','\u21C2':'dharr','\u21C3':'dharl','\u21C4':'rlarr','\u21C5':'udarr','\u21C6':'lrarr','\u21C7':'llarr','\u21C8':'uuarr','\u21C9':'rrarr','\u21CA':'ddarr','\u21CB':'lrhar','\u21CC':'rlhar','\u21D0':'lArr','\u21CD':'nlArr','\u21D1':'uArr','\u21D2':'rArr','\u21CF':'nrArr','\u21D3':'dArr','\u21D4':'iff','\u21CE':'nhArr','\u21D5':'vArr','\u21D6':'nwArr','\u21D7':'neArr','\u21D8':'seArr','\u21D9':'swArr','\u21DA':'lAarr','\u21DB':'rAarr','\u21DD':'zigrarr','\u21E4':'larrb','\u21E5':'rarrb','\u21F5':'duarr','\u21FD':'loarr','\u21FE':'roarr','\u21FF':'hoarr','\u2200':'forall','\u2201':'comp','\u2202':'part','\u2202\u0338':'npart','\u2203':'exist','\u2204':'nexist','\u2205':'empty','\u2207':'Del','\u2208':'in','\u2209':'notin','\u220B':'ni','\u220C':'notni','\u03F6':'bepsi','\u220F':'prod','\u2210':'coprod','\u2211':'sum','+':'plus','\xB1':'pm','\xF7':'div','\xD7':'times','<':'lt','\u226E':'nlt','<\u20D2':'nvlt','=':'equals','\u2260':'ne','=\u20E5':'bne','\u2A75':'Equal','>':'gt','\u226F':'ngt','>\u20D2':'nvgt','\xAC':'not','|':'vert','\xA6':'brvbar','\u2212':'minus','\u2213':'mp','\u2214':'plusdo','\u2044':'frasl','\u2216':'setmn','\u2217':'lowast','\u2218':'compfn','\u221A':'Sqrt','\u221D':'prop','\u221E':'infin','\u221F':'angrt','\u2220':'ang','\u2220\u20D2':'nang','\u2221':'angmsd','\u2222':'angsph','\u2223':'mid','\u2224':'nmid','\u2225':'par','\u2226':'npar','\u2227':'and','\u2228':'or','\u2229':'cap','\u2229\uFE00':'caps','\u222A':'cup','\u222A\uFE00':'cups','\u222B':'int','\u222C':'Int','\u222D':'tint','\u2A0C':'qint','\u222E':'oint','\u222F':'Conint','\u2230':'Cconint','\u2231':'cwint','\u2232':'cwconint','\u2233':'awconint','\u2234':'there4','\u2235':'becaus','\u2236':'ratio','\u2237':'Colon','\u2238':'minusd','\u223A':'mDDot','\u223B':'homtht','\u223C':'sim','\u2241':'nsim','\u223C\u20D2':'nvsim','\u223D':'bsim','\u223D\u0331':'race','\u223E':'ac','\u223E\u0333':'acE','\u223F':'acd','\u2240':'wr','\u2242':'esim','\u2242\u0338':'nesim','\u2243':'sime','\u2244':'nsime','\u2245':'cong','\u2247':'ncong','\u2246':'simne','\u2248':'ap','\u2249':'nap','\u224A':'ape','\u224B':'apid','\u224B\u0338':'napid','\u224C':'bcong','\u224D':'CupCap','\u226D':'NotCupCap','\u224D\u20D2':'nvap','\u224E':'bump','\u224E\u0338':'nbump','\u224F':'bumpe','\u224F\u0338':'nbumpe','\u2250':'doteq','\u2250\u0338':'nedot','\u2251':'eDot','\u2252':'efDot','\u2253':'erDot','\u2254':'colone','\u2255':'ecolon','\u2256':'ecir','\u2257':'cire','\u2259':'wedgeq','\u225A':'veeeq','\u225C':'trie','\u225F':'equest','\u2261':'equiv','\u2262':'nequiv','\u2261\u20E5':'bnequiv','\u2264':'le','\u2270':'nle','\u2264\u20D2':'nvle','\u2265':'ge','\u2271':'nge','\u2265\u20D2':'nvge','\u2266':'lE','\u2266\u0338':'nlE','\u2267':'gE','\u2267\u0338':'ngE','\u2268\uFE00':'lvnE','\u2268':'lnE','\u2269':'gnE','\u2269\uFE00':'gvnE','\u226A':'ll','\u226A\u0338':'nLtv','\u226A\u20D2':'nLt','\u226B':'gg','\u226B\u0338':'nGtv','\u226B\u20D2':'nGt','\u226C':'twixt','\u2272':'lsim','\u2274':'nlsim','\u2273':'gsim','\u2275':'ngsim','\u2276':'lg','\u2278':'ntlg','\u2277':'gl','\u2279':'ntgl','\u227A':'pr','\u2280':'npr','\u227B':'sc','\u2281':'nsc','\u227C':'prcue','\u22E0':'nprcue','\u227D':'sccue','\u22E1':'nsccue','\u227E':'prsim','\u227F':'scsim','\u227F\u0338':'NotSucceedsTilde','\u2282':'sub','\u2284':'nsub','\u2282\u20D2':'vnsub','\u2283':'sup','\u2285':'nsup','\u2283\u20D2':'vnsup','\u2286':'sube','\u2288':'nsube','\u2287':'supe','\u2289':'nsupe','\u228A\uFE00':'vsubne','\u228A':'subne','\u228B\uFE00':'vsupne','\u228B':'supne','\u228D':'cupdot','\u228E':'uplus','\u228F':'sqsub','\u228F\u0338':'NotSquareSubset','\u2290':'sqsup','\u2290\u0338':'NotSquareSuperset','\u2291':'sqsube','\u22E2':'nsqsube','\u2292':'sqsupe','\u22E3':'nsqsupe','\u2293':'sqcap','\u2293\uFE00':'sqcaps','\u2294':'sqcup','\u2294\uFE00':'sqcups','\u2295':'oplus','\u2296':'ominus','\u2297':'otimes','\u2298':'osol','\u2299':'odot','\u229A':'ocir','\u229B':'oast','\u229D':'odash','\u229E':'plusb','\u229F':'minusb','\u22A0':'timesb','\u22A1':'sdotb','\u22A2':'vdash','\u22AC':'nvdash','\u22A3':'dashv','\u22A4':'top','\u22A5':'bot','\u22A7':'models','\u22A8':'vDash','\u22AD':'nvDash','\u22A9':'Vdash','\u22AE':'nVdash','\u22AA':'Vvdash','\u22AB':'VDash','\u22AF':'nVDash','\u22B0':'prurel','\u22B2':'vltri','\u22EA':'nltri','\u22B3':'vrtri','\u22EB':'nrtri','\u22B4':'ltrie','\u22EC':'nltrie','\u22B4\u20D2':'nvltrie','\u22B5':'rtrie','\u22ED':'nrtrie','\u22B5\u20D2':'nvrtrie','\u22B6':'origof','\u22B7':'imof','\u22B8':'mumap','\u22B9':'hercon','\u22BA':'intcal','\u22BB':'veebar','\u22BD':'barvee','\u22BE':'angrtvb','\u22BF':'lrtri','\u22C0':'Wedge','\u22C1':'Vee','\u22C2':'xcap','\u22C3':'xcup','\u22C4':'diam','\u22C5':'sdot','\u22C6':'Star','\u22C7':'divonx','\u22C8':'bowtie','\u22C9':'ltimes','\u22CA':'rtimes','\u22CB':'lthree','\u22CC':'rthree','\u22CD':'bsime','\u22CE':'cuvee','\u22CF':'cuwed','\u22D0':'Sub','\u22D1':'Sup','\u22D2':'Cap','\u22D3':'Cup','\u22D4':'fork','\u22D5':'epar','\u22D6':'ltdot','\u22D7':'gtdot','\u22D8':'Ll','\u22D8\u0338':'nLl','\u22D9':'Gg','\u22D9\u0338':'nGg','\u22DA\uFE00':'lesg','\u22DA':'leg','\u22DB':'gel','\u22DB\uFE00':'gesl','\u22DE':'cuepr','\u22DF':'cuesc','\u22E6':'lnsim','\u22E7':'gnsim','\u22E8':'prnsim','\u22E9':'scnsim','\u22EE':'vellip','\u22EF':'ctdot','\u22F0':'utdot','\u22F1':'dtdot','\u22F2':'disin','\u22F3':'isinsv','\u22F4':'isins','\u22F5':'isindot','\u22F5\u0338':'notindot','\u22F6':'notinvc','\u22F7':'notinvb','\u22F9':'isinE','\u22F9\u0338':'notinE','\u22FA':'nisd','\u22FB':'xnis','\u22FC':'nis','\u22FD':'notnivc','\u22FE':'notnivb','\u2305':'barwed','\u2306':'Barwed','\u230C':'drcrop','\u230D':'dlcrop','\u230E':'urcrop','\u230F':'ulcrop','\u2310':'bnot','\u2312':'profline','\u2313':'profsurf','\u2315':'telrec','\u2316':'target','\u231C':'ulcorn','\u231D':'urcorn','\u231E':'dlcorn','\u231F':'drcorn','\u2322':'frown','\u2323':'smile','\u232D':'cylcty','\u232E':'profalar','\u2336':'topbot','\u233D':'ovbar','\u233F':'solbar','\u237C':'angzarr','\u23B0':'lmoust','\u23B1':'rmoust','\u23B4':'tbrk','\u23B5':'bbrk','\u23B6':'bbrktbrk','\u23DC':'OverParenthesis','\u23DD':'UnderParenthesis','\u23DE':'OverBrace','\u23DF':'UnderBrace','\u23E2':'trpezium','\u23E7':'elinters','\u2423':'blank','\u2500':'boxh','\u2502':'boxv','\u250C':'boxdr','\u2510':'boxdl','\u2514':'boxur','\u2518':'boxul','\u251C':'boxvr','\u2524':'boxvl','\u252C':'boxhd','\u2534':'boxhu','\u253C':'boxvh','\u2550':'boxH','\u2551':'boxV','\u2552':'boxdR','\u2553':'boxDr','\u2554':'boxDR','\u2555':'boxdL','\u2556':'boxDl','\u2557':'boxDL','\u2558':'boxuR','\u2559':'boxUr','\u255A':'boxUR','\u255B':'boxuL','\u255C':'boxUl','\u255D':'boxUL','\u255E':'boxvR','\u255F':'boxVr','\u2560':'boxVR','\u2561':'boxvL','\u2562':'boxVl','\u2563':'boxVL','\u2564':'boxHd','\u2565':'boxhD','\u2566':'boxHD','\u2567':'boxHu','\u2568':'boxhU','\u2569':'boxHU','\u256A':'boxvH','\u256B':'boxVh','\u256C':'boxVH','\u2580':'uhblk','\u2584':'lhblk','\u2588':'block','\u2591':'blk14','\u2592':'blk12','\u2593':'blk34','\u25A1':'squ','\u25AA':'squf','\u25AB':'EmptyVerySmallSquare','\u25AD':'rect','\u25AE':'marker','\u25B1':'fltns','\u25B3':'xutri','\u25B4':'utrif','\u25B5':'utri','\u25B8':'rtrif','\u25B9':'rtri','\u25BD':'xdtri','\u25BE':'dtrif','\u25BF':'dtri','\u25C2':'ltrif','\u25C3':'ltri','\u25CA':'loz','\u25CB':'cir','\u25EC':'tridot','\u25EF':'xcirc','\u25F8':'ultri','\u25F9':'urtri','\u25FA':'lltri','\u25FB':'EmptySmallSquare','\u25FC':'FilledSmallSquare','\u2605':'starf','\u2606':'star','\u260E':'phone','\u2640':'female','\u2642':'male','\u2660':'spades','\u2663':'clubs','\u2665':'hearts','\u2666':'diams','\u266A':'sung','\u2713':'check','\u2717':'cross','\u2720':'malt','\u2736':'sext','\u2758':'VerticalSeparator','\u27C8':'bsolhsub','\u27C9':'suphsol','\u27F5':'xlarr','\u27F6':'xrarr','\u27F7':'xharr','\u27F8':'xlArr','\u27F9':'xrArr','\u27FA':'xhArr','\u27FC':'xmap','\u27FF':'dzigrarr','\u2902':'nvlArr','\u2903':'nvrArr','\u2904':'nvHarr','\u2905':'Map','\u290C':'lbarr','\u290D':'rbarr','\u290E':'lBarr','\u290F':'rBarr','\u2910':'RBarr','\u2911':'DDotrahd','\u2912':'UpArrowBar','\u2913':'DownArrowBar','\u2916':'Rarrtl','\u2919':'latail','\u291A':'ratail','\u291B':'lAtail','\u291C':'rAtail','\u291D':'larrfs','\u291E':'rarrfs','\u291F':'larrbfs','\u2920':'rarrbfs','\u2923':'nwarhk','\u2924':'nearhk','\u2925':'searhk','\u2926':'swarhk','\u2927':'nwnear','\u2928':'toea','\u2929':'tosa','\u292A':'swnwar','\u2933':'rarrc','\u2933\u0338':'nrarrc','\u2935':'cudarrr','\u2936':'ldca','\u2937':'rdca','\u2938':'cudarrl','\u2939':'larrpl','\u293C':'curarrm','\u293D':'cularrp','\u2945':'rarrpl','\u2948':'harrcir','\u2949':'Uarrocir','\u294A':'lurdshar','\u294B':'ldrushar','\u294E':'LeftRightVector','\u294F':'RightUpDownVector','\u2950':'DownLeftRightVector','\u2951':'LeftUpDownVector','\u2952':'LeftVectorBar','\u2953':'RightVectorBar','\u2954':'RightUpVectorBar','\u2955':'RightDownVectorBar','\u2956':'DownLeftVectorBar','\u2957':'DownRightVectorBar','\u2958':'LeftUpVectorBar','\u2959':'LeftDownVectorBar','\u295A':'LeftTeeVector','\u295B':'RightTeeVector','\u295C':'RightUpTeeVector','\u295D':'RightDownTeeVector','\u295E':'DownLeftTeeVector','\u295F':'DownRightTeeVector','\u2960':'LeftUpTeeVector','\u2961':'LeftDownTeeVector','\u2962':'lHar','\u2963':'uHar','\u2964':'rHar','\u2965':'dHar','\u2966':'luruhar','\u2967':'ldrdhar','\u2968':'ruluhar','\u2969':'rdldhar','\u296A':'lharul','\u296B':'llhard','\u296C':'rharul','\u296D':'lrhard','\u296E':'udhar','\u296F':'duhar','\u2970':'RoundImplies','\u2971':'erarr','\u2972':'simrarr','\u2973':'larrsim','\u2974':'rarrsim','\u2975':'rarrap','\u2976':'ltlarr','\u2978':'gtrarr','\u2979':'subrarr','\u297B':'suplarr','\u297C':'lfisht','\u297D':'rfisht','\u297E':'ufisht','\u297F':'dfisht','\u299A':'vzigzag','\u299C':'vangrt','\u299D':'angrtvbd','\u29A4':'ange','\u29A5':'range','\u29A6':'dwangle','\u29A7':'uwangle','\u29A8':'angmsdaa','\u29A9':'angmsdab','\u29AA':'angmsdac','\u29AB':'angmsdad','\u29AC':'angmsdae','\u29AD':'angmsdaf','\u29AE':'angmsdag','\u29AF':'angmsdah','\u29B0':'bemptyv','\u29B1':'demptyv','\u29B2':'cemptyv','\u29B3':'raemptyv','\u29B4':'laemptyv','\u29B5':'ohbar','\u29B6':'omid','\u29B7':'opar','\u29B9':'operp','\u29BB':'olcross','\u29BC':'odsold','\u29BE':'olcir','\u29BF':'ofcir','\u29C0':'olt','\u29C1':'ogt','\u29C2':'cirscir','\u29C3':'cirE','\u29C4':'solb','\u29C5':'bsolb','\u29C9':'boxbox','\u29CD':'trisb','\u29CE':'rtriltri','\u29CF':'LeftTriangleBar','\u29CF\u0338':'NotLeftTriangleBar','\u29D0':'RightTriangleBar','\u29D0\u0338':'NotRightTriangleBar','\u29DC':'iinfin','\u29DD':'infintie','\u29DE':'nvinfin','\u29E3':'eparsl','\u29E4':'smeparsl','\u29E5':'eqvparsl','\u29EB':'lozf','\u29F4':'RuleDelayed','\u29F6':'dsol','\u2A00':'xodot','\u2A01':'xoplus','\u2A02':'xotime','\u2A04':'xuplus','\u2A06':'xsqcup','\u2A0D':'fpartint','\u2A10':'cirfnint','\u2A11':'awint','\u2A12':'rppolint','\u2A13':'scpolint','\u2A14':'npolint','\u2A15':'pointint','\u2A16':'quatint','\u2A17':'intlarhk','\u2A22':'pluscir','\u2A23':'plusacir','\u2A24':'simplus','\u2A25':'plusdu','\u2A26':'plussim','\u2A27':'plustwo','\u2A29':'mcomma','\u2A2A':'minusdu','\u2A2D':'loplus','\u2A2E':'roplus','\u2A2F':'Cross','\u2A30':'timesd','\u2A31':'timesbar','\u2A33':'smashp','\u2A34':'lotimes','\u2A35':'rotimes','\u2A36':'otimesas','\u2A37':'Otimes','\u2A38':'odiv','\u2A39':'triplus','\u2A3A':'triminus','\u2A3B':'tritime','\u2A3C':'iprod','\u2A3F':'amalg','\u2A40':'capdot','\u2A42':'ncup','\u2A43':'ncap','\u2A44':'capand','\u2A45':'cupor','\u2A46':'cupcap','\u2A47':'capcup','\u2A48':'cupbrcap','\u2A49':'capbrcup','\u2A4A':'cupcup','\u2A4B':'capcap','\u2A4C':'ccups','\u2A4D':'ccaps','\u2A50':'ccupssm','\u2A53':'And','\u2A54':'Or','\u2A55':'andand','\u2A56':'oror','\u2A57':'orslope','\u2A58':'andslope','\u2A5A':'andv','\u2A5B':'orv','\u2A5C':'andd','\u2A5D':'ord','\u2A5F':'wedbar','\u2A66':'sdote','\u2A6A':'simdot','\u2A6D':'congdot','\u2A6D\u0338':'ncongdot','\u2A6E':'easter','\u2A6F':'apacir','\u2A70':'apE','\u2A70\u0338':'napE','\u2A71':'eplus','\u2A72':'pluse','\u2A73':'Esim','\u2A77':'eDDot','\u2A78':'equivDD','\u2A79':'ltcir','\u2A7A':'gtcir','\u2A7B':'ltquest','\u2A7C':'gtquest','\u2A7D':'les','\u2A7D\u0338':'nles','\u2A7E':'ges','\u2A7E\u0338':'nges','\u2A7F':'lesdot','\u2A80':'gesdot','\u2A81':'lesdoto','\u2A82':'gesdoto','\u2A83':'lesdotor','\u2A84':'gesdotol','\u2A85':'lap','\u2A86':'gap','\u2A87':'lne','\u2A88':'gne','\u2A89':'lnap','\u2A8A':'gnap','\u2A8B':'lEg','\u2A8C':'gEl','\u2A8D':'lsime','\u2A8E':'gsime','\u2A8F':'lsimg','\u2A90':'gsiml','\u2A91':'lgE','\u2A92':'glE','\u2A93':'lesges','\u2A94':'gesles','\u2A95':'els','\u2A96':'egs','\u2A97':'elsdot','\u2A98':'egsdot','\u2A99':'el','\u2A9A':'eg','\u2A9D':'siml','\u2A9E':'simg','\u2A9F':'simlE','\u2AA0':'simgE','\u2AA1':'LessLess','\u2AA1\u0338':'NotNestedLessLess','\u2AA2':'GreaterGreater','\u2AA2\u0338':'NotNestedGreaterGreater','\u2AA4':'glj','\u2AA5':'gla','\u2AA6':'ltcc','\u2AA7':'gtcc','\u2AA8':'lescc','\u2AA9':'gescc','\u2AAA':'smt','\u2AAB':'lat','\u2AAC':'smte','\u2AAC\uFE00':'smtes','\u2AAD':'late','\u2AAD\uFE00':'lates','\u2AAE':'bumpE','\u2AAF':'pre','\u2AAF\u0338':'npre','\u2AB0':'sce','\u2AB0\u0338':'nsce','\u2AB3':'prE','\u2AB4':'scE','\u2AB5':'prnE','\u2AB6':'scnE','\u2AB7':'prap','\u2AB8':'scap','\u2AB9':'prnap','\u2ABA':'scnap','\u2ABB':'Pr','\u2ABC':'Sc','\u2ABD':'subdot','\u2ABE':'supdot','\u2ABF':'subplus','\u2AC0':'supplus','\u2AC1':'submult','\u2AC2':'supmult','\u2AC3':'subedot','\u2AC4':'supedot','\u2AC5':'subE','\u2AC5\u0338':'nsubE','\u2AC6':'supE','\u2AC6\u0338':'nsupE','\u2AC7':'subsim','\u2AC8':'supsim','\u2ACB\uFE00':'vsubnE','\u2ACB':'subnE','\u2ACC\uFE00':'vsupnE','\u2ACC':'supnE','\u2ACF':'csub','\u2AD0':'csup','\u2AD1':'csube','\u2AD2':'csupe','\u2AD3':'subsup','\u2AD4':'supsub','\u2AD5':'subsub','\u2AD6':'supsup','\u2AD7':'suphsub','\u2AD8':'supdsub','\u2AD9':'forkv','\u2ADA':'topfork','\u2ADB':'mlcp','\u2AE4':'Dashv','\u2AE6':'Vdashl','\u2AE7':'Barv','\u2AE8':'vBar','\u2AE9':'vBarv','\u2AEB':'Vbar','\u2AEC':'Not','\u2AED':'bNot','\u2AEE':'rnmid','\u2AEF':'cirmid','\u2AF0':'midcir','\u2AF1':'topcir','\u2AF2':'nhpar','\u2AF3':'parsim','\u2AFD':'parsl','\u2AFD\u20E5':'nparsl','\u266D':'flat','\u266E':'natur','\u266F':'sharp','\xA4':'curren','\xA2':'cent','$':'dollar','\xA3':'pound','\xA5':'yen','\u20AC':'euro','\xB9':'sup1','\xBD':'half','\u2153':'frac13','\xBC':'frac14','\u2155':'frac15','\u2159':'frac16','\u215B':'frac18','\xB2':'sup2','\u2154':'frac23','\u2156':'frac25','\xB3':'sup3','\xBE':'frac34','\u2157':'frac35','\u215C':'frac38','\u2158':'frac45','\u215A':'frac56','\u215D':'frac58','\u215E':'frac78','\uD835\uDCB6':'ascr','\uD835\uDD52':'aopf','\uD835\uDD1E':'afr','\uD835\uDD38':'Aopf','\uD835\uDD04':'Afr','\uD835\uDC9C':'Ascr','\xAA':'ordf','\xE1':'aacute','\xC1':'Aacute','\xE0':'agrave','\xC0':'Agrave','\u0103':'abreve','\u0102':'Abreve','\xE2':'acirc','\xC2':'Acirc','\xE5':'aring','\xC5':'angst','\xE4':'auml','\xC4':'Auml','\xE3':'atilde','\xC3':'Atilde','\u0105':'aogon','\u0104':'Aogon','\u0101':'amacr','\u0100':'Amacr','\xE6':'aelig','\xC6':'AElig','\uD835\uDCB7':'bscr','\uD835\uDD53':'bopf','\uD835\uDD1F':'bfr','\uD835\uDD39':'Bopf','\u212C':'Bscr','\uD835\uDD05':'Bfr','\uD835\uDD20':'cfr','\uD835\uDCB8':'cscr','\uD835\uDD54':'copf','\u212D':'Cfr','\uD835\uDC9E':'Cscr','\u2102':'Copf','\u0107':'cacute','\u0106':'Cacute','\u0109':'ccirc','\u0108':'Ccirc','\u010D':'ccaron','\u010C':'Ccaron','\u010B':'cdot','\u010A':'Cdot','\xE7':'ccedil','\xC7':'Ccedil','\u2105':'incare','\uD835\uDD21':'dfr','\u2146':'dd','\uD835\uDD55':'dopf','\uD835\uDCB9':'dscr','\uD835\uDC9F':'Dscr','\uD835\uDD07':'Dfr','\u2145':'DD','\uD835\uDD3B':'Dopf','\u010F':'dcaron','\u010E':'Dcaron','\u0111':'dstrok','\u0110':'Dstrok','\xF0':'eth','\xD0':'ETH','\u2147':'ee','\u212F':'escr','\uD835\uDD22':'efr','\uD835\uDD56':'eopf','\u2130':'Escr','\uD835\uDD08':'Efr','\uD835\uDD3C':'Eopf','\xE9':'eacute','\xC9':'Eacute','\xE8':'egrave','\xC8':'Egrave','\xEA':'ecirc','\xCA':'Ecirc','\u011B':'ecaron','\u011A':'Ecaron','\xEB':'euml','\xCB':'Euml','\u0117':'edot','\u0116':'Edot','\u0119':'eogon','\u0118':'Eogon','\u0113':'emacr','\u0112':'Emacr','\uD835\uDD23':'ffr','\uD835\uDD57':'fopf','\uD835\uDCBB':'fscr','\uD835\uDD09':'Ffr','\uD835\uDD3D':'Fopf','\u2131':'Fscr','\uFB00':'fflig','\uFB03':'ffilig','\uFB04':'ffllig','\uFB01':'filig','fj':'fjlig','\uFB02':'fllig','\u0192':'fnof','\u210A':'gscr','\uD835\uDD58':'gopf','\uD835\uDD24':'gfr','\uD835\uDCA2':'Gscr','\uD835\uDD3E':'Gopf','\uD835\uDD0A':'Gfr','\u01F5':'gacute','\u011F':'gbreve','\u011E':'Gbreve','\u011D':'gcirc','\u011C':'Gcirc','\u0121':'gdot','\u0120':'Gdot','\u0122':'Gcedil','\uD835\uDD25':'hfr','\u210E':'planckh','\uD835\uDCBD':'hscr','\uD835\uDD59':'hopf','\u210B':'Hscr','\u210C':'Hfr','\u210D':'Hopf','\u0125':'hcirc','\u0124':'Hcirc','\u210F':'hbar','\u0127':'hstrok','\u0126':'Hstrok','\uD835\uDD5A':'iopf','\uD835\uDD26':'ifr','\uD835\uDCBE':'iscr','\u2148':'ii','\uD835\uDD40':'Iopf','\u2110':'Iscr','\u2111':'Im','\xED':'iacute','\xCD':'Iacute','\xEC':'igrave','\xCC':'Igrave','\xEE':'icirc','\xCE':'Icirc','\xEF':'iuml','\xCF':'Iuml','\u0129':'itilde','\u0128':'Itilde','\u0130':'Idot','\u012F':'iogon','\u012E':'Iogon','\u012B':'imacr','\u012A':'Imacr','\u0133':'ijlig','\u0132':'IJlig','\u0131':'imath','\uD835\uDCBF':'jscr','\uD835\uDD5B':'jopf','\uD835\uDD27':'jfr','\uD835\uDCA5':'Jscr','\uD835\uDD0D':'Jfr','\uD835\uDD41':'Jopf','\u0135':'jcirc','\u0134':'Jcirc','\u0237':'jmath','\uD835\uDD5C':'kopf','\uD835\uDCC0':'kscr','\uD835\uDD28':'kfr','\uD835\uDCA6':'Kscr','\uD835\uDD42':'Kopf','\uD835\uDD0E':'Kfr','\u0137':'kcedil','\u0136':'Kcedil','\uD835\uDD29':'lfr','\uD835\uDCC1':'lscr','\u2113':'ell','\uD835\uDD5D':'lopf','\u2112':'Lscr','\uD835\uDD0F':'Lfr','\uD835\uDD43':'Lopf','\u013A':'lacute','\u0139':'Lacute','\u013E':'lcaron','\u013D':'Lcaron','\u013C':'lcedil','\u013B':'Lcedil','\u0142':'lstrok','\u0141':'Lstrok','\u0140':'lmidot','\u013F':'Lmidot','\uD835\uDD2A':'mfr','\uD835\uDD5E':'mopf','\uD835\uDCC2':'mscr','\uD835\uDD10':'Mfr','\uD835\uDD44':'Mopf','\u2133':'Mscr','\uD835\uDD2B':'nfr','\uD835\uDD5F':'nopf','\uD835\uDCC3':'nscr','\u2115':'Nopf','\uD835\uDCA9':'Nscr','\uD835\uDD11':'Nfr','\u0144':'nacute','\u0143':'Nacute','\u0148':'ncaron','\u0147':'Ncaron','\xF1':'ntilde','\xD1':'Ntilde','\u0146':'ncedil','\u0145':'Ncedil','\u2116':'numero','\u014B':'eng','\u014A':'ENG','\uD835\uDD60':'oopf','\uD835\uDD2C':'ofr','\u2134':'oscr','\uD835\uDCAA':'Oscr','\uD835\uDD12':'Ofr','\uD835\uDD46':'Oopf','\xBA':'ordm','\xF3':'oacute','\xD3':'Oacute','\xF2':'ograve','\xD2':'Ograve','\xF4':'ocirc','\xD4':'Ocirc','\xF6':'ouml','\xD6':'Ouml','\u0151':'odblac','\u0150':'Odblac','\xF5':'otilde','\xD5':'Otilde','\xF8':'oslash','\xD8':'Oslash','\u014D':'omacr','\u014C':'Omacr','\u0153':'oelig','\u0152':'OElig','\uD835\uDD2D':'pfr','\uD835\uDCC5':'pscr','\uD835\uDD61':'popf','\u2119':'Popf','\uD835\uDD13':'Pfr','\uD835\uDCAB':'Pscr','\uD835\uDD62':'qopf','\uD835\uDD2E':'qfr','\uD835\uDCC6':'qscr','\uD835\uDCAC':'Qscr','\uD835\uDD14':'Qfr','\u211A':'Qopf','\u0138':'kgreen','\uD835\uDD2F':'rfr','\uD835\uDD63':'ropf','\uD835\uDCC7':'rscr','\u211B':'Rscr','\u211C':'Re','\u211D':'Ropf','\u0155':'racute','\u0154':'Racute','\u0159':'rcaron','\u0158':'Rcaron','\u0157':'rcedil','\u0156':'Rcedil','\uD835\uDD64':'sopf','\uD835\uDCC8':'sscr','\uD835\uDD30':'sfr','\uD835\uDD4A':'Sopf','\uD835\uDD16':'Sfr','\uD835\uDCAE':'Sscr','\u24C8':'oS','\u015B':'sacute','\u015A':'Sacute','\u015D':'scirc','\u015C':'Scirc','\u0161':'scaron','\u0160':'Scaron','\u015F':'scedil','\u015E':'Scedil','\xDF':'szlig','\uD835\uDD31':'tfr','\uD835\uDCC9':'tscr','\uD835\uDD65':'topf','\uD835\uDCAF':'Tscr','\uD835\uDD17':'Tfr','\uD835\uDD4B':'Topf','\u0165':'tcaron','\u0164':'Tcaron','\u0163':'tcedil','\u0162':'Tcedil','\u2122':'trade','\u0167':'tstrok','\u0166':'Tstrok','\uD835\uDCCA':'uscr','\uD835\uDD66':'uopf','\uD835\uDD32':'ufr','\uD835\uDD4C':'Uopf','\uD835\uDD18':'Ufr','\uD835\uDCB0':'Uscr','\xFA':'uacute','\xDA':'Uacute','\xF9':'ugrave','\xD9':'Ugrave','\u016D':'ubreve','\u016C':'Ubreve','\xFB':'ucirc','\xDB':'Ucirc','\u016F':'uring','\u016E':'Uring','\xFC':'uuml','\xDC':'Uuml','\u0171':'udblac','\u0170':'Udblac','\u0169':'utilde','\u0168':'Utilde','\u0173':'uogon','\u0172':'Uogon','\u016B':'umacr','\u016A':'Umacr','\uD835\uDD33':'vfr','\uD835\uDD67':'vopf','\uD835\uDCCB':'vscr','\uD835\uDD19':'Vfr','\uD835\uDD4D':'Vopf','\uD835\uDCB1':'Vscr','\uD835\uDD68':'wopf','\uD835\uDCCC':'wscr','\uD835\uDD34':'wfr','\uD835\uDCB2':'Wscr','\uD835\uDD4E':'Wopf','\uD835\uDD1A':'Wfr','\u0175':'wcirc','\u0174':'Wcirc','\uD835\uDD35':'xfr','\uD835\uDCCD':'xscr','\uD835\uDD69':'xopf','\uD835\uDD4F':'Xopf','\uD835\uDD1B':'Xfr','\uD835\uDCB3':'Xscr','\uD835\uDD36':'yfr','\uD835\uDCCE':'yscr','\uD835\uDD6A':'yopf','\uD835\uDCB4':'Yscr','\uD835\uDD1C':'Yfr','\uD835\uDD50':'Yopf','\xFD':'yacute','\xDD':'Yacute','\u0177':'ycirc','\u0176':'Ycirc','\xFF':'yuml','\u0178':'Yuml','\uD835\uDCCF':'zscr','\uD835\uDD37':'zfr','\uD835\uDD6B':'zopf','\u2128':'Zfr','\u2124':'Zopf','\uD835\uDCB5':'Zscr','\u017A':'zacute','\u0179':'Zacute','\u017E':'zcaron','\u017D':'Zcaron','\u017C':'zdot','\u017B':'Zdot','\u01B5':'imped','\xFE':'thorn','\xDE':'THORN','\u0149':'napos','\u03B1':'alpha','\u0391':'Alpha','\u03B2':'beta','\u0392':'Beta','\u03B3':'gamma','\u0393':'Gamma','\u03B4':'delta','\u0394':'Delta','\u03B5':'epsi','\u03F5':'epsiv','\u0395':'Epsilon','\u03DD':'gammad','\u03DC':'Gammad','\u03B6':'zeta','\u0396':'Zeta','\u03B7':'eta','\u0397':'Eta','\u03B8':'theta','\u03D1':'thetav','\u0398':'Theta','\u03B9':'iota','\u0399':'Iota','\u03BA':'kappa','\u03F0':'kappav','\u039A':'Kappa','\u03BB':'lambda','\u039B':'Lambda','\u03BC':'mu','\xB5':'micro','\u039C':'Mu','\u03BD':'nu','\u039D':'Nu','\u03BE':'xi','\u039E':'Xi','\u03BF':'omicron','\u039F':'Omicron','\u03C0':'pi','\u03D6':'piv','\u03A0':'Pi','\u03C1':'rho','\u03F1':'rhov','\u03A1':'Rho','\u03C3':'sigma','\u03A3':'Sigma','\u03C2':'sigmaf','\u03C4':'tau','\u03A4':'Tau','\u03C5':'upsi','\u03A5':'Upsilon','\u03D2':'Upsi','\u03C6':'phi','\u03D5':'phiv','\u03A6':'Phi','\u03C7':'chi','\u03A7':'Chi','\u03C8':'psi','\u03A8':'Psi','\u03C9':'omega','\u03A9':'ohm','\u0430':'acy','\u0410':'Acy','\u0431':'bcy','\u0411':'Bcy','\u0432':'vcy','\u0412':'Vcy','\u0433':'gcy','\u0413':'Gcy','\u0453':'gjcy','\u0403':'GJcy','\u0434':'dcy','\u0414':'Dcy','\u0452':'djcy','\u0402':'DJcy','\u0435':'iecy','\u0415':'IEcy','\u0451':'iocy','\u0401':'IOcy','\u0454':'jukcy','\u0404':'Jukcy','\u0436':'zhcy','\u0416':'ZHcy','\u0437':'zcy','\u0417':'Zcy','\u0455':'dscy','\u0405':'DScy','\u0438':'icy','\u0418':'Icy','\u0456':'iukcy','\u0406':'Iukcy','\u0457':'yicy','\u0407':'YIcy','\u0439':'jcy','\u0419':'Jcy','\u0458':'jsercy','\u0408':'Jsercy','\u043A':'kcy','\u041A':'Kcy','\u045C':'kjcy','\u040C':'KJcy','\u043B':'lcy','\u041B':'Lcy','\u0459':'ljcy','\u0409':'LJcy','\u043C':'mcy','\u041C':'Mcy','\u043D':'ncy','\u041D':'Ncy','\u045A':'njcy','\u040A':'NJcy','\u043E':'ocy','\u041E':'Ocy','\u043F':'pcy','\u041F':'Pcy','\u0440':'rcy','\u0420':'Rcy','\u0441':'scy','\u0421':'Scy','\u0442':'tcy','\u0422':'Tcy','\u045B':'tshcy','\u040B':'TSHcy','\u0443':'ucy','\u0423':'Ucy','\u045E':'ubrcy','\u040E':'Ubrcy','\u0444':'fcy','\u0424':'Fcy','\u0445':'khcy','\u0425':'KHcy','\u0446':'tscy','\u0426':'TScy','\u0447':'chcy','\u0427':'CHcy','\u045F':'dzcy','\u040F':'DZcy','\u0448':'shcy','\u0428':'SHcy','\u0449':'shchcy','\u0429':'SHCHcy','\u044A':'hardcy','\u042A':'HARDcy','\u044B':'ycy','\u042B':'Ycy','\u044C':'softcy','\u042C':'SOFTcy','\u044D':'ecy','\u042D':'Ecy','\u044E':'yucy','\u042E':'YUcy','\u044F':'yacy','\u042F':'YAcy','\u2135':'aleph','\u2136':'beth','\u2137':'gimel','\u2138':'daleth'}; + + var regexEscape = /["&'<>`]/g; + var escapeMap = { + '"': '"', + '&': '&', + '\'': ''', + '<': '<', + // See https://mathiasbynens.be/notes/ambiguous-ampersands: in HTML, the + // following is not strictly necessary unless it’s part of a tag or an + // unquoted attribute value. We’re only escaping it to support those + // situations, and for XML support. + '>': '>', + // In Internet Explorer ≤ 8, the backtick character can be used + // to break out of (un)quoted attribute values or HTML comments. + // See http://html5sec.org/#102, http://html5sec.org/#108, and + // http://html5sec.org/#133. + '`': '`' + }; + + var regexInvalidEntity = /&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/; + var regexInvalidRawCodePoint = /[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + var regexDecode = /&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)([=a-zA-Z0-9])?/g; + var decodeMap = {'aacute':'\xE1','Aacute':'\xC1','abreve':'\u0103','Abreve':'\u0102','ac':'\u223E','acd':'\u223F','acE':'\u223E\u0333','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','acy':'\u0430','Acy':'\u0410','aelig':'\xE6','AElig':'\xC6','af':'\u2061','afr':'\uD835\uDD1E','Afr':'\uD835\uDD04','agrave':'\xE0','Agrave':'\xC0','alefsym':'\u2135','aleph':'\u2135','alpha':'\u03B1','Alpha':'\u0391','amacr':'\u0101','Amacr':'\u0100','amalg':'\u2A3F','amp':'&','AMP':'&','and':'\u2227','And':'\u2A53','andand':'\u2A55','andd':'\u2A5C','andslope':'\u2A58','andv':'\u2A5A','ang':'\u2220','ange':'\u29A4','angle':'\u2220','angmsd':'\u2221','angmsdaa':'\u29A8','angmsdab':'\u29A9','angmsdac':'\u29AA','angmsdad':'\u29AB','angmsdae':'\u29AC','angmsdaf':'\u29AD','angmsdag':'\u29AE','angmsdah':'\u29AF','angrt':'\u221F','angrtvb':'\u22BE','angrtvbd':'\u299D','angsph':'\u2222','angst':'\xC5','angzarr':'\u237C','aogon':'\u0105','Aogon':'\u0104','aopf':'\uD835\uDD52','Aopf':'\uD835\uDD38','ap':'\u2248','apacir':'\u2A6F','ape':'\u224A','apE':'\u2A70','apid':'\u224B','apos':'\'','ApplyFunction':'\u2061','approx':'\u2248','approxeq':'\u224A','aring':'\xE5','Aring':'\xC5','ascr':'\uD835\uDCB6','Ascr':'\uD835\uDC9C','Assign':'\u2254','ast':'*','asymp':'\u2248','asympeq':'\u224D','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','awconint':'\u2233','awint':'\u2A11','backcong':'\u224C','backepsilon':'\u03F6','backprime':'\u2035','backsim':'\u223D','backsimeq':'\u22CD','Backslash':'\u2216','Barv':'\u2AE7','barvee':'\u22BD','barwed':'\u2305','Barwed':'\u2306','barwedge':'\u2305','bbrk':'\u23B5','bbrktbrk':'\u23B6','bcong':'\u224C','bcy':'\u0431','Bcy':'\u0411','bdquo':'\u201E','becaus':'\u2235','because':'\u2235','Because':'\u2235','bemptyv':'\u29B0','bepsi':'\u03F6','bernou':'\u212C','Bernoullis':'\u212C','beta':'\u03B2','Beta':'\u0392','beth':'\u2136','between':'\u226C','bfr':'\uD835\uDD1F','Bfr':'\uD835\uDD05','bigcap':'\u22C2','bigcirc':'\u25EF','bigcup':'\u22C3','bigodot':'\u2A00','bigoplus':'\u2A01','bigotimes':'\u2A02','bigsqcup':'\u2A06','bigstar':'\u2605','bigtriangledown':'\u25BD','bigtriangleup':'\u25B3','biguplus':'\u2A04','bigvee':'\u22C1','bigwedge':'\u22C0','bkarow':'\u290D','blacklozenge':'\u29EB','blacksquare':'\u25AA','blacktriangle':'\u25B4','blacktriangledown':'\u25BE','blacktriangleleft':'\u25C2','blacktriangleright':'\u25B8','blank':'\u2423','blk12':'\u2592','blk14':'\u2591','blk34':'\u2593','block':'\u2588','bne':'=\u20E5','bnequiv':'\u2261\u20E5','bnot':'\u2310','bNot':'\u2AED','bopf':'\uD835\uDD53','Bopf':'\uD835\uDD39','bot':'\u22A5','bottom':'\u22A5','bowtie':'\u22C8','boxbox':'\u29C9','boxdl':'\u2510','boxdL':'\u2555','boxDl':'\u2556','boxDL':'\u2557','boxdr':'\u250C','boxdR':'\u2552','boxDr':'\u2553','boxDR':'\u2554','boxh':'\u2500','boxH':'\u2550','boxhd':'\u252C','boxhD':'\u2565','boxHd':'\u2564','boxHD':'\u2566','boxhu':'\u2534','boxhU':'\u2568','boxHu':'\u2567','boxHU':'\u2569','boxminus':'\u229F','boxplus':'\u229E','boxtimes':'\u22A0','boxul':'\u2518','boxuL':'\u255B','boxUl':'\u255C','boxUL':'\u255D','boxur':'\u2514','boxuR':'\u2558','boxUr':'\u2559','boxUR':'\u255A','boxv':'\u2502','boxV':'\u2551','boxvh':'\u253C','boxvH':'\u256A','boxVh':'\u256B','boxVH':'\u256C','boxvl':'\u2524','boxvL':'\u2561','boxVl':'\u2562','boxVL':'\u2563','boxvr':'\u251C','boxvR':'\u255E','boxVr':'\u255F','boxVR':'\u2560','bprime':'\u2035','breve':'\u02D8','Breve':'\u02D8','brvbar':'\xA6','bscr':'\uD835\uDCB7','Bscr':'\u212C','bsemi':'\u204F','bsim':'\u223D','bsime':'\u22CD','bsol':'\\','bsolb':'\u29C5','bsolhsub':'\u27C8','bull':'\u2022','bullet':'\u2022','bump':'\u224E','bumpe':'\u224F','bumpE':'\u2AAE','bumpeq':'\u224F','Bumpeq':'\u224E','cacute':'\u0107','Cacute':'\u0106','cap':'\u2229','Cap':'\u22D2','capand':'\u2A44','capbrcup':'\u2A49','capcap':'\u2A4B','capcup':'\u2A47','capdot':'\u2A40','CapitalDifferentialD':'\u2145','caps':'\u2229\uFE00','caret':'\u2041','caron':'\u02C7','Cayleys':'\u212D','ccaps':'\u2A4D','ccaron':'\u010D','Ccaron':'\u010C','ccedil':'\xE7','Ccedil':'\xC7','ccirc':'\u0109','Ccirc':'\u0108','Cconint':'\u2230','ccups':'\u2A4C','ccupssm':'\u2A50','cdot':'\u010B','Cdot':'\u010A','cedil':'\xB8','Cedilla':'\xB8','cemptyv':'\u29B2','cent':'\xA2','centerdot':'\xB7','CenterDot':'\xB7','cfr':'\uD835\uDD20','Cfr':'\u212D','chcy':'\u0447','CHcy':'\u0427','check':'\u2713','checkmark':'\u2713','chi':'\u03C7','Chi':'\u03A7','cir':'\u25CB','circ':'\u02C6','circeq':'\u2257','circlearrowleft':'\u21BA','circlearrowright':'\u21BB','circledast':'\u229B','circledcirc':'\u229A','circleddash':'\u229D','CircleDot':'\u2299','circledR':'\xAE','circledS':'\u24C8','CircleMinus':'\u2296','CirclePlus':'\u2295','CircleTimes':'\u2297','cire':'\u2257','cirE':'\u29C3','cirfnint':'\u2A10','cirmid':'\u2AEF','cirscir':'\u29C2','ClockwiseContourIntegral':'\u2232','CloseCurlyDoubleQuote':'\u201D','CloseCurlyQuote':'\u2019','clubs':'\u2663','clubsuit':'\u2663','colon':':','Colon':'\u2237','colone':'\u2254','Colone':'\u2A74','coloneq':'\u2254','comma':',','commat':'@','comp':'\u2201','compfn':'\u2218','complement':'\u2201','complexes':'\u2102','cong':'\u2245','congdot':'\u2A6D','Congruent':'\u2261','conint':'\u222E','Conint':'\u222F','ContourIntegral':'\u222E','copf':'\uD835\uDD54','Copf':'\u2102','coprod':'\u2210','Coproduct':'\u2210','copy':'\xA9','COPY':'\xA9','copysr':'\u2117','CounterClockwiseContourIntegral':'\u2233','crarr':'\u21B5','cross':'\u2717','Cross':'\u2A2F','cscr':'\uD835\uDCB8','Cscr':'\uD835\uDC9E','csub':'\u2ACF','csube':'\u2AD1','csup':'\u2AD0','csupe':'\u2AD2','ctdot':'\u22EF','cudarrl':'\u2938','cudarrr':'\u2935','cuepr':'\u22DE','cuesc':'\u22DF','cularr':'\u21B6','cularrp':'\u293D','cup':'\u222A','Cup':'\u22D3','cupbrcap':'\u2A48','cupcap':'\u2A46','CupCap':'\u224D','cupcup':'\u2A4A','cupdot':'\u228D','cupor':'\u2A45','cups':'\u222A\uFE00','curarr':'\u21B7','curarrm':'\u293C','curlyeqprec':'\u22DE','curlyeqsucc':'\u22DF','curlyvee':'\u22CE','curlywedge':'\u22CF','curren':'\xA4','curvearrowleft':'\u21B6','curvearrowright':'\u21B7','cuvee':'\u22CE','cuwed':'\u22CF','cwconint':'\u2232','cwint':'\u2231','cylcty':'\u232D','dagger':'\u2020','Dagger':'\u2021','daleth':'\u2138','darr':'\u2193','dArr':'\u21D3','Darr':'\u21A1','dash':'\u2010','dashv':'\u22A3','Dashv':'\u2AE4','dbkarow':'\u290F','dblac':'\u02DD','dcaron':'\u010F','Dcaron':'\u010E','dcy':'\u0434','Dcy':'\u0414','dd':'\u2146','DD':'\u2145','ddagger':'\u2021','ddarr':'\u21CA','DDotrahd':'\u2911','ddotseq':'\u2A77','deg':'\xB0','Del':'\u2207','delta':'\u03B4','Delta':'\u0394','demptyv':'\u29B1','dfisht':'\u297F','dfr':'\uD835\uDD21','Dfr':'\uD835\uDD07','dHar':'\u2965','dharl':'\u21C3','dharr':'\u21C2','DiacriticalAcute':'\xB4','DiacriticalDot':'\u02D9','DiacriticalDoubleAcute':'\u02DD','DiacriticalGrave':'`','DiacriticalTilde':'\u02DC','diam':'\u22C4','diamond':'\u22C4','Diamond':'\u22C4','diamondsuit':'\u2666','diams':'\u2666','die':'\xA8','DifferentialD':'\u2146','digamma':'\u03DD','disin':'\u22F2','div':'\xF7','divide':'\xF7','divideontimes':'\u22C7','divonx':'\u22C7','djcy':'\u0452','DJcy':'\u0402','dlcorn':'\u231E','dlcrop':'\u230D','dollar':'$','dopf':'\uD835\uDD55','Dopf':'\uD835\uDD3B','dot':'\u02D9','Dot':'\xA8','DotDot':'\u20DC','doteq':'\u2250','doteqdot':'\u2251','DotEqual':'\u2250','dotminus':'\u2238','dotplus':'\u2214','dotsquare':'\u22A1','doublebarwedge':'\u2306','DoubleContourIntegral':'\u222F','DoubleDot':'\xA8','DoubleDownArrow':'\u21D3','DoubleLeftArrow':'\u21D0','DoubleLeftRightArrow':'\u21D4','DoubleLeftTee':'\u2AE4','DoubleLongLeftArrow':'\u27F8','DoubleLongLeftRightArrow':'\u27FA','DoubleLongRightArrow':'\u27F9','DoubleRightArrow':'\u21D2','DoubleRightTee':'\u22A8','DoubleUpArrow':'\u21D1','DoubleUpDownArrow':'\u21D5','DoubleVerticalBar':'\u2225','downarrow':'\u2193','Downarrow':'\u21D3','DownArrow':'\u2193','DownArrowBar':'\u2913','DownArrowUpArrow':'\u21F5','DownBreve':'\u0311','downdownarrows':'\u21CA','downharpoonleft':'\u21C3','downharpoonright':'\u21C2','DownLeftRightVector':'\u2950','DownLeftTeeVector':'\u295E','DownLeftVector':'\u21BD','DownLeftVectorBar':'\u2956','DownRightTeeVector':'\u295F','DownRightVector':'\u21C1','DownRightVectorBar':'\u2957','DownTee':'\u22A4','DownTeeArrow':'\u21A7','drbkarow':'\u2910','drcorn':'\u231F','drcrop':'\u230C','dscr':'\uD835\uDCB9','Dscr':'\uD835\uDC9F','dscy':'\u0455','DScy':'\u0405','dsol':'\u29F6','dstrok':'\u0111','Dstrok':'\u0110','dtdot':'\u22F1','dtri':'\u25BF','dtrif':'\u25BE','duarr':'\u21F5','duhar':'\u296F','dwangle':'\u29A6','dzcy':'\u045F','DZcy':'\u040F','dzigrarr':'\u27FF','eacute':'\xE9','Eacute':'\xC9','easter':'\u2A6E','ecaron':'\u011B','Ecaron':'\u011A','ecir':'\u2256','ecirc':'\xEA','Ecirc':'\xCA','ecolon':'\u2255','ecy':'\u044D','Ecy':'\u042D','eDDot':'\u2A77','edot':'\u0117','eDot':'\u2251','Edot':'\u0116','ee':'\u2147','efDot':'\u2252','efr':'\uD835\uDD22','Efr':'\uD835\uDD08','eg':'\u2A9A','egrave':'\xE8','Egrave':'\xC8','egs':'\u2A96','egsdot':'\u2A98','el':'\u2A99','Element':'\u2208','elinters':'\u23E7','ell':'\u2113','els':'\u2A95','elsdot':'\u2A97','emacr':'\u0113','Emacr':'\u0112','empty':'\u2205','emptyset':'\u2205','EmptySmallSquare':'\u25FB','emptyv':'\u2205','EmptyVerySmallSquare':'\u25AB','emsp':'\u2003','emsp13':'\u2004','emsp14':'\u2005','eng':'\u014B','ENG':'\u014A','ensp':'\u2002','eogon':'\u0119','Eogon':'\u0118','eopf':'\uD835\uDD56','Eopf':'\uD835\uDD3C','epar':'\u22D5','eparsl':'\u29E3','eplus':'\u2A71','epsi':'\u03B5','epsilon':'\u03B5','Epsilon':'\u0395','epsiv':'\u03F5','eqcirc':'\u2256','eqcolon':'\u2255','eqsim':'\u2242','eqslantgtr':'\u2A96','eqslantless':'\u2A95','Equal':'\u2A75','equals':'=','EqualTilde':'\u2242','equest':'\u225F','Equilibrium':'\u21CC','equiv':'\u2261','equivDD':'\u2A78','eqvparsl':'\u29E5','erarr':'\u2971','erDot':'\u2253','escr':'\u212F','Escr':'\u2130','esdot':'\u2250','esim':'\u2242','Esim':'\u2A73','eta':'\u03B7','Eta':'\u0397','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','euro':'\u20AC','excl':'!','exist':'\u2203','Exists':'\u2203','expectation':'\u2130','exponentiale':'\u2147','ExponentialE':'\u2147','fallingdotseq':'\u2252','fcy':'\u0444','Fcy':'\u0424','female':'\u2640','ffilig':'\uFB03','fflig':'\uFB00','ffllig':'\uFB04','ffr':'\uD835\uDD23','Ffr':'\uD835\uDD09','filig':'\uFB01','FilledSmallSquare':'\u25FC','FilledVerySmallSquare':'\u25AA','fjlig':'fj','flat':'\u266D','fllig':'\uFB02','fltns':'\u25B1','fnof':'\u0192','fopf':'\uD835\uDD57','Fopf':'\uD835\uDD3D','forall':'\u2200','ForAll':'\u2200','fork':'\u22D4','forkv':'\u2AD9','Fouriertrf':'\u2131','fpartint':'\u2A0D','frac12':'\xBD','frac13':'\u2153','frac14':'\xBC','frac15':'\u2155','frac16':'\u2159','frac18':'\u215B','frac23':'\u2154','frac25':'\u2156','frac34':'\xBE','frac35':'\u2157','frac38':'\u215C','frac45':'\u2158','frac56':'\u215A','frac58':'\u215D','frac78':'\u215E','frasl':'\u2044','frown':'\u2322','fscr':'\uD835\uDCBB','Fscr':'\u2131','gacute':'\u01F5','gamma':'\u03B3','Gamma':'\u0393','gammad':'\u03DD','Gammad':'\u03DC','gap':'\u2A86','gbreve':'\u011F','Gbreve':'\u011E','Gcedil':'\u0122','gcirc':'\u011D','Gcirc':'\u011C','gcy':'\u0433','Gcy':'\u0413','gdot':'\u0121','Gdot':'\u0120','ge':'\u2265','gE':'\u2267','gel':'\u22DB','gEl':'\u2A8C','geq':'\u2265','geqq':'\u2267','geqslant':'\u2A7E','ges':'\u2A7E','gescc':'\u2AA9','gesdot':'\u2A80','gesdoto':'\u2A82','gesdotol':'\u2A84','gesl':'\u22DB\uFE00','gesles':'\u2A94','gfr':'\uD835\uDD24','Gfr':'\uD835\uDD0A','gg':'\u226B','Gg':'\u22D9','ggg':'\u22D9','gimel':'\u2137','gjcy':'\u0453','GJcy':'\u0403','gl':'\u2277','gla':'\u2AA5','glE':'\u2A92','glj':'\u2AA4','gnap':'\u2A8A','gnapprox':'\u2A8A','gne':'\u2A88','gnE':'\u2269','gneq':'\u2A88','gneqq':'\u2269','gnsim':'\u22E7','gopf':'\uD835\uDD58','Gopf':'\uD835\uDD3E','grave':'`','GreaterEqual':'\u2265','GreaterEqualLess':'\u22DB','GreaterFullEqual':'\u2267','GreaterGreater':'\u2AA2','GreaterLess':'\u2277','GreaterSlantEqual':'\u2A7E','GreaterTilde':'\u2273','gscr':'\u210A','Gscr':'\uD835\uDCA2','gsim':'\u2273','gsime':'\u2A8E','gsiml':'\u2A90','gt':'>','Gt':'\u226B','GT':'>','gtcc':'\u2AA7','gtcir':'\u2A7A','gtdot':'\u22D7','gtlPar':'\u2995','gtquest':'\u2A7C','gtrapprox':'\u2A86','gtrarr':'\u2978','gtrdot':'\u22D7','gtreqless':'\u22DB','gtreqqless':'\u2A8C','gtrless':'\u2277','gtrsim':'\u2273','gvertneqq':'\u2269\uFE00','gvnE':'\u2269\uFE00','Hacek':'\u02C7','hairsp':'\u200A','half':'\xBD','hamilt':'\u210B','hardcy':'\u044A','HARDcy':'\u042A','harr':'\u2194','hArr':'\u21D4','harrcir':'\u2948','harrw':'\u21AD','Hat':'^','hbar':'\u210F','hcirc':'\u0125','Hcirc':'\u0124','hearts':'\u2665','heartsuit':'\u2665','hellip':'\u2026','hercon':'\u22B9','hfr':'\uD835\uDD25','Hfr':'\u210C','HilbertSpace':'\u210B','hksearow':'\u2925','hkswarow':'\u2926','hoarr':'\u21FF','homtht':'\u223B','hookleftarrow':'\u21A9','hookrightarrow':'\u21AA','hopf':'\uD835\uDD59','Hopf':'\u210D','horbar':'\u2015','HorizontalLine':'\u2500','hscr':'\uD835\uDCBD','Hscr':'\u210B','hslash':'\u210F','hstrok':'\u0127','Hstrok':'\u0126','HumpDownHump':'\u224E','HumpEqual':'\u224F','hybull':'\u2043','hyphen':'\u2010','iacute':'\xED','Iacute':'\xCD','ic':'\u2063','icirc':'\xEE','Icirc':'\xCE','icy':'\u0438','Icy':'\u0418','Idot':'\u0130','iecy':'\u0435','IEcy':'\u0415','iexcl':'\xA1','iff':'\u21D4','ifr':'\uD835\uDD26','Ifr':'\u2111','igrave':'\xEC','Igrave':'\xCC','ii':'\u2148','iiiint':'\u2A0C','iiint':'\u222D','iinfin':'\u29DC','iiota':'\u2129','ijlig':'\u0133','IJlig':'\u0132','Im':'\u2111','imacr':'\u012B','Imacr':'\u012A','image':'\u2111','ImaginaryI':'\u2148','imagline':'\u2110','imagpart':'\u2111','imath':'\u0131','imof':'\u22B7','imped':'\u01B5','Implies':'\u21D2','in':'\u2208','incare':'\u2105','infin':'\u221E','infintie':'\u29DD','inodot':'\u0131','int':'\u222B','Int':'\u222C','intcal':'\u22BA','integers':'\u2124','Integral':'\u222B','intercal':'\u22BA','Intersection':'\u22C2','intlarhk':'\u2A17','intprod':'\u2A3C','InvisibleComma':'\u2063','InvisibleTimes':'\u2062','iocy':'\u0451','IOcy':'\u0401','iogon':'\u012F','Iogon':'\u012E','iopf':'\uD835\uDD5A','Iopf':'\uD835\uDD40','iota':'\u03B9','Iota':'\u0399','iprod':'\u2A3C','iquest':'\xBF','iscr':'\uD835\uDCBE','Iscr':'\u2110','isin':'\u2208','isindot':'\u22F5','isinE':'\u22F9','isins':'\u22F4','isinsv':'\u22F3','isinv':'\u2208','it':'\u2062','itilde':'\u0129','Itilde':'\u0128','iukcy':'\u0456','Iukcy':'\u0406','iuml':'\xEF','Iuml':'\xCF','jcirc':'\u0135','Jcirc':'\u0134','jcy':'\u0439','Jcy':'\u0419','jfr':'\uD835\uDD27','Jfr':'\uD835\uDD0D','jmath':'\u0237','jopf':'\uD835\uDD5B','Jopf':'\uD835\uDD41','jscr':'\uD835\uDCBF','Jscr':'\uD835\uDCA5','jsercy':'\u0458','Jsercy':'\u0408','jukcy':'\u0454','Jukcy':'\u0404','kappa':'\u03BA','Kappa':'\u039A','kappav':'\u03F0','kcedil':'\u0137','Kcedil':'\u0136','kcy':'\u043A','Kcy':'\u041A','kfr':'\uD835\uDD28','Kfr':'\uD835\uDD0E','kgreen':'\u0138','khcy':'\u0445','KHcy':'\u0425','kjcy':'\u045C','KJcy':'\u040C','kopf':'\uD835\uDD5C','Kopf':'\uD835\uDD42','kscr':'\uD835\uDCC0','Kscr':'\uD835\uDCA6','lAarr':'\u21DA','lacute':'\u013A','Lacute':'\u0139','laemptyv':'\u29B4','lagran':'\u2112','lambda':'\u03BB','Lambda':'\u039B','lang':'\u27E8','Lang':'\u27EA','langd':'\u2991','langle':'\u27E8','lap':'\u2A85','Laplacetrf':'\u2112','laquo':'\xAB','larr':'\u2190','lArr':'\u21D0','Larr':'\u219E','larrb':'\u21E4','larrbfs':'\u291F','larrfs':'\u291D','larrhk':'\u21A9','larrlp':'\u21AB','larrpl':'\u2939','larrsim':'\u2973','larrtl':'\u21A2','lat':'\u2AAB','latail':'\u2919','lAtail':'\u291B','late':'\u2AAD','lates':'\u2AAD\uFE00','lbarr':'\u290C','lBarr':'\u290E','lbbrk':'\u2772','lbrace':'{','lbrack':'[','lbrke':'\u298B','lbrksld':'\u298F','lbrkslu':'\u298D','lcaron':'\u013E','Lcaron':'\u013D','lcedil':'\u013C','Lcedil':'\u013B','lceil':'\u2308','lcub':'{','lcy':'\u043B','Lcy':'\u041B','ldca':'\u2936','ldquo':'\u201C','ldquor':'\u201E','ldrdhar':'\u2967','ldrushar':'\u294B','ldsh':'\u21B2','le':'\u2264','lE':'\u2266','LeftAngleBracket':'\u27E8','leftarrow':'\u2190','Leftarrow':'\u21D0','LeftArrow':'\u2190','LeftArrowBar':'\u21E4','LeftArrowRightArrow':'\u21C6','leftarrowtail':'\u21A2','LeftCeiling':'\u2308','LeftDoubleBracket':'\u27E6','LeftDownTeeVector':'\u2961','LeftDownVector':'\u21C3','LeftDownVectorBar':'\u2959','LeftFloor':'\u230A','leftharpoondown':'\u21BD','leftharpoonup':'\u21BC','leftleftarrows':'\u21C7','leftrightarrow':'\u2194','Leftrightarrow':'\u21D4','LeftRightArrow':'\u2194','leftrightarrows':'\u21C6','leftrightharpoons':'\u21CB','leftrightsquigarrow':'\u21AD','LeftRightVector':'\u294E','LeftTee':'\u22A3','LeftTeeArrow':'\u21A4','LeftTeeVector':'\u295A','leftthreetimes':'\u22CB','LeftTriangle':'\u22B2','LeftTriangleBar':'\u29CF','LeftTriangleEqual':'\u22B4','LeftUpDownVector':'\u2951','LeftUpTeeVector':'\u2960','LeftUpVector':'\u21BF','LeftUpVectorBar':'\u2958','LeftVector':'\u21BC','LeftVectorBar':'\u2952','leg':'\u22DA','lEg':'\u2A8B','leq':'\u2264','leqq':'\u2266','leqslant':'\u2A7D','les':'\u2A7D','lescc':'\u2AA8','lesdot':'\u2A7F','lesdoto':'\u2A81','lesdotor':'\u2A83','lesg':'\u22DA\uFE00','lesges':'\u2A93','lessapprox':'\u2A85','lessdot':'\u22D6','lesseqgtr':'\u22DA','lesseqqgtr':'\u2A8B','LessEqualGreater':'\u22DA','LessFullEqual':'\u2266','LessGreater':'\u2276','lessgtr':'\u2276','LessLess':'\u2AA1','lesssim':'\u2272','LessSlantEqual':'\u2A7D','LessTilde':'\u2272','lfisht':'\u297C','lfloor':'\u230A','lfr':'\uD835\uDD29','Lfr':'\uD835\uDD0F','lg':'\u2276','lgE':'\u2A91','lHar':'\u2962','lhard':'\u21BD','lharu':'\u21BC','lharul':'\u296A','lhblk':'\u2584','ljcy':'\u0459','LJcy':'\u0409','ll':'\u226A','Ll':'\u22D8','llarr':'\u21C7','llcorner':'\u231E','Lleftarrow':'\u21DA','llhard':'\u296B','lltri':'\u25FA','lmidot':'\u0140','Lmidot':'\u013F','lmoust':'\u23B0','lmoustache':'\u23B0','lnap':'\u2A89','lnapprox':'\u2A89','lne':'\u2A87','lnE':'\u2268','lneq':'\u2A87','lneqq':'\u2268','lnsim':'\u22E6','loang':'\u27EC','loarr':'\u21FD','lobrk':'\u27E6','longleftarrow':'\u27F5','Longleftarrow':'\u27F8','LongLeftArrow':'\u27F5','longleftrightarrow':'\u27F7','Longleftrightarrow':'\u27FA','LongLeftRightArrow':'\u27F7','longmapsto':'\u27FC','longrightarrow':'\u27F6','Longrightarrow':'\u27F9','LongRightArrow':'\u27F6','looparrowleft':'\u21AB','looparrowright':'\u21AC','lopar':'\u2985','lopf':'\uD835\uDD5D','Lopf':'\uD835\uDD43','loplus':'\u2A2D','lotimes':'\u2A34','lowast':'\u2217','lowbar':'_','LowerLeftArrow':'\u2199','LowerRightArrow':'\u2198','loz':'\u25CA','lozenge':'\u25CA','lozf':'\u29EB','lpar':'(','lparlt':'\u2993','lrarr':'\u21C6','lrcorner':'\u231F','lrhar':'\u21CB','lrhard':'\u296D','lrm':'\u200E','lrtri':'\u22BF','lsaquo':'\u2039','lscr':'\uD835\uDCC1','Lscr':'\u2112','lsh':'\u21B0','Lsh':'\u21B0','lsim':'\u2272','lsime':'\u2A8D','lsimg':'\u2A8F','lsqb':'[','lsquo':'\u2018','lsquor':'\u201A','lstrok':'\u0142','Lstrok':'\u0141','lt':'<','Lt':'\u226A','LT':'<','ltcc':'\u2AA6','ltcir':'\u2A79','ltdot':'\u22D6','lthree':'\u22CB','ltimes':'\u22C9','ltlarr':'\u2976','ltquest':'\u2A7B','ltri':'\u25C3','ltrie':'\u22B4','ltrif':'\u25C2','ltrPar':'\u2996','lurdshar':'\u294A','luruhar':'\u2966','lvertneqq':'\u2268\uFE00','lvnE':'\u2268\uFE00','macr':'\xAF','male':'\u2642','malt':'\u2720','maltese':'\u2720','map':'\u21A6','Map':'\u2905','mapsto':'\u21A6','mapstodown':'\u21A7','mapstoleft':'\u21A4','mapstoup':'\u21A5','marker':'\u25AE','mcomma':'\u2A29','mcy':'\u043C','Mcy':'\u041C','mdash':'\u2014','mDDot':'\u223A','measuredangle':'\u2221','MediumSpace':'\u205F','Mellintrf':'\u2133','mfr':'\uD835\uDD2A','Mfr':'\uD835\uDD10','mho':'\u2127','micro':'\xB5','mid':'\u2223','midast':'*','midcir':'\u2AF0','middot':'\xB7','minus':'\u2212','minusb':'\u229F','minusd':'\u2238','minusdu':'\u2A2A','MinusPlus':'\u2213','mlcp':'\u2ADB','mldr':'\u2026','mnplus':'\u2213','models':'\u22A7','mopf':'\uD835\uDD5E','Mopf':'\uD835\uDD44','mp':'\u2213','mscr':'\uD835\uDCC2','Mscr':'\u2133','mstpos':'\u223E','mu':'\u03BC','Mu':'\u039C','multimap':'\u22B8','mumap':'\u22B8','nabla':'\u2207','nacute':'\u0144','Nacute':'\u0143','nang':'\u2220\u20D2','nap':'\u2249','napE':'\u2A70\u0338','napid':'\u224B\u0338','napos':'\u0149','napprox':'\u2249','natur':'\u266E','natural':'\u266E','naturals':'\u2115','nbsp':'\xA0','nbump':'\u224E\u0338','nbumpe':'\u224F\u0338','ncap':'\u2A43','ncaron':'\u0148','Ncaron':'\u0147','ncedil':'\u0146','Ncedil':'\u0145','ncong':'\u2247','ncongdot':'\u2A6D\u0338','ncup':'\u2A42','ncy':'\u043D','Ncy':'\u041D','ndash':'\u2013','ne':'\u2260','nearhk':'\u2924','nearr':'\u2197','neArr':'\u21D7','nearrow':'\u2197','nedot':'\u2250\u0338','NegativeMediumSpace':'\u200B','NegativeThickSpace':'\u200B','NegativeThinSpace':'\u200B','NegativeVeryThinSpace':'\u200B','nequiv':'\u2262','nesear':'\u2928','nesim':'\u2242\u0338','NestedGreaterGreater':'\u226B','NestedLessLess':'\u226A','NewLine':'\n','nexist':'\u2204','nexists':'\u2204','nfr':'\uD835\uDD2B','Nfr':'\uD835\uDD11','nge':'\u2271','ngE':'\u2267\u0338','ngeq':'\u2271','ngeqq':'\u2267\u0338','ngeqslant':'\u2A7E\u0338','nges':'\u2A7E\u0338','nGg':'\u22D9\u0338','ngsim':'\u2275','ngt':'\u226F','nGt':'\u226B\u20D2','ngtr':'\u226F','nGtv':'\u226B\u0338','nharr':'\u21AE','nhArr':'\u21CE','nhpar':'\u2AF2','ni':'\u220B','nis':'\u22FC','nisd':'\u22FA','niv':'\u220B','njcy':'\u045A','NJcy':'\u040A','nlarr':'\u219A','nlArr':'\u21CD','nldr':'\u2025','nle':'\u2270','nlE':'\u2266\u0338','nleftarrow':'\u219A','nLeftarrow':'\u21CD','nleftrightarrow':'\u21AE','nLeftrightarrow':'\u21CE','nleq':'\u2270','nleqq':'\u2266\u0338','nleqslant':'\u2A7D\u0338','nles':'\u2A7D\u0338','nless':'\u226E','nLl':'\u22D8\u0338','nlsim':'\u2274','nlt':'\u226E','nLt':'\u226A\u20D2','nltri':'\u22EA','nltrie':'\u22EC','nLtv':'\u226A\u0338','nmid':'\u2224','NoBreak':'\u2060','NonBreakingSpace':'\xA0','nopf':'\uD835\uDD5F','Nopf':'\u2115','not':'\xAC','Not':'\u2AEC','NotCongruent':'\u2262','NotCupCap':'\u226D','NotDoubleVerticalBar':'\u2226','NotElement':'\u2209','NotEqual':'\u2260','NotEqualTilde':'\u2242\u0338','NotExists':'\u2204','NotGreater':'\u226F','NotGreaterEqual':'\u2271','NotGreaterFullEqual':'\u2267\u0338','NotGreaterGreater':'\u226B\u0338','NotGreaterLess':'\u2279','NotGreaterSlantEqual':'\u2A7E\u0338','NotGreaterTilde':'\u2275','NotHumpDownHump':'\u224E\u0338','NotHumpEqual':'\u224F\u0338','notin':'\u2209','notindot':'\u22F5\u0338','notinE':'\u22F9\u0338','notinva':'\u2209','notinvb':'\u22F7','notinvc':'\u22F6','NotLeftTriangle':'\u22EA','NotLeftTriangleBar':'\u29CF\u0338','NotLeftTriangleEqual':'\u22EC','NotLess':'\u226E','NotLessEqual':'\u2270','NotLessGreater':'\u2278','NotLessLess':'\u226A\u0338','NotLessSlantEqual':'\u2A7D\u0338','NotLessTilde':'\u2274','NotNestedGreaterGreater':'\u2AA2\u0338','NotNestedLessLess':'\u2AA1\u0338','notni':'\u220C','notniva':'\u220C','notnivb':'\u22FE','notnivc':'\u22FD','NotPrecedes':'\u2280','NotPrecedesEqual':'\u2AAF\u0338','NotPrecedesSlantEqual':'\u22E0','NotReverseElement':'\u220C','NotRightTriangle':'\u22EB','NotRightTriangleBar':'\u29D0\u0338','NotRightTriangleEqual':'\u22ED','NotSquareSubset':'\u228F\u0338','NotSquareSubsetEqual':'\u22E2','NotSquareSuperset':'\u2290\u0338','NotSquareSupersetEqual':'\u22E3','NotSubset':'\u2282\u20D2','NotSubsetEqual':'\u2288','NotSucceeds':'\u2281','NotSucceedsEqual':'\u2AB0\u0338','NotSucceedsSlantEqual':'\u22E1','NotSucceedsTilde':'\u227F\u0338','NotSuperset':'\u2283\u20D2','NotSupersetEqual':'\u2289','NotTilde':'\u2241','NotTildeEqual':'\u2244','NotTildeFullEqual':'\u2247','NotTildeTilde':'\u2249','NotVerticalBar':'\u2224','npar':'\u2226','nparallel':'\u2226','nparsl':'\u2AFD\u20E5','npart':'\u2202\u0338','npolint':'\u2A14','npr':'\u2280','nprcue':'\u22E0','npre':'\u2AAF\u0338','nprec':'\u2280','npreceq':'\u2AAF\u0338','nrarr':'\u219B','nrArr':'\u21CF','nrarrc':'\u2933\u0338','nrarrw':'\u219D\u0338','nrightarrow':'\u219B','nRightarrow':'\u21CF','nrtri':'\u22EB','nrtrie':'\u22ED','nsc':'\u2281','nsccue':'\u22E1','nsce':'\u2AB0\u0338','nscr':'\uD835\uDCC3','Nscr':'\uD835\uDCA9','nshortmid':'\u2224','nshortparallel':'\u2226','nsim':'\u2241','nsime':'\u2244','nsimeq':'\u2244','nsmid':'\u2224','nspar':'\u2226','nsqsube':'\u22E2','nsqsupe':'\u22E3','nsub':'\u2284','nsube':'\u2288','nsubE':'\u2AC5\u0338','nsubset':'\u2282\u20D2','nsubseteq':'\u2288','nsubseteqq':'\u2AC5\u0338','nsucc':'\u2281','nsucceq':'\u2AB0\u0338','nsup':'\u2285','nsupe':'\u2289','nsupE':'\u2AC6\u0338','nsupset':'\u2283\u20D2','nsupseteq':'\u2289','nsupseteqq':'\u2AC6\u0338','ntgl':'\u2279','ntilde':'\xF1','Ntilde':'\xD1','ntlg':'\u2278','ntriangleleft':'\u22EA','ntrianglelefteq':'\u22EC','ntriangleright':'\u22EB','ntrianglerighteq':'\u22ED','nu':'\u03BD','Nu':'\u039D','num':'#','numero':'\u2116','numsp':'\u2007','nvap':'\u224D\u20D2','nvdash':'\u22AC','nvDash':'\u22AD','nVdash':'\u22AE','nVDash':'\u22AF','nvge':'\u2265\u20D2','nvgt':'>\u20D2','nvHarr':'\u2904','nvinfin':'\u29DE','nvlArr':'\u2902','nvle':'\u2264\u20D2','nvlt':'<\u20D2','nvltrie':'\u22B4\u20D2','nvrArr':'\u2903','nvrtrie':'\u22B5\u20D2','nvsim':'\u223C\u20D2','nwarhk':'\u2923','nwarr':'\u2196','nwArr':'\u21D6','nwarrow':'\u2196','nwnear':'\u2927','oacute':'\xF3','Oacute':'\xD3','oast':'\u229B','ocir':'\u229A','ocirc':'\xF4','Ocirc':'\xD4','ocy':'\u043E','Ocy':'\u041E','odash':'\u229D','odblac':'\u0151','Odblac':'\u0150','odiv':'\u2A38','odot':'\u2299','odsold':'\u29BC','oelig':'\u0153','OElig':'\u0152','ofcir':'\u29BF','ofr':'\uD835\uDD2C','Ofr':'\uD835\uDD12','ogon':'\u02DB','ograve':'\xF2','Ograve':'\xD2','ogt':'\u29C1','ohbar':'\u29B5','ohm':'\u03A9','oint':'\u222E','olarr':'\u21BA','olcir':'\u29BE','olcross':'\u29BB','oline':'\u203E','olt':'\u29C0','omacr':'\u014D','Omacr':'\u014C','omega':'\u03C9','Omega':'\u03A9','omicron':'\u03BF','Omicron':'\u039F','omid':'\u29B6','ominus':'\u2296','oopf':'\uD835\uDD60','Oopf':'\uD835\uDD46','opar':'\u29B7','OpenCurlyDoubleQuote':'\u201C','OpenCurlyQuote':'\u2018','operp':'\u29B9','oplus':'\u2295','or':'\u2228','Or':'\u2A54','orarr':'\u21BB','ord':'\u2A5D','order':'\u2134','orderof':'\u2134','ordf':'\xAA','ordm':'\xBA','origof':'\u22B6','oror':'\u2A56','orslope':'\u2A57','orv':'\u2A5B','oS':'\u24C8','oscr':'\u2134','Oscr':'\uD835\uDCAA','oslash':'\xF8','Oslash':'\xD8','osol':'\u2298','otilde':'\xF5','Otilde':'\xD5','otimes':'\u2297','Otimes':'\u2A37','otimesas':'\u2A36','ouml':'\xF6','Ouml':'\xD6','ovbar':'\u233D','OverBar':'\u203E','OverBrace':'\u23DE','OverBracket':'\u23B4','OverParenthesis':'\u23DC','par':'\u2225','para':'\xB6','parallel':'\u2225','parsim':'\u2AF3','parsl':'\u2AFD','part':'\u2202','PartialD':'\u2202','pcy':'\u043F','Pcy':'\u041F','percnt':'%','period':'.','permil':'\u2030','perp':'\u22A5','pertenk':'\u2031','pfr':'\uD835\uDD2D','Pfr':'\uD835\uDD13','phi':'\u03C6','Phi':'\u03A6','phiv':'\u03D5','phmmat':'\u2133','phone':'\u260E','pi':'\u03C0','Pi':'\u03A0','pitchfork':'\u22D4','piv':'\u03D6','planck':'\u210F','planckh':'\u210E','plankv':'\u210F','plus':'+','plusacir':'\u2A23','plusb':'\u229E','pluscir':'\u2A22','plusdo':'\u2214','plusdu':'\u2A25','pluse':'\u2A72','PlusMinus':'\xB1','plusmn':'\xB1','plussim':'\u2A26','plustwo':'\u2A27','pm':'\xB1','Poincareplane':'\u210C','pointint':'\u2A15','popf':'\uD835\uDD61','Popf':'\u2119','pound':'\xA3','pr':'\u227A','Pr':'\u2ABB','prap':'\u2AB7','prcue':'\u227C','pre':'\u2AAF','prE':'\u2AB3','prec':'\u227A','precapprox':'\u2AB7','preccurlyeq':'\u227C','Precedes':'\u227A','PrecedesEqual':'\u2AAF','PrecedesSlantEqual':'\u227C','PrecedesTilde':'\u227E','preceq':'\u2AAF','precnapprox':'\u2AB9','precneqq':'\u2AB5','precnsim':'\u22E8','precsim':'\u227E','prime':'\u2032','Prime':'\u2033','primes':'\u2119','prnap':'\u2AB9','prnE':'\u2AB5','prnsim':'\u22E8','prod':'\u220F','Product':'\u220F','profalar':'\u232E','profline':'\u2312','profsurf':'\u2313','prop':'\u221D','Proportion':'\u2237','Proportional':'\u221D','propto':'\u221D','prsim':'\u227E','prurel':'\u22B0','pscr':'\uD835\uDCC5','Pscr':'\uD835\uDCAB','psi':'\u03C8','Psi':'\u03A8','puncsp':'\u2008','qfr':'\uD835\uDD2E','Qfr':'\uD835\uDD14','qint':'\u2A0C','qopf':'\uD835\uDD62','Qopf':'\u211A','qprime':'\u2057','qscr':'\uD835\uDCC6','Qscr':'\uD835\uDCAC','quaternions':'\u210D','quatint':'\u2A16','quest':'?','questeq':'\u225F','quot':'"','QUOT':'"','rAarr':'\u21DB','race':'\u223D\u0331','racute':'\u0155','Racute':'\u0154','radic':'\u221A','raemptyv':'\u29B3','rang':'\u27E9','Rang':'\u27EB','rangd':'\u2992','range':'\u29A5','rangle':'\u27E9','raquo':'\xBB','rarr':'\u2192','rArr':'\u21D2','Rarr':'\u21A0','rarrap':'\u2975','rarrb':'\u21E5','rarrbfs':'\u2920','rarrc':'\u2933','rarrfs':'\u291E','rarrhk':'\u21AA','rarrlp':'\u21AC','rarrpl':'\u2945','rarrsim':'\u2974','rarrtl':'\u21A3','Rarrtl':'\u2916','rarrw':'\u219D','ratail':'\u291A','rAtail':'\u291C','ratio':'\u2236','rationals':'\u211A','rbarr':'\u290D','rBarr':'\u290F','RBarr':'\u2910','rbbrk':'\u2773','rbrace':'}','rbrack':']','rbrke':'\u298C','rbrksld':'\u298E','rbrkslu':'\u2990','rcaron':'\u0159','Rcaron':'\u0158','rcedil':'\u0157','Rcedil':'\u0156','rceil':'\u2309','rcub':'}','rcy':'\u0440','Rcy':'\u0420','rdca':'\u2937','rdldhar':'\u2969','rdquo':'\u201D','rdquor':'\u201D','rdsh':'\u21B3','Re':'\u211C','real':'\u211C','realine':'\u211B','realpart':'\u211C','reals':'\u211D','rect':'\u25AD','reg':'\xAE','REG':'\xAE','ReverseElement':'\u220B','ReverseEquilibrium':'\u21CB','ReverseUpEquilibrium':'\u296F','rfisht':'\u297D','rfloor':'\u230B','rfr':'\uD835\uDD2F','Rfr':'\u211C','rHar':'\u2964','rhard':'\u21C1','rharu':'\u21C0','rharul':'\u296C','rho':'\u03C1','Rho':'\u03A1','rhov':'\u03F1','RightAngleBracket':'\u27E9','rightarrow':'\u2192','Rightarrow':'\u21D2','RightArrow':'\u2192','RightArrowBar':'\u21E5','RightArrowLeftArrow':'\u21C4','rightarrowtail':'\u21A3','RightCeiling':'\u2309','RightDoubleBracket':'\u27E7','RightDownTeeVector':'\u295D','RightDownVector':'\u21C2','RightDownVectorBar':'\u2955','RightFloor':'\u230B','rightharpoondown':'\u21C1','rightharpoonup':'\u21C0','rightleftarrows':'\u21C4','rightleftharpoons':'\u21CC','rightrightarrows':'\u21C9','rightsquigarrow':'\u219D','RightTee':'\u22A2','RightTeeArrow':'\u21A6','RightTeeVector':'\u295B','rightthreetimes':'\u22CC','RightTriangle':'\u22B3','RightTriangleBar':'\u29D0','RightTriangleEqual':'\u22B5','RightUpDownVector':'\u294F','RightUpTeeVector':'\u295C','RightUpVector':'\u21BE','RightUpVectorBar':'\u2954','RightVector':'\u21C0','RightVectorBar':'\u2953','ring':'\u02DA','risingdotseq':'\u2253','rlarr':'\u21C4','rlhar':'\u21CC','rlm':'\u200F','rmoust':'\u23B1','rmoustache':'\u23B1','rnmid':'\u2AEE','roang':'\u27ED','roarr':'\u21FE','robrk':'\u27E7','ropar':'\u2986','ropf':'\uD835\uDD63','Ropf':'\u211D','roplus':'\u2A2E','rotimes':'\u2A35','RoundImplies':'\u2970','rpar':')','rpargt':'\u2994','rppolint':'\u2A12','rrarr':'\u21C9','Rrightarrow':'\u21DB','rsaquo':'\u203A','rscr':'\uD835\uDCC7','Rscr':'\u211B','rsh':'\u21B1','Rsh':'\u21B1','rsqb':']','rsquo':'\u2019','rsquor':'\u2019','rthree':'\u22CC','rtimes':'\u22CA','rtri':'\u25B9','rtrie':'\u22B5','rtrif':'\u25B8','rtriltri':'\u29CE','RuleDelayed':'\u29F4','ruluhar':'\u2968','rx':'\u211E','sacute':'\u015B','Sacute':'\u015A','sbquo':'\u201A','sc':'\u227B','Sc':'\u2ABC','scap':'\u2AB8','scaron':'\u0161','Scaron':'\u0160','sccue':'\u227D','sce':'\u2AB0','scE':'\u2AB4','scedil':'\u015F','Scedil':'\u015E','scirc':'\u015D','Scirc':'\u015C','scnap':'\u2ABA','scnE':'\u2AB6','scnsim':'\u22E9','scpolint':'\u2A13','scsim':'\u227F','scy':'\u0441','Scy':'\u0421','sdot':'\u22C5','sdotb':'\u22A1','sdote':'\u2A66','searhk':'\u2925','searr':'\u2198','seArr':'\u21D8','searrow':'\u2198','sect':'\xA7','semi':';','seswar':'\u2929','setminus':'\u2216','setmn':'\u2216','sext':'\u2736','sfr':'\uD835\uDD30','Sfr':'\uD835\uDD16','sfrown':'\u2322','sharp':'\u266F','shchcy':'\u0449','SHCHcy':'\u0429','shcy':'\u0448','SHcy':'\u0428','ShortDownArrow':'\u2193','ShortLeftArrow':'\u2190','shortmid':'\u2223','shortparallel':'\u2225','ShortRightArrow':'\u2192','ShortUpArrow':'\u2191','shy':'\xAD','sigma':'\u03C3','Sigma':'\u03A3','sigmaf':'\u03C2','sigmav':'\u03C2','sim':'\u223C','simdot':'\u2A6A','sime':'\u2243','simeq':'\u2243','simg':'\u2A9E','simgE':'\u2AA0','siml':'\u2A9D','simlE':'\u2A9F','simne':'\u2246','simplus':'\u2A24','simrarr':'\u2972','slarr':'\u2190','SmallCircle':'\u2218','smallsetminus':'\u2216','smashp':'\u2A33','smeparsl':'\u29E4','smid':'\u2223','smile':'\u2323','smt':'\u2AAA','smte':'\u2AAC','smtes':'\u2AAC\uFE00','softcy':'\u044C','SOFTcy':'\u042C','sol':'/','solb':'\u29C4','solbar':'\u233F','sopf':'\uD835\uDD64','Sopf':'\uD835\uDD4A','spades':'\u2660','spadesuit':'\u2660','spar':'\u2225','sqcap':'\u2293','sqcaps':'\u2293\uFE00','sqcup':'\u2294','sqcups':'\u2294\uFE00','Sqrt':'\u221A','sqsub':'\u228F','sqsube':'\u2291','sqsubset':'\u228F','sqsubseteq':'\u2291','sqsup':'\u2290','sqsupe':'\u2292','sqsupset':'\u2290','sqsupseteq':'\u2292','squ':'\u25A1','square':'\u25A1','Square':'\u25A1','SquareIntersection':'\u2293','SquareSubset':'\u228F','SquareSubsetEqual':'\u2291','SquareSuperset':'\u2290','SquareSupersetEqual':'\u2292','SquareUnion':'\u2294','squarf':'\u25AA','squf':'\u25AA','srarr':'\u2192','sscr':'\uD835\uDCC8','Sscr':'\uD835\uDCAE','ssetmn':'\u2216','ssmile':'\u2323','sstarf':'\u22C6','star':'\u2606','Star':'\u22C6','starf':'\u2605','straightepsilon':'\u03F5','straightphi':'\u03D5','strns':'\xAF','sub':'\u2282','Sub':'\u22D0','subdot':'\u2ABD','sube':'\u2286','subE':'\u2AC5','subedot':'\u2AC3','submult':'\u2AC1','subne':'\u228A','subnE':'\u2ACB','subplus':'\u2ABF','subrarr':'\u2979','subset':'\u2282','Subset':'\u22D0','subseteq':'\u2286','subseteqq':'\u2AC5','SubsetEqual':'\u2286','subsetneq':'\u228A','subsetneqq':'\u2ACB','subsim':'\u2AC7','subsub':'\u2AD5','subsup':'\u2AD3','succ':'\u227B','succapprox':'\u2AB8','succcurlyeq':'\u227D','Succeeds':'\u227B','SucceedsEqual':'\u2AB0','SucceedsSlantEqual':'\u227D','SucceedsTilde':'\u227F','succeq':'\u2AB0','succnapprox':'\u2ABA','succneqq':'\u2AB6','succnsim':'\u22E9','succsim':'\u227F','SuchThat':'\u220B','sum':'\u2211','Sum':'\u2211','sung':'\u266A','sup':'\u2283','Sup':'\u22D1','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','supdot':'\u2ABE','supdsub':'\u2AD8','supe':'\u2287','supE':'\u2AC6','supedot':'\u2AC4','Superset':'\u2283','SupersetEqual':'\u2287','suphsol':'\u27C9','suphsub':'\u2AD7','suplarr':'\u297B','supmult':'\u2AC2','supne':'\u228B','supnE':'\u2ACC','supplus':'\u2AC0','supset':'\u2283','Supset':'\u22D1','supseteq':'\u2287','supseteqq':'\u2AC6','supsetneq':'\u228B','supsetneqq':'\u2ACC','supsim':'\u2AC8','supsub':'\u2AD4','supsup':'\u2AD6','swarhk':'\u2926','swarr':'\u2199','swArr':'\u21D9','swarrow':'\u2199','swnwar':'\u292A','szlig':'\xDF','Tab':'\t','target':'\u2316','tau':'\u03C4','Tau':'\u03A4','tbrk':'\u23B4','tcaron':'\u0165','Tcaron':'\u0164','tcedil':'\u0163','Tcedil':'\u0162','tcy':'\u0442','Tcy':'\u0422','tdot':'\u20DB','telrec':'\u2315','tfr':'\uD835\uDD31','Tfr':'\uD835\uDD17','there4':'\u2234','therefore':'\u2234','Therefore':'\u2234','theta':'\u03B8','Theta':'\u0398','thetasym':'\u03D1','thetav':'\u03D1','thickapprox':'\u2248','thicksim':'\u223C','ThickSpace':'\u205F\u200A','thinsp':'\u2009','ThinSpace':'\u2009','thkap':'\u2248','thksim':'\u223C','thorn':'\xFE','THORN':'\xDE','tilde':'\u02DC','Tilde':'\u223C','TildeEqual':'\u2243','TildeFullEqual':'\u2245','TildeTilde':'\u2248','times':'\xD7','timesb':'\u22A0','timesbar':'\u2A31','timesd':'\u2A30','tint':'\u222D','toea':'\u2928','top':'\u22A4','topbot':'\u2336','topcir':'\u2AF1','topf':'\uD835\uDD65','Topf':'\uD835\uDD4B','topfork':'\u2ADA','tosa':'\u2929','tprime':'\u2034','trade':'\u2122','TRADE':'\u2122','triangle':'\u25B5','triangledown':'\u25BF','triangleleft':'\u25C3','trianglelefteq':'\u22B4','triangleq':'\u225C','triangleright':'\u25B9','trianglerighteq':'\u22B5','tridot':'\u25EC','trie':'\u225C','triminus':'\u2A3A','TripleDot':'\u20DB','triplus':'\u2A39','trisb':'\u29CD','tritime':'\u2A3B','trpezium':'\u23E2','tscr':'\uD835\uDCC9','Tscr':'\uD835\uDCAF','tscy':'\u0446','TScy':'\u0426','tshcy':'\u045B','TSHcy':'\u040B','tstrok':'\u0167','Tstrok':'\u0166','twixt':'\u226C','twoheadleftarrow':'\u219E','twoheadrightarrow':'\u21A0','uacute':'\xFA','Uacute':'\xDA','uarr':'\u2191','uArr':'\u21D1','Uarr':'\u219F','Uarrocir':'\u2949','ubrcy':'\u045E','Ubrcy':'\u040E','ubreve':'\u016D','Ubreve':'\u016C','ucirc':'\xFB','Ucirc':'\xDB','ucy':'\u0443','Ucy':'\u0423','udarr':'\u21C5','udblac':'\u0171','Udblac':'\u0170','udhar':'\u296E','ufisht':'\u297E','ufr':'\uD835\uDD32','Ufr':'\uD835\uDD18','ugrave':'\xF9','Ugrave':'\xD9','uHar':'\u2963','uharl':'\u21BF','uharr':'\u21BE','uhblk':'\u2580','ulcorn':'\u231C','ulcorner':'\u231C','ulcrop':'\u230F','ultri':'\u25F8','umacr':'\u016B','Umacr':'\u016A','uml':'\xA8','UnderBar':'_','UnderBrace':'\u23DF','UnderBracket':'\u23B5','UnderParenthesis':'\u23DD','Union':'\u22C3','UnionPlus':'\u228E','uogon':'\u0173','Uogon':'\u0172','uopf':'\uD835\uDD66','Uopf':'\uD835\uDD4C','uparrow':'\u2191','Uparrow':'\u21D1','UpArrow':'\u2191','UpArrowBar':'\u2912','UpArrowDownArrow':'\u21C5','updownarrow':'\u2195','Updownarrow':'\u21D5','UpDownArrow':'\u2195','UpEquilibrium':'\u296E','upharpoonleft':'\u21BF','upharpoonright':'\u21BE','uplus':'\u228E','UpperLeftArrow':'\u2196','UpperRightArrow':'\u2197','upsi':'\u03C5','Upsi':'\u03D2','upsih':'\u03D2','upsilon':'\u03C5','Upsilon':'\u03A5','UpTee':'\u22A5','UpTeeArrow':'\u21A5','upuparrows':'\u21C8','urcorn':'\u231D','urcorner':'\u231D','urcrop':'\u230E','uring':'\u016F','Uring':'\u016E','urtri':'\u25F9','uscr':'\uD835\uDCCA','Uscr':'\uD835\uDCB0','utdot':'\u22F0','utilde':'\u0169','Utilde':'\u0168','utri':'\u25B5','utrif':'\u25B4','uuarr':'\u21C8','uuml':'\xFC','Uuml':'\xDC','uwangle':'\u29A7','vangrt':'\u299C','varepsilon':'\u03F5','varkappa':'\u03F0','varnothing':'\u2205','varphi':'\u03D5','varpi':'\u03D6','varpropto':'\u221D','varr':'\u2195','vArr':'\u21D5','varrho':'\u03F1','varsigma':'\u03C2','varsubsetneq':'\u228A\uFE00','varsubsetneqq':'\u2ACB\uFE00','varsupsetneq':'\u228B\uFE00','varsupsetneqq':'\u2ACC\uFE00','vartheta':'\u03D1','vartriangleleft':'\u22B2','vartriangleright':'\u22B3','vBar':'\u2AE8','Vbar':'\u2AEB','vBarv':'\u2AE9','vcy':'\u0432','Vcy':'\u0412','vdash':'\u22A2','vDash':'\u22A8','Vdash':'\u22A9','VDash':'\u22AB','Vdashl':'\u2AE6','vee':'\u2228','Vee':'\u22C1','veebar':'\u22BB','veeeq':'\u225A','vellip':'\u22EE','verbar':'|','Verbar':'\u2016','vert':'|','Vert':'\u2016','VerticalBar':'\u2223','VerticalLine':'|','VerticalSeparator':'\u2758','VerticalTilde':'\u2240','VeryThinSpace':'\u200A','vfr':'\uD835\uDD33','Vfr':'\uD835\uDD19','vltri':'\u22B2','vnsub':'\u2282\u20D2','vnsup':'\u2283\u20D2','vopf':'\uD835\uDD67','Vopf':'\uD835\uDD4D','vprop':'\u221D','vrtri':'\u22B3','vscr':'\uD835\uDCCB','Vscr':'\uD835\uDCB1','vsubne':'\u228A\uFE00','vsubnE':'\u2ACB\uFE00','vsupne':'\u228B\uFE00','vsupnE':'\u2ACC\uFE00','Vvdash':'\u22AA','vzigzag':'\u299A','wcirc':'\u0175','Wcirc':'\u0174','wedbar':'\u2A5F','wedge':'\u2227','Wedge':'\u22C0','wedgeq':'\u2259','weierp':'\u2118','wfr':'\uD835\uDD34','Wfr':'\uD835\uDD1A','wopf':'\uD835\uDD68','Wopf':'\uD835\uDD4E','wp':'\u2118','wr':'\u2240','wreath':'\u2240','wscr':'\uD835\uDCCC','Wscr':'\uD835\uDCB2','xcap':'\u22C2','xcirc':'\u25EF','xcup':'\u22C3','xdtri':'\u25BD','xfr':'\uD835\uDD35','Xfr':'\uD835\uDD1B','xharr':'\u27F7','xhArr':'\u27FA','xi':'\u03BE','Xi':'\u039E','xlarr':'\u27F5','xlArr':'\u27F8','xmap':'\u27FC','xnis':'\u22FB','xodot':'\u2A00','xopf':'\uD835\uDD69','Xopf':'\uD835\uDD4F','xoplus':'\u2A01','xotime':'\u2A02','xrarr':'\u27F6','xrArr':'\u27F9','xscr':'\uD835\uDCCD','Xscr':'\uD835\uDCB3','xsqcup':'\u2A06','xuplus':'\u2A04','xutri':'\u25B3','xvee':'\u22C1','xwedge':'\u22C0','yacute':'\xFD','Yacute':'\xDD','yacy':'\u044F','YAcy':'\u042F','ycirc':'\u0177','Ycirc':'\u0176','ycy':'\u044B','Ycy':'\u042B','yen':'\xA5','yfr':'\uD835\uDD36','Yfr':'\uD835\uDD1C','yicy':'\u0457','YIcy':'\u0407','yopf':'\uD835\uDD6A','Yopf':'\uD835\uDD50','yscr':'\uD835\uDCCE','Yscr':'\uD835\uDCB4','yucy':'\u044E','YUcy':'\u042E','yuml':'\xFF','Yuml':'\u0178','zacute':'\u017A','Zacute':'\u0179','zcaron':'\u017E','Zcaron':'\u017D','zcy':'\u0437','Zcy':'\u0417','zdot':'\u017C','Zdot':'\u017B','zeetrf':'\u2128','ZeroWidthSpace':'\u200B','zeta':'\u03B6','Zeta':'\u0396','zfr':'\uD835\uDD37','Zfr':'\u2128','zhcy':'\u0436','ZHcy':'\u0416','zigrarr':'\u21DD','zopf':'\uD835\uDD6B','Zopf':'\u2124','zscr':'\uD835\uDCCF','Zscr':'\uD835\uDCB5','zwj':'\u200D','zwnj':'\u200C'}; + var decodeMapLegacy = {'aacute':'\xE1','Aacute':'\xC1','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','aelig':'\xE6','AElig':'\xC6','agrave':'\xE0','Agrave':'\xC0','amp':'&','AMP':'&','aring':'\xE5','Aring':'\xC5','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','brvbar':'\xA6','ccedil':'\xE7','Ccedil':'\xC7','cedil':'\xB8','cent':'\xA2','copy':'\xA9','COPY':'\xA9','curren':'\xA4','deg':'\xB0','divide':'\xF7','eacute':'\xE9','Eacute':'\xC9','ecirc':'\xEA','Ecirc':'\xCA','egrave':'\xE8','Egrave':'\xC8','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','frac12':'\xBD','frac14':'\xBC','frac34':'\xBE','gt':'>','GT':'>','iacute':'\xED','Iacute':'\xCD','icirc':'\xEE','Icirc':'\xCE','iexcl':'\xA1','igrave':'\xEC','Igrave':'\xCC','iquest':'\xBF','iuml':'\xEF','Iuml':'\xCF','laquo':'\xAB','lt':'<','LT':'<','macr':'\xAF','micro':'\xB5','middot':'\xB7','nbsp':'\xA0','not':'\xAC','ntilde':'\xF1','Ntilde':'\xD1','oacute':'\xF3','Oacute':'\xD3','ocirc':'\xF4','Ocirc':'\xD4','ograve':'\xF2','Ograve':'\xD2','ordf':'\xAA','ordm':'\xBA','oslash':'\xF8','Oslash':'\xD8','otilde':'\xF5','Otilde':'\xD5','ouml':'\xF6','Ouml':'\xD6','para':'\xB6','plusmn':'\xB1','pound':'\xA3','quot':'"','QUOT':'"','raquo':'\xBB','reg':'\xAE','REG':'\xAE','sect':'\xA7','shy':'\xAD','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','szlig':'\xDF','thorn':'\xFE','THORN':'\xDE','times':'\xD7','uacute':'\xFA','Uacute':'\xDA','ucirc':'\xFB','Ucirc':'\xDB','ugrave':'\xF9','Ugrave':'\xD9','uml':'\xA8','uuml':'\xFC','Uuml':'\xDC','yacute':'\xFD','Yacute':'\xDD','yen':'\xA5','yuml':'\xFF'}; + var decodeMapNumeric = {'0':'\uFFFD','128':'\u20AC','130':'\u201A','131':'\u0192','132':'\u201E','133':'\u2026','134':'\u2020','135':'\u2021','136':'\u02C6','137':'\u2030','138':'\u0160','139':'\u2039','140':'\u0152','142':'\u017D','145':'\u2018','146':'\u2019','147':'\u201C','148':'\u201D','149':'\u2022','150':'\u2013','151':'\u2014','152':'\u02DC','153':'\u2122','154':'\u0161','155':'\u203A','156':'\u0153','158':'\u017E','159':'\u0178'}; + var invalidReferenceCodePoints = [1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65000,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111]; + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + var has = function(object, propertyName) { + return hasOwnProperty.call(object, propertyName); + }; + + var contains = function(array, value) { + var index = -1; + var length = array.length; + while (++index < length) { + if (array[index] == value) { + return true; + } + } + return false; + }; + + var merge = function(options, defaults) { + if (!options) { + return defaults; + } + var result = {}; + var key; + for (key in defaults) { + // A `hasOwnProperty` check is not needed here, since only recognized + // option names are used anyway. Any others are ignored. + result[key] = has(options, key) ? options[key] : defaults[key]; + } + return result; + }; + + // Modified version of `ucs2encode`; see https://mths.be/punycode. + var codePointToSymbol = function(codePoint, strict) { + var output = ''; + if ((codePoint >= 0xD800 && codePoint <= 0xDFFF) || codePoint > 0x10FFFF) { + // See issue #4: + // “Otherwise, if the number is in the range 0xD800 to 0xDFFF or is + // greater than 0x10FFFF, then this is a parse error. Return a U+FFFD + // REPLACEMENT CHARACTER.” + if (strict) { + parseError('character reference outside the permissible Unicode range'); + } + return '\uFFFD'; + } + if (has(decodeMapNumeric, codePoint)) { + if (strict) { + parseError('disallowed character reference'); + } + return decodeMapNumeric[codePoint]; + } + if (strict && contains(invalidReferenceCodePoints, codePoint)) { + parseError('disallowed character reference'); + } + if (codePoint > 0xFFFF) { + codePoint -= 0x10000; + output += stringFromCharCode(codePoint >>> 10 & 0x3FF | 0xD800); + codePoint = 0xDC00 | codePoint & 0x3FF; + } + output += stringFromCharCode(codePoint); + return output; + }; + + var hexEscape = function(codePoint) { + return '&#x' + codePoint.toString(16).toUpperCase() + ';'; + }; + + var decEscape = function(codePoint) { + return '&#' + codePoint + ';'; + }; + + var parseError = function(message) { + throw Error('Parse error: ' + message); + }; + + /*--------------------------------------------------------------------------*/ + + var encode = function(string, options) { + options = merge(options, encode.options); + var strict = options.strict; + if (strict && regexInvalidRawCodePoint.test(string)) { + parseError('forbidden code point'); + } + var encodeEverything = options.encodeEverything; + var useNamedReferences = options.useNamedReferences; + var allowUnsafeSymbols = options.allowUnsafeSymbols; + var escapeCodePoint = options.decimal ? decEscape : hexEscape; + + var escapeBmpSymbol = function(symbol) { + return escapeCodePoint(symbol.charCodeAt(0)); + }; + + if (encodeEverything) { + // Encode ASCII symbols. + string = string.replace(regexAsciiWhitelist, function(symbol) { + // Use named references if requested & possible. + if (useNamedReferences && has(encodeMap, symbol)) { + return '&' + encodeMap[symbol] + ';'; + } + return escapeBmpSymbol(symbol); + }); + // Shorten a few escapes that represent two symbols, of which at least one + // is within the ASCII range. + if (useNamedReferences) { + string = string + .replace(/>\u20D2/g, '>⃒') + .replace(/<\u20D2/g, '<⃒') + .replace(/fj/g, 'fj'); + } + // Encode non-ASCII symbols. + if (useNamedReferences) { + // Encode non-ASCII symbols that can be replaced with a named reference. + string = string.replace(regexEncodeNonAscii, function(string) { + // Note: there is no need to check `has(encodeMap, string)` here. + return '&' + encodeMap[string] + ';'; + }); + } + // Note: any remaining non-ASCII symbols are handled outside of the `if`. + } else if (useNamedReferences) { + // Apply named character references. + // Encode `<>"'&` using named character references. + if (!allowUnsafeSymbols) { + string = string.replace(regexEscape, function(string) { + return '&' + encodeMap[string] + ';'; // no need to check `has()` here + }); + } + // Shorten escapes that represent two symbols, of which at least one is + // `<>"'&`. + string = string + .replace(/>\u20D2/g, '>⃒') + .replace(/<\u20D2/g, '<⃒'); + // Encode non-ASCII symbols that can be replaced with a named reference. + string = string.replace(regexEncodeNonAscii, function(string) { + // Note: there is no need to check `has(encodeMap, string)` here. + return '&' + encodeMap[string] + ';'; + }); + } else if (!allowUnsafeSymbols) { + // Encode `<>"'&` using hexadecimal escapes, now that they’re not handled + // using named character references. + string = string.replace(regexEscape, escapeBmpSymbol); + } + return string + // Encode astral symbols. + .replace(regexAstralSymbols, function($0) { + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + var high = $0.charCodeAt(0); + var low = $0.charCodeAt(1); + var codePoint = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000; + return escapeCodePoint(codePoint); + }) + // Encode any remaining BMP symbols that are not printable ASCII symbols + // using a hexadecimal escape. + .replace(regexBmpWhitelist, escapeBmpSymbol); + }; + // Expose default options (so they can be overridden globally). + encode.options = { + 'allowUnsafeSymbols': false, + 'encodeEverything': false, + 'strict': false, + 'useNamedReferences': false, + 'decimal' : false + }; + + var decode = function(html, options) { + options = merge(options, decode.options); + var strict = options.strict; + if (strict && regexInvalidEntity.test(html)) { + parseError('malformed character reference'); + } + return html.replace(regexDecode, function($0, $1, $2, $3, $4, $5, $6, $7) { + var codePoint; + var semicolon; + var decDigits; + var hexDigits; + var reference; + var next; + if ($1) { + // Decode decimal escapes, e.g. `𝌆`. + decDigits = $1; + semicolon = $2; + if (strict && !semicolon) { + parseError('character reference was not terminated by a semicolon'); + } + codePoint = parseInt(decDigits, 10); + return codePointToSymbol(codePoint, strict); + } + if ($3) { + // Decode hexadecimal escapes, e.g. `𝌆`. + hexDigits = $3; + semicolon = $4; + if (strict && !semicolon) { + parseError('character reference was not terminated by a semicolon'); + } + codePoint = parseInt(hexDigits, 16); + return codePointToSymbol(codePoint, strict); + } + if ($5) { + // Decode named character references with trailing `;`, e.g. `©`. + reference = $5; + if (has(decodeMap, reference)) { + return decodeMap[reference]; + } else { + // Ambiguous ampersand. https://mths.be/notes/ambiguous-ampersands + if (strict) { + parseError( + 'named character reference was not terminated by a semicolon' + ); + } + return $0; + } + } + // If we’re still here, it’s a legacy reference for sure. No need for an + // extra `if` check. + // Decode named character references without trailing `;`, e.g. `&` + // This is only a parse error if it gets converted to `&`, or if it is + // followed by `=` in an attribute context. + reference = $6; + next = $7; + if (next && options.isAttributeValue) { + if (strict && next == '=') { + parseError('`&` did not start a character reference'); + } + return $0; + } else { + if (strict) { + parseError( + 'named character reference was not terminated by a semicolon' + ); + } + // Note: there is no need to check `has(decodeMapLegacy, reference)`. + return decodeMapLegacy[reference] + (next || ''); + } + }); + }; + // Expose default options (so they can be overridden globally). + decode.options = { + 'isAttributeValue': false, + 'strict': false + }; + + var escape = function(string) { + return string.replace(regexEscape, function($0) { + // Note: there is no need to check `has(escapeMap, $0)` here. + return escapeMap[$0]; + }); + }; + + /*--------------------------------------------------------------------------*/ + + var he = { + 'version': '1.1.1', + 'encode': encode, + 'decode': decode, + 'escape': escape, + 'unescape': decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return he; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = he; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in he) { + has(he, key) && (freeExports[key] = he[key]); + } + } + } else { // in Rhino or a web browser + root.he = he; + } + +}(this)); \ No newline at end of file diff --git a/webapp/candysorter/static/js/init.js b/webapp/candysorter/static/js/init.js index f528c4c..a27c7de 100644 --- a/webapp/candysorter/static/js/init.js +++ b/webapp/candysorter/static/js/init.js @@ -1,392 +1,567 @@ $(function () { + // UI variables + var winH = window.innerHeight; + var winW = window.innerWidth; - // API settings - var pid = Math.floor(Math.random() * 10000000000000000); // POST ID - var morUrl = "/api/morphs"; // API for Morphological analysis - var simUrl = "/api/similarities"; // API for Similarity analysis - var pickUrl = "/api/pickup"; // API for pick up candy - var simSec = 5000; // delay time - var simNoWaitNum = 5; - var plotSec = 5000; // display time of scatter plot(milisec) - var camSec = 3000; // display tiem of camera image(milisec) - - // variables - var recognition = new webkitSpeechRecognition(); - var lang = "en"; // language seting - var speechTxt = "I like chewy chocolate candy"; - var sim = ""; - var winW = window.innerWidth; - var winH = window.innerHeight; - - // process of voice recognition - var speech = function () { - $("body").addClass("mode-speech-start"); - recognition.lang = lang; - $(".speech-mic").click(function () { - $("body").addClass("mode-speech-in"); - recognition.start(); - }); - recognition.onerror = function () { - $("body").removeClass("mode-speech-in"); - }; - recognition.onresult = function (e) { - speechTxt = e.results[0][0].transcript - $(".speech-out").text(speechTxt); - $("body").addClass("mode-speech-out"); - setTimeout(nl, 1500); - }; - } - - // switch language - $(".speech-lang a").click(function () { - if ($(this).text() == "EN") { - $(this).text("JP"); - lang = "ja"; - } else { - $(this).text("EN"); - lang = "en"; - } - recognition.lang = lang; - return false; - }); - - // NL processing - var nl = function () { - var morXHR = null; - var simXHR = null; - - morXHR = $.ajax({ - type: "POST", - contentType: "application/json", - dataType: "json", - url: morUrl, - data: JSON.stringify({ - "id": pid, - "text": speechTxt, - "lang": lang - }), - error: function (jqXHR, textStatus) { - if (textStatus == 'abort') { return; } - console.log(jqXHR); - if (simXHR !== null && simXHR.readyState > 0 && simXHR.readyState < 4) { - simAjax.abort(); - } - sorry(); - }, - success: function (data) { - // generate morpheme - data = data.morphs - for (var i in data) { - var morph = ""; - for (key in data[i].pos) { - var txt = data[i].pos[key]; - if (key != "tag" && txt.indexOf("UNKNOWN") == -1) { - morph += key + "=" + txt + "
"; - } - } - var desc = "
"; - desc += "
" + data[i].depend.label + "
"; - desc += "
" + data[i].word + "
"; - desc += "
" + data[i].pos.tag + "
"; - desc += "
" + morph + "
"; - desc += "
" - $(".nl-syntax").append(desc); - // generate arrow - for (var j in data[i].depend.index) { - $(".nl-depend").append("
"); - } - } - // measure X coordinate - var dependX = []; - $(".nl-syntax dl").each(function (index) { - var x = $(".nl-syntax dl:nth-child(" + (index + 1) + ")").position().left; - var w = $(".nl-syntax dl:nth-child(" + (index + 1) + ")").outerWidth(); - dependX.push(Math.round(x + w / 2)); - }); - // rearrange arrow - $(".nl-depend dd").each(function (index) { - var from = $(this).data().from; - var to = $(this).data().to; - if (from < to) { - var x = dependX[from]; - var w = dependX[to] - dependX[from]; - } else { - var x = dependX[to]; - var w = dependX[from] - dependX[to]; - $(this).addClass("left"); - } - $(this).css({ - top: (Math.abs(from - to) - 1) * -30 + "px", - left: x + "px", - width: w + "px" - }); - }); - // effect settings - $(".nl-word").each(function (index) { - $(this).css("transition-delay", index / 5 + "s"); - }); - $(".nl-tag, .nl-pos").each(function (index) { - $(this).css("transition-delay", 1 + index / 10 + "s"); - }); - $(".nl-label, .nl-depend dd").css("transition-delay", 2.5 + "s"); - $("body").addClass("mode-nl-loaded"); - // repeat effects - setInterval(function () { - $("body").addClass("mode-nl-repeat"); - setTimeout(function () { - $("body").removeClass("mode-nl-loaded"); - }, 400); - setTimeout(function () { - $("body").addClass("mode-nl-loaded"); - $("body").removeClass("mode-nl-repeat"); - }, 500); - }, 6000); - } - }); - // retrieve inference data - simXHR = $.ajax({ - type: "POST", - contentType: "application/json", - dataType: "json", - url: simUrl, - data: JSON.stringify({ - "id": pid, - "text": speechTxt, - "lang": lang - }), - error: function (jqXHR, textStatus) { - if (textStatus == 'abort') { return; } - console.log(jqXHR); - if (morXHR !== null && morXHR.readyState > 0 && morXHR.readyState < 4) { - morXHR.abort(); - } - sorry(); - }, - success: function (data) { - var sec = data.similarities.embedded.length >= simNoWaitNum ? 0 : simSec; - sim = data; - setTimeout(function () { - force(); - plot(); - }, sec); - } - }); - }; - - // drow force layout - var force = function () { - $("body").addClass("mode-force-start"); - // generate dataset - var data = sim.similarities.force; - var dataSet = { - "nodes": [], - "links": [] - }; - for (var i in data) { - dataSet.nodes.push(data[i]); - dataSet.links.push({ - "source": 0, - "target": parseInt(i) + 1 - }); - } - // Add a node for input string at the beginning of the data set - dataSet.nodes.unshift({ - "label": "", - "lid": 0, - "em": 0, - "x": winW / 2, - "y": winH / 2, - "fixed": true - }); - // create SVG - var svg = d3.select(".force").append("svg").attr({ - width: winW, - height: winH - }); - // layout setttings - var d3force = d3.layout.force() - .nodes(dataSet.nodes) - .links(dataSet.links) - .size([winW, winH]) - .linkDistance(250) - .charge(-1000) - .start(); - var link = svg.selectAll("line") - .data(dataSet.links) - .enter() - .append("line"); - var g = svg.selectAll("g") - .data(dataSet.nodes) - .enter() - .append("g") - .attr("class", function (d) { - return "label-" + d.lid; - }); - var circle = g.append("circle") - .attr("r", function (d) { - var r = 50 + d.em * 100; - return r; - }); - var label = g.append("text") - .text(function (d) { - return d.label; - }); - // setting coorinate (input string(lid=0)fix the mid of display) - d3force.on("tick", function () { - g.attr("transform", function (d) { - return "translate(" + d.x + "," + d.y + ")"; - }); - link.attr("x1", function (d) { - return d.source.x; - }) - .attr("y1", function (d) { - return d.source.y; - }) - .attr("x2", function (d) { - return d.target.x; - }) - .attr("y2", function (d) { - return d.target.y; - }); - }); - // Place the input character string in the center of the screen - $(".force").prepend("
" + speechTxt + "
"); - var txtW = $(".force-txt").outerWidth(); - var txtH = $(".force-txt").outerHeight(); - $(".force-txt").css({ - top: (winH - txtH) / 2 + "px", - left: (winW - txtW) / 2 + "px" - }); - }; - - // draw scatter plot - var plot = function () { - // generate dataset - var data = sim.similarities.embedded; - var dataSet = []; - for (var i in data) { - var em = 0; // extract hight similarity - var lid = 0; - for (var j in data[i].similarities) { - if (data[i].similarities[j].em > em) { - lid = data[i].similarities[j].lid; - em = data[i].similarities[j].em; - } - } - dataSet.push({ - "x": data[i].coords[0] * winW, - "y": data[i].coords[1] * winH, - "img": data[i].url, - "lid": lid - }); - } - // add nearest at the last of dataset - data = sim.similarities.nearest; - em = 0; // extract high similarity label - lid = 0; - for (var i in data.similarities) { - if (data.similarities[i].em > em) { - lid = data.similarities[i].lid; - em = data.similarities[i].em; - } - } - dataSet.push({ - "x": data.coords[0] * winW, - "y": data.coords[1] * winH, - "img": data.url, - "lid": lid - }); - $(".cam polygon").addClass("label-" + lid); - // draw scatter plot - for (var i in dataSet) { - $(".plot").append("
"); - $(".plot dd:last-child").addClass("label-" + dataSet[i].lid) - .css({ - left: dataSet[i].x + "px", - top: dataSet[i].y + "px", - transitionDelay: parseInt(i) * 0.05 + "s", - animationDelay: parseInt(i) * 0.05 + "s" - }); - $(".plot dd:last-child i") - .css({ - backgroundImage: "url(" + dataSet[i].img + ")" - }); - } - $(".plot dd:last-child").addClass("nearest"); - // draw with time difference - setTimeout(function () { - $("body").addClass("mode-plot-start"); - }, 3000); - setTimeout(function () { - $("body").addClass("mode-plot-end"); - cam(); - }, plotSec); - }; - - // output camera image - var cam = function () { - var imgUrl = sim.similarities.url; - // retrieve image size - var img = new Image(); - img.src = imgUrl; - img.onload = function () { - var w = img.width; - var h = img.height; - $(".cam-img svg").attr("viewBox", "0 0 " + w + " " + h); - if (w > winW) { - h = h / w * winW; - w = winW; - } - if (h > winH) { - w = w / h * winH; - h = winH; - } - $(".cam-img").width(w).height(h); - }; - // setting image - $(".cam-img").css("background-image", "url(" + imgUrl + ")"); - var box = sim.similarities.nearest.box; - $(".cam polygon").attr("points", box[0][0] + "," + box[0][1] + " " + box[1][0] + "," + box[1][1] + " " + box[2][0] + "," + box[2][1] + " " + box[3][0] + "," + box[3][1] + " "); - // draw with time difference - setTimeout(function () { - $("body").addClass("mode-cam-start"); - }, 2000); - setTimeout(function () { - thanks(); - }, camSec); - // operation of pickup - $.ajax({ - type: "POST", - contentType: "application/json", - dataType: "json", - url: pickUrl, - data: JSON.stringify({ - "id": pid - }), - error: function (textStatus) { - console.log(textStatus); - }, - success: function (data) { - sim = data; - } - }); - }; - - // draw endroll - var thanks = function () { - $("body").addClass("mode-thanks-start"); - setTimeout(function () { - $("body").addClass("mode-thanks-end"); - }, 3000); - setTimeout(function () { - $("body").addClass("mode-thanks-btn"); - }, 5000); - }; - - // draw sorry - var sorry = function () { - $("body").addClass("mode-sorry-p"); - }; - - speech(); + // Get config object + var config = new FycConfig(); + + let timeoutManager = new TimeoutManager(); + + // API settings + var pid = Math.floor(Math.random() * 10000000000000000); // POST ID + var morUrl = "/api/morphs"; // API for Morphological analysis + var simUrl = "/api/similarities"; // API for Similarity analysis + var pickUrl = "/api/pickup"; // API for pick up candy + var tranUrl = "/api/translate"; // API for translation + + // variables + var inputSpeech = "Kan jeg få sjokolade"; // Spoken sentence + var speechTxt = ""; // Translated text/text used for nl and similiarities + var sim = ""; + + + // Box candy suggestions + //var examples = ["\"Kan jeg få sjokolade?\"","\"Jeg liker smurf\"","\"Kan jeg få lakris?\"", "\"Kan jeg få noe søtt?\""] + //Twist suggestions + //var examples = ["\"Kan jeg noe med nøtter?\"","\"Jeg liker karamell\"","\"Kan jeg få noe salt?\"", "\"Kan jeg få noe søtt?\"", "\"Jeg liker kokos\""] + // general suggestions + var examplesNO = ["Beskriv hva du har lyst på","Hvilken smak liker du best?"]; + var examplesEN = ["Describe the candy you want","What flavour do you like the best?"]; + + function init() { + if (config.getUIlang() === "no") examples = examplesNO; + if (config.getUIlang() === "en") examples = examplesEN; + + /* EXAMPLES OF WHAT TO SAY */ + // variable to keep track of last text displayed + setTimeout(function () { + var i = 0; + var textTimer = function() { + if (i >= examples.length) { i = 0; } + $("#example-text").fadeOut(function() { + $(this).text(examples[i++]).fadeIn(); + }); + } + $(".speech-hand-animation").show(); + $("#example-text").text(examples[i++]); // initialize with first quote + setInterval(textTimer, 3500); // how long each text example is shown + }, 3000); //wait time until demo starts + + // Set end text + $('#textend').html(config.getEndText()); + + } + + var speech = function() { + $("body").addClass("mode-speech-start"); + + recognition_result = ""; + + function speechCallback(data) { + if (data.event === "end_of_speech") { + console.log("DONE!") + inputSpeech = recognition_result + + if(config.getSpeechLang().translate === 'en') { + speechTxt = inputSpeech; + setTimeout(function () { + nl(); + },500); + } else { + setTimeout(function () { + translation(); + },500); + } + + } + else { + recognition_result = data.transcript + document.getElementById('speech-interim-text').innerHTML = data.transcript + } + } + + var gcpSpeech = new GcpSpeechStreamer(speechCallback, config.getSpeechLang().stream, true, "twist"); + + $(".speech-mic").click(function () { + $(".pauseIcon img").replaceWith("") + $(".speech-mic").css({ // Changes the color of the mic-icon when clicked + background: "#ff5f63", + border: "solid 0 #ff5f63" + } + ); + $(".speech-footer").hide(); + $(".speech-hand-animation").hide(); + $("body").addClass("mode-speech-in"); + + if (!gcpSpeech.isRecording()) { + gcpSpeech.start_recognition(); + } else { + gcpSpeech.stop_recognition(); + } + }); + } + + + /* + */ + /* + var speech = function () { + $("body").addClass("mode-speech-start"); + recognition.lang = lang; + $(".speech-mic").click(function () { + $(".speech-mic").css({ // Changes the color of the mic-icon when clicked + background: "#D12F33", + border: "solid 0 #D12F33" + } + ); + $(".speech-footer").hide(); + $(".speech-hand-animation").hide(); //Hide demo animation when record starts + $("body").addClass("mode-speech-in"); + setTimeout(function () { + translation(); + },3500); + }); + }*/ + + var translation = function () { + $.ajax({ + type: "POST", + contentType: "application/json", + dataType: "json", + url: tranUrl, + data: JSON.stringify({ + "id": pid, + "text": inputSpeech, + "source": config.getSpeechLang().translate, + }), + error: function (textStatus) { + console.log(textStatus); + }, + success: function (data) { + speechTxt = he.decode(data[0].translatedText); + $("body").addClass("mode-tran-loaded"); + $(".tran-word").text(inputSpeech); + + /*FOOTER LOADING ANIMATION*/ + setTimeout(function () { + $(".tran-footer").show(); + }, 500); + + + timeoutManager.startTimer(nl, config.getTransitionTimeouts().tranSec) + /* + setTimeout(function () { + nl() + }, 5000);*/ + } + }); + // inputTxt --> translateAPI + // success --> speechTxt = data.string + } + + + // NL processing + var nl = function () { + var morXHR = null; + var simXHR = null; + + morXHR = $.ajax({ + type: "POST", + contentType: "application/json", + dataType: "json", + url: morUrl, + data: JSON.stringify({ + "id": pid, + "text": speechTxt, + "lang": config.getNlLang() + }), + error: function (jqXHR, textStatus) { + if (textStatus == 'abort') { return; } + console.log(jqXHR); + if (simXHR !== null && simXHR.readyState > 0 && simXHR.readyState < 4) { + simAjax.abort(); + } + sorry(); + }, + success: function (data) { + // remove translation UI + $("body").addClass("mode-tran-finish"); + // generate morpheme + data = data.morphs + console.log(data); + for (var i in data) { + var morph = ""; + for (key in data[i].pos) { + var txt = data[i].pos[key]; + if (key != "tag" && txt.indexOf("UNKNOWN") == -1) { + morph += key + "=" + txt + "
"; + } + } + var desc = "
"; + desc += "
" + data[i].depend.label + "
"; + if (data[i].pos.tag === "NOUN") { + desc += "
" + data[i].word + "
" + } + else { + desc += "
" + data[i].word + "
"; + } + desc += "
" + data[i].pos.tag + "
"; + desc += "
" + morph + "
"; + desc += "
" + $(".nl-syntax").append(desc); + // generate arrow + for (var j in data[i].depend.index) { + $(".nl-depend").append("
"); + } + } + // measure X coordinate + var dependX = []; + $(".nl-syntax dl").each(function (index) { + var x = $(".nl-syntax dl:nth-child(" + (index + 1) + ")").position().left; + var w = $(".nl-syntax dl:nth-child(" + (index + 1) + ")").outerWidth(); + dependX.push(Math.round(x + w / 2)); + }); + + // rearrange arrow + $(".nl-depend dd").each(function (index) { + var from = $(this).data().from; + var to = $(this).data().to; + if (from < to) { + var x = dependX[from]; + var w = dependX[to] - dependX[from]; + } else { + var x = dependX[to]; + var w = dependX[from] - dependX[to]; + $(this).addClass("left"); + } + $(this).css({ + top: (Math.abs(from - to) - 1) * -30 + "px", + left: x + "px", + width: w + "px" + }); + }); + /*FOOTER LOADING ANIMATION*/ + setTimeout(function () { + $(".nl-footer").show(); + }, 500); + + // effect settings + $(".nl-word").each(function (index) { + $(this).css("transition-delay", index / 5 + "s"); + }); + $(".nl-tag, .nl-pos").each(function (index) { + $(this).css("transition-delay", 1 + index / 10 + "s"); + }); + $(".nl-label, .nl-depend dd").css("transition-delay", 3 + "s"); //endret fra 2.5 + $("body").addClass("mode-nl-loaded"); + + /*MAKES IT LOOK VERY CRASHED WHEN DISABLED WITHOUT FOOTER LOADING ANIMATION*/ + // repeat effects + /*setInterval(function () { + $("body").addClass("mode-nl-repeat"); + setTimeout(function () { + $("body").removeClass("mode-nl-loaded"); + }, 400); + setTimeout(function () { + $("body").addClass("mode-nl-loaded"); + $("body").removeClass("mode-nl-repeat"); + }, 500); + }, 6000);*/ + } + }); + // retrieve inference data + simXHR = $.ajax({ + type: "POST", + contentType: "application/json", + dataType: "json", + url: simUrl, + data: JSON.stringify({ + "id": pid, + "text": speechTxt, + "lang": config.getNlLang() + }), + error: function (jqXHR, textStatus) { + if (textStatus == 'abort') { return; } + console.log(jqXHR); + if (morXHR !== null && morXHR.readyState > 0 && morXHR.readyState < 4) { + morXHR.abort(); + } + sorry(); + }, + success: function (data) { + sim = data; + console.log("(sim = data) from simURL. Sim = "); + console.log(sim); + + timeoutManager.startTimer(force, config.getTransitionTimeouts().nlSec); + } + }); + }; + + // drow force layout + var force = function () { + $("body").addClass("mode-force-start"); + $(".force-footer").show(); + // generate dataset + var data = sim.similarities.force; + var dataSet = { + "nodes": [], + "links": [] + }; + for (var i in data) { + dataSet.nodes.push(data[i]); + dataSet.links.push({ + "source": 0, + "target": parseInt(i) + 1 + }); + } + + // Add a node for input string at the beginning of the data set + dataSet.nodes.unshift({ + "label": "", + "lid": 0, + "em": 0, + "x": winW / 2, + "y": winH / 2, + "fixed": true + }); + // create SVG + var svg = d3.select(".force").append("svg").attr({ + width: winW, + height: winH + }); + // layout setttings + var d3force = d3.layout.force() + .nodes(dataSet.nodes) + .links(dataSet.links) + .size([winW, winH]) + .linkDistance(winH / 2.5) + .charge(-1000) + .start(); + var link = svg.selectAll("line") + .data(dataSet.links) + .enter() + .append("line"); + var g = svg.selectAll("g") + .data(dataSet.nodes) + .enter() //separating all nodes in the array + .append("g") //appending g-tag to all nodes + .attr("class", function (d) { //adding class .label-X to all nodes, varying color + return "label-" + d.lid; + }); + var sort = svg.selectAll('g') + .sort(function(a, b) { + return a.em > b.em; + }) + var circle = g.append("circle") //all elements "g" appended circle class + .attr("r", function (d) { //setting each radius based on similarity + var r = 70 + d.em * 75; + return r; + }); + var label = g.append("text") + .text(function (d) { + //if (d.em > 0.50 && d.em < 1) + return d.label; //appending a text label to each element g + //else return d.label + " < 0.1"; + }); + // setting coordinate (input string(lid=0)fix the mid of display) + d3force.on("tick", function () { + g.attr("transform", function (d) { + return "translate(" + d.x + "," + d.y + ")"; + }); + link.attr("x1", function (d) { + return d.source.x; + }) + .attr("y1", function (d) { + return d.source.y; + }) + .attr("x2", function (d) { + return d.target.x; + }) + .attr("y2", function (d) { + return d.target.y; + }); + }); + // Place the input character string in the center of the screen + $(".force").prepend("
" + speechTxt + "
"); + var txtW = $(".force-txt").outerWidth(); + var txtH = $(".force-txt").outerHeight(); + $(".force-txt").css({ + top: (winH - txtH) / 2 + "px", + left: (winW - txtW) / 2 + "px" + }); + + $(".plot dd:last-child").addClass("nearest"); + // draw with time difference + setTimeout(function () { + $("body").addClass("mode-plot-start"); + }, 6000); // WAS 3000; //How long just the circles are displayed + + timeoutManager.startTimer(cam, config.getTransitionTimeouts().forceSec); + }; + + // output camera image + var cam = function () { + $("body").addClass("mode-plot-end"); + $("body").addClass("mode-cam-start"); + var imgUrl = sim.similarities.url; + // retrieve image size + var img = new Image(); + img.src = imgUrl; + img.onload = function () { + var w = img.width; + var h = img.height; + $(".cam-img svg").attr("viewBox", "0 0 " + w + " " + h); + if (w > winW) { + h = h / w * winW; + w = winW; + } + if (h > winH) { + w = w / h * winH; + h = winH; + } + $(".cam-img").width(w).height(h); + }; + + + /* SVG TEST SPACE*/ + // Generate datasets + var camdata = sim.similarities.embedded; + var dataSet2 = []; // keeping track of all candies highest em and lid. + for (var i in camdata) { + var em = 0; + var label = ""; + var lid = ""; + for (var j in camdata[i].similarities) { + if (camdata[i].similarities[j].em > em) { + label = camdata[i].similarities[j].label; + lid = camdata[i].similarities[j].lid; + em = camdata[i].similarities[j].em; + } + } + dataSet2.push({ + "lid": lid, + "em": em, + "label": label + }); + } + + // setting image + $(".cam-img").css("background-image", "url(" + imgUrl + ")"); + + + /* + * Having different attr on polygon and circles + * .attr("class", "label-" + camdata[i].similarities[i].lid) + * .attr("class", "label-" + dataSet2[i].lid) + * determines if the colors should all be different, or if the candy with + * the same label should be the same + * + */ + + // adding svg elements + var svg = d3.select(".cam-img svg"); + for (var i in camdata) { + svg.append("polygon") + .attr("points", camdata[i].box[0][0] + "," + camdata[i].box[0][1] + " " + camdata[i].box[1][0] + "," + camdata[i].box[1][1] + " " + camdata[i].box[2][0] + "," + camdata[i].box[2][1] + " " + camdata[i].box[3][0] + "," + camdata[i].box[3][1] + " ") + .attr("class", "label-" + dataSet2[i].lid); + } + for (var i in camdata) { + svg.append("circle") + .attr("r", "80") + .attr("cx", camdata[i].box[0][0]).attr("cy", camdata[i].box[0][1]) + .attr("class", "label-" + dataSet2[i].lid + " delay"); + svg.append("text") + .attr("x", camdata[i].box[0][0]).attr("y", camdata[i].box[0][1] + 15) + .text(dataSet2[i].label); + svg.append("text") + .attr("x", camdata[i].box[0][0]).attr("y", camdata[i].box[0][1] - 15) + .text((dataSet2[i].em * 100).toFixed(1) + "%"); + } + + setTimeout(function () { + $("body").addClass("mode-cam-mid"); + }, 750); + + console.log("Starting cam timer"); + timeoutManager.startTimer(select, config.getTransitionTimeouts().camSec); + }; + + var select = function() { + $("body").addClass("mode-cam-end"); + $("body").addClass("mode-cam-finished"); + + var svg = d3.select(".cam-img svg"); + var nearest = sim.similarities.nearest; + + svg.selectAll("polygon").remove(); + svg.selectAll("text").remove(); + svg.selectAll("circle").remove(); + svg.append("polygon") + .attr("points",nearest.box[0][0] + "," + nearest.box[0][1] + " " + nearest.box[1][0] + "," + nearest.box[1][1] + " " + nearest.box[2][0] + "," + nearest.box[2][1] + " " + nearest.box[3][0] + "," + nearest.box[3][1] + " ") + .attr("style", "stroke: #49bca1; stroke-width: 20px;") + svg.append("circle") + .attr("r", "120") + .attr("cx", nearest.box[0][0]).attr("cy", nearest.box[0][1]) + .attr("style", "fill: #49bca1; opacity: 1; "); + if(config.getUIlang() === "no") { + svg.append("text") + .attr("x", nearest.box[0][0]).attr("y", nearest.box[0][1]) + .attr("style", "fill: #003459; font-size: 25px;") + .text("Jeg velger denne!"); + } + if(config.getUIlang() === "en") { + svg.append("text") + .attr("x", nearest.box[0][0]).attr("y", nearest.box[0][1]) + .attr("style", "fill: #003459; font-size: 25px;") + .text("I choose this!"); + } + + pickup(); + + timeoutManager.startTimer(thanks, config.getTransitionTimeouts().selectSec); + } + + var pickup = function() { + console.log("starting pickup") + // operation of pickup + $.ajax({ + type: "POST", + contentType: "application/json", + dataType: "json", + url: pickUrl, + data: JSON.stringify({ + "id": pid + }), + error: function (textStatus) { + console.log(textStatus); + }, + success: function (data) { + sim = data; + console.log(sim); + } + }); + } + + // draw endroll + var thanks = function () { + $("body").addClass("mode-thanks-start"); + + /* Automatic return to startpage + setTimeout(function () { + location.reload(); + },15000); + */ + }; + + // draw sorry + var sorry = function () { + $("body").addClass("mode-sorry-p"); + }; + + + init(); + + speech(); }); diff --git a/webapp/candysorter/static/js/learn.js b/webapp/candysorter/static/js/learn.js index 23d9c15..beca747 100644 --- a/webapp/candysorter/static/js/learn.js +++ b/webapp/candysorter/static/js/learn.js @@ -11,7 +11,10 @@ $(function () { var statTest = 0; // dummy variable for test(dev:1、prd:0) var loadSec = 2000; // Time lag for loading screen test (return to 0 in prd environment) - // variables + + var config = new FycConfig(); + + // variables var stepFlg = 0; // capture step var capDat = []; // save capture data var winW = window.innerWidth; @@ -19,6 +22,7 @@ $(function () { // processing capture mode var cap = function () { + $("body").addClass("mode-cap-init"); }; $(".cap-start, .cap-retry").click(function () { diff --git a/webapp/candysorter/static/js/pause.js b/webapp/candysorter/static/js/pause.js new file mode 100644 index 0000000..a993013 --- /dev/null +++ b/webapp/candysorter/static/js/pause.js @@ -0,0 +1,138 @@ +nextWithTimeout = (nextFunc, timeout) => { + console.log("Setting paused to false and starting duration") + var paused = false; + + var timeout = setTimeout(function() { + window.document.onkeyup = null; + nextFunc() + }, timeout); + + window.document.onkeyup = (event) => { + console.log("Key pressed") + if (event.which === 32) { + if (paused) { + $(".pauseIcon img").replaceWith("") + console.log("Resuming after pause"); + window.document.onkeyup = null; + nextFunc(); + } else { + console.log("Clearing duration and pausing") + clearTimeout(timeout); + paused = true; + $(".pauseIcon img").replaceWith(""); + } + } + }; +} + +class TimeoutManager { + constructor() { + // Function to be called on next or when the timer expires + this.nextFunc = null; + // The time before the timeout expires + this.duration = null; + // The timeout object so that the timeout can be cancelled + this.timeout = null; + + // Flag to indicate if we can pause + this.canStop = false; + // Flag to indicate that the timeout is active + this.stopped = false; + + this.initEventHandlers() + } + + startTimer(nextFunc, duration) { + this.nextFunc = nextFunc; + this.duration = duration; + this.timeout = setTimeout(this.onContinue.bind(this), this.duration) + + this.canStop = true; + this.stopped = false; + + this.showCountdown(); + } + + initEventHandlers() { + window.document.onkeyup = (event) => { + console.log("Key pressed", event.code) + switch (event.code) { + case "Space": + this.onSpacePressed(); + break; + case "PageUp": + this.onStop(); + break; + case "PageDown": + this.onContinue() + break; + } + }; + } + + showCountdown() { + $(".pauseIcon img").replaceWith("") + + var svg = d3.select(".progress"); + svg.selectAll("circle").remove(); + svg.append("circle") + .attr("r", "17.5") + .attr("cx", 20).attr("cy", 20) + .attr("class", "progress_value") + .attr("style", "animation: progress " + this.duration + "ms linear forwards") + }; + + onSpacePressed(){ + console.log("Space pressed", this) + if (this.canStop) { + this.onStop(); + } + else if (this.stopped) { + this.onContinue(); + } + } + + onStop() { + if (!this.canStop) return; + + console.log("onStop"); + // Remove timeout + clearTimeout(this.timeout); + + // Complete animation/show full circle + var svg = d3.select(".progress"); + svg.selectAll("circle").remove(); + svg.append("circle") + .attr("r", "17.5") + .attr("cx", 20).attr("cy", 20) + .attr("class", "progress_value progress_complete") + + // Show next symbol + $(".pauseIcon img").replaceWith(""); + + //Set status to stopped + this.canStop = false; + this.stopped = true; + } + + onContinue() { + if (!this.stopped && !this.canStop) return; + this.onStop(); + + console.log("onContinue"); + + this.canStop = false; + this.stopped = false; + + // Remove symbol + var svg = d3.select(".progress"); + svg.selectAll("circle").remove(); + + // Hide icon + $(".pauseIcon img").replaceWith(""); + + // Call next function + this.nextFunc(); + + } +} \ No newline at end of file diff --git a/webapp/candysorter/static/js/settings.js b/webapp/candysorter/static/js/settings.js new file mode 100644 index 0000000..26725eb --- /dev/null +++ b/webapp/candysorter/static/js/settings.js @@ -0,0 +1,164 @@ +$(function () { + $("body").removeClass("mode-speech-start"); + + // API settings + var pid = Math.floor(Math.random() * 10000000000000000); // POST ID + + var config = new FycConfig(); + + + function onLoad() { + checkCam(); + checkRobot(); + checkConnection(); + + initSpeechSelect(); + initUILangSelect(); + initEndText(); + initResetButton(); + initTiming(); + }; + + function initSpeechSelect() { + $('#speech-lang').val(config.getSpeechLang().translate); + + $('#speech-lang').on('change', function() { + new_value = $('#speech-lang').val(); + if (new_value === "no") { + config.setSpeechLang({ + stream: 'nb-NO', + translate: 'no', + }); + } + else if (new_value === "en") { + config.setSpeechLang({ + stream: 'en-US', + translate: 'en', + }); + } + }); + }; + + function initUILangSelect() { + $('#ui-lang').val(config.getUIlang()); + + $('#ui-lang').on('change', function () { + new_value = $('#ui-lang').val(); + console.log("New value: ", new_value) + if (new_value === "no") { + console.log("Setting language to no"); + config.setUIlang("no"); + } + else if (new_value === "en") { + config.setUIlang("en"); + } + }); + }; + + function initEndText() { + $('#thanks-text').val(config.getEndText()); + $('#end-text-button').on('click', setEndText); + }; + + function initTiming() { + var values = ["tranSec", "nlSec", "forceSec", "camSec", "selectSec"]; + $(".timeout-setting input").each(function(i) { + $(this).val(config.getTransitionTimeouts()[values[i]]); + $(this).on('keyup', null, values[i], function(event) { + let key = event.data; + let value = parseInt($(event.target).val()); + if(value>=1000) { + config.setTransitionTimeouts(key, value); + } + }) + }) + }; + + function setEndText() { + config.setEndText($('#thanks-text').val()); + }; + + function setTiming() { + + } + + function initResetButton(){ + $('#reset-btn').on('click', function() { + reset(); + }); + } + + function checkConnection() { + updateConnectionStatus(navigator.onLine); + }; + + function checkRobot() { + $.ajax({ + type: "GET", + contentType: "application/json", + dataType: "json", + url: config.getApiEndpoints().robStatusUrl, + error: function (textStatus) { + updateRobotStatus(false); + }, + success: function (data) { + updateRobotStatus(true); + } + }); + }; + + function checkCam() { + $.ajax({ + type: "GET", + contentType: "application/json", + dataType: "json", + url: config.getApiEndpoints().camStatusUrl, + error: function (textStatus) { + updateCameraStatus(false); + }, + success: function (data) { + updateCameraStatus(true); + } + }); + }; + + function updateConnectionStatus(statusOk){ + color = statusOk ? "#49bca1" : "#ff5f63"; + text = statusOk ? "OK" : "Error"; + + $(".flex-container div:nth-child(1)").css({ + "background-color": color + }); + $(".flex-container #OKconn").text(text); + }; + + function updateRobotStatus(statusOk){ + color = statusOk ? "#49bca1" : "#ff5f63"; + text = statusOk ? "OK" : "Error"; + + $(".flex-container div:nth-child(2)").css({ + "background-color": color + }); + $(".flex-container #OKrob").text(text); + }; + + function updateCameraStatus(statusOk){ + color = statusOk ? "#49bca1" : "#ff5f63"; + text = statusOk ? "OK" : "Error"; + + $(".flex-container div:nth-child(3)").css({ + "background-color": color + }); + $(".flex-container #OKcam").text(text); + }; + + function reset() { + config.reset(); + initSpeechSelect(); + initEndText(); + initTiming(); + initUILangSelect(); + } + + onLoad() +}); diff --git a/webapp/candysorter/static/js/speech_stream.js b/webapp/candysorter/static/js/speech_stream.js new file mode 100644 index 0000000..8d80368 --- /dev/null +++ b/webapp/candysorter/static/js/speech_stream.js @@ -0,0 +1,117 @@ +class GcpSpeechStreamer { + constructor(resCallback, lang, get_interim=true, phrase_key="") { + this.lang = lang; + this.get_interim = get_interim; + this.phrase_key = phrase_key; //"twist", "box_candy" or "" + + this.result_callback = resCallback; + this.audioContext = new AudioContext(); + this.scriptProcessor = this.audioContext.createScriptProcessor(2048, 1, 1); + this.audioInput = null; + this.userMediaStream = null; + this.recording = false; + this.socket = null; + } + + start_recognition() { + if (!this.recording) { + this.getUserMedia(this.start_streaming.bind(this), (event) => console.log(event)); + this.recording = true; + } + } + + stop_recognition() { + if (this.recording) { + if (this.audioInput) { + this.audioInput.disconnect(); + } + + if (this.userMediaStream) { + this.userMediaStream.getTracks().map((track) => { + track.stop(); + }); + } + + if (this.socket) { + this.socket.close(); + } + this.recording = false; + } + } + + isRecording () { + return this.recording; + } + + getUserMedia(successFn, failureFn) { + return navigator.getUserMedia({ audio: true }, successFn, failureFn); + } + + start_streaming(stream) { + this.userMediaStream = stream; + + // get all the audio capture, processing and streaming ready to go... + this.userMediaStream.getTracks().forEach((track) => { + track.onended = this.onAudioEnd + }); + this.audioInput = this.audioContext.createMediaStreamSource(this.userMediaStream); + this.audioInput.connect(this.scriptProcessor); + this.scriptProcessor.connect(this.audioContext.destination); + this.scriptProcessor.onaudioprocess = this.processAudioData.bind(this); + + this.setupWebsocketConnection('ws://' + window.location.hostname + ':18002'); + + }; + + setupWebsocketConnection(Uri) { + this.socket = new WebSocket(Uri); + + // Open connection + this.socket.addEventListener('open', this.onConnectionOpened.bind(this)); + + // Register message handler + this.socket.addEventListener('message', this.onMessageReceived.bind(this)); + } + + onConnectionOpened(event) { + // First message to the server should be config data + this.socket.send(JSON.stringify({ + "sample_rate": this.audioContext.sampleRate, + "lang": this.lang, + "interim_results": this.get_interim, + "phrase_key": this.phrase_key //"twist" or "box_candy" + })); + } + + onMessageReceived(event) { + var data = JSON.parse(event.data) + + if (data.event === "end_of_speech") { + this.stop_recognition(); + } + + this.result_callback(data); + } + + processAudioData(data) { + // we're only using one audio channel here... + let leftChannel = event.inputBuffer.getChannelData(0); + if (this.socket && this.socket.readyState == 1) { + this.socket.send(this.convertFloat32ToInt16(leftChannel)); + } + } + + onAudioEnd() { + console.log("Audio ended"); + } + + convertFloat32ToInt16(buffer) { + let l = buffer.length; + let buf = new Int16Array(l); + while (l >= 0) { + buf[l] = Math.min(1, buffer[l]) * 0x7FFF; + l = l - 1; + } + return buf.buffer; + } +} \ No newline at end of file diff --git a/webapp/candysorter/static/lang/en.json b/webapp/candysorter/static/lang/en.json new file mode 100644 index 0000000..55b3401 --- /dev/null +++ b/webapp/candysorter/static/lang/en.json @@ -0,0 +1,29 @@ +{ + "tranfoottext": "Cloud Translate API: Translating", + "nlfoottext": "Cloud Natural language API: Analyzing", + "camfoottext": "Image processing", + "chosentext" : "I choose this", + "tryagain" : "Try again", + "sorrytext" : "Sorry, something went wrong", + "statustext" : "Status", + "settingstext" : "Settings", + "langselNO" : "Norwegian", + "langselEN" : "English", + "speechlangtext" : "Spoken language", + "uilangtext" : "UI language", + "edittext" : "Edit text on end page:", + "timeouttext" : "Change timeouts (in milliseconds, miminum 1000)", + "resetheadertext" : "Escape zone", + "resettext" : "Reset settings to default values:", + "resetbtntext" : "Reset", + "helpheadertext" : "Help/Troubleshooting", + "modetext" : "Change mode", + "modemltext" : "Machine learning", + "instructtext" : "Put candies and labels on the table", + "changetodemotext": "Change to demo mode", + "steptext": "Step", + "startnext" : "Start next", + "finishtext": "Finished", + "canceltext": "Cancel", + "preparecloudML": "Preparing CloudML" +} \ No newline at end of file diff --git a/webapp/candysorter/static/lang/no.json b/webapp/candysorter/static/lang/no.json new file mode 100644 index 0000000..0a7bb8b --- /dev/null +++ b/webapp/candysorter/static/lang/no.json @@ -0,0 +1,29 @@ +{ + "tranfoottext": "Cloud Translate API: Oversetter", + "nlfoottext": "Cloud Natural language API: Analyserer", + "camfoottext": "Bildeanalyse", + "chosentext" : "Jeg velger denne", + "tryagain" : "Prøv igjen", + "sorrytext" : "Beklager, noe gikk galt", + "statustext" : "Status", + "settingstext" : "Innstillinger", + "langselNO" : "Norsk", + "langselEN" : "Engelsk", + "speechlangtext" : "Talespråk", + "uilangtext" : "Visningsspråk", + "edittext" : "Rediger teksten på siste side:", + "timeouttext" : "Endre timeouts (i millisekunder, miminum 1000)", + "resetheadertext" : "Escape zone", + "resettext" : "Tilbakestill innstillingene til standardverdier:", + "resetbtntext" : "Tilbakestill", + "helpheadertext" : "Hjelp/Troubleshooting", + "modetext" : "Skift modus", + "modemltext" : "Maskinlæring", + "instructtext" : "Legg godteri og etiketter på bordet,
og trykk start", + "changetodemotext": "Bytt til demo-modus", + "steptext": "Steg", + "startnext" : "Start neste", + "finishtext": "Ferdig", + "canceltext": "Avbryt", + "preparecloudML": "Forbereder CloudML" +} \ No newline at end of file diff --git a/webapp/candysorter/static/learn.html b/webapp/candysorter/static/learn.html index df2c9e9..191dd92 100644 --- a/webapp/candysorter/static/learn.html +++ b/webapp/candysorter/static/learn.html @@ -1,15 +1,17 @@ - + Find Your Candy + + @@ -21,11 +23,11 @@
-

Please put labels and candies
- on the table, then start.

-

START

+

Legg godteri og etiketter på bordet,
+ og trykk start

+

Start

- +
@@ -39,12 +41,12 @@
-

STEP 1/4

-

START NEXT

-

DONE

-

RETRY

+

Steg 1/4

+

START NESTE

+

FERDIG

+

PRØV IGJEN

- +
@@ -53,14 +55,14 @@
- - - - + + + +
-
Preparing CloudML
+
Forbereder CloudML
@@ -86,15 +88,15 @@
- +
-

Sorry, something went wrong.

-

TRY AGAIN

+

Beklager, noe gikk galt.

+

PRØV IGJEN

- + \ No newline at end of file diff --git a/webapp/candysorter/static/predict.html b/webapp/candysorter/static/predict.html index 6e51b14..fd10bdf 100644 --- a/webapp/candysorter/static/predict.html +++ b/webapp/candysorter/static/predict.html @@ -1,55 +1,109 @@ - + - -Find Your Candy - - - - - - - - + + Find Your Candy + + + + + + + + + + + + + - + +
+ + + +
-
+
+
+
- - +
+ +
+
+

+ +

+
+ + +
+
+
+
+
+
+
+ - + /*Cam UI - might not keep this*/ + + + + + + + + + /* /Cam UI*/
-
+
+ +
+
-
Do you like my choice of candy?
-
Thank you!
-

Sorry, something went wrong.

-

TRY AGAIN

+

Beklager, noe gikk galt.

+

PRØV IGJEN

diff --git a/webapp/candysorter/static/settings.html b/webapp/candysorter/static/settings.html new file mode 100644 index 0000000..3124ed2 --- /dev/null +++ b/webapp/candysorter/static/settings.html @@ -0,0 +1,121 @@ + + + + + + Settings + + + + + + + + + + + +
+
+ + + +
+
+
+

+
+
+
+ +

...

+
+
+ +

...

+
+
+ +

...

+
+
+
+
+
+

+
+
+
+
+ +
+
+
Visningsspråk
+
+ +
+
+
+
+

+
+ +
+
+ +
+
+

Endre timeouts (i millisekunder, minimum 1000)

+
+
+

Translate

+ +
+
+

NL

+ +
+
+

Force

+ +
+
+

Camera

+ +
+
+

Select

+ +
+
+
+
+
+
+

Escape zone

+
+

Tilbakestill innstillingene til standardverdier:

+ +
+
+
+

Hjelp/Troubleshooting

+
+
+ Readme +
+
+
+

Skift modus

+ Maskinlæring +
+
+ + + \ No newline at end of file diff --git a/webapp/candysorter/utils.py b/webapp/candysorter/utils.py index 9a9e270..63f09cf 100644 --- a/webapp/candysorter/utils.py +++ b/webapp/candysorter/utils.py @@ -18,6 +18,7 @@ import errno import importlib import os +import platform import random import string @@ -28,10 +29,35 @@ def load_class(name): return getattr(module, parts[-1]) +def get_classifier_dir(config): + if platform.system() == 'Windows': + with open(config['CLASSIFIER_MODEL_DIR']) as f: + return os.path.join(config['CANDY_MODEL_DIR'], f.readline()) + else: + return config['CLASSIFIER_MODEL_DIR'] + + +def update_classifier_dir(config, job_id): + folder_name = 'classifier_{}'.format(job_id) + if platform.system() == 'Windows': + with open(config['CLASSIFIER_MODEL_DIR'], 'w') as f: + f.write(folder_name) + else: + new_dir = os.path.join(config['CANDY_MODEL_DIR'], folder_name) + symlink_force(new_dir, config['CLASSIFIER_MODEL_DIR']) + +def reset_classifier_dir(config): + if platform.system() == 'Windows': + with open(config['CLASSIFIER_MODEL_DIR'], 'w') as f: + f.write(config['CLASSIFIER_DIR_NAME_INITIAL']) + else: + symlink_force(os.path.basename(config['CLASSIFIER_MODEL_DIR_INITIAL']), config['CLASSIFIER_MODEL_DIR']) + + def symlink_force(source, link_name): try: os.symlink(source, link_name) - except OSError, e: + except OSError as e: if e.errno == errno.EEXIST: os.unlink(link_name) os.symlink(source, link_name) diff --git a/webapp/candysorter/views/api.py b/webapp/candysorter/views/api.py index 2c6dded..9248c33 100644 --- a/webapp/candysorter/views/api.py +++ b/webapp/candysorter/views/api.py @@ -20,6 +20,7 @@ import glob import logging import os +import platform import shutil import string import time @@ -33,20 +34,23 @@ from sklearn.preprocessing import MinMaxScaler from candysorter.cache import Cache -from candysorter.config import Config from candysorter.ext.google.cloud.ml import State +from candysorter.ext.google.cloud.auth import test_auth from candysorter.models.images.calibrate import ImageCalibrator from candysorter.models.images.classify import CandyClassifier from candysorter.models.images.detect import CandyDetector, detect_labels from candysorter.models.images.filter import exclude_unpickables from candysorter.models.images.train import CandyTrainer -from candysorter.utils import load_class, random_str, symlink_force +from candysorter.utils import load_class, random_str, symlink_force, reset_classifier_dir, update_classifier_dir +from candysorter.ext.google.cloud.translation import TranslatorClient logger = logging.getLogger(__name__) api = Blueprint('api', __name__, url_prefix='/api') +config = None cache = Cache() +text_translator = None text_analyzer = None candy_detector = None candy_classifier = None @@ -57,25 +61,31 @@ @api.record def record(state): + global config + config = state.app.config + global text_analyzer - text_analyzer = load_class(Config.CLASS_TEXT_ANALYZER).from_config(Config) + text_analyzer = load_class(config['CLASS_TEXT_ANALYZER']).from_config(config) text_analyzer.init() global candy_detector - candy_detector = CandyDetector.from_config(Config) + candy_detector = CandyDetector.from_config(config) global candy_classifier - candy_classifier = CandyClassifier.from_config(Config) + candy_classifier = CandyClassifier.from_config(config) candy_classifier.init() global candy_trainer - candy_trainer = CandyTrainer.from_config(Config) + candy_trainer = CandyTrainer.from_config(config) global image_capture - image_capture = load_class(Config.CLASS_IMAGE_CAPTURE).from_config(Config) + image_capture = load_class(config['CLASS_IMAGE_CAPTURE']).from_config(config) global image_calibrator - image_calibrator = ImageCalibrator.from_config(Config) + image_calibrator = ImageCalibrator.from_config(config) + + global text_translator + text_translator = TranslatorClient() @api.errorhandler(400) @@ -100,9 +110,25 @@ def wrapper(*args, **kwargs): abort(400) g.id = id_ return f(*args, **kwargs) + return wrapper +@api.route('/translate', methods=['POST']) +@id_required +def translate(): + text = request.json.get('text') + if not text: + abort(400) + + source_lang = request.json.get('source', None) + target_lang = request.json.get('target', 'en') + + translation_result = text_translator.translate_text(text, source_lang=source_lang, target_lang=target_lang) + + return jsonify(translation_result) + + @api.route('/morphs', methods=['POST']) @id_required def morphs(): @@ -114,34 +140,68 @@ def morphs(): logger.info('=== Analyze text: id=%s ===', g.id) tokens = text_analyzer.analyze_syntax(text, lang) + + cache.set('text', text) + cache.set('lang', lang) + cache.set('tokens', tokens) + return jsonify(morphs=[ - dict(word=t.text.content, - depend=dict(label=t.dep.label, index=[ - _i for _i, _t in enumerate(tokens) - if _i != i and _t.dep.index == i - ]), - pos=dict(tag=t.pos.tag, case=t.pos.case, number=t.pos.number)) + dict( + word=t.text.content, + depend=dict( + label=t.dep.label, + index=[_i for _i, _t in enumerate(tokens) if _i != i and _t.dep.index == i]), + pos=dict(tag=t.pos.tag, case=t.pos.case, number=t.pos.number)) for i, t in enumerate(tokens) ]) +def calculate_rotation(candy): + tl_x, tl_y = candy.box_coords[0] + br_x, br_y = candy.box_coords[2] + bl_x, bl_y = candy.box_coords[3] + + length_left = np.sqrt(np.power(tl_x - bl_x, 2) + np.power(tl_y - bl_y, 2)) + length_bottom = np.sqrt(np.power(br_x - bl_x, 2) + np.power(br_y - bl_y, 2)) + + if length_left < length_bottom: + return -1 * candy.box_rotation + else: + return -1 * (candy.box_rotation - 90) + + @api.route('/similarities', methods=['POST']) @id_required def similarities(): - text = request.json.get('text') - if not text: - abort(400) - lang = request.json.get('lang', 'en') - - logger.info('=== Calculate similarities: id=%s ===', g.id) - # Session session_id = _session_id() - # Analyze text - logger.info('Analyaing text.') labels = text_analyzer.labels - tokens = text_analyzer.analyze_syntax(text, lang) + + logger.info('=== Calculate similarities: id=%s ===', g.id) + + # See if we already have analyzed the text + text = cache.get('text') + lang = cache.get('lang') + tokens = cache.get('tokens') + + request_text = request.json.get('text') + + if not tokens or not lang or text != request_text: + if not request_text: + abort(400) + lang = request.json.get('lang', 'en') + + # Analyze text + logger.info('Analyzing text.') + tokens = text_analyzer.analyze_syntax(request_text, lang) + + # Cache result for subsequent calls + cache.set('text', request_text) + cache.set('lang', lang) + cache.set('tokens', tokens) + else: + logger.info('Using cached text analysis') # Calculate speech similarity logger.info('Calculating speech similarity.') @@ -149,13 +209,24 @@ def similarities(): # Capture image logger.info('Capturing image.') - img = _capture_image() + try: + img = _capture_image() + except RuntimeError as e: + return _error_response("CAMERA", "Unable to detect barcodes and/or camera has been moved.", str(e)) + except FileNotFoundError as e: + return _error_response("CAMERA", "Could not find the fake image specified in the config", str(e)) + except Exception as e: + return _error_response("CAMERA", str(e)) # Detect candies logger.info('Detecting candies.') candies = candy_detector.detect(img) logger.info(' %d candies detected.', len(candies)) + if len(candies) == 0: + return _error_response("CANDY", + "No candy detected on the table. Place candy on the table, or calibrate camera.") + # Create image directory save_dir = _create_save_dir(session_id) @@ -193,35 +264,47 @@ def similarities(): # Save pickup point logger.info('Saving pickup point.') nearest_centroid = candies[nearest_idx].box_centroid - pickup_point = image_calibrator.get_coordinate(nearest_centroid[0], nearest_centroid[1]) - cache.set('pickup_point', pickup_point) + if config['PICKUP_TYPE'] == 'suction_cup': + pickup_point = image_calibrator.get_coordinate(nearest_centroid[0], nearest_centroid[1]) + cache.set('pickup_point', pickup_point) + elif config['PICKUP_TYPE'] == 'gripper': + pick_x, pick_y = image_calibrator.get_coordinate(nearest_centroid[0], nearest_centroid[1]) + rotation = calculate_rotation(candies[nearest_idx]) + cache.set('pickup_point', (pick_x, pick_y, rotation)) # For json def _sim_as_json(sim): - return [dict(label=l, lid=i + 1, em=np.asscalar(s)) - for i, (l, s) in enumerate(zip(labels, sim))] + return [ + dict(label=l, lid=i + 1, em=np.asscalar(s)) + for i, (l, s) in enumerate(zip(labels, sim)) + ] def _coords_as_json(rsim): return list(rsim) def _box_as_json(box_coords): - return [[x.astype(int), y.astype(int)] for x, y in box_coords] + return [[int(x), int(y)] for x, y in box_coords] + + def _centroid_as_json(center): + return list(center) return jsonify(similarities=dict( force=_sim_as_json(speech_sim), url=snapshot_url, embedded=[ - dict(url=url, - similarities=_sim_as_json(sim), - coords=_coords_as_json(rsim), - box=_box_as_json(candy.box_coords)) + dict( + url=url, + similarities=_sim_as_json(sim), + coords=_coords_as_json(rsim), + box=_box_as_json(candy.box_coords), + center=_centroid_as_json(candy.box_centroid)) for candy, sim, rsim, url in zip(candies, candy_sims, candy_rsims, candy_urls) ], - nearest=dict(url=candy_urls[nearest_idx], - similarities=_sim_as_json(candy_sims[nearest_idx]), - coords=_coords_as_json(candy_rsims[nearest_idx]), - box=_box_as_json(candies[nearest_idx].box_coords)), - )) + nearest=dict( + url=candy_urls[nearest_idx], + similarities=_sim_as_json(candy_sims[nearest_idx]), + coords=_coords_as_json(candy_rsims[nearest_idx]), + box=_box_as_json(candies[nearest_idx].box_coords)), )) @api.route('/pickup', methods=['POST']) @@ -234,7 +317,15 @@ def pickup(): logger.info('=== Pickup candy: id=%s ===', g.id) logger.info('Picking candy. x=%f, y=%f', pickup_point[0], pickup_point[1]) - requests.post(Config.PICKUP_ENDOPOINT, json=dict(x=pickup_point[0], y=pickup_point[1])) + + if config['PICKUP_TYPE'] == 'suction_cup': + requests.post(config['PICKUP_SUCTION_CUP_ENDPOINT'], + json=dict(x=pickup_point[0], y=pickup_point[1])) + + elif config['PICKUP_TYPE'] == 'gripper': + requests.post(config['PICKUP_GRIPPER_ENDPOINT'], + json=dict(x=pickup_point[0], y=pickup_point[1], r=pickup_point[2])) + return jsonify() @@ -283,8 +374,8 @@ def capture(): # Crop label and candies logger.info('Cropping image.') - img_label = img[:Config.TRAIN_LABEL_AREA_HEIGHT] - img_candies = img[Config.TRAIN_LABEL_AREA_HEIGHT:] + img_label = img[:config['TRAIN_LABEL_AREA_HEIGHT']] + img_candies = img[config['TRAIN_LABEL_AREA_HEIGHT']:] # Save snapshot image logger.info('Saving snapshot image.') @@ -385,8 +476,8 @@ def status(): key = 'model_updated_{}'.format(job_id) if not cache.get(key): logger.info('Training completed, updating model: job_id=%s', job_id) - new_checkpoint_dir = candy_trainer.download_checkpoints(job_id) - symlink_force(os.path.basename(new_checkpoint_dir), Config.CLASSIFIER_MODEL_DIR) + candy_trainer.download_checkpoints(job_id) + update_classifier_dir(config, job_id) text_analyzer.reload() candy_classifier.reload() cache.set(key, True) @@ -394,6 +485,77 @@ def status(): return jsonify(status=status, loss=losses, embedded=embedded) +@api.route('/status/auth', methods=['GET']) +def status_auth(): + """ + Test status of the webapp. The camera is tested independently, so only auth and communication with the + robot arm is + """ + # Check auth + try: + test_auth() + except Exception as e: + return _error_response('AUTH', 'Authentication with GCP failed.', str(e)) + + return jsonify(status="SUCCESS") + + +@api.route('/status/camera', methods=['GET']) +def status_camera(): + """ + Test the status of the camera by capturing an image, detecting corners and then detecting candies. + Not meant for calibrating/tuning the camera, just as a simple test that the camera is working. + + Returns either a json with status set to success and number of candies detected, or + a message with status set to error, see _error_response() for format + """ + + logger.info('=== Check camera ===') + + # Capture image + logger.info('Capturing image.') + try: + img = _capture_image() + except RuntimeError as e: + return _error_response("CAMERA", "Unable to detect barcodes and/or camera has been moved.", str(e)) + except FileNotFoundError as e: + return _error_response("CAMERA", "Could not find the fake image specified in the config", str(e)) + except Exception as e: + return _error_response("CAMERA", "", str(e)) + + # Detect candies + logger.info('Detecting candies.') + candies = candy_detector.detect(img) + logger.info(' %d candies detected.', len(candies)) + + return jsonify(status="SUCCESS", candy_count=len(candies)) + + +@api.route('/status/robot', methods=['GET']) +def status_robot(): + """ + Check that the webapp is able to communicate with the robot, and return the robot state + """ + try: + response = requests.get(config['ROBOT_ARM_STATUS_ENDPOINT']) + response_data = response.json() + + if len(response_data['alarms']): + return jsonify( + status='ERROR', + error_type="ROBOT", + error="", + message="One or more alarms has been triggered on the robot. It might still function, but try turning it on and off again.", + robot_status=response_data + ), 500 + + else: + return jsonify(status="SUCCESS", **response_data) + + except Exception as e: + return _error_response("COMMUNICATION", "Cannot communicate with robot", str(e)) + + @api.route('/_labels') def labels(): return jsonify(labels=text_analyzer.labels) @@ -408,8 +570,7 @@ def reload(): @api.route('/_reset', methods=['POST']) def reset(): - symlink_force(os.path.basename(Config.CLASSIFIER_MODEL_DIR_INITIAL), - Config.CLASSIFIER_MODEL_DIR) + reset_classifier_dir(config) text_analyzer.reload() candy_classifier.reload() return jsonify({}) @@ -421,20 +582,24 @@ def _session_id(): def _capture_image(retry_count=5, retry_interval=0.1): + error = None for i in range(retry_count): try: img = image_capture.capture() img = image_calibrator.calibrate(img) return img - except Exception: + except FileNotFoundError: + raise + except (RuntimeError, Exception) as e: + error = e logger.warning(' Retrying: %d times.', (i + 1)) time.sleep(retry_interval) - raise Exception('Failed to capture image.') + raise error def _create_save_dir(session_id): # e.g. 20170209_130952_reqid -> /tmp/download/image/20170209_130952_reqid/ - d = os.path.join(Config.DOWNLOAD_IMAGE_DIR, session_id) + d = os.path.join(config['DOWNLOAD_IMAGE_DIR'], session_id) os.makedirs(d) return d @@ -442,14 +607,17 @@ def _create_save_dir(session_id): def _candy_file(save_dir, i): # e.g. /tmp/download/image/20170209_130952_reqid/candy_01_xxxxxxxx.png return os.path.join( - save_dir, - 'candy_{:02d}_{}.jpg'.format(i, random_str(8, string.lowercase + string.digits)) - ) + save_dir, 'candy_{:02d}_{}.jpg'.format(i, random_str(8, string.ascii_lowercase + string.digits))) def _image_url(image_file): # e.g. 20170209_130952_reqid/candy_01_xxxxxxxx.png - rel = os.path.relpath(image_file, Config.DOWNLOAD_IMAGE_DIR) + rel = os.path.relpath(image_file, config['DOWNLOAD_IMAGE_DIR']) + + # On Windows, the url will not contain slash, but the encoded \ which will return a 404 + # So we replace it with forward slash + if platform.system() == 'Windows': + rel = rel.replace('\\', '/') # e.g. /image/20170209_130952_reqid/candy_01_xxxxxxxx.png return url_for('ui.image', filename=rel) @@ -467,3 +635,21 @@ def _reduce_dimension(speech_sim, candy_sims): def _job_id(session_id): return 'candy_sorter_{}'.format(session_id) + + +def _error_response(err_type, msg, error=""): + """ + Helper to format an error response in a predictable way. + + Will return a response with the following fields: + - status: Always set to ERROR + - error_type: Used to denote a "class" of errors, ie. CAMERA or AUTH + - message: Should be a human readable message that describes the error and possible fixes + - error: The error as string, or a more technical error message. + """ + return jsonify( + status='ERROR', + error_type=err_type, + error=error, + message=msg + ), 500 diff --git a/webapp/candysorter/views/ui.py b/webapp/candysorter/views/ui.py index 40dee02..e07cfb9 100644 --- a/webapp/candysorter/views/ui.py +++ b/webapp/candysorter/views/ui.py @@ -21,11 +21,17 @@ import cv2 from flask import Blueprint, current_app, send_file, send_from_directory -from candysorter.config import Config from candysorter.decorators import after_this_request from candysorter.utils import load_class ui = Blueprint('ui', __name__) +config = None + + +@ui.record +def record(state): + global config + config = state.app.config @ui.route('/predict') @@ -38,14 +44,19 @@ def learn(): return current_app.send_static_file('learn.html') +@ui.route('/settings') +def settings(): + return current_app.send_static_file('settings.html') + + @ui.route('/image/') def image(filename): - return send_from_directory(Config.DOWNLOAD_IMAGE_DIR, filename) + return send_from_directory(config['DOWNLOAD_IMAGE_DIR'], filename) @ui.route('/_capture') def capture(): - tmp_dir = os.path.join(Config.DOWNLOAD_IMAGE_DIR, 'tmp') + tmp_dir = os.path.join(config['DOWNLOAD_IMAGE_DIR'], 'tmp') if not os.path.exists(tmp_dir): os.makedirs(tmp_dir) tmp_file = os.path.join(tmp_dir, '{}.jpg'.format(datetime.now().strftime('%Y%m%d_%H%M%S'))) @@ -54,7 +65,7 @@ def capture(): def remove_tmp_file(response): os.remove(tmp_file) - image_capture = load_class(Config.CLASS_IMAGE_CAPTURE).from_config(Config) + image_capture = load_class(config['CLASS_IMAGE_CAPTURE']).from_config(config) img = image_capture.capture() cv2.imwrite(tmp_file, img) diff --git a/webapp/mock-api/README.md b/webapp/mock-api/README.md new file mode 100644 index 0000000..d3d963d --- /dev/null +++ b/webapp/mock-api/README.md @@ -0,0 +1,35 @@ +### Warning +This mock-api might to out of date. Currently this does not return the centroid of the candies, which the real API does. This data is not used by the frontend at the time of writing, but this app is used at your own risk to simplify frontend development. + +### About +This Flask application will return static, but valid, data to enable faster setup and development of the UI. All data and images are hard-coded. The static files are collected from the static folder in candysorter. + +### Setup +Python 3.x is required with flask installed. pip install -r requirements.txt + + +### Usage + +#### *nix +(you must be in the correct directory: mock-api) +``` +$ export FLASK_APP=api.py +``` + +#### Windows +If you are on Windows, the environment variable syntax depends on command line interpreter. +On Command Prompt: +``` +$ C:\path\to\app>set FLASK_APP=api.py +``` + +And on PowerShell: +``` +$ PS C:\path\to\app> $env:FLASK_APP = "api.py" +``` + +#### Run +``` +$ flask run +* Running on http://127.0.0.1:5000/ +``` diff --git a/webapp/mock-api/api.py b/webapp/mock-api/api.py new file mode 100644 index 0000000..5c84e5b --- /dev/null +++ b/webapp/mock-api/api.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +from flask import Flask, make_response, send_from_directory, send_file + +app = Flask(__name__, static_folder='../candysorter/static') + + +def __respond_json(text): + resp = make_response(text) + resp.headers['Content-Type'] = 'application/json' + return resp + + +@app.route('/api/translate', methods=['POST']) +def translate(): + return __respond_json(''' + [ + { + "input": "kan jeg få sjokolade", + "translatedText": "Can I have some chocolate" + } + ] + ''') + + +@app.route('/api/morphs', methods=['POST']) +def morph(): + return __respond_json(''' + { + "morphs": [ + { + "depend": { + "index": [], + "label": "AUX" + }, + "pos": { + "case": "CASE_UNKNOWN", + "number": "NUMBER_UNKNOWN", + "tag": "VERB" + }, + "word": "Can" + }, + { + "depend": { + "index": [], + "label": "NSUBJ" + }, + "pos": { + "case": "NOMINATIVE", + "number": "SINGULAR", + "tag": "PRON" + }, + "word": "I" + }, + { + "depend": { + "index": [ + 0, + 1, + 4 + ], + "label": "ROOT" + }, + "pos": { + "case": "CASE_UNKNOWN", + "number": "NUMBER_UNKNOWN", + "tag": "VERB" + }, + "word": "have" + }, + { + "depend": { + "index": [], + "label": "NN" + }, + "pos": { + "case": "CASE_UNKNOWN", + "number": "NUMBER_UNKNOWN", + "tag": "NOUN" + }, + "word": "som" + }, + { + "depend": { + "index": [ + 3 + ], + "label": "DOBJ" + }, + "pos": { + "case": "CASE_UNKNOWN", + "number": "SINGULAR", + "tag": "NOUN" + }, + "word": "chocolate" + } + ] + } + ''') + + +@app.route('/api/similarities', methods=['POST']) +def similarities(): + return __respond_json(''' + { + "similarities": { + "embedded": [ + { + "box": [ + [ + 1194, + 120 + ], + [ + 1442, + 114 + ], + [ + 1450, + 408 + ], + [ + 1201, + 415 + ] + ], + "coords": [ + 0.3052026444050034, + 0.46767496541417386 + ], + "similarities": [ + { + "em": 0.011613314040005207, + "label": "chocolate", + "lid": 1 + }, + { + "em": 0.0621197447180748, + "label": "gum", + "lid": 2 + }, + { + "em": 0.9146535396575928, + "label": "liquorice", + "lid": 3 + }, + { + "em": 0.011613314040005207, + "label": "smurf", + "lid": 4 + } + ], + "url": "/image/20180710_094456_7523664522134304/candy_00_8kx2jt12.jpg" + }, + { + "box": [ + [ + 392, + 510 + ], + [ + 668, + 553 + ], + [ + 622, + 853 + ], + [ + 345, + 810 + ] + ], + "coords": [ + 0.30000000000000004, + 0.4668201521292226 + ], + "similarities": [ + { + "em": 0.004918794147670269, + "label": "chocolate", + "lid": 1 + }, + { + "em": 0.05848325043916702, + "label": "gum", + "lid": 2 + }, + { + "em": 0.9316791296005249, + "label": "liquorice", + "lid": 3 + }, + { + "em": 0.004918794147670269, + "label": "smurf", + "lid": 4 + } + ], + "url": "/image/20180710_094456_7523664522134304/candy_01_d4qu3c7b.jpg" + }, + { + "box": [ + [ + 1185, + 603 + ], + [ + 1421, + 538 + ], + [ + 1528, + 929 + ], + [ + 1292, + 993 + ] + ], + "coords": [ + 0.7, + 0.29999999999999993 + ], + "similarities": [ + { + "em": 0.005034829024225473, + "label": "chocolate", + "lid": 1 + }, + { + "em": 0.9848954677581787, + "label": "gum", + "lid": 2 + }, + { + "em": 0.005034829024225473, + "label": "liquorice", + "lid": 3 + }, + { + "em": 0.005034829024225473, + "label": "smurf", + "lid": 4 + } + ], + "url": "/image/20180710_094456_7523664522134304/candy_02_37bvx84t.jpg" + } + ], + "force": [ + { + "em": 0.9, + "label": "chocolate", + "lid": 1 + }, + { + "em": 0.6333333333333333, + "label": "gum", + "lid": 2 + }, + { + "em": 0.3666666666666667, + "label": "liquorice", + "lid": 3 + }, + { + "em": 0.1, + "label": "smurf", + "lid": 4 + } + ], + "nearest": { + "box": [ + [ + 1185, + 603 + ], + [ + 1421, + 538 + ], + [ + 1528, + 929 + ], + [ + 1292, + 993 + ] + ], + "coords": [ + 0.7, + 0.29999999999999993 + ], + "similarities": [ + { + "em": 0.005034829024225473, + "label": "chocolate", + "lid": 1 + }, + { + "em": 0.9848954677581787, + "label": "gum", + "lid": 2 + }, + { + "em": 0.005034829024225473, + "label": "liquorice", + "lid": 3 + }, + { + "em": 0.005034829024225473, + "label": "smurf", + "lid": 4 + } + ], + "url": "/image/20180710_094456_7523664522134304/candy_02_37bvx84t.jpg" + }, + "url": "/image/20180710_094456_7523664522134304/snapshot.jpg" + } + } + ''') + + +@app.route('/api/pickup', methods=['POST']) +def pickup(): + return __respond_json(''' + [ + { + "input": "kan jeg få sjokolade", + "translatedText": "Can I have some chocolate" + } + ] + ''') + + +@app.route('/image/') +def image(filename): + return send_from_directory('images', filename) + + +@app.route('/predict') +def predict(): + return send_file('../candysorter/static/predict.html') + + +@app.route('/learn') +def learn(): + return send_file('../candysorter/static/learn.html') + +@app.route('/settings') +def settings(): + return send_file('../candysorter/static/settings.html') diff --git a/webapp/mock-api/images/20180710_094456_7523664522134304/candy_00_8kx2jt12.jpg b/webapp/mock-api/images/20180710_094456_7523664522134304/candy_00_8kx2jt12.jpg new file mode 100644 index 0000000..7d215a0 Binary files /dev/null and b/webapp/mock-api/images/20180710_094456_7523664522134304/candy_00_8kx2jt12.jpg differ diff --git a/webapp/mock-api/images/20180710_094456_7523664522134304/candy_01_d4qu3c7b.jpg b/webapp/mock-api/images/20180710_094456_7523664522134304/candy_01_d4qu3c7b.jpg new file mode 100644 index 0000000..867dcab Binary files /dev/null and b/webapp/mock-api/images/20180710_094456_7523664522134304/candy_01_d4qu3c7b.jpg differ diff --git a/webapp/mock-api/images/20180710_094456_7523664522134304/candy_02_37bvx84t.jpg b/webapp/mock-api/images/20180710_094456_7523664522134304/candy_02_37bvx84t.jpg new file mode 100644 index 0000000..0a66ef6 Binary files /dev/null and b/webapp/mock-api/images/20180710_094456_7523664522134304/candy_02_37bvx84t.jpg differ diff --git a/webapp/mock-api/images/20180710_094456_7523664522134304/snapshot.jpg b/webapp/mock-api/images/20180710_094456_7523664522134304/snapshot.jpg new file mode 100644 index 0000000..99222a2 Binary files /dev/null and b/webapp/mock-api/images/20180710_094456_7523664522134304/snapshot.jpg differ diff --git a/webapp/mock-api/requirements.txt b/webapp/mock-api/requirements.txt new file mode 100644 index 0000000..5c8c61b --- /dev/null +++ b/webapp/mock-api/requirements.txt @@ -0,0 +1 @@ +flask==1.0.2 \ No newline at end of file diff --git a/webapp/requirements/base.txt b/webapp/requirements/base.txt index 525927f..7fd3aa5 100644 --- a/webapp/requirements/base.txt +++ b/webapp/requirements/base.txt @@ -1,14 +1,18 @@ -Flask==0.12 -uWSGI==2.0.14 +Flask==1.0.2 -pytz==2016.10 -google-cloud==0.23.0 -numpy==1.12.0 -scipy==0.18.1 -gensim==0.13.4.1 -scikit-learn==0.18.1 +pytz==2018.3 +google-cloud-core==0.23.1 +google-cloud-language==0.23.1 +google-cloud-storage==0.23.1 +google-cloud-vision==0.23.3 +google-cloud-translate==0.23.0 -h5py==2.6.0 -protobuf==3.2.0 -https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.12.1-cp27-none-linux_x86_64.whl +numpy==1.14.5 +scipy==1.1.0 +gensim==3.4.0 +scikit-learn==0.19.1 + +h5py==2.8.0 +protobuf==3.6.0 +tensorflow==1.8.0 diff --git a/webapp/requirements/dev.txt b/webapp/requirements/dev.txt index e40cb4a..442c8dc 100644 --- a/webapp/requirements/dev.txt +++ b/webapp/requirements/dev.txt @@ -1,10 +1,11 @@ -r base.txt -flake8==3.2.1 +flake8==3.5.0 flake8-coding==1.3.0 flake8-copyright==0.2.0 -flake8-future-import==0.4.3 -flake8-import-order==0.11 -flake8-print==2.0.2 -flake8-quotes==0.8.1 -pep8-naming==0.4.1 +flake8-future-import==0.4.5 +flake8-import-order==0.17.1 +flake8-print==3.1.0 +flake8-quotes==1.0.0 +pep8-naming==0.7.0 + diff --git a/webapp/requirements/prd.txt b/webapp/requirements/prd.txt index a3e81b8..ae850a3 100644 --- a/webapp/requirements/prd.txt +++ b/webapp/requirements/prd.txt @@ -1 +1,2 @@ -r base.txt +uWSGI==2.0.17 \ No newline at end of file diff --git a/webapp/run.py b/webapp/run.py index db441ce..e21279e 100644 --- a/webapp/run.py +++ b/webapp/run.py @@ -17,14 +17,15 @@ import argparse -from candysorter import create_app +from candysorter.app import create_app parser = argparse.ArgumentParser(description='CandySorter WebApp') parser.add_argument('--host', type=str, default='0.0.0.0') parser.add_argument('--port', type=int, default=18000) +parser.add_argument('--instance_path', type=str, default=None) args = parser.parse_args() -app = create_app() +app = create_app(args.instance_path) if __name__ == '__main__': - app.run(host=args.host, port=args.port) + app.run(host=args.host, port=args.port, use_reloader=False, threaded=False)