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



68
69
70
71
72
# File 'lib/cicada/correction/position_corrector.rb', line 68

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.



61
62
63
# File 'lib/cicada/correction/position_corrector.rb', line 61

def logger
  @logger
end

#parametersObject

Returns the value of attribute parameters.



61
62
63
# File 'lib/cicada/correction/position_corrector.rb', line 61

def parameters
  @parameters
end

#pixel_to_distance_conversionsObject

Returns the value of attribute pixel_to_distance_conversions.



61
62
63
# File 'lib/cicada/correction/position_corrector.rb', line 61

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



82
83
84
85
86
87
88
# File 'lib/cicada/correction/position_corrector.rb', line 82

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.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/cicada/correction/position_corrector.rb', line 173

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 c.nil? then
    corrected_vec_diffs = vec_diffs
  else
    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) }    
  end
  
  print_distance_components(vec_diffs, corrected_vec_diffs)
  corrected_vec_diffs.map { |e| e.norm  } 
end

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

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

Parameters:

  • iobjs (Enumerable<ImageObject>)

    the objects to be corrected

  • isc (InSituCorrection)

    the in situ correction object.

Returns:

  • (Array< Array <Numeric> >)

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



282
283
284
# File 'lib/cicada/correction/position_corrector.rb', line 282

def apply_in_situ_correction(iobjs, isc)
  isc.apply(iobjs)
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)



160
161
162
# File 'lib/cicada/correction/position_corrector.rb', line 160

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



236
237
238
239
240
241
242
243
244
# File 'lib/cicada/correction/position_corrector.rb', line 236

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



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/cicada/correction/position_corrector.rb', line 294

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



97
98
99
100
101
102
103
104
105
106
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
# File 'lib/cicada/correction/position_corrector.rb', line 97

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



251
252
253
254
# File 'lib/cicada/correction/position_corrector.rb', line 251

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) ⇒ InSituCorrection

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:

  • (InSituCorrection)

    an InSituCorrection object containing the necessary information to perform the correction.



265
266
267
268
269
270
271
# File 'lib/cicada/correction/position_corrector.rb', line 265

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]

  InSituCorrection.new(ref_ch, cicada_ch, corr_ch, iobjs_for_in_situ_corr, @parameters[:disable_in_situ_corr_constant_offset])
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



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/cicada/correction/position_corrector.rb', line 206

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