now Compute_Globules_Area displays the images of detected particles

- this allows the user to check if the image processing settings are correct
- fixes #5
This commit is contained in:
Guillaume Raffy 2021-11-22 14:20:58 +01:00
parent 71b1ed5b8b
commit 4dba5a99af
6 changed files with 41 additions and 518 deletions

View File

@ -4,6 +4,7 @@
#@output ImagePlus OUTPUT_PLOT #@output ImagePlus OUTPUT_PLOT
"""This script is supposed to be launched from fiji's jython interpreter """This script is supposed to be launched from fiji's jython interpreter
This plugin estimates the global area of globules
""" """
@ -15,16 +16,41 @@ print('python version %s' % sys.version) # prints python version
from lipase.settings import UserSettings from lipase.settings import UserSettings
# from lipase import Lipase, ImageLogger # from lipase import Lipase, ImageLogger
from lipase.imageengine import IImageEngine, PixelType, StackImageFeeder from lipase.imageengine import IImageEngine, PixelType, StackImageFeeder, IImageProcessingDebugger
from lipase.imagej.ijimageengine import IJImageEngine from lipase.imagej.ijimageengine import IJImageEngine
from lipase.lipase import UserProvidedBackground, GlobulesAreaEstimator from lipase.lipase import UserProvidedBackground, GlobulesAreaEstimator
# from lipase.improc.improlis import IMovieProcessListener
# from ij import IJ # pylint: disable=import-error # from ij import IJ # pylint: disable=import-error
# from ij.gui import GenericDialog, DialogListener # pylint: disable=import-error # from ij.gui import GenericDialog, DialogListener # pylint: disable=import-error
# from java.awt.event import ItemListener # pylint: disable=import-error # from java.awt.event import ItemListener # pylint: disable=import-error
import ij.gui import ij.gui
from jarray import zeros, array from jarray import zeros, array
class IsParticleCollector(IImageProcessingDebugger):
def __init__(self, num_frames):
IImageProcessingDebugger.__init__(self)
ie = IImageEngine.get_instance()
self.is_particle = None
self.num_frames = num_frames
self.frame_index = 0
def on_image(self, image, image_id):
# print('IsParticleCollector.on_image : image_id = %s' % image_id)
if image_id == 'is_particle':
ie = IImageEngine.get_instance()
if self.is_particle is None:
self.is_particle = ie.create_hyperstack(width=image.get_width(), height=image.get_height(), num_channels=1, num_slices=1, num_frames=self.num_frames, pixel_type=PixelType.U8)
self.is_particle.set_image(image=image, frame_index=self.frame_index)
print("IsParticleCollector.on_image : %f %f" % image.get_value_range())
self.is_particle.set_image(image=image, frame_index=self.frame_index)
self.frame_index += 1
def on_hyperstack(self, hyperstack, hyperstack_id):
# print('IsParticleCollector.on_hyperstack : hyperstack_id = %s' % hyperstack_id)
pass
def run_script(): def run_script():
global INPUT_STACK # pylint:disable=global-variable-not-assigned global INPUT_STACK # pylint:disable=global-variable-not-assigned
global INPUT_BACKGROUND # pylint:disable=global-variable-not-assigned global INPUT_BACKGROUND # pylint:disable=global-variable-not-assigned
@ -41,8 +67,8 @@ def run_script():
background_estimator = UserProvidedBackground(background_image=src_background) background_estimator = UserProvidedBackground(background_image=src_background)
processor = GlobulesAreaEstimator(background_estimator=background_estimator, particle_threshold=PARTICLE_THRESHOLD) # pylint: disable=undefined-variable processor = GlobulesAreaEstimator(background_estimator=background_estimator, particle_threshold=PARTICLE_THRESHOLD) # pylint: disable=undefined-variable
is_particle_collector = IsParticleCollector(num_frames = src_hyperstack.num_frames())
results = processor.detect_particles(src_hyperstack) results = processor.detect_particles(src_hyperstack, image_proc_debugger=is_particle_collector)
# save_hdf5_file('results.h5', results) # save_hdf5_file('results.h5', results)
# results file could be checked with "h5dump --xml ./lipase.git/results.h5" # results file could be checked with "h5dump --xml ./lipase.git/results.h5"
@ -50,8 +76,16 @@ def run_script():
x = array(results['frame index'].elements, 'f') x = array(results['frame index'].elements, 'f')
y = array(results['globules_area_ratio'].elements, 'f') y = array(results['globules_area_ratio'].elements, 'f')
plot = ij.gui.Plot('my_title', 'frame', 'globules area ratio', x, y) plot = ij.gui.Plot('globules area graph', 'frame', 'globules area ratio', x, y)
plot.show() plot.show()
print("Compute_Globules_Area is_particle_collector.is_particle size : %d" % (is_particle_collector.is_particle.num_frames()))
first_frame = is_particle_collector.is_particle.get_image(frame_index=0, slice_index=0, channel_index=0)
print("Compute_Globules_Area is_particle_collector.is_particle size : %f %f" % first_frame.get_value_range())
is_particle_collector.is_particle.hyperstack.setTitle('is_particle')
is_particle_collector.is_particle.hyperstack.show()
OUTPUT_PLOT = plot OUTPUT_PLOT = plot

View File

@ -1 +0,0 @@
from .improlis import MovieProcessDebugger

View File

@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
installation des modules nécéssaires sur macos x :
sudo port install opencv +python27
sudo port install py-pyqt4
sudo port install py-matplotlib
"""
from matplotlib import pyplot
from matplotlib.backends.backend_pdf import PdfPages
def setLatexLookingFonts():
import matplotlib
matplotlib.rcParams['mathtext.fontset'] = 'stix'
matplotlib.rcParams['font.family'] = 'STIXGeneral'
#matplotlib.pyplot.title(r'ABC123 vs $\mathrm{ABC123}^{123}$')
class Signal:
def __init__(self, signal, name=None):
self.m_signalValues = signal
self.m_name = name
class Point2D(object):
def __init__(self, x, y):
self.m_x = x
self.m_y = y
@property
def x(self):
return self.m_x
@property
def y(self):
return self.m_y
class ScatterPlot(object):
def __init__(self, xAxisDesc = None, yAxisDesc = None):
self.m_points = []
self.m_xAxisDesc = xAxisDesc
self.m_yAxisDesc = yAxisDesc
def append(self, point2d ):
self.m_points.append(point2d)
@property
def xAxisDesc(self):
return self.m_xAxisDesc
@property
def yAxisDesc(self):
return self.m_yAxisDesc
def setXAxisDesc(self, description):
self.m_xAxisDesc = description
def setYAxisDesc(self, description):
self.m_yAxisDesc = description
def __iter__(self):
return iter(self.m_points)
def saveScatterPlot( scatterPlot, pdfFileName):
setLatexLookingFonts()
fig = pyplot.figure()
pyplot.subplot(1,1,1)
x = []
y = []
for point in scatterPlot:
x.append( point.x )
y.append( point.y )
pyplot.scatter(x, y, marker='x')
if scatterPlot.xAxisDesc is not None:
pyplot.xlabel(scatterPlot.xAxisDesc)
if scatterPlot.yAxisDesc is not None:
pyplot.ylabel(scatterPlot.yAxisDesc)
#print('saving to '+pdfFileName)
pp = PdfPages(pdfFileName)
pp.savefig( fig )
pp.close()
pyplot.close(fig)
def saveGraph(signal, pdfFileName):
setLatexLookingFonts()
fig = pyplot.figure()
pyplot.subplot(1,1,1)
pyplot.plot(signal, marker='x')
print('saving to '+pdfFileName)
pp = PdfPages(pdfFileName)
pp.savefig( fig )
pp.close()
pyplot.close(fig)
def saveMultiGraph(signals, pdfFileName):
setLatexLookingFonts()
fig = pyplot.figure()
pyplot.subplot(1,1,1)
for signal in signals:
print('plotting signal %s' % str(signal.m_signalValues.shape))
pyplot.plot(signal.m_signalValues, label=signal.m_name)
print('saving to '+pdfFileName)
pyplot.legend()
pp = PdfPages(pdfFileName)
pp.savefig( fig )
pp.close()
pyplot.close(fig)

View File

@ -1,238 +0,0 @@
# -*- coding: utf-8 -*-
"""
installation des modules nécéssaires sur macos x :
sudo port install opencv +python27
sudo port install py-pyqt4
sudo port install py-matplotlib
"""
#from PyQt4.QtGui import *
#from PyQt4.QtCore import *
#import sys
import cv2
#from cv2 import cv
import numpy
import os
import sys
#from matplotlib import pyplot as plt
#from matplotlib.backends.backend_pdf import PdfPages
sys.path.append('../Libraries/python')
import scene2d
import graphing
class Line2D(object):
def __init__( self, point1, point2 ):
self.m_point1 = point1
self.m_point2 = point2
class IImageProcessListener(object):
"""
an abstract base class that handle events that happen during an image processing. This provides a flexible way to debug image processing
"""
def __init__(self):
self.m_imageIndex = 0
def onSignal(self, signal, signalName):
"""
a new signal (1d array) has just been computed
"""
assert( False )
def onImage(self, image, imageName):
"""
a new image has just been computed
"""
assert( False )
class NullImageProcessListener(IImageProcessListener):
def __init__(self):
IImageProcessListener.__init__( self )
def onSignal(self, signal, signalName):
pass
def onBaseImage(self, image, imageName=''):
pass
def onImage(self, image, imageName):
pass
def onPoint(self, point, layerPath, label=None ):
pass
def onLine(self, line, layerPath, label=None ):
pass
def onCircle(self, circle, layerPath, label=None ):
pass
class ImageProcessDebugger(IImageProcessListener):
def __init__(self, outputFolder='./debug'):
IImageProcessListener.__init__( self )
self.m_scene = None
self.m_baseImageName = None
self.setOutputFolder(outputFolder)
def __del__(self):
#assert( self.m_scene is not None )
if self.m_scene:
self.m_scene.saveAsSvg('%s/%s.svg' % (self.m_outputFolder, self.m_baseImageName))
self.m_scene = None
def setOutputFolder(self, outputFolderPath):
if self.m_scene:
self.m_scene.saveAsSvg('%s/%s.svg' % (self.m_outputFolder, self.m_baseImageName))
self.m_scene = None
self.m_outputFolder = outputFolderPath
pathParts = self.m_outputFolder.split('/')
path = ''
for i in range(len(pathParts)):
if i != 0:
path += '/'
path += pathParts[i]
try:
os.mkdir(path)
except (OSError): # this exception is raised if the folder already exists
pass
def onSignal(self, signal, signalName):
graphing.saveGraph(signal, '%s/%s.pdf' % (self.m_outputFolder, signalName))
def onSignals(self, signals, signalName):
graphing.saveMultiGraph(signals, '%s/%s.pdf' % (self.m_outputFolder, signalName))
def onImage(self, image, imageName):
#plt.subplot(1,1,1),plt.imshow(contourImage,cmap = 'gray')
#plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
#plt.show()
filePath = '%s/%s.tif' % (self.m_outputFolder, imageName)
saveImage( image, filePath )
def onBaseImage(self, image, imageName=''):
assert(imageName != '')
self.m_baseImageName=imageName
filePath = '%s/%s.png' % (self.m_outputFolder, imageName)
saveImage( image, filePath )
self.m_scene = scene2d.Scene()
assert( self.m_scene is not None )
if self.m_scene:
self.m_scene.setBaseImage(scene2d.Image(filePath))
def onPoint(self, point, layerPath, label=None ):
assert( self.m_scene )
self.m_scene.getLayer(layerPath).addChild(scene2d.Point(point, label))
def onLine(self, line, layerPath, label=None ):
assert( self.m_scene )
self.m_scene.getLayer(layerPath).addChild(scene2d.Line(line, label))
def onCircle(self, circle, layerPath, label=None ):
assert( self.m_scene )
self.m_scene.getLayer(layerPath).addChild(scene2d.Circle(circle, label))
class IMovieProcessListener(object):
"""
an abstract base class that handle events that happen durin a movie image processing. This provides a flexible way to debug imageprocessing
"""
def __init__(self, imageProcessListener ):
self.m_imageIndex = 0
self.m_imageProcessListener = imageProcessListener
def onImageProcessingStart(self):
"""
the processing of a new image starts
"""
self.m_imageIndex += 1
def onImageProcessingEnd(self):
"""
the processing of a new image ends
"""
pass
def onSignal(self, signal, signalName):
"""
a new signal (1d array) has just been computed
"""
self.m_imageProcessListener.onSignal( signal, signalName )
def onImage(self, image, imageName):
"""
a new image has just been computed
"""
self.m_imageProcessListener.onImage( image, imageName )
class NullMovieProcessListener(IMovieProcessListener):
def __init__(self):
IMovieProcessListener.__init__( self, NullImageProcessListener() )
def onSignal(self, signal, signalName):
pass
def onImage(self, image, imageName):
pass
def saveImage(image, filePath):
print('%s original image type : %s range=(%f:%f)' % (filePath, str(image.dtype), image.min(), image.max()))
if image.dtype == numpy.bool:
cv2.imwrite(filePath, image.astype(numpy.uint8)*255)
elif image.dtype == numpy.uint16:
fileExt = filePath.split('.')[-1]
if fileExt == 'tif':
# tif file format supports 16 bits per pixel
cv2.imwrite(filePath, image)
else:
u8Image = cv2.normalize(image, alpha=0.0, beta=255.0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
cv2.imwrite(filePath, u8Image)
elif image.dtype == numpy.float32 or image.dtype == numpy.float64:
print('image range : %d-%d' % (image.min(), image.max()))
u8Image = cv2.normalize(image, numpy.array(image.shape, dtype=numpy.uint8), alpha=0.0, beta=255.0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
print('u8Image range : %d-%d' % (u8Image.min(), u8Image.max()))
cv2.imwrite(filePath, u8Image)
elif image.dtype == numpy.int32:
print('image range : %d-%d' % (image.min(), image.max()))
u8Image = cv2.normalize(image, numpy.array(image.shape, dtype=numpy.uint8), alpha=0.0, beta=255.0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
print('u8Image range : %d-%d' % (u8Image.min(), u8Image.max()))
cv2.imwrite(filePath, u8Image)
else:
#assert( False )
cv2.imwrite(filePath, image)
class MovieProcessDebugger(IMovieProcessListener):
def __init__(self):
IMovieProcessListener.__init__( self, ImageProcessDebugger() )
self.m_outputFolder='./debug'
self.m_scene = None
try:
os.mkdir(self.m_outputFolder)
except (OSError): # this exception is raised if the folder already exists
pass
def onImageProcessingStart(self):
"""
the processing of a new image starts
"""
IMovieProcessListener.onImageProcessingStart(self)
self.m_imageProcessListener.setOutputFolder( '%s/image%d' % (self.m_outputFolder, self.m_imageIndex) )
def onImageProcessingEnd(self):
IMovieProcessListener.onImageProcessingEnd(self)
self.m_imageProcessListener.setOutputFolder( None )
def onSignal(self, signal, signalName):
self.m_imageProcessListener.onSignal( signal, signalName )
def onSignals(self, signals, signalName):
self.m_imageProcessListener.onSignal( signals, signalName )
def onImage(self, image, imageName):
self.m_imageProcessListener.onImage( image, imageName )
def onBaseImage(self, image, imageName):
self.m_imageProcessListener.onBaseImage( image, imageName )
def onPoint(self, point, layerPath, label=None ):
self.m_imageProcessListener.onPoint( image, point, layerPath, label )
def onCircle(self, circle, layerPath, label=None ):
self.m_imageProcessListener.onPoint( image, circle, layerPath, label )
class IImageProcessor(object):
def __init__(self, movieProcessListener = NullMovieProcessListener()):
self.m_movieProcessListener = movieProcessListener
def processImage(self, image):
assert( False ) # this method is not supposed to be called
def get_image_process_listener(self):
return self.m_movieProcessListener
def findEdges(image):
sx = cv2.Sobel(image, cv2.CV_32F, dx=1, dy=0, ksize=3)
sy = cv2.Sobel(image, cv2.CV_32F, dx=0, dy=1, ksize=3)
return numpy.sqrt(sx*sx + sy*sy)

View File

@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
import cv2
class ISceneNodeVisitor(object):
def __init__(self):
pass
def visitPoint(self, point):
assert( False )
def visitImage(self, image):
assert( False )
def visitLayer(self, layer):
assert( False )
def visitScene(self, scene):
assert( False )
def visitLine(self, line):
assert( False )
class ISceneNode(object):
def __init__(self):
pass
def onVisit(self, visitor ):
assert( False )
class Point(ISceneNode):
def __init__(self, coords, label = None):
self.m_coords = coords
self.m_label = label
def onVisit( self, visitor ):
visitor.visitPoint( self )
class Line(ISceneNode):
def __init__(self, line, label = None):
self.m_from = line[0]
self.m_to = line[1]
self.m_label = label
def onVisit( self, visitor ):
visitor.visitLine( self )
class Circle(ISceneNode):
def __init__(self, circle, label = None):
self.m_center = (circle[0], circle[1])
self.m_radius = circle[2]
self.m_label = label
def onVisit( self, visitor ):
visitor.visitCircle( self )
class Image(ISceneNode):
def __init__(self, imageFilePath):
self.m_imageFilePath = imageFilePath
def onVisit(self, visitor ):
visitor.visitImage( self )
def getFilePath(self):
return self.m_imageFilePath
def getSize(self):
cv_img = cv2.imread(self.m_imageFilePath, cv2.IMREAD_ANYDEPTH)
return cv_img.shape
class Layer(ISceneNode):
def __init__(self, layerName):
self.m_name = layerName
self.m_children = []
self.m_layers = {}
def getName(self):
return self.m_name
def addChild(self, childNode):
self.m_children.append( childNode )
def onVisit(self, visitor ):
visitor.visitLayer( self )
def addLayer(self, layer):
print('adding layer %s' % layer.getName())
self.m_layers[ layer.getName() ] = layer
class Scene(ISceneNode):
def __init__(self):
self.m_image = None
self.m_rootLayer = Layer('root')
def onVisit(self, visitor ):
visitor.visitScene( self )
def setBaseImage( self, image ):
self.m_image = image
def getLayer(self, layerPath):
pathParts = layerPath.split('/')
parentLayer = self.m_rootLayer
for layerName in pathParts:
if layerName not in parentLayer.m_layers:
parentLayer.addLayer( Layer(layerName) )
parentLayer = parentLayer.m_layers[layerName]
return parentLayer
def saveAsSvg(self, filePath):
visitor = SvgExporter( filePath )
self.onVisit(visitor)
class SvgExporter(ISceneNodeVisitor):
def __init__(self, svgFilePath):
self.m_svgFilePath = svgFilePath
def visitPoint(self, point):
radius = 1.0
self.m_f.write('<circle cx="%.3f" cy="%.3f" r="%.3f"/>\n' % (point.m_coords[0], point.m_coords[1], radius) )
if point.m_label is not None :
self.m_f.write('<text x="%.3f" y="%.3f">%s</text>\n' % (point.m_coords[0], point.m_coords[1], point.m_label) )
def visitLine(self, line):
radius = 1.0
self.m_f.write('<line x1="%.3f" x2="%.3f" y1="%.3f" y2="%.3f"/>\n' % (line.m_from[0], line.m_to[0], line.m_from[1], line.m_to[1]) )
if line.m_label is not None :
self.m_f.write('<text x="%.3f" y="%.3f">%s</text>\n' % (line.m_from[0], line.m_from[1], point.m_label) )
def visitCircle(self, circle):
radius = 1.0
self.m_f.write('<circle cx="%.3f" cy="%.3f" r="%.3f"/>\n' % (circle.m_center[0], circle.m_center[1], circle.m_radius) )
if circle.m_label is not None :
self.m_f.write('<text x="%.3f" y="%.3f">%s</text>\n' % (circle.m_center[0], circle.m_center[1], point.m_label) )
def visitImage(self, image):
imageSize = image.getSize()
self.m_f.write('<image xlink:href="%s" x="0" y="0" width="%d" height="%d"/>\n' % (image.getFilePath().split('/')[-1], imageSize[1], imageSize[0]) )
def visitLayer(self, layer):
self.m_f.write('<g id="%s" style="fill:red;stroke:cyan">\n' % layer.getName())
for child in layer.m_children:
child.onVisit( self )
for layer in layer.m_layers.itervalues():
layer.onVisit( self )
self.m_f.write('</g>\n')
def visitScene(self, scene):
with open(self.m_svgFilePath, 'wt') as self.m_f:
print('exporting scene2d as %s' % self.m_svgFilePath)
self.m_f.write('<?xml version="1.0"?>')
self.m_f.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="3008" height="2280" onload="init()">')
self.m_f.write('<defs>')
self.m_f.write('<script type="text/ecmascript" xlink:href="MeniscusUI.js"/>')
self.m_f.write('</defs>')
if scene.m_image is not None:
scene.m_image.onVisit(self)
scene.m_rootLayer.onVisit(self)
#f.write('<image xlink:href="FullImage0235.png" x="0" y="0" width="3008" height="2280"/>')
self.m_f.write('</svg>')

View File

@ -173,7 +173,6 @@ class EmptyFrameBackgroundEstimator(IBackgroundEstimator):
background_estimate = visible_traps_sequence.get_image(frame_index=self.empty_frame_index).clone() background_estimate = visible_traps_sequence.get_image(frame_index=self.empty_frame_index).clone()
return background_estimate return background_estimate
class GlobulesAreaEstimator(object): class GlobulesAreaEstimator(object):
""" An image processing suite targetted to visible images of traps with moving particles (globules) """ An image processing suite targetted to visible images of traps with moving particles (globules)
""" """
@ -186,7 +185,7 @@ class GlobulesAreaEstimator(object):
self.background_estimator = background_estimator self.background_estimator = background_estimator
self.particle_threshold = particle_threshold self.particle_threshold = particle_threshold
def detect_particles(self, visible_traps_sequence): def detect_particles(self, visible_traps_sequence, image_proc_debugger=NullDebugger()):
""" """
:param IHyperStack visible_traps_sequence: :param IHyperStack visible_traps_sequence:
:rtype: hdf5_data.Group :rtype: hdf5_data.Group
@ -206,6 +205,7 @@ class GlobulesAreaEstimator(object):
particle_image = ie.subtract(visible_traps_sequence.get_image(frame_index=frame_index), background_image) particle_image = ie.subtract(visible_traps_sequence.get_image(frame_index=frame_index), background_image)
abs_particle_image = ie.abs( particle_image ) abs_particle_image = ie.abs( particle_image )
is_particle = ie.threshold(abs_particle_image, self.particle_threshold) is_particle = ie.threshold(abs_particle_image, self.particle_threshold)
image_proc_debugger.on_image(is_particle, 'is_particle')
measured_mean_value = is_particle.get_mean_value() measured_mean_value = is_particle.get_mean_value()
particle_pixel_value = 255.0 particle_pixel_value = 255.0
num_pixels = is_particle.get_width() * is_particle.get_height() num_pixels = is_particle.get_width() * is_particle.get_height()