Example #1
0
def serve_mosaic_image(time_series=None, pid=None, params='/'):
    """Generate a mosaic of ROIs from a sample bin.
    params include the following, with default values
    - series (mvco) - time series (FIXME: handle with resolver, clarify difference between namespace, time series, pid, lid)
    - size (1024x1024) - size of the mosaic image
    - page (1) - page. for typical image sizes the entire bin does not fit and so is split into pages.
    - scale - scaling factor for image dimensions """
    # parse params
    params = parse_params(params, size='1024x1024',page=1,scale=1.0)
    (w,h) = tuple(map(int,re.split('x',params[SIZE])))
    scale = float(params[SCALE])
    page = int(params[PAGE])
    # parse pid/lid
    hit = pid_resolver.resolve(pid=pid)
    # perform layout operation
    scaled_size = (int(w/scale), int(h/scale))
    layout = get_mosaic_layout(hit.bin_pid, scaled_size, page)
    # serve JSON on request
    if hit.extension == 'json':
        return jsonr(list(layout2json(layout, scale)))
    # resolve ROI file
    roi_path = resolve_roi(hit.bin_pid)
    # read all images needed for compositing and inject into Tiles
    with open(roi_path,'rb') as roi_file:
        for tile in layout:
            target = tile.image
            # FIXME use fast stitching
            tile.image = as_pil(get_fast_stitched_roi(hit.bin_pid, target[TARGET_NUMBER]))
    # produce and serve composite image
    mosaic_image = thumbnail(mosaic.composite(layout, scaled_size, mode='L', bgcolor=160), (w,h))
    (pil_format, mimetype) = image_types(hit)
    return image_response(mosaic_image, pil_format, mimetype)
Example #2
0
def im2bytes(im):
    """Convert a numpy uint8 array to a bytearray"""
    im = as_pil(im)
    buf = BytesIO()
    with tempfile.SpooledTemporaryFile() as imtemp:
        im.save(imtemp,'PNG')
        imtemp.seek(0)
        shutil.copyfileobj(imtemp, buf)
    return buf.getvalue()
Example #3
0
def stitch_raw(targets,images,box=None,background=0):
    # compute bounds relative to the camera field
    if box is None:
        box = stitched_box(targets)
    (x,y,w,h) = box
    # now we swap width and height to rotate the image 90 degrees
    s = Image.new('L',(h,w),background) # the stitched image with a missing region
    for (roi,image) in zip(targets,images):
        image = as_pil(image)
        rx = roi[LEFT] - x
        ry = roi[BOTTOM] - y
        rw = roi[WIDTH]
        rh = roi[HEIGHT]
        roi_box = (ry, rx, ry + rh, rx + rw)
        s.paste(image, roi_box) # paste in the right location
    return np.array(s)
Example #4
0
def image_response(image,format,mimetype):
    """Construct a Flask Response object for the given image, PIL format, and MIME type."""
    buf = StringIO()
    im = as_pil(image).save(buf,format)
    return Response(buf.getvalue(), mimetype=mimetype)
Example #5
0
def bright_mv(image,mask=None):
    mask = as_pil(mask)
    eh = image.histogram(mask)
    # toast extrema
    return bright_mv_hist(eh)
Example #6
0
def stitch(targets,images):
    # compute bounds relative to the camera field
    (x,y,w,h) = stitched_box(targets)
    # note that w and h are switched from here on out to rotate 90 degrees.
    # step 1: compute masks
    s = as_pil(stitch_raw(targets,images,(x,y,w,h))) # stitched ROI's with black gaps
    rois_mask = as_pil(mask(targets)) # a mask of where the ROI's are
    gaps_mask = ImageChops.invert(rois_mask) # its inverse is where the gaps are
    edges = edges_mask(targets,images) # edges are pixels along the ROI edges
    # step 2: estimate background from edges
    # compute the mean and variance of the edges
    (mean,variance) = bright_mv(s,edges)
    # now use that as an estimated background
    flat_bg = Image.new('L',(h,w),mean) # FIXME
    s.paste(mean,None,gaps_mask)
    # step 3: compute "probable background": low luminance delta from estimated bg
    bg = extract_background(s,flat_bg)
    # also mask out the gaps, which are not "probable background"
    bg.paste(255,None,gaps_mask)
    # step 3a: improve mean/variance estimate
    (mean,variance) = bright_mv(bg)
    std_dev = sqrt(variance)
    # step 4: sample probable background to compute RBF for illumination gradient
    # grid
    div = 6
    means = []
    nodes = []
    rad = avg([h,w]) / div
    rad_step = int(rad/2)+1
    for x in range(0,h+rad,rad):
        for y in range(0,w+rad,rad):
            for r in range(rad,max(h,w),int(rad/3)+1):
                box = (max(0,x-r),max(0,y-r),min(h-1,x+r),min(w-1,y+r))
                region = bg.crop(box)
                nabe = region.histogram()
                (m,v) = bright_mv_hist(nabe)
                if m > 0 and m < 255: # reject outliers
                    nodes.append((x,y))
                    means.append(m)
                    break
    # now construct radial basis functions for mean, based on the samples
    mean_rbf = interpolate.Rbf([x for x,y in nodes], [y for x,y in nodes], means, epsilon=rad)
    # step 5: fill gaps with mean based on RBF and variance from bright_mv(edges)
    mask_pix = gaps_mask.load()
    noise = Image.new('L',(h,w),mean)
    noise_pix = noise.load()
    np.random.seed(0)
    gaussian = np.random.normal(0, 1.0, size=(h,w)) # it's normal
    std_dev *= 0.66 # err on the side of smoother rather than noisier
    mask_x = []
    mask_y = []
    for x in xrange(h):
        for y in xrange(w):
            if mask_pix[x,y] == 255: # only for pixels in the mask
                mask_x.append(x)
                mask_y.append(y)
    rbf_fill = mean_rbf(np.array(mask_x), np.array(mask_y))
    for x,y,r in zip(mask_x, mask_y, rbf_fill):
        # fill is illumination gradient + noise
        noise_pix[x,y] = r + (gaussian[x,y] * std_dev)
    # step 6: final composite
    s.paste(noise,None,gaps_mask)
    return (np.array(s),rois_mask)