2D Experiments
An abstract type representing a type of experimental NMR analysis. The type system is split between fixed-peak experiments (where peak positions are constant between spectra) and peak-tracking experiments (where peak positions can vary).
Required Fields
All concrete subtypes must include the following fields:
specdata: ASpecDataobject containing the observed and simulated data plus maskpeaks: AnObservablelist of peaks in the experimentclusters: AnObservablelist of clusters of peakstouched: AnObservablelist of touched clustersisfitting: AnObservableboolean indicating if real-time fitting is activexradius: AnObservablenumber defining peak detection radius in x dimensionyradius: AnObservablenumber defining peak detection radius in y dimensionstate: AnObservabledictionary of GUI state variablesskipplanes: AVector{Int}of planes left out of the fitting (seeskipset)
Required Implementation
Concrete subtypes must implement both the core analysis functions below and the visualisation functions documented separately. Note that many other functions have default implementations that can be used unless special behaviour is needed.
Type Hierarchy
The Experiment type has two immediate subtypes that handle position behaviour:
FixedPeakExperiment: Implementation wherehasfixedpositions(expt) = trueMovingPeakExperiment: Implementation wherehasfixedpositions(expt) = false
Concrete experiment types should inherit from one of these intermediate types rather than directly from Experiment.
addpeak!(expt, position, [label], [xradius], [yradius])
Add a new peak to the experiment at the specified position.
Arguments:
expt: The experimentposition: APoint2fspecifying the (x,y) coordinates of the peaklabel: Optional string label for the peak (defaults to auto-generated)xradius: Optional peak radius in x dimension (defaults to experiment default)yradius: Optional peak radius in y dimension (defaults to experiment default)
Every peak needs the parameters :x, :y, :R2x, :R2y and :amp, which the shared lineshape fit in fitting.jl works on. A fixed-peak experiment holds one position and pair of linewidths for the whole series and one amplitude per plane; a moving-peak experiment holds all five per plane.
Lineshape fitting
The fitting is shared by every experiment and needs nothing from a new one. Each peak is the product of two lineshapes, scaled so that its amplitude is the peak height, and the model is linear in the amplitudes. They are solved for exactly by least squares at every step of the nonlinear fit (variable projection), so for a fixed-peak experiment only four parameters per peak are optimised, however many planes there are; a moving-peak experiment is fitted plane by plane. Planes in skipplanes do not constrain the shapes of a fixed-peak experiment, but their amplitudes are still measured.
fit!(expt) fits the touched clusters in a background task, in parallel when Julia has threads. Nothing is written into a peak until its cluster's fit is complete, so a fit that is superseded or cancelled changes nothing. Each peak records how its last fit ended, in fitstatus(peak): :converged, :maxiter, :bound or :timeout.
Default Implementations
The abstract type provides several default implementations that can be used as-is or overridden when needed:
maskplane!(m, peak, expt, i)
Default implementation marks an elliptical fitting region for the peak in plane i using maskellipse!. Only needs to be overridden if a different region shape is required.
Arguments:
m: The plane's mask, aBitMatrixpeak: The peak to maskexpt: The experimenti: The plane
postfit!(peak, expt)
Perform additional fitting operations after spectrum fitting.
Arguments:
peak: The peak to post-fitexpt: The experiment
slicelabel(expt, idx)
Generate a label for a specific slice/plane of the experiment.
Arguments:
expt: The experimentidx: The slice index
Returns:
- A string label for the slice
peakinfotext(expt, idx)
Generate information text about a specific peak.
Arguments:
expt: The experimentidx: The peak index
Returns:
- A string containing formatted peak information
experimentinfo(expt)
Generate information text about the experiment.
Returns:
- A string containing formatted experiment information
completestate!(state, expt)
Set up observables for the GUI state.
Arguments:
state: The GUI state dictionaryexpt: The experiment
Functions Handled by Abstract Type
The following functions are implemented generically and do not need to be reimplemented by concrete subtypes:
Peak Management
nslices(expt): Get the number of slices in the experimentnpeaks(expt): Get the number of peaks in the experimentmovepeak!(expt, idx, newpos): Move a peak to a new positiondeletepeak!(expt, idx): Delete a specific peakdeleteallpeaks!(expt): Delete all peaks
Data Processing
mask!(expt): Calculate peak masks and update internal specdatasimulate!(z, peaks, expt): Simulate the experiment and update internal specdatafit!(expt): Fit the touched clusters of peaks in the experimentfitcluster!(peaks, expt, check): Fit one cluster of overlapping peakscontinuefit!(expt): Fit the peaks awaiting a fit, or else continue the fits stopped at the time or iteration limitnotice!(expt, text): Showtextin the window's status line until the peaks are next changed or fittedsimulate!(z, peak, expt): Add a peak's fitted lineshape to every plane ofzbatchupdate(f, expt): Make several changes to the peaks, then mask, cluster and fit oncechecktouched!(expt): Check which clusters have been modified
Observable Setup
setupexptobservables!(expt): Set up reactive behaviours for experiment observables
Required Visualisation Functions
Concrete subtypes must implement the following visualisation functions:
makepeakplot!(gui, state, expt)
Create the interactive peak plot in the GUI context. This function is crucial for real-time visualisation and interaction.
Arguments:
gui: The GUI context containing plot panelsstate: The GUI state dictionaryexpt: The experiment
save_peak_plots!(expt, folder)
Save publication-quality plots for all peaks to a specified folder.
Arguments:
expt: The experimentfolder: String path to the output folder
Utility Functions
get_model_data(peak, expt): The observed and fitted series for a single peakplot_peak!(panel, peak, expt): Plot a single peak's data
Implemented Experiment Types
The codebase includes implementations for several specific experiment types:
RelaxationExperiment: For relaxation measurementsHetNOEExperiment: For heteronuclear NOE measurements
Each implementation specialises the simulation and fitting behaviour for its specific experiment type while inheriting the common functionality from the abstract type.
Guide: Creating a New Experiment Type
Here's a step-by-step guide to implementing a new type of NMR experiment:
Choose Base Type
- Inherit from
FixedPeakExperimentif peak positions are constant between spectra - Inherit from
MovingPeakExperimentif peak positions can vary
- Inherit from
Define Structure
struct MyNewExperiment <: FixedPeakExperiment # Required fields specdata peaks clusters touched isfitting xradius yradius state skipplanes::Vector{Int} # Experiment-specific fields my_special_parameter endConstructor
- Create a constructor that initialises all required fields
- Set up observables using
setupexptobservables! - Initialise experiment-specific parameters
Core Analysis Functions
- Implement
addpeak!to set up experiment-specific peak parametersfunction addpeak!(expt::MyNewExperiment, initialposition::Point2f, label="") # Create basic peak newpeak = Peak(initialposition, label) # Add experiment-specific parameters newpeak.parameters[:my_param] = Parameter("My Parameter", initial_value) # Add post-fit parameters that will be saved in results newpeak.postparameters[:final_result] = Parameter("Final Result", 0.0) push!(expt.peaks[], newpeak) notify(expt.peaks) end - Implement
postfit!to calculate final parameters from fit results, leaving out the planes inskipset(expt)
- Implement
Information Functions
- Implement
slicelabelfor spectrum navigation - Implement
peakinfotextto show fit results - Implement
experimentinfoto show experiment details
- Implement
Visualisation Functions
- Implement
makepeakplot!for the interactive GUIfunction makepeakplot!(gui, state, expt::MyNewExperiment) # Create appropriate plot(s) for your data gui[:axpeakplot] = ax = Axis(gui[:panelpeakplot][1,1], xlabel="My X Label", ylabel="My Y Label") # Add plot elements plot!(ax, ...) end - Implement
save_peak_plots!for publication figuresfunction save_peak_plots!(expt::MyNewExperiment, folder::AbstractString) CairoMakie.activate!() for peak in expt.peaks[] fig = Figure() # Create publication-quality figure save(joinpath(folder, "peak_$(peak.label[]).pdf"), fig) end GLMakie.activate!() end
- Implement
Optional Overrides
- Override
maskplane!only if you need non-elliptical fitting regions - Override clustering functions only if you need special peak grouping
- Override other default implementations only if needed
- Override
Remember:
- Post-fit parameters (
postparameters) are what get saved in results files - Peak parameters (
parameters) are used during fitting - Use the existing implementations (IntensityExperiment, HetNOEExperiment) as templates
- Resolve each parameter the entry point needs as the 1D analyses do: an explicit keyword, then an annotation, then an acquisition parameter, then
ask(seesrc/prompts.jl) - The entry point returns
gui!(expt), which blocks until the window closes and returns the experiment - Most functionality can be inherited from the abstract type