Exemple #1
0
 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:","</"))
Exemple #2
0
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
Exemple #3
0
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()}")
        
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
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()
Exemple #7
0
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()
Exemple #8
0
def python_reader(scope="class"):
    return bfio.BioReader(image_path, backend='python')
Exemple #9
0
def java_reader():
    return bfio.BioReader(image_path, backend='java')