added a debugging mechansism so that debug images can now be turned on or off
This commit is contained in:
parent
68e2c82eae
commit
ded2856619
|
@ -3,7 +3,10 @@
|
|||
Using these abstract classes allows the user to write image processing code that can run with various different image engines (eg imagej or opencv).
|
||||
"""
|
||||
|
||||
|
||||
import abc
|
||||
import errno
|
||||
import os
|
||||
|
||||
ABC = abc.ABCMeta('ABC', (object,), {})
|
||||
|
||||
|
@ -215,7 +218,7 @@ class StackImageFeeder(IImageFeeder):
|
|||
return self
|
||||
|
||||
def next(self):
|
||||
print("channel %d/%d frame %d/%d slice %d/%d" % (self.next_channel_index, self.hyperstack.num_channels(), self.next_frame_index, self.hyperstack.num_frames(), self.next_slice_index, self.hyperstack.num_slices()))
|
||||
# print("channel %d/%d frame %d/%d slice %d/%d" % (self.next_channel_index, self.hyperstack.num_channels(), self.next_frame_index, self.hyperstack.num_frames(), self.next_slice_index, self.hyperstack.num_slices()))
|
||||
if self.end_is_reached:
|
||||
raise StopIteration
|
||||
else:
|
||||
|
@ -245,13 +248,58 @@ class StackImageFeeder(IImageFeeder):
|
|||
# self.next_channel_index += 1
|
||||
# else:
|
||||
# self.end_is_reached = True
|
||||
print("after : channel %d/%d frame %d/%d slice %d/%d" % (self.next_channel_index, self.hyperstack.num_channels(), self.next_frame_index, self.hyperstack.num_frames(), self.next_slice_index, self.hyperstack.num_slices()))
|
||||
# print("after : channel %d/%d frame %d/%d slice %d/%d" % (self.next_channel_index, self.hyperstack.num_channels(), self.next_frame_index, self.hyperstack.num_frames(), self.next_slice_index, self.hyperstack.num_slices()))
|
||||
return image
|
||||
|
||||
def get_num_images(self):
|
||||
return self.hyperstack.num_frames()
|
||||
|
||||
|
||||
class IImageProcessingDebugger(ABC):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_image(self, image, image_id):
|
||||
'''
|
||||
:param IImage image:
|
||||
:param str image_id:
|
||||
'''
|
||||
|
||||
class NullDebugger(IImageProcessingDebugger):
|
||||
|
||||
def __init__(self):
|
||||
IImageProcessingDebugger.__init__(self)
|
||||
|
||||
def on_image(self, image, image_id):
|
||||
pass # do nothing
|
||||
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc: # Python >= 2.5
|
||||
already_exists_error_code = 20000 # normally it's errno.EEXIST (17) but for some reason it's 20000 in jython
|
||||
if exc.errno == already_exists_error_code and os.path.isdir(path):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class FileBasedDebugger(IImageProcessingDebugger):
|
||||
|
||||
|
||||
def __init__(self, debug_images_root_path='/tmp'):
|
||||
IImageProcessingDebugger.__init__(self)
|
||||
self.debug_images_root_path = debug_images_root_path
|
||||
mkdir_p(self.debug_images_root_path)
|
||||
|
||||
def on_image(self, image, image_id):
|
||||
print('FileBasedDebugger.on_image : image_id = %s' % image_id)
|
||||
ie = IImageEngine.get_instance()
|
||||
ie.save_as_tiff(image, '%s/%s.tiff' % (self.debug_images_root_path, image_id))
|
||||
|
||||
|
||||
class IImageEngine(ABC):
|
||||
"""
|
||||
The active image processor.
|
||||
|
@ -279,6 +327,12 @@ class IImageEngine(ABC):
|
|||
assert IImageEngine.__instance is not None
|
||||
return IImageEngine.__instance
|
||||
|
||||
def __init__(self, debugger=NullDebugger()):
|
||||
"""
|
||||
:param IImageProcessingDebugger debugger:
|
||||
"""
|
||||
self.debugger = debugger
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_hyperstack(self, width, height, num_channels, num_slices, num_frames, pixel_type):
|
||||
"""
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
"""Image processing using imagej.
|
||||
"""
|
||||
|
||||
from ..imageengine import IImage, IHyperStack, IImageEngine, PixelType
|
||||
import os.path
|
||||
from ..imageengine import IImage, IHyperStack, IImageEngine, PixelType, IImageProcessingDebugger, NullDebugger
|
||||
from ..maxima_finder import Match
|
||||
from ij import IJ, ImagePlus # pylint: disable=import-error
|
||||
from ij.process import FloodFiller # pylint: disable=import-error
|
||||
|
@ -227,9 +228,20 @@ def perform_gray_morphology_with_ijopencv(image, operator, structuring_element_s
|
|||
mat2imp = MatImagePlusConverter()
|
||||
image.ij_image.setProcessor(mat2imp.toImageProcessor(cv_dst_image))
|
||||
|
||||
class ImageLogger(IImageProcessingDebugger):
|
||||
|
||||
def __init__(self):
|
||||
IImageProcessingDebugger.__init__(self)
|
||||
|
||||
def on_image(self, image, image_id):
|
||||
# todo : WindowManager
|
||||
image.show()
|
||||
|
||||
class IJImageEngine(IImageEngine):
|
||||
|
||||
def __init__(self, debugger=NullDebugger()):
|
||||
IImageEngine.__init__(self, debugger)
|
||||
|
||||
def missing(self):
|
||||
pass
|
||||
|
||||
|
@ -245,6 +257,9 @@ class IJImageEngine(IImageEngine):
|
|||
|
||||
def save_as_tiff(self, image, out_file_path):
|
||||
IJ.saveAsTiff(image.ij_image, out_file_path)
|
||||
# IJ.saveAsTiff catches io exception in case the file couldn't be saved. So we have to check if the operation succeeded in another way
|
||||
if not os.path.isfile(out_file_path):
|
||||
assert False, 'failed to create %s' % out_file_path
|
||||
|
||||
def divide(self, numerator_image, denominator_image):
|
||||
ic = ImageCalculator()
|
||||
|
|
|
@ -13,58 +13,9 @@ from ij.plugin import ZProjector # pylint: disable=import-error
|
|||
from ij import WindowManager # pylint: disable=import-error
|
||||
from catalog import Sequence, ImageCatalog
|
||||
import telemos
|
||||
from imageengine import IImageProcessingDebugger, NullDebugger
|
||||
# greeting = "Hello, " + name + "!"
|
||||
|
||||
# image prefix :
|
||||
# AF
|
||||
# blé : coupes de blé
|
||||
# CA : coupe d'amande
|
||||
# FE : feuille d'épinard
|
||||
# GGH : globule gras humain
|
||||
# CRF chloroplastes de feuille d'épinard
|
||||
# OL : oléosome
|
||||
# DARK : dark
|
||||
# white :
|
||||
#
|
||||
# - cin1 : cinétique 1
|
||||
# phiG_40x_1 : cinétique avant et après injection enzyme gastrique
|
||||
# phiG_40x_Zstack20um_1 : stack
|
||||
# 0mn : on commence à enregistrer et on attend 10mn (pour le bleaching) -> phiG_40x_1
|
||||
# 10mn : debut injection phase gastrique (poussée)
|
||||
# 13mn : la phase gastrique (le petit tuyau contient 20ul) arrive dans la cellule d'un coup (1 nanol)
|
||||
# 15mn : on arrête l'injection
|
||||
# 50mn : on fait un stack -> phiG_40x_Zstack20um_1
|
||||
# 51mn : début d'injection phase intestinale (poussée) -> phiG_I_40x_1
|
||||
# x mn : on arrête l'injection
|
||||
# 90mn : on fait un stack -> phiG_I_40x_Zstack20um_1
|
||||
# - cin2 : autre échantillon similaire à cin1
|
||||
# - cond[5678] : condition non réalistes
|
||||
|
||||
|
||||
|
||||
|
||||
class IImageProcessingDebugger(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def on_image(self, image, image_id):
|
||||
'''
|
||||
:param ImagePlus image:
|
||||
:param str image_id:
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class NullDebugger(IImageProcessingDebugger):
|
||||
|
||||
def __init__(self):
|
||||
IImageProcessingDebugger.__init__(self)
|
||||
|
||||
def on_image(self, image, image_id):
|
||||
pass
|
||||
|
||||
|
||||
class ImageLogger(IImageProcessingDebugger):
|
||||
|
||||
def __init__(self):
|
||||
|
@ -212,6 +163,8 @@ def test_find_white():
|
|||
|
||||
|
||||
def run_script():
|
||||
with open('/tmp/test_result.txt', 'w') as f:
|
||||
f.write('%d' % {True: 0, False: 1}[False])
|
||||
test_find_white()
|
||||
|
||||
if False:
|
||||
|
@ -226,6 +179,8 @@ def run_script():
|
|||
# dark_image = IJ.openImage(raw_images_root + '/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos0/img_000000000_DM300_327-353_fluo_000.tif')
|
||||
# src_image.show()
|
||||
# assert src_image
|
||||
with open('/tmp/test_result.txt', 'w') as f:
|
||||
f.write('%d' % {True: 0, False: 1}[True])
|
||||
|
||||
|
||||
# If a Jython script is run, the variable __name__ contains the string '__main__'.
|
||||
|
|
|
@ -21,7 +21,7 @@ class TemplateMatcher(object):
|
|||
|
||||
correlation_image = ie.match_template(image, pattern)
|
||||
|
||||
ie.save_as_tiff(correlation_image, './similarity.tiff')
|
||||
ie.debugger.on_image(correlation_image, 'similarity')
|
||||
|
||||
matches = self.maxima_finder.find_maxima(correlation_image)
|
||||
return matches
|
||||
|
|
|
@ -22,7 +22,7 @@ class TrapBinarizer(object):
|
|||
# Process/Find edges
|
||||
# https://imagej.nih.gov/ij/developer/api/ij/process/ImageProcessor.html#findEdges--
|
||||
edge_image = ie.compute_edge_transform(clean_trap_image)
|
||||
ie.save_as_tiff(edge_image, './clean_trap_edge_image.tiff')
|
||||
ie.debugger.on_image(edge_image, 'clean_trap_edge_image')
|
||||
# IJ.run(imp, "Find Edges", "");
|
||||
# after find edges, the walls are dark lines surrounded by 2 lines of clear pixels.
|
||||
# Image/Adjust/Threshold, choose auto, then click apply
|
||||
|
@ -33,22 +33,22 @@ class TrapBinarizer(object):
|
|||
threshold_value = 0.20 # TODO: remove hardcoded value
|
||||
is_edge = ie.threshold( edge_image, max_value * threshold_value)
|
||||
# is_edge = ie.auto_threshold( edge_image)
|
||||
ie.save_as_tiff(is_edge, './is_trap_edge.tiff')
|
||||
ie.debugger.on_image(is_edge, 'is_trap_edge')
|
||||
# convert image to mask
|
||||
# at this point, the walls are black lines surrounded by 2 thick lines of white pixels
|
||||
background_seed_pos = (70,34)
|
||||
outer_edge_merged = ie.flood_fill(is_edge, background_seed_pos)
|
||||
ie.save_as_tiff(outer_edge_merged, './outer_edge_merged.tiff')
|
||||
ie.debugger.on_image(outer_edge_merged, 'outer_edge_merged')
|
||||
# floodFill(70,34);
|
||||
# ij.process.FloodFiller
|
||||
# the start point of the flood fill is expected to be outside the trap so that the flood will merge with the outer white line, and stop at the wall position.
|
||||
# IJ.run(imp, "Invert");
|
||||
is_trap_outer_limit = ie.invert(outer_edge_merged)
|
||||
ie.save_as_tiff(is_trap_outer_limit, './is_trap_outer_limit.tiff')
|
||||
ie.debugger.on_image(is_trap_outer_limit, 'is_trap_outer_limit')
|
||||
# the wall is now white and surrounded wy black. Inside the walls we have the inner black line, plus som more white pixels in the trap's flat areas. Filling holes will then perge together what's not the outside; after this, all trap pixels are white.
|
||||
# IJ.run(imp, "Fill Holes", "");
|
||||
is_trap = ie.fill_holes(is_trap_outer_limit)
|
||||
ie.save_as_tiff(is_trap, './is_trap.tiff')
|
||||
ie.debugger.on_image(is_trap, 'is_trap')
|
||||
return is_trap
|
||||
|
||||
class TrapsDetector(object):
|
||||
|
@ -80,7 +80,7 @@ class TrapsDetector(object):
|
|||
template_trap_image = first_image.get_subimage(template_trap_aabb)
|
||||
|
||||
ie = IImageEngine.get_instance()
|
||||
ie.save_as_tiff(template_trap_image, './template_trap_image.tiff')
|
||||
ie.debugger.on_image(template_trap_image, 'template_trap_image')
|
||||
|
||||
matches = self.template_matcher.match_template(first_image, template_trap_image)
|
||||
num_traps_per_frame = len(matches)
|
||||
|
@ -102,7 +102,7 @@ class TrapsDetector(object):
|
|||
image_feeder = StackImageFeeder(traps_stack)
|
||||
|
||||
clean_trap_image = ie.compute_median(image_feeder)
|
||||
ie.save_as_tiff(clean_trap_image, './clean_trap_image.tiff')
|
||||
ie.debugger.on_image(clean_trap_image, 'clean_trap_image')
|
||||
|
||||
binarizer = TrapBinarizer((70,34))
|
||||
trap_mask = binarizer.binarize_trap(clean_trap_image)
|
||||
|
@ -111,10 +111,10 @@ class TrapsDetector(object):
|
|||
for frame_trap_index in range(num_traps_per_frame):
|
||||
match = matches[frame_trap_index]
|
||||
traps_mask.set_subimage(trap_mask, (match.x, match.y))
|
||||
ie.save_as_tiff(traps_mask, './traps_mask.tiff')
|
||||
ie.debugger.on_image(traps_mask, 'traps_mask')
|
||||
|
||||
#non_uniform_stack = sequence.as_stack()
|
||||
#uniform_stack = IImageEngine.get_instance().divide(non_uniform_stack, white_estimate)
|
||||
# IImageEngine.get_instance().save_as_tiff(white_estimate, './white_estimate.tiff')
|
||||
# IImageEngine.get_instance().debugger.on_image(white_estimate, 'white_estimate')
|
||||
return traps_mask
|
||||
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
import unittest # unittest2 doesn't exist in fiji
|
||||
import sys
|
||||
from lipase.imageengine import IImageEngine, PixelType, Aabb # pylint: disable=import-error
|
||||
from lipase.imageengine import IImageEngine, PixelType, Aabb, NullDebugger, FileBasedDebugger # pylint: disable=import-error
|
||||
from lipase.imagej.ijimageengine import IJImageEngine, IJImage # pylint: disable=import-error
|
||||
from lipase.telemos import WhiteEstimator, correct_non_uniform_lighting # pylint: disable=import-error
|
||||
from lipase.maxima_finder import MaximaFinder # pylint: disable=import-error
|
||||
from lipase.template_matcher import TemplateMatcher # pylint: disable=import-error
|
||||
from lipase.traps_detector import TrapsDetector # pylint: disable=import-error
|
||||
from lipase.catalog import ImageCatalog, Sequence # pylint: disable=import-error
|
||||
from lipase.lipase import Lipase, ImageLogger # pylint: disable=import-error
|
||||
|
||||
class TestLipase(unittest.TestCase):
|
||||
|
||||
|
@ -24,49 +25,49 @@ class TestLipase(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
print("initializing TestLipase instance")
|
||||
IImageEngine.set_instance(IJImageEngine())
|
||||
IImageEngine.set_instance(IJImageEngine(debugger=FileBasedDebugger('./debug-images')))
|
||||
self.catalog = ImageCatalog(self.RAW_IMAGES_ROOT_PATH)
|
||||
|
||||
def tearDown(self):
|
||||
print("uninitializing TestLipase instance")
|
||||
self.catalog = None
|
||||
|
||||
def test_estimate_white(self):
|
||||
sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos2']
|
||||
white_estimator = WhiteEstimator(open_size=75, close_size=75, average_size=75)
|
||||
white_estimate = white_estimator.estimate_white([sequence], ['DM300_327-353_fluo'])
|
||||
# find_white_reference_image(white_estimate, sequence.get_white())
|
||||
print(white_estimate)
|
||||
IImageEngine.get_instance().save_as_tiff(white_estimate, './white_estimate.tiff')
|
||||
# assert False, "hellooooo"
|
||||
print('end of test_estimate_white')
|
||||
# def test_estimate_white(self):
|
||||
# sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos2']
|
||||
# white_estimator = WhiteEstimator(open_size=75, close_size=75, average_size=75)
|
||||
# white_estimate = white_estimator.estimate_white([sequence], ['DM300_327-353_fluo'])
|
||||
# # find_white_reference_image(white_estimate, sequence.get_white())
|
||||
# print(white_estimate)
|
||||
# IImageEngine.get_instance().debugger.on_image(white_estimate, 'white_estimate')
|
||||
# # assert False, "hellooooo"
|
||||
# print('end of test_estimate_white')
|
||||
|
||||
def test_uniform_lighting_correction(self):
|
||||
non_uniform_sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos0']
|
||||
uniform_sequence = correct_non_uniform_lighting(non_uniform_sequence, 'DM300_nofilter_vis', white_estimator=WhiteEstimator(open_size=75, close_size=75, average_size=75))
|
||||
# def test_uniform_lighting_correction(self):
|
||||
# non_uniform_sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos0']
|
||||
# uniform_sequence = correct_non_uniform_lighting(non_uniform_sequence, 'DM300_nofilter_vis', white_estimator=WhiteEstimator(open_size=75, close_size=75, average_size=75))
|
||||
|
||||
def test_template_matcher(self):
|
||||
sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos0']
|
||||
stack = sequence.as_hyperstack(['DM300_nofilter_vis'], selected_frames=[0])
|
||||
first_image = stack.get_image(frame_index=0)
|
||||
x_min = 423
|
||||
x_max = 553
|
||||
y_min = 419
|
||||
y_max = 533
|
||||
template_trap_aabb = Aabb(x_min, y_min, x_max, y_max)
|
||||
template_trap_image = first_image.get_subimage(template_trap_aabb)
|
||||
for image in [first_image, template_trap_image]:
|
||||
print(image.get_pixel_type(), image.get_width(), image.get_height())
|
||||
# the typical value of peaks is -2.e10 and the value between peaks is below -8.0e10
|
||||
threshold = -3.0e10
|
||||
tolerance = 1.0e10
|
||||
maxima_finder = MaximaFinder(threshold, tolerance)
|
||||
template_matcher = TemplateMatcher(maxima_finder)
|
||||
matches = template_matcher.match_template(first_image, template_trap_image)
|
||||
num_traps = len(matches)
|
||||
print("number of traps found : %d" % num_traps)
|
||||
num_expected_traps = 13 # 13 traps are completely visible in the first image
|
||||
self.assertAlmostEqual(len(matches), num_expected_traps, delta=1.0)
|
||||
# def test_template_matcher(self):
|
||||
# sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos0']
|
||||
# stack = sequence.as_hyperstack(['DM300_nofilter_vis'], selected_frames=[0])
|
||||
# first_image = stack.get_image(frame_index=0)
|
||||
# x_min = 423
|
||||
# x_max = 553
|
||||
# y_min = 419
|
||||
# y_max = 533
|
||||
# template_trap_aabb = Aabb(x_min, y_min, x_max, y_max)
|
||||
# template_trap_image = first_image.get_subimage(template_trap_aabb)
|
||||
# for image in [first_image, template_trap_image]:
|
||||
# print(image.get_pixel_type(), image.get_width(), image.get_height())
|
||||
# # the typical value of peaks is -2.e10 and the value between peaks is below -8.0e10
|
||||
# threshold = -3.0e10
|
||||
# tolerance = 1.0e10
|
||||
# maxima_finder = MaximaFinder(threshold, tolerance)
|
||||
# template_matcher = TemplateMatcher(maxima_finder)
|
||||
# matches = template_matcher.match_template(first_image, template_trap_image)
|
||||
# num_traps = len(matches)
|
||||
# print("number of traps found : %d" % num_traps)
|
||||
# num_expected_traps = 13 # 13 traps are completely visible in the first image
|
||||
# self.assertAlmostEqual(len(matches), num_expected_traps, delta=1.0)
|
||||
|
||||
|
||||
def test_traps_detector(self):
|
||||
|
@ -91,6 +92,10 @@ class TestLipase(unittest.TestCase):
|
|||
print("measured_mean_value: %f" % measured_mean_value)
|
||||
self.assertAlmostEqual(measured_mean_value, expected_mean_value, delta=0.01)
|
||||
|
||||
# def test_lipase_process(self):
|
||||
# lipase = Lipase(self.catalog, debugger=NullDebugger())
|
||||
|
||||
|
||||
def run_script():
|
||||
|
||||
# unittest.main() # this would result in : ImportError: No module named __main__
|
||||
|
|
Loading…
Reference in New Issue