Ejemplo n.º 1
0
def create_animation_from_b64_jpgs_route():
    
    # If using a GET request, return some info for how to use POST route
    if flask_request.method == "GET":
        info_list = ["Use (as a POST request) to create animations",
                     "Data is expected to be provided in JSON, in the following format:",
                     "{",
                     " 'frame_rate': (float),",
                     " 'b64_jpgs': (list of b64-encoded jpgs)",
                     "}",
                     "The 'b64_jpgs' entry should contain a sequence of base64 encoded jpgs to be rendered",
                     "The first entry in the list will be the first frame of the animation"]
        return json_response(info_list, status_code = 200)
    
    # -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
    
    # If we get here, we're dealing with a POST request, make sure we got something...
    animation_data_dict = flask_request.get_json(force = True)
    missing_animation_data = (animation_data_dict is None)
    if missing_animation_data:
        error_msg = "Missing animation data. Call this route as a GET request for more info"
        return error_response(error_msg, status_code = 400)
    
    # Pull out global information
    frame_rate = animation_data_dict.get("frame_rate", get_default_fps())
    b64_jpgs_list = animation_data_dict.get("b64_jpgs", [])
    
    # Bail if we got no image data
    data_is_valid = (len(b64_jpgs_list) > 0)
    if not data_is_valid:
        error_msg = "Did not find any base64 jpgs data to render!"
        return error_response(error_msg, status_code = 400)
    
    return create_video_response_from_b64_jpgs(b64_jpgs_list, frame_rate)
Ejemplo n.º 2
0
def simple_replay_route(camera_select, start_ems, end_ems):
    
    # Check dbserver connection, since we'll need it to get snapshot listing
    dbserver_is_connected = check_server_connection(DBSERVER_URL, feedback_on_error = False)
    if not dbserver_is_connected:
        error_msg = "No connection to dbserver!"
        return error_response(error_msg, status_code = 500)
    
    # Interpret ghosting flag
    enable_ghosting_str = flask_request.args.get("ghost", "true")
    enable_ghosting_bool = (enable_ghosting_str.lower() in {"1", "true", "on", "enable"})
    
    # Request snapshot timing info from dbserver
    snap_ems_list = get_snapshot_ems_list(DBSERVER_URL, camera_select, start_ems, end_ems)
    no_snapshots_to_download = (len(snap_ems_list) == 0)
    if no_snapshots_to_download:
        error_msg = "No snapshots in provided time range"
        return error_response(error_msg, status_code = 400)
    
    # Make sure snapshot times are ordered!
    snap_ems_list = sorted(snap_ems_list)
    
    return create_video_simple_replay(DBSERVER_URL, camera_select, snap_ems_list, enable_ghosting_bool)
Ejemplo n.º 3
0
def create_video_response_from_b64_jpgs(base64_jpgs_list, frames_per_second):

    try:
        # Convert each base64 string into image data
        with TemporaryDirectory() as temp_dir:
            for each_idx, each_b64_jpg_string in enumerate(base64_jpgs_list):

                # Remove encoding prefix data
                data_prefix, base64_string = each_b64_jpg_string.split(",")
                image_bytes = base64.b64decode(base64_string)
                image_array = np.frombuffer(image_bytes, np.uint8)

                # Save image data to file system
                save_name = "{}.jpg".format(each_idx).rjust(20, "0")
                save_path = os.path.join(temp_dir, save_name)
                with open(save_path, "wb") as out_file:
                    out_file.write(image_array)

            # Create the video file and return for download
            path_to_video = create_video(temp_dir, frames_per_second,
                                         "From b64 jpgs")
            video_response = send_file(path_to_video,
                                       mimetype="video/mp4",
                                       as_attachment=False)

    except Exception as err:
        # If anything goes wrong, return an error response instead
        error_type = err.__class__.__name__
        print("",
              "{} (create_video_response_from_b64_jpgs):".format(error_type),
              str(err),
              sep="\n")
        error_msg = [
            "({}) Error creating video from b64 jpgs:".format(error_type),
            str(err)
        ]
        video_response = error_response(error_msg, status_code=500)

    return video_response
Ejemplo n.º 4
0
def create_video_from_instructions(dbserver_url, camera_select,
                                   instructions_list, frames_per_second,
                                   ghost_config_dict):

    try:

        # Grab a background image if we're ghosting
        bg_frame = None
        enable_ghosting = ghost_config_dict.get("enable", False)
        if enable_ghosting:
            last_snapshot_instruction = instructions_list[-1]
            last_snap_ems = last_snapshot_instruction.get("snapshot_ems", None)
            got_background, bg_bytes = get_background_image_bytes(
                dbserver_url, camera_select, last_snap_ems)
            if not got_background:
                raise FileNotFoundError(
                    "Couldn't retrieve background image for ghosting!")
            bg_frame = image_bytes_to_pixels(bg_bytes)

        # Convert each base64 string into image data
        with TemporaryDirectory() as temp_dir:
            for each_idx, each_instruction_dict in enumerate(
                    instructions_list):

                # Pull out instruction data (skip if snapshot epoch ms value is missing)
                drawing_list = each_instruction_dict.get("drawing", [])
                snapshot_ems = each_instruction_dict.get("snapshot_ems", None)
                if snapshot_ems is None:
                    continue

                # First retrieve snapshot
                got_snapshot, snap_bytes = get_snapshot_image_bytes(
                    dbserver_url, camera_select, snapshot_ems)
                if not got_snapshot:
                    continue

                # Convert to pixel data so we can work with the image and apply ghosting if needed
                display_frame = image_bytes_to_pixels(snap_bytes)
                if enable_ghosting:
                    display_frame = apply_ghosting(bg_frame, display_frame,
                                                   **ghost_config_dict)

                # Interpret all drawing instructions
                for each_draw_call in drawing_list:
                    display_frame = interpret_drawing_call(
                        display_frame, each_draw_call)

                # Save image data to file system
                save_name = "{}.jpg".format(each_idx).rjust(20, "0")
                save_path = os.path.join(temp_dir, save_name)
                cv2.imwrite(save_path, display_frame)

            # Create the video file and return for download
            path_to_video = create_video(temp_dir, frames_per_second,
                                         "From instructions")
            video_response = send_file(path_to_video,
                                       mimetype="video/mp4",
                                       as_attachment=False)

    except Exception as err:
        # If anything goes wrong, return an error response instead
        error_type = err.__class__.__name__
        print("",
              "{} (create_video_from_instructions):".format(error_type),
              str(err),
              sep="\n")
        error_msg = [
            "({}) Error creating video from instructions:".format(error_type),
            str(err)
        ]
        video_response = error_response(error_msg, status_code=500)

    return video_response
Ejemplo n.º 5
0
def create_video_simple_replay(dbserver_url, camera_select, snapshot_ems_list,
                               enable_ghosting):

    # Hard-code 'simple' video parameters
    frame_rate = get_default_fps()
    ghost_config_dict = {
        "enable": enable_ghosting,
        "brightness_scaling": 1.5,
        "blur_size": 2,
        "pixelation_factor": 3
    }

    try:

        # Grab a background image if we're ghosting
        bg_frame = None
        enable_ghosting = ghost_config_dict.get("enable", False)
        if enable_ghosting:
            last_snap_ems = snapshot_ems_list[-1]
            got_background, bg_bytes = get_background_image_bytes(
                dbserver_url, camera_select, last_snap_ems)
            if not got_background:
                raise FileNotFoundError(
                    "Couldn't retrieve background image for ghosting!")
            bg_frame = image_bytes_to_pixels(bg_bytes)

        # Download each of the snapshot images to a temporary folder
        with TemporaryDirectory() as temp_dir:

            # Save a jpg for each of the provided epoch ms values
            for each_idx, each_snap_ems in enumerate(snapshot_ems_list):

                # Request image data from dbserver
                got_snapshot, snap_bytes = get_snapshot_image_bytes(
                    dbserver_url, camera_select, each_snap_ems)
                if not got_snapshot:
                    continue

                # Apply ghosting if needed
                if enable_ghosting:
                    snap_frame = image_bytes_to_pixels(snap_bytes)
                    ghost_frame = apply_ghosting(bg_frame, snap_frame,
                                                 **ghost_config_dict)
                    snap_bytes = image_pixels_to_bytes(ghost_frame)

                # Save the jpgs!
                save_one_jpg(temp_dir, each_idx, snap_bytes)

            # Create the video file and return for download
            path_to_video = create_video(temp_dir, frame_rate, "Simple replay")
            user_file_name = "simple_replay.mp4"
            video_response = send_file(path_to_video,
                                       attachment_filename=user_file_name,
                                       mimetype="video/mp4",
                                       as_attachment=True)

    except Exception as err:
        # If anything goes wrong, return an error response instead
        error_type = err.__class__.__name__
        print("",
              "{} (create_simple_video_response):".format(error_type),
              str(err),
              sep="\n")
        error_msg = [
            "({}) Error creating simple replay video:".format(error_type),
            str(err)
        ]
        video_response = error_response(error_msg, status_code=500)

    return video_response
Ejemplo n.º 6
0
def get_perspective_correction_route():
    
    # If using a GET request, return some info for how to use POST route
    if flask_request.method == "GET":
        info_list = ["Use (as a POST request) to get perspective correction data",
                     "Data is expected to be provided in JSON, in the following format:",
                     "{",
                     " 'input_quad': list of 4 xy-pairs",
                     "}",
                     "- Input quad is assumed to represent a shape that would be rectangular if not for perspective",
                     "- Input quad points are expected to be in normalized units",
                     "- The order of the quad points will affect the orientation of the output mapping",
                     "  - Assumed to be provided as [top-left, top-right, bot-right, bot-left]",
                     "- The warping will map these points to: (0, 0), (1, 0), (1, 1), (0, 1)",
                     "",
                     "This route will return two matrices, for mapping points in either direction",
                     "- 'in_to_out_matrix' maps points from the input co-ords. to (warped) output co-ords.",
                     "- 'out_to_in_matrix' maps (warped) outputs back to input co-ords.",
                     "",
                     "To use in-to-out mapping:",
                     "Assume we have an input xy co-ordinate (xi, yi)",
                     "Assume we have an in-to-out matrix:",
                     "",
                     "         [m11, m12, m13]",
                     "  Mi2o = [m21, m22, m23]",
                     "         [m31, m32, m33]",
                     "",
                     "Form intermediate values Nx, Ny and D, given by:",
                     "",
                     "  Nx = (m11 * xi) + (m12 * yi) + m13",
                     "  Ny = (m21 * xi) + (m22 * yi) + m23",
                     "   D = (m31 * xi) + (m32 * yi) + m33",
                     "",
                     "Note: This result is a matrix-vector multiplication using: Mi2o * [xi, yi, 1]^T",
                     "The warped outputs (xo, yo) are then given by:",
                     "",
                     "  xo = Nx / D",
                     "  yo = Ny / D",
                     "",
                     "The out-to-in matrix can be used in the same way to warp back to input co-ordinates!"]
        return json_response(info_list, status_code = 200)
    
    # -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
    
    # If we get here, we're dealing with a POST request, make sure we got something...
    post_data_dict = flask_request.get_json(force = True)
    missing_data = (post_data_dict is None)
    if missing_data:
        error_msg = "Missing post data. Call this route as a GET request for more info"
        return error_response(error_msg, status_code = 400)
    
    # Pull out correction request data information
    input_quad = post_data_dict.get("input_quad", None)
    
    # Bail if the input quad draw is bad
    in_quad_is_valid, error_msg = check_valid_quad(input_quad)
    if not in_quad_is_valid:
        return error_response(error_msg, status_code = 400)
    
    # Get perspective correction data
    correction_is_valid = False
    try:
        correction_is_valid, in_to_out_warp_mat_as_list, out_to_in_warp_mat_as_list = \
        calculate_perspective_correction_factors(input_quad)
        
    except (ValueError, TypeError, AttributeError) as err:
        error_msg = "Unknown error calculating perspective matricies ({})".format(str(err))
        return error_response(error_msg)
    
    # Bail on bad corrections
    if not correction_is_valid:
        error_msg = "Invalid perspective correction! Quad may not be possible to correct..."
        return error_response(error_msg)
    
    # Bundle outputs if we get this far
    return_result = {"in_to_out_matrix": in_to_out_warp_mat_as_list,
                     "out_to_in_matrix": out_to_in_warp_mat_as_list}
    
    return json_response(return_result, status_code = 200)
Ejemplo n.º 7
0
def create_animation_from_instructions_route():
    
    # If using a GET request, return some info for how to use POST route
    if flask_request.method == "GET":
        info_list = ["Use (as a POST request) to create animations",
                     "Data is expected to be provided in JSON, in the following format:",
                     "{",
                     " 'camera_select: (string),",
                     " 'frame_rate': (float),",
                     " 'ghosting': {",
                     "              'enable': (boolean),",
                     "              'brightness_scaling': (float),",
                     "              'blur_size': (int),",
                     "              'pixelation_factor': (int)",
                     "             },",
                     " 'instructions': [...]",
                     "}",
                     "",
                     "The 'instructions' key should be a list drawing instructions for each snapshot",
                     "The first entry in the list will be the first frame of the animation",
                     "Each entry in the instructions list should be another JSON object, in the following format:",
                     "[",
                     " {'snapshot_ems': (int), 'drawing': [...]},",
                     " {... next frame ...},",
                     " {... next frame ...},",
                     " etc.",
                     "]",
                     "",
                     "The 'drawing' key should hold a list of what should be drawn on the corresponding snapshot",
                     "Each entry in the drawing list should be a JSON object (see below for options)",
                     "If nothing is to be drawn, the drawing instructions should be an empty list: []",
                     "The following drawing instructions are available:",
                     "{",
                     " 'type': 'polyline',",
                     " 'xy_points_norm': (list of xy pairs in normalized co-ordinates),",
                     " 'is_closed': (boolean),",
                     " 'color_rgb': (list of 3 values between 0-255),",
                     " 'thickness_px': (int, use -1 to fill),",
                     " 'antialiased': (boolean)",
                     "}",
                     "",
                     "{",
                     " 'type': 'circle',",
                     " 'center_xy_norm': (pair of xy values in normalized co-ordinates),",
                     " 'radius_norm': (float, normalized to frame diagonal length),",
                     " 'color_rgb': (list of 3 values between 0-255),",
                     " 'thickness_px': (int, use -1 to fill),",
                     " 'antialiased': (boolean)",
                     "}",
                     "",
                     "{",
                     " 'type': 'rectangle',",
                     " 'top_left_norm': (pair of xy values in normalized co-ordinates),",
                     " 'bottom_right_norm': (pair of xy values in normalized co-ordinates),",
                     " 'color_rgb': (list of 3 values between 0-255),",
                     " 'thickness_px': (int, use -1 to fill),",
                     " 'antialiased': (boolean)",
                     "}",
                     "",
                     "{",
                     " 'type': 'text',",
                     " 'message': (string),",
                     " 'text_xy_norm': (pair of xy values in normalized co-ordinates),",
                     " 'align_horizontal': ('left', 'center' or 'right')",
                     " 'align_vertical': ('top', 'center' or 'bottom')",
                     " 'text_scale': (float)",
                     " 'color_rgb': (list of 3 values between 0-255),",
                     " 'bg_color_rgb': (list of 3 values between 0-255 or null to disable),",
                     " 'thickness_px': (int, use -1 to fill),",
                     " 'antialiased': (boolean)",
                     "}"]
        return json_response(info_list, status_code = 200)
    
    # -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
    
    # If we get here, we're dealing with a POST request, make sure we got something...
    animation_data_dict = flask_request.get_json(force = True)
    missing_animation_data = (animation_data_dict is None)
    if missing_animation_data:
        error_msg = "Missing animation data. Call this route as a GET request for more info"
        return error_response(error_msg, status_code = 400)
    
    # Pull-out global config settings (or defaults)
    camera_select = animation_data_dict.get("camera_select", None)
    frame_rate = animation_data_dict.get("frame_rate", get_default_fps())
    ghost_config_dict = animation_data_dict.get("ghosting", {"enable": False})
    instructions_list = animation_data_dict.get("instructions", [])
    
    # Bail if no camera was selected
    bad_camera = (camera_select is None)
    if bad_camera:
        error_msg = "No camera selected"
        return error_response(error_msg, status_code = 400)
    
    # Bail if we got no frame instructions
    data_is_valid = (len(instructions_list) > 0)
    if not data_is_valid:
        error_msg = "Did not find any drawing instructions"
        return error_response(error_msg, status_code = 400)
    
    # Check dbserver connection, since we'll need it to get snapshot data
    dbserver_is_connected = check_server_connection(DBSERVER_URL, feedback_on_error = False)
    if not dbserver_is_connected:
        error_msg = "No connection to dbserver!"
        return error_response(error_msg, status_code = 500)
    
    # Use instructions to get target snapshots & draw overlay as needed
    return create_video_from_instructions(DBSERVER_URL,
                                          camera_select, instructions_list, frame_rate, ghost_config_dict)