cicada 0.9.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,363 @@
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 'rexml/document'
28
+ require 'ostruct'
29
+ require 'cicada/mutable_matrix'
30
+
31
+ require 'facets/enumerable/ewise'
32
+
33
+ module Cicada
34
+
35
+ ##
36
+ # An error indicating that a position cannot be corrected (probably due to incomplete
37
+ # coverage in the correction dataset).
38
+ class UnableToCorrectError < StandardError; end
39
+
40
+ ##
41
+ # Stores data for a standard 3d high-resolution colocalization correction, including
42
+ # positions for a number of objects used for correction, and local quadratic fits of
43
+ # aberration near these objects. Can correct 2d positions based upon this data.
44
+ #
45
+ class Correction
46
+
47
+ # Strings used in XML elements and attributes when writing a correction to an XML format
48
+ XML_STRINGS = { correction_element: "correction",
49
+ correction_point_element: "point",
50
+ n_points_attr: "n",
51
+ ref_channel_attr: "reference_channel",
52
+ corr_channel_attr: "correction_channel",
53
+ x_pos_attr: "x_position",
54
+ y_pos_attr: "y_position",
55
+ z_pos_attr: "z_position",
56
+ x_param_element: "x_dimension_parameters",
57
+ y_param_element: "y_dimension_parameters",
58
+ z_param_element: "z_dimension_parameters",
59
+ binary_data_element: "serialized_form",
60
+ encoding_attr: "encoding",
61
+ encoding_name: "base64"}
62
+
63
+ # Number of parameters used for correction (6, as this is the number of parameters
64
+ # for a 2d quadratic fit)
65
+ NUM_CORR_PARAM = 6
66
+
67
+ attr_accessor :tre, :correction_x, :correction_y, :correction_z, :reference_channel, :correction_channel, :positions_for_correction, :distance_cutoffs
68
+
69
+ ##
70
+ # Constructs a new correction based on the supplied data
71
+ #
72
+ # @param [Array<MVector>] c_x an array of mutable vectors each of which contains the parameters for
73
+ # the interpolating function centered at an image object used for correction in the x direction
74
+ # @param [Array<MVector>] c_y an array of mutable vectors each of which contains the parameters for
75
+ # the interpolating function centered at an image object used for correction in the y direction
76
+ # @param [Array<MVector>] c_z an array of mutable vectors each of which contains the parameters for
77
+ # the interpolating function centered at an image object used for correction in the z direction
78
+ # @param [MVector] distance_cutoffs a mutable vector containing the distance to the farthest point
79
+ # used to generate the interpolating function for each image object
80
+ # @param [Array<ImageObject>] image_objects the image objects used for correction
81
+ # @param [Integer] reference_channel the reference channel relative to which others are corrected
82
+ # @param [Integer] correction_channel the channel being corrected
83
+ #
84
+ def initialize(c_x, c_y, c_z, distance_cutoffs, image_objects, reference_channel, correction_channel)
85
+
86
+ @correction_x = c_x
87
+ @correction_y = c_y
88
+ @correction_z = c_z
89
+ @reference_channel = reference_channel
90
+ @correction_channel = correction_channel
91
+ @distance_cutoffs = distance_cutoffs
92
+
93
+ n_dims = 3
94
+
95
+ @positions_for_correction = MMatrix.build(image_objects.size, n_dims) do |r, c|
96
+
97
+ image_objects[r].getPositionForChannel(reference_channel).toArray[c]
98
+
99
+ end
100
+
101
+ end
102
+
103
+ ##
104
+ # Writes the correction to a specified file in XML format.
105
+ #
106
+ # @param [String] fn the filename to which to write the correction
107
+ #
108
+ # @return [void]
109
+ #
110
+ def write_to_file(fn)
111
+
112
+ File.open(fn, 'w') do |f|
113
+
114
+ f.puts(write_to_xml)
115
+
116
+ end
117
+
118
+ end
119
+
120
+ ##
121
+ # Writes the correction to a string in XML format
122
+ #
123
+ # @return [String] the correction data encoded as XML
124
+ #
125
+ def write_to_xml
126
+
127
+ doc = REXML::Document.new
128
+
129
+ ce = doc.add_element XML_STRINGS[:correction_element]
130
+
131
+ ce.attributes[XML_STRINGS[:n_points_attr]] = @distance_cutoffs.size
132
+
133
+ ce.attributes[XML_STRINGS[:ref_channel_attr]] = @reference_channel
134
+
135
+ ce.attributes[XML_STRINGS[:corr_channel_attr]] = @correction_channel
136
+
137
+ @distance_cutoffs.each_with_index do |e, i|
138
+
139
+ cp = ce.add_element XML_STRINGS[:correction_point_element]
140
+
141
+ cp.attributes[XML_STRINGS[:x_pos_attr]]= @positions_for_correction[i,0]
142
+ cp.attributes[XML_STRINGS[:y_pos_attr]]= @positions_for_correction[i,1]
143
+ cp.attributes[XML_STRINGS[:z_pos_attr]]= @positions_for_correction[i,2]
144
+
145
+ xp = cp.add_element XML_STRINGS[:x_param_element]
146
+
147
+ xp.text = @correction_x[i].to_a.join(", ")
148
+
149
+ yp = cp.add_element XML_STRINGS[:y_param_element]
150
+
151
+ yp.text = @correction_y[i].to_a.join(", ")
152
+
153
+ zp = cp.add_element XML_STRINGS[:z_param_element]
154
+
155
+ zp.text = @correction_z[i].to_a.join(", ")
156
+
157
+ end
158
+
159
+ bd = ce.add_element XML_STRINGS[:binary_data_element]
160
+
161
+ bd.attributes[XML_STRINGS[:encoding_attr]]= XML_STRINGS[:encoding_name]
162
+
163
+ bin_data = Base64.encode64(Marshal.dump(self))
164
+
165
+ bd.text = bin_data
166
+
167
+ doc_string = ""
168
+
169
+ doc.write doc_string, 2
170
+
171
+ doc_string
172
+
173
+ end
174
+
175
+ ##
176
+ # Reads a correction from a specified file containing an XML-encoded correction.
177
+ #
178
+ # @param [String] fn the filename from which to read the correction
179
+ #
180
+ # @return [Correction] the correction contained in the file.
181
+ #
182
+ def self.read_from_file(fn)
183
+
184
+ return nil unless File.exist?(fn)
185
+
186
+ xml_str = ""
187
+
188
+ File.open(fn) do |f|
189
+
190
+ xml_str = f.read
191
+
192
+ end
193
+
194
+ read_from_xml(xml_str)
195
+
196
+ end
197
+
198
+
199
+ ##
200
+ # Reads a correction from an XML string.
201
+ #
202
+ # @param [String] xml_str the XML string containing the information
203
+ #
204
+ # @return [Correction] the correction contained in the string.
205
+ #
206
+ def self.read_from_xml(xml_str)
207
+
208
+ doc = REXML::Document.new xml_str
209
+
210
+ bin_el = doc.root.elements[1, XML_STRINGS[:binary_data_element]]
211
+
212
+ Marshal.load(Base64.decode64(bin_el.text))
213
+
214
+ end
215
+
216
+
217
+
218
+ ##
219
+ # Calculates the 2d distances from a specified 2d point to the centroid of each of the image objects
220
+ # used for the correction.
221
+ #
222
+ # @param [Numeric] x the x coordinate of the point
223
+ # @param [Numeric] y the y coordinate of the point
224
+ #
225
+ # @return [Vector] the distance from the specified point to each image object.
226
+ #
227
+ def calculate_normalized_dists_to_centroids(x,y)
228
+
229
+ dists_to_centroids = @positions_for_correction.column(0).map { |x0| (x0-x)**2 }
230
+
231
+ dists_to_centroids += @positions_for_correction.column(1).map { |y0| (y0-y)**2 }
232
+
233
+ dists_to_centroids = dists_to_centroids.map { |e| Math.sqrt(e) }
234
+
235
+ dists_to_centroids.map2(@distance_cutoffs) { |e1, e2| e1/e2 }
236
+
237
+ end
238
+
239
+ ##
240
+ # Calculates the weight of each local quadratic fit for correcting a specified point.
241
+ #
242
+ # @param (see #calculate_normalized_dists_to_centroids)
243
+ #
244
+ # @return [Vector] the weights for each local fit used for correction
245
+ #
246
+ def calculate_weights(x, y)
247
+
248
+ dist_ratio = calculate_normalized_dists_to_centroids(x,y)
249
+
250
+ dist_ratio_mask = MVector.zero(dist_ratio.size)
251
+
252
+ dist_ratio_mask = dist_ratio.map { |e| e <= 1 ? 1 : 0 }
253
+
254
+ weights = dist_ratio.map { |e| -3*e**2 + 1 + 2*e**3 }
255
+
256
+ weights.map2(dist_ratio_mask) { |e1, e2| e1*e2 }
257
+
258
+ end
259
+
260
+ ##
261
+ # Selects the local fits and their associated image objects that are to be used for correcting
262
+ # a specified point (i.e. those fits with nonzero weight).
263
+ #
264
+ # @param (see #calculate_normalized_dists_to_centroids)
265
+ #
266
+ # @return [OpenStruct, #cx, #cy, #cz, #x_vec, #y_vec, #weights] an OpenStruct containing the
267
+ # selected fits for the x dimension, y dimension, and z dimension; the x and y positions
268
+ # of the selected image objects used for correction; and the weights of the fits
269
+ #
270
+ def find_points_for_correction(x,y)
271
+
272
+ weights = calculate_weights(x,y)
273
+
274
+ count_weights = weights.count { |e| e > 0 }
275
+
276
+ raise UnableToCorrectError, "Incomplete coverage in correction dataset at (x,y) = (#{x}, #{y})." if count_weights == 0
277
+
278
+ cx = MMatrix.zero(count_weights, @correction_x[0].size)
279
+ cy = MMatrix.zero(count_weights, @correction_y[0].size)
280
+ cz = MMatrix.zero(count_weights, @correction_z[0].size)
281
+
282
+ x_vec = MVector.zero(count_weights)
283
+ y_vec = MVector.zero(count_weights)
284
+
285
+ kept_weights = MVector.zero(count_weights)
286
+
287
+ kept_counter = 0
288
+
289
+ weights.each_with_index do |w, i|
290
+
291
+ if w > 0 then
292
+
293
+ cx.replace_row(kept_counter, @correction_x[i])
294
+ cy.replace_row(kept_counter, @correction_y[i])
295
+ cz.replace_row(kept_counter, @correction_z[i])
296
+
297
+ x_vec[kept_counter] = x - positions_for_correction[i,0]
298
+ y_vec[kept_counter] = y - positions_for_correction[i,1]
299
+
300
+ kept_weights[kept_counter] = weights[i]
301
+
302
+ kept_counter += 1
303
+
304
+ end
305
+
306
+ end
307
+
308
+ OpenStruct.new(cx: cx,
309
+ cy: cy,
310
+ cz: cz,
311
+ x_vec: x_vec,
312
+ y_vec: y_vec,
313
+ weights: kept_weights)
314
+
315
+ end
316
+
317
+ ##
318
+ # Calculates the correction for a specified position.
319
+ #
320
+ # @param (see #calculate_normalized_dists_to_centroids)
321
+ #
322
+ # @return [MVector] a mutable vector containing the correction in the x, y, and z dimensions
323
+ # for the specified position.
324
+ #
325
+ def correct_position(x, y)
326
+
327
+ points = find_points_for_correction(x,y)
328
+
329
+ x_corr = 0.0
330
+ y_corr = 0.0
331
+ z_corr = 0.0
332
+
333
+ all_correction_parameters = MMatrix.columns([MVector.unit(points.x_vec.size),
334
+ points.x_vec,
335
+ points.y_vec,
336
+ points.x_vec.map { |e| e**2 },
337
+ points.y_vec.map { |e| e**2 },
338
+ points.x_vec.map2(points.y_vec) { |e1, e2| e1*e2 }])
339
+
340
+
341
+ all_correction_parameters.row_size.times do |i|
342
+
343
+ x_corr += all_correction_parameters.row(i).inner_product(points.cx.row(i))*points.weights[i]
344
+ y_corr += all_correction_parameters.row(i).inner_product(points.cy.row(i))*points.weights[i]
345
+ z_corr += all_correction_parameters.row(i).inner_product(points.cz.row(i))*points.weights[i]
346
+
347
+ end
348
+
349
+ sum_weights = points.weights.reduce(0.0) { |a,e| a + e }
350
+
351
+ x_corr /= sum_weights
352
+ y_corr /= sum_weights
353
+ z_corr /= sum_weights
354
+
355
+ MVector[x_corr, y_corr, z_corr]
356
+
357
+ end
358
+
359
+ end
360
+
361
+ end
362
+
363
+
@@ -0,0 +1,501 @@
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 'cicada/mutable_matrix'
28
+
29
+ require 'cicada/correction/correction'
30
+
31
+ require 'ostruct'
32
+
33
+ require 'pqueue'
34
+
35
+ require 'facets/enumerable/ewise'
36
+ require 'facets/math/mean'
37
+
38
+ require 'rimageanalysistools/fitting/bisquare_linear_fit'
39
+ require 'rimageanalysistools/thread_queue'
40
+
41
+ java_import Java::org.apache.commons.math3.linear.ArrayRealVector
42
+ java_import Java::java.util.concurrent.Executors
43
+
44
+ module Cicada
45
+
46
+ ##
47
+ # Generates and applies aberration corrections. Used both for standard 3d
48
+ # high-resolution colocalization corrections and in-situ corrections.
49
+ #
50
+ class PositionCorrector
51
+
52
+ # parameters required by the methods in this class
53
+ REQUIRED_PARAMETERS = [:pixelsize_nm, :z_sectionsize_nm, :num_points, :reference_channel, :channel_to_correct]
54
+
55
+ # parmeters used but not required in this class or only required for optional functionality
56
+ OPTIONAL_PARAMETERS = [:determine_correction, :max_threads, :in_situ_aberr_corr_channel, :inverted_z_axis, :disable_in_situ_corr_constant_offset]
57
+
58
+ # Number of parameters used for correction (6, as this is the number of parameters
59
+ # for a 2d quadratic fit)
60
+ NUM_CORR_PARAM = 6
61
+
62
+ attr_accessor :parameters, :pixel_to_distance_conversions
63
+
64
+ ##
65
+ # Constructs a new position corrector with the specified parameters
66
+ #
67
+ # @param [ParameterDictionary, Hash] p a hash-like object containing the analysis parameters
68
+ #
69
+ def initialize(p)
70
+ @parameters = p
71
+ @pixel_to_distance_conversions = Vector[p[:pixelsize_nm].to_f, p[:pixelsize_nm].to_f, p[:z_sectionsize_nm].to_f]
72
+ end
73
+
74
+
75
+ ##
76
+ # Creates a RealVector (org.apache.commons.math3.linear.RealVector) that is a copy of
77
+ # the contents of the supplied vector.
78
+ #
79
+ # @param [Vector] vec the Vector to convert
80
+ #
81
+ # @return [RealVector] the commons math RealVector containing the same elements
82
+ #
83
+ def self.convert_to_realvector(vec)
84
+
85
+ conv = ArrayRealVector.new(vec.size, 0.0)
86
+
87
+ vec.each_with_index do |e, i|
88
+
89
+ conv.setEntry(i, e)
90
+
91
+ end
92
+
93
+ conv
94
+
95
+ end
96
+
97
+
98
+ ##
99
+ # Generates a correction from a specified array of image objects.
100
+ #
101
+ # @param [Array<ImageObject>] iobjs the image objects to be used for the correction
102
+ #
103
+ # @return [Correction] the correction generated from the input objects
104
+ #
105
+ def generate_correction(iobjs)
106
+
107
+ #TODO refactor into smaller chunks
108
+
109
+ ref_ch = parameters[:reference_channel].to_i
110
+ corr_ch = parameters[:channel_to_correct].to_i
111
+
112
+ unless parameters[:determine_correction] then
113
+
114
+ return Correction.read_from_file(FileInteraction.correction_filename(parameters))
115
+
116
+ end
117
+
118
+ correction_x = []
119
+ correction_y = []
120
+ correction_z = []
121
+
122
+ distance_cutoffs = MVector.zero(iobjs.size)
123
+
124
+ iobjs.each_with_index do |obj, ind|
125
+
126
+ obj_pos = obj.getPositionForChannel(ref_ch)
127
+
128
+ distances_to_objects = iobjs.map { |obj2| obj2.getPositionForChannel(ref_ch).subtract(obj_pos).getNorm }
129
+
130
+ pq = PQueue.new
131
+
132
+ np = @parameters[:num_points].to_i
133
+
134
+ distances_to_objects.each do |d|
135
+
136
+ if pq.size < np + 1 then
137
+
138
+ pq.push d
139
+
140
+ elsif d < pq.top then
141
+
142
+ pq.pop
143
+ pq.push d
144
+
145
+ end
146
+
147
+ end
148
+
149
+
150
+ first_exclude = pq.pop
151
+
152
+ last_dist = pq.pop
153
+
154
+ distance_cutoff = (last_dist + first_exclude)/2.0
155
+
156
+ distance_cutoffs[ind] = distance_cutoff
157
+
158
+ objs_ind_to_fit = (0...iobjs.size).select { |i| distances_to_objects[i] < distance_cutoff }
159
+
160
+ objs_to_fit = iobjs.values_at(*objs_ind_to_fit)
161
+
162
+ diffs_to_fit = MMatrix[*objs_to_fit.map { |e| e.getVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray }]
163
+ x_to_fit = objs_to_fit.map { |e| e.getPositionForChannel(ref_ch).getEntry(0) }
164
+ y_to_fit = objs_to_fit.map { |e| e.getPositionForChannel(ref_ch).getEntry(1) }
165
+
166
+ x = Vector[*x_to_fit.map { |e| e - obj_pos.getEntry(0) }]
167
+ y = Vector[*y_to_fit.map { |e| e - obj_pos.getEntry(1) }]
168
+
169
+ correction_parameters = Matrix.columns([MVector.unit(objs_to_fit.size), x, y, x.map { |e| e**2 }, y.map { |e| e**2 }, x.map2(y) { |ex, ey| ex*ey }])
170
+
171
+ cpt = correction_parameters.transpose
172
+
173
+ cpt_cp = cpt * correction_parameters
174
+
175
+ cpt_cp_lup = cpt_cp.lup
176
+
177
+ correction_x << cpt_cp_lup.solve(cpt * diffs_to_fit.column(0))
178
+ correction_y << cpt_cp_lup.solve(cpt * diffs_to_fit.column(1))
179
+ correction_z << cpt_cp_lup.solve(cpt * diffs_to_fit.column(2))
180
+
181
+
182
+ end
183
+
184
+ Correction.new(correction_x, correction_y, correction_z, distance_cutoffs, iobjs, ref_ch, corr_ch)
185
+
186
+ end
187
+
188
+ ##
189
+ # Changes the scale of a vector from image units to physical distances using distance specified
190
+ # in the analysis parameters.
191
+ #
192
+ # @param [Vector] vec the vector to scale
193
+ #
194
+ # @return [Vector] the vector scaled to physical units (by parameter naming convention, in nm)
195
+ #
196
+ def apply_scale(vec)
197
+
198
+ vec.map2(@pixel_to_distance_conversions) { |e1, e2| e1*e2 }
199
+
200
+ end
201
+
202
+ ##
203
+ # Corrects an array of image objects using the provided correction.
204
+ #
205
+ # @param [Correction] c the correction to be used
206
+ # @param [Array<ImageObject>] iobjs the image objects to be corrected.
207
+ #
208
+ # @return [Array<Numeric>] the corrected scalar difference between
209
+ # wavelengths for each image object provided.
210
+ #
211
+ def apply_correction(c, iobjs)
212
+
213
+ ref_ch = @parameters[:reference_channel].to_i
214
+ corr_ch = @parameters[:channel_to_correct].to_i
215
+
216
+ vec_diffs = iobjs.map { |e| e.getVectorDifferenceBetweenChannels(ref_ch, corr_ch) }
217
+
218
+ vec_diffs.map! { |e| apply_scale(Vector[*e.toArray]) }
219
+
220
+ corrected_vec_diffs = []
221
+
222
+ if @parameters[:correct_images] then
223
+
224
+ iobjs.each do |iobj|
225
+
226
+ begin
227
+
228
+ corrected_vec_diffs << correct_single_object(c, iobj, ref_ch, corr_ch)
229
+
230
+ iobj.setCorrectionSuccessful(true)
231
+
232
+ rescue UnableToCorrectError => e
233
+
234
+ iobj.setCorrectionSuccessful(false)
235
+
236
+ end
237
+
238
+ end
239
+
240
+ corrected_vec_diffs.map! { |e| apply_scale(e) }
241
+
242
+ else
243
+
244
+ corrected_vec_diffs = vec_diffs
245
+
246
+ end
247
+
248
+ print_distance_components(vec_diffs, corrected_vec_diffs)
249
+
250
+ corrected_vec_diffs.map { |e| e.norm }
251
+
252
+ end
253
+
254
+ ##
255
+ # Prints the mean scalar and vector differences both corrected and uncorrected.
256
+ #
257
+ # @param [Array<Vector>] vec_diffs an array of the uncorrected vector differences
258
+ # @param [Array<Vector>] corrected_vec_diffs an array of the corrected vector differences
259
+ #
260
+ # @return [void]
261
+ #
262
+ def print_distance_components(vec_diffs, corrected_vec_diffs)
263
+
264
+ mean_uncorr_vec = [0.0, 0.0, 0.0]
265
+
266
+ vec_diffs.each do |e|
267
+
268
+ mean_uncorr_vec = mean_uncorr_vec.ewise + e.to_a
269
+
270
+ end
271
+
272
+ mean_corr_vec = [0.0, 0.0, 0.0]
273
+
274
+ corrected_vec_diffs.each do |e|
275
+
276
+ mean_corr_vec = mean_corr_vec.ewise + e.to_a
277
+
278
+ end
279
+
280
+ mean_uncorr_vec.map! { |e| e / vec_diffs.length }
281
+
282
+ mean_corr_vec.map! { |e| e / corrected_vec_diffs.length }
283
+
284
+ #TODO - logging
285
+
286
+ puts "mean components uncorrected: [#{mean_uncorr_vec.join(', ')}]"
287
+ puts "mean distance uncorrected: #{Vector[*mean_uncorr_vec].norm}"
288
+ puts "mean components corrected: [#{mean_corr_vec.join(', ')}]"
289
+ puts "mean distance corrected: #{Vector[*mean_corr_vec].norm}"
290
+
291
+ end
292
+
293
+ ##
294
+ # Corrects a single image object for the two specified channels.
295
+ #
296
+ # @param [Correction] c the correction to be used
297
+ # @param [ImageObject] iobj the object being corrected
298
+ # @param [Integer] ref_ch the reference channel relative to which the other will be corrected
299
+ # @param [Integer] corr_ch the channel being corrected
300
+ #
301
+ # @return [Vector] the corrected (x,y,z) vector difference between the two channels
302
+ #
303
+ def correct_single_object(c, iobj, ref_ch, corr_ch)
304
+
305
+ corr = c.correct_position(iobj.getPositionForChannel(ref_ch).getEntry(0), iobj.getPositionForChannel(corr_ch).getEntry(1))
306
+
307
+ if parameters[:invert_z_axis] then
308
+
309
+ corr.setEntry(2, -1.0*corr.getEntry(2))
310
+
311
+ end
312
+
313
+ iobj.applyCorrectionVectorToChannel(corr_ch, PositionCorrector.convert_to_realvector(corr))
314
+
315
+ Vector.elements(iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray)
316
+
317
+ end
318
+
319
+ ##
320
+ # Generates an in situ aberration correction (using the data specified in a parameter file)
321
+ #
322
+ # @return @see #generate_in_situ_correction_from_iobjs
323
+ #
324
+ def generate_in_situ_correction
325
+
326
+ iobjs_for_in_situ_corr = FileInteraction.read_in_situ_corr_data(@parameters)
327
+
328
+ generate_in_situ_correction_from_iobjs(iobjs_for_in_situ_corr)
329
+
330
+ end
331
+
332
+ ##
333
+ # Generates an in situ aberration correction from the supplied image objects.
334
+ #
335
+ # @param [Array<ImageObject>] an array containing the image objects from which the in situ
336
+ # correction will be generated
337
+ #
338
+ # @return [Array< Array<Numeric> >] an array containing the x, y, and z corrections; each
339
+ # correction is a 2-element array containing the slope and intercept for the fit in each
340
+ # dimension. The intercept will be zero if disabled in the parameter file.
341
+ #
342
+ def generate_in_situ_correction_from_iobjs(iobjs_for_in_situ_corr)
343
+
344
+ ref_ch = @parameters[:reference_channel].to_i
345
+ corr_ch = @parameters[:channel_to_correct].to_i
346
+ cicada_ch = @parameters[:in_situ_aberr_corr_channel]
347
+
348
+ corr_diffs = Matrix.rows(iobjs_for_in_situ_corr.map { |iobj| iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, cicada_ch).toArray })
349
+ expt_diffs = Matrix.rows(iobjs_for_in_situ_corr.map { |iobj| iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray })
350
+
351
+ bslf = BisquareLinearFit.new
352
+
353
+ bslf.disableIntercept if @parameters[:disable_in_situ_corr_constant_offset]
354
+
355
+ all_parameters = 0.upto(corr_diffs.column_size - 1).collect do |i|
356
+
357
+ bslf.fit_rb(corr_diffs.column(i), expt_diffs.column(i)).toArray
358
+
359
+ end
360
+
361
+ all_parameters
362
+
363
+ end
364
+
365
+ ##
366
+ # Applies an in situ aberration correction to an array of image objects.
367
+ #
368
+ # @param [Enumerable<ImageObject>] iobjs the objects to be corrected
369
+ # @param [Array< Array<Numeric> >] corr_params the in situ correction parameters (an array
370
+ # for each dimension containing the correction's slope and intercept).
371
+ #
372
+ # @return [Array< Array <Numeric> >] an array of the corrected vector distance between
373
+ # wavelengths for each image object being corrected.
374
+ #
375
+ def apply_in_situ_correction(iobjs, corr_params)
376
+
377
+ corr_params = corr_params.transpose
378
+
379
+ ref_ch = @parameters[:reference_channel].to_i
380
+ corr_ch = @parameters[:channel_to_correct].to_i
381
+ cicada_ch = @parameters[:in_situ_aberr_corr_channel]
382
+
383
+ corrected_differences = iobjs.map do |iobj|
384
+
385
+ corr_diff = iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, cicada_ch).toArray.to_a
386
+ expt_diff = iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray.to_a
387
+
388
+ correction = (corr_diff.ewise * corr_params[0]).ewise + corr_params[1]
389
+
390
+ Vector.elements(expt_diff.ewise - correction, false)
391
+
392
+ end
393
+
394
+ corrected_differences
395
+
396
+ end
397
+
398
+ ##
399
+ # Caluclates the target registration error (TRE) for an array of image objects
400
+ # to be used for correction.
401
+ #
402
+ # @param [Enumerable<ImageObject>] iobjs the objects whose TRE will be calculated
403
+ #
404
+ # @return [Float] the (3d) TRE
405
+ #
406
+ def determine_tre(iobjs)
407
+
408
+ ref_ch = @parameters[:reference_channel].to_i
409
+ corr_ch = @parameters[:channel_to_correct].to_i
410
+
411
+ results = []
412
+
413
+ max_threads = 1
414
+
415
+ if @parameters[:max_threads]
416
+ max_threads = @parameters[:max_threads].to_i
417
+ end
418
+
419
+ tq = Executors.newFixedThreadPool(max_threads)
420
+
421
+ mut = Mutex.new
422
+
423
+ iobjs.each_with_index do |iobj, i|
424
+
425
+ RImageAnalysisTools::ThreadQueue.new_scope_with_vars(iobj, iobjs, i) do |obj, objs, ii|
426
+
427
+ tq.submit do
428
+
429
+ puts "Calculating TRE. Progress: #{ii} of #{objs.length}" if ii.modulo(10) == 0
430
+
431
+ temp_objs = objs.select { |e| e != obj }
432
+
433
+ c = generate_correction(temp_objs)
434
+
435
+ pos = obj.getPositionForChannel(ref_ch)
436
+
437
+ result = OpenStruct.new
438
+
439
+ begin
440
+
441
+ corr = c.correct_position(pos.getEntry(0), pos.getEntry(1))
442
+
443
+ result.success = true
444
+
445
+ tre_vec = Vector[*obj.getVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray] - corr
446
+
447
+ tre_vec = tre_vec.map2(@pixel_to_distance_conversions) { |e1, e2| e1*e2 }
448
+
449
+ result.tre = tre_vec.norm
450
+
451
+ result.tre_xy = Math.hypot(tre_vec[0], tre_vec[1])
452
+
453
+ rescue UnableToCorrectError => e
454
+
455
+ result.success = false
456
+
457
+ end
458
+
459
+ mut.synchronize do
460
+
461
+ results << result
462
+
463
+ end
464
+
465
+ result
466
+
467
+ end
468
+
469
+ end
470
+
471
+ end
472
+
473
+ tq.shutdown
474
+
475
+ until tq.isTerminated do
476
+
477
+ sleep 0.4
478
+
479
+ end
480
+
481
+ tre_values = results
482
+
483
+ tre_values.select! { |e| e.success }
484
+
485
+ tre_3d = Math.mean(tre_values) { |e| e.tre }
486
+
487
+ tre_2d = Math.mean(tre_values) { |e| e.tre_xy }
488
+
489
+ #TODO logging
490
+
491
+ puts "TRE: #{tre_3d}"
492
+ puts "X-Y TRE: #{tre_2d}"
493
+
494
+ tre_3d
495
+
496
+ end
497
+
498
+ end
499
+
500
+ end
501
+