#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Copyright (c) 2022 Univ. Lyon, ENS de Lyon, UCB Lyon 1, CNRS, INRAe, Inria
# All rights reserved.
# This file is part of the TimageTK library, and is released under the "GPLv3"
# license. Please see the LICENSE.md file that should have been included as
# part of this package.
# ------------------------------------------------------------------------------
"""Multi-angle image class.
Since they can be made of a great number of images, and that these can be
too big to save them all in memory we use disk file access instead of loading
everything in memory.
"""
from os.path import join
from timagetk.components.spatial_image import SpatialImage
from timagetk.components.trsf import Trsf
from timagetk.io import imread
from timagetk.io import imsave
from timagetk.io.util import assert_all_files
from timagetk.bin.logger import get_logger
log = get_logger(__name__)
#: Naming convention for mean image files.
MEAN_IMG_FNAME = "{}-mean_image-iter{}.{}" # name, iter_id, extension
#: Naming convention for transformation files.
TRSF_FNAME = "{}t{}_on_t{}-{}-{}.trsf" # name, float_angle, ref_angle, iter_id, trsf_type
#: Default extension for image files.
DEF_IMG_EXT = 'mha'
#: Default transformations dictionary.
DEF_DICT_TRSF = {'rigid': {}, 'affine': {}, 'vectorfield': {}}
[docs]
class MultiAngleImage(list):
"""Multi-angles image data structure.
This data structure rely on filenames to save some memory during the registration process, especially for large images.
Attributes
----------
trsfs : dict
Dictionary of transformation files location structured as
{'<trsf_type>': {n_iter : {(flo_angle, ref_angle): '<path_to_Trsf>'}}},
with:
* ``'<trsf_type>'`` the type of transformation
* ``n_iter`` the iteration id
* ``flo_angle`` & ``ref_angle`` the floating and reference angle-id, use 'mean_<n_iter>' when using mean image as reference
* ``'<path_to_Trsf>'`` the corresponding transformation file location
mean_image : list
List of mean image, ordered by iterations, if any.
.. todo::
Use saved 'acquisition_time' to check images are indeed multiple angle of the same object!
Examples
--------
>>> from timagetk.components.multi_angle import MultiAngleImage
>>> from timagetk.io.util import shared_data
>>> from timagetk.io import imread
>>> list_fnames = [shared_data(f'p58-t0-a{idx}.lsm', "p58") for idx in range(3)]
>>> # Step 1: Create the MultiAngle object
>>> ma = MultiAngleImage(list_fnames)
>>> # Manual creation of a rigid trsf to generate an artificial multi-angles image:
>>> from timagetk.algorithms.trsf import create_trsf
>>> from timagetk.algorithms.trsf import apply_trsf
>>> trsf = create_trsf('random', trsf_type='rigid', angle_range=[0.2, 0.25], translation_range=[0.2, 1.2])
>>> trsf.print()
>>> float_img = apply_trsf(ref_img, trsf)
>>> # Step 1: Create the MultiAngle object
>>> ma = MultiAngleImage([ref_img, float_img])
"""
[docs]
def __init__(self, list_img, prefix="", **kwargs):
"""MultiAngleImage object constructor.
Parameters
----------
list_img : list of SpatialImages or list of str
List of images representing multiple angles of the same object.
prefix : str, optional
If set, filename prefix used to save the intermediary images at each iteration.
Else only the last one is saved.
Other Parameters
----------------
name : str
Name of the multi-angles image.
Raises
------
IOError
If not all image files can be found.
"""
# - Initialize EMPTY attributes:
self.trsfs = DEF_DICT_TRSF
# -- Mean images computed for each iteration of fusion:- {n_iter : location}}, with
# * ``n_iter`` (int) the iteration id as key
# * ``filename`` (str) the corresponding 'mean image' file location
self.mean_image = {}
# -- Reference images:- {n_iter : location}}, with
# * ``n_iter`` the iteration id
# * ``location`` the corresponding mean image file location
self.ref_image = {} # dictionary of reference images with 'iteration id' as key and 'filename' (str) as value
assert_all_files(list_img)
super().__init__(list_img)
# - Defines attributes:
# -- Update the number of angles:
self.nb_angles = len(self)
# -- Prefix or name
self.prefix = prefix
# - Set some attributes from kwargs:
self._ext = kwargs.get("ext", DEF_IMG_EXT)
# -- Output path:
self._out_path = ""
def _get_trsf_fname(self, float_a, ref_a, iter_id, trsf_type):
"""Formatter for transformation file names.
Parameters
----------
float_a : int
Floating (registered) angle-id
ref_a : int
Reference angle-id
iter_id : int
Iteration id
trsf_type : str
Type of transformation used
Returns
-------
str
Formatted filename for a transformation
"""
name = self.prefix
if self.prefix != "":
name += "-"
fname = TRSF_FNAME.format(name, float_a, ref_a, iter_id, trsf_type)
return join(self._out_path, fname)
def _get_mean_img_fname(self, iter_id):
"""Formatter for mean image file names.
Parameters
----------
iter_id : int
Iteration id
Returns
-------
str
Formatted filename for a mean image
"""
name = self.prefix
if self.prefix != "":
name += "-"
fname = MEAN_IMG_FNAME.format(name, iter_id, self._ext)
return join(self._out_path, fname)
# def _handle_trsf(self, trsf, float_a, ref_a, iter_id):
# """
# Generic function to know what to do when you get a Trsf.
#
# Save the matrix if the Trsf is linear, else write a file and save its
# location in ``self.trsf``.
#
# Parameters
# ----------
# trsf : Trsf
# transformation to handle
# method : str
# used method
# float_a : int
# floating image angle index
# ref_a : int
# reference image angle index
# iter_id : int
# iteration id
#
# """
# if isinstance(trsf, list):
# return [self._handle_trsf(t, float_a, ref_a, iter_id) for t
# in trsf]
# else:
# # - Add iter_id key and empty trsf dict
# if not iter_id in self.rigid_trsf:
# self.rigid_trsf.update({iter_id: DEF_DICT_TRSF})
# if trsf.is_linear():
# # - In case of linear transformation, save the matrix
# self.rigid_trsf[iter_id].update({(float_a, ref_a): trsf})
# else:
# # - In case of a non-linear transformation, write the file and save its location
# fname = self._get_trsf_fname(float_a, ref_a, iter_id,
# 'vectorfield')
# trsf.write(fname)
# self.rigid_trsf[iter_id].update({(float_a, ref_a): fname})
def _handle_mean_img(self, image, iter_id):
"""Generic function to know what to do when you get a mean image.
Write a file with its 'iteration id' in its name and save its location in ``self.mean_image[iter_id]``.
Parameters
----------
image : timagetk.SpatialImage
Image to handle
iter_id : int
Iteration id
"""
if isinstance(image, list):
return [self._handle_mean_img(t, iter_id) for t in image]
else:
fname = self._get_mean_img_fname(iter_id)
imsave(fname, image)
self.mean_image.update({iter_id: fname})
return None
[docs]
def get_angle_image(self, angle_id):
"""Return SpatialImage for given angle-id.
Parameters
----------
angle_id : int
Angle-id of the image, *i.e.* same order as given list.
Returns
-------
SpatialImage
Image of given angle-id
"""
if isinstance(angle_id, int):
return imread(self[angle_id])
elif isinstance(angle_id, str):
return imread(self.mean_image[angle_id])
else:
raise ValueError("Unknown angle id '{}'!".format(angle_id))
[docs]
def get_images(self):
"""Return all SpatialImage as a list.
Returns
-------
list of SpatialImage
List with all SpatialImages
"""
# FIXME:
return [self.get_angle_image(angle_id) for angle_id in range(self.nb_angles)]
[docs]
def get_average_image(self, iter_id):
"""Return the average image corresponding to the iteration.
Parameters
----------
iter_id : int
Iteration step to get the image from.
Returns
-------
SpatialImage
The corresponding averaged intensity image.
"""
return imread(self.mean_image[iter_id])
[docs]
def get_trsf(self, trsf_type, iter_id, angle_id):
"""Return the average image corresponding to the iteration.
Parameters
----------
trsf_type : {'rigid', 'affine', 'vectorfield'}
Type of transformation to get.
iter_id : int
Iteration step to get the transformation from.
angle_id : int
Angle image id to get the transformation from.
Returns
-------
Trsf
The loaded transformation object.
"""
ref_img = self.ref_image[iter_id]
return Trsf(self.trsfs[trsf_type][iter_id][(angle_id, ref_img)])
[docs]
def save(self, filename):
"""Save the data structure to a file.
Parameters
----------
filename : str
File path & name to use.
"""
raise NotImplementedError("YET...")