def write_info(self): """ This creates the multiscales metadata for zarr pyramids """ # https://forum.image.sc/t/multiscale-arrays-v0-1/37930 multiscales = [{ "version": "0.1", "name": self.base_path.name, "datasets": [], "metadata": { "method": "mean" } }] pad = len(self.scale_info(-1)['key']) max_scale = int(self.scale_info(-1)['key']) for S in reversed(range(10,len(self.info['scales']))): scale_info = self.scale_info(S) key = str(max_scale - int(scale_info['key'])) multiscales[0]["datasets"].append({"path": key}) self.root.attrs["multiscales"] = multiscales with bfio.BioReader(self.image_path,max_workers=1) as bfio_reader: metadata = OMEXML(str(bfio_reader.metadata)) metadata.image(0).Pixels.SizeC = self.max_output_depth metadata.image(0).Pixels.channel_count = self.max_output_depth for c in range(self.max_output_depth): metadata.image().Pixels.Channel(c).Name = f'Channel {c}' with open(self.base_path.joinpath("METADATA.ome.xml"),'x') as fw: fw.write(str(metadata).replace("<ome:","<").replace("</ome:","</"))
def label_thread(input_path, output_path, connectivity): with ProcessManager.thread() as active_threads: with bfio.BioReader(input_path, max_workers=active_threads.count) as br: with bfio.BioWriter(output_path, max_workers=active_threads.count, metadata=br.metadata) as bw: # Load an image and convert to binary image = (br[..., 0, 0] > 0).squeeze() if connectivity > image.ndim: ProcessManager.log( "{}: Connectivity is not less than or equal to the number of image dimensions, skipping this image. connectivity={}, ndim={}" .format(input_path.name, connectivity, image.ndim)) return # Run the labeling algorithm labels = ftl.label_nd(image.squeeze(), connectivity) # Save the image bw.dtype = labels.dtype bw[:] = labels
def build_pyramid(input_image : str, output_image : str, imagetype : str, mesh : bool): """ This function builds the pyramids for Volume Generation and Meshes (if specified) Args: input_image : Where the input directory is located output_image : Where the output directory is located imagetype : Specifying whether we are averaging or taking the mode of the images when blurring the images for the pyramids mesh : Whether or not meshes are generated with segmented volumes Returns: None, generates pyramids or volumes of input data Raises: ValueError: If imagetype is not properly specified """ try: with bfio.BioReader(input_image) as bf: bfshape = (bf.X, bf.Y, bf.Z, bf.C, bf.T) datatype = np.dtype(bf.dtype) logger.info("Image Shape (XYZCT) {}".format(bfshape)) logger.info("Image Datatype {}".format(datatype)) num_scales = np.floor(np.log2(max(bfshape[:3]))).astype('int')+1 highest_res_directory = os.path.join(output_image, f"{num_scales}") if not os.path.exists(highest_res_directory): os.makedirs(highest_res_directory) # info file specifications resolution = get_resolution(phys_y=bf.physical_size_y, phys_x=bf.physical_size_x, phys_z=bf.physical_size_z) if imagetype == "segmentation": if mesh == False: logger.info("\n Creating info file for segmentations ...") file_info = nginfo.info_segmentation(directory=output_image, dtype=datatype, chunk_size = chunk_size, size=(bf.X, bf.Y, bf.Z), resolution=resolution) else: # if generating meshes # Creating a temporary files for the polygon meshes -- will later be converted to Draco with tempfile.TemporaryDirectory() as temp_dir: # keep track of labelled segments all_identities = [] cache_tile = bf._TILE_SIZE logger.info("\n Starting to Cache Section Sizes of {} for Meshes".format(cache_tile)) # cache tiles of 1024 for x1_cache, x2_cache, \ y1_cache, y2_cache, \ z1_cache, z2_cache, \ c1_cache, c2_cache, \ t1_cache, t2_cache, bf.cache in iterate_cache_tiles(bf_image = bf): cached_shape = bf.cache.shape bf.cache = np.reshape(bf.cache, cached_shape[:3]) for x_dim, y_dim, z_dim, volume in iterate_chunk_tiles(cached_image = bf.cache, x_dimensions = (x1_cache, x2_cache), y_dimensions = (y1_cache, y2_cache), z_dimensions = (z1_cache, z2_cache), chunk_tile_size = mesh_chunk_size): # iterate through mesh chunks in cached tile ids = np.unique(volume[volume>0]) len_ids = len(ids) logger.debug("({0:0>4}, {0:0>4}), ".format(x_dim[0], x_dim[1]) + \ "({0:0>4}, {0:0>4}), ".format(y_dim[0], y_dim[1]) + \ "({0:0>4}, {0:0>4}) ".format(z_dim[0], z_dim[1]) + \ "has {0:0>2} IDS".format(len_ids)) all_identities = np.unique(np.append(all_identities, ids)) if len_ids > 0: with ThreadPoolExecutor(max_workers=max([os.cpu_count()-1,2])) as executor: executor.submit(create_plyfiles(subvolume = volume, ids=ids, temp_dir=temp_dir, start_y=y_dim[0], start_x=x_dim[0], start_z=z_dim[0])) # concatenate and decompose the meshes in the temporary file for all segments logger.info("\n Generate Progressive Meshes for segments ...") all_identities = np.unique(all_identities).astype('int') with ThreadPoolExecutor(max_workers=max([os.cpu_count()-1,2])) as executor: executor.map(concatenate_and_generate_meshes, all_identities, repeat(temp_dir), repeat(output_image), repeat(bit_depth), repeat(mesh_chunk_size)) # Once you have all the labelled segments, then create segment_properties file logger.info("\n Creating info file for segmentations and meshes ...") file_info = nginfo.info_mesh(directory=output_image, chunk_size=chunk_size, size=(bf.X, bf.Y, bf.Z), dtype=np.dtype(bf.dtype).name, ids=all_identities, resolution=resolution, segmentation_subdirectory="segment_properties", bit_depth=bit_depth, order="XYZ") if imagetype == "image": file_info = nginfo.info_image(directory=output_image, dtype=datatype, chunk_size = chunk_size, size=(bf.X, bf.Y, bf.Z), resolution=resolution) logger.info(f"\n Creating chunked volumes of {chunk_size} based on the info file ...") get_highest_resolution_volumes(bf_image = bf, resolution_directory = highest_res_directory) logger.info("\n Getting the Rest of the Pyramid ...") for higher_scale in reversed(range(0, num_scales)): # bfshape is XYZ, look at line 357 inputshape = np.ceil(np.array(bfshape[:3])/(2**(num_scales-higher_scale-1))).astype('int') scale_directory = os.path.join(output_image, str(higher_scale+1)) #images are read from this directory if not os.path.exists(scale_directory): os.makedirs(scale_directory) assert os.path.exists(scale_directory), f"Key Directory {scale_directory} does not exist" if imagetype == "image": ngvol.get_rest_of_the_pyramid(directory=scale_directory, input_shape=inputshape, chunk_size=chunk_size, datatype=datatype, blurring_method='average') else: ngvol.get_rest_of_the_pyramid(directory=scale_directory, input_shape=inputshape, chunk_size=chunk_size, datatype=datatype, blurring_method='mode') logger.info(f"Saved Encoded Volumes for Scale {higher_scale} from Key Directory {os.path.basename(scale_directory)}") logger.info("\n Info basesd on Info File ...") logger.info("Data Type: {}".format(file_info['data_type'])) logger.info("Number of Channels: {}".format(file_info['num_channels'])) logger.info("Number of Scales: {}".format(len(file_info['scales']))) logger.info("Image Type: {}".format(file_info['type'])) except Exception as e: raise ValueError(f"Something Went Wrong!: {traceback.print_exc()}")
def bfio_metadata_to_slide_info(image_path,outPath,stackheight,imagetype,min_scale=0): """ Generate a Neuroglancer info file from Bioformats metadata Neuroglancer requires an info file in the root of the pyramid directory. All information necessary for this info file is contained in Bioformats metadata, so this function takes the metadata and generates the info file. Inputs: bfio_reader - A BioReader object outPath - Path to directory where pyramid will be generated Outputs: info - A dictionary containing the information in the info file """ with bfio.BioReader(image_path,max_workers=1) as bfio_reader: # Get metadata info from the bfio reader sizes = [bfio_reader.X,bfio_reader.Y,stackheight] phys_x = bfio_reader.ps_x if None in phys_x: phys_x = (1000,'nm') phys_y = bfio_reader.ps_y if None in phys_y: phys_y = (1000,'nm') phys_z = bfio_reader.ps_z if None in phys_z: phys_z = ((phys_x[0] + phys_y[0]) / 2,phys_x[1]) resolution = [phys_x[0] * UNITS[phys_x[1]]] resolution.append(phys_y[0] * UNITS[phys_y[1]]) resolution.append(phys_z[0] * UNITS[phys_z[1]]) # Just used as a placeholder dtype = str(np.dtype(bfio_reader.dtype)) num_scales = int(np.ceil(np.log2(max(sizes)))) # create a scales template, use the full resolution8 scales = { "chunk_sizes":[[CHUNK_SIZE,CHUNK_SIZE,1]], "encoding":"raw", "key": str(num_scales), "resolution":resolution, "size":sizes, "voxel_offset":[0,0,0] } # initialize the json dictionary info = { "data_type": dtype, "num_channels": 1, "scales": [scales], "type": imagetype, } if imagetype == "segmentation": info["segment_properties"] = "infodir" for i in reversed(range(min_scale,num_scales)): previous_scale = info['scales'][-1] current_scale = copy.deepcopy(previous_scale) current_scale['key'] = str(i) current_scale['size'] = [int(np.ceil(previous_scale['size'][0]/2)),int(np.ceil(previous_scale['size'][1]/2)),stackheight] current_scale['resolution'] = [2*previous_scale['resolution'][0],2*previous_scale['resolution'][1],previous_scale['resolution'][2]] info['scales'].append(current_scale) return info
def _get_higher_res(S: int, slide_writer: PyramidWriter, X: typing.Tuple[int,int] = None, Y: typing.Tuple[int,int] = None, Z: typing.Tuple[int,int] = (0,1)): """ Recursive function for pyramid building This is a recursive function that builds an image pyramid by indicating an original region of an image at a given scale. This function then builds a pyramid up from the highest resolution components of the pyramid (the original images) to the given position resolution. As an example, imagine the following possible pyramid: Scale S=0 1234 / \ Scale S=1 12 34 / \ / \ Scale S=2 1 2 3 4 At scale 2 (the highest resolution) there are 4 original images. At scale 1, images are averaged and concatenated into one image (i.e. image 12). Calling this function using S=0 will attempt to generate 1234 by calling this function again to get image 12, which will then call this function again to get image 1 and then image 2. Note that this function actually builds images in quadrants (top left and right, bottom left and right) rather than two sections as displayed above. Due to the nature of how this function works, it is possible to build a pyramid in parallel, since building the subpyramid under image 12 can be run independently of the building of subpyramid under 34. Args: S: Top level scale from which the pyramid will be built file_path: Path to image slide_writer: object used to encode and write pyramid tiles X: Range of X values [min,max] to get at the indicated scale Y: Range of Y values [min,max] to get at the indicated scale Returns: image: The image corresponding to the X,Y values at scale S """ # Get the scale info scale_info = slide_writer.scale_info(S) if X == None: X = [0,scale_info['size'][0]] if Y == None: Y = [0,scale_info['size'][1]] # Modify upper bound to stay within resolution dimensions if X[1] > scale_info['size'][0]: X[1] = scale_info['size'][0] if Y[1] > scale_info['size'][1]: Y[1] = scale_info['size'][1] if str(S)==slide_writer.scale_info(-1)['key']: with ProcessManager.thread(): with bfio.BioReader(slide_writer.image_path,max_workers=1) as br: image = br[Y[0]:Y[1],X[0]:X[1],Z[0]:Z[1],...].squeeze() # Write the chunk slide_writer.store_chunk(image,str(S),(X[0],X[1],Y[0],Y[1])) return image else: # Initialize the output image = np.zeros((Y[1]-Y[0],X[1]-X[0]),dtype=slide_writer.dtype) # Set the subgrid dimensions subgrid_dims = [[2*X[0],2*X[1]],[2*Y[0],2*Y[1]]] for dim in subgrid_dims: while dim[1]-dim[0] > CHUNK_SIZE: dim.insert(1,dim[0] + ((dim[1] - dim[0]-1)//CHUNK_SIZE) * CHUNK_SIZE) def load_and_scale(*args,**kwargs): sub_image = _get_higher_res(**kwargs) with ProcessManager.thread(): image = args[0] x_ind = args[1] y_ind = args[2] image[y_ind[0]:y_ind[1],x_ind[0]:x_ind[1]] = kwargs['slide_writer'].scale(sub_image) with ThreadPoolExecutor(1) as executor: for y in range(0,len(subgrid_dims[1])-1): y_ind = [subgrid_dims[1][y] - subgrid_dims[1][0],subgrid_dims[1][y+1] - subgrid_dims[1][0]] y_ind = [np.ceil(yi/2).astype('int') for yi in y_ind] for x in range(0,len(subgrid_dims[0])-1): x_ind = [subgrid_dims[0][x] - subgrid_dims[0][0],subgrid_dims[0][x+1] - subgrid_dims[0][0]] x_ind = [np.ceil(xi/2).astype('int') for xi in x_ind] executor.submit(load_and_scale, image,x_ind,y_ind, # args X=subgrid_dims[0][x:x+2], # kwargs Y=subgrid_dims[1][y:y+2], Z=Z, S=S+1, slide_writer=slide_writer) # Write the chunk slide_writer.store_chunk(image,str(S),(X[0],X[1],Y[0],Y[1])) return image
def main(input_dir: pathlib.Path, pyramid_type: str, image_type: str, file_pattern: str, output_dir: pathlib.Path): # Set ProcessManager config and initialize ProcessManager.num_processes(multiprocessing.cpu_count()) ProcessManager.num_threads(2 * ProcessManager.num_processes()) ProcessManager.threads_per_request(1) ProcessManager.init_processes('pyr') logger.info('max concurrent processes = %s', ProcessManager.num_processes()) # Parse the input file directory fp = filepattern.FilePattern(input_dir, file_pattern) group_by = '' if 'z' in fp.variables and pyramid_type == 'Neuroglancer': group_by += 'z' logger.info( 'Stacking images by z-dimension for Neuroglancer precomputed format.' ) elif 'c' in fp.variables and pyramid_type == 'Zarr': group_by += 'c' logger.info('Stacking channels by c-dimension for Zarr format') elif 't' in fp.variables and pyramid_type == 'DeepZoom': group_by += 't' logger.info('Creating time slices by t-dimension for DeepZoom format.') else: logger.info( f'Creating one pyramid for each image in {pyramid_type} format.') depth = 0 depth_max = 0 image_dir = '' processes = [] for files in fp(group_by=group_by): # Create the output name for Neuroglancer format if pyramid_type in ['Neuroglancer', 'Zarr']: try: image_dir = fp.output_name([file for file in files]) except: pass if image_dir in ['', '.*']: image_dir = files[0]['file'].name # Reset the depth depth = 0 depth_max = 0 pyramid_writer = None for file in files: with bfio.BioReader(file['file'], max_workers=1) as br: if pyramid_type == 'Zarr': d_z = br.c else: d_z = br.z depth_max += d_z for z in range(d_z): pyramid_args = { 'base_dir': output_dir.joinpath(image_dir), 'image_path': file['file'], 'image_depth': z, 'output_depth': depth, 'max_output_depth': depth_max, 'image_type': image_type } pw = PyramidWriter[pyramid_type](**pyramid_args) ProcessManager.submit_process(pw.write_slide) depth += 1 if pyramid_type == 'DeepZoom': pw.write_info() if pyramid_type in ['Neuroglancer', 'Zarr']: if image_type == 'segmentation': ProcessManager.join_processes() pw.write_info() ProcessManager.join_processes()
def main(): # Mute Tensorflow messages os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # Initialize the logger logging.basicConfig( format='%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') logger = logging.getLogger("segment") logger.setLevel(logging.INFO) # Parse the inputs parser = argparse.ArgumentParser() parser.add_argument('--batch', dest='batch', type=str, required=True) parser.add_argument('--outDir', dest='output_directory', type=str, required=True) args = parser.parse_args() # Input and output directory batch = args.batch.split(',') output_dir = args.output_directory # Start the javabridge with proper java logging log_config = Path(__file__).parent.joinpath("log4j.properties") jutil.start_vm(args=[ "-Dlog4j.configuration=file:{}".format(str(log_config.absolute())) ], class_path=bioformats.JARS) # Load Model Architecture and model weights model = unet() model.load_weights('unet.h5') # Iterate over the files to be processed for filename in batch: logger.info("Processing image: {}".format(filename)) # Use bfio to read the image bf = bfio.BioReader(filename) img = bf.read_image() # Extract the 2-D grayscale image. Bfio loads an image as a 5-D array. img = (img[:, :, 0, 0, 0]) # The network expects the pixel values to be in the range of (0,1). # Interpolate the pixel values to (0,1) img = np.interp(img, (img.min(), img.max()), (0, 1)) # The network expects a 3 channel image. img = np.dstack((img, img, img)) # Add reflective padding to make the image dimensions a multiple of 256 # pad_dimensions will help us extract the final result from the padded output. padded_img, pad_dimensions = padding(img) # Intitialize an emtpy array to store the output from the network final_img = np.zeros((padded_img.shape[0], padded_img.shape[1])) # Extract 256 x 256 tiles from the padded input image. for i in range(int(padded_img.shape[0] / 256)): for j in range(int(padded_img.shape[1] / 256)): # tile to be processed temp_img = padded_img[i * 256:(i + 1) * 256, j * 256:(j + 1) * 256] inp = np.expand_dims(temp_img, axis=0) #predict x = model.predict(inp) # Extract the output image out = x[0, :, :, 0] # Store the output tile final_img[i * 256:(i + 1) * 256, j * 256:(j + 1) * 256] = out # get pad dimensions on all 4 sides of the image top_pad, bottom_pad, left_pad, right_pad = pad_dimensions # Extract the Desired output from the padded output out_image = final_img[top_pad:final_img.shape[0] - bottom_pad, left_pad:final_img.shape[1] - right_pad] # Form a binary image out_image = np.rint(out_image) * 255 out_image = out_image.astype(np.uint8) # Convert the output to a 5-D arrray to enable bfio to write the image. output_image_5channel = np.zeros( (out_image.shape[0], out_image.shape[1], 1, 1, 1), dtype='uint8') output_image_5channel[:, :, 0, 0, 0] = out_image # Export the output to the desired directory bw = bfio.BioWriter(str( Path(output_dir).joinpath(Path(filename).name).absolute()), metadata=bf.read_metadata()) bw.write_image(output_image_5channel) bw.close_image() # Stop the VM jutil.kill_vm()
def python_reader(scope="class"): return bfio.BioReader(image_path, backend='python')
def java_reader(): return bfio.BioReader(image_path, backend='java')