Source code for pyfibre.addons.shg_pl_trans.shg_reader
import json
import logging
import os
import numpy as np
from skimage.util import img_as_float
from skimage.external.tifffile import TiffFile
from pyfibre.core.base_multi_image_reader import (
BaseMultiImageReader)
from .shg_image import SHGImage
from .shg_pl_trans_parser import SHGPLTransFileSet
logger = logging.getLogger(__name__)
[docs]def lookup_page(tiff_page):
"""Obtain relevant information from a TiffPage object"""
xy_dim = (tiff_page.image_width, tiff_page.image_length)
description = tiff_page.image_description.decode('utf-8')
return xy_dim, description
[docs]def get_fluoview_param(description, xy_dim, shape):
desc_list = description.split('\n')
channel_lines = [
line.strip() for line in desc_list if 'Gamma' in line]
n_modes = len(channel_lines)
# If number of modes is not in image shape (typically
# because the image only contains one mode)
if shape.count(n_modes) == 0:
if n_modes == 1 and len(shape) == 2:
minor_axis = None
elif n_modes == 1 and len(shape) == 3:
minor_axis = np.argmin(shape)
else:
raise IndexError(
f"Image shape {shape} not supported")
return minor_axis, n_modes, xy_dim
# If there is an exact match in the image shape, identify
# this as the axis containing each mode
if shape.count(n_modes) == 1:
major_axis = shape.index(n_modes)
# If multiple image dimensions share the same number of
# elements as number of modes, identify which corresponds
# to each mode
else:
if shape[0] == n_modes:
major_axis = 0
else:
raise IndexError(
f"Image shape {shape} not supported")
# Work out the minor axis (stack to average over) from the
# remaining image dimensions
minor_axes = [
index for index, value in enumerate(shape)
if value not in xy_dim and index != major_axis]
if len(minor_axes) == 0:
minor_axis = None
elif len(minor_axes) == 1:
minor_axis = minor_axes[0]
else:
raise IndexError(
f"Image shape {shape} not supported")
return minor_axis, n_modes, xy_dim
[docs]def get_imagej_param(description, xy_dim, shape):
desc_list = description.split('\n')
slices = [
line.strip() for line in desc_list if 'slices' in line]
if not slices:
raise IndexError(
f"Image shape {shape} not supported")
else:
n_slices = int(slices[0].split('=')[-1])
minor_axis = shape.index(n_slices)
# Work out the number of modes from the
# remaining image dimensions
n_modes = [
index for index, value in enumerate(shape)
if value not in xy_dim and index != minor_axis
]
if len(n_modes) == 0:
n_modes = 1
elif len(n_modes) == 1:
n_modes = shape[n_modes[0]]
else:
raise IndexError(
f"Image shape {shape} not supported")
return minor_axis, n_modes, xy_dim
[docs]def get_tiff_param(tiff_file):
"""Obtain relevant parameters of TiffFile object"""
xy_dim, description = lookup_page(tiff_file.pages[0])
shape = tiff_file.asarray().shape
if tiff_file.is_fluoview:
return get_fluoview_param(description, xy_dim, shape)
elif tiff_file.is_imagej:
return get_imagej_param(description, xy_dim, shape)
else:
# We are using test data
desc_dict = json.loads(description)
minor_axis = desc_dict['minor_axis']
n_modes = desc_dict['n_modes']
xy_dim = tuple(desc_dict['xy_dim'])
return minor_axis, n_modes, xy_dim
[docs]def get_accumulation_number(file_name):
""" Extrct accumulation from file name if present.
Return default value of 1 if not present.
Parameters
----------
file_name: str
File name of Tiff image
Returns
-------
acc_number: int
Accumulation number for image
Notes
-----
Expects the following file formatting:
<prefix>-acc<number>.ext
"""
path, ext = os.path.splitext(file_name)
if 'acc' in path.lower():
_, number = path.split('acc')
return int(number)
return 1
[docs]class SHGReader(BaseMultiImageReader):
"""Reader class for a combined SHG file"""
[docs] def get_multi_image_class(self):
return SHGImage
[docs] def get_supported_file_sets(self):
"""Returns class of IFileSets that will be supported."""
return [SHGPLTransFileSet]
[docs] def get_filenames(self, file_set):
yield file_set.registry['SHG']
def _format_image(self, image, minor_axis=None, acc_number=1):
"""Transform image to normalised float array and average
over stack + accumulation number """
# Average over minor axis if needed
if minor_axis is not None:
image = np.mean(image, axis=minor_axis)
return img_as_float(image) / acc_number
[docs] def load_image(self, filename):
with TiffFile(filename) as tiff_file:
image = tiff_file.asarray()
minor_axis, n_modes, xy_dim = get_tiff_param(tiff_file)
logger.debug(f"Number of image modes = {n_modes}")
logger.debug(f"Size of image = {xy_dim}")
if minor_axis is not None:
n_stacks = image.shape[minor_axis]
logger.debug(f"Number of stacks = {n_stacks}")
acc_number = get_accumulation_number(filename)
logger.debug(f"Using accumulation number = {acc_number}")
image = self._format_image(
image, minor_axis=minor_axis, acc_number=acc_number
)
return image
[docs] def can_load(self, filename):
"""Perform check to see whether file is formatted
correctly to be loaded"""
try:
with TiffFile(filename) as tiff_file:
# Check is this is Olympus FluoView formatted
if tiff_file.is_fluoview:
return True
# Check is this is ImageJ formatted
if tiff_file.is_imagej:
return True
# Check if this is test data
_, description = lookup_page(tiff_file.pages[0])
desc_dict = json.loads(description)
for key in ['minor_axis', 'n_modes', 'xy_dim']:
_ = desc_dict[key]
except Exception as e:
logger.info(
f'File type not supported: {e}')
return False
return True