find_beads 0.9.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: