def save_stack(name, data, luts=None, display_ranges=None, resolution=1., compress=0, dimensions=None): """ Saves `data`, an array with 5, 4, 3, or 2 dimensions [TxZxCxYxX]. `resolution` can be specified in microns per pixel. Setting `compress` to 1 saves a lot of space for integer masks. The ImageJ lookup table for each channel can be set with `luts` (e.g, ops.io.GRAY, ops.io.GREEN, etc). The display range can be set with a list of (min, max) pairs for each channel. >>> random_data = np.random.randint(0, 2**16, size=(3, 100, 100), dtype=np.uint16) >>> luts = ops.io.GREEN, ops.io.RED, ops.io.BLUE >>> display_ranges = (0, 60000), (0, 40000), (0, 20000) >>> save('random_image', random_data, luts=luts, display_ranges=display_ranges) Compatible array data types are: bool (converted to uint8 0, 255) uint8 uint16 float32 float64 (converted to float32) """ if name.split('.')[-1] != 'tif': name += '.tif' name = os.path.abspath(name) if isinstance(data, list): data = np.array(data) if not (2 <= data.ndim <= 5): error = 'Input has shape {}, but number of dimensions must be in range [2, 5]' raise ValueError(error.format(data.shape)) if (data.dtype == np.int64): if (data >= 0).all() and (data < 2**16).all(): data = data.astype(np.uint16) else: data = data.astype(np.float32) print('Cast int64 to float32') if data.dtype == np.float64: data = data.astype(np.float32) # print('Cast float64 to float32') if data.dtype == np.bool: data = 255 * data.astype(np.uint8) if data.dtype == np.int32: if data.min() >= 0 & data.max() < 2**16: data = data.astype(np.uint16) else: raise ValueError('error casting from np.int32 to np.uint16, ' 'data out of range') if not data.dtype in (np.uint8, np.uint16, np.float32): raise ValueError('Cannot save data of type %s' % data.dtype) resolution = (1. / resolution, ) * 2 if not os.path.isdir(os.path.dirname(name)): os.makedirs(os.path.dirname(name), exist_ok=True) if data.ndim == 2: # simple description min, max = single_contrast(data, display_ranges) description = imagej_description_2D(min, max) imsave(name, data, photometric='minisblack', description=description, resolution=resolution, compress=compress) else: # hyperstack description nchannels = data.shape[-3] luts, display_ranges = infer_luts_display_ranges( data, luts, display_ranges) leading_shape = data.shape[:-2] if dimensions is None: dimensions = 'TZC'[::-1][:len(leading_shape)][::-1] if 'C' not in dimensions: # TODO: support lut contrast = single_contrast(data, display_ranges) description = imagej_description(leading_shape, dimensions, contrast=contrast) imsave(name, data, photometric='minisblack', description=description, resolution=resolution, compress=compress) else: # the full monty description = imagej_description(leading_shape, dimensions) # metadata encoding LUTs and display ranges # see http://rsb.info.nih.gov/ij/developer/source/ij/io/TiffEncoder.java.html tag_50838 = ij_tag_50838(nchannels) tag_50839 = ij_tag_50839(luts, display_ranges) imsave(name, data, photometric='minisblack', description=description, resolution=resolution, compress=compress, extratags=[ (50838, 'I', len(tag_50838), tag_50838, True), (50839, 'B', len(tag_50839), tag_50839, True), ])
def save_stack(name, data, luts=None, display_ranges=None, resolution=1., compress=0): """ Saves `data`, an array with 5, 4, 3, or 2 dimensions [TxZxCxYxX]. `resolution` can be specified in microns per pixel. Setting `compress` to 1 saves a lot of space for integer masks. The ImageJ lookup table for each channel can be set with `luts` (e.g, ops.io.GRAY, ops.io.GREEN, etc). The display range can be set with a list of (min, max) pairs for each channel. >>> random_data = np.random.randint(0, 2**16, size=(3, 100, 100), dtype=np.uint16) >>> luts = ops.io.GREEN, ops.io.RED, ops.io.BLUE >>> display_ranges = (0, 60000), (0, 40000), (0, 20000) >>> save('random_image', random_data, luts=luts, display_ranges=display_ranges) Compatible array data types are: bool (converted to uint8 0, 255) uint8 uint16 float32 float64 (converted to float32) """ if name.split('.')[-1] != 'tif': name += '.tif' name = os.path.abspath(name) if isinstance(data, list): data = np.array(data) if data.ndim == 2: data = data[None] if (data.dtype == np.int64): if (data >= 0).all() and (data < 2**16).all(): data = data.astype(np.uint16) else: data = data.astype(np.float32) print('Cast int64 to float32') if data.dtype == np.float64: data = data.astype(np.float32) print('Cast float64 to float32') if data.dtype == np.bool: data = 255 * data.astype(np.uint8) if not data.dtype in (np.uint8, np.uint16, np.float32): raise ValueError('Cannot save data of type %s' % data.dtype) nchannels = data.shape[-3] resolution = (1. / resolution, ) * 2 if luts is None: luts = DEFAULT_LUTS + (GRAY, ) * nchannels if display_ranges is None: display_ranges = [None] * data.shape[-3] for i, dr in enumerate(display_ranges): if dr is None: x = data[..., i, :, :] display_ranges[i] = x.min(), x.max() try: luts = luts[:nchannels] display_ranges = display_ranges[:nchannels] except IndexError: raise IndexError('Must provide at least %d luts and display ranges' % nchannels) # metadata encoding LUTs and display ranges # see http://rsb.info.nih.gov/ij/developer/source/ij/io/TiffEncoder.java.html description = ij_description(data.shape) tag_50838 = ij_tag_50838(nchannels) tag_50839 = ij_tag_50839(luts, display_ranges) if not os.path.isdir(os.path.dirname(name)): os.makedirs(os.path.dirname(name)) imsave(name, data, photometric='minisblack', description=description, resolution=resolution, compress=compress, extratags=[ (50838, 'I', len(tag_50838), tag_50838, True), (50839, 'B', len(tag_50839), tag_50839, True), ])