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' | ||||
|         """ | ||||
| 
 | ||||
|     @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 | ||||
|     def compute_edge_transform(self, image): | ||||
|         """ | ||||
|  |  | |||
|  | @ -261,11 +261,20 @@ class IJImageEngine(IImageEngine): | |||
|         if not os.path.isfile(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): | ||||
|         ic = ImageCalculator() | ||||
|         result_image = ic.run("Divide create float", numerator_image.ij_image, denominator_image.ij_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): | ||||
|         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 catalog import Sequence, ImageCatalog | ||||
| import telemos | ||||
| from imageengine import IImageProcessingDebugger, NullDebugger | ||||
| from imageengine import IImageEngine, IImageProcessingDebugger, NullDebugger, StackImageFeeder, IHyperStack | ||||
| # 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): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|  | @ -111,23 +117,13 @@ class Lipase(object): | |||
|                 best_z_index = z_index | ||||
|         return best_z_index | ||||
| 
 | ||||
|     def compute_sequence_median_image(self, sequence, channel_id='DM300_nofilter_vis'): | ||||
|         ''' 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): | ||||
|     def estimate_background(self, sequence): | ||||
|         channel_id = 'DM300_nofilter_vis' | ||||
|         sequence.as_stack(channel_id).show() | ||||
|         background_image = self.compute_sequence_median_image(sequence, channel_id) | ||||
|         background_image.show() | ||||
|         # sequence.as_stack(channel_id).show() | ||||
|         visible_stack = sequence.as_hyperstack(selected_channel_ids=[channel_id]) | ||||
|         visible_image_feeder = StackImageFeeder(visible_stack) | ||||
|         ie = IImageEngine.get_instance() | ||||
|         background_image = ie.compute_median(visible_image_feeder) | ||||
|         return background_image | ||||
| 
 | ||||
|     def estimate_white(self, sequence, channel_id): | ||||
|  | @ -142,6 +138,80 @@ class Lipase(object): | |||
|         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(): | ||||
| 
 | ||||
|     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: | ||||
|         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. | ||||
|         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. | ||||
|         """ | ||||
|  |  | |||
|  | @ -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.catalog import ImageCatalog, Sequence  # 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): | ||||
| 
 | ||||
|  | @ -92,10 +95,19 @@ 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_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): | ||||
|     #     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