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
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()
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
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')
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