Class: Cicada::PositionCorrector

Inherits:
Object
  • Object
show all
Defined in:
lib/cicada/correction/position_corrector.rb

Overview

Generates and applies aberration corrections. Used both for standard 3d high-resolution colocalization corrections and in-situ corrections.

Constant Summary collapse

REQUIRED_PARAMETERS =

parameters required by the methods in this class

[:pixelsize_nm, :z_sectionsize_nm, :num_points, :reference_channel, :channel_to_correct]
OPTIONAL_PARAMETERS =

parmeters used but not required in this class or only required for optional functionality

[:determine_correction, :max_threads, :in_situ_aberr_corr_channel, :inverted_z_axis, :disable_in_situ_corr_constant_offset]
NUM_CORR_PARAM =

Number of parameters used for correction (6, as this is the number of parameters for a 2d quadratic fit)

6

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(p) ⇒ PositionCorrector

Constructs a new position corrector with the specified parameters

Parameters:

  • p (ParameterDictionary, Hash)

    a hash-like object containing the analysis parameters



70
71
72
73
74
# File 'lib/cicada/correction/position_corrector.rb', line 70

def initialize(p)
  @parameters = p
  @pixel_to_distance_conversions = Vector[p[:pixelsize_nm].to_f, p[:pixelsize_nm].to_f, p[:z_sectionsize_nm].to_f]
  @logger = Logger.new(STDOUT)
end

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.



63
64
65
# File 'lib/cicada/correction/position_corrector.rb', line 63

def logger
  @logger
end

#parametersObject

Returns the value of attribute parameters.



63
64
65
# File 'lib/cicada/correction/position_corrector.rb', line 63

def parameters
  @parameters
end

#pixel_to_distance_conversionsObject

Returns the value of attribute pixel_to_distance_conversions.



63
64
65
# File 'lib/cicada/correction/position_corrector.rb', line 63

def pixel_to_distance_conversions
  @pixel_to_distance_conversions
end

Class Method Details

.convert_to_realvector(vec) ⇒ RealVector

Creates a RealVector (org.apache.commons.math3.linear.RealVector) that is a copy of the contents of the supplied vector.

Parameters:

  • vec (Vector)

    the Vector to convert

Returns:

  • (RealVector)

    the commons math RealVector containing the same elements



85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/cicada/correction/position_corrector.rb', line 85

def self.convert_to_realvector(vec)

  conv = ArrayRealVector.new(vec.size, 0.0)

  vec.each_with_index do |e, i|

    conv.setEntry(i, e)

  end

  conv

end

Instance Method Details

#apply_correction(c, iobjs) ⇒ Array<Numeric>

Corrects an array of image objects using the provided correction.

Parameters:

  • c (Correction)

    the correction to be used

  • iobjs (Array<ImageObject>)

    the image objects to be corrected.

Returns:

  • (Array<Numeric>)

    the corrected scalar difference between wavelengths for each image object provided.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/cicada/correction/position_corrector.rb', line 213

def apply_correction(c, iobjs)
 
  ref_ch = @parameters[:reference_channel].to_i
  corr_ch = @parameters[:channel_to_correct].to_i

  vec_diffs = iobjs.map { |e| e.getVectorDifferenceBetweenChannels(ref_ch, corr_ch) }

  vec_diffs.map! { |e| apply_scale(Vector[*e.toArray]) }

  corrected_vec_diffs = []

  if @parameters[:correct_images] then

    iobjs.each do |iobj|

      begin

        corrected_vec_diffs << correct_single_object(c, iobj, ref_ch, corr_ch)

        iobj.setCorrectionSuccessful(true)

      rescue UnableToCorrectError => e

        iobj.setCorrectionSuccessful(false)

      end

    end

    corrected_vec_diffs.map! { |e| apply_scale(e) }

  else 

    corrected_vec_diffs = vec_diffs
    
  end
  
  print_distance_components(vec_diffs, corrected_vec_diffs)

  corrected_vec_diffs.map { |e| e.norm  } 

end

#apply_in_situ_correction(iobjs, corr_params) ⇒ Array< Array <Numeric> >

Applies an in situ aberration correction to an array of image objects.

Parameters:

  • iobjs (Enumerable<ImageObject>)

    the objects to be corrected

  • corr_params (Array< Array<Numeric> >)

    the in situ correction parameters (an array for each dimension containing the correction’s slope and intercept).

Returns:

  • (Array< Array <Numeric> >)

    an array of the corrected vector distance between wavelengths for each image object being corrected.



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/cicada/correction/position_corrector.rb', line 375

def apply_in_situ_correction(iobjs, corr_params)

  corr_params = corr_params.transpose

  ref_ch = @parameters[:reference_channel].to_i
  corr_ch = @parameters[:channel_to_correct].to_i
  cicada_ch = @parameters[:in_situ_aberr_corr_channel]

  corrected_differences = iobjs.map do |iobj|
    
    corr_diff = iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, cicada_ch).toArray.to_a
    expt_diff = iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray.to_a

    correction = (corr_diff.ewise * corr_params[0]).ewise + corr_params[1]

    Vector.elements(expt_diff.ewise - correction, false)

  end

  corrected_differences
      
end

#apply_scale(vec) ⇒ Vector

Changes the scale of a vector from image units to physical distances using distance specified in the analysis parameters.

Parameters:

  • vec (Vector)

    the vector to scale

Returns:

  • (Vector)

    the vector scaled to physical units (by parameter naming convention, in nm)



198
199
200
201
202
# File 'lib/cicada/correction/position_corrector.rb', line 198

def apply_scale(vec)

  vec.map2(@pixel_to_distance_conversions) { |e1, e2| e1*e2 }

end

#correct_single_object(c, iobj, ref_ch, corr_ch) ⇒ Vector

Corrects a single image object for the two specified channels.

Parameters:

  • c (Correction)

    the correction to be used

  • iobj (ImageObject)

    the object being corrected

  • ref_ch (Integer)

    the reference channel relative to which the other will be corrected

  • corr_ch (Integer)

    the channel being corrected

Returns:

  • (Vector)

    the corrected (x,y,z) vector difference between the two channels



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/cicada/correction/position_corrector.rb', line 303

def correct_single_object(c, iobj, ref_ch, corr_ch)
  
  corr = c.correct_position(iobj.getPositionForChannel(ref_ch).getEntry(0), iobj.getPositionForChannel(corr_ch).getEntry(1))

  if parameters[:invert_z_axis] then

    corr.setEntry(2, -1.0*corr.getEntry(2))

  end

  iobj.applyCorrectionVectorToChannel(corr_ch, PositionCorrector.convert_to_realvector(corr))
  
  Vector.elements(iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray)

end

#determine_tre(iobjs) ⇒ Float

Caluclates the target registration error (TRE) for an array of image objects to be used for correction.

Parameters:

  • iobjs (Enumerable<ImageObject>)

    the objects whose TRE will be calculated

Returns:

  • (Float)

    the (3d) TRE



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/cicada/correction/position_corrector.rb', line 406

def determine_tre(iobjs)
  
  ref_ch = @parameters[:reference_channel].to_i
  corr_ch = @parameters[:channel_to_correct].to_i

  results = []

  max_threads = 1

  if @parameters[:max_threads]
    max_threads = @parameters[:max_threads].to_i
  end

  tq = Executors.newFixedThreadPool(max_threads)

  mut = Mutex.new

  iobjs.each_with_index do |iobj, i|

    RImageAnalysisTools::ThreadQueue.new_scope_with_vars(iobj, iobjs, i) do |obj, objs, ii|
      
      tq.submit do 
        
        self.logger.debug("Calculating TRE.  Progress: #{ii} of #{objs.length}") if ii.modulo(10) == 0

        temp_objs = objs.select { |e| e != obj }

        c = generate_correction(temp_objs)

        pos = obj.getPositionForChannel(ref_ch)
        
        result = OpenStruct.new

        begin
        
          corr = c.correct_position(pos.getEntry(0), pos.getEntry(1))

          result.success = true

          tre_vec = Vector[*obj.getVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray] - corr

          tre_vec = tre_vec.map2(@pixel_to_distance_conversions) { |e1, e2| e1*e2 }

          result.tre = tre_vec.norm

          result.tre_xy = Math.hypot(tre_vec[0], tre_vec[1])

        rescue UnableToCorrectError => e

          result.success = false

        end

        mut.synchronize do

          results << result

        end

        result
        
      end

    end

  end

  tq.shutdown

  until tq.isTerminated do

    sleep 0.4

  end

  tre_values = results

  tre_values.select! { |e| e.success }

  tre_3d = Math.mean(tre_values) { |e| e.tre }
  
  tre_2d = Math.mean(tre_values) { |e| e.tre_xy }

  self.logger.info("TRE: #{tre_3d}")
  self.logger.info("X-Y TRE: #{tre_2d}")

  tre_3d

end

#generate_correction(iobjs) ⇒ Correction

Generates a correction from a specified array of image objects.

Parameters:

  • iobjs (Array<ImageObject>)

    the image objects to be used for the correction

Returns:

  • (Correction)

    the correction generated from the input objects



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/cicada/correction/position_corrector.rb', line 107

def generate_correction(iobjs)
  
  #TODO refactor into smaller chunks

  ref_ch = parameters[:reference_channel].to_i
  corr_ch = parameters[:channel_to_correct].to_i

  unless parameters[:determine_correction] then

    return Correction.read_from_file(FileInteraction.correction_filename(parameters))

  end

  correction_x = []
  correction_y = []
  correction_z = []

  distance_cutoffs = MVector.zero(iobjs.size)

  iobjs.each_with_index do |obj, ind|

    obj_pos = obj.getPositionForChannel(ref_ch)
    
    distances_to_objects = iobjs.map { |obj2| obj2.getPositionForChannel(ref_ch).subtract(obj_pos).getNorm }
           
    pq = PQueue.new

    np = @parameters[:num_points].to_i

    distances_to_objects.each do |d|

      if pq.size < np + 1 then

        pq.push d

      elsif d < pq.top then

        pq.pop
        pq.push d

      end

    end


    first_exclude = pq.pop

    last_dist = pq.pop

    distance_cutoff = (last_dist + first_exclude)/2.0

    distance_cutoffs[ind] = distance_cutoff

    objs_ind_to_fit = (0...iobjs.size).select { |i| distances_to_objects[i] < distance_cutoff }
            
    objs_to_fit = iobjs.values_at(*objs_ind_to_fit)

    diffs_to_fit = MMatrix[*objs_to_fit.map { |e| e.getVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray }]
    x_to_fit = objs_to_fit.map { |e| e.getPositionForChannel(ref_ch).getEntry(0) }
    y_to_fit = objs_to_fit.map { |e| e.getPositionForChannel(ref_ch).getEntry(1) }
    
    x = Vector[*x_to_fit.map { |e| e - obj_pos.getEntry(0) }]
    y = Vector[*y_to_fit.map { |e| e - obj_pos.getEntry(1) }]

    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 }])

    cpt = correction_parameters.transpose

    cpt_cp = cpt * correction_parameters

    cpt_cp_lup = cpt_cp.lup

    correction_x << cpt_cp_lup.solve(cpt * diffs_to_fit.column(0))
    correction_y << cpt_cp_lup.solve(cpt * diffs_to_fit.column(1))
    correction_z << cpt_cp_lup.solve(cpt * diffs_to_fit.column(2))
 

  end

  Correction.new(correction_x, correction_y, correction_z, distance_cutoffs, iobjs, ref_ch, corr_ch)

end

#generate_in_situ_correctionObject

Generates an in situ aberration correction (using the data specified in a parameter file)

Returns:

  • @see #generate_in_situ_correction_from_iobjs



324
325
326
327
328
329
330
# File 'lib/cicada/correction/position_corrector.rb', line 324

def generate_in_situ_correction
  
  iobjs_for_in_situ_corr = FileInteraction.read_in_situ_corr_data(@parameters)

  generate_in_situ_correction_from_iobjs(iobjs_for_in_situ_corr)

end

#generate_in_situ_correction_from_iobjs(iobjs_for_in_situ_corr) ⇒ Array< Array<Numeric> >

Generates an in situ aberration correction from the supplied image objects.

Parameters:

  • an (Array<ImageObject>)

    array containing the image objects from which the in situ correction will be generated

Returns:

  • (Array< Array<Numeric> >)

    an array containing the x, y, and z corrections; each correction is a 2-element array containing the slope and intercept for the fit in each dimension. The intercept will be zero if disabled in the parameter file.



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/cicada/correction/position_corrector.rb', line 342

def generate_in_situ_correction_from_iobjs(iobjs_for_in_situ_corr)

  ref_ch = @parameters[:reference_channel].to_i
  corr_ch = @parameters[:channel_to_correct].to_i
  cicada_ch = @parameters[:in_situ_aberr_corr_channel]

  corr_diffs = Matrix.rows(iobjs_for_in_situ_corr.map { |iobj| iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, cicada_ch).toArray })
  expt_diffs = Matrix.rows(iobjs_for_in_situ_corr.map { |iobj| iobj.getCorrectedVectorDifferenceBetweenChannels(ref_ch, corr_ch).toArray })

  bslf = BisquareLinearFit.new

  bslf.disableIntercept if @parameters[:disable_in_situ_corr_constant_offset]

  all_parameters = 0.upto(corr_diffs.column_size - 1).collect do |i|

    bslf.fit_rb(corr_diffs.column(i), expt_diffs.column(i)).toArray

  end
 
  all_parameters

end

This method returns an undefined value.

Prints the mean scalar and vector differences both corrected and uncorrected.

Parameters:

  • vec_diffs (Array<Vector>)

    an array of the uncorrected vector differences

  • corrected_vec_diffs (Array<Vector>)

    an array of the corrected vector differences



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/cicada/correction/position_corrector.rb', line 264

def print_distance_components(vec_diffs, corrected_vec_diffs)

  mean_uncorr_vec = [0.0, 0.0, 0.0] 

  vec_diffs.each do |e|

    mean_uncorr_vec = mean_uncorr_vec.ewise + e.to_a

  end

  mean_corr_vec = [0.0, 0.0, 0.0]

  corrected_vec_diffs.each do |e|

    mean_corr_vec = mean_corr_vec.ewise + e.to_a

  end

  mean_uncorr_vec.map! { |e| e / vec_diffs.length }

  mean_corr_vec.map! { |e| e / corrected_vec_diffs.length }

  self.logger.info("mean components uncorrected: [#{mean_uncorr_vec.join(', ')}]")
  self.logger.info("mean distance uncorrected: #{Vector[*mean_uncorr_vec].norm}")
  self.logger.info("mean components corrected: [#{mean_corr_vec.join(', ')}]")
  self.logger.info("mean distance corrected: #{Vector[*mean_corr_vec].norm}")

end