def test_mosaic_image_two(data_dir, fname, expects): with open(data_dir / fname, 'rb') as fp: czi = CziFile(czi_filename=fp) sze = czi.read_mosaic_size() rgion = (sze[0], sze[1], int(sze[2] / 2), int(sze[3] / 2)) img = czi.read_mosaic(region=rgion, C=0, M=0) assert img.shape == expects
def test_mosaic_image(data_dir, fname, expects): with open(data_dir / fname, 'rb') as fp: czi = CziFile(czi_filename=fp) sze = czi.read_mosaic_size() assert sze[2] == 1756 assert sze[3] == 624 img = czi.read_mosaic(scale_factor=0.1, C=0) assert img.shape[0] == 1
def load_tile(coords, slide_path, data_mask, region_mask, width, downsample, fast): """Load a tile from the czi-file (parallizeable).""" # Load reader. reader = CziFile(slide_path) # Unpack coords. i, (x, y) = coords # Define final out_shape. out_shape = (int(width/downsample), int(width/downsample)) # Get data and region percentages. tile_mask = Polygon( [[x, y], [x+width, y], [x+width, y+width], [x, y+width], [x, y]]) intersection = tile_mask.intersection(data_mask) data_perc = intersection.area/tile_mask.area region_perc = tile_mask.intersection(region_mask).area/tile_mask.area if data_perc < 0.05 or region_perc < 1: # Return empty image. tile = np.ones(out_shape + (3,)) * 255 else: # Load tile. bbox = (x, y, width, width) if fast: tile = reader.read_mosaic(bbox, C=0, scale_factor=1/downsample) else: tile = reader.read_mosaic(bbox, C=0) if tile.shape[0] == 1: # Something is wrong with the tile... tile = np.ones(out_shape + (3,)) * 255 else: tile = np.moveaxis(tile, 0, 2) tile = cv2.resize(tile, out_shape, cv2.INTER_LANCZOS4) if data_perc < 1: mask = polygon_to_mask(intersection, x, y, width, downsample) mask = cv2.resize(mask, out_shape, cv2.INTER_LANCZOS4) tile[mask == 0] = 255 tile = tile.astype(np.uint8) return i, tile
filename = r"C:\Temp\input\DTScan_ID4.czi" md, addmd = imf.get_metadata(filename) czi = CziFile(filename) # Get the shape of the data, the coordinate pairs are (start index, size) dimensions = czi.dims_shape() print(dimensions) print(czi.dims) print(czi.size) print(czi.is_mosaic()) # True # Mosaic files ignore the S dimension and use an internal mIndex to reconstruct, the scale factor allows one to generate a manageable image mosaic_data = czi.read_mosaic(C=0, scale_factor=1) print('CZI Mosaic Data Shape : ', mosaic_data.shape) md = {} md['SizeS'] = 1 md['SizeT'] = 3 md['SizeZ'] = 5 md['SizeC'] = 2 md['SizeY'] = 100 md['SizeX'] = 200 dimorder = 'STCYX' dims_dict, dimindex_list, numvalid_dims = get_dimorder(dimorder) new = {k: v for k, v in dims_dict.items() if v != -1}
if minholesize > minsize: minsize = minholesize # read the czi mosaic image czi = CziFile(filename) # Get the shape of the data print('Dimensions : ', czi.dims) print('Size : ', czi.size) print('Shape : ', czi.dims_shape()) print('IsMoasic : ', czi.is_mosaic()) if czi.is_mosaic(): print('Mosaic Size : ', czi.read_mosaic_size()) # read the mosaic pixel data mosaic = czi.read_mosaic(C=0, scale_factor=1.0) print('Mosaic Shape :', mosaic.shape) image2d = np.squeeze(mosaic, axis=0) md['SizeX_readmosaic'] = image2d.shape[1] md['SizeY_readmosaic'] = image2d.shape[0] image_counter = 0 results = pd.DataFrame() # create the savename for the OME-TIFF savename = filename.split('.')[0] + '.ome.tiff' #savename = filename.split('.')[0] + '.tiff' # open the TiffWriter in order to save as Multi-Series OME-TIFF with tifffile.TiffWriter(savename, append=False) as tif:
def execute(filepath, separator=';', filter_method='none', filter_size=3, threshold_method='triangle', min_objectsize=1000, max_holesize=100, saveformat='ome.tiff'): """Main function that executed the workflow. :param filepath: file path of the CZI image :type filepath: tsr :param separator: sepeartor for the CSV table, defaults to ';' :type separator: str, optional :param filter_method: smoothing filer, defaults to 'none' :type filter_method: str, optional :param filter_size: kernel size or radius of filter element, defaults to 3 :type filter_size: int, optional :param threshold_method: threshold method, defaults to 'triangle' :type threshold_method: str, optional :param min_objectsize: minimum object size, defaults to 1000 :type min_objectsize: int, optional :param max_holesize: maximum object size, defaults to 100 :type max_holesize: int, optional :param saveformat: format to save the segmented image, defaults to 'ome.tiff' :type saveformat: str, optional :return: outputs :rtype: dict """ print('--------------------------------------------------') print('FilePath : ', filepath) print(os.getcwd()) print('File exists : ', os.path.exists(filepath)) print('--------------------------------------------------') # define name for figure to be saved filename = os.path.basename(filepath) # get the metadata from the czi file md, additional_mdczi = imf.get_metadata(filepath) # to make it more readable extravt values from metadata dictionary stageX = md['SceneStageCenterX'] stageY = md['SceneStageCenterY'] # define columns names for dataframe cols = ['S', 'T', 'Z', 'C', 'Number'] objects = pd.DataFrame(columns=cols) # optional dipslay of "some" results - empty list = no display show_image = [0] # scalefactor to read CZI sf = 1.0 # index for channel - currently only single channel images are supported ! chindex = 0 # define maximum object sizes max_objectsize = 1000000000 # define save format for mask adapt_dtype_mask = True dtype_mask = np.int8 # check if it makes sense if max_holesize > min_objectsize: min_objectsize = max_holesize # read the czi mosaic image czi = CziFile(filepath) # get the shape of the data using aicspylibczi print('Dimensions : ', czi.dims) print('Size : ', czi.size) print('Shape : ', czi.dims_shape()) print('IsMosaic : ', czi.is_mosaic()) # read the mosaic pixel data mosaic = czi.read_mosaic(C=0, scale_factor=sf) print('Mosaic Shape :', mosaic.shape) # get the mosaic as NumPy.Array - must fit im memory !!! image2d = np.squeeze(mosaic, axis=0) md['SizeX_readmosaic'] = image2d.shape[1] md['SizeY_readmosaic'] = image2d.shape[0] # create the savename for the OME-TIFF if saveformat == 'ome.tiff': savename_seg = filename.split('.')[0] + '.ome.tiff' if saveformat == 'tiff': savename_seg = filename.split('.')[0] + '.tiff' # initialize empty dataframe results = pd.DataFrame() # main loop over all T - Z - C slices for s in progressbar.progressbar(range(md['SizeS']), redirect_stdout=True): for t in range(md['SizeT']): for z in range(md['SizeZ']): values = {'S': s, 'T': t, 'Z': z, 'C': chindex, 'Number': 0} # preprocessing - filter the image if filter_method == 'none' or filter_method == 'None': image2d_filtered = image2d if filter_method == 'median': image2d_filtered = median(image2d, selem=disk(filter_size)) if filter_method == 'gauss': image2d_filtered = gaussian(image2d, sigma=filter_size, mode='reflect') # threshold image binary = sgt.autoThresholding(image2d_filtered, method=threshold_method) # Remove contiguous holes smaller than the specified size mask = morphology.remove_small_holes(binary, area_threshold=max_holesize, connectivity=1, in_place=True) # remove small objects mask = morphology.remove_small_objects(mask, min_size=min_objectsize, in_place=True) # clear the border mask = segmentation.clear_border(mask, bgval=0, in_place=True) # label the objects mask = measure.label(binary) # adapt pixel type of mask if adapt_dtype_mask: mask = mask.astype(dtype_mask, copy=False) # measure region properties to_measure = ('label', 'area', 'centroid', 'bbox') # measure the specified parameters store in dataframe props = pd.DataFrame( measure.regionprops_table( mask, intensity_image=image2d, properties=to_measure)).set_index('label') # filter objects by size props = props[(props['area'] >= min_objectsize) & (props['area'] <= max_objectsize)] # add well information for CZI metadata try: props['WellId'] = md['Well_ArrayNames'][s] props['Well_ColId'] = md['Well_ColId'][s] props['Well_RowId'] = md['Well_RowId'][s] except (IndexError, KeyError) as error: # Output expected ImportErrors. print('Key not found:', error) print('Well Information not found. Using S-Index.') props['WellId'] = s props['Well_ColId'] = s props['Well_RowId'] = s # add plane indices props['S'] = s props['T'] = t props['Z'] = z props['C'] = chindex # count the number of objects values['Number'] = props.shape[0] # update dataframe containing the number of objects objects = objects.append(pd.DataFrame(values, index=[0]), ignore_index=True) results = results.append(props, ignore_index=True) # make sure the array as 5D of order (T, Z, C, X, Y) to write an correct OME-TIFF mask = imf.expand_dims5d(mask, md) # write the OME-TIFF suing apeer-ometiff-library io.write_ometiff(savename_seg, mask, omexml_string=None) # rename columns in pandas datatable results.rename(columns={ 'bbox-0': 'ystart', 'bbox-1': 'xstart', 'bbox-2': 'yend', 'bbox-3': 'xend' }, inplace=True) # calculate the bbox width in height in [pixel] and [micron] results['bbox_width'] = results['xend'] - results['xstart'] results['bbox_height'] = results['yend'] - results['ystart'] results['bbox_width_scaled'] = results['bbox_width'] * md['XScale'] results['bbox_height_scaled'] = results['bbox_height'] * md['XScale'] # calculate the bbox center StageXY results['bbox_center_stageX'], results[ 'bbox_center_stageY'] = bbox2stageXY( image_stageX=stageX, image_stageY=stageY, sizeX=md['SizeX'], sizeY=md['SizeY'], scale=md['XScale'], xstart=results['xstart'], ystart=results['ystart'], bbox_width=results['bbox_width'], bbox_height=results['bbox_height']) # show results print(results) print('Done.') # write the CSV data table print('Write to CSV File : ', filename) csvfile = os.path.splitext(filename)[0] + '_planetable.csv' results.to_csv(csvfile, sep=separator, index=False) # set the outputs outputs = {} outputs['segmented_image'] = savename_seg outputs['objects_table'] = csvfile return outputs
def read_mosaic(filename: str, scale: float=1.0) -> Tuple[Union[np.ndarray, None], czimd.CziMetadata]: """Read the pixel data of an CZI image file with an option scale factor to read the image with lower resolution and array size :param filename: filename of the CZI mosaic file to be read :param scale: scaling factor when reading the mosaic. :return: CZI pixel data and the updated CziMetadata class """ # do not allow scale > 1.0 if scale > 1.0: print("Scale factor > 1.0 is not recommended. Using scale = 1.0.") scale = 1.0 # get the CZI metadata md = czimd.CziMetadata(filename) # read CZI using aicspylibczi aicsczi = CziFile(filename) if not aicsczi.is_mosaic(): # check if this CZI is really a non-mosaic file print("CZI is not a mosaic file. Please use the read_nonmosaic method instead") return None, md # get data for 1st scene and create the required shape for all scenes scene = czimd.CziScene(aicsczi, sceneindex=0) shape_all = scene.shape_single_scene if scene.hasS: shape_all[scene.posS] = md.dims.SizeS if not scene.hasS: num_scenes = 1 print("Shape all Scenes (scale=1.0): ", shape_all) print("DimString all Scenes : ", scene.single_scene_dimstr) # create empty array to hold all scenes all_scenes = np.empty(shape_all, dtype=md.npdtype) resize_done = False # loop over scenes if CZI is not Mosaic for s in range(num_scenes): scene = czimd.CziScene(aicsczi, sceneindex=s) # create a slice object for all_scenes array if not scene.isRGB: #idl_all = [slice(None, None, None)] * (len(all_scenes.shape) - 2) idl_all = [slice(None, None, None)] * (len(shape_all) - 2) if scene.isRGB: #idl_all = [slice(None, None, None)] * (len(all_scenes.shape) - 3) idl_all = [slice(None, None, None)] * (len(shape_all) - 3) # update the entry with the current S index if not scene.hasS: idl_all[s] = s if scene.hasS: idl_all[scene.posS] = s # in case T-Z-H dimension are found if scene.hasT is True and scene.hasZ is True and scene.hasH is True: # read array for the scene for h, t, z, c in product(range(scene.sizeH), range(scene.sizeT), range(scene.sizeZ), range(scene.sizeC)): # read the array for the 1st scene using the ROI scene_array_htzc = aicsczi.read_mosaic(region=(scene.xstart, scene.ystart, scene.width, scene.height), scale_factor=scale, H=h, T=t, Z=z, C=c) print("Shape Single Scene (Scalefactor: ", scale, ": ", scene_array_htzc.shape) # check if all_scenes array must be resized due to scaling if scale < 1.0 and not resize_done: shape_all[-1] = scene_array_htzc.shape[-1] shape_all[-2] = scene_array_htzc.shape[-2] all_scenes = np.resize(all_scenes, shape_all) # add new entries to metadata md = adaptmd_scale(md, scene_array_htzc.shape[-1], scene_array_htzc.shape[-2], scale=scale) resize_done = True # create slide object for the current mosaic scene # idl_scene = [slice(None, None, None)] * (len(scene.shape_single_scene) - 2) idl_all[scene.posS] = s idl_all[scene.posH] = h idl_all[scene.posT] = t idl_all[scene.posZ] = z idl_all[scene.posC] = c # cast the current scene into the stack for all scenes all_scenes[tuple(idl_all)] = scene_array_htzc # in case T-Z-H dimension are found if scene.hasT is True and scene.hasZ is True and scene.hasH is False: # read array for the scene for t, z, c in product(range(scene.sizeT), range(scene.sizeZ), range(scene.sizeC)): # read the array for the 1st scene using the ROI scene_array_tzc = aicsczi.read_mosaic(region=(scene.xstart, scene.ystart, scene.width, scene.height), scale_factor=scale, T=t, Z=z, C=c) print("Shape Single Scene (Scalefactor: ", scale, ": ", scene_array_tzc.shape) # check if all_scenes array must be resized due to scaling if scale < 1.0 and not resize_done: shape_all[-1] = scene_array_tzc.shape[-1] shape_all[-2] = scene_array_tzc.shape[-2] all_scenes = np.resize(all_scenes, shape_all) # add new entries to metadata md = adaptmd_scale(md, scene_array_tzc.shape[-1], scene_array_tzc.shape[-2], scale=scale) resize_done = True # create slide object for the current mosaic scene # idl_scene = [slice(None, None, None)] * (len(scene.shape_single_scene) - 2) idl_all[scene.posS] = s idl_all[scene.posT] = t idl_all[scene.posZ] = z idl_all[scene.posC] = c # cast the current scene into the stack for all scenes all_scenes[tuple(idl_all)] = scene_array_tzc if scene.hasT is False and scene.hasZ is False: # create an array for the scene for c in range(scene.sizeC): scene_array_c = aicsczi.read_mosaic(region=(scene.xstart, scene.ystart, scene.width, scene.height), scale_factor=scale, C=c) print("Shape Single Scene (Scalefactor: ", scale, ": ", scene_array_c.shape) # check if all_scenes array must be resized due to scaling if scale < 1.0 and not resize_done: #new_shape = shape_all shape_all[-1] = scene_array_c.shape[-1] shape_all[-2] = scene_array_c.shape[-2] all_scenes = np.resize(all_scenes, shape_all) # add new entries to metadata md = adaptmd_scale(md, scene_array_c.shape[-1], scene_array_c.shape[-2], scale=scale) resize_done = True idl_all[scene.posS] = s idl_all[scene.posC] = c # cast the current scene into the stack for all scenes all_scenes[tuple(idl_all)] = scene_array_c return all_scenes, md