cicada 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/cicada ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # /* ***** BEGIN LICENSE BLOCK *****
5
+ # *
6
+ # * Copyright (c) 2013 Colin J. Fuller
7
+ # *
8
+ # * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # * of this software and associated documentation files (the Software), to deal
10
+ # * in the Software without restriction, including without limitation the rights
11
+ # * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # * copies of the Software, and to permit persons to whom the Software is
13
+ # * furnished to do so, subject to the following conditions:
14
+ # *
15
+ # * The above copyright notice and this permission notice shall be included in
16
+ # * all copies or substantial portions of the Software.
17
+ # *
18
+ # * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # * SOFTWARE.
25
+ # *
26
+ # * ***** END LICENSE BLOCK ***** */
27
+ #++
28
+
29
+ require 'cicada'
30
+
31
+ ##
32
+ # Start the analysis using the parameter file
33
+ # specified on the command line.
34
+ #
35
+ Cicada::CicadaMain.run_from_parameter_file(ARGV[0])
36
+
37
+
data/lib/cicada.rb ADDED
@@ -0,0 +1,31 @@
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
+
29
+ require 'cicada/cicada_main'
30
+
31
+
@@ -0,0 +1,674 @@
1
+ #--
2
+ # /* ***** BEGIN LICENSE BLOCK *****
3
+ # *
4
+ # * Copyright (c) 2012 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 'cicada/file_interaction'
28
+ require 'cicada/correction/correction'
29
+ require 'cicada/correction/position_corrector'
30
+ require 'cicada/fitting/p3d_fitter'
31
+ require 'ostruct'
32
+ require 'logger'
33
+
34
+
35
+ require 'rimageanalysistools'
36
+ require 'rimageanalysistools/image_shortcuts'
37
+ require 'rimageanalysistools/create_parameters'
38
+
39
+ require 'facets/math/sum'
40
+
41
+ require 'edu/stanford/cfuller/imageanalysistools/resources/common_methods'
42
+
43
+ java_import Java::edu.stanford.cfuller.imageanalysistools.filter.ImageSubtractionFilter
44
+ java_import Java::edu.stanford.cfuller.imageanalysistools.image.Histogram
45
+ java_import Java::edu.stanford.cfuller.imageanalysistools.fitting.GaussianImageObject
46
+ java_import Java::edu.stanford.cfuller.imageanalysistools.meta.parameters.ParameterDictionary
47
+
48
+ java_import Java::java.util.concurrent.Executors
49
+
50
+
51
+ module Cicada
52
+
53
+
54
+ ##
55
+ # This class is the main entry point for running 3D high-resolution colocalization and CICADA.
56
+ #
57
+ class CicadaMain
58
+
59
+ include IATScripting #TODO: find a better way to get at these methods.
60
+
61
+ # parameters required by the methods in this class
62
+ REQUIRED_PARAMETERS = [:dirname, :basename, :im_border_size, :half_z_size, :determine_correction, :pixelsize_nm, :z_sectionsize_nm, :num_wavelengths, :photons_per_greylevel]
63
+
64
+ # parmeters used but not required in this class or only required for optional functionality
65
+ OPTIONAL_PARAMETERS = [:precomputed_position_data, :max_threads, :darkcurrent_image, :residual_cutoff, :max_greylevel_cutoff, :distance_cutoff, :fit_error_cutoff, :determine_correction, :determine_tre, :output_positions_to_directory, :in_situ_aberr_corr_basename, :in_situ_aberr_corr_channel, :log_to_file, :log_detailed_messages]
66
+
67
+ attr_accessor :parameters, :failures, :logger
68
+
69
+ FAILURE_REASONS = {r2: "R^2 value", edge: "Too close to image edge", sat: "Saturated pixels", sep: "Separation between channels too large", err: "Fit error too large"}
70
+
71
+ ##
72
+ # Sets up the analysis from a parameter dictionary.
73
+ #
74
+ # @param [ParameterDictionary, Hash] p a parameter dictionary or other object with hash-like behavior
75
+ # containing all the parameters for the analysis.
76
+ #
77
+ def initialize(p)
78
+
79
+ @parameters = p
80
+
81
+ @parameters = RImageAnalysisTools.create_parameter_dictionary(p) unless @parameters.is_a? ParameterDictionary
82
+
83
+ @failures = {r2: 0, edge: 0, sat: 0, sep: 0, err: 0}
84
+
85
+ if @parameters[:darkcurrent_image] then
86
+
87
+ @dark_image = FileInteraction.load_image(@parameters[:darkcurrent_image])
88
+
89
+ end
90
+
91
+ set_up_logging
92
+
93
+ end
94
+
95
+
96
+ ##
97
+ # Load the position data from disk if this is requested in the specified parameters. If this
98
+ # has not been requested or if the position data file does not exist, returns nil.
99
+ #
100
+ # @return [Array<ImageObject>] the image objects, complete with their fitted positions, or nil if
101
+ # this should be recalculated or if the file cannot be found.
102
+ #
103
+ def load_position_data
104
+
105
+ if @parameters[:precomputed_position_data] and FileInteraction.position_file_exists?(@parameters) then
106
+
107
+ return FileInteraction.read_position_data(@parameters)
108
+
109
+ end
110
+
111
+ nil
112
+
113
+ end
114
+
115
+
116
+ ##
117
+ # Loads the image and mask from an image and mask pair and darkcurrent corrects the
118
+ # image if specified in the parameters.
119
+ #
120
+ # @param [OpenStruct, #image_fn, #mask_fn, #image=, #mask=] im_set An object that
121
+ # specified the filename of image and mask and can store the loaded image
122
+ # and mask. Should respond to #image_fn, #mask_fn, #image=, and #mask= for
123
+ # getting the filenames and setting the loaded images, respectively.
124
+ # @return [void]
125
+ #
126
+ def load_and_dark_correct_image(im_set)
127
+
128
+ im_set.image = FileInteraction.load_image(im_set.image_fn)
129
+ im_set.mask = FileInteraction.load_image(im_set.mask_fn)
130
+
131
+ if (@dark_image) then
132
+
133
+ im_set.image = im_set.image.writableInstance
134
+
135
+ isf = ImageSubtractionFilter.new
136
+
137
+ isf.setSubtractPlanarImage(true)
138
+
139
+ isf.setReferenceImage(@dark_image)
140
+ isf.apply(im_set.image)
141
+
142
+ end
143
+
144
+ end
145
+
146
+ ##
147
+ # Submits a single object to a thread queue for fitting.
148
+ #
149
+ # @param [ImageObject] obj the image object to fit
150
+ # @param [ExecutorService] queue the thread queue
151
+ #
152
+ # @return [void]
153
+ #
154
+ def submit_single_object(obj, queue)
155
+
156
+ queue.submit do
157
+
158
+ @logger.debug { "Processing object #{obj.getLabel}" }
159
+
160
+ obj.fitPosition(@parameters)
161
+
162
+ end
163
+
164
+ end
165
+
166
+ ##
167
+ # Fits all the image objects in a single supplied image.
168
+ #
169
+ # Does not check whether the fitting was successful.
170
+ # @param [OpenStruct, #image, #mask] im_set An object that references the image
171
+ # and the mask from which the objects will be fit. Should respond to #image and #mask.
172
+ # @return [Array<ImageObject>] an array containing all the image objects in the image
173
+ # (one per unique greylevel in the mask).
174
+ #
175
+ def fit_objects_in_single_image(im_set)
176
+
177
+ objs = []
178
+
179
+ load_and_dark_correct_image(im_set)
180
+
181
+ unless im_set.image and im_set.mask then
182
+
183
+ logger.error { "Unable to process image #{im_set.image_fn}." }
184
+
185
+ return objs
186
+
187
+ end
188
+
189
+ h = Histogram.new(im_set.mask)
190
+
191
+ max_threads = 1
192
+
193
+ if @parameters[:max_threads] then
194
+ max_threads = @parameters[:max_threads].to_i
195
+ end
196
+
197
+ thread_queue = Executors.newFixedThreadPool(max_threads)
198
+
199
+ 1.upto(h.getMaxValue) do |i|
200
+
201
+ obj = GaussianImageObject.new(i, image_shallow_copy(im_set.mask), image_shallow_copy(im_set.image), ParameterDictionary.new(@parameters))
202
+
203
+ obj.setImageID(im_set.image_fn)
204
+
205
+ objs << obj
206
+
207
+ end
208
+
209
+ objs.each do |obj|
210
+
211
+ submit_single_object(obj, thread_queue)
212
+
213
+ end
214
+
215
+ thread_queue.shutdown
216
+
217
+ until thread_queue.isTerminated do
218
+ sleep 0.4
219
+ end
220
+
221
+ objs
222
+
223
+ end
224
+
225
+
226
+ ##
227
+ # Checks whether the fitting was successful for a given object according to several criteria:
228
+ # whether the fitting finished without error, whether the R^2 value of the fit is above the
229
+ # cutoff, whether the object is too close to the image edges, whether the camera is saturated
230
+ # in the object, whether the separation between channels is above some cutoff, and whether the
231
+ # calculated fitting error is too large. Cutoffs for all these criteria are specified in the
232
+ # parameters file.
233
+ #
234
+ # @param [ImageObject] to_check the ImageObject to check for fitting success
235
+ # @return [Boolean] whether the fitting was successful by all criteria.
236
+ #
237
+ def check_fit(to_check)
238
+
239
+ checks = [:check_r2, :check_edges, :check_saturation, :check_separation, :check_error]
240
+
241
+ to_check.finishedFitting and checks.all? { |c| self.send(c, to_check) }
242
+
243
+ end
244
+
245
+
246
+ ##
247
+ # Checks whether the fit R^2 value is below the specified cutoff.
248
+ #
249
+ # @param (see #check_fit)
250
+ # @return [Boolean] whether the fitting was successful by this criterion.
251
+ #
252
+ def check_r2(to_check)
253
+
254
+ return true unless @parameters[:residual_cutoff]
255
+
256
+ to_check.getFitR2ByChannel.each do |r2|
257
+
258
+ if r2 < @parameters[:residual_cutoff].to_f then
259
+
260
+ @failures[:r2] += 1
261
+
262
+ @logger.debug { "check failed for object #{to_check.getLabel} R^2 = #{r2}" }
263
+
264
+ return false
265
+
266
+ end
267
+
268
+ end
269
+
270
+ true
271
+
272
+ end
273
+
274
+ ##
275
+ # Checks whether the fitted position is too close to the image edges.
276
+ # @param (see #check_fit)
277
+ # @return (see #check_r2)
278
+ #
279
+ def check_edges(to_check)
280
+
281
+ eps = 0.1
282
+
283
+ border_size = @parameters[:im_border_size].to_f
284
+ z_size = @parameters[:half_z_size].to_f
285
+
286
+ range_x = border_size...(to_check.getParent.getDimensionSizes[:x] - border_size)
287
+ range_y = border_size...(to_check.getParent.getDimensionSizes[:y] - border_size)
288
+ range_z = z_size...(to_check.getParent.getDimensionSizes[:z] - z_size)
289
+
290
+ to_check.getFitParametersByChannel.each do |fp|
291
+
292
+ x = fp.getPosition(ImageCoordinate::X)
293
+ y = fp.getPosition(ImageCoordinate::Y)
294
+ z = fp.getPosition(ImageCoordinate::Z)
295
+
296
+ ok = (range_x.include?(x) and range_y.include?(y) and range_z.include?(z))
297
+
298
+ unless ok then
299
+
300
+ @failures[:edge] += 1
301
+
302
+ @logger.debug { "check failed for object #{to_check.getLabel} position: #{x}, #{y}, #{z}" }
303
+
304
+ return false
305
+
306
+ end
307
+
308
+ end
309
+
310
+ true
311
+
312
+ end
313
+
314
+
315
+ ##
316
+ # Checks whether the camera has saturated in the object.
317
+ # @param (see #check_fit)
318
+ # @return (see #check_r2)
319
+ #
320
+ def check_saturation(to_check)
321
+
322
+ if @parameters[:max_greylevel_cutoff] then
323
+
324
+ to_check.boxImages
325
+
326
+ cutoff = @parameters[:max_greylevel_cutoff].to_f
327
+
328
+ to_check.getParent.each do |ic|
329
+
330
+ if to_check.getParent[ic] > cutoff then
331
+
332
+ to_check.unboxImages
333
+ @failures[:sat] += 1
334
+
335
+ @logger.debug { "check failed for object #{to_check.getLabel} greylevel: #{to_check.getParent[ic]}" }
336
+
337
+ return false
338
+
339
+ end
340
+
341
+ end
342
+
343
+ end
344
+
345
+ true
346
+
347
+ end
348
+
349
+ ##
350
+ # Checks whether the separation between channels is too large.
351
+ #
352
+ # Note that this check can significantly skew the distance measurements if the cutoff is too small.
353
+ # This remains here because occasionally closely spaced objects are fit as a single object and produce
354
+ # ridiculous values.
355
+ #
356
+ # @param (see #check_fit)
357
+ # @return (see #check_r2)
358
+ #
359
+ def check_separation(to_check)
360
+
361
+ if @parameters[:distance_cutoff] then
362
+
363
+ size_c = to_check.getFitParametersByChannel.size
364
+
365
+ xy_pixelsize_2 = @parameters[:pixelsize_nm].to_f**2
366
+
367
+ z_sectionsize_2 = @parameters[:z_sectionsize_nm].to_f**2
368
+
369
+ 0.upto(size_c-1) do |ci|
370
+ 0.upto(size_c-1) do |cj|
371
+
372
+ fp1 = to_check.getFitParametersByChannel.get(ci)
373
+ fp2 = to_check.getFitParametersByChannel.get(cj)
374
+
375
+ ijdist = xy_pixelsize_2 * (fp1.getPosition(ImageCoordinate::X) - fp2.getPosition(ImageCoordinate::X))**2 +
376
+ xy_pixelsize_2 * (fp1.getPosition(ImageCoordinate::Y) - fp2.getPosition(ImageCoordinate::Y))**2 +
377
+ z_sectionsize_2 * (fp1.getPosition(ImageCoordinate::Z) - fp2.getPosition(ImageCoordinate::Z))**2
378
+
379
+ ijdist = ijdist**0.5
380
+
381
+ if (ijdist > @parameters[:distance_cutoff].to_f) then
382
+
383
+ @failures[:sep] += 1
384
+ @logger.debug { "check failed for object #{to_check.getLabel} with distance: #{ijdist}" }
385
+
386
+ return false
387
+
388
+ end
389
+
390
+ end
391
+ end
392
+
393
+ end
394
+
395
+ true
396
+
397
+ end
398
+
399
+
400
+ ##
401
+ # Checks whether the caluclated fitting error (summed in quadrature over all wavelengths)
402
+ # is larger than a specified cutoff.
403
+ #
404
+ # @param (see #check_fit)
405
+ # @return (see #check_r2)
406
+ #
407
+ def check_error(to_check)
408
+
409
+ if @parameters[:fit_error_cutoff] then
410
+
411
+ total_error = 0
412
+
413
+ to_check.getFitErrorByChannel.each do |d|
414
+
415
+ total_error += d**2
416
+
417
+ end
418
+
419
+ total_error = total_error**0.5
420
+
421
+ if total_error > @parameters[:fit_error_cutoff].to_f or total_error.nan? then
422
+
423
+ @failures[:err] += 1
424
+
425
+ @logger.debug { "check failed for object #{to_check.getLabel} with total fitting error: #{total_error}" }
426
+
427
+ return false
428
+
429
+ end
430
+
431
+ end
432
+
433
+ true
434
+
435
+ end
436
+
437
+ ##
438
+ # Sets up a logger to either standard output or a file with appropriate detail level
439
+ # as specified in the parameters
440
+ #
441
+ # @return (void)
442
+ #
443
+ def set_up_logging
444
+
445
+ if @parameters[:log_to_file] then
446
+
447
+ @logger = Logger.new(@parameters[:log_to_file])
448
+
449
+ else
450
+
451
+ @logger = Logger.new(STDOUT)
452
+
453
+ end
454
+
455
+ if @parameters[:log_detailed_messages] then
456
+
457
+ @logger.sev_threshold = Logger::DEBUG
458
+
459
+ else
460
+
461
+ @logger.sev_threshold = Logger::INFO
462
+
463
+ end
464
+
465
+ end
466
+
467
+ ##
468
+ # Loads previously existing image objects for the current images or fits them anew
469
+ # if they don't exist or this is requested in the parameters.
470
+ #
471
+ # @return [Array<ImageObject>] The image objects that have been loaded or fit. Only
472
+ # successfully fit objects that have passed all checks are included.
473
+ #
474
+ def load_or_fit_image_objects
475
+
476
+ image_objects = load_position_data
477
+
478
+ unless image_objects then
479
+
480
+ image_objects = []
481
+
482
+ to_process = FileInteraction.list_files(@parameters)
483
+
484
+ to_process.each do |im_set|
485
+
486
+ objs = fit_objects_in_single_image(im_set)
487
+
488
+ objs.each do |o|
489
+
490
+ if check_fit(o) then
491
+
492
+ image_objects << o
493
+
494
+ end
495
+
496
+ o.nullifyImages
497
+
498
+ end
499
+
500
+ end
501
+
502
+ log_fitting_failures
503
+
504
+ end
505
+
506
+ image_objects
507
+
508
+ end
509
+
510
+
511
+ ##
512
+ # Formats the fitting failures using their description strings and logs them
513
+ #
514
+ # @return [void]
515
+ #
516
+ def log_fitting_failures
517
+
518
+ @logger.info { "fitting failures by type:" }
519
+
520
+ @failures.each_key do |k|
521
+
522
+ @logger.info { FAILURE_REASONS[k] + ": " + @failures[k].to_s }
523
+
524
+ end
525
+
526
+ end
527
+
528
+
529
+ ##
530
+ # Runs the analysis.
531
+ #
532
+ # @return [void]
533
+ #
534
+ def go
535
+
536
+ image_objects = load_or_fit_image_objects
537
+
538
+ FileInteraction.write_position_data(image_objects, @parameters)
539
+
540
+ pc = PositionCorrector.new(@parameters)
541
+
542
+ c = pc.generate_correction(image_objects)
543
+
544
+ tre = 0.0
545
+
546
+ if @parameters[:determine_tre] and @parameters[:determine_correction] then
547
+
548
+ puts "calculating tre"
549
+
550
+ tre = pc.determine_tre(image_objects)
551
+
552
+ c.tre= tre
553
+
554
+ else
555
+
556
+ tre = c.tre
557
+
558
+ end
559
+
560
+
561
+ c.write_to_file(FileInteraction.correction_filename(@parameters))
562
+
563
+
564
+
565
+ diffs = pc.apply_correction(c, image_objects)
566
+
567
+ corrected_image_objects = []
568
+
569
+ image_objects.each do |iobj|
570
+
571
+ if iobj.getCorrectionSuccessful then
572
+
573
+ corrected_image_objects << iobj
574
+
575
+ end
576
+
577
+ end
578
+
579
+ FileInteraction.write_position_data(corrected_image_objects, @parameters)
580
+
581
+
582
+ image_objects = corrected_image_objects
583
+
584
+ df= P3DFitter.new(@parameters)
585
+
586
+ fitparams = df.fit(image_objects, diffs)
587
+
588
+ @logger.info { "p3d fit parameters: #{fitparams.join(', ')}" }
589
+
590
+ if @parameters[:in_situ_aberr_corr_basename] and @parameters[:in_situ_aberr_corr_channel] then
591
+
592
+ slopes = pc.determine_in_situ_aberration_correction
593
+
594
+ vector_diffs = pc.apply_in_situ_aberration_correction(image_objects, slopes)
595
+
596
+ scalar_diffs = get_scalar_diffs_from_vector(vector_diffs)
597
+
598
+ corr_fit_params = df.fit(image_objects, scalar_diffs)
599
+
600
+ FileInteraction.write_differences(diffs, @parameters)
601
+
602
+ if corr_fit_params then
603
+
604
+ @logger.info { "p3d fit parameters after in situ correction: #{fitparams.join(', ') }" }
605
+
606
+ else
607
+
608
+ @logger.info { "unable to fit after in situ correction" }
609
+
610
+ end
611
+
612
+ end
613
+
614
+ end
615
+
616
+ ##
617
+ # Converts an array of vectors to an array of scalars by taking their 2-norm.
618
+ #
619
+ # @param [Enumerable< Enumerable<Numeric> >] vector_diffs an array of arrays (vectors, etc.)
620
+ # each of which will be normed.
621
+ #
622
+ # @return [Array] an array of the norms of the vectors provided.
623
+ #
624
+ def get_scalar_diffs_from_vector(vector_diffs)
625
+
626
+ vector_diffs.map do |vd|
627
+
628
+ Math.sqrt(Math.sum(vd) { |e| e**2 })
629
+
630
+ end
631
+
632
+ end
633
+
634
+ ##
635
+ # Runs analysis using a specified parameter file.
636
+ #
637
+ # @param [String] fn the filename of the parameter file
638
+ #
639
+ # @return [void]
640
+ #
641
+ def self.run_from_parameter_file(fn)
642
+
643
+ java_import Java::edu.stanford.cfuller.imageanalysistools.meta.AnalysisMetadataParserFactory
644
+
645
+ parser = AnalysisMetadataParserFactory.createParserForFile(fn)
646
+
647
+ p = parser.parseFileToParameterDictionary(fn)
648
+
649
+ c = new(p)
650
+
651
+ c.go
652
+
653
+ end
654
+
655
+
656
+ end
657
+
658
+ end
659
+
660
+ ##
661
+ # If this file is run from the command line, start the analysis using the parameter file
662
+ # specified on the command line.
663
+ #
664
+ if __FILE__ == $0 then
665
+
666
+ Cicada::CicadaMain.run_from_parameter_file(ARGV[0])
667
+
668
+ end
669
+
670
+
671
+
672
+
673
+
674
+