Source code for paprica.loader

"""
Submodule containing classes and functions relative to data **loading**.

Usually, tileLoader objects are instantiated directly by iterating over the parser. Alternatively, they can be
instantiated directly by the constructor by calling *tile = paprica.loader.tileLoader()*.

By using this code you agree to the terms of the software license agreement.

© Copyright 2020 Wyss Center for Bio and Neuro Engineering – All rights reserved
"""

import os
import shutil
from glob import glob

import matplotlib.pyplot as plt
import numpy as np
import pyapr
from skimage.io import imread
from tqdm import tqdm

import paprica


[docs]def tile_from_apr(apr, parts): """ Function to generate a *tile* object from an APR object. Parameters ---------- apr: pyapr.APR APR to generate a tile from parts: pyapr.ParticleData ParticleData to generate a tile from Returns ------- tile: tileLoader *tile* containing the given APR """ tile = tileLoader(path=None, row=None, col=None, ftype='apr', neighbors=None, neighbors_tot=None, neighbors_path=None, frame_size=2048, folder_root=None, channel=None) tile.apr = apr tile.parts = parts return tile
[docs]def tile_from_path(path): """ Function to generate a *tile* object from the path of an APR object. Parameters ---------- path: string path to the stored APR object Returns ------- tile: tileLoader *tile* containing the given APR """ return tileLoader(path=path, row=1, col=1, ftype='apr', neighbors=None, neighbors_tot=None, neighbors_path=None, frame_size=2048, folder_root=os.path.basename(path), channel=None)
[docs]class tileLoader(): """ Class to load each tile, neighboring tiles, segmentation and neighboring segmentation. """
[docs] def __init__(self, path, row, col, ftype, neighbors, neighbors_tot, neighbors_path, frame_size, folder_root, channel): """ Constructor of tileLoader object. Parameters ---------- path: string path to the tile (APR and tiff3D) or the folder containing the frames (tiff2D) row: int vertical position of the tile (for multi-tile acquisition) col: int horizontal position of the tile (for multi-tile acquisition) ftype: str tile file type ('apr', 'tiff3D', 'colm', 'clearscope') neighbors: list neighbors list containing the neighbors position [row, col] of only the EAST and SOUTH neighbors to avoid the redundancy computation when stitching. For example, below the tile [0, 1] is represented by an 'o' while other tile are represented by an 'x':: x --- o --- x --- x | | | | x --- x --- x --- x | | | | x --- x --- x --- x | | | | x --- x --- x --- x in this case neighbors = [[0, 2], [1, 1]] neighbors_tot: list neighbors list containing all the neighbors position [row, col]. For example, below the tile [0, 1] is represented by an 'o' while other tile are represented by an 'x':: x --- o --- x --- x | | | | x --- x --- x --- x | | | | x --- x --- x --- x | | | | x --- x --- x --- x in this case neighbors_tot = [[0, 0], [0, 2], [1, 1]] neighbors_path: list path of the neighbors whose coordinates are stored in neighbors frame_size: int camera frame size (only square sensors are supported for now). folder_root: str root folder where everything should be saved. channel: int fluorescence channel for multi-channel acquisition. This is used to load the right data in the case of COLM acquisition where all the channel are saved in the same folder as tiff2D. """ self.path = path self.row = row self.col = col self.type = ftype self.neighbors = neighbors self.neighbors_tot = neighbors_tot self.neighbors_path = neighbors_path self.frame_size = frame_size self.folder_root = folder_root self.channel = channel self.is_loaded = False # Initialize attributes to load tile data self.data = None # Pixel data self.apr = None # APR tree self.parts = None # Particles self.parts_cc = None # Connected component self.lazy_data = None # Lazy reconstructed data # Initialize attributes to load neighbors data self.data_neighbors = None self.apr_neighbors = None self.parts_neighbors = None self.parts_cc_neighbors = None
[docs] def load_tile(self): """ Load the current tile if not already loaded. Returns ------- None """ if self.type == 'apr': if self.apr is None: self.apr, self.parts = self._load_data(self.path) else: if self.data is None: self.data = self._load_data(self.path) self.is_loaded = True
[docs] def lazy_load_tile(self, level_delta=0): """ Load the tile lazily at the given resolution. Parameters ---------- level_delta: int parameter controlling the resolution at which the APR will be read lazily Returns ------- None """ if self.type != 'apr': raise TypeError('Error: lazy loading is only supported for APR data.') self.lazy_data = pyapr.reconstruction.LazySlicer(self.path, level_delta=level_delta, parts_name='particles', tree_parts_name='particles') self.is_loaded = True
[docs] def load_neighbors(self): """ Load the current tile neighbors if not already loaded. Returns ------- None """ if self.data_neighbors is None: if self.type == 'apr': aprs = [] partss = [] for path_neighbor in self.neighbors_path: apr, parts = self._load_data(path_neighbor) aprs.append(apr) partss.append(parts) self.apr_neighbors = aprs self.parts_neighbors = partss else: u = [] for path_neighbor in self.neighbors_path: u.append(self._load_data(path_neighbor)) self.data_neighbors = u else: print('Tile neighbors already loaded.')
[docs] def load_segmentation(self, load_tree=False): """ Load the current tile connected component (cc) if not already loaded. Returns ------- None """ if self.parts_cc is None: self.parts_cc = pyapr.io.read_particles(self.path, parts_name='segmentation cc') if load_tree: self.apr = pyapr.io.read_apr(self.path) else: print('Tile cc already loaded.')
[docs] def lazy_load_segmentation(self, level_delta=0): """ Load the parts_cc lazily at the given resolution. Parameters ---------- level_delta: int parameter controlling the resolution at which the APR will be read lazily Returns ------- None """ if self.type != 'apr': raise TypeError('Error: lazy loading is only supported for APR data.') self.lazy_segmentation = pyapr.reconstruction.LazySlicer(self.path, level_delta=level_delta, parts_name='segmentation cc', tree_parts_name='segmentation cc')
[docs] def load_neighbors_segmentation(self, load_tree=False): """ Load the current tile neighbors connected component (cc) if not already loaded. Returns ------- None """ if self.data_neighbors is None: if self.type == 'apr': aprs = [] ccs = [] for i, path_neighbor in enumerate(self.neighbors_path): if not load_tree: apr = self.apr_neighbors[i] cc = pyapr.LongParticles() aprfile = pyapr.io.APRFile() aprfile.set_read_write_tree(True) aprfile.open(path_neighbor, 'READ') if load_tree: apr = pyapr.APR() aprfile.read_apr(apr, t=0, channel_name='t') aprfile.read_particles(apr, 'segmentation cc', cc, t=0) aprfile.close() aprs.append(apr) ccs.append(cc) self.apr_neighbors = aprs self.parts_neighbors = ccs else: u = [] for path_neighbor in self.neighbors_path: u.append(self._load_data(path_neighbor)) self.data_neighbors = u else: print('Tile neighbors already loaded.')
[docs] def view_tile(self, **kwargs): """ Display tile using napari. Returns ------- None """ if self.apr is None: self.load_tile() paprica.viewer.display_apr(self.apr, self.parts, **kwargs)
[docs] def plot_particles_size_distribution(self): """ Plot the particle size distribution of the tile. Returns ------- None """ if self.type != 'apr': raise TypeError('Error: particles distributoin can only be computed for APR.') if not self.is_loaded: self.load_tile() it = self.apr.iterator() nparts = [] for level in range(self.apr.level_min(), self.apr.level_max()): nparts.append(it.total_number_particles(level + 1) - it.total_number_particles(level)) x = np.array([2 ** x for x in range(self.apr.level_max() - self.apr.level_min() - 1, -1, -1)]) plt.figure() plt.bar(x[2:], nparts[2:] / np.sum(nparts), width=np.diff(x[:-1]) / 10, log=True, align="center") plt.xscale('log') plt.xlabel('Particle size [pixel]', fontsize=14) plt.ylabel('Normalized particle distribution', fontsize=14) plt.xticks(x[2:], x[2:]) plt.rc('xtick', labelsize=12) plt.rc('ytick', labelsize=12) plt.title('CR = {:0.2f}'.format(self.apr.computational_ratio())) plt.tight_layout()
def _compute_segmentation_cc_tree_particles(self): # Load APR file self.apr, self.parts = self._load_data(self.path) # Compute tree particles apr, parts = pyapr.io.read(self.path, parts_name='segmentation cc') tree_parts = pyapr.tree.fill_tree_max(apr, parts) # Save back data pyapr.io.write_particles(self.path, tree_parts, t=0, channel_name='t', parts_name='segmentation cc', tree=True, append=True)
[docs] def _load_data(self, path): """ Load data at given path. Parameters ---------- path: string path to the data to be loaded. Returns ------- u: array_like numpy array containing the data. """ if self.type == 'colm': u = self._load_colm(path) elif self.type == 'clearscope': u = self._load_clearscope(path) elif self.type == 'tiff3D': u = imread(path) elif self.type == 'apr': apr = pyapr.APR() parts = pyapr.ShortParticles() pyapr.io.read(path, apr, parts) u = (apr, parts) elif self.type == 'raw': u = self._load_raw(path) else: raise TypeError('Error: image type {} not supported.'.format(self.type)) return u
[docs] def _load_raw(self, path): """ Load raw data at given path. Parameters ---------- path: string path to the data to be loaded. Returns ------- u: array_like numpy array containing the data. """ u = np.fromfile(path, dtype='uint16', count=-1) return u.reshape((-1, self.frame_size, self.frame_size))
[docs] def _load_colm(self, path): """ Load a sequence of images in a folder and return it as a 3D array. Parameters ---------- path: string path to folder where the data should be loaded. Returns ------- v: array_like numpy array containing the data. """ files_sorted = sorted(glob(os.path.join(path, '*CHN0' + str(self.channel) + '_*tif'))) n_files = len(files_sorted) v = np.empty((n_files, self.frame_size, self.frame_size), dtype='uint16') for i, f in enumerate(tqdm(files_sorted, desc='Loading sequence', leave=False)): v[i] = imread(f) return v
[docs] def _load_clearscope(self, path): """ Load a sequence of images in a folder and return it as a 3D array. Parameters ---------- path: string path to folder where the data should be loaded. Returns ------- v: array_like numpy array containing the data. """ files_sorted = sorted(glob(os.path.join(path, '*'))) n_files = len(files_sorted) v = np.empty((n_files, self.frame_size, self.frame_size), dtype='uint16') for i, f in enumerate(tqdm(files_sorted, desc='Loading sequence', leave=False)): v[i] = imread(f) return v
# def _load_mesospim(self, path): # """ # Load MESOSPIM data and return it as a 3D array. # # Parameters # ---------- # path: string # path to folder where the data should be loaded. # # Returns # ------- # v: array_like # numpy array containing the data. # """ # if path.endswith('.raw'): # return self._load_raw(path) # elif path.endswith('.tiff') or path.endswith('.tif'): # return imread(path)
[docs] def _erase_from_disk(self): """ Delete tile from disk, use with caution! Returns ------- None """ if self.type == 'apr': os.remove(self.path) else: shutil.rmtree(self.path)