def test_bgr_plane_data_x(data_dir, fname, p_index, ans_file): ans = np.load(data_dir / ans_file) with open(data_dir / fname, 'rb') as fp: czi = CziFile(czi_filename=fp) img, dims = czi.read_image() assert img[0, p_index, :, :].shape == ans.shape np.testing.assert_array_almost_equal(img[0, p_index, :, :], ans)
def _read_image( img: Path, read_dims: Optional[Dict[str, int]] = None ) -> Tuple[np.ndarray, List[Tuple[str, int]]]: # Catch optional read dim if read_dims is None: read_dims = {} # Init czi czi = CziFile(img) # Read image log.debug(f"Reading dimensions: {read_dims}") data, dims = czi.read_image(**read_dims) # Drop dims that shouldn't be provided back ops = [] real_dims = [] for i, dim_info in enumerate(dims): # Expand dimension info dim, size = dim_info # If the dim was provided in the read dims we know a single plane for that # dimension was requested so remove it if dim in read_dims: ops.append(0) # Otherwise just read the full slice else: ops.append(slice(None, None, None)) real_dims.append(dim_info) # Convert ops and run getitem return data[tuple(ops)], real_dims
def _read_immediate(self) -> da.core.Array: # Init temp czi czi = CziFile(self._file) # Safely construct the numpy array or catch any exception try: # Get image dims indicies image_dim_indices = czi.dims_shape() # Catch inconsistent scene dimension sizes if len(image_dim_indices) > 1: # Choose the provided scene log.info( f"File contains variable dimensions per scene, " f"selected scene: {self.specific_s_index} for data retrieval." ) # Get the specific scene if self.specific_s_index < len(image_dim_indices): data, _ = czi.read_image( **{Dimensions.Scene: self.specific_s_index}) else: raise exceptions.InconsistentShapeError( f"The CZI image provided has variable dimensions per scene. " f"Please provide a valid index to the 'S' parameter to create " f"a dask array for the index provided. " f"Provided scene index: {self.specific_s_index}. " f"Scene index range: 0-{len(image_dim_indices)}.") else: # If the list is length one that means that all the scenes in the image # have the same dimensions # Read all data in the image data, _ = czi.read_image() # A really bad way to close any connection to the CZI object czi._bytes = None czi.reader = None except Exception as e: # A really bad way to close any connection to the CZI object czi._bytes = None czi.reader = None raise e return data
def read_nonmosaic(filename: str) -> Tuple[Union[np.ndarray, None], czimd.CziMetadata]: """Read CZI pixel data from non-mosaic image data :param filename: filename of the CZI file to be read :return: CZI pixel data and the CziMetadata class """ # get the CZI metadata md = czimd.CziMetadata(filename) # read CZI using aicspylibczi aicsczi = CziFile(filename) if aicsczi.is_mosaic(): # check if this CZIn is really a non-mosaic file print("CZI is a mosaic file. Please use the readczi_mosaic method instead") return None, md # get the shape for the 1st scene scene = czimd.CziScene(aicsczi, sceneindex=0) shape_all = scene.shape_single_scene # only update the shape for the scene if the CZI has an S-Dimension if scene.hasS: shape_all[scene.posS] = md.dims.SizeS print("Shape all Scenes : ", shape_all) print("DimString all Scenes : ", scene.single_scene_dimstr) # create an empty array with the correct dimensions all_scenes = np.empty(aicsczi.size, dtype=md.npdtype) # loop over scenes if CZI is not a mosaic image if md.dims.SizeS is None: sizeS = 1 else: sizeS = md.dims.SizeS for s in range(sizeS): # read the image stack for the current scene current_scene, shp = aicsczi.read_image(S=s) # create th index lists containing the slice objects if scene.hasS: idl_scene = [slice(None, None, None)] * (len(all_scenes.shape) - 2) idl_scene[aicsczi.dims.index("S")] = 0 idl_all = [slice(None, None, None)] * (len(all_scenes.shape) - 2) idl_all[aicsczi.dims.index("S")] = s # cast current stack into the stack for all scenes all_scenes[tuple(idl_all)] = current_scene[tuple(idl_scene)] # if there is no S-Dimension use the stack directly if not scene.hasS: all_scenes = current_scene print("Shape all scenes (no mosaic)", all_scenes.shape) return all_scenes, md
def test_read_image_from_istream(data_dir, fname, expected_img_shape, expected_img_dims): with open(data_dir / fname, 'rb') as fp: czi = CziFile(czi_filename=fp) assert czi.shape_is_consistent data = czi.read_image() assert data[0].shape == expected_img_shape assert data[1] == expected_img_dims
def _read_image( img: Path, read_dims: Optional[Dict[str, int]] = None ) -> Tuple[np.ndarray, List[Tuple[str, int]]]: """ Read and return the squeezed image data requested along with the dimension info that was read. Parameters ---------- img: Path Path to the CZI file to read. read_dims: Optional[Dict[str, int]] The dimensions to read from the file as a dictionary of string to integer. Default: None (Read all data from the image) Returns ------- data: np.ndarray The data read for the dimensions provided. read_dimensions: List[Tuple[str, int]]] The dimension sizes that were returned from the read. """ # Catch optional read dim if read_dims is None: read_dims = {} # Init czi czi = CziFile(img) # Read image log.debug(f"Reading dimensions: {read_dims}") data, dims = czi.read_image(**read_dims) # Drop dims that shouldn't be provided back ops = [] real_dims = [] for i, dim_info in enumerate(dims): # Expand dimension info dim, size = dim_info # If the dim was provided in the read dims we know a single plane for that # dimension was requested so remove it if dim in read_dims: ops.append(0) # Otherwise just read the full slice else: ops.append(slice(None, None, None)) real_dims.append(dim_info) # Convert ops and run getitem return data[tuple(ops)], real_dims
def test_read_image_mosaic(data_dir, fname): czi = CziFile(str(data_dir / fname)) czi.read_image(M=0) assert True
def test_read_image_args(data_dir, fname, args, expected): czi = CziFile(data_dir / fname) img, shp = czi.read_image(**args) assert img.shape == expected
def test_read_image(data_dir, fname, expected): czi = CziFile(str(data_dir / fname)) img, shp = czi.read_image() assert img.shape == expected
def load_data(filename, max_xy=512, dtype='uint8', reload=False): filename = Path(filename) cache_filename = (filename.parent / "processed" / f"max_xy_{max_xy}_dtype_{dtype}" / filename.with_suffix(".pkl").name) if not reload and cache_filename.exists(): with cache_filename.open("rb") as fh: return pickle.load(fh) fh = CziFile(filename) x_pixels = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/DimensionX" ).text) y_pixels = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/DimensionY" ).text) z_pixels = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/DimensionZ" ).text) x_scaling = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/ScalingX" ).text) y_scaling = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/ScalingY" ).text) z_scaling = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/ScalingZ" ).text) x_offset = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/OffsetX" ).text) y_offset = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/OffsetY" ).text) z_offset = float( fh.meta.find( "Metadata/Experiment/ExperimentBlocks/AcquisitionBlock/AcquisitionModeSetup/OffsetZ" ).text) node = fh.meta.find( "Metadata/Information/Image/Dimensions/S/Scenes/Scene/Positions/Position" ) info = {} info["offset"] = np.array([x_offset, y_offset, z_offset]) info["pixels"] = np.array([x_pixels, y_pixels, z_pixels]).astype("i") info["scaling"] = np.array([x_scaling, y_scaling, z_scaling]) info["origin"] = np.array([float(v) * 1e-6 for k, v in node.items()]) info["lower"] = info["origin"] info["extent"] = info["pixels"] * info["scaling"] info["upper"] = info["lower"] + info["extent"] del info["pixels"] img = fh.read_image()[0][0, 0, 0] # First, do the zoom. This is the best time to handle it before we do # additional manipulations. zoom = max_xy / max(x_pixels, y_pixels) if zoom < 1: img = np.concatenate( [ndimage.zoom(i, (1, zoom, zoom))[np.newaxis] for i in img]) info["scaling"][:2] /= zoom # Initial ordering is czyx # 0123 # Final ordering xyzc img = img.swapaxes(0, 3).swapaxes(1, 2) # Add a third channel to allow for RGB images padding = [(0, 0)] * img.ndim padding[-1] = (0, 1) img = np.pad(img, padding, "constant") # Rescale to range 0 ... 1 img = img / img.max(axis=(0, 1, 2), keepdims=True) if 'int' in dtype: img *= 255 img = img.astype(dtype) cache_filename.parent.mkdir(exist_ok=True, parents=True) with cache_filename.open("wb") as fh: pickle.dump((info, img), fh, pickle.HIGHEST_PROTOCOL) return info, img
import numpy as np import imgfileutils as imf from aicspylibczi import CziFile import czifile as zis import xmltodict # filename = r'/datadisk1/tuxedo/temp/input/WP96_T=3_Z=4_Ch=2_3x3_A4-A5.czi' # filename = r'/datadisk1/tuxedo/temp/input/WP96_T=3_Z=4_Ch=2_5x5_A4.czi' filename = r'/datadisk1/tuxedo/temp/input/DTScan_ID4.czi' print('----------czifile array ----------') czi_czifile_array, md, addmd = imf.get_array_czi(filename, remove_HDim=False) print('Shape czi_czifile', czi_czifile_array.shape) czi_aics = CziFile(filename) czi_aics_out = czi_aics.read_image(S=0) czi_aics_array = czi_aics_out[0] czi_aics_dims = czi_aics_out[1] print('Shape czi_aics_array', czi_aics_array.shape) print('Shape czi_aics_dims', czi_aics_dims) print('CZI Mosaic', md['czi_ismosaic']) print('SizeX', md['SizeX']) print('SizeY', md['SizeY']) print('SizeC', md['SizeC']) print('SizeZ', md['SizeZ']) print('SizeT', md['SizeT']) print('SizeS', md['SizeS']) print('SizeM', md['SizeM']) print('SizeB', md['SizeB']) print('SizeH', md['SizeH'])
def daread(img: Union[str, Path]) -> da.core.Array: """ Read a CZI image file as a delayed dask array where each YX plane will be read on request. Parameters ---------- img: Union[str, Path] The filepath to read. Returns ------- img: dask.array.core.Array The constructed dask array where each YX plane is a delayed read. """ # Convert pathlike to CziFile if isinstance(img, (str, Path)): # Resolve path img = Path(img).expanduser().resolve(strict=True) # Check path if img.is_dir(): raise IsADirectoryError( f"Please provide a single file to the `img` parameter. " f"Received directory: {img}") # Check that no other type was provided if not isinstance(img, Path): raise TypeError( f"Please provide a path to a file as a string, or an pathlib.Path, to the " f"`img` parameter. " f"Received type: {type(img)}") # Init temp czi czi = CziFile(img) # Get image dims shape image_dims = czi.dims_shape() # Setup the read dimensions dictionary for reading the first plane first_plane_read_dims = {} for dim, dim_info in image_dims.items(): # Unpack dimension info dim_begin_index, dim_end_index = dim_info # Add to read dims first_plane_read_dims[dim] = dim_begin_index # Read first plane for information used by dask.array.from_delayed sample, sample_dims = czi.read_image(**first_plane_read_dims) # The Y and X dimensions are always the last two dimensions, in that order. # These dimensions cannot be operated over but the shape information is used # in multiple places so we pull them out for easier access. sample_YX_shape = sample.shape[-2:] # Create operating shape and dim order list operating_shape = czi.size[:-2] dims = [dim for dim in czi.dims[:-2]] # Create empty numpy array with the operating shape so that we can iter through # and use the multi_index to create the readers. # We add empty dimensions of size one to fake being the Y and X dimensions. lazy_arrays = np.ndarray(operating_shape + (1, 1), dtype=object) # We can enumerate over the multi-indexed array and construct read_dims # dictionaries by simply zipping together the ordered dims list and the current # multi-index plus the begin index for that plane. # We then set the value of the array at the same multi-index to # the delayed reader using the constructed read_dims dictionary. begin_indicies = tuple(image_dims[dim][0] for dim in dims) for i, _ in np.ndenumerate(lazy_arrays): this_plane_read_indicies = (current_dim_begin_index + curr_dim_index for current_dim_begin_index, curr_dim_index in zip(begin_indicies, i)) this_plane_read_dims = dict(zip(dims, this_plane_read_indicies)) lazy_arrays[i] = da.from_delayed( delayed(_imread)(img, this_plane_read_dims), shape=sample_YX_shape, dtype=sample.dtype, ) # Convert the numpy array of lazy readers into a dask array merged = da.block(lazy_arrays.tolist()) # Because dimensions outside of Y and X can be in any order and present or not # we also return the dimension order string. dims = dims + ["Y", "X"] return merged, "".join(dims)
if md['ImageType'] == 'czi': isCZI = True else: isCZI = False if md['czi_isRGB']: isRGB = True else: isRGB = False if md['czi_isMosaic']: isMosaic = True else: isMosaic = False output = czi.read_image(S=0, T=0, Z=0, C=0) image = output[0] image_dims = output[1] #mosaic_data = czi.read_mosaic(C=0, T=0, Z=0, scale_factor=1.0) #image2d = np.squeeze(mosaic_data) #image2d = np.moveaxis(image2d, 0, -1) # convert ZEN BGR into RGB #image2d = image2d[..., ::-1] plt.figure(figsize=(12, 12)) # plt.imshow(image2d) plt.imshow(image) plt.axis('off') plt.show() """
def _daread(img: Path, czi: CziFile, chunk_by_dims: List[str] = [ Dimensions.SpatialZ, Dimensions.SpatialY, Dimensions.SpatialX ], S: int = 0) -> Tuple[da.core.Array, str]: """ Read a CZI image file as a delayed dask array where certain dimensions act as the chunk size. Parameters ---------- img: Path The filepath to read. czi: CziFile The loaded CziFile object created from reading the filepath. chunk_by_dims: List[str] The dimensions to use as the for mapping the chunks / blocks. Default: [Dimensions.SpatialZ, Dimensions.SpatialY, Dimensions.SpatialX] Note: SpatialY and SpatialX will always be added to the list if not present. S: int If the image has different dimensions on any scene from another, the dask array construction will fail. In that case, use this parameter to specify a specific scene to construct a dask array for. Default: 0 (select the first scene) Returns ------- img: dask.array.core.Array The constructed dask array where certain dimensions are chunked. dims: str The dimension order as a string. """ # Get image dims indicies image_dim_indices = czi.dims_shape() # Catch inconsistent scene dimension sizes if len(image_dim_indices) > 1: # Choose the provided scene try: image_dim_indices = image_dim_indices[S] log.info( f"File contains variable dimensions per scene, selected scene: {S} for data retrieval." ) except IndexError: raise exceptions.InconsistentShapeError( f"The CZI image provided has variable dimensions per scene. " f"Please provide a valid index to the 'S' parameter to create a dask array for the index provided. " f"Provided scene index: {S}. Scene index range: 0-{len(image_dim_indices)}." ) else: # If the list is length one that means that all the scenes in the image have the same dimensions # Just select the first dictionary in the list image_dim_indices = image_dim_indices[0] # Uppercase dimensions provided to chunk by dims chunk_by_dims = [d.upper() for d in chunk_by_dims] # Always add Y and X dims to chunk by dims because that is how CZI files work if Dimensions.SpatialY not in chunk_by_dims: log.info( f"Adding the Spatial Y dimension to chunk by dimensions as it was not found." ) chunk_by_dims.append(Dimensions.SpatialY) if Dimensions.SpatialX not in chunk_by_dims: log.info( f"Adding the Spatial X dimension to chunk by dimensions as it was not found." ) chunk_by_dims.append(Dimensions.SpatialX) # Setup read dimensions for an example chunk first_chunk_read_dims = {} for dim, (dim_begin_index, dim_end_index) in image_dim_indices.items(): # Only add the dimension if the dimension isn't a part of the chunk if dim not in chunk_by_dims: # Add to read dims first_chunk_read_dims[dim] = dim_begin_index # Read first chunk for information used by dask.array.from_delayed sample, sample_dims = czi.read_image(**first_chunk_read_dims) # Get the shape for the chunk and operating shape for the dask array # We also collect the chunk and non chunk dimension ordering so that we can swap the dimensions after we # block the dask array together. sample_chunk_shape = [] operating_shape = [] non_chunk_dimension_ordering = [] chunk_dimension_ordering = [] for i, dim_info in enumerate(sample_dims): # Unpack dim info dim, size = dim_info # If the dim is part of the specified chunk dims then append it to the sample, and, append the dimension # to the chunk dimension ordering if dim in chunk_by_dims: sample_chunk_shape.append(size) chunk_dimension_ordering.append(dim) # Otherwise, append the dimension to the non chunk dimension ordering, and, append the true size of the # image at that dimension else: non_chunk_dimension_ordering.append(dim) operating_shape.append(image_dim_indices[dim][1] - image_dim_indices[dim][0]) # Convert shapes to tuples and combine the non and chunked dimension orders as that is the order the data will # actually come out of the read data as sample_chunk_shape = tuple(sample_chunk_shape) blocked_dimension_order = non_chunk_dimension_ordering + chunk_dimension_ordering # Fill out the rest of the operating shape with dimension sizes of 1 to match the length of the sample chunk # When dask.block happens it fills the dimensions from inner-most to outer-most with the chunks as long as # the dimension is size 1 # Basically, we are adding empty dimensions to the operating shape that will be filled by the chunks from dask operating_shape = tuple( operating_shape) + (1, ) * len(sample_chunk_shape) # Create empty numpy array with the operating shape so that we can iter through and use the multi_index to # create the readers. lazy_arrays = np.ndarray(operating_shape, dtype=object) # We can enumerate over the multi-indexed array and construct read_dims dictionaries by simply zipping together # the ordered dims list and the current multi-index plus the begin index for that plane. We then set the value # of the array at the same multi-index to the delayed reader using the constructed read_dims dictionary. dims = [d for d in czi.dims] begin_indicies = tuple(image_dim_indices[d][0] for d in dims) for i, _ in np.ndenumerate(lazy_arrays): # Add the czi file begin index for each dimension to the array dimension index this_chunk_read_indicies = ( current_dim_begin_index + curr_dim_index for current_dim_begin_index, curr_dim_index in zip( begin_indicies, i)) # Zip the dims with the read indices this_chunk_read_dims = dict( zip(blocked_dimension_order, this_chunk_read_indicies)) # Remove the dimensions that we want to chunk by from the read dims for d in chunk_by_dims: if d in this_chunk_read_dims: this_chunk_read_dims.pop(d) # Add delayed array to lazy arrays at index lazy_arrays[i] = da.from_delayed( delayed(CziReader._imread)(img, this_chunk_read_dims), shape=sample_chunk_shape, dtype=sample.dtype, ) # Convert the numpy array of lazy readers into a dask array and fill the inner-most empty dimensions with chunks merged = da.block(lazy_arrays.tolist()) # Because we have set certain dimensions to be chunked and others not # we will need to transpose back to original dimension ordering # Example being, if the original dimension ordering was "SZYX" and we want to chunk by "S", "Y", and "X" # We created an array with dimensions ordering "ZSYX" transpose_indices = [] transpose_required = False for i, d in enumerate(czi.dims): new_index = blocked_dimension_order.index(d) if new_index != i: transpose_required = True transpose_indices.append(new_index) else: transpose_indices.append(i) # Only run if the transpose is actually required # The default case is "Z", "Y", "X", which _usually_ doesn't need to be transposed because that is _usually_ # The normal dimension order of the CZI file anyway if transpose_required: merged = da.transpose(merged, tuple(transpose_indices)) # Because dimensions outside of Y and X can be in any order and present or not # we also return the dimension order string. return merged, "".join(dims)