added a a method that estimates the global area of globules over time
This commit is contained in:
parent
57c594cf4d
commit
8f6d1188e5
|
@ -359,6 +359,36 @@ class IImageEngine(ABC):
|
||||||
:param str out_file_path: eg './white_estimate.tiff'
|
:param str out_file_path: eg './white_estimate.tiff'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def subtract(self, image1, image2):
|
||||||
|
"""
|
||||||
|
computes the difference image1-image2
|
||||||
|
|
||||||
|
:param IImage image1:
|
||||||
|
:param IImage image2:
|
||||||
|
:rtype IImage: image1-image2
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def divide(self, numerator_image, denominator_image):
|
||||||
|
"""
|
||||||
|
computes the division numerator_image/denominator_image
|
||||||
|
|
||||||
|
:param IImage numerator_image:
|
||||||
|
:param IImage denominator_image:
|
||||||
|
:rtype IImage: numerator_image/denominator_image
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def abs(self, image):
|
||||||
|
"""
|
||||||
|
computes the absolute value of each pixel in the input image
|
||||||
|
|
||||||
|
:param IImage image:
|
||||||
|
:rtype IImage: an image containing the absolute values of the input image
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def compute_edge_transform(self, image):
|
def compute_edge_transform(self, image):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -261,11 +261,20 @@ class IJImageEngine(IImageEngine):
|
||||||
if not os.path.isfile(out_file_path):
|
if not os.path.isfile(out_file_path):
|
||||||
assert False, 'failed to create %s' % out_file_path
|
assert False, 'failed to create %s' % out_file_path
|
||||||
|
|
||||||
|
def subtract(self, image1, image2):
|
||||||
|
ic = ImageCalculator()
|
||||||
|
result_image = ic.run("Subtract create float", image1.ij_image, image2.ij_image)
|
||||||
|
return IJImage(self, result_image)
|
||||||
|
|
||||||
def divide(self, numerator_image, denominator_image):
|
def divide(self, numerator_image, denominator_image):
|
||||||
ic = ImageCalculator()
|
ic = ImageCalculator()
|
||||||
result_image = ic.run("Divide create float", numerator_image.ij_image, denominator_image.ij_image)
|
result_image = ic.run("Divide create float", numerator_image.ij_image, denominator_image.ij_image)
|
||||||
return IJImage(self, result_image)
|
return IJImage(self, result_image)
|
||||||
|
|
||||||
|
def abs(self, image):
|
||||||
|
result_image = image.clone()
|
||||||
|
result_image.ij_image.getProcessor().abs()
|
||||||
|
return result_image
|
||||||
|
|
||||||
def compute_edge_transform(self, image):
|
def compute_edge_transform(self, image):
|
||||||
result_image = image.clone()
|
result_image = image.clone()
|
||||||
|
|
|
@ -13,9 +13,15 @@ from ij.plugin import ZProjector # pylint: disable=import-error
|
||||||
from ij import WindowManager # pylint: disable=import-error
|
from ij import WindowManager # pylint: disable=import-error
|
||||||
from catalog import Sequence, ImageCatalog
|
from catalog import Sequence, ImageCatalog
|
||||||
import telemos
|
import telemos
|
||||||
from imageengine import IImageProcessingDebugger, NullDebugger
|
from imageengine import IImageEngine, IImageProcessingDebugger, NullDebugger, StackImageFeeder, IHyperStack
|
||||||
# greeting = "Hello, " + name + "!"
|
# greeting = "Hello, " + name + "!"
|
||||||
|
|
||||||
|
import abc
|
||||||
|
# h5py is not available in fiji's jython
|
||||||
|
from ncsa.hdf.hdf5lib import HDFArray # found in FIJI_HOME/jars/jhdf5-14.12.6.jar
|
||||||
|
|
||||||
|
ABC = abc.ABCMeta('ABC', (object,), {})
|
||||||
|
|
||||||
class ImageLogger(IImageProcessingDebugger):
|
class ImageLogger(IImageProcessingDebugger):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -111,23 +117,13 @@ class Lipase(object):
|
||||||
best_z_index = z_index
|
best_z_index = z_index
|
||||||
return best_z_index
|
return best_z_index
|
||||||
|
|
||||||
def compute_sequence_median_image(self, sequence, channel_id='DM300_nofilter_vis'):
|
def estimate_background(self, sequence):
|
||||||
''' computes for each pixel its median value along time, so that in the end we have a single image that only shows structures (things that don't move such as the traps)
|
|
||||||
|
|
||||||
:param Sequence sequence:
|
|
||||||
:param str channel_id:
|
|
||||||
'''
|
|
||||||
stack = sequence.as_stack(channel_id)
|
|
||||||
projector = ZProjector()
|
|
||||||
median_image = projector.run(stack, 'median')
|
|
||||||
# pixel_value_along_time = ByteProcessor.createProcessor(stack.getDepth(), 1)
|
|
||||||
return median_image
|
|
||||||
|
|
||||||
def extract_background(self, sequence):
|
|
||||||
channel_id = 'DM300_nofilter_vis'
|
channel_id = 'DM300_nofilter_vis'
|
||||||
sequence.as_stack(channel_id).show()
|
# sequence.as_stack(channel_id).show()
|
||||||
background_image = self.compute_sequence_median_image(sequence, channel_id)
|
visible_stack = sequence.as_hyperstack(selected_channel_ids=[channel_id])
|
||||||
background_image.show()
|
visible_image_feeder = StackImageFeeder(visible_stack)
|
||||||
|
ie = IImageEngine.get_instance()
|
||||||
|
background_image = ie.compute_median(visible_image_feeder)
|
||||||
return background_image
|
return background_image
|
||||||
|
|
||||||
def estimate_white(self, sequence, channel_id):
|
def estimate_white(self, sequence, channel_id):
|
||||||
|
@ -142,6 +138,80 @@ class Lipase(object):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class IBackgroundEstimator(ABC):
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def estimate_background(self, visible_traps_sequence):
|
||||||
|
"""
|
||||||
|
:param IHyperStack visible_traps_sequence:
|
||||||
|
:rtype IImage: the estimated background image
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class EmptyFrameBackgroundEstimator(IBackgroundEstimator):
|
||||||
|
|
||||||
|
def __init__(self, empty_frame_index):
|
||||||
|
"""
|
||||||
|
:param int empty_frame_index: the index of the frame that is chosen as background (it is supposed to not contain any particle, only static objects (traps, lens spots, etc.))
|
||||||
|
"""
|
||||||
|
IBackgroundEstimator.__init__(self)
|
||||||
|
self.empty_frame_index = empty_frame_index
|
||||||
|
|
||||||
|
def estimate_background(self, visible_traps_sequence):
|
||||||
|
background_estimate = visible_traps_sequence.get_image(frame_index=self.empty_frame_index).clone()
|
||||||
|
return background_estimate
|
||||||
|
|
||||||
|
|
||||||
|
class GlobulesAreaEstimator(object):
|
||||||
|
""" An image processing suite targetted to visible images of traps with moving particles (globules)
|
||||||
|
"""
|
||||||
|
|
||||||
|
class FrameResult(object):
|
||||||
|
""" The results related to a frame
|
||||||
|
"""
|
||||||
|
def __init__(self, globules_area_ratio):
|
||||||
|
"""
|
||||||
|
:param int globules_area_ratio: area of globules in this frame, expressed as a ratio of the frame area
|
||||||
|
"""
|
||||||
|
self.globules_area_ratio = globules_area_ratio
|
||||||
|
|
||||||
|
class Results(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.frames = {}
|
||||||
|
|
||||||
|
def __init__(self, background_estimator, particle_threshold):
|
||||||
|
"""
|
||||||
|
:param IBackgroundEstimator background_estimator: the method that is used to estimate the background image.
|
||||||
|
:param float particle_threshold: if the absolute difference between the pixel and its corresponding value in the background image exceeds this, value, then this pixel is considered as a part of a particle
|
||||||
|
"""
|
||||||
|
self.background_estimator = background_estimator
|
||||||
|
self.particle_threshold = particle_threshold
|
||||||
|
|
||||||
|
def detect_particles(self, visible_traps_sequence):
|
||||||
|
"""
|
||||||
|
:param IHyperStack visible_traps_sequence:
|
||||||
|
"""
|
||||||
|
background_image = self.background_estimator.estimate_background(visible_traps_sequence)
|
||||||
|
ie = IImageEngine.get_instance()
|
||||||
|
# particles_stack = ie.create_hyperstack(width=background_image.width, height=background_image.height, num_slices=1, num_frames=visible_traps_sequence.num_frames(), num_channels=1, pixel_type=PixelType.F32)
|
||||||
|
|
||||||
|
results = GlobulesAreaEstimator.Results()
|
||||||
|
frame_area = background_image.get_width() * background_image.get_height()
|
||||||
|
for frame_index in range(visible_traps_sequence.num_frames()):
|
||||||
|
particle_image = ie.subtract(visible_traps_sequence.get_image(frame_index=frame_index), background_image)
|
||||||
|
abs_particle_image = ie.abs( particle_image )
|
||||||
|
is_particle = ie.threshold(abs_particle_image, self.particle_threshold)
|
||||||
|
measured_mean_value = is_particle.get_mean_value()
|
||||||
|
particle_pixel_value = 255.0
|
||||||
|
num_pixels = is_particle.get_width() * is_particle.get_height()
|
||||||
|
num_particle_pixels = int(measured_mean_value * num_pixels / particle_pixel_value)
|
||||||
|
print("num_particle_pixels: %d " % num_particle_pixels)
|
||||||
|
globules_area_ratio = float(num_particle_pixels)/frame_area
|
||||||
|
results.frames[frame_index] = GlobulesAreaEstimator.FrameResult(globules_area_ratio=globules_area_ratio)
|
||||||
|
# particles_stack.set_image(frame_index=frame_index, image=particle_image)
|
||||||
|
|
||||||
|
|
||||||
def test_find_white():
|
def test_find_white():
|
||||||
|
|
||||||
raw_images_root = '/Users/graffy/ownCloud/ipr/lipase/raw-images'
|
raw_images_root = '/Users/graffy/ownCloud/ipr/lipase/raw-images'
|
||||||
|
|
|
@ -70,7 +70,7 @@ class TrapsDetector(object):
|
||||||
the idea of this method is as follows:
|
the idea of this method is as follows:
|
||||||
1. allow the user to provide an axis-aligned bounding box that contains a trap, and use this area as a pattern to be searched in all images of the sequence. Provided the tolerance is big enough, this should find all traps in the sequence.
|
1. allow the user to provide an axis-aligned bounding box that contains a trap, and use this area as a pattern to be searched in all images of the sequence. Provided the tolerance is big enough, this should find all traps in the sequence.
|
||||||
2. compute a clean (without globules) trap, by computing for each pixel, the median value from all traps for that pixel.
|
2. compute a clean (without globules) trap, by computing for each pixel, the median value from all traps for that pixel.
|
||||||
3. For each trap location, remove the trap by substracting the clean trap. This should leave the globules only.
|
3. For each trap location, remove the trap by subtracting the clean trap. This should leave the globules only.
|
||||||
|
|
||||||
At the end of this image processing, we expect to only have the globules in the image.
|
At the end of this image processing, we expect to only have the globules in the image.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -15,6 +15,9 @@ from lipase.template_matcher import TemplateMatcher # pylint: disable=import-er
|
||||||
from lipase.traps_detector import TrapsDetector # 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.catalog import ImageCatalog, Sequence # pylint: disable=import-error
|
||||||
from lipase.lipase import Lipase, ImageLogger # pylint: disable=import-error
|
from lipase.lipase import Lipase, ImageLogger # pylint: disable=import-error
|
||||||
|
from lipase.lipase import GlobulesAreaEstimator, EmptyFrameBackgroundEstimator # pylint: disable=import-error
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestLipase(unittest.TestCase):
|
class TestLipase(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -92,10 +95,19 @@ class TestLipase(unittest.TestCase):
|
||||||
print("measured_mean_value: %f" % measured_mean_value)
|
print("measured_mean_value: %f" % measured_mean_value)
|
||||||
self.assertAlmostEqual(measured_mean_value, expected_mean_value, delta=0.01)
|
self.assertAlmostEqual(measured_mean_value, expected_mean_value, delta=0.01)
|
||||||
|
|
||||||
|
def test_visible_traps_sequence_processing(self):
|
||||||
|
|
||||||
|
traps_sequence = self.catalog.sequences['res_soleil2018/GGH/GGH_2018_cin2_phiG_I_327_vis_-40_1/Pos0']
|
||||||
|
visible_traps_sequence = traps_sequence.as_hyperstack(['DM300_nofilter_vis'])
|
||||||
|
background_estimator = EmptyFrameBackgroundEstimator(empty_frame_index=39)
|
||||||
|
processor = GlobulesAreaEstimator(background_estimator=background_estimator, particle_threshold=2000.0)
|
||||||
|
processor.detect_particles(visible_traps_sequence)
|
||||||
|
|
||||||
# def test_lipase_process(self):
|
# def test_lipase_process(self):
|
||||||
# lipase = Lipase(self.catalog, debugger=NullDebugger())
|
# lipase = Lipase(self.catalog, debugger=NullDebugger())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_script():
|
def run_script():
|
||||||
|
|
||||||
# unittest.main() # this would result in : ImportError: No module named __main__
|
# unittest.main() # this would result in : ImportError: No module named __main__
|
||||||
|
|
Loading…
Reference in New Issue