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 +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
|
+
|