def compute_features_collection(snaps, pic_dir, cache=None):
    """Compute the features for every element in the paths"""
    if cache is None:
        cache = {}

    snaps_with_features = []
    features_list = []
    for snap in snaps:
        snap_id = snap.filename
        features = cache.get(snap_id)
        if features is not None:
            snaps_with_features.append(snap)
            features_list.append(features)
            continue

        # Compute the features
        if not snap.get('warped', False):
            logger.warning('Cannot compute feature for unwarped %s',
                           snap_id)
            continue

        warped_file_path = os.path.join(pic_dir, snap.warped_filename)
        new_features = compute_features(warped_file_path)
        cache[snap_id] = new_features
        snaps_with_features.append(snap)
        features_list.append(new_features)

    return snaps_with_features, np.array(features_list)
Esempio n. 2
0
def snapshot(request):
    """Index page."""
    filename = request.matchdict['file']
    settings = dict(request.registry.settings)
    pic_dir = settings['thumbs.document_root']
    file_path = os.path.join(pic_dir, filename)

    try:
        height, width = get_img_size(file_path)
    except IOError:
        logger.error("Failed to open file with path: %s", file_path,
                     exc_info=True)
        raise HTTPNotFound("Could not load image for snap %s" % filename)

    # security loop
    elmts = filename.split(os.sep)
    for unsecure in ('.', '..'):
        if unsecure in elmts:
            return HTTPNotFound()

    file_uuid = os.path.splitext(filename)[0]

    if request.method == 'POST':
        base = _toint(request.POST['bottom_y']), _toint(request.POST['bottom_x'])
        top = _toint(request.POST['top_y']), _toint(request.POST['top_x'])
        warped_img_path, new_base, new_top = warp_img(file_path, base, top)
        # There should be only one
        snap_idx, snap_type = 'snaps', 'snaptype'
        snap = request.db.get(snap_idx, snap_type, file_uuid)
        warped_filename = os.path.basename(warped_img_path)
        if snap is not None:
            snap['warped_filename'] = warped_filename
            snap['warped'] = True
            snap['base_x'] = base[0]
            snap['base_y'] = base[1]
            snap['top_x'] = top[0]
            snap['top_y'] = top[1]
            logger.debug("Warping snap %s", file_uuid)
            request.db.index(snap, snap_idx, snap_type, file_uuid)
            request.db.refresh()
            # Precompute the cached features incrementally
            compute_features_collection([snap], pic_dir, cache=FEATURES_CACHE)
        else:
            logger.warning("Could not find snap for %s", file_uuid)
        return HTTPFound(location='/warped/%s' % warped_filename)

    data = {'snapshot': filename,
            'width': width,
            'height': height}

    return _basic(request, data)
Esempio n. 3
0
def warped(request):
    """Index page."""
    filename = request.matchdict['file']
    settings = dict(request.registry.settings)
    pic_dir = settings['thumbs.document_root']
    orig_img = os.path.join(pic_dir, filename)
    res = get_img_size(orig_img)

    warped_stemname = os.path.splitext(filename)[0]
    snap_idx, snap_type = 'snaps', 'snaptype'

    if warped_stemname.endswith('_warped'):
        file_uuid = warped_stemname[:-len('_warped')]
    else:
        logger.warning(
            'Warped stemname is expected to end with "_warped", got: %s',
            warped_stemname
        )
        file_uuid = warped_stemname

    try:
        current_snap = request.db.get(snap_idx, snap_type, file_uuid)
    except NotFoundException:
        raise HTTPNotFound("Could not find snapshot for %s" % file_uuid)

    next_snap = None
    unwarped_count = ""

    if not current_snap.plant:
        query = FieldQuery(FieldParameter('warped', True))
        candidates = request.db.search(query, size=200, indices=['snaps'],
                                       sort='timestamp:desc')

        # TODO: filter out non-plant snaps in the query directly
        candidates = [c for c in candidates if c.plant != None]

        logger.debug('Computing suggestion for %s', current_snap.filename)
        best_snaps, scores = suggest_snaps(current_snap, candidates, pic_dir,
                                           FEATURES_CACHE)
        suggestions = OrderedDict()
        for i, (snap, score) in enumerate(zip(best_snaps, scores)):
            name = snap.plant
            logger.debug("#%02d: %s with score = %f - %s",
                         i, name, score, snap.filename)
            suggestion = suggestions.get(name)
            if suggestion is None:
                query = FieldQuery(FieldParameter('name', name))
                plants = list(request.db.search(query, indices=['plants']))
                if not plants:
                    raise HTTPNotFound("No plant registered under %s"
                                       % name)
                plant = plants[0]
                suggestions[name] = (plant, [snap])
            else:
                suggestion[1].append(snap)
    else:
        suggestions = None

        # Suggest to warp the next unwarped snapshot from the same plant
        query = FieldQuery(fieldparameters=(
            FieldParameter('warped', None),
            FieldParameter('filename', '-' + current_snap.filename),
            FieldParameter('plant', current_snap.plant)))
        max_count = 100
        next_snaps = request.db.search({
            "bool" : {
                "must" : [
                    {'field': {'filename': '-' + current_snap.filename}},
                    {'field': {'plant': current_snap.plant}},
                ],
                "must_not" : [
                    {'field': {'warped': True}},
                ],
            },
        }, size=max_count, indices=['snaps'], sort='timestamp:desc')
        logger.debug(len(next_snaps))
        if len(next_snaps) > 0:
            next_snap = next_snaps[0]

        unwarped_count = "%d" % len(next_snaps)
        if len(next_snaps) == max_count:
            unwarped_count += "+"

    if len(res) == 2:
        height, width = res
    else:
        height, width = 500, 500

    # security loop
    elmts = filename.split(os.sep)
    for unsecure in ('.', '..'):
        if unsecure in elmts:
            return HTTPNotFound()

    data = {'snapshot': filename,
            'original': get_original_path(filename),
            'width': width,
            'height': height,
            'snap': current_snap,
            'uuid': file_uuid,
            'suggestions': suggestions,
            'next_snap': next_snap,
            'unwarped_count': unwarped_count}

    return _basic(request, data)
def warp_img(raw_img_path, base, top, warped_img_size=WARPED_IMG_SIZE):
    """Rotate and rescale to normalize the position of base and top points

    Return the file system path of the transformed image and the transformed
    base and top positions in the new image.

    """
    # Load the data
    original = imread(raw_img_path)
    base = np.array(base)
    top = np.array(top)

    # Check the expected size of the resulting file
    warped_img_size = np.array(warped_img_size)
    warped_h, warped_w = warped_img_size

    bigger = np.max(original.shape[:2]) * 1.5
    embedded = np.zeros(shape=(bigger, bigger, original.shape[2]),
                        dtype=original.dtype)

    original_center = (base + top) / 2
    embedded_center = np.array(embedded.shape[:2]) / 2
    offset = embedded_center - original_center

    x_slice = slice(offset[0], offset[0] + original.shape[0])
    y_slice = slice(offset[1], offset[1] + original.shape[1])

    try:
        embedded[x_slice, y_slice, :] = original
    except ValueError:
        logger.warning("Invalid base and top positions on image %s: %r, %r",
                       raw_img_path, base, top)
        return raw_img_path, tuple(base), tuple(top)

    # Check the original base and top coordinate positions
    embedded_base = base + offset
    embedded_top = top + offset

    warped_base = ((0.9, 0.5) * warped_img_size).astype(np.int)
    warped_top = ((0.05, 0.5) * warped_img_size).astype(np.int)
    logger.debug("About to warp from {base, top = %r, %r} to "
                 "{warped_base, warped_top = %r, %r}",
                 base, top, warped_base, warped_top)

    orig_vector = embedded_top - embedded_base
    orig_norm = np.linalg.norm(orig_vector)

    warped_vector = warped_top - warped_base
    warped_norm = np.linalg.norm(warped_vector)

    if orig_norm == 0:
        logger.warning("Cannot warp image using a null original vector")
        # Cannot warp with this data, return null operation insted
        return raw_img_path, tuple(base), tuple(top)

    scale = warped_norm / orig_norm

    rotation = (np.arctan2(warped_vector[0], warped_vector[1])
                - np.arctan2(orig_vector[0], orig_vector[1]))

    rotation_degrees = rotation * -180. / np.pi

    pil_rotated = Image.fromarray(embedded).rotate(rotation_degrees)

    scaled_size = (int(embedded.shape[0] * scale),
                   int(embedded.shape[1] * scale))
    logger.debug("Warping from %r to %r using rot=%0.2f scale=%0.2f",
                 orig_vector, warped_vector, rotation_degrees, scale)

    pil_scaled = pil_rotated.resize(scaled_size, Image.ANTIALIAS)

    scaled = np.array(pil_scaled)
    scaled_center = np.array(scaled.shape[:2]) / 2

    x_slice = slice(scaled_center[0] - warped_h / 2,
                    scaled_center[0] + warped_h / 2)
    y_slice = slice(scaled_center[1] - warped_w / 2,
                    scaled_center[1] + warped_w / 2)
    warped = scaled[x_slice, y_slice, :]

    # Save the result back on the harddrive
    warped_path = get_warped_img_path(raw_img_path)
    imsave(warped_path, warped)
    return warped_path, tuple(warped_base), tuple(warped_top)