added the processing that computes the traps mask from the clean trap image

Bug 2804 - Faire un traitement qui détecte les pièges
This commit is contained in:
Guillaume Raffy 2020-02-04 18:00:54 +01:00
parent cc6d89ed7c
commit b4458c0697
4 changed files with 158 additions and 5 deletions

View File

@ -39,6 +39,10 @@ class PixelType:
class IImage(ABC):
@abc.abstractmethod
def clone(self):
pass
@abc.abstractmethod
def width(self):
pass
@ -51,16 +55,25 @@ class IImage(ABC):
def get_subimage(self, aabb):
"""
:param Aabb aabb: axis-aligned bounding box of the subimage
:return IImage:
"""
pass
@abc.abstractmethod
def set_subimage(self, subimage, position):
"""
:param IImage subimage: the image to copy into self
:param (int, int) position: where to insert the subimage in self (upper left pixel)
"""
@abc.abstractmethod
def get_pixel_type(self):
"""
:rtype PixelType:
"""
pass
class IHyperStack(ABC):
@ -277,6 +290,15 @@ class IImageEngine(ABC):
:param str out_file_path: eg './white_estimate.tiff'
"""
@abc.abstractmethod
def compute_edge_transform(self, image):
"""
computes the edge transform using a sobel filter
:param IImage image:
:rtype IImage:
"""
@abc.abstractmethod
def compute_max(self, image_feeder):
"""Compute for each pixel position the maximum at this position in all input images.
@ -349,3 +371,37 @@ class IImageEngine(ABC):
:param float tolerance: ignore local maxima if they are in the neighborhood of another maximum. The neighborhood of a maximum is defined by the area that propagates until the the difference between the pixel value and the value of the considered local maximum exceeds tolerance. The higher the tolerance, the more local maxima are removed....
:return list(Match): maxima
"""
@abc.abstractmethod
def auto_threshold(self, src_image):
"""
:rtype IImage:
"""
@abc.abstractmethod
def flood_fill(self, src_binary_image, flood_start_pos):
"""
performs flood fill from starting point
:param IImage src_binary_image:
:param (int, int) flood_start_pos: start position of the flood fill
:rtype IImage: binary image
"""
@abc.abstractmethod
def fill_holes(self, src_binary_image):
"""
fills the holes in the given binary image
:param IImage src_binary_image:
:rtype IImage: binary image
"""
@abc.abstractmethod
def invert(self, src_binary_image):
"""
inverts the given image pixel values (black becomes white and white becomes black)
:param IImage src_binary_image:
:rtype IImage: binary image
"""

View File

@ -4,6 +4,8 @@
from ..imageengine import IImage, IHyperStack, IImageEngine, PixelType
from ..maxima_finder import Match
from ij import IJ, ImagePlus # pylint: disable=import-error
from ij.process import FloodFiller # pylint: disable=import-error
from ij.process import Blitter # pylint: disable=import-error
from ij.measure import ResultsTable # pylint: disable=import-error
from ij.plugin import ImageCalculator # pylint: disable=import-error
from ij.plugin.filter import MaximumFinder # pylint: disable=import-error
@ -41,6 +43,11 @@ class IJImage(IImage):
stack_name = ''
self.ij_image = IJ.createHyperStack(stack_name, width, height, 1, 1, 1, PixelType.get_num_bits(pixel_type))
def clone(self):
copy = IJImage(self.image_engine, width=self.width(), height=self.height(), pixel_type=self.get_pixel_type())
copy.ij_image.setProcessor( self.ij_image.getProcessor().duplicate() )
return copy
def width(self):
return self.ij_image.width
@ -61,6 +68,11 @@ class IJImage(IImage):
# ij_subimage.paste()
return IJImage(self.image_engine, ij_subimage)
def set_subimage(self, subimage, position):
(x, y) = position
src_processor = subimage.ij_image.getProcessor()
dst_processor = self.ij_image.getProcessor()
dst_processor.copyBits(src_processor, x, y, Blitter.COPY)
class IJHyperStack(IHyperStack):
@ -216,6 +228,12 @@ 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_edge_transform(self, image):
result_image = image.clone()
result_image.ij_image.getProcessor().findEdges()
return result_image
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.
@ -347,4 +365,34 @@ class IJImageEngine(IImageEngine):
match = Match(x, y, correlation)
# print(match)
matches.append(match)
return matches
return matches
def auto_threshold(self, src_image):
# Image/Adjust/Threshold, choose auto, then click apply
# IJ.setAutoThreshold(imp, "Default dark");
binary_image = src_image.clone()
IJ.setAutoThreshold(binary_image.ij_image, "Default dark")
IJ.run(binary_image.ij_image, "Convert to Mask", "")
return binary_image
def flood_fill(self, src_binary_image, flood_start_pos):
# https://imagej.nih.gov/ij/developer/api/ij/process/FloodFiller.html
flood_filled_image = src_binary_image.clone()
image_processor = flood_filled_image.ij_image.getProcessor()
image_processor.setValue(255)
flood_filler = FloodFiller(image_processor)
(x, y) = flood_start_pos
flood_filler.fill(x, y)
return flood_filled_image
def fill_holes(self, src_binary_image):
result_image = src_binary_image.clone()
IJ.run(result_image.ij_image, "Fill Holes", "")
return result_image
def invert(self, src_binary_image):
inverted_image = src_binary_image.clone()
image_processor = inverted_image.ij_image.getProcessor()
image_processor.invert()
return inverted_image

View File

@ -21,7 +21,7 @@ class TemplateMatcher(object):
correlation_image = ie.match_template(image, pattern)
# ie.save_as_tiff(correlation_image, '/home/graffy/Desktop/similarity.tiff')
ie.save_as_tiff(correlation_image, './similarity.tiff')
matches = self.maxima_finder.find_maxima(correlation_image)
return matches

View File

@ -7,6 +7,45 @@ from preprocessing import WhiteEstimator, correct_non_uniform_lighting
from maxima_finder import Match
from template_matcher import TemplateMatcher
class TrapBinarizer(object):
def __init__(self, flood_fill_start_point):
self.flood_fill_start_point = flood_fill_start_point
def binarize_trap(self, clean_trap_image):
"""
:param IImage clean_trap_image:
"""
ie = IImageEngine.get_instance()
# convert image to edges because the grey level of traps is not uinform across the image
# The threshold works much better on the edge image because the edges of the traps are clearly visible
# 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')
# 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
# IJ.setAutoThreshold(imp, "Default dark");
is_edge = ie.auto_threshold( edge_image )
ie.save_as_tiff(is_edge, './is_trap_edge.tiff')
# 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')
# 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')
# 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')
return is_trap
class TrapsDetector(object):
def __init__(self, template_matcher):
@ -34,13 +73,14 @@ class TrapsDetector(object):
first_image = uniform_stack.get_image(frame_index=0)
template_trap_image = first_image.get_subimage(template_trap_aabb)
# ie.save_as_tiff(trap_image, '/home/graffy/Desktop/template.tiff')
ie = IImageEngine.get_instance()
ie.save_as_tiff(template_trap_image, './template_trap_image.tiff')
matches = self.template_matcher.match_template(first_image, template_trap_image)
num_traps_per_frame = len(matches)
num_traps = uniform_stack.num_frames() * num_traps_per_frame
ie = IImageEngine.get_instance()
traps_stack = ie.create_hyperstack(width=template_trap_aabb.width, height=template_trap_aabb.height, num_slices=1, num_frames=num_traps, num_channels=1, pixel_type=PixelType.F32)
trap_index = 0
@ -59,6 +99,15 @@ class TrapsDetector(object):
clean_trap_image = ie.compute_median(image_feeder)
ie.save_as_tiff(clean_trap_image, './clean_trap_image.tiff')
binarizer = TrapBinarizer((70,34))
trap_mask = binarizer.binarize_trap(clean_trap_image)
traps_mask = ie.create_image(width=uniform_stack.width(), height=uniform_stack.height(), pixel_type=uniform_stack.get_pixel_type())
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')
#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')