def _gen(): for fx, fy in pairs: x, y = imread(str(fx)), imread(str(fy)) len(axes) >= x.ndim or _raise(ValueError()) yield x, y, axes[-x.ndim:], None, fx, fy
def from_folder(basepath, source_dirs, target_dir, axes='CZYX', pattern='*.tif*'): """Get pairs of corresponding TIFF images read from folders. Two images correspond to each other if they have the same file name, but are located in different folders. Parameters ---------- basepath : str Base folder that contains sub-folders with images. source_dirs : list or tuple List of folder names relative to `basepath` that contain the source images (e.g., with low SNR). target_dir : str Folder name relative to `basepath` that contains the target images (e.g., with high SNR). axes : str Semantics of axes of loaded images (assumed to be the same for all images). pattern : str Glob-style pattern to match the desired TIFF images. Returns ------- RawData :obj:`RawData` object, whose `generator` is used to yield all matching TIFF pairs. The generator will return a tuple `(x,y,axes,mask)`, where `x` is from `source_dirs` and `y` is the corresponding image from the `target_dir`; `mask` is set to `None`. Raises ------ FileNotFoundError If an image found in a `source_dir` does not exist in `target_dir`. Example -------- >>> !tree data data ├── GT │ ├── imageA.tif │ ├── imageB.tif │ └── imageC.tif ├── source1 │ ├── imageA.tif │ └── imageB.tif └── source2 ├── imageA.tif └── imageC.tif >>> data = RawData.from_folder(basepath='data', source_dirs=['source1','source2'], target_dir='GT', axes='YX') >>> n_images = data.size >>> for source_x, target_y, axes, mask in data.generator(): ... pass """ p = Path(basepath) pairs = [(f, p / target_dir / f.name) for f in chain(*((p / source_dir).glob(pattern) for source_dir in source_dirs))] len(pairs) > 0 or _raise(FileNotFoundError("Didn't find any images.")) consume(t.exists() or _raise(FileNotFoundError(t)) for s, t in pairs) axes = axes_check_and_normalize(axes) n_images = len(pairs) description = "{p}: target='{o}', sources={s}, axes='{a}', pattern='{pt}'".format( p=basepath, s=list(source_dirs), o=target_dir, a=axes, pt=pattern) def _gen(): for fx, fy in pairs: x, y = imread(str(fx)), imread(str(fy)) len(axes) >= x.ndim or _raise(ValueError()) yield x, y, axes[-x.ndim:], None, fx, fy return RawData(_gen, n_images, description)
def _gen(): for x, y in zip(X, Y): len(axes) >= x.ndim or _raise(ValueError()) yield x, y, axes[-x.ndim:], None
def anisotropic_distortions( subsample, psf, psf_axes = None, poisson_noise = False, gauss_sigma = 0, subsample_axis = 'X', yield_target = 'source', crop_threshold = 0.2, ): """Simulate anisotropic distortions. Modify the first image (obtained from input generator) along one axis to mimic the distortions that typically occur due to low resolution along the Z axis. Note that the modified image is finally upscaled to obtain the same resolution as the unmodified input image and is yielded as the 'source' image (see :class:`RawData`). The mask from the input generator is simply passed through. The following operations are applied to the image (in order): 1. Convolution with PSF 2. Poisson noise 3. Gaussian noise 4. Subsampling along ``subsample_axis`` 5. Upsampling along ``subsample_axis`` (to former size). Parameters ---------- subsample : float Subsampling factor to mimic distortions along Z. psf : :class:`numpy.ndarray` or None Point spread function (PSF) that is supposed to mimic blurring of the microscope due to reduced axial resolution. Set to ``None`` to disable. psf_axes : str or None Axes of the PSF. If ``None``, psf axes are assumed to be the same as of the image that it is applied to. poisson_noise : bool Flag to indicate whether Poisson noise should be applied to the image. gauss_sigma : float Standard deviation of white Gaussian noise to be added to the image. subsample_axis : str Subsampling image axis (default X). yield_target : str Which image from the input generator should be yielded by the generator ('source' or 'target'). If 'source', the unmodified input/source image (from which the distorted image is computed) is yielded as the target image. If 'target', the target image from the input generator is simply passed through. crop_threshold : float The subsample factor must evenly divide the image size along the subsampling axis to prevent potential image misalignment. If this is not the case the subsample factors are modified and the raw image may be cropped along the subsampling axis up to a fraction indicated by `crop_threshold`. Returns ------- Transform Returns a :class:`Transform` object intended to be used with :func:`create_patches`. Raises ------ ValueError Various reasons. """ zoom_order = 1 (np.isscalar(subsample) and subsample >= 1) or _raise(ValueError('subsample must be >= 1')) _subsample = subsample subsample_axis = axes_check_and_normalize(subsample_axis) len(subsample_axis)==1 or _raise(ValueError()) psf is None or isinstance(psf,np.ndarray) or _raise(ValueError()) if psf_axes is not None: psf_axes = axes_check_and_normalize(psf_axes) 0 < crop_threshold < 1 or _raise(ValueError()) yield_target in ('source','target') or _raise(ValueError()) if psf is None and yield_target == 'source': warnings.warn( "It is strongly recommended to use an appropriate PSF to " "mimic the optical effects of the microscope. " "We found that training with synthesized anisotropic images " "that were created without a PSF " "can sometimes lead to unwanted artifacts in the reconstructed images." ) def _make_normalize_data(axes_in): """Move X to front of image.""" axes_in = axes_check_and_normalize(axes_in) axes_out = subsample_axis # (a in axes_in for a in 'XY') or _raise(ValueError('X and/or Y axis missing.')) # add axis in axes_in to axes_out (if it doesn't exist there) axes_out += ''.join(a for a in axes_in if a not in axes_out) def _normalize_data(data,undo=False): if undo: return move_image_axes(data, axes_out, axes_in) else: return move_image_axes(data, axes_in, axes_out) return _normalize_data def _scale_down_up(data,subsample): from scipy.ndimage.interpolation import zoom with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) factor = np.ones(data.ndim) factor[0] = subsample return zoom(zoom(data, 1/factor, order=0), factor, order=zoom_order) def _adjust_subsample(d,s,c): """length d, subsample s, tolerated crop loss fraction c""" from fractions import Fraction def crop_size(n_digits,frac): _s = round(s,n_digits) _div = frac.denominator s_multiple_max = np.floor(d/_s) s_multiple = (s_multiple_max//_div)*_div # print(n_digits, _s,_div,s_multiple) size = s_multiple * _s assert np.allclose(size,round(size)) return size def decimals(v,n_digits=None): if n_digits is not None: v = round(v,n_digits) s = str(v) assert '.' in s decimals = s[1+s.find('.'):] return int(decimals), len(decimals) s = float(s) dec, n_digits = decimals(s) frac = Fraction(dec,10**n_digits) # a multiple of s that is also an integer number must be # divisible by the denominator of the fraction that represents the decimal points # round off decimals points if needed while n_digits > 0 and (d-crop_size(n_digits,frac))/d > c: n_digits -= 1 frac = Fraction(decimals(s,n_digits)[0], 10**n_digits) size = crop_size(n_digits,frac) if size == 0 or (d-size)/d > c: raise ValueError("subsample factor %g too large (crop_threshold=%g)" % (s,c)) return round(s,n_digits), int(round(crop_size(n_digits,frac))) def _make_divisible_by_subsample(x,size): def _split_slice(v): return slice(None) if v==0 else slice(v//2,-(v-v//2)) slices = [slice(None) for _ in x.shape] slices[0] = _split_slice(x.shape[0]-size) return x[slices] def _generator(inputs): for img,y,axes,mask in inputs: if yield_target == 'source': y is None or np.allclose(img,y) or warnings.warn("ignoring 'target' image from input generator") target = img else: target = y img.shape == target.shape or _raise(ValueError()) axes = axes_check_and_normalize(axes) _normalize_data = _make_normalize_data(axes) # print(axes, img.shape) x = img.astype(np.float32, copy=False) if psf is not None: from scipy.signal import fftconvolve # print("blurring with psf") _psf = psf.astype(np.float32,copy=False) np.min(_psf) >= 0 or _raise(ValueError('psf has negative values.')) _psf /= np.sum(_psf) if psf_axes is not None: _psf = move_image_axes(_psf, psf_axes, axes, True) x.ndim == _psf.ndim or _raise(ValueError('image and psf must have the same number of dimensions.')) if 'C' in axes: ch = axes_dict(axes)['C'] n_channels = x.shape[ch] # convolve with psf separately for every channel if _psf.shape[ch] == 1: warnings.warn('applying same psf to every channel of the image.') if _psf.shape[ch] in (1,n_channels): x = np.stack([ fftconvolve( np.take(x, i,axis=ch), np.take(_psf,i,axis=ch,mode='clip'), mode='same' ) for i in range(n_channels) ],axis=ch) else: raise ValueError('number of psf channels (%d) incompatible with number of image channels (%d).' % (_psf.shape[ch],n_channels)) else: x = fftconvolve(x, _psf, mode='same') if bool(poisson_noise): # print("apply poisson noise") x = np.random.poisson(np.maximum(0,x).astype(np.int)).astype(np.float32) if gauss_sigma > 0: # print("adding gaussian noise with sigma = ", gauss_sigma) noise = np.random.normal(0,gauss_sigma,size=x.shape).astype(np.float32) x = np.maximum(0,x+noise) if _subsample != 1: # print("down and upsampling X by factor %s" % str(_subsample)) target = _normalize_data(target) x = _normalize_data(x) subsample, subsample_size = _adjust_subsample(x.shape[0],_subsample,crop_threshold) # print(subsample, subsample_size) if _subsample != subsample: warnings.warn('changing subsample from %s to %s' % (str(_subsample),str(subsample))) target = _make_divisible_by_subsample(target,subsample_size) x = _make_divisible_by_subsample(x, subsample_size) x = _scale_down_up(x,subsample) assert x.shape == target.shape, (x.shape, target.shape) target = _normalize_data(target,undo=True) x = _normalize_data(x, undo=True) yield x, target, axes, mask return Transform('Anisotropic distortion (along %s axis)' % subsample_axis, _generator, 1)
def _generator(inputs): for x, y, axes, mask in inputs: axes = axes_check_and_normalize(axes) len(axes) == len(slices) or _raise(ValueError()) yield x[slices], y[slices], axes, (mask[slices] if mask is not None else None)
def _generator(inputs): for img,y,axes,mask in inputs: if yield_target == 'source': y is None or np.allclose(img,y) or warnings.warn("ignoring 'target' image from input generator") target = img else: target = y img.shape == target.shape or _raise(ValueError()) axes = axes_check_and_normalize(axes) _normalize_data = _make_normalize_data(axes) # print(axes, img.shape) x = img.astype(np.float32, copy=False) if psf is not None: from scipy.signal import fftconvolve # print("blurring with psf") _psf = psf.astype(np.float32,copy=False) np.min(_psf) >= 0 or _raise(ValueError('psf has negative values.')) _psf /= np.sum(_psf) if psf_axes is not None: _psf = move_image_axes(_psf, psf_axes, axes, True) x.ndim == _psf.ndim or _raise(ValueError('image and psf must have the same number of dimensions.')) if 'C' in axes: ch = axes_dict(axes)['C'] n_channels = x.shape[ch] # convolve with psf separately for every channel if _psf.shape[ch] == 1: warnings.warn('applying same psf to every channel of the image.') if _psf.shape[ch] in (1,n_channels): x = np.stack([ fftconvolve( np.take(x, i,axis=ch), np.take(_psf,i,axis=ch,mode='clip'), mode='same' ) for i in range(n_channels) ],axis=ch) else: raise ValueError('number of psf channels (%d) incompatible with number of image channels (%d).' % (_psf.shape[ch],n_channels)) else: x = fftconvolve(x, _psf, mode='same') if bool(poisson_noise): # print("apply poisson noise") x = np.random.poisson(np.maximum(0,x).astype(np.int)).astype(np.float32) if gauss_sigma > 0: # print("adding gaussian noise with sigma = ", gauss_sigma) noise = np.random.normal(0,gauss_sigma,size=x.shape).astype(np.float32) x = np.maximum(0,x+noise) if _subsample != 1: # print("down and upsampling X by factor %s" % str(_subsample)) target = _normalize_data(target) x = _normalize_data(x) subsample, subsample_size = _adjust_subsample(x.shape[0],_subsample,crop_threshold) # print(subsample, subsample_size) if _subsample != subsample: warnings.warn('changing subsample from %s to %s' % (str(_subsample),str(subsample))) target = _make_divisible_by_subsample(target,subsample_size) x = _make_divisible_by_subsample(x, subsample_size) x = _scale_down_up(x,subsample) assert x.shape == target.shape, (x.shape, target.shape) target = _normalize_data(target,undo=True) x = _normalize_data(x, undo=True) yield x, target, axes, mask