Source code for fissa.readimagejrois

'''
Based on code originally written by Luis Pedro Coelho <luis@luispedro.org>,
2012, available at https://gist.github.com/luispedro/3437255, distributed
under the MIT License.

Modified
    - 2014 by Jeffrey Zaremba (@jzaremba), https://github.com/losonczylab/sima
    - 2015 by Scott Lowe (@scottclowe) and Sander Keemink (@swkeemink).
'''

from __future__ import division
from __future__ import unicode_literals

from builtins import str
from builtins import range

from itertools import product

import numpy as np
from skimage.draw import ellipse
import zipfile



[docs]def read_imagej_roi_zip(filename): """Reads an ImageJ ROI zip set and parses each ROI individually Parameters ---------- filename : str Path to the ImageJ ROis zip file Returns ------- roi_list : list List of the parsed ImageJ ROIs """ roi_list = [] with zipfile.ZipFile(filename) as zf: for name in zf.namelist(): roi = read_roi(zf.open(name)) if roi is None: continue roi['label'] = str(name).rstrip('.roi') roi_list.append(roi) return roi_list
[docs]def read_roi(roi_obj): """Parses an individual ImageJ ROI This is based on the Java implementation: http://rsbweb.nih.gov/ij/developer/source/ij/io/RoiDecoder.java.html Parameters ---------- roi_obj : file object File object containing a single ImageJ ROI Returns ------- dict Returns a parsed ROI object, a dictionary with either a `'polygons'` or a `'mask'` field. Raises ------ IOError If there is an error reading the roi file object ValueError If unable to parse ROI """ # Note: # _getX() calls with no assignment are present to move our pointer # within the imageJ roi file through bytes that we do not currently use. # In line comments indicate what they are; these could be extracted if # needed in the future. sub_pixel_resolution = 128 # Other options that are not currently used # SPLINE_FIT = 1 # DOUBLE_HEADED = 2 # OUTLINE = 4 # OVERLAY_LABELS = 8 # OVERLAY_NAMES = 16 # OVERLAY_BACKGROUNDS = 32 # OVERLAY_BOLD = 64 # DRAW_OFFSET = 256 pos = [4] def _get8(): """Read 1 byte from the roi file object""" pos[0] += 1 s = roi_obj.read(1) if not s: raise IOError('read_imagej_roi: Unexpected EOF') return ord(s) def _get16(): """Read 2 bytes from the roi file object""" b0 = _get8() b1 = _get8() return (b0 << 8) | b1 def _get16signed(): """Read a signed 16 bit integer from 2 bytes from roi file object""" b0 = _get8() b1 = _get8() out = (b0 << 8) | b1 # This is a signed integer, so need to check if the value is # positive or negative. if b0 > 127: out = out - 65536 return out def _get32(): """Read 4 bytes from the roi file object""" s0 = _get16() s1 = _get16() return (s0 << 16) | s1 def _getfloat(): """Read a float from the roi file object""" v = np.int32(_get32()) return v.view(np.float32) def _getcoords(z=0): """Get the next coordinate of an roi polygon""" if options & sub_pixel_resolution: getc = _getfloat points = np.empty((n_coordinates, 3), dtype=np.float32) else: getc = _get16 points = np.empty((n_coordinates, 3), dtype=np.int16) points[:, 0] = [getc() for _ in range(n_coordinates)] points[:, 1] = [getc() for _ in range(n_coordinates)] points[:, 0] += left points[:, 1] += top points[:, 2] = z return points magic = roi_obj.read(4) if magic != b'Iout': raise IOError('read_imagej_roi: Magic number not found') _get16() # version roi_type = _get8() # Discard extra second Byte: _get8() if not (0 <= roi_type < 11): raise ValueError('read_imagej_roi: \ ROI type {} not supported'.format(roi_type)) top = _get16signed() left = _get16signed() bottom = _get16signed() right = _get16signed() n_coordinates = _get16() x1 = _getfloat() # x1 y1 = _getfloat() # y1 x2 = _getfloat() # x2 y2 = _getfloat() # y2 _get16() # stroke width _get32() # shape roi size _get32() # stroke color _get32() # fill color subtype = _get16() if subtype != 0 and subtype != 3: raise ValueError('read_imagej_roi: \ ROI subtype {} not supported (!= 0)'.format(subtype)) options = _get16() if subtype == 3 and roi_type == 7: # ellipse aspect ratio aspect_ratio = _getfloat() else: _get8() # arrow style _get8() # arrow head size _get16() # rectangle arc size z = _get32() # position if z > 0: z -= 1 # Multi-plane images start indexing at 1 instead of 0 _get32() # header 2 offset if roi_type == 0: # Polygon coords = _getcoords(z) coords = coords.astype('float') return {'polygons': coords} elif roi_type == 1: # Rectangle coords = [[left, top, z], [right, top, z], [right, bottom, z], [left, bottom, z]] coords = np.array(coords).astype('float') return {'polygons': coords} elif roi_type == 2: # Oval width = right - left height = bottom - top # 0.5 moves the mid point to the center of the pixel x_mid = (right + left) / 2.0 - 0.5 y_mid = (top + bottom) / 2.0 - 0.5 mask = np.zeros((z + 1, right, bottom), dtype=bool) for y, x in product(np.arange(top, bottom), np.arange(left, right)): mask[z, x, y] = ((x - x_mid) ** 2 / (width / 2.0) ** 2 + (y - y_mid) ** 2 / (height / 2.0) ** 2 <= 1) return {'mask': mask} elif roi_type == 7: if subtype == 3: # ellipse mask = np.zeros((1, right+10, bottom+10), dtype=bool) r_radius = np.sqrt((x2-x1)**2+(y2-y1)**2)/2.0 c_radius = r_radius*aspect_ratio r = (x1+x2)/2-0.5 c = (y1+y2)/2-0.5 shpe = mask.shape orientation = np.arctan2(y2-y1, x2-x1) X, Y = ellipse(r, c, r_radius, c_radius, shpe[1:], orientation) mask[0, X, Y] = True return {'mask': mask} else: # Freehand coords = _getcoords(z) coords = coords.astype('float') return {'polygons': coords} else: try: coords = _getcoords(z) coords = coords.astype('float') return {'polygons': coords} except BaseException: raise ValueError( 'read_imagej_roi: ROI type {} not supported'.format(roi_type))