def _find_display_coords(marker, marker_vertices, display):
    """Calculate the display corners from the marker corners. For now,
    assume everything is orthogonal."""

    # Get bounding box
    minv = Vec3.min(*marker_vertices)
    maxv = Vec3.max(*marker_vertices)
    size = maxv - minv

    # Stretch to account for white border
    minv -= size / 8.0
    maxv += size / 8.0
    size = maxv - minv

    # Stretch to display aspect ratio
    direction = marker.world_direction
    dar = display.aspectRatio
    if direction == 1 or direction == 3 :
        dar = 1.0 / dar

    if dar > 1:
        # The display is bigger horizontally than vertically.
        width = size.y * dar
        stretch = (width - size.x) / 2.0
        # The image is centered, so stretch in both directions.
        minv.x -= stretch
        maxv.x += stretch

    else:
        # The display is bigger vertically than horizontally.
        height = size.x / dar
        stretch = (height - size.y) / 2.0
        # The image is centered, so stretch in both directions.
        minv.y -= stretch
        maxv.y += stretch

    # Done
    return [minv, maxv]
def _calibrate(jumbotron, displays, image, debug=False, debug_image=False):
    """Calibrate a jumbotron with a calibration images."""
    if debug_image:
        draw = ImageDraw.Draw(image, "RGBA")

    # Initialize display viewports
    for display in displays.values():
        display.viewport = None

    # Find markers
    found_markers = artoolkit.detect(image, confidence_threshold=0.5,
                                     debug=debug, debug_image=debug_image)

    # Throw out unknown markers
    markers = [marker for marker in found_markers if marker.id in displays]
    if not markers:
        return 0
    if len(markers) < len(found_markers):
        # TODO: get this info to node.js
        #logging.warn("Found unknown markers in jumbotron %s", jumbotron.name)
        pass

    # The method used here has problems when combining very small
    # displays with very large displays. The small displays will seem
    # to be very far away, so any vertical or horizontal separation
    # between them and the larger display will be amplified. An
    # alternative might be to use the screen coordinates of each
    # marker. Needs more thought if this becomes an issue.

    # Get camera xform
    cam_xform = _get_camera_xform(image)

    # Align markers as best we can 
    _align_markers(markers)

    # Find best fitting plane
    plane_xform = _find_best_change_basis_xform(markers)

    # Project each marker to x-y plane and stretch to display size
    coords = []
    for marker in markers:
        idx = marker.id

        # Rotate from the best-fitting plane to the x-y plane.
        rot_vertices = [plane_xform.transform(vertex)
                        for vertex in marker.world_vertices]

        # Then rotate about the marker's center to the x-y plane
        center = plane_xform.transform(marker.world_center)
        normal = plane_xform.transform_vector(marker.world_normal).normalize()
        up     = plane_xform.transform_vector(marker.world_up).normalize()
        xform = _get_change_basis_xform(center, up, normal)
        xy_vertices = [xform.transform(vertex) for vertex in rot_vertices]

        # Use the camera transform to project to the screen
        screen_vertices = [cam_xform.transform(vertex)
                           for vertex in xy_vertices]

        # Finally, stretch to the display corners
        coord = _find_display_coords(marker, screen_vertices, displays[idx])
        coords.append(coord)

        if debug:
            _debug(Display=marker.id,
                   world=marker.world_vertices, plane=rot_vertices,
                   xyplane=xy_vertices, cam=screen_vertices, bbox=coord)
        if debug_image:
            _debug_image(draw, coords[idx], xform, plane_xform, cam_xform)

    # Find the bounding box of all the markers
    allminv = Vec3.min(*(coord[0] for coord in coords))
    allmaxv = Vec3.max(*(coord[1] for coord in coords))
    allsize = allmaxv - allminv
    if debug_image:
        _draw_rectangle(draw, allminv, allmaxv, "white", width=1)
    jumbotron.aspectRatio = allsize.x / allsize.y

    # Normalize viewports and set in displays
    for marker, coord in zip(markers, coords):
        idx = marker.id
        minv = (coord[0] - allminv)
        maxv = (coord[1] - allminv)
        size = maxv - minv

        displays[idx].viewport = dict(x=minv.x / allsize.x,
                                      y=minv.y / allsize.y,
                                      width=size.x / allsize.x,
                                      height=size.y / allsize.y,
                                      rotation=marker.world_direction)

    if debug:
        logging.debug("Final displays:")
        for marker, coord in zip(markers, coords):
            logging.debug("display {0} {1}".format(marker.id, coord))

    if debug_image:
        image.save("calibrate_out.jpg")

    return len(markers)