Callbacks

RegularizedLeastSquares provides a callback mechanism that allows you to access and monitor the state of the optimization process. These callbacks are invoked at the start of each iteration and have the form f(solver, iteration). Via the solver, you can access any internal property of the solver state.

MPIReco exposes this interface for compatible algorithms. You can use do-syntax to pass a callback of the form f(solver, frame, iteration) to the reconstruction:

c = reconstruct("SinglePatch", b; parameters...) do solver, frame, iteration
  tmp = sum(solversolution(solver))
  @info "Sum of concentration at frame $frame and iteration $iteration is $tmp"
end
Float32 ImageMeta with:
  data: 5-dimensional AxisArray{Float32,5,...} with axes:
    :color, 1:1
    :x, (-27.375:1.25:11.375) mm
    :y, (-11.375:1.25:27.375) mm
    :z, (0.0:1.0:0.0) mm
    :time, (0.0:6.5280000000000005:0.0) s
And data, a 1×32×32×1×1 Array{Float32, 5}
  properties:
    tracerName: ["Resovist"]
    version: 2.0.1
    experimentDescription: Ph5DotsDF10Overlap0LL (E99)
    scannerOperator: nmrsu
    rxNumSamplingPoints: 1632
    acqOffsetField: [-0.01; 0.01; -0.0;;;]
    dfStrength: [0.01 0.01 0.0;;;]
    scannerTopology: FFP
    rxDataConversionFactor: [0.0001 0.0001 0.0001; 0.0 0.0 0.0]
    experimentNumber: 99
    dfDivider: [102; 96; 99;;]
    tracerBatch: ["0"]
    acqStartTime: 2014-11-27T14:40:01.280
    tracerInjectionTime: [DateTime("2014-11-27T14:40:01.280")]
    dfWaveform: ["sine";;]
    dim: 2
    uuid: 8eadd786-d630-4f53-ae91-c8f90721bbdc
    dfBaseFrequency: 2.5e6
    dfCycle: 0.0006528
    experimentName: Ph5DotsDF10Overlap0LL (E99)
    studyUuid: 12246d79-646a-41b8-a278-5274fc9ae7c5
    tracerVolume: [0.0]
    studyDescription: n.a.
    experimentSubject: FocusField
    experimentUuid: 787b3b7b-734a-4872-8bad-a38a64478cca
    acqNumFrames: 1
    tracerConcentration: [0.5]
    dfNumChannels: 3
    dfPhase: [1.5708 1.5708 1.5708;;;]
    scannerManufacturer: Bruker/Philips
    studyTime: 2014-11-13T16:20:01.217
    scannerFacility: Universitätsklinikum Hamburg Eppendorf
    studyName: FocusField
    scannerName: Preclinical MPI System
    tracerSolute: ["Fe"]
    size: [32, 32, 1]
    time: 2021-12-26T19:52:45.666
    experimentIsSimulation: false
    datatype: MPI
    acqNumPeriodsPerFrame: 1
    experimentIsCalibration: false
    studyNumber: 1
    rxBandwidth: 1.25e6
    tracerVendor: ["n.a."]
    rxUnit: a.u.
    acqNumAverages: 10000
    rxNumChannels: 3
    acqGradient: [-1.25 0.0 0.0; 0.0 -1.25 0.0; 0.0 0.0 2.5;;;;]

MPIReco also provides built-in callbacks that can store the solution in each iteration or compare the current solution with a reference:

cb = StoreSolutionPerFrameCallback()
reconstruct(cb, "SinglePatch", b; parameters...);
cb.solutions[1]
4-element Vector{Vector{Float32}}:
 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 [0.0, 0.00020932728, 0.0, 2.7252778f-5, 0.00021386408, 0.0, 0.0, 0.0, 0.0, 0.00050126185  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

You can also combine multiple callbacks:

cb1 = StoreSolutionPerFrameCallback()
ref = reshape(c[1, :, :, :, 1].data.data, :, 1)
cb2 = CompareSolutionPerFrameCallback(ref)
reconstruct("SinglePatch", b; parameters...) do solver, frame, iteration
  cb1(solver, frame, iteration)
  cb2(solver, frame, iteration)
end
cb2.results[1]
4-element Vector{Float64}:
 0.09727259689947518
 0.019788756997517483
 0.0063009444256531865
 2.3881879308376115e-8

Note that callbacks are used directly in the solver and thus reflect its value domain. This means that in sparse reconstruction, the current solution approximation is not in the image domain. Likewise, the solution might still be a complex number.

Both variants shown above get passed as a callbacks keyword argument to the algorithm. When using the callback directly like this, the interface changes from f(solver, frame, iteration) to f(solver, iteration). And you have to manually differentiate between frames by watching for the start of a new reconstruction:

frame = 0
function my_callback(solver, iteration)
  if iteration == 0
    global frame += 1
  end
  @info "Frame $frame, Iteration $iteration"
end
reconstruct("SinglePatch", b; parameters..., callbacks = my_callback);
[ Info: Loading SM
[ Info: Preparing SF
[ Info: Adapting SF
[ Info: Frame 1, Iteration 0
[ Info: Frame 1, Iteration 1
[ Info: Frame 1, Iteration 2
[ Info: Frame 1, Iteration 3

This page was generated using Literate.jl.