Ejemplo n.º 1
0
def czi2tif(czifile, tiffile=None, squeeze=True, verbose=True, **kwargs):
    """Convert CZI file to memory-mappable TIFF file.

    To read the image data from the created TIFF file: Read the 'StripOffsets'
    and 'ImageDescription' tags from the first TIFF page. Get the 'dtype' and
    'shape' attributes from the ImageDescription string using a JSON decoder.
    Memory-map 'product(shape) * sizeof(dtype)' bytes in the file starting at
    StripOffsets[0]. Cast the mapped bytes to an array of 'dtype' and 'shape'.

    """
    verbose = print_ if verbose else lambda *a, **b: None

    if tiffile is None:
        tiffile = czifile + '.tif'
    elif tiffile.lower() == 'none':
        tiffile = None

    verbose("\nOpening CZI file... ", end='', flush=True)
    start_time = time.time()

    with CziFile(czifile) as czi:
        if squeeze:
            shape, axes = squeeze_axes(czi.shape, czi.axes, '')
        else:
            shape = czi.shape
            axes = czi.axes
        dtype = str(czi.dtype)
        size = product(shape) * czi.dtype.itemsize

        verbose("%.3f s" % (time.time() - start_time))
        verbose("Image\n  axes:  %s\n  shape: %s\n  dtype: %s\n  size:  %s" %
                (axes, shape, dtype, format_size(size)),
                flush=True)

        if not tiffile:
            verbose("Copying image from CZI file to RAM... ",
                    end='',
                    flush=True)
            start_time = time.time()
            czi.asarray(order=0)
        else:
            verbose("Creating empty TIF file... ", end='', flush=True)
            start_time = time.time()
            if 'software' not in kwargs:
                kwargs['software'] = 'czi2tif'
            metadata = kwargs.pop('metadata', {})
            metadata.update(axes=axes, dtype=dtype)
            data = memmap(tiffile,
                          shape=shape,
                          dtype=dtype,
                          metadata=metadata,
                          **kwargs)
            data = data.reshape(czi.shape)
            verbose("%.3f s" % (time.time() - start_time))
            verbose("Copying image from CZI to TIF file... ",
                    end='',
                    flush=True)
            start_time = time.time()
            czi.asarray(order=0, out=data)
        verbose("%.3f s" % (time.time() - start_time), flush=True)
Ejemplo n.º 2
0
def czi2tif(czifile, tiffile=None, squeeze=True, verbose=True, **kwargs):
    """Convert CZI file to memory-mappable TIFF file.

    To read the image data from the created TIFF file: Read the 'StripOffsets'
    and 'ImageDescription' tags from the first TIFF page. Get the 'dtype' and
    'shape' attributes from the ImageDescription string using a JSON decoder.
    Memory-map 'product(shape) * sizeof(dtype)' bytes in the file starting at
    StripOffsets[0]. Cast the mapped bytes to an array of 'dtype' and 'shape'.

    """
    verbose = print_ if verbose else lambda *a, **b: None

    if tiffile is None:
        tiffile = czifile + '.tif'
    elif tiffile.lower() == 'none':
        tiffile = None

    verbose("\nOpening CZI file... ", end='', flush=True)
    start_time = time.time()

    with CziFile(czifile) as czi:
        if squeeze:
            shape, axes = squeeze_axes(czi.shape, czi.axes, '')
        else:
            shape = czi.shape
            axes = czi.axes
        dtype = str(czi.dtype)
        size = product(shape) * czi.dtype.itemsize

        verbose("%.3f s" % (time.time() - start_time))
        verbose("Image\n  axes:  %s\n  shape: %s\n  dtype: %s\n  size:  %s"
                % (axes, shape, dtype, format_size(size)), flush=True)

        if not tiffile:
            verbose("Copying image from CZI file to RAM... ",
                    end='', flush=True)
            start_time = time.time()
            czi.asarray(order=0)
        else:
            verbose("Creating empty TIF file... ", end='', flush=True)
            start_time = time.time()
            if 'software' not in kwargs:
                kwargs['software'] = 'czi2tif'
            metadata = kwargs.pop('metadata', {})
            metadata.update(axes=axes, dtype=dtype)
            data = memmap(tiffile, shape=shape, dtype=dtype,
                          metadata=metadata, **kwargs)
            data = data.reshape(czi.shape)
            verbose("%.3f s" % (time.time() - start_time))
            verbose("Copying image from CZI to TIF file... ",
                    end='', flush=True)
            start_time = time.time()
            czi.asarray(order=0, out=data)
        verbose("%.3f s" % (time.time() - start_time), flush=True)
Ejemplo n.º 3
0
def asarray(self,
            out=None,
            squeeze=True,
            lock=None,
            reopen=True,
            maxsize=None,
            maxworkers=None,
            validate=True,
            loc=None,
            size=None):
    """Read image data from file and return as numpy array.

    Raise ValueError if format is unsupported.

    Parameters
    ----------
    out : numpy.ndarray, str, or file-like object
        Buffer where image data will be saved.
        If None (default), a new array will be created.
        If numpy.ndarray, a writable array of compatible dtype and shape.
        If 'memmap', directly memory-map the image data in the TIFF file
        if possible; else create a memory-mapped array in a temporary file.
        If str or open file, the file name or file object used to
        create a memory-map to an array stored in a binary file on disk.
    squeeze : bool
        If True (default), all length-1 dimensions (except X and Y) are
        squeezed out from the array.
        If False, the shape of the returned array might be different from
        the page.shape.
    lock : {RLock, NullContext}
        A reentrant lock used to synchronize seeks and reads from file.
        If None (default), the lock of the parent's filehandle is used.
    reopen : bool
        If True (default) and the parent file handle is closed, the file
        is temporarily re-opened and closed if no exception occurs.
    maxsize: int
        Maximum size of data before a ValueError is raised.
        Can be used to catch DOS. Default: 16 TB.
    maxworkers : int or None
        Maximum number of threads to concurrently decode compressed
        segments. If None (default), up to half the CPU cores are used.
        See remarks in TiffFile.asarray.
    validate : bool
        If True (default), validate various parameters.
        If None, only validate parameters and return None.

    Returns
    -------
    numpy.ndarray
        Numpy array of decompressed, depredicted, and unpacked image data
        read from Strip/Tile Offsets/ByteCounts, formatted according to
        shape and dtype metadata found in tags and parameters.
        Photometric conversion, pre-multiplied alpha, orientation, and
        colorimetry corrections are not applied. Specifically, CMYK images
        are not converted to RGB, MinIsWhite images are not inverted,
        and color palettes are not applied. An exception are YCbCr JPEG
        compressed images, which will be converted to RGB.

    """
    # properties from TiffPage or TiffFrame
    fh = self.parent.filehandle
    byteorder = self.parent.tiff.byteorder
    offsets, bytecounts = self._offsetscounts
    self_ = self
    self = self.keyframe  # self or keyframe

    if not self._shape or tifffile.product(self._shape) == 0:
        return None

    tags = self.tags

    if validate or validate is None:
        if maxsize is None:
            maxsize = 2**44
        if maxsize and tifffile.product(self._shape) > maxsize:
            raise ValueError('TiffPage %i: data are too large %s' %
                             (self.index, str(self._shape)))
        if self.dtype is None:
            raise ValueError(
                'TiffPage %i: data type not supported: %s%i' %
                (self.index, self.sampleformat, self.bitspersample))
        if self.compression not in tifffile.TIFF.DECOMPESSORS:
            raise ValueError('TiffPage %i: cannot decompress %s' %
                             (self.index, self.compression.name))
        if 'SampleFormat' in tags:
            tag = tags['SampleFormat']
            if (tag.count != 1 and any(i - tag.value[0] for i in tag.value)):
                raise ValueError(
                    'TiffPage %i: sample formats do not match %s' %
                    (self.index, tag.value))
        if self.is_subsampled and (self.compression not in (6, 7)
                                   or self.planarconfig == 2):
            raise NotImplementedError(
                'TiffPage %i: chroma subsampling not supported' % self.index)
        if validate is None:
            return None

    lock = fh.lock if lock is None else lock
    with lock:
        closed = fh.closed
        if closed:
            if reopen:
                fh.open()
            else:
                raise IOError('TiffPage %i: file handle is closed' %
                              self.index)

    dtype = self._dtype
    shape = self._shape
    imagewidth = self.imagewidth
    imagelength = self.imagelength
    imagedepth = self.imagedepth
    bitspersample = self.bitspersample
    typecode = byteorder + dtype.char
    lsb2msb = self.fillorder == 2
    istiled = self.is_tiled
    result_offset = None

    if istiled:
        tilewidth = self.tilewidth
        tilelength = self.tilelength
        tiledepth = self.tiledepth
        tw = (imagewidth + tilewidth - 1) // tilewidth
        tl = (imagelength + tilelength - 1) // tilelength
        td = (imagedepth + tiledepth - 1) // tiledepth
        tiledshape = (td, tl, tw)
        tileshape = (tiledepth, tilelength, tilewidth, shape[-1])
        runlen = tilewidth
    else:
        runlen = imagewidth

    if self.planarconfig == 1:
        runlen *= self.samplesperpixel

    if isinstance(out, str) and out == 'memmap' and self.is_memmappable:
        # direct memory map array in file
        with lock:
            result = fh.memmap_array(typecode, shape, offset=offsets[0])
    elif self.is_contiguous:
        # read contiguous bytes to array
        if out is not None:
            out = tifffile.create_output(out, shape, dtype)
        with lock:
            fh.seek(offsets[0])
            result = fh.read_array(typecode, tifffile.product(shape), out=out)
        if lsb2msb:
            tifffile.bitorder_decode(result, out=result)
    else:
        # decompress, unpack,... individual strips or tiles

        if loc is not None and size is not None and istiled:
            result = np.zeros(shape=(shape[0], shape[1], shape[2], size[1],
                                     size[0], shape[5]),
                              dtype=dtype)
            result_offset = (0, 0, 0, loc[1], loc[0], 0)
        else:
            result = tifffile.create_output(out, shape, dtype)

        decompress = tifffile.TIFF.DECOMPESSORS[self.compression]

        if self.compression in (6, 7):  # COMPRESSION.JPEG
            colorspace = None
            outcolorspace = None
            jpegtables = None
            if lsb2msb:
                tifffile.log_warning('TiffPage %i: disabling LSB2MSB for JPEG',
                                     self.index)
                lsb2msb = False
            if 'JPEGTables' in tags:
                # load JPEGTables from TiffFrame
                jpegtables = self_._gettags({347}, lock=lock)[0][1].value
            # TODO: obtain table from OJPEG tags
            # elif ('JPEGInterchangeFormat' in tags and
            #       'JPEGInterchangeFormatLength' in tags and
            #       tags['JPEGInterchangeFormat'].value != offsets[0]):
            #     fh.seek(tags['JPEGInterchangeFormat'].value)
            #     fh.read(tags['JPEGInterchangeFormatLength'].value)
            if 'ExtraSamples' in tags:
                pass
            elif self.photometric == 6:
                # YCBCR -> RGB
                outcolorspace = 'RGB'
            elif self.photometric == 2:
                if self.planarconfig == 1:
                    colorspace = outcolorspace = 'RGB'
            else:
                outcolorspace = tifffile.TIFF.PHOTOMETRIC(
                    self.photometric).name
            if istiled:
                heightwidth = tilelength, tilewidth
            else:
                heightwidth = imagelength, imagewidth

            def decompress(data,
                           bitspersample=bitspersample,
                           jpegtables=jpegtables,
                           colorspace=colorspace,
                           outcolorspace=outcolorspace,
                           shape=heightwidth,
                           out=None,
                           _decompress=decompress):
                return _decompress(data, bitspersample, jpegtables, colorspace,
                                   outcolorspace, shape, out)

            def unpack(data):
                return data.reshape(-1)

        elif bitspersample in (8, 16, 32, 64, 128):
            if (bitspersample * runlen) % 8:
                raise ValueError('TiffPage %i: data and sample size mismatch' %
                                 self.index)
            if self.predictor == 3:  # PREDICTOR.FLOATINGPOINT
                # the floating-point horizontal differencing decoder
                # needs the raw byte order
                typecode = dtype.char

            def unpack(data, typecode=typecode, out=None):
                try:
                    # read only numpy array
                    return numpy.frombuffer(data, typecode)
                except ValueError:
                    # strips may be missing EOI
                    # log_warning('TiffPage.asarray: ...')
                    bps = bitspersample // 8
                    xlen = (len(data) // bps) * bps
                    return numpy.frombuffer(data[:xlen], typecode)

        elif isinstance(bitspersample, tuple):

            def unpack(data, out=None):
                return tifffile.unpack_rgb(data, typecode, bitspersample)

        else:

            def unpack(data, out=None):
                return tifffile.packints_decode(data, typecode, bitspersample,
                                                runlen)

        # TODO: store decode function for future use
        # TODO: unify tile and strip decoding
        if istiled:
            unpredict = tifffile.TIFF.UNPREDICTORS[self.predictor]

            def decode(tile,
                       tileindex,
                       tileshape=tileshape,
                       tiledshape=tiledshape,
                       lsb2msb=lsb2msb,
                       decompress=decompress,
                       unpack=unpack,
                       unpredict=unpredict,
                       nodata=self.nodata,
                       out=result[0]):
                return tile_decode(tile,
                                   tileindex,
                                   tileshape,
                                   tiledshape,
                                   lsb2msb,
                                   decompress,
                                   unpack,
                                   unpredict,
                                   nodata,
                                   out,
                                   offset=result_offset[1:]
                                   if result_offset is not None else None,
                                   total_shape=shape[1:])

            use = get_used_tiles(len(offsets), tileshape, tiledshape, loc,
                                 size)

            bytecounts = [b for i, b in enumerate(bytecounts) if i in use]
            offsets = [b for i, b in enumerate(offsets) if i in use]

            tileiter = fh.read_segments(offsets, bytecounts, lock)

            if self.compression == 1 or len(offsets) < 3:
                maxworkers = 1
            elif maxworkers is None or maxworkers < 1:
                import multiprocessing
                maxworkers = max(multiprocessing.cpu_count() // 2, 1)

            if maxworkers < 2:
                for i, tile in enumerate(tileiter):
                    decode(tile, use[i])
            else:
                # decode first tile un-threaded to catch exceptions
                decode(next(tileiter), use[0])
                with ThreadPoolExecutor(maxworkers) as executor:
                    executor.map(decode, tileiter, use[1:])

        else:
            stripsize = self.rowsperstrip * self.imagewidth
            if self.planarconfig == 1:
                stripsize *= self.samplesperpixel
            outsize = stripsize * self.dtype.itemsize
            result = result.reshape(-1)
            index = 0
            for strip in fh.read_segments(offsets, bytecounts, lock):
                if strip is None:
                    result[index:index + stripsize] = self.nodata
                    index += stripsize
                    continue
                if lsb2msb:
                    strip = tifffile.bitorder_decode(strip, out=strip)
                strip = decompress(strip, out=outsize)
                strip = unpack(strip)
                size = min(result.size, strip.size, stripsize,
                           result.size - index)
                result[index:index + size] = strip[:size]
                del strip
                index += size

    #result.shape = self._shape

    if self.predictor != 1 and not (istiled and not self.is_contiguous):
        unpredict = tifffile.TIFF.UNPREDICTORS[self.predictor]
        result = unpredict(result, axis=-2, out=result)

    if squeeze:
        try:
            if result_offset is not None:
                result = np.asarray(result)[0, 0, 0]
            else:
                result.shape = self.shape
        except ValueError:
            tifffile.log_warning('TiffPage %i: failed to reshape %s to %s',
                                 self.index, result.shape, self.shape)

    if closed:
        # TODO: file should remain open if an exception occurred above
        fh.close()
    return result
Ejemplo n.º 4
0
def bin2cmap(
    binfiles: Sequence[PathLike] | str,
    shape: tuple[int, ...],
    dtype: numpy.dtype,
    offset: int = 0,
    cmapfile: PathLike | None = None,
    fail: bool = True,
    **kwargs,
) -> None:
    r"""Convert series of SimFCS BIN files to Chimera MAP file.

    SimFCS BIN files contain homogeneous data of any type and shape,
    stored C-contiguously in little endian order.
    A common format is: shape=(-1, 256, 256), dtype='uint16'.

    TODO: Support generic strides, storage order, and byteorder.

    Parameters
    ----------
    binfiles : str or sequence of str
        List of BIN file names or file pattern, e.g. '\*.bin'
    shape : tuple of 3 int
        Shape of data in BIN files in ZYX order, e.g. (32, 256, 256).
    dtype : numpy dtype
        Type of data in the BIN files, e.g. 'uint16'.
    offset : int, optional
        Number of bytes to skip at beginning of BIN file (default: 0).
    cmapfile : str, optional
        Name of the output CMAP file. If None (default), the name is
        derived from the first BIN file.
    fail : bool, optional
        If True (default), raise error when reading invalid BIN files.
    **kwargs
        Additional parameters passed to the CmapFile.addmap function,
        e.g. verbose, step, origin, cell_angles, rotation_axis,
        rotation_angle, subsample, chunks, and compression.

    """
    binfiles_list = parse_files(binfiles)
    validate_shape(shape, 3)
    shape = tuple(shape)
    dtype = numpy.dtype(dtype)
    count = product(shape)
    if count < 0:
        count = -1
    if not cmapfile:
        cmapfile = os.fspath(binfiles_list[0]) + '.cmap'
    verbose = kwargs.get('verbose', False)
    if verbose:
        print(f"Creating '{cmapfile}'", flush=True)
    with CmapFile(cmapfile, 'w') as cmap:
        for binfile in binfiles_list:
            if verbose:
                print('+', os.path.basename(binfile), end=' ', flush=True)
            try:
                with open(binfile, 'rb') as fh:
                    fh.seek(offset)
                    data = numpy.fromfile(fh, dtype=dtype, count=count)
                    data.shape = shape
                    shape = data.shape
            except Exception:
                if fail:
                    raise
                if verbose:
                    print('failed!', end='', flush=True)
                continue
            cmap.addmap(data, name=os.path.basename(binfile), **kwargs)
            if verbose:
                print(flush=True)