Example #1
0
def sv_to_mesh(server,
               uuid,
               instance,
               sv,
               smoothing_iterations=0,
               simplification_fraction=1.0,
               max_box_volume=DEFAULT_MAX_BOUNDING_BOX_VOL):
    """
    Download a mask for the given supervoxel and generate a mesh from it.
    If the mask bounding box would be large at scale 0, a smaller scale will be used.
    The returned mesh will always use scale-0 coordinates, though.
    """
    with Timer("Fetching supervoxel mask", logger):
        mask, scale, scaled_box = fetch_supervoxel_mask(
            server, uuid, instance, sv, max_box_volume)
        fullres_box = scaled_box * (2**scale)

    with Timer(f"Generating mesh from scale {scale}", logger):
        mesh = Mesh.from_binary_vol(mask, fullres_box)

    with Timer(f"Smoothing ({smoothing_iterations})", logger):
        mesh.laplacian_smooth(smoothing_iterations)

    # If we chose a scale other than 0, automatically reduce the
    # amount of decimation, since there will already be fewer vertices at lower resolution.
    simplification_fraction *= (2**scale)**2
    simplification_fraction = min(1.0, simplification_fraction)

    with Timer(f"Decimating ({simplification_fraction})", logger):
        mesh.simplify(simplification_fraction, in_memory=True)

    logger.info(
        f"Mesh has {len(mesh.vertices_zyx)} vertices and {len(mesh.faces)} faces"
    )
    return mesh
Example #2
0
def process_point(seg_src, seg_dst, point, radius, src_body, dst_body):
    """
    Generate a neighborhood segment around a particular point.
    Upload the voxels for the segment and the corresponding mesh.
    """
    r = radius
    src_box = np.asarray((point - r, point + r + 1))
    src_vol = fetch_labelmap_voxels(*seg_src, src_box)

    if src_body is None:
        src_body = src_vol[r, r, r]

    if dst_body is None:
        # Generate a neighborhood segment ID from the coordinate.
        # Divide by 4 to ensure the coordinates fit within 2^53.
        # (The segment ID will not retain the full resolution of
        # the coordinate, but that's usually OK for our purposes.)
        dst_body = encode_point_to_uint64(point // 4, 17)

    mask = (src_vol == src_body) & sphere_mask(r)

    dst_box = round_box(src_box, 64, 'out')
    dst_vol = fetch_labelmap_voxels(*seg_dst, dst_box)

    dst_view = dst_vol[b2s(*(src_box - dst_box[0]))]
    dst_view[mask] = dst_body

    post_labelmap_voxels(*seg_dst, dst_box[0], dst_vol, downres=True)

    # Mesh needs to be written in nm, hence 8x
    mesh = Mesh.from_binary_vol(mask, 8 * src_box, smoothing_rounds=2)
    mesh.simplify(0.05, in_memory=True)
    post_key(*seg_dst[:2], f'{seg_dst[2]}_meshes', f'{dst_body}.ngmesh',
             mesh.serialize(fmt='ngmesh'))

    centroid = src_box[0] + mask_centroid(mask, True)
    top_z = mask.sum(axis=(1, 2)).nonzero()[0][0]
    top_coords = np.transpose(mask[top_z].nonzero())
    top_point = src_box[0] + (top_z, *top_coords[len(top_coords) // 2])

    return point, centroid, top_point, src_body, dst_body, mask.sum()
Example #3
0
def _generate_and_store_mesh():
    try:
        dvid = request.args['dvid']
        body = request.args['body']
    except KeyError as ex:
        return Response(f"Missing required parameter: {ex.args[0]}", 400)

    segmentation = request.args.get('segmentation', 'segmentation')
    mesh_kv = request.args.get('mesh_kv', f'{segmentation}_meshes')

    uuid = request.args.get('uuid') or find_master(dvid)
    if not uuid:
        uuid = find_master(dvid)

    scale = request.args.get('scale')
    if scale is not None:
        scale = int(scale)

    smoothing = int(request.args.get('smoothing', 2))

    # Note: This is just the effective desired decimation assuming scale-1 data.
    # If we're forced to select a higher scale than scale-1, then we'll increase
    # this number to compensate.
    decimation = float(request.args.get('decimation', 0.1))

    user = request.args.get('u')
    user = user or request.args.get('user', "UNKNOWN")

    # TODO: The global cache of DVID sessions should store authentication info
    #       and use it as part of the key lookup, to avoid creating a new dvid
    #       session for every single cloud call!
    dvid_session = default_dvid_session('cloud-meshgen', user)
    auth = request.headers.get('Authorization')
    if auth:
        dvid_session = copy.deepcopy(dvid_session)
        dvid_session.headers['Authorization'] = auth

    with Timer(f"Body {body}: Fetching coarse sparsevol"):
        svc_ranges = fetch_sparsevol_coarse(dvid,
                                            uuid,
                                            segmentation,
                                            body,
                                            format='ranges',
                                            session=dvid_session)

    #svc_mask, _svc_box = fetch_sparsevol_coarse(dvid, uuid, segmentation, body, format='mask', session=dvid_session)
    #np.save(f'mask-{body}-svc.npy', svc_mask)

    box_s6 = rle_ranges_box(svc_ranges)
    box_s0 = box_s6 * (2**6)
    logger.info(f"Body {body}: Bounding box: {box_s0[:, ::-1].tolist()}")

    if scale is None:
        # Use scale 1 if possible or a higher scale
        # if necessary due to bounding-box RAM usage.
        scale = max(1, select_scale(box_s0))

    if scale > 1:
        # If we chose a low-res scale, then we
        # can reduce the decimation as needed.
        decimation = min(1.0, decimation * 4**(scale - 1))

    with Timer(f"Body {body}: Fetching scale-{scale} sparsevol"):
        mask, mask_box = fetch_sparsevol(dvid,
                                         uuid,
                                         segmentation,
                                         body,
                                         scale=scale,
                                         format='mask',
                                         session=dvid_session)
        # np.save(f'mask-{body}-s{scale}.npy', mask)

        # Pad with a thin halo of zeros to avoid holes in the mesh at the box boundary
        mask = np.pad(mask, 1)
        mask_box += [(-1, -1, -1), (1, 1, 1)]

    with Timer(f"Body {body}: Computing mesh"):
        # The 'ilastik' marching cubes implementation supports smoothing during mesh construction.
        mesh = Mesh.from_binary_vol(mask,
                                    mask_box * VOXEL_NM * (2**scale),
                                    smoothing_rounds=smoothing)

        logger.info(f"Body {body}: Decimating mesh at fraction {decimation}")
        mesh.simplify(decimation)

        logger.info(f"Body {body}: Preparing ngmesh")
        mesh_bytes = mesh.serialize(fmt='ngmesh')

    if scale > 2:
        logger.info(f"Body {body}: Not storing to dvid (scale > 2)")
    else:
        with Timer(
                f"Body {body}: Storing {body}.ngmesh in DVID ({len(mesh_bytes)/MB:.1f} MB)"
        ):
            try:
                post_key(dvid,
                         uuid,
                         mesh_kv,
                         f"{body}.ngmesh",
                         mesh_bytes,
                         session=dvid_session)
            except HTTPError as ex:
                err = ex.response.content.decode('utf-8')
                if 'locked node' in err:
                    logger.info(
                        "Body {body}: Not storing to dvid (uuid {uuid[:4]} is locked)."
                    )
                else:
                    logger.warning("Mesh could not be cached to dvid:\n{err}")

    r = make_response(mesh_bytes)
    r.headers.set('Content-Type', 'application/octet-stream')
    return r
Example #4
0
        def compute_mesh_and_write(body):
            with Timer() as timer:
                # Fetch the sparsevol to determine the bounding-box size (in scale-0 voxels)
                try:
                    with mgr_client.access_context(server, True, 1, 0):
                        # sparsevol-coarse is at scale-6
                        coords_s6 = fetch_sparsevol_coarse(
                            server, uuid, instance, body, is_supervoxels)
                except:
                    return (body, 0, 0, 0, 0.0, timer.seconds,
                            'error-sparsevol-coarse')

                box_s6 = np.array(
                    [coords_s6.min(axis=0), 1 + coords_s6.max(axis=0)])
                box_s0 = (2**6) * box_s6
                shape_s0 = (box_s0[1] - box_s0[0])
                box_voxels_s0 = np.prod(shape_s0.astype(float))

                # Determine the scale we'll use.
                # Solve for 'scale' in the following relationship:
                #
                #   box_voxels_s0/((2^scale)^3) <= max_box_voxels
                #
                scale = log2(pow(box_voxels_s0 / max_box_voxels, 1 / 3))
                scale = max(ceil(scale), min_scale)

                if scale > max_scale:
                    raise RuntimeError(
                        f"Can't compute mesh for body {body}. Bounding box is {box_s0[:, ::-1].tolist()}, "
                        f"which is too large to fit in desired RAM, even at scale {max_scale}"
                    )

                try:
                    with mgr_client.access_context(server, True, 1, 0):
                        coords = fetch_sparsevol(server,
                                                 uuid,
                                                 instance,
                                                 body,
                                                 is_supervoxels,
                                                 scale,
                                                 dtype=np.int16)
                except:
                    return (body, 0, 0, 0, 0.0, timer.seconds,
                            'error-sparsevol')

                box = box_s0 // (2**scale)
                coords -= box[0]
                num_voxels = len(coords)

                shape = box[1] - box[0]
                vol = np.zeros(shape, np.uint8)
                vol[(*coords.transpose(), )] = 1
                del coords

                try:
                    mesh = Mesh.from_binary_vol(vol, box_s0)
                except:
                    return (body, scale, num_voxels, 0, 0.0, timer.seconds,
                            'error-construction')

                del vol
                try:
                    mesh.laplacian_smooth(smoothing_iterations)
                except:
                    return (body, scale, num_voxels, 0.0,
                            len(mesh.vertices_zyx), timer.seconds,
                            'error-smoothing')

                fraction = decimation_fraction
                if scale > min_scale:
                    # Since we're starting from a lower resolution than the user requested,
                    # Reduce the decimation we're applying accordingly.
                    # Since meshes are 2D surfaces, we approximate the difference in
                    # vertexes as the SQUARE of the difference in resolution.
                    fraction *= (2**(scale - min_scale))**2
                    fraction = min(fraction, 1.0)

                try:
                    mesh.simplify(fraction, in_memory=True)
                except:
                    return (body, scale, num_voxels, 0.0,
                            len(mesh.vertices_zyx), timer.seconds,
                            'error-decimation')

                output_path = f'{options["output-directory"]}/{body}.{options["format"]}'
                mesh.serialize(output_path)

                return (body, scale, num_voxels, fraction,
                        len(mesh.vertices_zyx), timer.seconds, 'success')
Example #5
0
 def create_brick_mesh(brick):
     mesh = Mesh.from_binary_vol(brick.volume, brick.physical_box)
     if rescale != 1.0:
         mesh.vertices_zyx *= rescale
     return mesh