The Estimate White plugin now takes the selected stack as input instead of asking the user the sequence

This makes Estimate White plugin's UI more in line with imagej's plugin conventions.
This commit is contained in:
Guillaume Raffy 2019-10-09 17:42:32 +02:00
parent 44dacc1a97
commit 7973cfc3ec
4 changed files with 175 additions and 78 deletions

View File

@ -1,6 +1,7 @@
#@ Integer (label="open size (in pixels)", value=75, style="slider") OPEN_SIZE
#@ Integer (label="close size (in pixels)", value=75, style="slider") CLOSE_SIZE
#@ Integer (label="average size (in pixels)", value=75, style="slider") AVERAGE_SIZE
#@ ImagePlus (label="the input image stack") INPUT_STACK
#@ Integer (label="open size (in pixels)", value=75, min=0, max=500, style="slider") OPEN_SIZE
#@ Integer (label="close size (in pixels)", value=75, min=0, max=500, style="slider") CLOSE_SIZE
#@ Integer (label="average size (in pixels)", value=75, min=0, max=500, style="slider") AVERAGE_SIZE
#@output ImagePlus WHITE_ESTIMATE
"""This script is supposed to be launched from fiji's jython interpreter
@ -26,7 +27,7 @@ from lipase.settings import UserSettings
# sys.path.append(lipase_src_root_path) # pylint: disable=undefined-variable
# from lipase import Lipase, ImageLogger
from lipase.imageengine import IImageEngine
from lipase.imageengine import IImageEngine, PixelType, StackImageFeeder
from lipase.imagej.ijimageengine import IJImageEngine
from lipase.preprocessing import WhiteEstimator
from lipase.catalog import ImageCatalog
@ -45,65 +46,15 @@ from java.awt.event import ItemListener
# # event's attributes : ['ACTION_EVENT_MASK', 'ADJUSTMENT_EVENT_MASK', 'COMPONENT_EVENT_MASK', 'CONTAINER_EVENT_MASK', 'DESELECTED', 'FOCUS_EVENT_MASK', 'HIERARCHY_BOUNDS_EVENT_MASK', 'HIERARCHY_EVENT_MASK', 'ID', 'INPUT_METHOD_EVENT_MASK', 'INVOCATION_EVENT_MASK', 'ITEM_EVENT_MASK', 'ITEM_FIRST', 'ITEM_LAST', 'ITEM_STATE_CHANGED', 'KEY_EVENT_MASK', 'MOUSE_EVENT_MASK', 'MOUSE_MOTION_EVENT_MASK', 'MOUSE_WHEEL_EVENT_MASK', 'PAINT_EVENT_MASK', 'RESERVED_ID_MAX', 'SELECTED', 'TEXT_EVENT_MASK', 'WINDOW_EVENT_MASK', 'WINDOW_FOCUS_EVENT_MASK', 'WINDOW_STATE_EVENT_MASK', '__class__', '__copy__', '__deepcopy__', '__delattr__', '__doc__', '__ensure_finalizer__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__subclasshook__', '__unicode__', 'class', 'equals', 'getClass', 'getID', 'getItem', 'getItemSelectable', 'getSource', 'getStateChange', 'hashCode', 'item', 'itemSelectable', 'notify', 'notifyAll', 'paramString', 'setSource', 'source', 'stateChange', 'toString', 'wait']
class SequenceChoiceListener(ItemListener):
def __init__(self, channel_choice, catalog):
ItemListener.__init__(self)
self.channel_choice = channel_choice
self.catalog = catalog
def itemStateChanged(self, event):
IJ.log("SequenceChoiceListener : Something was changed (event = %s)" % event)
# SequenceChoiceListener : Something was changed (event = java.awt.event.ItemEvent[ITEM_STATE_CHANGED,item=res_soleil2018/DARK/DARK_40X_60min_1 im pae min_1/Pos0,stateChange=SELECTED] on choice3)
IJ.log("SequenceChoiceListener : event's attributes : %s" % str(dir(event)))
# SequenceChoiceListener : event's attributes : ['ACTION_EVENT_MASK', 'ADJUSTMENT_EVENT_MASK', 'COMPONENT_EVENT_MASK', 'CONTAINER_EVENT_MASK', 'DESELECTED', 'FOCUS_EVENT_MASK', 'HIERARCHY_BOUNDS_EVENT_MASK', 'HIERARCHY_EVENT_MASK', 'ID', 'INPUT_METHOD_EVENT_MASK', 'INVOCATION_EVENT_MASK', 'ITEM_EVENT_MASK', 'ITEM_FIRST', 'ITEM_LAST', 'ITEM_STATE_CHANGED', 'KEY_EVENT_MASK', 'MOUSE_EVENT_MASK', 'MOUSE_MOTION_EVENT_MASK', 'MOUSE_WHEEL_EVENT_MASK', 'PAINT_EVENT_MASK', 'RESERVED_ID_MAX', 'SELECTED', 'TEXT_EVENT_MASK', 'WINDOW_EVENT_MASK', 'WINDOW_FOCUS_EVENT_MASK', 'WINDOW_STATE_EVENT_MASK', '__class__', '__copy__', '__deepcopy__', '__delattr__', '__doc__', '__ensure_finalizer__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__subclasshook__', '__unicode__', 'class', 'equals', 'getClass', 'getID', 'getItem', 'getItemSelectable', 'getSource', 'getStateChange', 'hashCode', 'item', 'itemSelectable', 'notify', 'notifyAll', 'paramString', 'setSource', 'source', 'stateChange', 'toString', 'wait']
IJ.log("SequenceChoiceListener : event.item : %s" % str(event.item))
IJ.log("SequenceChoiceListener : type(event.item) : %s" % str(type(event.item)))
selected_sequence_id = event.item
selected_sequence = self.catalog.sequences[selected_sequence_id]
channel_ids = selected_sequence.get_channel_names()
self.channel_choice.removeAll()
for channel_id in channel_ids:
self.channel_choice.add(channel_id)
def ask_for_sequence(catalog):
title = 'select the sequence to process'
gd = GenericDialog(title)
# gd.addDialogListener(MyListener())
sequence_ids = catalog.sequences.keys()
assert len(sequence_ids) > 0
default_sequence_id = sequence_ids[0]
gd.addChoice('sequence', sequence_ids, default_sequence_id)
channel_ids = catalog.sequences[default_sequence_id].get_channel_names()
gd.addChoice('channel', channel_ids, channel_ids[0])
choices = gd.getChoices()
IJ.log("choices = %s" % choices)
sequence_choice = choices[0]
channel_choice = choices[1]
sequence_choice.addItemListener(SequenceChoiceListener(channel_choice, catalog))
gd.showDialog()
if gd.wasCanceled():
return {}
selected_sequence_id = sequence_ids[gd.getNextChoiceIndex()] # eg 'res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos2'
selected_channel_id = catalog.sequences[selected_sequence_id].get_channel_names()[gd.getNextChoiceIndex()] # eg 'DM300_327-353_fluo'
print("chosen sequence : %s" % selected_sequence_id)
print("chosen channel : %s" % selected_channel_id)
return {'sequence': catalog.sequences[selected_sequence_id], 'channel_id':selected_channel_id}
def run_script():
user_settings = UserSettings()
IImageEngine.set_instance(IJImageEngine())
catalog = ImageCatalog(user_settings.raw_images_root_path)
user_selection = ask_for_sequence(catalog)
if len(user_selection) == 0:
return
sequence = user_selection['sequence']
channel_id = user_selection['channel_id']
src_hyperstack = IImageEngine.get_instance().create_hyperstack(width=1, height=1, num_channels=1, num_slices=1, num_frames=1, pixel_type=PixelType.U8)
src_hyperstack.hyperstack = INPUT_STACK
image_set = StackImageFeeder(src_hyperstack)
white_estimator = WhiteEstimator(open_size=OPEN_SIZE, close_size=CLOSE_SIZE, average_size=AVERAGE_SIZE)
white_estimate = white_estimator.estimate_white([sequence], [channel_id])
white_estimate = white_estimator.estimate_white_from_image_set(image_set)
print(type(white_estimate))
global WHITE_ESTIMATE
WHITE_ESTIMATE = white_estimate.ij_image

View File

@ -51,6 +51,19 @@ class IHyperStack(ABC):
def height(self):
pass
@abc.abstractmethod
def num_slices(self):
pass
@abc.abstractmethod
def num_frames(self):
pass
@abc.abstractmethod
def num_channels(self):
pass
@abc.abstractmethod
def set_image(self, image, frame_index=0, slice_index=0, channel_index=0):
"""
@ -58,6 +71,101 @@ class IHyperStack(ABC):
"""
pass
@abc.abstractmethod
def get_image(self, frame_index=0, slice_index=0, channel_index=0):
"""
:param IImage image:
"""
pass
class IImageFeeder(ABC):
"""An object that generates a collection of images of the same size and same format
"""
@abc.abstractmethod
def __iter__(self):
"""
for iterator
"""
pass
@abc.abstractmethod
def next(self):
"""returns the nex image in the collection
for iterator
"""
pass
class FileImageFeeder(IImageFeeder):
def __init__(self):
self.image_filepaths = []
self.next_image_index = 0
def __iter__(self):
self.next_image_index = 0
return self
def next(self):
if self.next_image_index < len(self.image_filepaths):
image_filepath = self.image_filepaths[self.next_image_index]
self.next_image_index += 1
image = IImageEngine.get_instance().load_image(image_filepath)
return image
raise StopIteration
def add_image(self, image_filepath):
self.image_filepaths.append(image_filepath)
class StackImageFeeder(IImageFeeder):
def __init__(self, hyperstack):
"""
:param IHyperStack hyperstack
"""
self.hyperstack = hyperstack
def _init_iter(self):
self.next_frame_index = 0
self.next_slice_index = 0
self.next_channel_index = 0
self.end_is_reached = False
def __iter__(self):
self._init_iter()
return self
def next(self):
print(self.next_channel_index, self.next_frame_index, self.next_slice_index)
print(self.hyperstack.num_channels, self.hyperstack.num_frames, self.hyperstack.num_slices)
if self.end_is_reached:
raise StopIteration
else:
image = self.hyperstack.get_image(frame_index=self.next_frame_index, slice_index=self.next_slice_index, channel_index=self.next_channel_index)
# compute next image index
if self.next_slice_index < self.hyperstack.num_slices():
self.next_slice_index += 1
else:
self.next_slice_index = 0
if self.next_frame_index < self.hyperstack.num_frames():
self.next_frame_index += 1
else:
self.next_frame_index = 0
if self.next_channel_index < self.hyperstack.num_channels():
self.next_channel_index += 1
else:
self.end_is_reached = True
return image
def add_image(self, image_filepath):
self.image_filepaths.append(image_filepath)
class IImageEngine(ABC):
@ -107,10 +215,10 @@ class IImageEngine(ABC):
"""
@abc.abstractmethod
def compute_max(self, images_file_path):
def compute_max(self, image_feeder):
"""Compute for each pixel position the maximum at this position in all input images.
:param list(str) images_file_path:
:param IImageFeeder image_feeder:
:rtype IImage:
"""

View File

@ -2,7 +2,7 @@
"""
from ..imageengine import IImage, IHyperStack, IImageEngine, PixelType
from ij import IJ # pylint: disable=import-error
from ij import IJ, ImagePlus # pylint: disable=import-error
from ij.plugin import ImageCalculator # pylint: disable=import-error
from ijopencv.ij import ImagePlusMatConverter # pylint: disable=import-error
from ijopencv.opencv import MatImagePlusConverter # pylint: disable=import-error
@ -30,9 +30,10 @@ class IJImage(IImage):
class IJHyperStack(IHyperStack):
def __init__(self, width, height, num_channels, num_slices, num_frames, pixel_type):
def __init__(self, image_engine, width, height, num_channels, num_slices, num_frames, pixel_type):
"""
"""
self.image_engine = image_engine
stack_name = ''
self.hyperstack = IJ.createHyperStack(stack_name, width, height, num_channels, num_slices, num_frames, PixelType.get_num_bits(pixel_type))
@ -40,12 +41,35 @@ class IJHyperStack(IHyperStack):
self.hyperstack.setPositionWithoutUpdate(channel_index + 1, slice_index + 1, frame_index + 1)
self.hyperstack.setProcessor(image.ij_image.getProcessor())
def get_image(self, frame_index=0, slice_index=0, channel_index=0):
self.hyperstack.setPositionWithoutUpdate(channel_index + 1, slice_index + 1, frame_index + 1)
stack_name = ''
image_plus = IJ.createHyperStack(stack_name, self.width(), self.height(), 1, 1, 1, PixelType.get_num_bits(self.get_pixel_type()))
image_plus.setProcessor(self.hyperstack.getProcessor())
ij_image = IJImage(self.image_engine, image_plus)
return ij_image
def width(self):
return self.hyperstack.width
def height(self):
return self.hyperstack.height
def num_slices(self):
return self.hyperstack.getNSlices()
def num_frames(self):
return self.hyperstack.getNFrames()
def num_channels(self):
return self.hyperstack.getNChannels()
def get_pixel_type(self):
ij_pixel_type = self.hyperstack.getType()
return { ImagePlus.GRAY8: PixelType.U8, ImagePlus.GRAY16: PixelType.U16 }[ij_pixel_type]
def imagej_run_image_command(image, command, options):
"""performs the given imagej command on the given image
@ -143,7 +167,7 @@ class IJImageEngine(IImageEngine):
pass
def create_hyperstack(self, width, height, num_channels, num_slices, num_frames, pixel_type):
return IJHyperStack(width, height, num_channels, num_slices, num_frames, pixel_type)
return IJHyperStack(self, width, height, num_channels, num_slices, num_frames, pixel_type)
def load_image(self, src_image_file_path):
image_plus = IJ.openImage(src_image_file_path)
@ -157,21 +181,27 @@ class IJImageEngine(IImageEngine):
result_image = ic.run("Divide create float", numerator_image.ij_image, denominator_image.ij_image)
return IJImage(self, result_image)
def compute_max(self, images_file_path):
def compute_max(self, image_feeder):
#def compute_max(self, images_file_path):
"""Computes for each pixel position the maximum at this position in all input images.
:param list(str) images_file_path:
:param IImageFeeder image_feeder:
:rtype IJImage:
"""
assert len(images_file_path) > 1
max_image = IJ.openImage(images_file_path[0])
print('max_image', max_image)
it = iter(image_feeder)
for image_file_path in images_file_path[2:-1]:
other_image = IJ.openImage(image_file_path)
# assert len(images_file_path) > 1
max_image = it.next().ij_image
#max_image = IJ.openImage(images_file_path[0])
#print('max_image', max_image)
# for image_file_path in images_file_path[2:-1]:
for other_image in it:
# other_image = IJ.openImage(image_file_path)
print('other_image', other_image)
ic = ImageCalculator()
ic.run("max", max_image, other_image)
ic.run("max", max_image, other_image.ij_image)
print('max_image', max_image)
return IJImage(self, max_image)

View File

@ -1,7 +1,7 @@
"""preprocessing of synchrotron images based on telemosToolbox."""
from catalog import ImageCatalog, Sequence
from imageengine import IImageEngine, PixelType
from imageengine import IImageEngine, PixelType, FileImageFeeder
class WhiteEstimator(object):
@ -25,8 +25,6 @@ class WhiteEstimator(object):
:param list(Sequence) sequences: the sequences to consider
:param list(str) channel_ids: the channels to consider, eg ['DM300_327-353_fluo']
:param WhiteEstimatorSettings white_estimator_settings:
:param ImagePlus or None dark:
:rtype ImagePlus:
Code adapted from matlab telemosToolbx's estimatedwhiteFluoImageTelemos function
@ -116,16 +114,26 @@ class WhiteEstimator(object):
% 27 mai 2019 : offset dark
% 4 juin 2019 : replace exist by isfolder or isfile
% 14 juin : close figure at the end
"""
images_file_path = []
"""
image_set = FileImageFeeder()
for sequence in sequences:
for channel_id in channel_ids:
channel_index = sequence.get_channel_index(channel_id)
for frame_index in range(sequence.num_frames):
for slice_index in range(sequence.num_slices):
images_file_path.append(sequence.get_image_file_path(channel_index, frame_index, slice_index))
image_set.add_image(sequence.get_image_file_path(channel_index, frame_index, slice_index))
white_estimate = IImageEngine.get_instance().compute_max(images_file_path)
white_estimate = self.estimate_white_from_image_set(image_set, dark)
return white_estimate
def estimate_white_from_image_set(self, image_set, dark=None):
"""Estimation of the white fluorescence image shape of synchrotron light from experimental images of Telemos microscope.
:param IImageFeeder image_set: the set of input images to consider
:param WhiteEstimatorSettings white_estimator_settings:
"""
white_estimate = IImageEngine.get_instance().compute_max(image_set)
# modify spurious pixels on the side of the images
try: