"""
module to ImageJ roi files
"""
import os
import struct
import zipfile
import logging
__all__ = ['read_roi_file', 'read_roi_zip']
OFFSET = dict(VERSION_OFFSET=4,
TYPE=6,
TOP=8,
LEFT=10,
BOTTOM=12,
RIGHT=14,
N_COORDINATES=16,
X1=18,
Y1=22,
X2=26,
Y2=30,
XD=18,
YD=22,
WIDTHD=26,
HEIGHTD=30,
STROKE_WIDTH=34,
SHAPE_ROI_SIZE=36,
STROKE_COLOR=40,
FILL_COLOR=44,
SUBTYPE=48,
OPTIONS=50,
ARROW_STYLE=52,
ELLIPSE_ASPECT_RATIO=52,
ARROW_HEAD_SIZE=53,
ROUNDED_RECT_ARC_SIZE=54,
POSITION=56,
HEADER2_OFFSET=60,
COORDINATES=64)
ROI_TYPE = dict(polygon=0,
rect=1,
oval=2,
line=3,
freeline=4,
polyline=5,
noRoi=6,
freehand=7,
traced=8,
angle=9,
point=10)
OPTIONS = dict(SPLINE_FIT=1,
DOUBLE_HEADED=2,
OUTLINE=4,
OVERLAY_LABELS=8,
OVERLAY_NAMES=16,
OVERLAY_BACKGROUNDS=32,
OVERLAY_BOLD=64,
SUB_PIXEL_RESOLUTION=128,
DRAW_OFFSET=256)
HEADER_OFFSET = dict(C_POSITION=4,
Z_POSITION=8,
T_POSITION=12,
NAME_OFFSET=16,
NAME_LENGTH=20,
OVERLAY_LABEL_COLOR=24,
OVERLAY_FONT_SIZE=28,
AVAILABLE_BYTE1=30,
IMAGE_OPACITY=31,
IMAGE_SIZE=32,
FLOAT_STROKE_WIDTH=36)
SUBTYPES = dict(TEXT=1,
ARROW=2,
ELLIPSE=3,
IMAGE=4)
def get_byte(data, base):
if isinstance(base, int):
return data[base]
elif isinstance(base, list):
return [data[b] for b in base]
def get_short(data, base):
b0 = data[base]
b1 = data[base + 1]
n = (b0 << 8) + b1
return n
def get_int(data, base):
b0 = data[base]
b1 = data[base + 1]
b2 = data[base + 2]
b3 = data[base + 3]
n = ((b0 << 24) + (b1 << 16) + (b2 << 8) + b3)
return n
def get_float(data, base):
s = struct.pack('I', get_int(data, base))
return struct.unpack('f', s)[0]
[docs]def read_roi_file(fpath):
"""
read .roi from ImageJ
:param fpath: path to roi file
:type fpath: str
:return: content of roi file
:rtype: dict
"""
if isinstance(fpath, zipfile.ZipExtFile):
data = fpath.read()
name = os.path.splitext(os.path.basename(fpath.name))[0]
elif isinstance(fpath, str):
fp = open(fpath, 'rb')
data = fp.read()
fp.close()
name = os.path.splitext(os.path.basename(fpath))[0]
else:
logging.error("Can't read {}".format(fpath))
return None
logging.debug("Read ROI for \"{}\"".format(name))
size = len(data)
code = '>'
roi = {}
magic = get_byte(data, list(range(4)))
magic = "".join([chr(c) for c in magic])
# TODO: raise error if magic != 'Iout'
version = get_short(data, OFFSET['VERSION_OFFSET'])
roi_type = get_byte(data, OFFSET['TYPE'])
subtype = get_short(data, OFFSET['SUBTYPE'])
top = get_short(data, OFFSET['TOP'])
left = get_short(data, OFFSET['LEFT'])
if top > 6000:
top -= 2**16
if left > 6000:
left -= 2**16
bottom = get_short(data, OFFSET['BOTTOM'])
right = get_short(data, OFFSET['RIGHT'])
width = right - left
height = bottom - top
n_coordinates = get_short(data, OFFSET['N_COORDINATES'])
options = get_short(data, OFFSET['OPTIONS'])
position = get_int(data, OFFSET['POSITION'])
hdr2Offset = get_int(data, OFFSET['HEADER2_OFFSET'])
logging.debug("n_coordinates: {}".format(n_coordinates))
logging.debug("position: {}".format(position))
logging.debug("options: {}".format(options))
sub_pixel_resolution = (options == OPTIONS['SUB_PIXEL_RESOLUTION']) and version >= 222
draw_offset = sub_pixel_resolution and (options == OPTIONS['DRAW_OFFSET'])
sub_pixel_rect = version >= 223 and sub_pixel_resolution and (
roi_type == ROI_TYPE['rect'] or roi_type == ROI_TYPE['oval'])
logging.debug("sub_pixel_resolution: {}".format(sub_pixel_resolution))
logging.debug("draw_offset: {}".format(draw_offset))
logging.debug("sub_pixel_rect: {}".format(sub_pixel_rect))
# Untested
if sub_pixel_rect:
xd = getFloat(data, OFFSET['XD'])
yd = getFloat(data, OFFSET['YD'])
widthd = getFloat(data, OFFSET['WIDTHD'])
heightd = getFloat(data, OFFSET['HEIGHTD'])
logging.debug("Entering in sub_pixel_rect")
# Untested
if hdr2Offset > 0 and hdr2Offset + HEADER_OFFSET['IMAGE_SIZE'] + 4 <= size:
channel = get_int(data, hdr2Offset + HEADER_OFFSET['C_POSITION'])
slice = get_int(data, hdr2Offset + HEADER_OFFSET['Z_POSITION'])
frame = get_int(data, hdr2Offset + HEADER_OFFSET['T_POSITION'])
overlayLabelColor = get_int(data, hdr2Offset + HEADER_OFFSET['OVERLAY_LABEL_COLOR'])
overlayFontSize = get_short(data, hdr2Offset + HEADER_OFFSET['OVERLAY_FONT_SIZE'])
imageOpacity = get_byte(data, hdr2Offset + HEADER_OFFSET['IMAGE_OPACITY'])
imageSize = get_int(data, hdr2Offset + HEADER_OFFSET['IMAGE_SIZE'])
logging.debug("Entering in hdr2Offset")
is_composite = get_int(data, OFFSET['SHAPE_ROI_SIZE']) > 0
# Not implemented
if is_composite:
if version >= 218:
pass
if channel > 0 or slice > 0 or frame > 0:
pass
if roi_type == ROI_TYPE['rect']:
roi = {'type': 'rectangle'}
if sub_pixel_rect:
roi.update(dict(left=xd, top=yd, width=widthd, height=heightd))
else:
roi.update(dict(left=left, top=top, width=width, height=height))
roi['arc_size'] = get_short(data, OFFSET['ROUNDED_RECT_ARC_SIZE'])
elif roi_type == ROI_TYPE['oval']:
roi = {'type': 'oval'}
if sub_pixel_rect:
roi.update(dict(left=xd, top=yd, width=widthd, height=heightd))
else:
roi.update(dict(left=left, top=top, width=width, height=height))
elif roi_type == ROI_TYPE['line']:
roi = {'type': 'line'}
x1 = get_float(data, OFFSET['X1'])
y1 = get_float(data, OFFSET['Y1'])
x2 = get_float(data, OFFSET['X2'])
y2 = get_float(data, OFFSET['Y2'])
if subtype == SUBTYPES['ARROW']:
# Not implemented
pass
else:
roi.update(dict(x1=x1, x2=x2, y1=y1, y2=y2))
roi['draw_offset'] = draw_offset
elif roi_type in [ROI_TYPE[t] for t in ["polygon", "freehand", "traced", "polyline", "freeline", "angle", "point"]]:
x = []
y = []
base1 = OFFSET['COORDINATES']
base2 = base1 + 2 * n_coordinates
for i in range(n_coordinates):
xtmp = get_short(data, base1 + i * 2)
ytmp = get_short(data, base2 + i * 2)
x.append(left + xtmp)
y.append(top + ytmp)
if sub_pixel_resolution:
xf = []
yf = []
base1 = OFFSET['COORDINATES'] + 4 * n_coordinates
base2 = base1 + 4 * n_coordinates
for i in range(n_coordinates):
xf.append(get_float(data, base1 + i * 4))
yf.append(get_float(data, base2 + i * 4))
if roi_type == ROI_TYPE['point']:
roi = {'type': 'point'}
if sub_pixel_resolution:
roi.update(dict(x=xf, y=yf, n=n_coordinates))
else:
roi.update(dict(x=x, y=y, n=n_coordinates))
if roi_type == ROI_TYPE['polygon']:
roi = {'type': 'polygon'}
elif roi_type == ROI_TYPE['freehand']:
roi = {'type': 'freehand'}
if subtype == SUBTYPES['ELLIPSE']:
ex1 = get_float(data, OFFSET['X1'])
ey1 = get_float(data, OFFSET['Y1'])
ex2 = get_float(data, OFFSET['X2'])
ey2 = get_float(data, OFFSET['Y2'])
roi['aspect_ratio'] = get_float(
data, OFFSET['ELLIPSE_ASPECT_RATIO'])
roi.update(dict(ex1=ex1, ey1=ey1, ex2=ex2, ey2=ey2))
elif roi_type == ROI_TYPE['traced']:
roi = {'type': 'traced'}
elif roi_type == ROI_TYPE['polyline']:
roi = {'type': 'polyline'}
elif roi_type == ROI_TYPE['freeline']:
roi = {'type': 'freeline'}
elif roi_type == ROI_TYPE['angle']:
roi = {'type': 'angle'}
else:
roi = {'type': 'freeroi'}
if sub_pixel_resolution:
roi.update(dict(x=xf, y=yf, n=n_coordinates))
#roi.update(dict(x=x, y=y, n=n_coordinates))
else:
roi.update(dict(x=x, y=y, n=n_coordinates))
else:
# TODO: raise an error for 'Unrecognized ROI type'
pass
roi['name'] = name
if version >= 218:
# Not implemented
# Read stroke width, stroke color and fill color
pass
if version >= 218 and subtype == SUBTYPES['TEXT']:
# Not implemented
# Read test ROI
pass
if version >= 218 and subtype == SUBTYPES['IMAGE']:
# Not implemented
# Get image ROI
pass
roi['position'] = position
if channel > 0 or slice > 0 or frame > 0:
roi['position'] = dict(channel=channel, slice=slice, frame=frame)
return {name: roi}
[docs]def read_roi_zip(zippath):
"""
read zip file(contain .roi from ImageJ)
wrapper for read_roi_file
:param zippath: path to zip file
:type zippath: str
:return: content of zip file
:rtype: OrderedDict
"""
import errno,os
if not os.path.exists(zippath):
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), zippath)
from collections import OrderedDict
rois = OrderedDict()
zf = zipfile.ZipFile(zippath)
for n in zf.namelist():
rois.update(read_roi_file(zf.open(n)))
return rois