"""
Folder watcher used for smart imaging
It first pop up a user input to choose the method to use for smart imaging anf if we are in a test mode (in this case we have to define an image folder and the job files will be saved in a subfolder)
Then it calls Config method from the corresponding script for smart imaging that pops up another user input window to configure this selected method.
Once this configuration is closed, the main script calls the Run method from the subscript with every image it founds while watching the folder
The Run method perform the smart imaging detection and return the objective coordinates for the high magnification acquisition.
The main script use this coordinates to generate the JobFile before going to the next image.
This is repeated until the max number of images set in the configuration window is reached (or until the macro is killed, otherwise the folder watching keeps going on)
This version is made for IM04 only since the metadata for objective... are extracted from the filename
"""
#@PrefService prefs
from __future__ import division
import os, time
from os.path import join,isdir,isfile
from ij import IJ
from fiji.util.gui import GenericDialogPlus
from java.lang.System import getProperty
from ast import literal_eval
from ROIdetection.SearchRoi import getSearchRoi, getRoiDim
#####################################################################################
def Process(ListFile,doJob):
'''call Run with each image and write Jobfile if specified'''
global count
for FileName in ListFile:
if IJ.escapePressed():
print "Escape"
IJ.log("Escape")
break
# Generate FilePath
FilePath = join(Path_ImageFolder,FileName)
# Check that it is a file and not a folder
if isdir(FilePath): # Filepath is a folder
print "Skip folder"+FilePath
continue
else: # Filepath is a file
# Report Progress
message = '\nProcess '+FileName
print message
IJ.log(message)
# Call Run and write job file
if doJob:
x,y = Run(FilePath)
# Check X,Y values
if x==None and y==None:
message = "Smart imaging returned None. No higher magnification imaging for this image"
print message
IJ.log(message)
else:
WriteJob(FilePath,FileName,x,y)
else: # doJob=False or Test mode -> dont write the job files
Run(FilePath)
# update count of processed image
count += 1
if count == CountMax :
message = '\nDONE - Processed all images'
print message
IJ.log(message)
break
# Create function that will write the job files
def WriteJob(ImagePath,ImageName,x,y):
'''
Function that will be call with each image after detection of the region of interest
x,y : pixel coordinates of the center of the ROI found by smart imaging
X0, Y0 : initial objective coordinates for this image (mm)
X,Y : Objective coordinates in mm with 3 decimal digits for high objective imaging
'''
global JobFolder,doSwitch
# Write job file for higher magnification for each processed image
IJ.log('Writing job file')
print 'Writing job file'
# Extract Metadata before writing job file - IM4 name convention here !
Well = ImageName[1:5] # A1 for instance
WellNum = int(ImageName[106:111]) # 1 to 96, convert to int to remove leading 0
PixelSize_Low = ImageName[34:39]
PixelSize_mm = int(PixelSize_Low)*10**-7 # /10000 -> um then /1000 -> mm
ObjIdx_Low = PixToObj[PixelSize_Low] # Get objective index from pixel size read from image name
Obj_Low = ObjToMag[ObjIdx_Low] # Magnification '2X','4X','10X' or '20X'
# initial objective coordinates at low resolution
X0 = int(ImageName[65:71]) /1000 # >0 in mm
Y0 = int(ImageName[74:80]) /1000 # >0 in mm
#Power = int(ImageName[43:47])
#Integration = int(ImageName[51:55])
# Recover image dimension
Image = IJ.openImage(ImagePath)
Image_Width = Image.width
Image_Height = Image.height
# Generate new coordinates for the objective from detected central ROI pixel
X = X0 + (-Image_Width/2 + x)*PixelSize_mm # result in mm
Y = Y0 + (-Image_Height/2 + Image_Height -y)*PixelSize_mm # for Y consider the 0 for pixel coordinates is at the bottom. Substract half the image (we are at the bottom), then add the Image height (we are at the top) then substract the Y center (template + tempalte Height)
X = round(X,3) # allow only 3 decimal
Y = round(Y,3)
# Create IMSF file
Path_TmpFileName = os.path.join(JobFolder, ImageName+".tmp") # first created as a tmp file to prevent machine from reading itthen rename as .job
JobFile = open(Path_TmpFileName,'w')
JobFile.write("SetWellNo({})\n".format(WellNum))
JobFile.write("SetMeta('Well Coordinate','{}')\n".format(Well))
JobFile.write("SetMeta('Well Pos in Well','1')\n")
JobFile.write("GotoXY({:.3f},{:.3f},'Abs')\n".format(X,Y)) # x,y as float 3-decimal, force the trailing 0 if 0 as last decimal
JobFile.write("SetMeta('Objective','{}')\n".format(HighObj_Mag)) # '2X','4X','10X' or '20X'
JobFile.write("SetMeta('Objective Pixel Size','{}')\n".format(PixelSize_High))
JobFile.write("SetObjective({})\n".format(ObjIdx_High))
'''
JobFile.write("SetLight(6,10,10,0.000)\n")
JobFile.write("AcquireAutofocus(7,100.000,2,'True','True')\n")
JobFile.write("SetLight(6,50,10,0.000,'Flash')\n")
JobFile.write("AcquireAutofocus(6,20.000,2,'True','True')\n")
JobFile.write("SetLight(6,50,10,0.000)\n")
JobFile.write("Acquire(1,0.000,1,'D:\IMAGING-DATA\IMAGES\SCRIPTS','True','SIdata')\n") # Images will be saved in a subfolder SIdata
'''
JobFile.write(Script+'\n')
# Switching off the light
JobFile.write("SetLight(0,0,0,0)\n")
if doSwitch:
# Going back to previous obj.
JobFile.write("SetMeta('Objective','{}')\n".format(Obj_Low))
JobFile.write("SetMeta('Objective Pixel Size','{}')\n".format(PixelSize_Low))
JobFile.write("SetObjective({})\n".format(ObjIdx_Low))
else:
# The switch should be done for the very last well ?
pass
JobFile.close()
# Rename from tmp to job
Path_JobFileName = Path_TmpFileName[:-4]+'.job'
# Check that the job file is not already existing
if isfile(Path_JobFileName):
IJ.log('Overwriting existing JobFile')
print 'Overwriting existing JobFile'
os.remove(Path_JobFileName)
# Finally rename from .tmp to .job
os.rename(Path_TmpFileName,Path_JobFileName)
########################################################################################
# I - Open GUI to select method
# Try to recover previous input from persistence
Method0 = prefs.get("SImethod","Template matching")
CountMax0 = prefs.getInt("nWell",96) # this one fails
Test0 = prefs.getInt("Test",True) # for Boolean use getInt (see forum for bug)
Path_ImageFolder0 = prefs.get("ImagePathSI","ImageFolder")
HighObj_Mag0 = prefs.get("HighMag","10x")
doJob0 = prefs.getInt("WriteJob",False)
doSwitch0 = prefs.getInt("SwitchObj",True)
template = """SetLight(6,10,10,0.000)
AcquireAutofocus(7,100.000,2,'True','True')
SetLight(6,50,10,0.000,'Flash')
AcquireAutofocus(6,20.000,2,'True','True')
SetLight(6,50,10,0.000)
Acquire(1,0.000,1,'D:\IMAGING-DATA\IMAGES\SCRIPTS','True','SIdata')
"""
Script0 = prefs.get(None,"SIscript",template)
# Input GUI
Win = GenericDialogPlus("Smart Imaging")
Win.addChoice("Smart imaging method",["Template matching","Keypoint matching","Run IJmacro"],Method0)
Win.addNumericField("Number of well to process",CountMax0,0)
Win.addCheckbox("Test mode (if not on the microscope)",Test0)
Win.addDirectoryField("Directory to watch (in test mode)",Path_ImageFolder0)
Win.addChoice("Objective for smart imaging",["2x","4x","10x","20x"],HighObj_Mag0)
Win.addCheckbox("Write job files", doJob0) # JobFile are written anyway if we are not in Test mode
Win.addCheckbox("Switch to previous objective after each smart imaging job", doSwitch0)
# Add a text field with the smart imaging script
Win.addMessage("Copy/Paste a smart imaging script in the field below or use a template : ")
Win.addTextAreas(Script0,None,5,100)
# Add help button
Disclaimer = '''
The smart imaging is the implementation of feedback microscopy on Acquifer's imaging machine.
The idea is to use image recognition scripts to automatically detect the ROI in the low magnification images,
and perform the corresponding high magnification acquisition. See documentation.
'''
Win.addHelp(Disclaimer)
Win.showDialog()
if Win.wasOKed():
# Recover inputs
Method = Win.getNextChoice()
CountMax = Win.getNextNumber()
Test = Win.getNextBoolean()
Path_ImageFolder = Win.getNextString()
HighObj_Mag = Win.getNextChoice()
doJob = Win.getNextBoolean()
doSwitch = Win.getNextBoolean()
Script = Win.getNextText()
# correspondance used to write the job file
PixToObj = {'32500':1, '16250':2, '6500':3, '3250':4} # map pixel size from the filename to objective index
ObjToPix = {v: k for k, v in PixToObj.iteritems()} # invert mapping
MagToObj = {'2x':1, '4x':2, '10x':3, '20x':4} # Mapping magnification to its objective index
ObjToMag = {v: k for k, v in MagToObj.iteritems()} # Mapping index to magnification
ObjIdx_High = MagToObj[HighObj_Mag] # Get objective index from magnification for SI given by user
PixelSize_High = ObjToPix[ObjIdx_High] # Get Pixel size from obj index
# Save the inputs to memory/persistence
prefs.put("SImethod",Method)
prefs.put("nWell",int(CountMax))
prefs.put("Test",Test)
prefs.put("ImagePathSI",Path_ImageFolder)
prefs.put("HighMag",HighObj_Mag)
prefs.put("WriteJob",doJob)
prefs.put("SwitchObj",doSwitch)
prefs.put("SIscript",Script)
# Check if there is a ROI to limit the search
searchROI = getSearchRoi()
# Set the run method according to choice
# TEMPLATE MATCHING
if Method == "Template matching":
from ROIdetection.MatchTemplate_Module import GUI, PreProcessTemplate, ProcessImage, AddToTable, OutPut
Input = GUI()
if Input == None :
Cancel = True # make sure it does not throw an error when it is cancelled
else:
Cancel = False
PathTemplate,FlipV,FlipH,inAngles,Method,ShowIm,ShowMap,ShowTable,SaveFound = Input
# Open template and pre-process it
Template = IJ.openImage(PathTemplate)
ListTemplateWithFlipped, ListTemplateFlip90_Name, ListTemplateFlip90CV, otherAngle = PreProcessTemplate(Template,FlipV,FlipH,inAngles,searchROI)
def Run(PathImage):
Image = IJ.openImage(PathImage)
BestTemplateName, BestXcorner, BestYcorner, BestXcenter, BestYcenter, BestCoeff, BestMap, FoundRoi = ProcessImage(Image, ListTemplateWithFlipped, ListTemplateFlip90_Name, ListTemplateFlip90CV, Method, otherAngle, searchROI)
# Update result table
if ShowTable:
AddToTable(Image.getTitle(), BestTemplateName, BestXcorner, BestYcorner, BestXcenter, BestYcenter, BestCoeff)
# Display image and results
OutPut(Image, ShowIm, ShowMap, SaveFound, FoundRoi)
return BestXcenter, BestYcenter
# KEYPOINT MATCHING
elif Method == "Keypoint matching" :
from ROIdetection.Keypoint_Module import GUI, SetupMethod, PreProcessTemplate, PreProcessImage, KpMatching, FindTransform, getResultRoi, BurnRoiToStackMatch, AddToTable
Input = GUI()
if Input == None :
Cancel = True # make sure it does not throw an error when it is cancelled
else:
Cancel = False
PathTemplate, Method, Rotation, EditDefault, MatchFilter, Nperc, Nmatch, ShowRaw, ShowMatch, ShowFound, ShowTable = Input
# Get dimensions of the search ROI if any
RoiDim = getRoiDim(searchROI) # return None if no searchROI
# Set up the kp detection/description method and matcher
DetectAndDescribeKp, Matcher = SetupMethod(Method, EditDefault, MatchFilter, Rotation) # Pop up a new Gui to setup the method if Edit Default is ticked
# Open template
Template = IJ.openImage(PathTemplate)
# Detect and Describe Kp in Template
KpTemp, KpTempCV, DesTemp, Corners = PreProcessTemplate(Template,DetectAndDescribeKp)
# Define the Run function to process one image
def Run(ImagePath):
# open ImagePlus
Image = IJ.openImage(ImagePath)
# Detect and Describe keypoints in the image, automatically display the result if ShowRaw = True
KpIm, KpImCV, DesIm = PreProcessImage(Image,DetectAndDescribeKp,RoiDim,ShowRaw)
# Match Kp from template and image based on descriptors, automatically display the image with matches if ShowMatch == True
ListMatch = KpMatching(Matcher,KpTempCV,DesTemp,KpImCV,DesIm,MatchFilter,Nperc,Nmatch,ShowMatch,Template,Image)
# Try to find a rigid transformation
RigidMatrix, List_ptImX, List_ptImY = FindTransform(KpTempCV, KpImCV, ListMatch, Rotation)
# If could not find a rigid transform use Modal point value instead (most probable point from points in the image that got matched)
FoundRoi, Xcenter, Ycenter = getResultRoi(RigidMatrix,Corners,List_ptImX, List_ptImY, Image.getTitle(), ShowFound)
# Burn found roi on image in StackMatch
if FoundRoi and ShowMatch:
BurnRoiToStackMatch(FoundRoi,Template.width)
if ShowTable:
# Report results into table
AddToTable(Image.getTitle(), Xcenter, Ycenter)
return Xcenter, Ycenter
# IJ MACRO
elif Method == "Run IJmacro":
from ROIdetection.Macro_Module import Run, Config
Cancel = Config()
'''
# Recover script services to run the fiji macro
from org.scijava.script import ScriptService
context = locals()['org.scijava.script.ScriptModule'].getContext()
ScriptServ = context.getService(ScriptService)
# We dont import Run directly we need to add the extra service parameter compare to the other cases
from ROIdetection import Macro_Module
def Run(ImagePath, doJob=False):
Macro_Module.Run(ScriptServ,ImagePath,doJob)
'''
### START PROCESSING ###
if Cancel:
print "Smart imaging was cancelled"
pass
else: # We perform the rest of the smart imaging
IJ.resetEscape() # used to monitor if we quit
# I - FIND IMAGE FOLDER
if not Test: # We are really doing smart imaging
Path_LocationFile = r"D:\IMAGING-DATA\JOBS\Image_Location.pth"
#Path_LocationFile = r"C:\Users\Laurent Thomas\Desktop\Test\Image_Location.pth" # for tests
JobFolder = r"D:\IMAGING-DATA\JOBS"
#Try to read the LocationFile to get the image folder
FoundLocation = False
while not FoundLocation :
if IJ.escapePressed(): # the escape can be caught by an image, the ImageJ menu bar or the log window (one of them must be selected)
break # but still execute code after the while if we dont stop it properly
time.sleep(1) # 1sec delay - prevent loop to run too fast and overload processes
try :
LocationFile = open(Path_LocationFile,'r')
Path_ImageFolder = LocationFile.readline().rstrip() # read content of file and remove new line character
LocationFile.close()
os.remove(Path_LocationFile) # delete file once read
FoundLocation = True
print 'Found image location : ', Path_ImageFolder
IJ.log('Found image location : ' + Path_ImageFolder)
except Exception, Error : # File not existing yet
FoundLocation = False
print Error
IJ.log(str(Error))
else: # in test mode
#Path_ImageFolder = Path_ImageFolder.getCanonicalPath() # Convert from Java file to its path
#Path_ImageFolder = Path_ImageFolder
if doJob :
JobFolder = join(Path_ImageFolder,"JOBS")
if not os.path.exists(JobFolder):
os.mkdir(JobFolder)
# II - FOLDER WATCHING and calling the Run method from the imported script
before = os.listdir(Path_ImageFolder) # initialisation must be out of the while loop since we are updating it with "current" at the end of the loop
# INITIALISATION - We process the image present in the folder before ImageLocation.pth is created (currently this is the case, some image are saved before the file is created)
count = 0 # initialise count of processed images
Process(before,doJob) # images in test folder (if test mode) or written before the ImageLocation.pth
# CONTINUATION
Delay = 5 # delay in sec between successive scans for new image
while count < CountMax and not IJ.escapePressed(): # strictly inferior since count starts at 0, and we update it after processing the image ie once we process the last image the counter will be CountMax and we dont want to run another round
# Check if new files
time.sleep(Delay) # before_List was updated just previously at the end of the loop so delay prior to a new listdir scan
current = os.listdir(Path_ImageFolder)
new = list( set(current) - set(before) ) # list containing new file and folder name in folder Path_ImageFolder
if new != []: # New Files have appeared - PUT THE CODE TO EXECUTE HERE
Process(new, doJob)
else: # No new files, keep runnning the loop
pass
# update the 'history' of the folder watcher, and going for another round of while (except if we reached the count)
before = current
# In any case report if escaped
if IJ.escapePressed():
print "Interrupted by Escape"
IJ.log("Interrupted by Escape")