cicada 0.9.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cicada +37 -0
- data/lib/cicada.rb +31 -0
- data/lib/cicada/cicada_main.rb +674 -0
- data/lib/cicada/correction/correction.rb +363 -0
- data/lib/cicada/correction/position_corrector.rb +501 -0
- data/lib/cicada/file_interaction.rb +377 -0
- data/lib/cicada/fitting/p3d_fitter.rb +235 -0
- data/lib/cicada/mutable_matrix.rb +133 -0
- data/lib/cicada/version.rb +33 -0
- data/spec/cicada/correction/correction_spec.rb +114 -0
- data/spec/cicada/correction/position_corrector_spec.rb +169 -0
- data/spec/cicada/file_interaction_spec.rb +58 -0
- data/spec/cicada/fitting/p3d_fitter_spec.rb +79 -0
- data/spec/cicada/mutable_matrix_spec.rb +89 -0
- data/spec/spec_helper.rb +50 -0
- metadata +137 -0
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
|
+
|