Custom Readers ============== You can define your own PIMS reader with a minimal amount of customized code. Components ---------- By subclassing ``FramesSequence``, you get PIMS's lazy-loading and slicing behavior for free. You have to provide: * a method for reading a single frame into a numpy array * a method for knowing the length of the sequence * properties giving the shape and the numpy data type of each frame Basic Example ------------- .. code-block:: python from pims import FramesSequence, Frame class MyReader(FramesSequence): def __init__(self, filename): self.filename = filename self._len = # however many frames there will be self._dtype = # the numpy datatype of the frames self._frame_shape = # the shape, like (512, 512), of an # individual frame -- maybe get this by # opening the first frame # Do whatever setup you need to do to be able to quickly access # individual frames later. def get_frame(self, i): # Access the data you need and get it into a numpy array. # Then return a Frame like so: return Frame(my_numpy_array, frame_no=i) def __len__(self): return self._len @property def frame_shape(self): return self._frame_shape @property def pixel_type(self): return self._dtype The ``__init__`` method is completely customizable. It can take whatever arguments make sense for your file format. Optionally, you might also wish to customize the ``__repr__`` and define a ``close`` method, which the base class calls when exiting a context manager. Plugging into PIMS's open function ---------------------------------- The function ``pims.open`` dispatches to a PIMS reader based on the file extension. To associate your reader with particle file extensions, add a ``class_exts`` class method. For example, the following code will invoke ``MyReader`` to open files ending in :file:`.xyz`. .. code-block:: python class MyReader(FramesSequence): ... @classmethod def class_exts(cls): return {'xyz'} | super(MyReader, cls).class_exts() New readers can be defined or imported at any time. They will be detected the next time ``pims.open`` is called, as it searches all subclasses of ``FramesSequence`` for an eligible reader. To prioritize readers, a field ``class_priority`` can optionally be given to the reader. A higher priority will be chosen over a lower priority. Default for all readers is 10. Example Demonstrating Generality of PIMS Design ----------------------------------------------- Here is a reader that extracts tiles from a tiled image or "sprite sheet." .. code-block:: python class SpriteSheet(FramesSequence): """ This is a class for providing an easy interface into a sprite sheet of uniformly sized images. Parameters ---------- sheet : ndarray The sprite sheet. It should consist of N paneled images. In this version all possible positions have an image, this may be changed to limit the number of acessable images in the sheet to be less than the possible number. rows : int cols : int The number of rows and columns of sprites. The sprite size is computed from these + the shape of the sheet. """ def __init__(self, sheet, rows, cols): self._sheet = sheet sheet_height, sheet_width = sheet.shape if sheet_width % cols != 0: raise ValueError("Sheet width not evenly divisible by cols") if sheet_height % rows != 0: raise ValueError("Sheet height not evenly divisible by rows") self._sheet_shape = (rows, cols) self._im_sz = sheet_height // rows, sheet_width // cols self._sprite_height, self._sprite_width = self._im_sz self._dtype = sheet.dtype @property def pixel_type(self): return self._dtype @property def frame_shape(self): return self._im_sz def __len__(self): return np.prod(self._sheet_shape) def get_frame(self, n): r, c = np.unravel_index(n, self._sheet_shape) slc_r = slice(r*self._sprite_height, (r+1)*self._sprite_height) slc_c = slice(c*self._sprite_width, (c+1)*self._sprite_width) tmp = self._sheet[slc_r, slc_c] return Frame(self.process_func(tmp), frame_no=n)