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