find_beads 0.9.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/bin/find_beads ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # /* ***** BEGIN LICENSE BLOCK *****
4
+ # *
5
+ # * Copyright (c) 2013 Colin J. Fuller
6
+ # *
7
+ # * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # * of this software and associated documentation files (the Software), to deal
9
+ # * in the Software without restriction, including without limitation the rights
10
+ # * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # * copies of the Software, and to permit persons to whom the Software is
12
+ # * furnished to do so, subject to the following conditions:
13
+ # *
14
+ # * The above copyright notice and this permission notice shall be included in
15
+ # * all copies or substantial portions of the Software.
16
+ # *
17
+ # * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # * SOFTWARE.
24
+ # *
25
+ # * ***** END LICENSE BLOCK ***** */
26
+ #++
27
+
28
+ require 'find_beads'
29
+
30
+ FindBeads.run_find_beads
31
+
32
+
33
+
@@ -0,0 +1,33 @@
1
+ #--
2
+ # /* ***** BEGIN LICENSE BLOCK *****
3
+ # *
4
+ # * Copyright (c) 2013 Colin J. Fuller
5
+ # *
6
+ # * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # * of this software and associated documentation files (the Software), to deal
8
+ # * in the Software without restriction, including without limitation the rights
9
+ # * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # * copies of the Software, and to permit persons to whom the Software is
11
+ # * furnished to do so, subject to the following conditions:
12
+ # *
13
+ # * The above copyright notice and this permission notice shall be included in
14
+ # * all copies or substantial portions of the Software.
15
+ # *
16
+ # * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # * SOFTWARE.
23
+ # *
24
+ # * ***** END LICENSE BLOCK ***** */
25
+ #++
26
+
27
+ module FindBeads
28
+
29
+ VERSION = '0.9.0'
30
+
31
+ end
32
+
33
+
data/lib/find_beads.rb ADDED
@@ -0,0 +1,502 @@
1
+ #--
2
+ # /* ***** BEGIN LICENSE BLOCK *****
3
+ # *
4
+ # * Copyright (c) 2013 Colin J. Fuller
5
+ # *
6
+ # * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # * of this software and associated documentation files (the Software), to deal
8
+ # * in the Software without restriction, including without limitation the rights
9
+ # * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # * copies of the Software, and to permit persons to whom the Software is
11
+ # * furnished to do so, subject to the following conditions:
12
+ # *
13
+ # * The above copyright notice and this permission notice shall be included in
14
+ # * all copies or substantial portions of the Software.
15
+ # *
16
+ # * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # * SOFTWARE.
23
+ # *
24
+ # * ***** END LICENSE BLOCK ***** */
25
+ #++
26
+
27
+ require 'rimageanalysistools'
28
+ require 'rimageanalysistools/get_image'
29
+ require 'rimageanalysistools/image_shortcuts'
30
+ require 'rimageanalysistools/create_parameters'
31
+ require 'rimageanalysistools/graythresh'
32
+
33
+ require 'edu/stanford/cfuller/imageanalysistools/resources/common_methods'
34
+
35
+ require 'trollop'
36
+
37
+ ##
38
+ # Functions for segmenting and quantifying beads.
39
+ #
40
+ module FindBeads
41
+
42
+ include IATScripting
43
+
44
+ java_import Java::edu.stanford.cfuller.imageanalysistools.image.ImageCoordinate
45
+ java_import Java::edu.stanford.cfuller.imageanalysistools.image.ImageSet
46
+ java_import Java::edu.stanford.cfuller.imageanalysistools.meta.parameters.ParameterDictionary
47
+ java_import Java::edu.stanford.cfuller.imageanalysistools.filter.MaximumSeparabilityThresholdingFilter
48
+ java_import Java::edu.stanford.cfuller.imageanalysistools.filter.LabelFilter
49
+ java_import Java::edu.stanford.cfuller.imageanalysistools.filter.SizeAbsoluteFilter
50
+ java_import Java::edu.stanford.cfuller.imageanalysistools.filter.VoronoiFilter
51
+ java_import Java::edu.stanford.cfuller.imageanalysistools.filter.MaskFilter
52
+ java_import Java::edu.stanford.cfuller.imageanalysistools.metric.IntensityPerPixelMetric
53
+ java_import Java::edu.stanford.cfuller.imageanalysistools.image.Histogram
54
+
55
+ DEFAULT_SEG_CH = 2
56
+ DEFAULT_SEG_PL = 8
57
+ DEFAULT_BEAD_RADIUS = 24.0
58
+
59
+ ##
60
+ # Finds the centroid of each unique-greylevel region in a mask.
61
+ #
62
+ # @param [Image] mask the mask in which the regions appear. 0 denotes background and will not be counted.
63
+ # @return [Hash] a hash where keys are the greylevels of each region in the mask, and the values are
64
+ # two-element arrays containing the x,y-coordinates of the centroids of these regions.
65
+ #
66
+ def self.centroids(mask)
67
+
68
+ cens = {}
69
+
70
+ mask.each do |ic|
71
+
72
+ next unless mask[ic] > 0
73
+
74
+ cens[mask[ic]] = [0.0, 0.0] unless cens[mask[ic]]
75
+
76
+ cens[mask[ic]][0] += ic[:x]
77
+ cens[mask[ic]][1] += ic[:y]
78
+
79
+ end
80
+
81
+ h = Histogram.new(mask)
82
+
83
+ cens.each_key do |k|
84
+
85
+ cens[k].map! { |e| e / h.getCounts(k) }
86
+
87
+ end
88
+
89
+ cens
90
+
91
+ end
92
+
93
+ ##
94
+ # Checks if a given coordinate would be approximately on the boundary between two regions of a
95
+ # Voronoi diagram of constructed from a set of points. The approximation is calculated such that no
96
+ # two regions in the Voronoi diagram would be 8-connected.
97
+ #
98
+ # @param [Array] points an array of two-element arrays containing the x,y coordinates of the points
99
+ # on which the diagram is calculated.
100
+ # @param [ImageCoordinate] ic an ImageCoordinate specifying the location to check.
101
+ # @return [Boolean] whether the point is on the border between two regions.
102
+ #
103
+ def self.is_on_voronoi_border?(points, ic)
104
+
105
+ x = ic[:x]
106
+ y = ic[:y]
107
+
108
+ closest_index = 0
109
+ next_index = 0
110
+ closest_dist = Float::MAX
111
+ next_dist = Float::MAX
112
+
113
+
114
+ points.each_with_index do |p, i|
115
+
116
+ dist = Math.hypot(p[0] - x, p[1] - y)
117
+
118
+ if dist < closest_dist then
119
+
120
+ next_dist = closest_dist
121
+
122
+ next_index = closest_index
123
+
124
+ closest_dist = dist
125
+
126
+ closest_index = i
127
+
128
+ end
129
+
130
+ end
131
+
132
+ cutoff = 2*Math.sqrt(2)
133
+
134
+ if next_dist - closest_dist < cutoff then
135
+
136
+ true
137
+
138
+ else
139
+
140
+ false
141
+
142
+ end
143
+
144
+ end
145
+
146
+ ##
147
+ # Recursively thresholds regions in a supplied mask and image using the method described in
148
+ # Xiong et al. (DOI: 10.1109/ICIP.2006.312365).
149
+ #
150
+ # @param [ParameterDictionary] p a ParameterDictionary specifying max_size and min_size parameters,
151
+ # which control the maximum size of regions before they are recursively thresholded to break them up,
152
+ # and the minimum size of regions before they are discarded.
153
+ # @param [Image] im the original image being segmented. This will not be modified.
154
+ # @param [Image] mask the initial segmentation mask for the supplied image. Regions in this mask may
155
+ # be divided up or discarded.
156
+ # @return [void]
157
+ #
158
+ def self.recursive_threshold(p, im, mask)
159
+
160
+ h = Histogram.new(mask)
161
+
162
+ changed = false
163
+
164
+ discard_list = {}
165
+
166
+ 1.upto(h.getMaxValue) do |i|
167
+
168
+ if h.getCounts(i) > p[:max_size].to_i then
169
+
170
+ values = []
171
+
172
+ im.each do |ic|
173
+
174
+ if mask[ic] == i then
175
+
176
+ values << im[ic]
177
+
178
+ end
179
+
180
+ end
181
+
182
+ thresh = RImageAnalysisTools.graythresh(values)
183
+
184
+ im.each do |ic|
185
+
186
+ if mask[ic] == i and im[ic] <= thresh then
187
+
188
+ mask[ic] = 0
189
+
190
+ changed = true
191
+
192
+ end
193
+
194
+ end
195
+
196
+ elsif h.getCounts(i) > 0 and h.getCounts(i) < p[:min_size].to_i then
197
+
198
+ discard_list[i] = true
199
+
200
+ end
201
+
202
+ end
203
+
204
+ im.each do |ic|
205
+
206
+ if discard_list[im[ic]] then
207
+
208
+ mask[ic] = 0
209
+
210
+ changed = true
211
+
212
+ end
213
+
214
+ end
215
+
216
+
217
+ if changed then
218
+
219
+ lf = LabelFilter.new
220
+
221
+ lf.apply(mask)
222
+
223
+ recursive_threshold(p, im, mask)
224
+
225
+ end
226
+
227
+ end
228
+
229
+ ##
230
+ # Caclulates the maximum allowed size of a bead from the supplied radius.
231
+ # This is set to be slightly larger than a circle of that radius.
232
+ #
233
+ # @param [Fixnum] rad the radius of the bead in units of pixels.
234
+ #
235
+ def self.calculate_max_size_from_radius(rad)
236
+
237
+ ((rad+1)**2 * 3.2).to_i
238
+
239
+ end
240
+
241
+ ##
242
+ # Calculates the minimum allowed size of a bead from the supplied radius.
243
+ # This is set to be slightly smaller than a third of a circle of that radius.
244
+ # (Note that this is smaller than any of the returned regions should be, but making
245
+ # the cutoff this small is useful for dividing up clumps of beads where several rounds
246
+ # or recursive thresholding may make the regions quite small temporarily.)
247
+ #
248
+ # @param @see #calculate_max_size_from_radius
249
+ #
250
+ def self.calculate_min_size_from_radius(rad)
251
+
252
+ (0.96* (rad+1)**2).to_i
253
+
254
+ end
255
+
256
+ ##
257
+ # Generates a segmented mask of beads from an image.
258
+ #
259
+ # @param [Image] im the image to segment
260
+ # @param [Hash] opts a hash of commandline arguments.
261
+ #
262
+ def self.mask_from_image(im, opts)
263
+
264
+ seg_ch = nil
265
+ seg_pl = nil
266
+ rad = nil
267
+
268
+ if opts then
269
+ seg_ch = opts[:segchannel]
270
+ seg_pl = opts[:segplane]
271
+ rad = opts[:beadradius]
272
+ else
273
+ seg_ch = DEFAULT_SEG_CH
274
+ seg_pl = DEFAULT_SEG_PL
275
+ rad = DEFAULT_BEAD_RADIUS
276
+ end
277
+
278
+ min_size = calculate_min_size_from_radius(rad)
279
+ max_size = calculate_max_size_from_radius(rad)
280
+
281
+ sizes = ImageCoordinate.cloneCoord(im.getDimensionSizes)
282
+
283
+ sizes[:c] = 1
284
+ sizes[:z] = 1
285
+
286
+ im0 = ImageCoordinate.createCoordXYZCT(0,0,0,0,0)
287
+
288
+ im0[:c] = seg_ch
289
+ im0[:z] = seg_pl
290
+
291
+ to_seg = im.subImage(sizes, im0).writableInstance
292
+
293
+ p = RImageAnalysisTools.create_parameter_dictionary(min_size: min_size, max_size: max_size)
294
+
295
+ im_cp = writable_image_copy(to_seg)
296
+
297
+ mstf = MaximumSeparabilityThresholdingFilter.new
298
+
299
+ lf = LabelFilter.new
300
+
301
+ saf = SizeAbsoluteFilter.new
302
+
303
+ filters = []
304
+
305
+ filters << mstf
306
+
307
+ filters << lf
308
+
309
+ filters.each do |f|
310
+
311
+ f.setParameters(p)
312
+ f.setReferenceImage(im_cp)
313
+ f.apply(to_seg)
314
+
315
+ end
316
+
317
+ recursive_threshold(p, im_cp, to_seg)
318
+
319
+ saf.setParameters(p)
320
+
321
+ saf.apply(to_seg)
322
+
323
+ cens = centroids(to_seg)
324
+
325
+ final_mask = writable_image_copy(to_seg)
326
+
327
+ radius = rad
328
+
329
+ final_mask.each do |ic|
330
+
331
+ final_mask[ic] = 0
332
+
333
+ end
334
+
335
+ final_mask.each do |ic|
336
+
337
+ x = ic[:x]
338
+ y = ic[:y]
339
+
340
+ cens.each_key do |k|
341
+
342
+ if Math.hypot(cens[k][0] - x, cens[k][1] - y) <= radius then
343
+
344
+ final_mask[ic] = k
345
+
346
+ end
347
+
348
+ end
349
+
350
+ end
351
+
352
+
353
+ final_mask.each do |ic|
354
+
355
+ next unless final_mask[ic] > 0
356
+
357
+ if is_on_voronoi_border?(cens.values, ic) then
358
+
359
+ final_mask[ic] = 0
360
+
361
+ end
362
+
363
+ end
364
+
365
+ lf.apply(final_mask)
366
+
367
+ saf.apply(final_mask)
368
+
369
+ lf.apply(final_mask)
370
+
371
+ final_mask
372
+
373
+ end
374
+
375
+ ##
376
+ # Writes the output data and mask to files.
377
+ #
378
+ # @param [String] fn_orig the original filename of the image being segmented/quantified.
379
+ # @param [String] quant_str the quantification data to write
380
+ # @param [Image] mask the mask to write
381
+ #
382
+ def self.write_output(fn_orig, quant_str, mask)
383
+
384
+ mask_dir = "output_mask"
385
+
386
+ quant_dir = "quantification"
387
+
388
+ mask_ext = "_mask.ome.tif"
389
+
390
+ quant_ext = "_quant.txt"
391
+
392
+ dir = File.dirname(fn_orig)
393
+
394
+ base = File.basename(fn_orig)
395
+
396
+ base = base.gsub(".ome.tif", "")
397
+
398
+ mask_dir = File.expand_path(mask_dir, dir)
399
+
400
+ quant_dir = File.expand_path(quant_dir, dir)
401
+
402
+ Dir.mkdir(mask_dir) unless Dir.exist?(mask_dir)
403
+ Dir.mkdir(quant_dir) unless Dir.exist?(quant_dir)
404
+
405
+ mask.writeToFile(File.expand_path(base + mask_ext, mask_dir))
406
+
407
+ File.open(File.expand_path(base + quant_ext, quant_dir), 'w') do |f|
408
+
409
+ f.puts(quant_str)
410
+
411
+ end
412
+
413
+ end
414
+
415
+ ##
416
+ # Processes a single file, which consists of creating a mask, quantifying regions, and writing output.
417
+ #
418
+ # @param [String] fn the filename of the image to process
419
+ # @param [Hash] opts a hash of command line options.
420
+ #
421
+ def self.process_file(fn, opts=nil)
422
+
423
+ puts "processing #{fn}"
424
+
425
+ im = RImageAnalysisTools.get_image(fn)
426
+
427
+ mask = mask_from_image(im, opts)
428
+
429
+ proj = Java::edu.stanford.cfuller.imageanalysistools.frontend.MaximumIntensityProjection.projectImage(im)
430
+
431
+ ims = proj.splitChannels
432
+
433
+ is = ImageSet.new(ParameterDictionary.emptyDictionary)
434
+
435
+ ims.each do |imc|
436
+
437
+ is.addImageWithImage(imc)
438
+
439
+ end
440
+
441
+ met = IntensityPerPixelMetric.new
442
+
443
+ q = met.quantify(mask, is)
444
+
445
+ outdat = Java::edu.stanford.cfuller.imageanalysistools.frontend.LocalAnalysis.generateDataOutputString(q, nil)
446
+
447
+ write_output(fn, outdat, mask)
448
+
449
+ end
450
+
451
+ ##
452
+ # Runs the bead finding on a file or directory, and grabs options from the command line.
453
+ #
454
+ def self.run_find_beads
455
+
456
+ opts = Trollop::options do
457
+
458
+ opt :dir, "Directory to process", :type => :string
459
+ opt :file, "File to process", :type => :string
460
+ opt :segchannel, "Channel on which to segment (0-indexed)", :type => :integer, :default => DEFAULT_SEG_CH
461
+ opt :segplane, "Plane on which to segment (0-indexed)", :type => :integer, :default => DEFAULT_SEG_PL
462
+ opt :beadradius, "Radius of the bead in pixels", :type => :float, :default => DEFAULT_BEAD_RADIUS
463
+
464
+ end
465
+
466
+ if opts[:dir] then
467
+
468
+ fod = opts[:dir]
469
+
470
+ Dir.foreach(fod) do |f|
471
+
472
+ fn = File.expand_path(f, fod)
473
+
474
+ if File.file?(fn) then
475
+
476
+ begin
477
+
478
+ process_file(fn, opts)
479
+
480
+ rescue Exception => e
481
+
482
+ puts "Unable to process #{fn}:"
483
+ puts e.message
484
+
485
+ end
486
+
487
+ end
488
+
489
+ end
490
+
491
+ end
492
+
493
+ if opts[:file] then
494
+
495
+ process_file(opts[:file], opts)
496
+
497
+ end
498
+
499
+ end
500
+
501
+ end
502
+
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: find_beads
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
6
+ platform: java
7
+ authors:
8
+ - Colin J. Fuller
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rimageanalysistools
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: !binary |-
21
+ MA==
22
+ none: false
23
+ requirement: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: !binary |-
28
+ MA==
29
+ none: false
30
+ prerelease: false
31
+ type: :runtime
32
+ - !ruby/object:Gem::Dependency
33
+ name: trollop
34
+ version_requirements: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: !binary |-
39
+ MA==
40
+ none: false
41
+ requirement: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: !binary |-
46
+ MA==
47
+ none: false
48
+ prerelease: false
49
+ type: :runtime
50
+ description: Segments and quantifies beads in microscopy images
51
+ email: cjfuller@gmail.com
52
+ executables:
53
+ - find_beads
54
+ extensions: []
55
+ extra_rdoc_files: []
56
+ files:
57
+ - lib/find_beads.rb
58
+ - lib/find_beads/version.rb
59
+ - bin/find_beads
60
+ homepage: http://github.com/cjfuller/find_beads
61
+ licenses:
62
+ - MIT
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: !binary |-
72
+ MA==
73
+ none: false
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: !binary |-
79
+ MA==
80
+ none: false
81
+ requirements:
82
+ - jruby
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.24
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Segments and quantifies beads in microscopy images
88
+ test_files: []
89
+ has_rdoc: