Beispiel #1
0
def assignment_load_handler(obj_response, form_values):
    """
    Load camera assignment from file.
    """
    camera_assignment_old = redis_tools.get_dict(db, 'camera_assignment')
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    select_values = create_select_values(mjpeg_info_dict)

    # Load current camera assignment from file
    yaml_dict = file_tools.read_camera_assignment()
    camera_assignment_new = assignment_from_yaml_dict(yaml_dict)

    camera_assignment = {}
    for camera_id in camera_assignment_old:
        try:
            value = camera_assignment_new[camera_id]
        except KeyError:
            value = '--'
        if not value in select_values:
            value = '--'
        camera_assignment[camera_id] = value

    redis_tools.set_dict(db, 'camera_assignment', camera_assignment)
    set_camera_assignment(obj_response, camera_assignment, select_values)
    obj_response.attr('#message', 'style', 'color:black')
    obj_response.html('#message', 'Current camera assignment loaded')
    obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #2
0
def index():

    if flask.g.sijax.is_sijax_request: 
        flask.g.sijax.register_callback('calibrate_button', calibrate_button_handler)
        flask.g.sijax.register_callback('save_button', save_button_handler)
        flask.g.sijax.register_callback('reset_button_ok', reset_button_ok_handler)
        flask.g.sijax.register_callback('reset_button_cancel', reset_button_cancel_handler)
        flask.g.sijax.register_callback('timer_update', timer_update_handler)
        return flask.g.sijax.process_request()

    else:
        # Get scale and compute image width
        scale, scale_options = common_args.get_scale(config,flask.request)
        redis_tools.set_str(db,'scale', scale)
        image_width, image_height = get_image_size(scale)

        ip_iface_ext = redis_tools.get_str(db,'ip_iface_ext')
        mjpeg_info = redis_tools.get_dict(db,'mjpeg_info_dict')
        camera_pairs = redis_tools.get_dict(db,'camera_pairs_dict')
        camera_pairs_mjpeg_info = get_camera_pairs_mjpeg_info(camera_pairs, mjpeg_info)
        calibration_info = get_calibration_info()
            
        render_dict = {
                'scale': scale,
                'scale_options': scale_options,
                'image_width': image_width,
                'image_height': image_height,
                'ip_iface_ext': ip_iface_ext,
                'camera_pairs': camera_pairs,
                'camera_pairs_mjpeg_info': camera_pairs_mjpeg_info,
                'calibration_info': calibration_info,
                }

        return flask.render_template('transform_2d_calibration.html',**render_dict)
Beispiel #3
0
def timer_update_handler(obj_response):
    """
    Updates the camera assignment form to the latest values in the database. This function
    is from a timer on the client and is used to keep multiple instances of the interface
    in sync.
    """

    camera_assignment = redis_tools.get_dict(db, 'camera_assignment')
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    select_values = create_select_values(mjpeg_info_dict)
    set_camera_assignment(obj_response, camera_assignment, select_values)
Beispiel #4
0
def get_extra_video_mjpeg_info(video):
    """
    Returns a dictionary containing the mjpeg stream information for the
    extra video named video
    """
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    extra_video_dict = redis_tools.get_dict(db, 'extra_video_dict')
    extra_video_topic = extra_video_dict[video]
    extra_video_mjpeg_info = {}
    for v in mjpeg_info_dict.values():
        if extra_video_topic == v['image_topic']:
            extra_video_mjpeg_info['image_topic'] = v['image_topic']
            extra_video_mjpeg_info['mjpeg_port'] = v['mjpeg_port']
            break
    return extra_video_mjpeg_info
Beispiel #5
0
def save_button_handler(obj_response):

    # Collect calibration data for camera pairs
    pairs_dict = redis_tools.get_dict(db,'camera_pairs_dict')
    calibration_dict = {}
    calibration_list = []
    for pairs_list in pairs_dict.values():
        for camera0, camera1 in pairs_list:
            calibrator_name = get_calibrator_name(camera0,camera1)
            if transform_2d_calibrator.is_calibrated(calibrator_name):
                rot, tx, ty = transform_2d_calibrator.get_transform_2d(calibrator_name)
                calibration_dict[(camera0,camera1)] = {
                        'rotation': rot,
                        'translation_x': tx,
                        'translation_y': ty,
                        }
                calibration_list.append((camera0,camera1))

    file_tools.write_transform_2d_calibration(calibration_dict)

    table_data = []
    for camera0, camera1 in calibration_list:
        table_data.append('<tr> <td>')
        table_data.append('{0}, {1}'.format(camera0,camera1))
        table_data.append('</td> </tr>')
    table_data = '\n'.join(table_data)

    if calibration_dict:
        obj_response.html('#message', 'Saved calibrations for camera pairs:')
        obj_response.html('#message_table', table_data)
        obj_response.attr('#message_table', 'style', 'display:block')
    else:
        obj_response.html('#message', 'No data to save')
        obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #6
0
def get_tab_list():
    """
    Generates list of tabs and their urls.
    """
    regions_dict = redis_tools.get_dict(db, 'regions_dict')
    extra_video_dict = redis_tools.get_dict(db, 'extra_video_dict')
    tab_list = []
    tab_list.append(('control', flask.url_for('show_control')))
    for region in regions_dict:
        tab_list.append((region, flask.url_for('show_region', region=region)))
    for name in extra_video_dict:
        tab_list.append((name, flask.url_for('show_extra_video', video=name)))
    tab_list.sort()
    tab_list.append(('lighting', flask.url_for('lighting')))
    tab_list.append(('ros', flask.url_for('show_ros_info')))
    return tab_list
Beispiel #7
0
def reset_button_cancel_handler(obj_response):
    """
    Callback for when the user clicks the cancel button and answers in the negative. 
    """
    target_info = redis_tools.get_dict(db, 'target_info')
    obj_response.html('#message', 'Reset canceled')
    obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #8
0
def index(): 

    # Get scale and compute image width
    scale, scale_options = common_args.get_scale(config,flask.request)
    redis_tools.set_str(db,'scale', scale)
    image_width, image_height = get_image_size(scale)

    ip_iface_ext = redis_tools.get_str(db,'ip_iface_ext')
    mjpeg_info_dict = redis_tools.get_dict(db,'mjpeg_info_dict')
    mjpeg_info = sorted(mjpeg_info_dict.items(), cmp=mjpeg_info_cmp)

    # Build dict of urls for single camera views
    single_view_url = {}
    for camera in mjpeg_info_dict:
        single_view_url[camera] = flask.url_for('single_camera_view.page',camera=camera)
        
    render_dict = {
            'scale'             : scale,
            'scale_options'     : scale_options,
            'image_width'       : image_width,
            'image_height'      : image_height,
            'ip_iface_ext'      : ip_iface_ext,
            'mjpeg_info'        : mjpeg_info,
            'single_view_url'   : single_view_url,
            }

    return flask.render_template('zoom_calibration.html',**render_dict)
Beispiel #9
0
def get_camera_calibration_info(): 
    mjpeg_info_dict = redis_tools.get_dict(db,'mjpeg_info_dict')
    calibration_info = mct_introspection.get_camera_calibration_info()
    for camera in mjpeg_info_dict:
        if not camera in calibration_info:
            calibration_info[camera] = {'modified': ''}
    return calibration_info
Beispiel #10
0
def get_calibration_info():
    """
    Gets the last modified date for any existing homography calibration files.
    """
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    calibration_info = mct_introspection.get_homography_calibration_info()
    for camera in mjpeg_info_dict:
        if not camera in calibration_info:
            calibration_info[camera] = {'modified': ''}
    return calibration_info
Beispiel #11
0
def index():

    if flask.g.sijax.is_sijax_request:
        flask.g.sijax.register_callback('calibrate_button_onclick', calibrate_button_handler)
        flask.g.sijax.register_callback('save_button_onclick', save_button_handler)
        flask.g.sijax.register_callback('reset_button_ok', reset_button_ok_handler)
        flask.g.sijax.register_callback('reset_button_cancel', reset_button_cancel_handler)
        flask.g.sijax.register_callback('timer_update', timer_update_handler)
        return flask.g.sijax.process_request()

    else:
        # Get scale and compute image width
        scale, scale_options = common_args.get_scale(config,flask.request)
        redis_tools.set_str(db,'scale', scale)
        image_width, image_height = get_image_size(scale)

        ip_iface_ext = redis_tools.get_str(db,'ip_iface_ext')
        mjpeg_info_dict = redis_tools.get_dict(db,'mjpeg_info_dict')
        mjpeg_info = sorted(mjpeg_info_dict.items(), cmp=mjpeg_info_cmp)
        target_info = redis_tools.get_dict(db,'target_info')

        # Get list of current camera calibration files
        calibration_info = get_camera_calibration_info()

        # Build dict of urls for single camera views
        single_view_url = {}
        for camera in mjpeg_info_dict:
            single_view_url[camera] = flask.url_for('single_camera_view.page',camera=camera)
            
        render_dict = {
                'scale'             : scale,
                'scale_options'     : scale_options,
                'image_width'       : image_width,
                'image_height'      : image_height,
                'ip_iface_ext'      : ip_iface_ext,
                'mjpeg_info'        : mjpeg_info,
                'target_info'       : target_info,
                'calibration_info'  : calibration_info,
                'single_view_url'   : single_view_url,
                }

        return flask.render_template('camera_calibration.html',**render_dict)
Beispiel #12
0
def get_camera_calibration_info():
    """
    Gets the last modified date for any existing homography calibration files.
    """
    regions_dict = redis_tools.get_dict(db, 'regions_dict')
    calibration_info = mct_introspection.get_camera_calibration_info()
    for region, camera_list in regions_dict.iteritems():
        for camera in camera_list:
            if not camera in calibration_info:
                calibration_info[camera] = {'modified': ''}
    return calibration_info
Beispiel #13
0
def show_control():
    """
    Tracking program main control page. Enables users to start/stop recording and 
    displays information about the running system.
    """
    if flask.g.sijax.is_sijax_request:
        flask.g.sijax.register_callback('mode_change_request',
                                        mode_change_handler)
        flask.g.sijax.register_callback('timer_update', timer_update_handler)
        return flask.g.sijax.process_request()
    else:
        current_mode = redis_tools.get_str(db, 'current_mode')
        watchdog_mjpeg_info = get_watchdog_mjpeg_info()
        regions_dict = redis_tools.get_dict(db, 'regions_dict')
        extra_video_dict = redis_tools.get_dict(db, 'extra_video_dict')
        homography_cal_info = get_homography_calibration_info()
        transform_2d_cal_info = get_transform_2d_calibration_info()
        camera_cal_info = get_camera_calibration_info()
        logging_params_dict = redis_tools.get_dict(db, 'logging_params_dict')
        camera_assignment = get_camera_assignment()
        all_cameras_sorted = camera_assignment.keys()
        all_cameras_sorted.sort(cmp=camera_name_cmp)
        camera_pairs_dict = get_camera_pairs_dict()

        render_dict = get_base_render_dict()
        page_render_dict = {
            'operating_modes': OPERATING_MODES,
            'current_mode': current_mode,
            'watchdog_mjpeg_info': watchdog_mjpeg_info,
            'regions_dict': regions_dict,
            'extra_video_dict': extra_video_dict,
            'logging_params_dict': logging_params_dict,
            'homography_cal_info': homography_cal_info,
            'transform_2d_cal_info': transform_2d_cal_info,
            'camera_cal_info': camera_cal_info,
            'camera_assignment': camera_assignment,
            'all_cameras_sorted': all_cameras_sorted,
            'camera_pairs_dict': camera_pairs_dict,
        }
        render_dict.update(page_render_dict)
        return flask.render_template('tracking_2d_control.html', **render_dict)
Beispiel #14
0
def reset_button_ok_handler(obj_response):
    """
    Callback for when the user clicks the reset button and answers in the
    affirmative. Requests that the calbirator_master node stop and re-start the
    individual camera calibrator nodes.
    """
    calibrator_master.stop()
    target_info = redis_tools.get_dict(db, 'target_info')
    obj_response.html('#develop', str(type(target_info['square'])))
    calibrator_master.start(target_info['size'], target_info['square'])
    obj_response.html('#message', 'Resetting camera calibrators')
    obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #15
0
def get_watchdog_mjpeg_info():
    """
    Returns a dictionary containing the mjpeg stream information for the
    watchdog information image stream.
    """
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    watchdog_mjpeg_info = {}
    for v in mjpeg_info_dict.values():
        if v['image_topic'] == '/image_frame_drop_watchdog':
            watchdog_mjpeg_info['image_topic'] = v['image_topic']
            watchdog_mjpeg_info['mjpeg_port'] = v['mjpeg_port']
            break
    return watchdog_mjpeg_info
Beispiel #16
0
def index():

    if flask.g.sijax.is_sijax_request:
        flask.g.sijax.register_callback('assignment_change',
                                        assignment_change_handler)
        flask.g.sijax.register_callback('clear_form', clear_form_handler)
        flask.g.sijax.register_callback('assignment_save',
                                        assignment_save_handler)
        flask.g.sijax.register_callback('assignment_load',
                                        assignment_load_handler)
        flask.g.sijax.register_callback('timer_update', timer_update_handler)
        flask.g.sijax.register_callback('test', test_handler)
        return flask.g.sijax.process_request()

    else:
        # Get scale and compute image width
        scale, scale_options = common_args.get_scale(config, flask.request)
        image_width = int(config.camera_image['width'] * float(scale))
        image_height = int(config.camera_image['height'] * float(scale))

        camera_assignment = redis_tools.get_dict(db, 'camera_assignment')
        mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
        ip_iface_ext = redis_tools.get_str(db, 'ip_iface_ext')
        select_values = create_select_values(mjpeg_info_dict)

        render_dict = {
            'mjpeg_info_dict': mjpeg_info_dict,
            'camera_assignment': camera_assignment,
            'select_values': select_values,
            'ip_iface_ext': ip_iface_ext,
            'scale_options': scale_options,
            'scale': scale,
            'image_width': image_width,
            'image_height': image_height,
        }

        redis_tools.set_dict(db, 'camera_assignment', camera_assignment)
        return flask.render_template('camera_assignment.html', **render_dict)
Beispiel #17
0
def show_extra_video(video):
    """
    Handles requests to view the requested extra video streams.
    """
    ip_iface_ext = redis_tools.get_str(db, 'ip_iface_ext')
    tab_list = get_tab_list()
    extra_video_mjpeg_info = get_extra_video_mjpeg_info(video)
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    render_dict = get_base_render_dict()
    page_render_dict = {
        'extra_video_name': video,
        'extra_video_mjpeg_info': extra_video_mjpeg_info,
    }
    render_dict.update(page_render_dict)
    return flask.render_template('tracking_2d_extra_video.html', **render_dict)
Beispiel #18
0
def get_empty_lighting_values():
    """
    Gets the lighting values from the controller
    """
    lighting_values = []
    lighting_params_dict = redis_tools.get_dict(db, 'lighting_params_dict')
    lighting_names_sorted = sorted(lighting_params_dict.keys())
    for name in lighting_names_sorted:
        channel_values = []
        for channel in lighting_params_dict[name]:
            channel_num = int(channel.split('_')[1])
            values_dict = {'enable': False, 'imax': '', 'iset': ''}
            channel_values.append((channel, values_dict))
        lighting_values.append((name, channel_values))
    return lighting_values
Beispiel #19
0
def get_region_mjpeg_info(region):
    """
    Returns a dictionary containing the mjpeg stream information for all
    of the images for the specified tracking region.
    """
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    region_mjpeg_info = {}
    for v in mjpeg_info_dict.values():
        image_topic_split = v['image_topic'].split('/')
        if region == image_topic_split[1]:
            image_name = image_topic_split[2]
            region_mjpeg_info[image_name] = {
                'image_topic': v['image_topic'],
                'mjpeg_port': v['mjpeg_port'],
            }
    return region_mjpeg_info
Beispiel #20
0
def get_calibration_info():
    """
    Gets the last modified date for any existing homography calibration files.
    """
    camera_pairs_dict = redis_tools.get_dict(db,'camera_pairs_dict')
    calibration_info = mct_introspection.get_transform_2d_calibration_info()
    calibration_info_mod = {}

    for pairs_list in camera_pairs_dict.values():
        for camera0, camera1 in pairs_list:
            pair_str = '{0}_{1}'.format(camera0,camera1)
            if not pair_str in calibration_info:
                calibration_info_mod[(camera0,camera1)] = {'modified': ''}
            else:
                calibration_info_mod[(camera0,camera1)] = calibration_info[pair_str]
    return calibration_info_mod
Beispiel #21
0
def assignment_change_handler(obj_response, form_values):
    """
    Handles changes to the camera assignment
    """
    camera_assignment_old = redis_tools.get_dict(db, 'camera_assignment')
    camera_assignment_new = json_tools.decode_dict(form_values)
    redis_tools.set_dict(db, 'camera_assignment', camera_assignment_new)

    message_str = ''
    for k, v in camera_assignment_new.iteritems():
        if v != camera_assignment_old[k]:
            message_str = 'Assigned camera {0} to GUID {1}'.format(v, k)

    obj_response.attr('#message', 'style', 'color:black')
    obj_response.html('#message', message_str)
    obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #22
0
def clear_form_handler(obj_response, form_values):
    """
    Handles requests to clear form
    """
    camera_assignment = json_tools.decode_dict(form_values)
    camera_assignment = dict((k, '--') for k in camera_assignment)
    redis_tools.set_dict(db, 'camera_assignment', camera_assignment)

    # Update form
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    select_values = create_select_values(mjpeg_info_dict)
    set_camera_assignment(obj_response, camera_assignment, select_values)

    # Update message
    obj_response.attr('#message', 'style', 'color:black')
    obj_response.html('#message', 'Camera assignment cleared')
    obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #23
0
def test_handler(obj_response, form_values):
    """
    Assign a test camera assignment - for development 
    """
    # Create a camera assignment
    camera_assignment = json_tools.decode_dict(form_values)
    cnt = 0
    test_camera_assignment = {}
    for k in camera_assignment:
        cnt += 1
        test_camera_assignment[k] = str(cnt)

    # Set camera assignment values
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
    select_values = create_select_values(mjpeg_info_dict)
    set_camera_assignment(obj_response, test_camera_assignment, select_values)

    redis_tools.set_dict(db, 'camera_assignment', test_camera_assignment)
    obj_response.attr('#message', 'style', 'color:black')
    obj_response.html('#message', 'Created test assignment')
    obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #24
0
def save_button_handler(obj_response):
    """
    Handler for requests to run save the homography matrices
    """
    mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')

    # Get dictionary of calibration data
    calibration_dict = {}
    for camera, info in mjpeg_info_dict.iteritems():
        topic = info['image_topic']
        calibrator_node = get_calibrator_from_topic(topic)
        if homography_calibrator.is_calibrated(calibrator_node):
            num_row, num_col, data = homography_calibrator.get_matrix(
                calibrator_node)
            calibration = {
                'rows': num_row,
                'cols': num_col,
                'data': list(data),
            }
            calibration_dict[camera] = calibration

    # Save calibration data
    file_tools.write_homography_calibration(calibration_dict)

    table_data = []
    camera_list = calibration_dict.keys()
    camera_list.sort(cmp=camera_name_cmp)
    for camera_name in camera_list:
        table_data.append('<tr> <td>')
        table_data.append(camera_name)
        table_data.append('</td> </tr>')
    table_data = '\n'.join(table_data)

    if calibration_dict:
        obj_response.html('#message', 'Saved calibrations for cameras:')
        obj_response.html('#message_table', table_data)
        obj_response.attr('#message_table', 'style', 'display:block')
    else:
        obj_response.html('#message', 'No data to save')
        obj_response.attr('#message_table', 'style', 'display:none')
Beispiel #25
0
def mode_change_handler(obj_response, new_mode):

    old_mode = redis_tools.get_str(db, 'current_mode')
    new_mode = str(new_mode)

    if old_mode == new_mode:
        return

    redis_tools.set_str(db, 'current_mode', new_mode)
    frame_rate = redis_tools.get_dict(db, 'frame_rate_dict')['tracking_2d']
    regions_dict = redis_tools.get_dict(db, 'regions_dict')

    # Stop camera triggers
    camera_trigger.stop()
    time.sleep(0.5)  # wait for all frames to pass throught the system
    reset_rand_sync()

    if old_mode == 'recording' or new_mode == 'recording':

        # Find logging and avi recording commands
        service_list = mct_introspection.get_services()
        logging_nodes = get_logging_nodes(service_list)
        recording_nodes = get_recording_nodes(service_list)

        if (old_mode == 'recording') and (new_mode != 'recording'):

            # Stop logging and recording
            for node in logging_nodes:
                tracking_pts_logger.stop_logging(node)
            for node in recording_nodes:
                avi_writer.stop_recording(node, 'dummy.avi', frame_rate)

        if new_mode == 'recording':

            # Create sub-directory for log files.
            log_dir_base = redis_tools.get_dict(
                db, 'logging_params_dict')['directory']
            log_dir = os.path.join(log_dir_base,
                                   datetime.datetime.now().isoformat())
            os.mkdir(log_dir)

            # Start loggers
            for node in logging_nodes:
                filename = '_'.join(node.split('/')[1:3])
                filename = os.path.join(log_dir, '{0}.json'.format(filename))
                tracking_pts_logger.start_logging(node, filename)

            # Start avi recordings
            for node in recording_nodes:
                filename = '_'.join(node.split('/')[1:3])
                filename = os.path.join(log_dir, '{0}.avi'.format(filename))
                avi_writer.start_recording(node, filename, frame_rate)

    if new_mode in ('preview', 'recording'):
        regions_dict = redis_tools.get_dict(db, 'regions_dict')
        # Reset frame drop correctors and restart camera triggers
        frame_drop_corrector.reset_all()
        frame_drop_watchdog.reset()

        # Reset image_stitcher and three_point_tracker synchronizer for all tracking regions
        for region in regions_dict:
            image_stitcher.reset(region)
            three_point_tracker_synchronizer.reset(region)

        camera_trigger.start(frame_rate)
Beispiel #26
0
def assignment_save_handler(obj_response, form_values):
    """
    Handles requests to save the current camera assignment. The assignment is 
    checked for unassigned guids and for duplicate assignments.
    """
    camera_assignment = json_tools.decode_dict(form_values)

    # Check for unassigned GUIDs
    unassigned = []
    for k, v in camera_assignment.iteritems():
        if v == '--':
            unassigned.append(k)

    # Check for duplicate values
    assignment_cnt = {}
    for k, v in camera_assignment.iteritems():
        try:
            assignment_cnt[v] += 1
        except KeyError:
            assignment_cnt[v] = 1

    duplicates = {}
    for k, v in camera_assignment.iteritems():
        try:
            cnt = assignment_cnt[v]
        except KeyError:
            continue

        if cnt > 1 and v != '--':
            try:
                duplicates[v].append(k)
            except KeyError:
                duplicates[v] = [k]

    # Send response
    if unassigned:
        # Unable to save - there are unassigned cameras
        table_data = []
        for k in unassigned:
            table_data.append('<tr>')
            table_data.append('<td> <b> GUID {0} </b> </td>'.format(k))
            table_data.append('</tr>')
        table_data = '\n'.join(table_data)
        message = 'Unable to save: unassigned GUIDs'
        obj_response.attr('#message', 'style', 'color:red')
        obj_response.html('#message', message)
        obj_response.html('#message_table', table_data)
        obj_response.attr('#message_table', 'style', 'display:block')

    elif duplicates:
        # Unable to save - there exist duplicate camera assignemts
        table_data = []
        for k, v in duplicates.iteritems():
            table_data.append('<tr>')
            table_data.append('<td> <b> camera {0} </b> </td>'.format(k))
            table_data.append('<td> &rarr; </td>')
            table_data.append('<td> <b> {0} </b> </td>'.format(str(v)))
            table_data.append('</tr>')
        table_data = '\n'.join(table_data)
        message = 'Unable to save: duplicate assignments exist'
        obj_response.attr('#message', 'style', 'color:red')
        obj_response.html('#message', message)
        obj_response.html('#message_table', table_data)
        obj_response.attr('#message_table', 'style', 'display:block')

    else:
        # Everthing is OK save camera assignment
        table_data = []
        for k, v in camera_assignment.iteritems():
            table_data.append('<tr>')
            table_data.append('<td> <b> camera {0} </b> </td>'.format(v))
            table_data.append('<td> &rarr; </td>')
            table_data.append('<td> <b> GUID {0} </b> </td>'.format(k))
            table_data.append('</tr>')
        table_data = '\n'.join(table_data)

        # Create camera assignment yaml dictionary and write to file
        mjpeg_info_dict = redis_tools.get_dict(db, 'mjpeg_info_dict')
        yaml_dict = create_yaml_dict(camera_assignment, mjpeg_info_dict)
        file_tools.write_camera_assignment(yaml_dict)

        message = 'Camera assignment saved'
        obj_response.attr('#message', 'style', 'color:green')
        obj_response.html('#message', message)
        obj_response.html('#message_table', table_data)
        obj_response.attr('#message_table', 'style', 'display:block')