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