Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# this is a python rewrite of the original ijm published at
# https://github.com/Hyojung-Choo/Myosoft/blob/Myosoft-hub/Scripts/central%20nuclei%20counter.ijm
# IJ imports
# TODO: are the imports RoiManager and ResultsTable needed when using the services?
from ij import IJ, WindowManager as wm
from ij.plugin import Duplicator, RoiEnlarger, RoiScaler
from trainableSegmentation import WekaSegmentation
from de.biovoxxel.toolbox import Extended_Particle_Analyzer
# Bio-formats imports
from loci.plugins import BF
from loci.plugins.in import ImporterOptions
# python imports
import time
import os
#@ String (visibility=MESSAGE, value="<html><b> Welcome to Myosoft - identify fibers! </b></html>") msg1
#@ File (label="Select directory with classifiers", style="directory") classifiers_dir
#@ File (label="Select directory for output", style="directory") output_dir
#@ File (label="Select image file", description="select your image") path_to_image
#@ Boolean (label="close image after processing", description="tick this box when using batch mode", value=False) close_raw
#@ String (visibility=MESSAGE, value="<html><b> Morphometric Gates </b></html>") msg2
#@ Integer (label="Min Area [um²]", value=10) minAr
#@ Integer (label="Max Area [um²]", value=6000) maxAr
#@ Float (label="Min Circularity", value=0.5) minCir
#@ Float (label="Max Circularity", value=1) maxCir
#@ Float (label="Min solidity", value=0.0) minSol
#@ Float (label="Max solidity", value=1) maxSol
#@ Integer (label="Min perimeter [um]", value=5) minPer
#@ Integer (label="Max perimeter [um]", value=300) maxPer
#@ Integer (label="Min min ferret [um]", value=0.1) minMinFer
#@ Integer (label="Max min ferret [um]", value=100) maxMinFer
#@ Integer (label="Min ferret AR", value=0) minFAR
#@ Integer (label="Max ferret AR", value=8) maxFAR
#@ Float (label="Min roundess", value=0.2) minRnd
#@ Float (label="Max roundess", value=1) maxRnd
#@ String (visibility=MESSAGE, value="<html><b> Expand ROIS to match fibers </b></html>") msg3
#@ Float (label="ROI expansion [microns]", value=1) enlarge
#@ String (visibility=MESSAGE, value="<html><b> channel positions in the hyperstack </b></html>") msg5
#@ Integer (label="Membrane staining channel number", style="slider", min=1, max=5, value=1) membrane_channel
#@ Integer (label="Fiber staining (MHC) channel number (0=skip)", style="slider", min=0, max=5, value=3) fiber_channel
#@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#@ Integer (label="sub-tiling to economize RAM", style="slider", min=1, max=8, value=4) tiling_factor
#@ ResultsTable rt
#@ RoiManager rm
def fix_ij_options():
"""put IJ into a defined state
"""
# disable inverting LUT
IJ.run("Appearance...", " menu=0 16-bit=Automatic")
# set foreground color to be white, background black
IJ.run("Colors...", "foreground=white background=black selection=red")
# black BG for binary images and pad edges when eroding
IJ.run("Options...", "black pad")
# set saving format to .txt files
IJ.run("Input/Output...", "file=.txt save_column save_row")
# ============= DON’T MOVE UPWARDS =============
# set "Black Background" in "Binary Options"
IJ.run("Options...", "black")
# scale when converting = checked
IJ.run("Conversions...", "scale")
def fix_ij_dirs(path):
"""use forward slashes in directory paths
Parameters
----------
path : string
a directory path obtained from dialogue or script parameter
Returns
-------
string
a more robust path with forward slashes as separators
"""
fixed_path = str(path).replace("\\", "/")
fixed_path = fixed_path + "/"
return fixed_path
def open_image_with_BF(path_to_file):
""" use Bio-Formats to opens the first image from an image file path
Parameters
----------
path_to_file : string
path to the image file
Returns
-------
ImagePlus
the first imp stored in a give file
"""
options = ImporterOptions()
options.setColorMode(ImporterOptions.COLOR_MODE_GRAYSCALE)
options.setAutoscale(True)
options.setId(path_to_file)
imps = BF.openImagePlus(options) # is an array of ImagePlus
return imps[0]
def fix_BF_czi_imagetitle(imp):
image_title = os.path.basename( imp.getTitle() )
image_title = image_title.replace(".czi", "")
image_title = image_title.replace(" ", "_")
image_title = image_title.replace("_-_", "")
image_title = image_title.replace("__", "_")
image_title = image_title.replace("#", "Series")
return image_title
def preprocess_membrane_channel(imp):
"""apply myosoft pre-processing steps for the membrane channel
Parameters
----------
imp : ImagePlus
a single channel image of the membrane staining
"""
IJ.run(imp, "Enhance Contrast", "saturated=0.35")
IJ.run(imp, "Apply LUT", "")
IJ.run(imp, "Enhance Contrast", "saturated=1")
IJ.run(imp, "8-bit", "")
IJ.run(imp, "Invert", "")
IJ.run(imp, "Convolve...", "text1=[-1.0 -1.0 -1.0 -1.0 -1.0\n-1.0 -1.0 -1.0 -1.0 0\n-1.0 -1.0 24.0 -1.0 -1.0\n-1.0 -1.0 -1.0 -1.0 -1.0\n-1.0 -1.0 -1.0 -1.0 0] normalize")
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def get_threshold_from_method(imp, channel, method):
"""returns the threshold value of chosen IJ AutoThreshold method in desired channel
Parameters
----------
imp : ImagePlus
the imp from which to get the threshold value
channel : integer
the channel in which to get the treshold
method : string
the AutoThreshold method to use
Returns
-------
list
the upper and the lower threshold (integer values)
"""
imp.setC(channel) # starts at 1
ip = imp.getProcessor()
ip.setAutoThreshold(method + " dark")
lower_thr = ip.getMinThreshold()
upper_thr = ip.getMaxThreshold()
ip.resetThreshold()
return lower_thr, upper_thr
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def apply_weka_model(model_path, imp, tiles_per_dim):
"""apply a pretrained WEKA model to an ImagePlus
Parameters
----------
model_path : string
path to the model file
imp : ImagePlus
ImagePlus to apply the model to
tiles_per_dim : integer
tiles the imp to save RAM
Returns
-------
ImagePlus
the result of the WEKA segmentation. One channel per class.
"""
segmentator = WekaSegmentation()
segmentator.loadClassifier( model_path )
result = segmentator.applyClassifier( imp, [tiles_per_dim, tiles_per_dim], 0, True ) #ImagePlus imp, int[x,y,z] tilesPerDim, int numThreads (0=all), boolean probabilityMaps
return result
def process_weka_result(imp):
"""apply myosoft pre-processing steps for the imp after WEKA classification to prepare it
for ROI detection with the extended particle analyzer
Parameters
----------
imp : ImagePlus
a single channel (= desired class) of the WEKA classification result imp
"""
IJ.run(imp, "8-bit", "")
IJ.run(imp, "Median...", "radius=3")
IJ.run(imp, "Gaussian Blur...", "sigma=2")
IJ.run(imp, "Auto Threshold", "method=MaxEntropy")
IJ.run(imp, "Invert", "")
def delete_channel(imp, channel_number):
"""delete a channel from target imp
Parameters
----------
imp : ImagePlus
the imp from which to delete target channel
channel_number : integer
the channel number to be deleted. starts at 0.
"""
imp.setC(channel_number)
IJ.run(imp, "Delete Slice", "delete=channel")
def run_extended_particle_analyzer( imp, eda_parameters ):
"""identifies ROIs in target imp using the extended particle analyzer of the BioVoxxel toolbox
with given parameters
Parameters
----------
imp : ImagePlus
the image on which to run the EPA on. Should be 8-bit thresholded
eda_parameters : array
all user defined parameters to restrict ROI identification
"""
epa = Extended_Particle_Analyzer()
epa.readInputImageParameters(imp)
epa.setDefaultParameterFields()
# expose all parameters explicitly
epa.usePixel = False
epa.usePixelForOutput = False
epa.Area = str(eda_parameters[0]) + "-" + str(eda_parameters[1])
epa.Extent = "0.00-1.00"
epa.Perimeter = str(eda_parameters[2]) + "-" + str(eda_parameters[3])
epa.Circularity = str(eda_parameters[4]) + "-" + str(eda_parameters[5])
epa.Roundness = str(eda_parameters[6]) + "-" + str(eda_parameters[7])
epa.Solidity = str(eda_parameters[8]) + "-" + str(eda_parameters[9])
epa.Compactness = "0.00-1.00"
epa.AR = "0-Infinity"
epa.FeretAR = str(eda_parameters[10]) + "-" + str(eda_parameters[11])
epa.EllipsoidAngle = "0-180"
epa.MaxFeret = "0-Infinity"
epa.MinFeret = str(eda_parameters[12]) + "-" + str(eda_parameters[13])
epa.FeretAngle = "0-180"
epa.COV = "0.00-1.00"
epa.Output = "Nothing"
epa.Redirect = "None"
epa.Correction = "None"
epa.Reset = False
epa.DisplayResults = False
epa.ClearResults = False
epa.Summarize = False
epa.AddToManager = True
epa.ExcludeEdges = False
epa.IncludeHoles = False
epa.defineParticleAnalyzers()
epa.particleAnalysis( imp.getProcessor(), imp, imp.getTitle() )
def measure_in_all_rois( imp, channel, rm ):
"""measures in all ROIS on a given channel of imp all parameters that are set in IJ "Set Measurements"
Parameters
----------
imp : ImagePlus
the imp to measure on
channel : integer
the channel to measure in. starts at 1.
rm : RoiManager
a reference of the IJ-RoiManager
"""
imp.setC(channel)
rm.runCommand(imp,"Deselect")
rm.runCommand(imp,"Measure")
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def change_all_roi_color( rm, color ):
"""change the color of all ROIs in the RoiManager
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
color : string
the desired color. e.g. "green", "red", "yellow", "magenta" ...
"""
number_of_rois = rm.getCount()
for roi in range( number_of_rois ):
rm.select(roi)
rm.runCommand("Set Color", color)
def change_subset_roi_color( rm, selected_rois, color ):
"""change the color of selected ROIs in the RoiManager
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
selected_rois : array
ROIs in the RoiManager to change
color : string
the desired color. e.g. "green", "red", "yellow", "magenta" ...
"""
rm.runCommand("Deselect")
rm.setSelectedIndexes(selected_rois)
rm.runCommand("Set Color", color)
rm.runCommand("Deselect")
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def show_all_rois_on_image(rm, imp):
"""shows all ROIs in the ROiManager on imp
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
imp : ImagePlus
the imp on which to show the ROIs
"""
imp.show()
rm.runCommand(imp,"Show All")
def save_all_rois(rm, target):
"""save all ROIs in the RoiManager as zip to target path
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
target : string
the path in to store the ROIs. e.g. /my-images/resulting_rois.zip
"""
rm.runCommand("Save", target)
def save_selected_rois( rm, selected_rois, target ):
"""save selected ROIs in the RoiManager as zip to target path
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
selected_rois : array
ROIs in the RoiManager to save
target : string
the path in to store the ROIs. e.g. /my-images/resulting_rois_subset.zip
"""
rm.runCommand("Deselect")
rm.setSelectedIndexes(selected_rois)
rm.runCommand("save selected", target)
rm.runCommand("Deselect")
def enlarge_all_rois( amount_in_um, rm, pixel_size_in_um ):
"""enlarges all ROIs in the RoiManager by x scaled units
Parameters
----------
amount_in_um : float
the value by which to enlarge in scaled units, e.g 3.5
rm : RoiManager
a reference of the IJ-RoiManager
pixel_size_in_um : float
the pixel size, e.g. 0.65 px/um
"""
amount_px = amount_in_um / pixel_size_in_um
all_rois = rm.getRoisAsArray()
rm.reset()
for roi in all_rois:
enlarged_roi = RoiEnlarger.enlarge(roi, amount_px)
rm.addRoi(enlarged_roi)
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
def select_positive_fibers( imp, channel, rm, min_intensity ):
"""For all ROIs in the RoiManager, select ROIs based on intensity measurement in given channel of imp.
See https://imagej.nih.gov/ij/developer/api/ij/process/ImageStatistics.html
Parameters
----------
imp : ImagePlus
the imp on which to measure
channel : integer
the channel on which to measure. starts at 1
rm : RoiManager
a reference of the IJ-RoiManager
min_intensity : integer
the selection criterion (here: intensity threshold)
Returns
-------
array
a selection of ROIs which passed the selection criterion (are above the threshold)
"""
imp.setC(channel)
all_rois = rm.getRoisAsArray()
selected_rois = []
for i, roi in enumerate(all_rois):
imp.setRoi(roi)
stats = imp.getStatistics()
if stats.mean > min_intensity:
selected_rois.append(i)
return selected_rois
def preset_results_column( rt, column, value):
"""pre-set all rows in given column of the IJ-ResultsTable with desired value
Parameters
----------
rt : ResultsTable
a reference of the IJ-ResultsTable
column : string
the desired column. will be created if it does not yet exist
value : string or float or integer
the value to be set
"""
for i in range( rt.size() ):
rt.setValue(column, i, value)
rt.show("Results")
def add_results( rt, column, row, value ):
"""adds a value in desired rows of a given column
Parameters
----------
rt : ResultsTable
a reference of the IJ-ResultsTable
column : string
the column in which to add the values
row : array
the row numbers in which too add the values.
value : string or float or integer
the value to be set
"""
for i in range( len( row ) ):
rt.setValue(column, row[i], value)
rt.show("Results")
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
def enhance_contrast( imp ):
"""use "Auto" Contrast & Brightness settings in each channel of imp
Parameters
----------
imp : ImagePlus
the imp on which to change C&B
"""
for channel in range( imp.getDimensions()[2] ):
imp.setC(channel + 1) # IJ channels start at 1
IJ.run(imp, "Enhance Contrast", "saturated=0.35")
def renumber_rois(rm):
"""rename all ROIs in the RoiManager according to their number
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
"""
number_of_rois = rm.getCount()
for roi in range( number_of_rois ):
rm.rename( roi, str(roi + 1) )
def setup_defined_ij(rm, rt):
"""set up a clean and defined Fiji user environment
Parameters
----------
rm : RoiManager
a reference of the IJ-RoiManager
rt : ResultsTable
a reference of the IJ-ResultsTable
"""
fix_ij_options()
rm.runCommand('reset')
rt.reset()
IJ.log("\\Clear")
execution_start_time = time.time()
setup_defined_ij(rm, rt)
# open image using Bio-Formats
path_to_image = fix_ij_dirs(path_to_image)
raw = open_image_with_BF(path_to_image)
# get image info
raw_image_calibration = raw.getCalibration()
raw_image_title = fix_BF_czi_imagetitle(raw)
# take care of paths and directories
output_dir = fix_ij_dirs(output_dir)
if not os.path.exists( str(output_dir + raw_image_title) ):
os.makedirs( str(output_dir + raw_image_title) )
output_dir = str( output_dir + raw_image_title ) + "/"
classifiers_dir = fix_ij_dirs(classifiers_dir)
primary_model = classifiers_dir + "primary.model"
secondary_model = classifiers_dir + "secondary_central_nuclei.model"
# update the log for the user
IJ.log( "Now working on " + str(raw_image_title) )
if raw_image_calibration.scaled() == False:
IJ.log("Your image is not spatially calibrated! Size measurements are only possible in [px].")
IJ.log( " -- settings used -- ")
IJ.log( "area = " + str(minAr) + "-" + str(maxAr) )
IJ.log( "perimeter = " + str(minPer) + "-" + str(maxPer) )
IJ.log( "circularity = " + str(minCir) + "-" + str(maxCir) )
IJ.log( "roundness = " + str(minRnd) + "-" + str(maxRnd) )
IJ.log( "solidity = " + str(minSol) + "-" + str(maxSol) )
IJ.log( "feret_ar = " + str(minFAR) + "-" + str(maxFAR) )
IJ.log( "min_feret = " + str(minMinFer) + "-" + str(maxMinFer) )
IJ.log( "ROI expansion [microns] = " + str(enlarge) )
IJ.log( "sub-tiling = " + str(tiling_factor) )
IJ.log( " -- settings used -- ")
# image (pre)processing and segmentation (-> ROIs)
membrane = Duplicator().run(raw, membrane_channel, membrane_channel, 1, 1, 1, 1) # imp, firstC, lastC, firstZ, lastZ, firstT, lastT
preprocess_membrane_channel(membrane)
weka_result1 = apply_weka_model(primary_model, membrane, tiling_factor )
delete_channel(weka_result1, 1)
weka_result2 = apply_weka_model(secondary_model, weka_result1, tiling_factor )
delete_channel(weka_result2, 1)
weka_result2.setCalibration(raw_image_calibration)
process_weka_result(weka_result2)
IJ.saveAs(weka_result2, "Tiff", output_dir + raw_image_title + "_all_fibers_binary")
eda_parameters = [minAr, maxAr, minPer, maxPer, minCir, maxCir, minRnd, maxRnd, minSol, maxSol, minFAR, maxFAR, minMinFer, maxMinFer]
raw.show() # EPA will not work if no image is shown
run_extended_particle_analyzer(weka_result2, eda_parameters)
# modify rois
rm.hide()
raw.hide()
enlarge_all_rois( enlarge, rm, raw_image_calibration.pixelWidth )
renumber_rois(rm)
save_all_rois( rm, output_dir + "all_fiber_rois.zip" )
# check for positive fibers
if fiber_channel > 0:
if min_fiber_intensity == 0:
min_fiber_intensity = get_threshold_from_method(raw, fiber_channel, "Mean")[0]
IJ.log( "fiber intensity threshold: " + str(min_fiber_intensity) )
positive_fibers = select_positive_fibers( raw, fiber_channel, rm, min_fiber_intensity )
change_subset_roi_color(rm, positive_fibers, "magenta")
save_selected_rois( rm, positive_fibers, output_dir + "mhc_positive_fiber_rois.zip")
# measure size & shape, save
IJ.run("Set Measurements...", "area perimeter shape feret's redirect=None decimal=4")
IJ.run("Clear Results", "")
measure_in_all_rois( raw, membrane_channel, rm )
if fiber_channel > 0:
preset_results_column( rt, "MHC Positive Fibers (magenta)", "NO" )
add_results( rt, "MHC Positive Fibers (magenta)", positive_fibers, "YES")
rt.save(output_dir + "all_fibers_results.csv")
# dress up the original image, save a overlay-png, present original to the user
rm.show()
raw.show()
show_all_rois_on_image( rm, raw )
raw.setDisplayMode(IJ.COMPOSITE)
enhance_contrast( raw )
IJ.run("From ROI Manager", "") # ROIs -> overlays so they show up in the saved png
qc_duplicate = raw.duplicate()
IJ.saveAs(qc_duplicate, "PNG", output_dir + raw_image_title + "_all_fibers")
qc_duplicate.close()
wm.toFront( raw.getWindow() )
IJ.run("Remove Overlay", "")
raw.setDisplayMode(IJ.GRAYSCALE)
show_all_rois_on_image( rm, raw )
total_execution_time_min = (time.time() - execution_start_time) / 60.0
IJ.log("total time in minutes: " + str(total_execution_time_min))
IJ.log( "~~ all done ~~" )
IJ.selectWindow("Log")
IJ.saveAs("Text", str(output_dir + raw_image_title + "_all_fibers_Log"))
if close_raw == True:
raw.close()