예제 #1
0
def upload_plant_snaps(request):
    plant_name = request.POST['name']
    uploaded = 0
    for name, value in request.POST.items():
        if name == 'name':
            continue
        if value == u'':
            continue
        logger.debug('Saving snap %r for plant %s', value, plant_name)
        file_uuid, ext = _save_pic(value, request)
        doc = {
           'user': request.user.id,
           'timestamp': datetime.datetime.utcnow(),
           'filename': file_uuid + ext,
           'plant': plant_name,
        }
        res = request.db.index(doc, 'snaps', 'snaptype', file_uuid)
        if not res['ok']:
            logger.error("Error while saving index")
            logger.error(res)
            raise HTTPServerError()
        uploaded += 1

    request.db.refresh()
    request.session.flash("Uploaded %d snaps" % uploaded)
    return HTTPFound(location='/plant/%s' % plant_name)
예제 #2
0
def compute_features(file_path):
    """Compute a vector of histogram-style features"""
    logger.debug("Extracting features for %s", file_path)
    image = color.rgb2gray(imread(file_path))
    fd = hog(image, orientations=8, pixels_per_cell=(16, 16),
             cells_per_block=(1, 1))
    return fd
예제 #3
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)
예제 #4
0
def upload_plant(request):
    index, type_, root = 'plants', 'planttype', 'plant'

    if request.user is None:
        raise Forbidden()

    if request.method == 'POST':
        pic = request.POST.get('picture')
        name = request.POST.get('name', '')
        if not name:
            name = str(uuid4())

        if pic not in (None, ''):
            name, ext = _save_pic(pic, request, name)
            filename = name + ext
        else:
            filename = None

        # TODO: check if plant exist first
        # Add to Elastic Search
        doc = {
            'user': request.user.id,
            'timestamp': datetime.datetime.utcnow(),
            'filename': filename,
            'gravatar': gravatar_image_url(request.user.email),
            'geo_longitude': request.POST.get('longitude'),
            'geo_latitude': request.POST.get('latitude'),
            'geo_accuracy': request.POST.get('accuracy'),
            'name': name,
        }
        logger.debug("Indexing plant %s: %r", name, doc)
        res = request.db.index(doc, index, type_, name)
        if not res['ok']:
            logger.error("Error while creating plant %s", name)
            logger.error(res)
            raise HTTPServerError()
        request.db.refresh()
        request.session.flash('Plant registered')
        return HTTPFound(location='/%s/%s' % (root, name))

    return _basic(request)
예제 #5
0
def send_report(rcpt, jobid=None, **options):
    subject = 'Your Marteau report'
    root = options.get('report.root', DEFAULT_ROOT)
    sender = options.get('report.sender', DEFAULT_SENDER)
    smtp_host = options.get('smtp.host', DEFAULT_SMTP_HOST)
    smtp_port = int(options.get('smtp.port', DEFAULT_SMTP_PORT))
    smtp_user = options.get('smtp.user')
    smtp_password = options.get('smtp.password')

    # preparing the message
    if jobid is None:
        url = root
    else:
        url = '%s/test/%s' % (root, jobid)

    body = TMPL % url

    msg = MIMEText(body.encode('utf-8'), 'plain', 'utf8')

    def _normalize_realname(field):
        address = AddressList(field).addresslist
        if len(address) == 1:
            realname, email = address[0]
            if realname != '':
                return '%s <%s>' % (str(Header(realname, 'utf-8')), str(email))
        return field

    msg['From'] = _normalize_realname(sender)
    msg['To'] = _normalize_realname(rcpt)
    msg['Subject'] = Header(subject, 'utf-8')

    logger.debug('Connecting to %s:%d' % (smtp_host, smtp_port))
    try:
        server = smtplib.SMTP(smtp_host, smtp_port, timeout=5)
    except (smtplib.SMTPConnectError, socket.error), e:
        return False, str(e)
예제 #6
0
                return '%s <%s>' % (str(Header(realname, 'utf-8')), str(email))
        return field

    msg['From'] = _normalize_realname(sender)
    msg['To'] = _normalize_realname(rcpt)
    msg['Subject'] = Header(subject, 'utf-8')

    logger.debug('Connecting to %s:%d' % (smtp_host, smtp_port))
    try:
        server = smtplib.SMTP(smtp_host, smtp_port, timeout=5)
    except (smtplib.SMTPConnectError, socket.error), e:
        return False, str(e)

    # auth
    if smtp_user is not None and smtp_password is not None:
        logger.debug('Login with %r' % smtp_user)
        try:
            server.login(smtp_user, smtp_password)
        except (smtplib.SMTPHeloError,
                smtplib.SMTPAuthenticationError,
                smtplib.SMTPException), e:
            return False, str(e)

    # the actual sending
    logger.debug('Sending the mail')
    try:
        server.sendmail(sender, [rcpt], msg.as_string())
    finally:
        server.quit()

    return True, None
예제 #7
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)
예제 #8
0
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)