Interface ready for preprocessing steps handling. style altered to support modular preprocessing pipeline, finished precalculations and preprocessing code, and its respective testing script, added preprocessingpipeline, a handler for the low level functions of preprocessing to work with the high level MSIData in the genie environment.
This commit is contained in:
parent
bdd9315796
commit
11c181b3b3
665
app.jl
665
app.jl
@ -2,7 +2,7 @@
|
||||
|
||||
module App
|
||||
# ==Packages ==
|
||||
using GenieFramework # Set up Genie development environment.
|
||||
using GenieFramework
|
||||
using Pkg
|
||||
using Libz
|
||||
using PlotlyBase
|
||||
@ -22,7 +22,7 @@ using Dates
|
||||
using Base.Threads
|
||||
|
||||
# Bring MSIData into App module's scope
|
||||
using .MSI_src: MSIData, OpenMSIData, process_spectrum, IterateSpectra, ImzMLSource, _iterate_spectra_fast, MzMLSource, find_mass, ViridisPalette, get_mz_slice, get_multiple_mz_slices, quantize_intensity, save_bitmap, median_filter, save_bitmap, downsample_spectrum, TrIQ, precompute_analytics, ImportMzmlFile, generate_colorbar_image, load_and_prepare_mask, set_global_mz_range!
|
||||
using .MSI_src: MSIData, OpenMSIData, process_spectrum, IterateSpectra, ImzMLSource, _iterate_spectra_fast, MzMLSource, find_mass, ViridisPalette, get_mz_slice, get_multiple_mz_slices, quantize_intensity, save_bitmap, median_filter, save_bitmap, downsample_spectrum, TrIQ, precompute_analytics, ImportMzmlFile, generate_colorbar_image, load_and_prepare_mask, set_global_mz_range!, main_precalculation, MutableSpectrum
|
||||
|
||||
if !@isdefined(increment_image)
|
||||
include("./julia_imzML_visual.jl")
|
||||
@ -72,6 +72,10 @@ end
|
||||
# == Reactive code ==
|
||||
# Reactive code to make the UI interactive
|
||||
@app begin
|
||||
# == Loading Screen Variables ==
|
||||
@in is_initializing = true
|
||||
@in initialization_message = "Initializing..."
|
||||
|
||||
# == Reactive variables ==
|
||||
# reactive variables exist in both the Julia backend and the browser with two-way synchronization
|
||||
# @out variables can only be modified by the backend
|
||||
@ -207,6 +211,139 @@ end
|
||||
# == Pre Processing Variables ==
|
||||
@in pre_tab = "stabilization"
|
||||
|
||||
## Preprocessing Parameters
|
||||
@in progressPrep=false
|
||||
@in stabilization_method="sqrt"
|
||||
@in smoothing_method="sg"
|
||||
@in smoothing_window = ""
|
||||
@in smoothing_order = ""
|
||||
@in baseline_method="snip"
|
||||
@in baseline_iterations = ""
|
||||
@in baseline_window = ""
|
||||
@in normalization_method="tic"
|
||||
@in alignment_method="lowess"
|
||||
@in alignment_span = ""
|
||||
@in alignment_tolerance = ""
|
||||
@in alignment_tolerance_unit="mz"
|
||||
@in alignment_max_shift_ppm = ""
|
||||
@in alignment_min_matched_peaks = ""
|
||||
@in peak_picking_method="profile"
|
||||
@in peak_picking_snr_threshold = ""
|
||||
@in peak_picking_half_window = ""
|
||||
@in peak_picking_min_peak_prominence = ""
|
||||
@in peak_picking_merge_peaks_tolerance = ""
|
||||
@in peak_picking_min_peak_width_ppm = ""
|
||||
@in peak_picking_max_peak_width_ppm = ""
|
||||
@in peak_picking_min_peak_shape_r2 = ""
|
||||
@in binning_method="adaptive"
|
||||
@in binning_tolerance = ""
|
||||
@in binning_tolerance_unit="ppm"
|
||||
@in binning_frequency_threshold = ""
|
||||
@in binning_min_peak_per_bin = ""
|
||||
@in binning_max_bin_width_ppm = ""
|
||||
@in binning_intensity_weighted_centers=true
|
||||
@in binning_num_uniform_bins = ""
|
||||
@in calibration_fit_order = ""
|
||||
@in calibration_ppm_tolerance = ""
|
||||
@in peak_selection_min_snr = ""
|
||||
@in peak_selection_min_fwhm_ppm = ""
|
||||
@in peak_selection_max_fwhm_ppm = ""
|
||||
@in peak_selection_min_shape_r2 = ""
|
||||
@in peak_selection_frequency_threshold = ""
|
||||
@in peak_selection_correlation_threshold = ""
|
||||
|
||||
# == Pipeline Step Order and Mask Route Variables ==
|
||||
@in pipeline_step_order = [
|
||||
Dict("name" => "stabilization", "label" => "Stabilization", "enabled" => true),
|
||||
Dict("name" => "smoothing", "label" => "Smoothing", "enabled" => true),
|
||||
Dict("name" => "baseline_correction", "label" => "Baseline Correction", "enabled" => true),
|
||||
Dict("name" => "peak_picking", "label" => "Peak Picking", "enabled" => true),
|
||||
Dict("name" => "peak_selection", "label" => "Peak Selection", "enabled" => true),
|
||||
Dict("name" => "calibration", "label" => "Calibration", "enabled" => true),
|
||||
Dict("name" => "peak_alignment", "label" => "Peak Alignment", "enabled" => true),
|
||||
Dict("name" => "normalization", "label" => "Normalization", "enabled" => true),
|
||||
Dict("name" => "peak_binning", "label" => "Peak Binning", "enabled" => true)
|
||||
]
|
||||
@in preprocessing_mask_route = ""
|
||||
@in selected_spectrum_id_for_plot = 1
|
||||
@in feature_matrix_result = nothing
|
||||
@in bin_info_result = nothing
|
||||
|
||||
@in reference_peaks_list = [
|
||||
Dict("mz" => 137.0244, "label" => "DHB_fragment"),
|
||||
Dict("mz" => 155.0349, "label" => "DHB_M+H"),
|
||||
]
|
||||
|
||||
# --- Methods for Reference Peaks List ---
|
||||
function addReferencePeak()
|
||||
push!(reference_peaks_list, Dict("mz" => 0.0, "label" => ""))
|
||||
reference_peaks_list = deepcopy(reference_peaks_list) # Force reactivity
|
||||
end
|
||||
|
||||
function removeReferencePeak(index::Int)
|
||||
deleteat!(reference_peaks_list, index)
|
||||
reference_peaks_list = deepcopy(reference_peaks_list) # Force reactivity
|
||||
end
|
||||
|
||||
# Step reordering functions
|
||||
function moveStepUp(index::Int)
|
||||
if index > 1
|
||||
pipeline_step_order = deepcopy(pipeline_step_order)
|
||||
temp = pipeline_step_order[index]
|
||||
pipeline_step_order[index] = pipeline_step_order[index-1]
|
||||
pipeline_step_order[index-1] = temp
|
||||
pipeline_step_order = pipeline_step_order # Force reactivity
|
||||
end
|
||||
end
|
||||
|
||||
function moveStepDown(index::Int)
|
||||
if index < length(pipeline_step_order)
|
||||
pipeline_step_order = deepcopy(pipeline_step_order)
|
||||
temp = pipeline_step_order[index]
|
||||
pipeline_step_order[index] = pipeline_step_order[index+1]
|
||||
pipeline_step_order[index+1] = temp
|
||||
pipeline_step_order = pipeline_step_order # Force reactivity
|
||||
end
|
||||
end
|
||||
@in save_feature_matrix_btn = false
|
||||
|
||||
# Trigger for running the full pipeline
|
||||
@in run_full_pipeline = false
|
||||
@out current_pipeline_step = "" # To indicate which step is currently running in the full pipeline
|
||||
|
||||
@in export_params_btn = false
|
||||
@in import_params_btn = false
|
||||
@in imported_params_file = nothing
|
||||
|
||||
@in suggested_smoothing_window = ""
|
||||
@in suggested_smoothing_order = ""
|
||||
@in suggested_baseline_iterations = ""
|
||||
@in suggested_baseline_window = ""
|
||||
@in suggested_alignment_span = ""
|
||||
@in suggested_alignment_tolerance = ""
|
||||
@in suggested_alignment_max_shift_ppm = ""
|
||||
@in suggested_alignment_min_matched_peaks = ""
|
||||
@in suggested_peak_picking_snr_threshold = ""
|
||||
@in suggested_peak_picking_half_window = ""
|
||||
@in suggested_peak_picking_min_peak_prominence = ""
|
||||
@in suggested_peak_picking_merge_peaks_tolerance = ""
|
||||
@in suggested_peak_picking_min_peak_width_ppm = ""
|
||||
@in suggested_peak_picking_max_peak_width_ppm = ""
|
||||
@in suggested_peak_picking_min_peak_shape_r2 = ""
|
||||
@in suggested_binning_tolerance = ""
|
||||
@in suggested_binning_frequency_threshold = ""
|
||||
@in suggested_binning_min_peak_per_bin = ""
|
||||
@in suggested_binning_max_bin_width_ppm = ""
|
||||
@in suggested_binning_num_uniform_bins = ""
|
||||
@in suggested_calibration_fit_order = ""
|
||||
@in suggested_calibration_ppm_tolerance = ""
|
||||
@in suggested_peak_selection_min_snr = ""
|
||||
@in suggested_peak_selection_min_fwhm_ppm = ""
|
||||
@in suggested_peak_selection_max_fwhm_ppm = ""
|
||||
@in suggested_peak_selection_min_shape_r2 = ""
|
||||
@in suggested_peak_selection_frequency_threshold = ""
|
||||
@in suggested_peak_selection_correlation_threshold = ""
|
||||
|
||||
# == Batch Summary Dialog ==
|
||||
@in showBatchSummary = false
|
||||
@out batch_summary = ""
|
||||
@ -323,11 +460,19 @@ end
|
||||
# Create conection to frontend
|
||||
@out plotdata=[traceSpectra]
|
||||
@out plotlayout=layoutSpectra
|
||||
|
||||
@in idSpectrum=0
|
||||
@in xCoord=0
|
||||
@in yCoord=0
|
||||
@out xSpectraMz = Vector{Float64}()
|
||||
@out ySpectraMz = Vector{Float64}()
|
||||
|
||||
# UI plot data for Preprocessing
|
||||
@out plotdata_before = [traceSpectra]
|
||||
@out plotlayout_before = layoutSpectra
|
||||
@out plotdata_after = [traceSpectra]
|
||||
@out plotlayout_after = layoutSpectra
|
||||
|
||||
# Interactive plot reactions
|
||||
@in data_click=Dict{String,Any}()
|
||||
#@in data_selected=Dict{String,Any}() # Selected is for areas, this can work for the masks
|
||||
@ -413,7 +558,7 @@ end
|
||||
msg = "Opening file: $(basename(picked_route))..."
|
||||
|
||||
try
|
||||
dataset_name = replace(basename(picked_route), r"(\.(imzML|imzml|mzML|mzml))$"i => "")
|
||||
dataset_name = replace(basename(picked_route), r"(\.(imzML|imzml|mzML|mzml))$ "i => "")
|
||||
registry = load_registry(registry_path)
|
||||
existing_entry = get(registry, dataset_name, nothing)
|
||||
|
||||
@ -469,6 +614,188 @@ end
|
||||
|
||||
precompute_analytics(loaded_data)
|
||||
|
||||
# Auto-suggest parameters
|
||||
try
|
||||
println("Calling main_precalculation to get recommended parameters...")
|
||||
recommended_params = main_precalculation(loaded_data)
|
||||
|
||||
for (step_name, params) in recommended_params
|
||||
for (param_key, value) in params
|
||||
# Convert value to appropriate type before assignment
|
||||
processed_value = if value === nothing
|
||||
nothing
|
||||
elseif value isa Tuple
|
||||
@warn "Skipping invalid parameter suggestion (tuple): $value for $param_key"
|
||||
"" # Set to empty string for safety
|
||||
elseif value isa Number
|
||||
value
|
||||
else
|
||||
string(value)
|
||||
end
|
||||
|
||||
if processed_value !== nothing
|
||||
if step_name == :Smoothing
|
||||
if param_key == :window
|
||||
suggested_smoothing_window = string(processed_value)
|
||||
smoothing_window = string(processed_value)
|
||||
println(" suggested_smoothing_window set to $(suggested_smoothing_window)")
|
||||
elseif param_key == :order
|
||||
suggested_smoothing_order = string(processed_value)
|
||||
smoothing_order = string(processed_value)
|
||||
println(" suggested_smoothing_order set to $(suggested_smoothing_order)")
|
||||
end
|
||||
elseif step_name == :BaselineCorrection
|
||||
if param_key == :iterations
|
||||
suggested_baseline_iterations = string(processed_value)
|
||||
baseline_iterations = string(processed_value)
|
||||
println(" suggested_baseline_iterations set to $(suggested_baseline_iterations)")
|
||||
elseif param_key == :window
|
||||
suggested_baseline_window = string(processed_value)
|
||||
baseline_window = string(processed_value)
|
||||
println(" suggested_baseline_window set to $(suggested_baseline_window)")
|
||||
end
|
||||
elseif step_name == :PeakAlignment
|
||||
if param_key == :span
|
||||
suggested_alignment_span = string(processed_value)
|
||||
alignment_span = string(processed_value)
|
||||
println(" suggested_alignment_span set to $(suggested_alignment_span)")
|
||||
elseif param_key == :tolerance
|
||||
suggested_alignment_tolerance = string(processed_value)
|
||||
alignment_tolerance = string(processed_value)
|
||||
println(" suggested_alignment_tolerance set to $(suggested_alignment_tolerance)")
|
||||
elseif param_key == :max_shift_ppm
|
||||
suggested_alignment_max_shift_ppm = string(processed_value)
|
||||
alignment_max_shift_ppm = string(processed_value)
|
||||
println(" suggested_alignment_max_shift_ppm set to $(suggested_alignment_max_shift_ppm)")
|
||||
elseif param_key == :min_matched_peaks
|
||||
suggested_alignment_min_matched_peaks = string(processed_value)
|
||||
alignment_min_matched_peaks = string(processed_value)
|
||||
println(" suggested_alignment_min_matched_peaks set to $(suggested_alignment_min_matched_peaks)")
|
||||
end
|
||||
elseif step_name == :Calibration
|
||||
if param_key == :fit_order
|
||||
suggested_calibration_fit_order = string(processed_value)
|
||||
calibration_fit_order = string(processed_value)
|
||||
println(" suggested_calibration_fit_order set to $(suggested_calibration_fit_order)")
|
||||
elseif param_key == :ppm_tolerance
|
||||
suggested_calibration_ppm_tolerance = string(processed_value)
|
||||
calibration_ppm_tolerance = string(processed_value)
|
||||
println(" suggested_calibration_ppm_tolerance set to $(suggested_calibration_ppm_tolerance)")
|
||||
end
|
||||
elseif step_name == :PeakPicking
|
||||
if param_key == :snr_threshold
|
||||
suggested_peak_picking_snr_threshold = string(processed_value)
|
||||
peak_picking_snr_threshold = string(processed_value)
|
||||
println(" suggested_peak_picking_snr_threshold set to $(suggested_peak_picking_snr_threshold)")
|
||||
elseif param_key == :half_window
|
||||
suggested_peak_picking_half_window = string(processed_value)
|
||||
peak_picking_half_window = string(processed_value)
|
||||
println(" suggested_peak_picking_half_window set to $(suggested_peak_picking_half_window)")
|
||||
elseif param_key == :min_peak_prominence
|
||||
suggested_peak_picking_min_peak_prominence = string(processed_value)
|
||||
peak_picking_min_peak_prominence = string(processed_value)
|
||||
println(" suggested_peak_picking_min_peak_prominence set to $(suggested_peak_picking_min_peak_prominence)")
|
||||
elseif param_key == :merge_peaks_tolerance
|
||||
suggested_peak_picking_merge_peaks_tolerance = string(processed_value)
|
||||
peak_picking_merge_peaks_tolerance = string(processed_value)
|
||||
println(" suggested_peak_picking_merge_peaks_tolerance set to $(suggested_peak_picking_merge_peaks_tolerance)")
|
||||
elseif param_key == :min_peak_width_ppm
|
||||
suggested_peak_picking_min_peak_width_ppm = string(processed_value)
|
||||
peak_picking_min_peak_width_ppm = string(processed_value)
|
||||
println(" suggested_peak_picking_min_peak_width_ppm set to $(suggested_peak_picking_min_peak_width_ppm)")
|
||||
elseif param_key == :max_peak_width_ppm
|
||||
suggested_peak_picking_max_peak_width_ppm = string(processed_value)
|
||||
peak_picking_max_peak_width_ppm = string(processed_value)
|
||||
println(" suggested_peak_picking_max_peak_width_ppm set to $(suggested_peak_picking_max_peak_width_ppm)")
|
||||
elseif param_key == :min_peak_shape_r2
|
||||
suggested_peak_picking_min_peak_shape_r2 = string(processed_value)
|
||||
peak_picking_min_peak_shape_r2 = string(processed_value)
|
||||
println(" suggested_peak_picking_min_peak_shape_r2 set to $(suggested_peak_picking_min_peak_shape_r2)")
|
||||
end
|
||||
elseif step_name == :PeakSelection
|
||||
if param_key == :min_snr
|
||||
suggested_peak_selection_min_snr = string(processed_value)
|
||||
peak_selection_min_snr = string(processed_value)
|
||||
println(" suggested_peak_selection_min_snr set to $(suggested_peak_selection_min_snr)")
|
||||
elseif param_key == :min_fwhm_ppm
|
||||
suggested_peak_selection_min_fwhm_ppm = string(processed_value)
|
||||
peak_selection_min_fwhm_ppm = string(processed_value)
|
||||
println(" suggested_peak_selection_min_fwhm_ppm set to $(suggested_peak_selection_min_fwhm_ppm)")
|
||||
elseif param_key == :max_fwhm_ppm
|
||||
suggested_peak_selection_max_fwhm_ppm = string(processed_value)
|
||||
peak_selection_max_fwhm_ppm = string(processed_value)
|
||||
println(" suggested_peak_selection_max_fwhm_ppm set to $(suggested_peak_selection_max_fwhm_ppm)")
|
||||
elseif param_key == :min_shape_r2
|
||||
suggested_peak_selection_min_shape_r2 = string(processed_value)
|
||||
peak_selection_min_shape_r2 = string(processed_value)
|
||||
println(" suggested_peak_selection_min_shape_r2 set to $(suggested_peak_selection_min_shape_r2)")
|
||||
elseif param_key == :frequency_threshold
|
||||
suggested_peak_selection_frequency_threshold = string(processed_value)
|
||||
peak_selection_frequency_threshold = string(processed_value)
|
||||
println(" suggested_peak_selection_frequency_threshold set to $(suggested_peak_selection_frequency_threshold)")
|
||||
elseif param_key == :correlation_threshold
|
||||
suggested_peak_selection_correlation_threshold = string(processed_value)
|
||||
peak_selection_correlation_threshold = string(processed_value)
|
||||
println(" suggested_peak_selection_correlation_threshold set to $(suggested_peak_selection_correlation_threshold)")
|
||||
end
|
||||
elseif step_name == :PeakBinning
|
||||
if param_key == :tolerance
|
||||
suggested_binning_tolerance = string(processed_value)
|
||||
binning_tolerance = string(processed_value)
|
||||
println(" suggested_binning_tolerance set to $(suggested_binning_tolerance)")
|
||||
elseif param_key == :frequency_threshold
|
||||
suggested_binning_frequency_threshold = string(processed_value)
|
||||
binning_frequency_threshold = string(processed_value)
|
||||
println(" suggested_binning_frequency_threshold set to $(suggested_binning_frequency_threshold)")
|
||||
elseif param_key == :min_peak_per_bin
|
||||
suggested_binning_min_peak_per_bin = string(processed_value)
|
||||
binning_min_peak_per_bin = string(processed_value)
|
||||
println(" suggested_binning_min_peak_per_bin set to $(suggested_binning_min_peak_per_bin)")
|
||||
elseif param_key == :max_bin_width_ppm
|
||||
suggested_binning_max_bin_width_ppm = string(processed_value)
|
||||
binning_max_bin_width_ppm = string(processed_value)
|
||||
println(" suggested_binning_max_bin_width_ppm set to $(suggested_binning_max_bin_width_ppm)")
|
||||
elseif param_key == :num_uniform_bins
|
||||
suggested_binning_num_uniform_bins = string(processed_value)
|
||||
binning_num_uniform_bins = string(processed_value)
|
||||
println(" suggested_binning_num_uniform_bins set to $(suggested_binning_num_uniform_bins)")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Also set method types for steps
|
||||
if haskey(recommended_params, :Smoothing) && haskey(recommended_params[:Smoothing], :method)
|
||||
smoothing_method = string(recommended_params[:Smoothing][:method])
|
||||
end
|
||||
if haskey(recommended_params, :BaselineCorrection) && haskey(recommended_params[:BaselineCorrection], :method)
|
||||
baseline_method = string(recommended_params[:BaselineCorrection][:method])
|
||||
end
|
||||
if haskey(recommended_params, :Normalization) && haskey(recommended_params[:Normalization], :method)
|
||||
normalization_method = string(recommended_params[:Normalization][:method])
|
||||
end
|
||||
if haskey(recommended_params, :PeakAlignment) && haskey(recommended_params[:PeakAlignment], :method)
|
||||
alignment_method = string(recommended_params[:PeakAlignment][:method])
|
||||
end
|
||||
if haskey(recommended_params, :PeakPicking) && haskey(recommended_params[:PeakPicking], :method)
|
||||
peak_picking_method = string(recommended_params[:PeakPicking][:method])
|
||||
end
|
||||
if haskey(recommended_params, :PeakBinning) && haskey(recommended_params[:PeakBinning], :method)
|
||||
binning_method = string(recommended_params[:PeakBinning][:method])
|
||||
end
|
||||
if haskey(recommended_params, :PeakAlignment) && haskey(recommended_params[:PeakAlignment], :tolerance_unit)
|
||||
alignment_tolerance_unit = string(recommended_params[:PeakAlignment][:tolerance_unit])
|
||||
end
|
||||
if haskey(recommended_params, :PeakBinning) && haskey(recommended_params[:PeakBinning], :tolerance_unit)
|
||||
binning_tolerance_unit = string(recommended_params[:PeakBinning][:tolerance_unit])
|
||||
end
|
||||
|
||||
msg = "File loaded and parameters suggested."
|
||||
catch e
|
||||
@warn "Could not suggest parameters. Using defaults. Error: $e"
|
||||
end
|
||||
|
||||
metadata_columns = [
|
||||
Dict("name" => "parameter", "label" => "Parameter", "field" => "parameter", "align" => "left"),
|
||||
Dict("name" => "value", "label" => "Value", "field" => "value", "align" => "left"),
|
||||
@ -511,15 +838,271 @@ end
|
||||
btnMetadataDisable = true
|
||||
@error "File loading failed" exception=(e, catch_backtrace())
|
||||
finally
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
progress = false
|
||||
progressSpectraPlot = false
|
||||
end
|
||||
end
|
||||
|
||||
@onbutton export_params_btn begin
|
||||
params_to_export = Dict(
|
||||
"pipeline_step_order" => pipeline_step_order,
|
||||
"enable_standards" => enable_standards, # Export global flag
|
||||
"stabilization_method" => stabilization_method,
|
||||
"smoothing_method" => smoothing_method,
|
||||
"smoothing_window" => smoothing_window,
|
||||
"smoothing_order" => smoothing_order,
|
||||
"baseline_method" => baseline_method,
|
||||
"baseline_iterations" => baseline_iterations,
|
||||
"baseline_window" => baseline_window,
|
||||
"normalization_method" => normalization_method,
|
||||
"alignment_method" => alignment_method,
|
||||
"alignment_span" => alignment_span,
|
||||
"alignment_tolerance" => alignment_tolerance,
|
||||
"alignment_tolerance_unit" => alignment_tolerance_unit,
|
||||
"alignment_max_shift_ppm" => alignment_max_shift_ppm,
|
||||
"alignment_min_matched_peaks" => alignment_min_matched_peaks,
|
||||
"peak_picking_method" => peak_picking_method,
|
||||
"peak_picking_snr_threshold" => peak_picking_snr_threshold,
|
||||
"peak_picking_half_window" => peak_picking_half_window,
|
||||
"peak_picking_min_peak_prominence" => peak_picking_min_peak_prominence,
|
||||
"peak_picking_merge_peaks_tolerance" => peak_picking_merge_peaks_tolerance,
|
||||
"peak_picking_min_peak_width_ppm" => peak_picking_min_peak_width_ppm,
|
||||
"peak_picking_max_peak_width_ppm" => peak_picking_max_peak_width_ppm,
|
||||
"peak_picking_min_peak_shape_r2" => peak_picking_min_peak_shape_r2,
|
||||
"binning_method" => binning_method,
|
||||
"binning_tolerance" => binning_tolerance,
|
||||
"binning_tolerance_unit" => binning_tolerance_unit,
|
||||
"binning_frequency_threshold" => binning_frequency_threshold,
|
||||
"binning_min_peak_per_bin" => binning_min_peak_per_bin,
|
||||
"binning_max_bin_width_ppm" => binning_max_bin_width_ppm,
|
||||
"binning_intensity_weighted_centers" => binning_intensity_weighted_centers,
|
||||
"binning_num_uniform_bins" => binning_num_uniform_bins,
|
||||
"calibration_fit_order" => calibration_fit_order,
|
||||
"calibration_ppm_tolerance" => calibration_ppm_tolerance,
|
||||
"peak_selection_min_snr" => peak_selection_min_snr,
|
||||
"peak_selection_min_fwhm_ppm" => peak_selection_min_fwhm_ppm,
|
||||
"peak_selection_max_fwhm_ppm" => peak_selection_max_fwhm_ppm,
|
||||
"peak_selection_min_shape_r2" => peak_selection_min_shape_r2,
|
||||
"peak_selection_frequency_threshold" => peak_selection_frequency_threshold,
|
||||
"peak_selection_correlation_threshold" => peak_selection_correlation_threshold,
|
||||
"reference_peaks_list" => reference_peaks_list
|
||||
)
|
||||
json_string = JSON.json(params_to_export)
|
||||
js_script = """
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/json;charset=utf-8,' + encodeURIComponent(`$json_string`));
|
||||
element.setAttribute('download', 'preprocessing_params.json');
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
"""
|
||||
run_js(js_script)
|
||||
msg = "Parameters exported."
|
||||
end
|
||||
|
||||
@onchange import_params_btn begin
|
||||
if import_params_btn
|
||||
try
|
||||
json_string = String(imported_params_file.data)
|
||||
params = JSON.parse(json_string)
|
||||
|
||||
for (key, value) in params
|
||||
if key == "reference_peaks_list"
|
||||
reference_peaks_list = value
|
||||
elseif key == "pipeline_step_order"
|
||||
pipeline_step_order = value
|
||||
elseif key == "enable_standards" # Import global flag
|
||||
enable_standards = value
|
||||
else
|
||||
# Use getfield and setproperty! to update reactive variables by name
|
||||
if hasfield(typeof(@__MODULE__), Symbol(key))
|
||||
getfield(@__MODULE__, Symbol(key))[] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
msg = "Parameters imported successfully."
|
||||
catch e
|
||||
msg = "Failed to import parameters: $e"
|
||||
warning_msg = true
|
||||
finally
|
||||
import_params_btn = false # Reset button state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@onbutton run_full_pipeline begin
|
||||
progressPrep = true
|
||||
current_pipeline_step = "Initializing..."
|
||||
|
||||
try
|
||||
# 1. Data Preparation
|
||||
if isempty(selected_folder_main)
|
||||
msg = "No dataset selected. Please process a file first."
|
||||
warning_msg = true
|
||||
return
|
||||
end
|
||||
|
||||
registry = load_registry(registry_path)
|
||||
entry = registry[selected_folder_main]
|
||||
target_path = entry["source_path"]
|
||||
|
||||
if msi_data === nothing || full_route != target_path
|
||||
if msi_data !== nothing
|
||||
close(msi_data)
|
||||
end
|
||||
full_route = target_path
|
||||
msi_data = OpenMSIData(target_path)
|
||||
end
|
||||
|
||||
# Apply mask if enabled
|
||||
spectrum_indices_to_process = collect(1:length(msi_data.spectra_metadata))
|
||||
if maskEnabled && !isempty(preprocessing_mask_route) && isfile(preprocessing_mask_route)
|
||||
current_pipeline_step = "Loading mask..."
|
||||
mask_matrix = load_and_prepare_mask(preprocessing_mask_route, msi_data.image_dims)
|
||||
masked_indices_set = get_masked_spectrum_indices(msi_data, mask_matrix)
|
||||
spectrum_indices_to_process = collect(masked_indices_set)
|
||||
end
|
||||
|
||||
# Initialize spectra data structure
|
||||
current_pipeline_step = "Loading spectra..."
|
||||
current_spectra = Vector{MutableSpectrum}(undef, length(spectrum_indices_to_process))
|
||||
|
||||
Threads. @threads for i in 1:length(spectrum_indices_to_process)
|
||||
original_idx = spectrum_indices_to_process[i]
|
||||
mz, intensity = GetSpectrum(msi_data, original_idx)
|
||||
current_spectra[i] = MutableSpectrum(original_idx, mz, intensity, [])
|
||||
end
|
||||
|
||||
# 2. Parameter Assembly
|
||||
current_pipeline_step = "Configuring parameters..."
|
||||
final_params = Dict(
|
||||
:Stabilization => Dict(:method => Symbol(stabilization_method)),
|
||||
:Smoothing => Dict(
|
||||
:method => Symbol(smoothing_method),
|
||||
:window => parse(Int, smoothing_window),
|
||||
:order => parse(Int, smoothing_order)
|
||||
),
|
||||
:BaselineCorrection => Dict(
|
||||
:method => Symbol(baseline_method),
|
||||
:iterations => parse(Int, baseline_iterations),
|
||||
:window => parse(Int, baseline_window)
|
||||
),
|
||||
:Normalization => Dict(:method => Symbol(normalization_method)),
|
||||
:PeakPicking => Dict(
|
||||
:method => Symbol(peak_picking_method),
|
||||
:snr_threshold => parse(Float64, peak_picking_snr_threshold),
|
||||
:half_window => parse(Int, peak_picking_half_window),
|
||||
:min_peak_prominence => parse(Float64, peak_picking_min_peak_prominence),
|
||||
:merge_peaks_tolerance => parse(Float64, peak_picking_merge_peaks_tolerance)
|
||||
),
|
||||
:PeakSelection => Dict(
|
||||
:min_snr => parse(Float64, peak_selection_min_snr),
|
||||
:min_fwhm_ppm => parse(Float64, peak_selection_min_fwhm_ppm),
|
||||
:max_fwhm_ppm => parse(Float64, peak_selection_max_fwhm_ppm),
|
||||
:min_shape_r2 => parse(Float64, peak_selection_min_shape_r2)
|
||||
),
|
||||
:Calibration => Dict(
|
||||
:method => :internal_standards,
|
||||
:ppm_tolerance => parse(Float64, calibration_ppm_tolerance),
|
||||
:fit_order => parse(Int, calibration_fit_order)
|
||||
),
|
||||
:PeakAlignment => Dict(
|
||||
:method => Symbol(alignment_method),
|
||||
:tolerance => parse(Float64, alignment_tolerance),
|
||||
:tolerance_unit => Symbol(alignment_tolerance_unit)
|
||||
),
|
||||
:PeakBinning => Dict(
|
||||
:method => Symbol(binning_method),
|
||||
:tolerance => parse(Float64, binning_tolerance),
|
||||
:tolerance_unit => Symbol(binning_tolerance_unit),
|
||||
:min_peak_per_bin => parse(Int, binning_min_peak_per_bin)
|
||||
)
|
||||
)
|
||||
|
||||
# Build pipeline steps from enabled steps in order
|
||||
pipeline_stp = [step["name"] for step in pipeline_step_order if step["enabled"]]
|
||||
|
||||
# Convert reference peaks
|
||||
ref_peaks = Dict(p["mz"] => p["label"] for p in reference_peaks_list)
|
||||
|
||||
# 3. Execute Pipeline
|
||||
current_pipeline_step = "Running preprocessing pipeline..."
|
||||
feature_matrix_result, bin_info_result = execute_full_preprocessing(
|
||||
current_spectra,
|
||||
final_params,
|
||||
pipeline_stp,
|
||||
ref_peaks,
|
||||
maskEnabled ? preprocessing_mask_route : nothing
|
||||
) do step
|
||||
current_pipeline_step = "Processing: $step"
|
||||
end
|
||||
|
||||
# 4. Update Results Display
|
||||
current_pipeline_step = "Updating results..."
|
||||
|
||||
# Find the spectrum to display in "after" plot
|
||||
display_spectrum_idx = findfirst(s -> s.id == selected_spectrum_id_for_plot, current_spectra)
|
||||
if display_spectrum_idx !== nothing
|
||||
processed_spectrum = current_spectra[display_spectrum_idx]
|
||||
|
||||
# Create "after" plot data
|
||||
after_trace = PlotlyBase.scatter(
|
||||
x=processed_spectrum.mz,
|
||||
y=processed_spectrum.intensity,
|
||||
mode="lines",
|
||||
name="Processed Spectrum"
|
||||
)
|
||||
|
||||
traces_after = [after_trace]
|
||||
|
||||
# Add peaks if they exist
|
||||
if !isempty(processed_spectrum.peaks)
|
||||
peak_mzs = [p.mz for p in processed_spectrum.peaks]
|
||||
peak_intensities = [p.intensity for p in processed_spectrum.peaks] # Fixed: Should be peak.intensity
|
||||
peak_trace = PlotlyBase.scatter(
|
||||
x=peak_mzs,
|
||||
y=peak_intensities,
|
||||
mode="markers",
|
||||
name="Picked Peaks",
|
||||
marker=attr(color="red", size=8)
|
||||
)
|
||||
push!(traces_after, peak_trace)
|
||||
end
|
||||
|
||||
plotdata_after = traces_after
|
||||
plotlayout_after = PlotlyBase.Layout(
|
||||
title="After Preprocessing (Spectrum $selected_spectrum_id_for_plot)",
|
||||
xaxis_title="m/z",
|
||||
yaxis_title="Intensity"
|
||||
)
|
||||
end
|
||||
|
||||
# Save feature matrix if binning was performed
|
||||
if feature_matrix_result !== nothing
|
||||
output_dir = joinpath("public", selected_folder_main, "preprocessing_results")
|
||||
mkpath(output_dir)
|
||||
save_feature_matrix(feature_matrix_result, bin_info_result, output_dir)
|
||||
msg = "Pipeline completed successfully. Feature matrix saved."
|
||||
else
|
||||
msg = "Pipeline completed successfully."
|
||||
end
|
||||
|
||||
catch e
|
||||
msg = "Error during pipeline execution: $e"
|
||||
warning_msg = true
|
||||
@error "Pipeline failed" exception=(e, catch_backtrace())
|
||||
finally
|
||||
progressPrep = false
|
||||
current_pipeline_step = ""
|
||||
GC.gc()
|
||||
end
|
||||
end
|
||||
|
||||
# This new handler correctly adds the file from full_route to the batch list.
|
||||
@onbutton btnAddBatch begin
|
||||
if isempty(full_route) || full_route == "unknown (manually added)"
|
||||
@ -704,7 +1287,7 @@ end
|
||||
files_without_mask = 0
|
||||
|
||||
for (file_idx, file_path) in enumerate(current_selected_files)
|
||||
progress_message = "Processing file $file_idx/$num_files: $(basename(file_path))"
|
||||
progress_message = "Processing file $(file_idx)/$(num_files): $(basename(file_path))"
|
||||
overall_progress = current_step / total_steps
|
||||
|
||||
all_params = (
|
||||
@ -832,9 +1415,9 @@ end
|
||||
SpectraEnabled = true
|
||||
overall_progress = 0.0
|
||||
println("Done")
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -891,6 +1474,8 @@ end
|
||||
end
|
||||
|
||||
plotdata, plotlayout, xSpectraMz, ySpectraMz = meanSpectrumPlot(msi_data, selected_folder_main, mask_path=mask_path_for_plot)
|
||||
plotdata_before = plotdata
|
||||
plotlayout_before = plotlayout
|
||||
selectedTab = "tab2"
|
||||
fTime = time()
|
||||
eTime = round(fTime - sTime, digits=3)
|
||||
@ -905,9 +1490,9 @@ end
|
||||
btnPlotDisable = false
|
||||
btnSpectraDisable = false
|
||||
btnStartDisable = false
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -963,6 +1548,8 @@ end
|
||||
end
|
||||
|
||||
plotdata, plotlayout, xSpectraMz, ySpectraMz = sumSpectrumPlot(msi_data, selected_folder_main, mask_path=mask_path_for_plot)
|
||||
plotdata_before = plotdata
|
||||
plotlayout_before = plotlayout
|
||||
selectedTab = "tab2"
|
||||
fTime = time()
|
||||
eTime = round(fTime - sTime, digits=3)
|
||||
@ -977,9 +1564,9 @@ end
|
||||
btnPlotDisable = false
|
||||
btnSpectraDisable = false
|
||||
btnStartDisable = false
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1003,7 +1590,7 @@ end
|
||||
|
||||
# Add error handling for registry access
|
||||
if !haskey(registry, selected_folder_main)
|
||||
msg = "Dataset '$selected_folder_main' not found in registry."
|
||||
msg = "Dataset '$(selected_folder_main)' not found in registry."
|
||||
warning_msg = true
|
||||
return
|
||||
end
|
||||
@ -1047,6 +1634,8 @@ end
|
||||
# Convert to positive coordinates for processing
|
||||
y_positive = yCoord < 0 ? abs(yCoord) : yCoord
|
||||
plotdata, plotlayout, xSpectraMz, ySpectraMz = xySpectrumPlot(msi_data, xCoord, y_positive, imgWidth, imgHeight, selected_folder_main, mask_path=mask_path_for_plot)
|
||||
plotdata_before = plotdata
|
||||
plotlayout_before = plotlayout
|
||||
|
||||
# Update coordinates based on actual plot title
|
||||
# Extract title text from the Dict safely
|
||||
@ -1092,9 +1681,9 @@ end
|
||||
btnPlotDisable = false
|
||||
btnSpectraDisable = false
|
||||
btnStartDisable = false
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1375,9 +1964,9 @@ end
|
||||
end
|
||||
msi_data = nothing
|
||||
log_memory_usage("Folder Changed (msi_data cleared)", msi_data)
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
|
||||
if !isempty(selected_folder_main)
|
||||
@ -1623,9 +2212,9 @@ end
|
||||
btnStartDisable=false
|
||||
btnSpectraDisable=false
|
||||
SpectraEnabled=true
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1650,7 +2239,7 @@ end
|
||||
try
|
||||
# --- Get Mask Path ---
|
||||
local mask_path_for_plot::Union{String, Nothing} = nothing
|
||||
if maskEnabled[] && !isempty(selected_folder_main)
|
||||
if maskEnabled && !isempty(selected_folder_main)
|
||||
registry = load_registry(registry_path)
|
||||
entry = get(registry, selected_folder_main, nothing)
|
||||
if entry !== nothing && get(entry, "has_mask", false)
|
||||
@ -1686,9 +2275,9 @@ end
|
||||
btnStartDisable=false
|
||||
btnSpectraDisable=false
|
||||
SpectraEnabled=true
|
||||
GC.gc()
|
||||
GC.gc() # Trigger garbage collection
|
||||
if Sys.islinux()
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0)
|
||||
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1789,6 +2378,24 @@ end
|
||||
# To include a visualization in the spectrum plot indicating where is the selected mass
|
||||
@onchange Nmass begin
|
||||
if !isempty(xSpectraMz)
|
||||
df = msi_data.spectrum_stats_df
|
||||
profile_count = 0
|
||||
if df !== nothing && hasproperty(df, :Mode)
|
||||
profile_count = count(==(MSI_src.PROFILE), df.Mode)
|
||||
end
|
||||
|
||||
if profile_count > 0
|
||||
# Main spectrum trace
|
||||
traceSpectra = PlotlyBase.scatter(
|
||||
x=xSpectraMz,
|
||||
y=ySpectraMz,
|
||||
marker=attr(size=1, color="blue", opacity=0.5),
|
||||
name="Spectrum",
|
||||
hoverinfo="x",
|
||||
hovertemplate="<b>m/z</b>: %{x:.4f}<extra></extra>",
|
||||
showlegend=false
|
||||
)
|
||||
else
|
||||
# Main spectrum trace
|
||||
traceSpectra = PlotlyBase.stem(
|
||||
x=xSpectraMz,
|
||||
@ -1799,6 +2406,7 @@ end
|
||||
hovertemplate="<b>m/z</b>: %{x:.4f}<extra></extra>",
|
||||
showlegend=false
|
||||
)
|
||||
end
|
||||
|
||||
# Parse all valid masses from the comma-separated string
|
||||
mass_strs = split(Nmass, ',', keepempty=false)
|
||||
@ -1955,9 +2563,11 @@ end
|
||||
|
||||
@onchange isready @time begin
|
||||
if isready && !registry_init_done
|
||||
initialization_message = "Pre-compiling functions at startup..."
|
||||
warmup_init()
|
||||
initialization_message = "Pre-compilation finished."
|
||||
try
|
||||
println("Synchronizing registry with filesystem on backend init...")
|
||||
initialization_message = "Synchronizing registry with filesystem on backend init..."
|
||||
reg_path = abspath(joinpath(@__DIR__, "public", "registry.json"))
|
||||
registry = isfile(reg_path) ? load_registry(reg_path) : Dict{String, Any}()
|
||||
|
||||
@ -1986,10 +2596,8 @@ end
|
||||
end
|
||||
|
||||
if !isempty(new_folders) || !isempty(removed_folders)
|
||||
println("Registry changed, saving...")
|
||||
open(reg_path, "w") do f
|
||||
JSON.print(f, registry, 4)
|
||||
end
|
||||
initialization_message = "Registry changed, saving..."
|
||||
save_registry(reg_path, registry)
|
||||
end
|
||||
|
||||
all_folders = sort(collect(keys(registry)), lt=natural)
|
||||
@ -2006,8 +2614,11 @@ end
|
||||
selected_files = String[]
|
||||
finally
|
||||
registry_init_done = true
|
||||
is_initializing = false # Hide loading screen when initialization is complete
|
||||
end
|
||||
end
|
||||
is_initializing = false # Hide loading screen when initialization is complete (current code is hidden due to incompatibility)
|
||||
initialization_message = "Done."
|
||||
log_memory_usage("App Ready", msi_data)
|
||||
end
|
||||
|
||||
|
||||
535
app.jl.html
535
app.jl.html
@ -4,6 +4,16 @@
|
||||
<h4>JuliaMSI </h4>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!--
|
||||
<div v-if="is_initializing" class="loading-overlay">
|
||||
<div class="loading-content">
|
||||
<q-spinner-hourglass color="white" size="4em" />
|
||||
<div class="q-mt-md text-white text-h6">{{ initialization_message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="extDivStyle" class="row col-12 q-pa-xl">
|
||||
<div class="row col-6">
|
||||
<!-- Left DIV -->
|
||||
@ -17,6 +27,8 @@
|
||||
<q-tab-panels v-model="left_tab" animated>
|
||||
<q-tab-panel name="pre_treatment">
|
||||
<div class="text-h6">imzML & mzML Data Pre-Treatment</div>
|
||||
|
||||
<!-- File Selection & Batch Controls (keep existing) -->
|
||||
<div class="row items-center">
|
||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer col" v-model="full_route" readonly
|
||||
:label="batch_file_count > 0 ? batch_file_count + ' file(s) in batch' : 'Select an imzMl / mzML file'"
|
||||
@ -29,206 +41,393 @@
|
||||
<q-btn class="q-ma-sm" icon="clear" v-on:click="clear_batch_btn=true" :disable="batch_file_count === 0"
|
||||
label="Clear"></q-btn>
|
||||
</div>
|
||||
<q-list bordered separator v-if="selected_files.length > 0">
|
||||
<q-item v-for="(file, index) in selected_files" :key="index">
|
||||
<q-item-section>
|
||||
{{ file }}
|
||||
|
||||
<!-- Mask Configuration -->
|
||||
<div class="row items-center q-mb-md">
|
||||
<q-toggle v-model="maskEnabled" label="Apply Mask During Preprocessing" color="green" class="q-mr-md" />
|
||||
</div>
|
||||
|
||||
<!-- Spectrum Selection for Visualization -->
|
||||
<div class="row items-center q-mb-md">
|
||||
<div class="text-subtitle2 q-mr-md">Preview Spectrum:</div>
|
||||
<q-btn-dropdown class="q-ma-sm" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
|
||||
label="Generate Spectra" icon="play_arrow">
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup v-on:click="createMeanPlot=true">
|
||||
<q-item-label>Mean spectrum plot</q-item-label>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="createSumPlot=true">
|
||||
<q-item-label>Sum Spectrum plot</q-item-label>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="createXYPlot=true">
|
||||
<q-item-label>Spectrum plot (X,Y)</q-item-label>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<div class="row col-6">
|
||||
<div class="st-col col-3 col-sm-3 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="idSpectrum" label="Spectrum ID" type="number"
|
||||
hint="Not for Mean/Sum plots."></q-input>
|
||||
</div>
|
||||
<div class="st-col col-3 col-sm-3 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="xCoord" label="X coord" type="number" :rules="[
|
||||
val => !btnSpectraDisable ? ( val >= 0 || 'Needs to be bigger than 0') : true
|
||||
]"></q-input>
|
||||
</div>
|
||||
<div class="st-col col-3 col-sm-3 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="yCoord" label="Y coord" type="number" :rules="[
|
||||
val => !btnSpectraDisable ? ( val >= 0 || 'Needs to be bigger than 0') : true
|
||||
]"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Internal Standards (Fixed First Item) -->
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Internal Standards</div>
|
||||
<div class="text-caption">Reference peaks for calibration and alignment</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<!-- Keep your existing reference_peaks_list implementation -->
|
||||
<q-list bordered separator class="q-mt-md">
|
||||
<q-item v-for="(peak, index) in reference_peaks_list" :key="index">
|
||||
<q-item-section avatar>
|
||||
<q-btn flat round icon="delete" color="negative" @click="removeReferencePeak(index)"></q-btn>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn flat round icon="delete" size="sm" v-on:click="selected_files.splice(index, 1)"></q-btn>
|
||||
<q-item-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="m/z" type="number" step="0.0001"
|
||||
v-model.number="peak.mz" :rules="[val => !!val || 'Required', val => val > 0 || 'Must be positive']"></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Label (optional)" v-model="peak.label"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-btn class="q-ma-sm btn-style" icon="add" label="Add Reference Peak" @click="addReferencePeak"></q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer col" v-model="full_route_cal" readonly
|
||||
label="Select a calibration imzMl / mzML file" v-on:click="btnSearchCal=true">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" v-on:click="btnSearchCal=true" class="cursor-pointer" />
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<!-- Reorderable Preprocessing Steps -->
|
||||
<div class="text-h6 q-mb-md">Preprocessing Pipeline</div>
|
||||
<q-list bordered>
|
||||
<q-expansion-item v-for="(step, index) in pipeline_step_order" :key="step.name"
|
||||
:label="step.label" group="preprocessing-steps"
|
||||
:class="step.enabled ? '' : 'text-grey'">
|
||||
|
||||
<!-- Header with controls -->
|
||||
<template v-slot:header>
|
||||
<q-item-section avatar>
|
||||
<div class="row no-wrap">
|
||||
<q-btn flat round icon="arrow_upward" size="sm"
|
||||
:disable="index === 0" @click.stop="moveStepUp(index)"></q-btn>
|
||||
<q-btn flat round icon="arrow_downward" size="sm"
|
||||
:disable="index === pipeline_step_order.length - 1" @click.stop="moveStepDown(index)"></q-btn>
|
||||
</div>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>
|
||||
{{ step.label }}
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section side>
|
||||
<q-toggle v-model="step.enabled" color="green" @click.stop />
|
||||
</q-item-section>
|
||||
</template>
|
||||
</q-input>-
|
||||
<br>
|
||||
<br>
|
||||
<q-tabs v-model="pre_tab" dense class="text-grey" indicator-color="primary" align="justify">
|
||||
<q-tab name="stabilization" label="Stabilization"></q-tab>
|
||||
<q-tab name="smoothing" label="Smoothing"></q-tab>
|
||||
<q-tab name="baseline" label="Baseline"></q-tab>
|
||||
<q-tab name="calibration" label="Calibration"></q-tab>
|
||||
<q-tab name="warping" label="Warping"></q-tab>
|
||||
<q-tab name="peak" label="Peak Detection"></q-tab>
|
||||
<q-tab name="binning" label="Binning"></q-tab>
|
||||
</q-tabs>
|
||||
<q-separator />
|
||||
<q-tab-panels v-model="pre_tab" animated>
|
||||
<q-tab-panel name="stabilization" class="q-pa-md">
|
||||
<q-card class="q-mb-md">
|
||||
|
||||
<!-- Step-specific parameters -->
|
||||
<q-card v-if="step.name === 'stabilization'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="stabilization_method" val="sqrt" label="SQRT" /><br>
|
||||
<q-radio v-model="stabilization_method" val="log" label="LOG" /><br>
|
||||
<q-radio v-model="stabilization_method" val="log2" label="LOG 2" /><br>
|
||||
<q-radio v-model="stabilization_method" val="log10" label="LOG 10" /><br>
|
||||
<q-radio v-model="stabilization_method" val="sqrt" label="SQRT" />
|
||||
<q-radio v-model="stabilization_method" val="log" label="LOG" />
|
||||
<q-radio v-model="stabilization_method" val="log2" label="LOG 2" />
|
||||
<q-radio v-model="stabilization_method" val="log10" label="LOG 10" />
|
||||
<q-radio v-model="stabilization_method" val="log1p" label="LOG 1P" />
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptStab=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
|
||||
<q-card v-if="step.name === 'smoothing'">
|
||||
<q-card-section>
|
||||
<q-radio v-model="smoothing_method" val="sg" label="Savitzky-Golay" />
|
||||
<q-radio v-model="smoothing_method" val="ma" label="Moving Average" />
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Window Size" v-model.number="smoothing_window" type="number" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Order (Savitzky-Golay)" v-model.number="smoothing_order" type="number" />
|
||||
</div>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="smoothing">
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="smoothing_method" val="sg" label="Savitzky-Golay" /><br>
|
||||
<q-radio v-model="smoothing_method" val="ma" label="Moving Average" /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
|
||||
<q-card v-if="step.name === 'baseline_correction'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The algorithm to use for baseline correction.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="baseline_method" val="snip" label="SNIP" hint="Sensitive Nonlinear Iterative Peak clipping." /><br>
|
||||
<q-radio v-model="baseline_method" val="convex_hull" label="CONVEX HULL" hint="Finds the lower convex hull of the spectrum." /><br>
|
||||
<q-radio v-model="baseline_method" val="median" label="MEDIAN" hint="Moving median filter." /><br>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Iterations (for SNIP)" type="number"
|
||||
:placeholder="suggested_baseline_iterations" v-model.number="baseline_iterations" hint="The number of iterations for the SNIP algorithm. A higher number results in a more aggressive baseline."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Window (for Median)" type="number"
|
||||
:placeholder="suggested_baseline_window" v-model.number="baseline_window" hint="The window size for the median method, determining the local region for median calculation."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card v-if="step.name === 'normalization'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The normalization method to apply.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="normalization_method" val="tic" label="TIC" hint="Total Ion Current normalization (divides by the sum of intensities)." /><br>
|
||||
<q-radio v-model="normalization_method" val="median" label="MEDIAN" hint="Divides by the median intensity." /><br>
|
||||
<q-radio v-model="normalization_method" val="rms" label="RMS" hint="Root Mean Square normalization." /><br>
|
||||
<q-radio v-model="normalization_method" val="none" label="NONE" hint="No normalization is applied." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card v-if="step.name === 'peak_alignment'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The alignment algorithm.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="alignment_method" val="lowess" label="LOWESS" hint="Locally Weighted Scatterplot Smoothing regression." /><br>
|
||||
<q-radio v-model="alignment_method" val="linear" label="LINEAR" hint="Linear regression." /><br>
|
||||
<q-radio v-model="alignment_method" val="ransac" label="RANSAC" hint="Random Sample Consensus algorithm for robust fitting." /><br>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Span (for LOWESS)" type="number" step="0.01"
|
||||
:placeholder="suggested_alignment_span" v-model.number="alignment_span" :rules="[val => val >= 0.0 && val <= 1.0 || 'Needs to be between 0 and 1']" hint="The span parameter for LOWESS regression, controlling smoothness (0.0 to 1.0)."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Tolerance" type="number" step="0.001"
|
||||
:placeholder="suggested_alignment_tolerance" v-model.number="alignment_tolerance" hint="The tolerance for matching peaks between the target and reference spectrum."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<q-select standout="custom-standout" label="Tolerance Unit" v-model="alignment_tolerance_unit"
|
||||
:options="['mz', 'ppm']" class="q-mt-md" hint="The unit for tolerance, either 'mz' (absolute) or 'ppm' (relative)."></q-select>
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Max Shift PPM" type="number"
|
||||
:placeholder="suggested_alignment_max_shift_ppm" v-model.number="alignment_max_shift_ppm" hint="The maximum allowed m/z shift in ppm to prevent spurious peak matches."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min Matched Peaks" type="number"
|
||||
:placeholder="suggested_alignment_min_matched_peaks" v-model.number="alignment_min_matched_peaks" hint="The minimum number of matching peaks required to perform the alignment."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card v-if="step.name === 'calibration'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Fit Order" type="number"
|
||||
:placeholder="suggested_calibration_fit_order" v-model.number="calibration_fit_order"
|
||||
hint="Polynomial order for the calibration curve (e.g., 1 or 2)."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="PPM Tolerance" type="number"
|
||||
:placeholder="suggested_calibration_ppm_tolerance" v-model.number="calibration_ppm_tolerance"
|
||||
hint="PPM tolerance for matching reference peaks to internal standards."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card v-if="step.name === 'peak_picking'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The peak detection algorithm.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="peak_picking_method" val="profile" label="PROFILE" hint="For profile-mode data, using local maxima and quality filters." /><br>
|
||||
<q-radio v-model="peak_picking_method" val="wavelet" label="WAVELET" hint="Continuous Wavelet Transform (CWT) based peak detection." /><br>
|
||||
<q-radio v-model="peak_picking_method" val="centroid" label="CENTROID" hint="For centroid-mode data, essentially a filtering step." /><br>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Signal to Noise Threshold" type="number" step="0.1"
|
||||
:placeholder="suggested_peak_picking_snr_threshold" v-model.number="peak_picking_snr_threshold" hint="Signal-to-Noise Ratio threshold. Peaks with SNR below this value are discarded."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Half Window Size" type="number"
|
||||
v-model.number="smoothing_window"></q-input>
|
||||
:placeholder="suggested_peak_picking_half_window" v-model.number="peak_picking_half_window" hint="Number of data points to the left and right of a potential peak to consider for local maximum detection (for Profile method)."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min Peak Prominence" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_picking_min_peak_prominence" v-model.number="peak_picking_min_peak_prominence" hint="Minimum required prominence of a peak, expressed as a fraction of its height."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Merge Peaks Tolerance (m/z)" type="number" step="0.001"
|
||||
:placeholder="suggested_peak_picking_merge_peaks_tolerance" v-model.number="peak_picking_merge_peaks_tolerance" hint="The m/z tolerance within which to merge adjacent peaks, keeping the more intense one."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min Peak Width (PPM)" type="number"
|
||||
:placeholder="suggested_peak_picking_min_peak_width_ppm" v-model.number="peak_picking_min_peak_width_ppm" hint="Minimum acceptable peak width (FWHM) in ppm."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Max Peak Width (PPM)" type="number"
|
||||
:placeholder="suggested_peak_picking_max_peak_width_ppm" v-model.number="peak_picking_max_peak_width_ppm" hint="Maximum acceptable peak width (FWHM) in ppm."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<q-input standout="custom-standout" label="Min Peak Shape R2" type="number" step="0.01" class="q-mt-md"
|
||||
:placeholder="suggested_peak_picking_min_peak_shape_r2" v-model.number="peak_picking_min_peak_shape_r2" hint="Minimum R-squared value from a Gaussian fit to the peak, used as a quality measure for peak shape."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptSmoo=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
|
||||
<q-card v-if="step.name === 'peak_selection'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Peak Quality Filters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min SNR" type="number" step="0.1"
|
||||
:placeholder="suggested_peak_selection_min_snr" v-model.number="peak_selection_min_snr" hint="Minimum Signal-to-Noise Ratio for a peak to be kept."></q-input>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="baseline">
|
||||
<q-card class="q-mb-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min FWHM (PPM)" type="number"
|
||||
:placeholder="suggested_peak_selection_min_fwhm_ppm" v-model.number="peak_selection_min_fwhm_ppm" hint="Minimum Full Width at Half Maximum (FWHM) in ppm for a peak to be kept."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Max FWHM (PPM)" type="number"
|
||||
:placeholder="suggested_peak_selection_max_fwhm_ppm" v-model.number="peak_selection_max_fwhm_ppm" hint="Maximum Full Width at Half Maximum (FWHM) in ppm for a peak to be kept."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min Peak Shape R2" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_selection_min_shape_r2" v-model.number="peak_selection_min_shape_r2" hint="Minimum R-squared value from a Gaussian fit, filtering for good peak shape."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Frequency Threshold" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_selection_frequency_threshold" v-model.number="peak_selection_frequency_threshold"
|
||||
hint="The minimum fraction of spectra a peak must be present in to be kept (0.0 to 1.0)."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Correlation Threshold" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_selection_correlation_threshold" v-model.number="peak_selection_correlation_threshold"
|
||||
hint="Minimum correlation with neighboring peaks (not yet implemented)."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card v-if="step.name === 'peak_binning'">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The binning strategy.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="baseline_method" val="snip" label="SNIP" /><br>
|
||||
<q-radio v-model="baseline_method" val="tophat" label="TOP HAT" /><br>
|
||||
<q-radio v-model="baseline_method" val="convex_hull" label="CONVEX HULL" /><br>
|
||||
<q-radio v-model="baseline_method" val="median" label="MEDIAN" /><br>
|
||||
<q-radio v-model="binning_method" val="adaptive" label="ADAPTIVE" hint="Creates bins based on the density of detected peaks." /><br>
|
||||
<q-radio v-model="binning_method" val="uniform" label="UNIFORM" hint="Creates a fixed number of equally spaced bins over the m/z range." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Iterations" type="number"
|
||||
v-model.number="baseline_iterations"></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptBase=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Tolerance (for Adaptive)" type="number" step="0.001"
|
||||
:placeholder="suggested_binning_tolerance" v-model.number="binning_tolerance" hint="Tolerance for grouping peaks into a bin in adaptive mode."></q-input>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="calibration">
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="calibration_method" val="tic" label="TIC" /><br>
|
||||
<q-radio v-model="calibration_method" val="pqn" label="PQN" /><br>
|
||||
<q-radio v-model="calibration_method" val="median" label="MEDIAN" /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptCali=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
<div class="col-6">
|
||||
<q-select standout="custom-standout" label="Tolerance Unit" v-model="binning_tolerance_unit"
|
||||
:options="['mz', 'ppm']" hint="The unit for tolerance, either 'mz' (absolute) or 'ppm' (relative)."></q-select>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="warping">
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="warping_method" val="snip" label="SNIP" /><br>
|
||||
<q-radio v-model="warping_method" val="tophat" label="TOP HAT" /><br>
|
||||
<q-radio v-model="warping_method" val="convex_hull" label="CONVEX HULL" /><br>
|
||||
<q-radio v-model="warping_method" val="median" label="MEDIAN" /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Iterations" type="number"
|
||||
v-model.number="warping_iterations"></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptWarp=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="peak">
|
||||
<p>Detect if profile or centroid to determine which elements to show.</p>
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="peak_method" val="mad" label="MAD" /><br>
|
||||
<q-radio v-model="peak_method" val="super_smoother" label="SUPER SMOOTHER" /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Signal to Noise" type="number"
|
||||
v-model.number="peak_snr"></q-input>
|
||||
<q-input standout="custom-standout" label="Half Window Size" type="number" class="q-mt-md"
|
||||
v-model.number="peak_window"></q-input>
|
||||
<q-input standout="custom-standout" label="Intensity Threshold" type="number" class="q-mt-md"
|
||||
v-model.number="peak_threshold"></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptPeak=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Frequency Threshold" type="number" step="0.01"
|
||||
:placeholder="suggested_binning_frequency_threshold" v-model.number="binning_frequency_threshold" hint="The minimum fraction of spectra a bin must contain a peak in to be kept (0.0 to 1.0)."></q-input>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="binning">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Tolerance" type="number"
|
||||
v-model.number="binning_tolerance"></q-input>
|
||||
<q-input standout="custom-standout" label="Frequency Threshold" type="number" class="q-mt-md"
|
||||
v-model.number="binning_threshold"></q-input>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Min Peaks Per Bin" type="number"
|
||||
:placeholder="suggested_binning_min_peak_per_bin" v-model.number="binning_min_peak_per_bin" hint="The minimum number of individual peaks required to form a bin in adaptive mode."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm q-mt-md">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Max Bin Width (PPM)" type="number"
|
||||
:placeholder="suggested_binning_max_bin_width_ppm" v-model.number="binning_max_bin_width_ppm" hint="Maximum width of a bin in ppm for adaptive mode."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Number of Uniform Bins" type="number"
|
||||
:placeholder="suggested_binning_num_uniform_bins" v-model.number="binning_num_uniform_bins" hint="The number of bins to create for the uniform method."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<q-toggle v-model="binning_intensity_weighted_centers" label="Intensity Weighted Centers"
|
||||
class="q-mt-md" hint="If enabled, calculates bin centers as an intensity-weighted average of the peaks within it."></q-toggle>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="acceptBinn=true"
|
||||
padding="lg" label="Accept" />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="clear" v-on:click="undoPrep=true"
|
||||
padding="lg" label="Undo" />
|
||||
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
|
||||
<!-- Pipeline Controls -->
|
||||
<div class="row justify-end items-center q-mt-md">
|
||||
<q-btn class="q-ma-sm" icon="save" @click="export_params_btn=true" label="Export Params" outline />
|
||||
<q-btn class="q-ma-sm" icon="upload_file" @click="import_params_btn=true" label="Import Params" outline />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow"
|
||||
v-on:click="run_full_pipeline=true" padding="lg" label="Run Pipeline" />
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-tab-panel>
|
||||
</q-tab-panel>
|
||||
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
|
||||
<!-- Pipeline Controls -->
|
||||
<div class="row justify-end items-center q-mt-md">
|
||||
<q-btn class="q-ma-sm" icon="save" @click="export_params_btn=true" label="Export Params" outline />
|
||||
<q-btn class="q-ma-sm" icon="upload_file" @click="import_params_btn=true" label="Import Params" outline />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow"
|
||||
v-on:click="run_full_pipeline=true" padding="lg" label="Run Pipeline" />
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="generator">
|
||||
<div class="text-h6">imzML & mzML Data Processor</div>
|
||||
<p>Please make sure the ibd and imzML file are located in the same directory and have the same name.
|
||||
@ -330,6 +529,10 @@
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
<div class="row col-6">
|
||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="idSpectrum" label="Spectrum ID" type="number"
|
||||
hint="Not for Mean/Sum plots."></q-input>
|
||||
</div>
|
||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="xCoord" label="X coord" type="number" :rules="[
|
||||
val => SpectraEnabled ? ( '* Required', val >= 0|| 'Needs to be bigger than 0') : true
|
||||
@ -455,7 +658,7 @@
|
||||
<div class="text-subtitle1">Before Preprocessing</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<plotly id="plotSpectraBefore" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
|
||||
<plotly id="plotSpectraBefore" :data="plotdata_before" :layout="plotlayout_before" class="q-pa-none q-ma-none"></plotly>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
|
||||
665
app_snippet.html
Normal file
665
app_snippet.html
Normal file
@ -0,0 +1,665 @@
|
||||
<header id="header">
|
||||
<img src="/css/LABI_logo.png" alt="Labi Logo Icon" id="imgLogo">
|
||||
<div>
|
||||
<h4>JuliaMSI </h4>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!--
|
||||
<div v-if="is_initializing" class="loading-overlay">
|
||||
<div class="loading-content">
|
||||
<q-spinner-hourglass color="white" size="4em" />
|
||||
<div class="q-mt-md text-white text-h6">{{ initialization_message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="extDivStyle" class="row col-12 q-pa-xl">
|
||||
<div class="row col-6">
|
||||
<!-- Left DIV -->
|
||||
<div id="intDivStyle-left" class="st-col col-12 st-module">
|
||||
<q-tabs v-model="left_tab" dense class="text-grey" indicator-color="primary" align="justify">
|
||||
<q-tab name="pre_treatment" label="Pre-Treatment"></q-tab>
|
||||
<q-tab name="generator" label="Slice Generator"></q-tab>
|
||||
<q-tab name="converter" label="Converter"></q-tab>
|
||||
</q-tabs>
|
||||
<q-separator></q-separator>
|
||||
<q-tab-panels v-model="left_tab" animated>
|
||||
<q-tab-panel name="pre_treatment">
|
||||
<div class="text-h6">imzML & mzML Data Pre-Treatment</div>
|
||||
<div class="row items-center">
|
||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer col" v-model="full_route" readonly
|
||||
:label="batch_file_count > 0 ? batch_file_count + ' file(s) in batch' : 'Select an imzMl / mzML file'"
|
||||
v-on:click="btnSearch=true">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" v-on:click="btnSearch=true" class="cursor-pointer" />
|
||||
</template>
|
||||
</q-input>
|
||||
<q-btn class="q-ma-sm" icon="add" v-on:click="btnAddBatch=true" label="Add"></q-btn>
|
||||
<q-btn class="q-ma-sm" icon="clear" v-on:click="clear_batch_btn=true" :disable="batch_file_count === 0"
|
||||
label="Clear"></q-btn>
|
||||
</div>
|
||||
<q-list bordered separator v-if="selected_files.length > 0">
|
||||
<q-item v-for="(file, index) in selected_files" :key="index">
|
||||
<q-item-section>
|
||||
{{ file }}
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn flat round icon="delete" size="sm" v-on:click="selected_files.splice(index, 1)"></q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<!--<q-input standout="custom-standout" class="q-ma-sm cursor-pointer col" v-model="full_route_cal" readonly
|
||||
label="Select a calibration imzMl / mzML file" v-on:click="btnSearchCal=true">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" v-on:click="btnSearchCal=true" class="cursor-pointer" />
|
||||
</template>
|
||||
</q-input>-->
|
||||
<br>
|
||||
<br>
|
||||
<q-tabs v-model="pre_tab" dense class="text-grey" indicator-color="primary" align="justify">
|
||||
<q-tab name="stabilization" label="Stabilization"></q-tab>
|
||||
<q-tab name="smoothing" label="Smoothing"></q-tab>
|
||||
<q-tab name="baseline" label="Baseline"></q-tab>
|
||||
<q-tab name="normalization" label="Normalization"></q-tab>
|
||||
<q-tab name="alignment" label="Alignment"></q-tab>
|
||||
<q-tab name="standards" label="Internal Standards"></q-tab>
|
||||
<q-tab name="calibration" label="Calibration"></q-tab>
|
||||
<q-tab name="peak_picking" label="Peak Picking"></q-tab>
|
||||
<q-tab name="peak_selection" label="Peak Selection"></q-tab>
|
||||
<q-tab name="binning" label="Binning"></q-tab>
|
||||
</q-tabs>
|
||||
<div class="row justify-end items-center q-mt-md">
|
||||
<q-btn class="q-ma-sm" icon="save" @click="export_params_btn=true" label="Export Params" outline />
|
||||
<q-btn class="q-ma-sm" icon="upload_file" @click="import_params_btn=true" label="Import Params" outline />
|
||||
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" v-on:click="run_full_pipeline=true"
|
||||
padding="lg" label="Run Pipeline" />
|
||||
</div>
|
||||
<q-separator />
|
||||
<q-tab-panels v-model="pre_tab" animated>
|
||||
<q-tab-panel name="stabilization" class="q-pa-md">
|
||||
<q-toggle v-model="enable_stabilization" label="Enable Stabilization" color="green" class="q-mb-md" hint="Enables variance-stabilizing transformation for intensities." />
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">Applies a variance-stabilizing transformation to the intensity vector.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="stabilization_method" val="sqrt" label="SQRT" hint="Square root transformation." /><br>
|
||||
<q-radio v-model="stabilization_method" val="log" label="LOG" hint="Natural log transformation." /><br>
|
||||
<q-radio v-model="stabilization_method" val="log2" label="LOG 2" hint="Base-2 log transformation." /><br>
|
||||
<q-radio v-model="stabilization_method" val="log10" label="LOG 10" hint="Base-10 log transformation." /><br>
|
||||
<q-radio v-model="stabilization_method" val="log1p" label="LOG 1P" hint="Natural log of `1 + x`, useful for data with zeros." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="smoothing">
|
||||
<q-toggle v-model="enable_smoothing" label="Enable Smoothing" color="green" class="q-mb-md" hint="Enables spectral smoothing to reduce high-frequency noise." />
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The smoothing algorithm.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="smoothing_method" val="sg" label="Savitzky-Golay" hint="Savitzky-Golay filtering." /><br>
|
||||
<q-radio v-model="smoothing_method" val="ma" label="Moving Average" hint="Moving Average filtering." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Half Window Size" type="number"
|
||||
:placeholder="suggested_smoothing_window" v-model.number="smoothing_window" hint="The half size of the smoothing window. For Savitzky-Golay, the full window (2*half_window + 1) must be an odd integer."></q-input>
|
||||
<q-input standout="custom-standout" label="Order (for Savitzky-Golay)" type="number"
|
||||
:placeholder="suggested_smoothing_order" v-model.number="smoothing_order" class="q-mt-md" hint="The polynomial order for the Savitzky-Golay filter. Must be less than the full window size."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="baseline">
|
||||
<q-toggle v-model="enable_baseline" label="Enable Baseline Correction" color="green" class="q-mb-md" hint="Estimates and subtracts the background noise (baseline) from the spectral intensities." />
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The algorithm to use for baseline correction.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="baseline_method" val="snip" label="SNIP" hint="Sensitive Nonlinear Iterative Peak clipping." /><br>
|
||||
<q-radio v-model="baseline_method" val="convex_hull" label="CONVEX HULL" hint="Finds the lower convex hull of the spectrum." /><br>
|
||||
<q-radio v-model="baseline_method" val="median" label="MEDIAN" hint="Moving median filter." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Iterations (for SNIP)" type="number"
|
||||
:placeholder="suggested_baseline_iterations" v-model.number="baseline_iterations" hint="The number of iterations for the SNIP algorithm. A higher number results in a more aggressive baseline."></q-input>
|
||||
<q-input standout="custom-standout" label="Window (for Median)" type="number"
|
||||
:placeholder="suggested_baseline_window" v-model.number="baseline_window" class="q-mt-md" hint="The window size for the median method, determining the local region for median calculation."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="normalization">
|
||||
<q-toggle v-model="enable_normalization" label="Enable Normalization" color="green" class="q-mb-md" hint="Corrects for variations in total ion current between different spectra, making them more comparable." />
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The normalization method to apply.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="normalization_method" val="tic" label="TIC" hint="Total Ion Current normalization (divides by the sum of intensities)." /><br>
|
||||
<q-radio v-model="normalization_method" val="median" label="MEDIAN" hint="Divides by the median intensity." /><br>
|
||||
<q-radio v-model="normalization_method" val="rms" label="RMS" hint="Root Mean Square normalization." /><br>
|
||||
<q-radio v-model="normalization_method" val="none" label="NONE" hint="No normalization is applied." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="alignment">
|
||||
<q-toggle v-model="enable_alignment" label="Enable Peak Alignment" color="green" class="q-mb-md" hint="Corrects for m/z shifts between spectra, ensuring that the same analyte peak appears at the same m/z across all samples." />
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The alignment algorithm.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="alignment_method" val="lowess" label="LOWESS" hint="Locally Weighted Scatterplot Smoothing regression." /><br>
|
||||
<q-radio v-model="alignment_method" val="linear" label="LINEAR" hint="Linear regression." /><br>
|
||||
<q-radio v-model="alignment_method" val="ransac" label="RANSAC" hint="Random Sample Consensus algorithm for robust fitting." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Span (for LOWESS)" type="number" step="0.01"
|
||||
:placeholder="suggested_alignment_span" v-model.number="alignment_span" :rules="[val => val >= 0.0 && val <= 1.0 || 'Needs to be between 0 and 1']" hint="The span parameter for LOWESS regression, controlling smoothness (0.0 to 1.0)."></q-input>
|
||||
<q-input standout="custom-standout" label="Tolerance" type="number" step="0.001"
|
||||
:placeholder="suggested_alignment_tolerance" v-model.number="alignment_tolerance" class="q-mt-md" hint="The tolerance for matching peaks between the target and reference spectrum."></q-input>
|
||||
<q-select standout="custom-standout" label="Tolerance Unit" v-model="alignment_tolerance_unit"
|
||||
:options="['mz', 'ppm']" class="q-mt-md" hint="The unit for tolerance, either 'mz' (absolute) or 'ppm' (relative)."></q-select>
|
||||
<q-input standout="custom-standout" label="Max Shift PPM" type="number"
|
||||
:placeholder="suggested_alignment_max_shift_ppm" v-model.number="alignment_max_shift_ppm" class="q-mt-md" hint="The maximum allowed m/z shift in ppm to prevent spurious peak matches."></q-input>
|
||||
<q-input standout="custom-standout" label="Min Matched Peaks" type="number"
|
||||
:placeholder="suggested_alignment_min_matched_peaks" v-model.number="alignment_min_matched_peaks" class="q-mt-md" hint="The minimum number of matching peaks required to perform the alignment."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="standards">
|
||||
<q-toggle v-model="enable_standards" label="Enable Internal Standards for Calibration" color="green" class="q-mb-md" hint="Enables the use of internal standards for mass calibration." />
|
||||
<div class="text-h6">Define Internal Standards / Reference Peaks</div>
|
||||
<p>Provide m/z values and optional labels for internal standards or reference peaks. These are used for mass calibration and alignment.</p>
|
||||
<q-list bordered separator class="q-mt-md">
|
||||
<q-item v-for="(peak, index) in reference_peaks_list" :key="index">
|
||||
<q-item-section avatar>
|
||||
<q-btn flat round icon="delete" color="negative" @click="removeReferencePeak(index)"></q-btn>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="m/z" type="number" step="0.0001"
|
||||
v-model.number="peak.mz" :rules="[val => !!val || 'Required', val => val > 0 || 'Must be positive']" hint="Theoretical m/z value of the internal standard."></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input standout="custom-standout" label="Label (optional)" v-model="peak.label" hint="Optional label for the internal standard."></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-btn class="q-ma-sm btn-style" icon="add" label="Add Reference Peak" @click="addReferencePeak"></q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="calibration">
|
||||
<q-toggle v-model="enable_calibration" label="Enable Mass Calibration" color="green" class="q-mb-md" hint="Enables mass calibration using internal standards." />
|
||||
<p>This step uses the peaks defined in the 'Internal Standards' tab to correct the m/z axis.</p>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Fit Order" type="number"
|
||||
:placeholder="suggested_calibration_fit_order" v-model.number="calibration_fit_order"
|
||||
hint="Polynomial order for the calibration curve (e.g., 1 or 2)."></q-input>
|
||||
<q-input standout="custom-standout" label="PPM Tolerance" type="number"
|
||||
:placeholder="suggested_calibration_ppm_tolerance" v-model.number="calibration_ppm_tolerance" class="q-mt-md"
|
||||
hint="PPM tolerance for matching reference peaks to internal standards."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="peak_picking">
|
||||
<q-toggle v-model="enable_peak_picking" label="Enable Peak Picking" color="green" class="q-mb-md" hint="Identifies peaks (signals of interest) in the profile or centroided spectra." />
|
||||
<p>Select the appropriate peak picking method and set its parameters.</p>
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The peak detection algorithm.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="peak_picking_method" val="profile" label="PROFILE" hint="For profile-mode data, using local maxima and quality filters." /><br>
|
||||
<q-radio v-model="peak_picking_method" val="wavelet" label="WAVELET" hint="Continuous Wavelet Transform (CWT) based peak detection." /><br>
|
||||
<q-radio v-model="peak_picking_method" val="centroid" label="CENTROID" hint="For centroid-mode data, essentially a filtering step." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Signal to Noise Threshold" type="number" step="0.1"
|
||||
:placeholder="suggested_peak_picking_snr_threshold" v-model.number="peak_picking_snr_threshold" hint="Signal-to-Noise Ratio threshold. Peaks with SNR below this value are discarded."></q-input>
|
||||
<q-input standout="custom-standout" label="Half Window Size" type="number" class="q-mt-md"
|
||||
:placeholder="suggested_peak_picking_half_window" v-model.number="peak_picking_half_window" hint="Number of data points to the left and right of a potential peak to consider for local maximum detection (for Profile method)."></q-input>
|
||||
<q-input standout="custom-standout" label="Min Peak Prominence" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_picking_min_peak_prominence" v-model.number="peak_picking_min_peak_prominence" class="q-mt-md" hint="Minimum required prominence of a peak, expressed as a fraction of its height."></q-input>
|
||||
<q-input standout="custom-standout" label="Merge Peaks Tolerance (m/z)" type="number" step="0.001"
|
||||
:placeholder="suggested_peak_picking_merge_peaks_tolerance" v-model.number="peak_picking_merge_peaks_tolerance" class="q-mt-md" hint="The m/z tolerance within which to merge adjacent peaks, keeping the more intense one."></q-input>
|
||||
<q-input standout="custom-standout" label="Min Peak Width (PPM)" type="number"
|
||||
:placeholder="suggested_peak_picking_min_peak_width_ppm" v-model.number="peak_picking_min_peak_width_ppm" class="q-mt-md" hint="Minimum acceptable peak width (FWHM) in ppm."></q-input>
|
||||
<q-input standout="custom-standout" label="Max Peak Width (PPM)" type="number"
|
||||
:placeholder="suggested_peak_picking_max_peak_width_ppm" v-model.number="peak_picking_max_peak_width_ppm" class="q-mt-md" hint="Maximum acceptable peak width (FWHM) in ppm."></q-input>
|
||||
<q-input standout="custom-standout" label="Min Peak Shape R2" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_picking_min_peak_shape_r2" v-model.number="peak_picking_min_peak_shape_r2" class="q-mt-md" hint="Minimum R-squared value from a Gaussian fit to the peak, used as a quality measure for peak shape."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="peak_selection">
|
||||
<q-toggle v-model="enable_peak_selection" label="Enable Peak Selection" color="green" class="q-mb-md" hint="Filters detected peaks based on various quality criteria to remove noise and irrelevant signals." />
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Peak Quality Filters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Min SNR" type="number" step="0.1"
|
||||
:placeholder="suggested_peak_selection_min_snr" v-model.number="peak_selection_min_snr" hint="Minimum Signal-to-Noise Ratio for a peak to be kept."></q-input>
|
||||
<q-input standout="custom-standout" label="Min FWHM (PPM)" type="number"
|
||||
:placeholder="suggested_peak_selection_min_fwhm_ppm" v-model.number="peak_selection_min_fwhm_ppm" class="q-mt-md" hint="Minimum Full Width at Half Maximum (FWHM) in ppm for a peak to be kept."></q-input>
|
||||
<q-input standout="custom-standout" label="Max FWHM (PPM)" type="number"
|
||||
:placeholder="suggested_peak_selection_max_fwhm_ppm" v-model.number="peak_selection_max_fwhm_ppm" class="q-mt-md" hint="Maximum Full Width at Half Maximum (FWHM) in ppm for a peak to be kept."></q-input>
|
||||
<q-input standout="custom-standout" label="Min Peak Shape R2" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_selection_min_shape_r2" v-model.number="peak_selection_min_shape_r2" class="q-mt-md" hint="Minimum R-squared value from a Gaussian fit, filtering for good peak shape."></q-input>
|
||||
<q-input standout="custom-standout" label="Frequency Threshold" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_selection_frequency_threshold" v-model.number="peak_selection_frequency_threshold" class="q-mt-md"
|
||||
hint="The minimum fraction of spectra a peak must be present in to be kept (0.0 to 1.0)."></q-input>
|
||||
<q-input standout="custom-standout" label="Correlation Threshold" type="number" step="0.01"
|
||||
:placeholder="suggested_peak_selection_correlation_threshold" v-model.number="peak_selection_correlation_threshold" class="q-mt-md"
|
||||
hint="Minimum correlation with neighboring peaks (not yet implemented)."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="binning">
|
||||
<q-toggle v-model="enable_binning" label="Enable Peak Binning" color="green" class="q-mb-md" hint="Groups peaks from all spectra into common m/z bins to generate a feature matrix." />
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Method</div>
|
||||
<div class="text-caption">The binning strategy.</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-radio v-model="binning_method" val="adaptive" label="ADAPTIVE" hint="Creates bins based on the density of detected peaks." /><br>
|
||||
<q-radio v-model="binning_method" val="uniform" label="UNIFORM" hint="Creates a fixed number of equally spaced bins over the m/z range." /><br>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Parameters</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input standout="custom-standout" label="Tolerance (for Adaptive)" type="number" step="0.001"
|
||||
:placeholder="suggested_binning_tolerance" v-model.number="binning_tolerance" hint="Tolerance for grouping peaks into a bin in adaptive mode."></q-input>
|
||||
<q-select standout="custom-standout" label="Tolerance Unit" v-model="binning_tolerance_unit"
|
||||
:options="['mz', 'ppm']" class="q-mt-md" hint="The unit for tolerance, either 'mz' (absolute) or 'ppm' (relative)."></q-select>
|
||||
<q-input standout="custom-standout" label="Frequency Threshold" type="number" step="0.01"
|
||||
:placeholder="suggested_binning_frequency_threshold" v-model.number="binning_frequency_threshold" class="q-mt-md" hint="The minimum fraction of spectra a bin must contain a peak in to be kept (0.0 to 1.0)."></q-input>
|
||||
<q-input standout="custom-standout" label="Min Peaks Per Bin" type="number"
|
||||
:placeholder="suggested_binning_min_peak_per_bin" v-model.number="binning_min_peak_per_bin" class="q-mt-md" hint="The minimum number of individual peaks required to form a bin in adaptive mode."></q-input>
|
||||
<q-input standout="custom-standout" label="Max Bin Width (PPM)" type="number"
|
||||
:placeholder="suggested_binning_max_bin_width_ppm" v-model.number="binning_max_bin_width_ppm" class="q-mt-md" hint="Maximum width of a bin in ppm for adaptive mode."></q-input>
|
||||
<q-toggle v-model="binning_intensity_weighted_centers" label="Intensity Weighted Centers"
|
||||
class="q-mt-md" hint="If enabled, calculates bin centers as an intensity-weighted average of the peaks within it."></q-toggle>
|
||||
<q-input standout="custom-standout" label="Number of Uniform Bins" type="number"
|
||||
:placeholder="suggested_binning_num_uniform_bins" v-model.number="binning_num_uniform_bins" class="q-mt-md" hint="The number of bins to create for the uniform method."></q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="generator">
|
||||
<div class="text-h6">imzML & mzML Data Processor</div>
|
||||
<p>Please make sure the ibd and imzML file are located in the same directory and have the same name.
|
||||
<br>It may take a while to generate the slice / spectrum, please be patient.
|
||||
<br>To generate the contour or surface plots, you have to select the desired slice first using the
|
||||
interface.
|
||||
</p>
|
||||
<div class="row items-center">
|
||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer col" v-model="full_route" readonly
|
||||
:label="batch_file_count > 0 ? batch_file_count + ' file(s) in batch' : 'Select an imzMl / mzML file'"
|
||||
v-on:click="btnSearch=true">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" v-on:click="btnSearch=true" class="cursor-pointer" />
|
||||
</template>
|
||||
</q-input>
|
||||
<q-btn class="q-ma-sm" icon="add" v-on:click="btnAddBatch=true" label="Add"></q-btn>
|
||||
<q-btn class="q-ma-sm" icon="clear" v-on:click="clear_batch_btn=true" :disable="batch_file_count === 0"
|
||||
label="Clear"></q-btn>
|
||||
</div>
|
||||
<q-list bordered separator v-if="selected_files.length > 0">
|
||||
<q-item v-for="(file, index) in selected_files" :key="index">
|
||||
<q-item-section>
|
||||
{{ file }}
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn flat round icon="delete" size="sm" v-on:click="selected_files.splice(index, 1)"></q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<!-- Variable Manipulation -->
|
||||
<div class="row">
|
||||
<div class="st-col col-4 col-sm q-ma-sm">
|
||||
<q-input standout="custom-standout" id="textNmass" v-model="Nmass"
|
||||
label="Mass-to-charge ratio(s) of interest" type="text" :rules="[
|
||||
val => !!val || '* Required',
|
||||
val => val.split(',').every(m => !isNaN(parseFloat(m.trim())) && parseFloat(m.trim()) > 0) || 'Need comma-separated positive numbers'
|
||||
]">
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="st-col col-4 col-sm q-ma-sm">
|
||||
<q-input standout="custom-standout" id="textTol" step="0.005" v-model="Tol"
|
||||
label="Mass-to-charge ratio tolerance" type="number"
|
||||
:rules="[val => !!val || '* Required', val => val >= 0.0 && val <= 1.0 || 'Needs to be in range between 0 and 1']"></q-input>
|
||||
</div>
|
||||
<div class="st-col col-4 col-sm q-ma-sm">
|
||||
<q-input standout="custom-standout" id="textcolorLevel" step="1" v-model="colorLevel" label="Color levels"
|
||||
type="number"
|
||||
:rules="[ val => !!val || '* Required', val => val >= 2 && val <= 256 || 'Needs to be in range between 2 and 256']"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- Triq Variable Manipulation and filters-->
|
||||
<div class="col-6">
|
||||
<div class="st-col col-6 col-sm q-ma-sm">
|
||||
<q-toggle id="btnEnableMFilter" v-on:click="MFilterEnabled" v-model="MFilterEnabled" color="green"
|
||||
label="Add Median Filter"></q-toggle>
|
||||
<q-toggle id="btnEnableTriq" v-on:click="triqEnabled" v-model="triqEnabled" color="blue"
|
||||
label="Add Threshold Intensity Quantization (TrIQ)"></q-toggle>
|
||||
<q-toggle id="btnEnableMask" v-on:click="maskEnabled" v-model="maskEnabled" color="black"
|
||||
label="Use Mask To Filter Data"></q-toggle>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||
<q-input standout="custom-standout" id="textTriqProb" step="0.01" v-model="triqProb"
|
||||
label="TrIQ probability" type="number" :rules="[
|
||||
val => triqEnabled ? ( '* Required', val >= 0.8 && val <= 1 || 'Needs to be in range between 0.8 and 1') : true
|
||||
]" :readonly="!triqEnabled" :disable="!triqEnabled"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Spectra Plot Manipulation -->
|
||||
<div class="col-6">
|
||||
<div class="st-col col-6 col-sm">
|
||||
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
|
||||
label="Generate Spectra" icon="play_arrow">
|
||||
<template v-slot:loading>
|
||||
<q-spinner-hourglass class="on-left" />
|
||||
Loading plot
|
||||
</template>
|
||||
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup v-on:click="createMeanPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Mean spectrum plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="createSumPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Sum Spectrum plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="createXYPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Spectrum plot (X,Y)</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
<div class="row col-6">
|
||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="xCoord" label="X coord" type="number" :rules="[
|
||||
val => SpectraEnabled ? ( '* Required', val >= 0|| 'Needs to be bigger than 0') : true
|
||||
]"></q-input>
|
||||
</div>
|
||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||
<q-input standout="custom-standout" step="1" v-model="yCoord" label="Y coord" type="number" :rules="[
|
||||
val => SpectraEnabled ? ( '* Required', val <= 0|| 'Needs to be lower than 0') : true
|
||||
]"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<q-btn :loading="progress" class="q-ma-sm btn-style" :disabled="btnStartDisable" icon="play_arrow"
|
||||
v-on:click="mainProcess=true" padding="lg" label="Generate Slice">
|
||||
<template v-slot:loading>
|
||||
<q-spinner-hourglass class="on-left" />
|
||||
Loading...
|
||||
</template>
|
||||
</q-btn>
|
||||
<q-btn icon="zoom_out_map" class="q-ma-sm on-right btn-style" v-on:click="compareBtn=true" padding="sm"
|
||||
label="Compare"></q-btn>
|
||||
<q-btn class="q-ma-sm btn-style" icon="edit" label="Mask Editor" href="/mask"></q-btn>
|
||||
<q-btn class="q-ma-sm btn-style" icon="dashboard" v-on:click="showMetadataBtn=true"
|
||||
label="Show Metadata"></q-btn>
|
||||
<div class="q-pa-md row items-center" v-show="progress">
|
||||
<q-spinner color="primary" size="2em" class="q-mr-sm"></q-spinner>
|
||||
<div class="text-caption">{{ progress_message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{msg}}</p>
|
||||
<div class="row st-col col-12">
|
||||
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressPlot" :disable="btnPlotDisable"
|
||||
label="Generate Plots" icon="play_arrow">
|
||||
<template v-slot:loading>
|
||||
<q-spinner-hourglass class="on-left" />
|
||||
Loading plot
|
||||
</template>
|
||||
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup v-on:click="imageCPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Image topography Plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup v-on:click="triqCPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>TrIQ topography Plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup v-on:click="image3dPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Image surface Plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup v-on:click="triq3dPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>TrIQ Surface Plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<q-btn-dropdown icon="search" class="q-ma-sm btn-style" :disable="btnOpticalDisable"
|
||||
label="Load your optical image">
|
||||
<q-item clickable v-close-popup v-on:click="btnOptical=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Over normal image</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="btnOpticalT=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Over TrIQ image</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-btn-dropdown>
|
||||
<div class="q-mx-sm">
|
||||
<q-slider color="black" v-model="imgTrans" :min="0.0" :max="1" :step="0.1" :disable="btnOpticalDisable" />
|
||||
<q-badge style="background-color: #009f90;"> Transparency: {{ imgTrans }}</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="converter">
|
||||
<div class="text-h6">mzML to imzML Converter</div>
|
||||
<p>Select the .mzML file and the corresponding .txt synchronization file to convert them into an .imzML/.ibd
|
||||
pair.</p>
|
||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="mzml_full_route" readonly
|
||||
label="Select your .mzML file" v-on:click="btnSearchMzml=true">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" v-on:click="btnSearchMzml=true" class="cursor-pointer" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="sync_full_route" readonly
|
||||
label="Select your .txt sync file" v-on:click="btnSearchSync=true">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" v-on:click="btnSearchSync=true" class="cursor-pointer" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-btn :loading="progress_conversion" class="q-ma-sm btn-style" :disabled="btnConvertDisable"
|
||||
icon="swap_horiz" v-on:click="convert_process=true" padding="lg" label="Convert File">
|
||||
<template v-slot:loading>
|
||||
<q-spinner-hourglass class="on-left" />
|
||||
Converting...
|
||||
</template>
|
||||
</q-btn>
|
||||
<p>{{msg_conversion}}</p>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row col-6">
|
||||
<!-- Right DIV -->
|
||||
<div id="intDivStyle-right" class="st-col col-12 col-sm st-module">
|
||||
<div v-if="left_tab === 'pre_treatment'">
|
||||
<div class="text-h6 q-mb-md">Spectrum View</div>
|
||||
<q-card class="q-mb-md">
|
||||
<q-card-section>
|
||||
<div class="text-subtitle1">Before Preprocessing</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<plotly id="plotSpectraBefore" :data="plotdata_before" :layout="plotlayout_before" class="q-pa-none q-ma-none"></plotly>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-subtitle1">After Preprocessing</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<plotly id="plotSpectraAfter" :data="plotdata_after" :layout="plotlayout_after" class="q-pa-none q-ma-none">
|
||||
</plotly>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<div v-else>
|
||||
<st-tabs id="tabHeader-main" :ids="tabIDs" :labels="tabLabels" v-model="selectedTab" no-arrows></st-tabs>
|
||||
<q-tab-panels v-model="selectedTab">
|
||||
<q-tab-panel name="tab0">
|
||||
<!-- Content for Tab 0 -->
|
||||
<h6>Image visualizer</h6>
|
||||
<div class="row items-center">
|
||||
<q-select v-model="selected_folder_main" :options="image_available_folders" label="Select Dataset"
|
||||
class="q-ma-sm" style="min-width: 200px;" v-on:focus="refetch_folders = true"></q-select>
|
||||
<q-space></q-space>
|
||||
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinus=true"></q-btn>
|
||||
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style" v-on:click="imgPlus=true"></q-btn>
|
||||
</div>
|
||||
<!-- Image manager -->
|
||||
<div id="image-container-normal" class="row st-col col-12">
|
||||
<div class="col-10 q-pa-none q-ma-none">
|
||||
<plotly id="plotImg" :data="plotdataImg" :layout="plotlayoutImg" class="q-pa-none q-ma-none sync_data"
|
||||
@click="data_click"></plotly>
|
||||
</div>
|
||||
<div class="col-2 q-pa-none q-ma-none">
|
||||
<q-img id="colorbar-normal" class="q-ma-none q-pa-none" :src="colorbar"></q-img>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="msgimg"></p>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="tab1">
|
||||
<!-- Content for Tab 1 -->
|
||||
<h6>TrIQ visualizer</h6>
|
||||
<div class="row items-center">
|
||||
<q-select v-model="selected_folder_main" :options="image_available_folders" label="Select Dataset"
|
||||
class="q-ma-sm" style="min-width: 200px;" v-on:focus="refetch_folders = true"></q-select>
|
||||
<q-space></q-space>
|
||||
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinusT=true"></q-btn>
|
||||
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style" v-on:click="imgPlusT=true"></q-btn>
|
||||
</div>
|
||||
<!-- Triq Image manager -->
|
||||
<div id="image-container-triq" class="row st-col col-12">
|
||||
<div class="col-10 q-pa-none q-ma-none ">
|
||||
<plotly id="plotImgT" :data="plotdataImgT" :layout="plotlayoutImgT"
|
||||
class="q-pa-none q-ma-none sync_data" @click="data_click"></plotly>
|
||||
</div>
|
||||
<div class="col-2 q-pa-none q-ma-none ">
|
||||
<q-img id="colorbar-triq" class="q-ma-none q-pa-none" :src="colorbarT"></q-img>
|
||||
</div>
|
||||
</div>
|
||||
<p v-html="msgtriq"></p>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="tab2">
|
||||
<div class="row items-center">
|
||||
<q-select v-model="selected_folder_main" :options="available_folders" label="Select Dataset"
|
||||
class="q-ma-sm" style="min-width: 200px;" v-on:focus="refetch_folders = true"></q-select>
|
||||
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
|
||||
label="Generate Spectra" icon="play_arrow">
|
||||
<template v-slot:loading>
|
||||
<q-spinner-hourglass class="on-left" />
|
||||
Loading plot
|
||||
</template>
|
||||
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup v-on:click="createMeanPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Mean spectrum plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="createSumPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Sum Spectrum plot</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup v-on:click="createXYPlot=true">
|
||||
<q-item-section>
|
||||
<q-item-label>Spectrum plot (X,Y)</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
<plotly id="plotSpectra" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="tab3">
|
||||
<!-- Content for Tab 3 -->
|
||||
<plotly id="plotTopo" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="tab4">
|
||||
<!-- Content for Tab 4 -->
|
||||
<plotly id="plot3d" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1524
app_snippet.jl
Normal file
1524
app_snippet.jl
Normal file
File diff suppressed because it is too large
Load Diff
@ -926,6 +926,27 @@ function update_registry(registry_path, dataset_name, source_path, metadata=noth
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
save_registry(registry_path, registry_data)
|
||||
|
||||
Saves the dataset registry to a JSON file, ensuring thread-safe access.
|
||||
|
||||
# Arguments
|
||||
- `registry_path`: Path to the `registry.json` file.
|
||||
- `registry_data`: The dictionary containing the registry data to save.
|
||||
"""
|
||||
function save_registry(registry_path, registry_data)
|
||||
lock(REGISTRY_LOCK) do
|
||||
try
|
||||
open(registry_path, "w") do f
|
||||
JSON.print(f, registry_data, 4)
|
||||
end
|
||||
catch e
|
||||
@error "Failed to write to registry.json: $e"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
process_file_safely(file_path, masses, params, progress_message_ref, overall_progress_ref)
|
||||
|
||||
|
||||
@ -41,6 +41,38 @@
|
||||
color: rgb(0, 0, 0) !important;
|
||||
}
|
||||
|
||||
/* Placeholder color for custom-standout q-inputs */
|
||||
.custom-standout .q-field__native::placeholder {
|
||||
color: #CFD8DC !important; /* Lighter grey for placeholder text */
|
||||
opacity: 1 !important; /* Ensure full visibility */
|
||||
}
|
||||
|
||||
.custom-standout .q-field__control::before {
|
||||
border-color: #7f8389 !important; /* Keep the original border color */
|
||||
}
|
||||
|
||||
/* Placeholder color for various browsers */
|
||||
.custom-standout input::placeholder {
|
||||
color: #CFD8DC !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.custom-standout input::-webkit-input-placeholder { /* WebKit, Blink, Edge */
|
||||
color: #CFD8DC !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.custom-standout input::-moz-placeholder { /* Mozilla Firefox 19+ */
|
||||
color: #CFD8DC !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.custom-standout input:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: #CFD8DC !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.custom-standout input::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: #CFD8DC !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
#tabHeader {
|
||||
color: #009f90;
|
||||
}
|
||||
@ -82,10 +114,6 @@
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
#intDivStyle-left .q-tab-panels, #intDivStyle-right .q-tab-panels {
|
||||
/* Removed fixed height */
|
||||
}
|
||||
|
||||
#intDivStyle-left .q-tab-panel, #intDivStyle-right .q-tab-panel {
|
||||
height: auto; /* Let content define height */
|
||||
overflow-y: hidden; /* Remove scrollbar */
|
||||
@ -118,3 +146,24 @@
|
||||
.q-option-group > div {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Loading Overlay Styles */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
text-align: center;
|
||||
}
|
||||
.loading-content .q-spinner {
|
||||
color: white;
|
||||
}
|
||||
.loading-content .text-h6 {
|
||||
color: white;
|
||||
}
|
||||
@ -30,19 +30,31 @@ export run_preprocessing_analysis,
|
||||
BaselineCorrection,
|
||||
Normalization,
|
||||
PeakPicking,
|
||||
PeakBinningParams,
|
||||
PeakBinning,
|
||||
get_masked_spectrum_indices,
|
||||
detect_peaks_profile,
|
||||
detect_peaks_centroid,
|
||||
smooth_spectrum,
|
||||
apply_baseline_correction,
|
||||
apply_normalization,
|
||||
bin_peaks,
|
||||
detect_peaks_profile_core,
|
||||
detect_peaks_centroid_core,
|
||||
smooth_spectrum_core,
|
||||
apply_baseline_correction_core,
|
||||
apply_normalization_core,
|
||||
bin_peaks_core,
|
||||
PeakSelection,
|
||||
PeakAlignment,
|
||||
find_calibration_peaks,
|
||||
align_peaks_lowess,
|
||||
MutableSpectrum
|
||||
find_calibration_peaks_core,
|
||||
align_peaks_lowess_core,
|
||||
MutableSpectrum,
|
||||
transform_intensity_core,
|
||||
calibrate_spectra_core
|
||||
|
||||
export apply_baseline_correction,
|
||||
apply_smoothing,
|
||||
apply_peak_picking,
|
||||
apply_calibration,
|
||||
apply_peak_alignment,
|
||||
apply_normalization,
|
||||
apply_peak_binning,
|
||||
apply_intensity_transformation,
|
||||
save_feature_matrix
|
||||
|
||||
# Include all source files directly into the main module
|
||||
include("BloomFilters.jl")
|
||||
@ -55,6 +67,7 @@ include("MzmlConverter.jl")
|
||||
include("Preprocessing.jl")
|
||||
include("ImageProcessing.jl")
|
||||
include("Precalculations.jl")
|
||||
include("PreprocessingPipeline.jl")
|
||||
|
||||
using Setfield # For immutable struct updates
|
||||
|
||||
|
||||
@ -5,10 +5,21 @@ using StatsBase # For mean, std, median, quantile, mad
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
find_ppm_error_by_region(msi_data, region_masks, reference_peaks)::Dict
|
||||
find_ppm_error_by_region(msi_data::MSIData, region_masks::Dict, reference_peaks::Dict) -> Dict
|
||||
|
||||
Calculates PPM error statistics for different spatial regions.
|
||||
`region_masks` is a Dict mapping region names (e.g., :tumor) to BitMatrix masks.
|
||||
Calculates and reports mass accuracy (PPM error) statistics for different spatial regions
|
||||
defined by masks. This is useful for identifying spatial variations in calibration.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `region_masks::Dict{Symbol, BitMatrix}`: A dictionary mapping region names (e.g., `:tumor`, `:stroma`)
|
||||
to `BitMatrix` masks. The dimensions of each mask must match `msi_data.image_dims`.
|
||||
- `reference_peaks::Dict{Float64, String}`: A dictionary of known reference peaks, mapping
|
||||
theoretical m/z to a name.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, NamedTuple}`: A dictionary where keys are region names and values are `NamedTuple`s
|
||||
containing the mass accuracy report for that region, as generated by `analyze_mass_accuracy`.
|
||||
"""
|
||||
function find_ppm_error_by_region(msi_data::MSIData, region_masks::Dict, reference_peaks::Dict)
|
||||
regional_reports = Dict{Symbol, NamedTuple}()
|
||||
@ -37,6 +48,28 @@ function find_ppm_error_by_region(msi_data::MSIData, region_masks::Dict, referen
|
||||
return regional_reports
|
||||
end
|
||||
|
||||
"""
|
||||
analyze_mass_accuracy(msi_data, reference_peaks; ...) -> NamedTuple
|
||||
|
||||
Analyzes the mass accuracy for a given subset of spectra by comparing detected peaks
|
||||
against a list of known reference masses.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `reference_peaks::Dict{Float64, String}`: A dictionary of known reference peaks, mapping
|
||||
theoretical m/z to a name.
|
||||
- `spectrum_indices::AbstractVector{Int}`: A vector of indices for the spectra to be analyzed.
|
||||
- `peak_detection_snr_threshold::Float64`: The Signal-to-Noise ratio threshold to use for
|
||||
detecting peaks within the spectra.
|
||||
- `ppm_tolerance_for_matching::Float64`: The tolerance in Parts Per Million (PPM) used to match
|
||||
a detected peak to a reference peak.
|
||||
|
||||
# Returns
|
||||
- `NamedTuple`: A report containing summary statistics of the PPM errors found, including:
|
||||
- `mean_ppm_error`, `median_ppm_error`, `std_ppm_error`, `min_ppm_error`, `max_ppm_error`
|
||||
- `total_matched_peaks`: The total count of successful matches between detected and reference peaks.
|
||||
- `total_spectra_analyzed`: The number of spectra processed.
|
||||
"""
|
||||
function analyze_mass_accuracy(
|
||||
msi_data::MSIData,
|
||||
reference_peaks::Dict{Float64, String}; # m/z => name
|
||||
@ -57,7 +90,7 @@ function analyze_mass_accuracy(
|
||||
return
|
||||
end
|
||||
|
||||
detected_peaks = detect_peaks_profile(mz, intensity; snr_threshold=peak_detection_snr_threshold)
|
||||
detected_peaks = detect_peaks_profile_core(mz, intensity; snr_threshold=peak_detection_snr_threshold)
|
||||
|
||||
for ref_mz in keys(reference_peaks)
|
||||
# Find the closest detected peak to this reference m/z within tolerance
|
||||
@ -88,7 +121,8 @@ function analyze_mass_accuracy(
|
||||
min_ppm_error = NaN,
|
||||
max_ppm_error = NaN,
|
||||
total_matched_peaks = 0,
|
||||
total_spectra_analyzed = total_spectra_processed
|
||||
total_spectra_analyzed = total_spectra_processed,
|
||||
ppm_error_distribution = Float64[]
|
||||
)
|
||||
end
|
||||
|
||||
@ -107,7 +141,8 @@ function analyze_mass_accuracy(
|
||||
min_ppm_error = min_err,
|
||||
max_ppm_error = max_err,
|
||||
total_matched_peaks = total_matched_peaks,
|
||||
total_spectra_analyzed = total_spectra_processed
|
||||
total_spectra_analyzed = total_spectra_processed,
|
||||
ppm_error_distribution = all_ppm_errors
|
||||
)
|
||||
end
|
||||
|
||||
@ -118,7 +153,16 @@ end
|
||||
"""
|
||||
calculate_adaptive_bin_tolerance(ppm_error_distribution) -> Float64
|
||||
|
||||
Calculates an appropriate binning tolerance based on observed mass accuracy.
|
||||
Calculates an appropriate binning tolerance in PPM based on the observed mass accuracy
|
||||
distribution. The strategy is to set the tolerance to capture the vast majority of
|
||||
peaks from the same analyte, typically using `mean + 3 * standard_deviation`.
|
||||
|
||||
# Arguments
|
||||
- `ppm_error_distribution::Vector{Float64}`: A vector of PPM error values from a mass
|
||||
accuracy analysis.
|
||||
|
||||
# Returns
|
||||
- `Float64`: The suggested binning tolerance in PPM, capped between 10.0 and 100.0.
|
||||
"""
|
||||
function calculate_adaptive_bin_tolerance(ppm_error_distribution::Vector{Float64})
|
||||
if isempty(ppm_error_distribution)
|
||||
@ -148,10 +192,20 @@ function calculate_adaptive_bin_tolerance(ppm_error_distribution::Vector{Float64
|
||||
end
|
||||
|
||||
"""
|
||||
calculate_preprocessing_hints(data::MSIData; sample_size::Int=100)::Dict{Symbol, Any}
|
||||
calculate_preprocessing_hints(data::MSIData; sample_indices)::Dict{Symbol, Any}
|
||||
|
||||
Analyzes a sample of spectra to determine optimal default parameters for
|
||||
preprocessing steps, returning a dictionary of hints.
|
||||
Analyzes a sample of spectra to determine initial "hints" for preprocessing parameters.
|
||||
This function provides quick, data-driven defaults for noise level, SNR, and smoothing.
|
||||
|
||||
# Arguments
|
||||
- `data::MSIData`: The main MSI data object.
|
||||
- `sample_indices::AbstractVector{Int}`: The indices of spectra to sample for the analysis.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary of hints, including:
|
||||
- `:estimated_noise`: The mean noise level estimated using Median Absolute Deviation (MAD).
|
||||
- `:suggested_snr`: A default SNR threshold (typically 3.0).
|
||||
- `:suggested_smoothing_window`: A suggested window size for smoothing, based on instrument resolution if available.
|
||||
"""
|
||||
function calculate_preprocessing_hints(data::MSIData; sample_indices::AbstractVector{Int})::Dict{Symbol, Any}
|
||||
println("Calculating preprocessing hints from a sample of $(length(sample_indices)) spectra...")
|
||||
@ -218,9 +272,22 @@ function calculate_preprocessing_hints(data::MSIData; sample_indices::AbstractVe
|
||||
end
|
||||
|
||||
"""
|
||||
analyze_instrument_characteristics(msi_data::MSIData)::Dict
|
||||
analyze_instrument_characteristics(msi_data::MSIData; sample_indices)::Dict
|
||||
|
||||
Analyzes instrument metadata and data characteristics to determine acquisition properties.
|
||||
Analyzes instrument metadata and spectral data to infer key acquisition properties.
|
||||
It combines information from the `msi_data.instrument_metadata` with direct analysis
|
||||
of the spectra.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `sample_indices::AbstractVector{Int}`: The indices of spectra to sample for the analysis.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary summarizing instrument characteristics:
|
||||
- `:acquisition_mode`: Inferred as `:profile`, `:centroid`, or `:mixed`.
|
||||
- `:mz_axis_type`: Inferred as `:regular` or `:irregular` based on m/z step consistency.
|
||||
- `:dynamic_range`: An estimate of the intensity dynamic range in orders of magnitude.
|
||||
- Other fields from `instrument_metadata` like `:resolution`, `:polarity`, etc.
|
||||
"""
|
||||
function analyze_instrument_characteristics(msi_data::MSIData; sample_indices::AbstractVector{Int})::Dict
|
||||
results = Dict{Symbol, Any}()
|
||||
@ -337,9 +404,20 @@ function analyze_instrument_characteristics(msi_data::MSIData; sample_indices::A
|
||||
end
|
||||
|
||||
"""
|
||||
analyze_signal_quality(msi_data::MSIData; sample_size::Int=100)::Dict
|
||||
analyze_signal_quality(msi_data::MSIData; sample_indices)::Dict
|
||||
|
||||
Analyzes noise characteristics, signal-to-noise ratios, and overall signal quality.
|
||||
Analyzes a sample of spectra to assess signal quality, including noise levels,
|
||||
Signal-to-Noise Ratio (SNR), and Total Ion Current (TIC) variation.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `sample_indices::AbstractVector{Int}`: The indices of spectra to sample for the analysis.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary of signal quality metrics:
|
||||
- `:noise_mean`, `:noise_std`, `:noise_cv`: Statistics of the noise level.
|
||||
- `:snr_mean`, `:snr_median`, `:snr_95th`: Distribution statistics of the estimated SNR.
|
||||
- `:tic_mean`, `:tic_std`, `:tic_cv`: Statistics of the Total Ion Current.
|
||||
"""
|
||||
function analyze_signal_quality(msi_data::MSIData; sample_indices::AbstractVector{Int})::Dict
|
||||
results = Dict{Symbol, Any}()
|
||||
@ -415,9 +493,21 @@ function analyze_signal_quality(msi_data::MSIData; sample_indices::AbstractVecto
|
||||
end
|
||||
|
||||
"""
|
||||
analyze_mass_accuracy_global(msi_data::MSIData, reference_peaks::Dict, sample_size::Int)::Dict
|
||||
analyze_mass_accuracy_global(msi_data, reference_peaks; spectrum_indices)::Dict
|
||||
|
||||
Analyzes mass accuracy across the dataset using reference peaks.
|
||||
Performs a global mass accuracy analysis across a sample of spectra and suggests an
|
||||
adaptive binning tolerance.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `reference_peaks::Dict`: A dictionary of known reference peaks.
|
||||
- `spectrum_indices::AbstractVector{Int}`: The indices of spectra to sample for the analysis.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary containing:
|
||||
- `:global_accuracy`: The `NamedTuple` report from `analyze_mass_accuracy`.
|
||||
- `:suggested_bin_tolerance`: An adaptive tolerance in PPM for peak binning, derived
|
||||
from the mass accuracy results.
|
||||
"""
|
||||
function analyze_mass_accuracy_global(msi_data::MSIData, reference_peaks::Dict;
|
||||
spectrum_indices::AbstractVector{Int})::Dict
|
||||
@ -431,7 +521,7 @@ function analyze_mass_accuracy_global(msi_data::MSIData, reference_peaks::Dict;
|
||||
empty_report = (
|
||||
mean_ppm_error = NaN, median_ppm_error = NaN, std_ppm_error = NaN,
|
||||
min_ppm_error = NaN, max_ppm_error = NaN, total_matched_peaks = 0,
|
||||
total_spectra_analyzed = 0
|
||||
total_spectra_analyzed = 0, ppm_error_distribution = Float64[]
|
||||
)
|
||||
results[:global_accuracy] = empty_report
|
||||
results[:suggested_bin_tolerance] = 20.0 # Default
|
||||
@ -444,7 +534,7 @@ function analyze_mass_accuracy_global(msi_data::MSIData, reference_peaks::Dict;
|
||||
|
||||
results[:global_accuracy] = accuracy_report
|
||||
results[:suggested_bin_tolerance] = calculate_adaptive_bin_tolerance(
|
||||
collect(Iterators.flatten([accuracy_report.mean_ppm_error])) # Simplified - would need actual distribution
|
||||
accuracy_report.ppm_error_distribution
|
||||
)
|
||||
|
||||
println(" - Mean PPM error: $(round(accuracy_report.mean_ppm_error, digits=2))")
|
||||
@ -454,9 +544,21 @@ function analyze_mass_accuracy_global(msi_data::MSIData, reference_peaks::Dict;
|
||||
end
|
||||
|
||||
"""
|
||||
analyze_spatial_regions(msi_data::MSIData, region_masks::Dict, reference_peaks::Dict)::Dict
|
||||
analyze_spatial_regions(msi_data, region_masks, reference_peaks)::Dict
|
||||
|
||||
Analyzes different spatial regions for variations in signal quality and mass accuracy.
|
||||
Analyzes different spatial regions for variations in mass accuracy. This function is a
|
||||
wrapper around `find_ppm_error_by_region` and summarizes the results.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `region_masks::Dict`: A dictionary of named `BitMatrix` masks for each region.
|
||||
- `reference_peaks::Dict`: A dictionary of known reference peaks.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary containing:
|
||||
- `:regional_ppm_errors`: A dictionary mapping each region name to its mass accuracy report.
|
||||
- `:max_regional_ppm_difference`: The difference between the highest and lowest mean PPM
|
||||
error across all analyzed regions.
|
||||
"""
|
||||
function analyze_spatial_regions(msi_data::MSIData, region_masks::Dict, reference_peaks::Dict)::Dict
|
||||
results = Dict{Symbol, Any}()
|
||||
@ -484,9 +586,24 @@ function analyze_spatial_regions(msi_data::MSIData, region_masks::Dict, referenc
|
||||
end
|
||||
|
||||
"""
|
||||
analyze_peak_characteristics(msi_data::MSIData, sample_size::Int)::Dict
|
||||
analyze_peak_characteristics(msi_data, instrument_analysis, mass_accuracy_results; spectrum_indices)::Dict
|
||||
|
||||
Analyzes peak shape, width, and quality characteristics.
|
||||
Analyzes peak shape, width, and quality from a sample of spectra. The behavior
|
||||
adapts based on whether the data is in `:profile` or `:centroid` mode.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `instrument_analysis::Dict`: The output from `analyze_instrument_characteristics`.
|
||||
- `mass_accuracy_results`: The output from `analyze_mass_accuracy_global`.
|
||||
- `spectrum_indices::AbstractVector{Int}`: The indices of spectra to sample.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary of peak metrics:
|
||||
- `:mean_fwhm_ppm`, `:median_fwhm_ppm`: Statistics of Full Width at Half Maximum (FWHM).
|
||||
For centroid data, this is estimated from mass accuracy.
|
||||
- `:mean_gaussian_r2`: The average goodness-of-fit to a Gaussian shape (profile data only).
|
||||
- `:peak_resolution_estimate`: An estimate of instrument resolution based on FWHM.
|
||||
- `:mean_peaks_per_spectrum`: The average number of peaks detected per spectrum.
|
||||
"""
|
||||
function analyze_peak_characteristics(msi_data::MSIData, instrument_analysis::Dict, mass_accuracy_results;
|
||||
spectrum_indices::AbstractVector{Int})::Dict
|
||||
@ -508,12 +625,21 @@ function analyze_peak_characteristics(msi_data::MSIData, instrument_analysis::Di
|
||||
if isempty(spectrum_indices)
|
||||
@warn "No indices to sample for peak characteristics analysis."
|
||||
# Provide sensible defaults if no analysis can be run
|
||||
if acquisition_mode == :centroid
|
||||
# Much more permissive defaults for centroid data
|
||||
results[:mean_fwhm_ppm] = 50.0
|
||||
results[:median_fwhm_ppm] = 50.0
|
||||
results[:mean_gaussian_r2] = 0.0 # Disable shape filtering for centroids
|
||||
results[:peak_resolution_estimate] = 20000.0
|
||||
results[:mean_peaks_per_spectrum] = 1000
|
||||
else
|
||||
estimated_fwhm = estimated_mean_ppm_error * (acquisition_mode == :profile ? 3 : 2)
|
||||
results[:mean_fwhm_ppm] = estimated_fwhm
|
||||
results[:median_fwhm_ppm] = estimated_fwhm
|
||||
results[:mean_gaussian_r2] = acquisition_mode == :profile ? 0.7 : 0.9
|
||||
results[:peak_resolution_estimate] = 1e6 / estimated_fwhm
|
||||
results[:mean_peaks_per_spectrum] = 0
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
@ -537,7 +663,7 @@ function analyze_peak_characteristics(msi_data::MSIData, instrument_analysis::Di
|
||||
meta = msi_data.spectra_metadata[idx]
|
||||
|
||||
# Detect peaks with lower SNR threshold to find more peaks
|
||||
peaks = detect_peaks_profile(mz, intensity; snr_threshold=2.0)
|
||||
peaks = detect_peaks_profile_core(mz, intensity; snr_threshold=2.0)
|
||||
push!(peak_counts, length(peaks))
|
||||
|
||||
if !isempty(peaks)
|
||||
@ -600,12 +726,10 @@ function analyze_peak_characteristics(msi_data::MSIData, instrument_analysis::Di
|
||||
println(" Analyzing peak characteristics for CENTROID mode from $(length(spectrum_indices)) sample spectra...")
|
||||
|
||||
peak_counts = Int[]
|
||||
# peak_intensities = Float64[] # Not strictly needed for these metrics
|
||||
|
||||
_iterate_spectra_fast(msi_data, spectrum_indices) do idx, mz, intensity
|
||||
if !isempty(mz)
|
||||
push!(peak_counts, length(mz))
|
||||
# append!(peak_intensities, intensity) # If needed for other metrics
|
||||
end
|
||||
end
|
||||
|
||||
@ -619,16 +743,16 @@ function analyze_peak_characteristics(msi_data::MSIData, instrument_analysis::Di
|
||||
results[:total_peaks_detected] = 0
|
||||
end
|
||||
|
||||
# For centroid data, FWHM is estimated from mass accuracy
|
||||
estimated_fwhm = estimated_mean_ppm_error * 2 # Centroid peaks are narrower, factor of 2-3 is common
|
||||
results[:mean_fwhm_ppm] = estimated_fwhm
|
||||
results[:median_fwhm_ppm] = estimated_fwhm # Defaulting median to mean
|
||||
results[:mean_gaussian_r2] = 0.9 # Default for centroid, assuming good peak shapes
|
||||
results[:peak_resolution_estimate] = 1e6 / estimated_fwhm
|
||||
# For centroid data, use much more permissive parameters
|
||||
# Don't estimate FWHM from mass accuracy - use reasonable defaults
|
||||
results[:mean_fwhm_ppm] = 50.0 # Reasonable default for centroid data
|
||||
results[:median_fwhm_ppm] = 50.0
|
||||
results[:mean_gaussian_r2] = 0.0 # Disable shape filtering for centroids
|
||||
results[:peak_resolution_estimate] = 20000.0 # Reasonable estimate
|
||||
|
||||
println(" - Mean peaks per spectrum: $(round(results[:mean_peaks_per_spectrum], digits=1))")
|
||||
println(" - Estimated peak width: $(round(estimated_fwhm, digits=2)) ppm")
|
||||
println(" - Estimated resolution: $(round(results[:peak_resolution_estimate], digits=0))")
|
||||
println(" - Using permissive FWHM for centroid data: 50.0 ppm")
|
||||
println(" - Shape filtering disabled for centroid data")
|
||||
end
|
||||
|
||||
# Common prints
|
||||
@ -641,7 +765,16 @@ end
|
||||
"""
|
||||
generate_preprocessing_recommendations(analysis_results::Dict)::Dict{Symbol, Any}
|
||||
|
||||
Generates intelligent preprocessing recommendations based on the analysis results.
|
||||
Generates intelligent preprocessing recommendations by synthesizing the results from
|
||||
various analysis functions (`analyze_instrument_characteristics`, `analyze_signal_quality`, etc.).
|
||||
|
||||
# Arguments
|
||||
- `analysis_results::Dict`: A dictionary containing the comprehensive analysis results from
|
||||
the `run_preprocessing_analysis` pipeline.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A dictionary where keys are preprocessing step names (e.g., `:smoothing`,
|
||||
`:peak_picking`) and values are dictionaries of recommended parameters for that step.
|
||||
"""
|
||||
function generate_preprocessing_recommendations(analysis_results::Dict)::Dict{Symbol, Any}
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
@ -651,14 +784,17 @@ function generate_preprocessing_recommendations(analysis_results::Dict)::Dict{Sy
|
||||
mass_accuracy = get(analysis_results, :mass_accuracy, Dict())
|
||||
peak_analysis = get(analysis_results, :peak_analysis, Dict())
|
||||
|
||||
# Stabilization Recommendations
|
||||
recommendations[:stabilization] = generate_stabilization_recommendations(signal_analysis)
|
||||
|
||||
# Baseline Correction Recommendations
|
||||
recommendations[:baseline_correction] = generate_baseline_recommendations(inst_analysis, signal_analysis)
|
||||
|
||||
# Smoothing Recommendations
|
||||
recommendations[:smoothing] = generate_smoothing_recommendations(inst_analysis, peak_analysis)
|
||||
|
||||
# Peak Picking Recommendations
|
||||
recommendations[:peak_picking] = generate_peak_picking_recommendations(signal_analysis, peak_analysis)
|
||||
# Peak Picking Recommendations - pass instrument analysis
|
||||
recommendations[:peak_picking] = generate_peak_picking_recommendations(signal_analysis, peak_analysis, inst_analysis)
|
||||
|
||||
# Normalization Recommendations
|
||||
recommendations[:normalization] = generate_normalization_recommendations(signal_analysis)
|
||||
@ -672,6 +808,7 @@ function generate_preprocessing_recommendations(analysis_results::Dict)::Dict{Sy
|
||||
return recommendations
|
||||
end
|
||||
|
||||
"""Generate baseline correction recommendations based on data properties."""
|
||||
function generate_baseline_recommendations(inst_analysis, signal_analysis)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
|
||||
@ -691,6 +828,7 @@ function generate_baseline_recommendations(inst_analysis, signal_analysis)
|
||||
return recommendations
|
||||
end
|
||||
|
||||
"""Generate smoothing recommendations based on peak width and m/z step."""
|
||||
function generate_smoothing_recommendations(inst_analysis, peak_analysis)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
|
||||
@ -713,19 +851,33 @@ function generate_smoothing_recommendations(inst_analysis, peak_analysis)
|
||||
return recommendations
|
||||
end
|
||||
|
||||
function generate_peak_picking_recommendations(signal_analysis, peak_analysis)
|
||||
"""Generate peak picking recommendations from signal and peak analyses."""
|
||||
function generate_peak_picking_recommendations(signal_analysis, peak_analysis, inst_analysis)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
|
||||
acquisition_mode = get(inst_analysis, :acquisition_mode, :profile)
|
||||
|
||||
if acquisition_mode == :centroid
|
||||
# Much more permissive parameters for centroid data
|
||||
recommendations[:snr_threshold] = 2.0 # Lower threshold for centroid
|
||||
recommendations[:min_peak_width_ppm] = 0.0 # No minimum width for centroids
|
||||
recommendations[:max_peak_width_ppm] = 200.0 # Very wide maximum for centroids
|
||||
recommendations[:reason] = "Centroid data: using permissive parameters"
|
||||
else
|
||||
# Existing profile mode logic
|
||||
snr_threshold = get(signal_analysis, :suggested_snr, 3.0)
|
||||
fwhm_ppm = get(peak_analysis, :mean_fwhm_ppm, 20.0)
|
||||
|
||||
recommendations[:snr_threshold] = snr_threshold
|
||||
recommendations[:min_peak_width_ppm] = fwhm_ppm * 0.5 # Avoid detecting noise as peaks
|
||||
recommendations[:max_peak_width_ppm] = fwhm_ppm * 3.0 # Avoid merging distinct peaks
|
||||
recommendations[:min_peak_width_ppm] = fwhm_ppm * 0.5
|
||||
recommendations[:max_peak_width_ppm] = fwhm_ppm * 3.0
|
||||
recommendations[:reason] = "Profile data: using standard parameters"
|
||||
end
|
||||
|
||||
return recommendations
|
||||
end
|
||||
|
||||
"""Generate normalization recommendations based on TIC variation."""
|
||||
function generate_normalization_recommendations(signal_analysis)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
|
||||
@ -742,6 +894,7 @@ function generate_normalization_recommendations(signal_analysis)
|
||||
return recommendations
|
||||
end
|
||||
|
||||
"""Generate alignment recommendations based on calibration status and mass error."""
|
||||
function generate_alignment_recommendations(mass_accuracy, inst_analysis)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
|
||||
@ -765,6 +918,7 @@ function generate_alignment_recommendations(mass_accuracy, inst_analysis)
|
||||
return recommendations
|
||||
end
|
||||
|
||||
"""Generate binning recommendations based on peak width and mass accuracy."""
|
||||
function generate_binning_recommendations(peak_analysis, mass_accuracy)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
|
||||
@ -794,18 +948,47 @@ function generate_binning_recommendations(peak_analysis, mass_accuracy)
|
||||
return recommendations
|
||||
end
|
||||
|
||||
"""Generate intensity stabilization recommendations."""
|
||||
function generate_stabilization_recommendations(signal_analysis)
|
||||
recommendations = Dict{Symbol, Any}()
|
||||
# Default to sqrt, as it's a common and generally robust transformation.
|
||||
# More advanced logic could analyze intensity distribution skewness if needed.
|
||||
recommendations[:method] = :sqrt
|
||||
return recommendations
|
||||
end
|
||||
|
||||
# =============================================================================
|
||||
# Pre-Analysis Pipeline for Auto Parameter Determination
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
run_preprocessing_analysis(msi_data::MSIData;
|
||||
reference_peaks::Dict{Float64, String}=Dict(),
|
||||
region_masks::Dict=Dict(),
|
||||
sample_size::Int=100)::Dict{Symbol, Any}
|
||||
run_preprocessing_analysis(msi_data; ...)
|
||||
|
||||
Runs a comprehensive pre-analysis pipeline to determine optimal preprocessing parameters.
|
||||
This function analyzes the dataset to provide intelligent defaults for preprocessing steps.
|
||||
This function orchestrates a series of analysis steps on a sample of the dataset to
|
||||
provide intelligent defaults for a full preprocessing workflow.
|
||||
|
||||
The pipeline consists of several phases:
|
||||
1. **Instrument & Data Characteristics**: Infers acquisition mode, m/z axis type, etc.
|
||||
2. **Noise & Signal Quality**: Estimates noise, SNR, and TIC variation.
|
||||
3. **Mass Accuracy**: Calculates PPM error against reference peaks (if provided).
|
||||
4. **Spatial Regions**: Analyzes regional variations (if masks are provided).
|
||||
5. **Peak Characteristics**: Measures peak width, shape, and density.
|
||||
6. **Recommendations**: Synthesizes all analysis results into actionable parameter suggestions.
|
||||
|
||||
# Arguments
|
||||
- `msi_data::MSIData`: The main MSI data object.
|
||||
- `reference_peaks::Dict`: Optional. Known m/z values for mass accuracy analysis.
|
||||
- `region_masks::Dict`: Optional. Named `BitMatrix` masks for regional analysis.
|
||||
- `sample_size::Int`: The number of spectra to sample for the analysis.
|
||||
- `mask_path::String`: Optional path to a PNG mask to restrict analysis to a specific ROI.
|
||||
- `spectrum_indices::AbstractVector{Int}`: Optional vector of indices to restrict analysis to,
|
||||
overriding `mask_path` and `sample_size` for selection.
|
||||
|
||||
# Returns
|
||||
- `Dict{Symbol, Any}`: A nested dictionary containing the results of each analysis phase
|
||||
and a final `:recommendations` dictionary. The recommendations are also stored in
|
||||
`msi_data.preprocessing_hints`.
|
||||
"""
|
||||
function run_preprocessing_analysis(msi_data::MSIData;
|
||||
reference_peaks::Dict{Float64, String}=Dict{Float64, String}(),
|
||||
@ -1019,7 +1202,7 @@ function calculate_resolution_fwhm(mz::Real, profile_mz::AbstractVector{<:Real},
|
||||
return fwhm > 0 ? Float64(mz) / fwhm : NaN
|
||||
end
|
||||
|
||||
# Helper functions for FWHM calculation
|
||||
"""Helper to find the last index in a vector with a value below a threshold, used for FWHM."""
|
||||
function find_last_below(v::AbstractVector{<:Real}, threshold::Real)
|
||||
for i in length(v):-1:2
|
||||
if v[i] >= threshold && v[i-1] < threshold
|
||||
@ -1029,6 +1212,7 @@ function find_last_below(v::AbstractVector{<:Real}, threshold::Real)
|
||||
return 0
|
||||
end
|
||||
|
||||
"""Helper to find the first index in a vector with a value below a threshold, used for FWHM."""
|
||||
function find_first_below(v::AbstractVector{<:Real}, threshold::Real)
|
||||
for i in 1:(length(v)-1)
|
||||
if v[i] >= threshold && v[i+1] < threshold
|
||||
@ -1270,10 +1454,11 @@ function main_precalculation(msi_data::MSIData;
|
||||
recs = get(analysis_results, :recommendations, Dict())
|
||||
signal_analysis = get(analysis_results, :signal_analysis, Dict())
|
||||
peak_analysis = get(analysis_results, :peak_analysis, Dict())
|
||||
mass_accuracy = get(analysis_results, :mass_accuracy, Dict())
|
||||
mass_accuracy = get(analysis_results, :mass_accuracy, nothing) # Can be nothing
|
||||
inst_analysis = get(analysis_results, :instrument_analysis, Dict())
|
||||
|
||||
# Initialize parameter dictionaries for each step
|
||||
stab_params = Dict{Symbol, Any}()
|
||||
cal_params = Dict{Symbol, Any}()
|
||||
sm_params = Dict{Symbol, Any}()
|
||||
bc_params = Dict{Symbol, Any}()
|
||||
@ -1284,11 +1469,22 @@ function main_precalculation(msi_data::MSIData;
|
||||
pb_params = Dict{Symbol, Any}()
|
||||
|
||||
# --- Populate Parameters for each step ---
|
||||
# Stabilization
|
||||
if !isempty(recs) && haskey(recs, :stabilization)
|
||||
stab_rec = recs[:stabilization]
|
||||
stab_params[:method] = get(stab_rec, :method, :sqrt)
|
||||
else
|
||||
stab_params[:method] = :sqrt # Default
|
||||
end
|
||||
|
||||
# Calibration & Alignment (Note: These are intertwined in the current logic)
|
||||
calibration_required = false
|
||||
mean_ppm_error = NaN
|
||||
suggested_bin_tol = NaN
|
||||
if mass_accuracy !== nothing
|
||||
mean_ppm_error = get(get(mass_accuracy, :global_accuracy, Dict()), :mean_ppm_error, NaN)
|
||||
suggested_bin_tol = get(mass_accuracy, :suggested_bin_tolerance, NaN)
|
||||
end
|
||||
|
||||
if !isempty(recs) && haskey(recs, :alignment) && get(recs[:alignment], :required, false)
|
||||
calibration_required = true
|
||||
@ -1400,30 +1596,46 @@ function main_precalculation(msi_data::MSIData;
|
||||
end
|
||||
|
||||
# Peak Picking
|
||||
# Robust method selection with fallback
|
||||
acquisition_mode = get(inst_analysis, :acquisition_mode, :profile) # Default to profile
|
||||
# Robust method selection based on acquisition mode
|
||||
acquisition_mode = get(inst_analysis, :acquisition_mode, :unknown)
|
||||
pp_params[:method] = acquisition_mode == :profile ? :profile : :centroid
|
||||
|
||||
if acquisition_mode == :centroid
|
||||
# Much more permissive parameters for centroid data
|
||||
pp_params[:snr_threshold] = 2.0 # Lower for centroid
|
||||
pp_params[:min_peak_width_ppm] = 0.0 # No minimum width
|
||||
pp_params[:max_peak_width_ppm] = 200.0 # Very wide maximum
|
||||
pp_params[:min_peak_shape_r2] = 0.0 # Disable shape filtering
|
||||
|
||||
# Much lower prominence threshold for centroid
|
||||
estimated_noise = get(signal_analysis, :noise_mean, NaN)
|
||||
if isfinite(estimated_noise)
|
||||
pp_params[:min_peak_prominence] = max(estimated_noise * 0.5, 0.001) # Much lower
|
||||
else
|
||||
pp_params[:min_peak_prominence] = 0.001 # Very permissive
|
||||
end
|
||||
|
||||
pp_params[:half_window] = 2 # Smaller window for centroid data
|
||||
pp_params[:merge_peaks_tolerance] = 10.0 # More permissive merging
|
||||
else # Profile mode logic
|
||||
if !isempty(recs) && haskey(recs, :peak_picking)
|
||||
pk_rec = recs[:peak_picking]
|
||||
pp_params[:snr_threshold] = get(pk_rec, :snr_threshold, 3.0) # Default to 3.0
|
||||
pp_params[:snr_threshold] = get(pk_rec, :snr_threshold, 2.0) # Default to 2.0
|
||||
pp_params[:min_peak_width_ppm] = get(pk_rec, :min_peak_width_ppm, nothing)
|
||||
pp_params[:max_peak_width_ppm] = get(pk_rec, :max_peak_width_ppm, nothing)
|
||||
else
|
||||
pp_params[:snr_threshold] = 3.0 # Default to 3.0
|
||||
pp_params[:snr_threshold] = 2.0 # Default to 2.0
|
||||
pp_params[:min_peak_width_ppm] = nothing
|
||||
pp_params[:max_peak_width_ppm] = nothing
|
||||
end
|
||||
|
||||
# Robust prominence calculation with safety cap
|
||||
# Robust prominence calculation with safety cap for profile
|
||||
estimated_noise = get(signal_analysis, :noise_mean, NaN)
|
||||
if isfinite(estimated_noise)
|
||||
# Never be more aggressive than 0.05, a reasonable upper limit
|
||||
calculated_prominence = round(estimated_noise * 2, digits=4)
|
||||
pp_params[:min_peak_prominence] = min(calculated_prominence, 0.05)
|
||||
pp_params[:min_peak_prominence] = min(calculated_prominence, 0.005)
|
||||
else
|
||||
# Fallback to a safe, non-aggressive value if noise couldn't be estimated
|
||||
pp_params[:min_peak_prominence] = 0.01
|
||||
pp_params[:min_peak_prominence] = 0.005
|
||||
end
|
||||
|
||||
if isfinite(suggested_bin_tol)
|
||||
@ -1432,31 +1644,38 @@ function main_precalculation(msi_data::MSIData;
|
||||
pp_params[:merge_peaks_tolerance] = nothing
|
||||
end
|
||||
|
||||
# Robust half_window calculation with safety floor
|
||||
# Robust half_window calculation with safety floor for profile
|
||||
mean_fwhm_ppm = get(peak_analysis, :mean_fwhm_ppm, NaN)
|
||||
avg_mz_step = get(inst_analysis, :average_mz_step, NaN)
|
||||
if isfinite(mean_fwhm_ppm) && isfinite(avg_mz_step) && avg_mz_step > 0
|
||||
fwhm_mz = 500.0 * mean_fwhm_ppm / 1e6 # At typical m/z 500
|
||||
window_points = fwhm_mz / avg_mz_step
|
||||
calculated_half_window = ceil(Int, window_points / 2)
|
||||
# Ensure half_window is at least 3, a safe absolute minimum
|
||||
pp_params[:half_window] = max(calculated_half_window, 3)
|
||||
else
|
||||
# Fallback if calculation is not possible
|
||||
pp_params[:half_window] = 5
|
||||
end
|
||||
|
||||
# Robust R^2 calculation with floor
|
||||
# Robust R^2 calculation with floor for profile
|
||||
mean_r2 = get(peak_analysis, :mean_gaussian_r2, NaN)
|
||||
if isfinite(mean_r2)
|
||||
pp_params[:min_peak_shape_r2] = round(max(0.5, mean_r2 * 0.8), digits=2)
|
||||
pp_params[:min_peak_shape_r2] = round(max(0.0, mean_r2 * 0.8), digits=2)
|
||||
else
|
||||
# Fallback to a reasonable default
|
||||
pp_params[:min_peak_shape_r2] = 0.6
|
||||
pp_params[:min_peak_shape_r2] = 0.0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Peak Selection
|
||||
if acquisition_mode == :centroid
|
||||
# Much more permissive parameters for centroid data
|
||||
ps_params[:min_snr] = 1.5 # Even lower than picking threshold
|
||||
ps_params[:min_fwhm_ppm] = 0.0 # No minimum
|
||||
ps_params[:max_fwhm_ppm] = 500.0 # Very wide maximum
|
||||
ps_params[:min_shape_r2] = 0.0 # Disable shape filtering
|
||||
ps_params[:frequency_threshold] = nothing # User input dependent / Hard to determine
|
||||
ps_params[:correlation_threshold] = nothing # Hard to determine
|
||||
else # Profile mode logic
|
||||
if haskey(pp_params, :snr_threshold) && pp_params[:snr_threshold] !== nothing
|
||||
ps_params[:min_snr] = pp_params[:snr_threshold]
|
||||
else
|
||||
@ -1471,18 +1690,20 @@ function main_precalculation(msi_data::MSIData;
|
||||
ps_params[:min_fwhm_ppm] = nothing
|
||||
ps_params[:max_fwhm_ppm] = nothing
|
||||
end
|
||||
mean_r2 = get(peak_analysis, :mean_gaussian_r2, NaN)
|
||||
if isfinite(mean_r2)
|
||||
ps_params[:min_shape_r2] = round(max(0.5, mean_r2 * 0.8), digits=2)
|
||||
ps_params[:min_shape_r2] = round(max(0.0, mean_r2 * 0.8), digits=2)
|
||||
else
|
||||
ps_params[:min_shape_r2] = nothing
|
||||
ps_params[:min_shape_r2] = 0.0 # Adjusted fallback
|
||||
end
|
||||
else
|
||||
ps_params[:min_fwhm_ppm] = nothing
|
||||
ps_params[:max_fwhm_ppm] = nothing
|
||||
ps_params[:min_shape_r2] = nothing
|
||||
end
|
||||
ps_params[:frequency_threshold] = nothing # User input dependent / Hard to determine
|
||||
ps_params[:correlation_threshold] = nothing # Hard to determine
|
||||
ps_params[:frequency_threshold] = nothing
|
||||
ps_params[:correlation_threshold] = nothing
|
||||
end
|
||||
|
||||
|
||||
# Peak Binning
|
||||
@ -1525,6 +1746,7 @@ function main_precalculation(msi_data::MSIData;
|
||||
end
|
||||
|
||||
return Dict(
|
||||
:Stabilization => stab_params,
|
||||
:Calibration => cal_params,
|
||||
:Smoothing => sm_params,
|
||||
:BaselineCorrection => bc_params,
|
||||
@ -1532,6 +1754,6 @@ function main_precalculation(msi_data::MSIData;
|
||||
:PeakPicking => pp_params,
|
||||
:PeakAlignment => pa_params,
|
||||
:PeakSelection => ps_params,
|
||||
:PeakBinningParams => pb_params
|
||||
:PeakBinning => pb_params
|
||||
)
|
||||
end
|
||||
|
||||
@ -21,6 +21,7 @@ using ContinuousWavelets # For CWT peak detection
|
||||
using ImageFiltering # For localmaxima in detect_peaks_wavelet
|
||||
using Interpolations # For calibration
|
||||
using Loess # For robust peak alignment
|
||||
using Base.Threads # For multithreading in apply functions
|
||||
|
||||
# =============================================================================
|
||||
# Data Structures
|
||||
@ -30,6 +31,13 @@ using Loess # For robust peak alignment
|
||||
FeatureMatrix
|
||||
|
||||
A struct to hold the final feature matrix generated from the preprocessing pipeline.
|
||||
|
||||
# Fields
|
||||
- `matrix::Array{Float64,2}`: The feature matrix where rows correspond to samples (spectra)
|
||||
and columns correspond to features (m/z bins).
|
||||
- `mz_bins::Vector{Tuple{Float64,Float64}}`: A vector of tuples defining the start and
|
||||
end m/z for each bin (column) in the `matrix`.
|
||||
- `sample_ids::Vector{Int}`: A vector of identifiers for each sample (row) in the `matrix`.
|
||||
"""
|
||||
struct FeatureMatrix
|
||||
matrix::Array{Float64,2}
|
||||
@ -38,9 +46,18 @@ struct FeatureMatrix
|
||||
end
|
||||
|
||||
"""
|
||||
MutableSpectrum
|
||||
|
||||
A mutable struct to hold spectrum data. Using a mutable struct allows
|
||||
in-place modification of fields (like intensity or m/z), which dramatically
|
||||
reduces memory allocations compared to creating new immutable tuples at each step.
|
||||
|
||||
# Fields
|
||||
- `id::Int`: A unique identifier for the spectrum.
|
||||
- `mz::AbstractVector{Float64}`: The m/z values of the spectrum.
|
||||
- `intensity::AbstractVector{Float64}`: The intensity values corresponding to the m/z values.
|
||||
- `peaks::Vector{NamedTuple}`: A vector of detected peaks, each a `NamedTuple` with fields
|
||||
like `:mz`, `:intensity`, `:fwhm`, etc.
|
||||
"""
|
||||
mutable struct MutableSpectrum
|
||||
id::Int
|
||||
@ -62,8 +79,18 @@ abstract type AbstractPreprocessingStep end
|
||||
"""
|
||||
Calibration(; method=:internal_standards, ...)
|
||||
|
||||
A preprocessing step for mass calibration. Set a parameter to `nothing` to use an
|
||||
auto-determined value from the data where applicable.
|
||||
A preprocessing step for mass calibration. Corrects systematic mass errors in the m/z axis.
|
||||
Set a parameter to `nothing` to use an auto-determined value from the data where applicable.
|
||||
|
||||
# Arguments
|
||||
- `method::Symbol`: The calibration method. Currently supports `:internal_standards`.
|
||||
- `internal_standards::Union{Dict{Float64, String}, Nothing}`: A dictionary mapping theoretical
|
||||
m/z values of internal standards to their names.
|
||||
- `base_peak_mz_references::Union{Vector{Float64}, Nothing}`: A vector of reference m/z values
|
||||
for base peak calibration (not yet implemented).
|
||||
- `ppm_tolerance::Union{Float64, Nothing}`: The tolerance in parts-per-million (ppm) for matching
|
||||
peaks to internal standards.
|
||||
- `fit_order::Int`: The polynomial order for the calibration fit (e.g., 1 for linear, 2 for quadratic).
|
||||
"""
|
||||
struct Calibration <: AbstractPreprocessingStep
|
||||
method::Symbol
|
||||
@ -80,7 +107,16 @@ end
|
||||
"""
|
||||
BaselineCorrection(; method=:snip, ...)
|
||||
|
||||
A preprocessing step for baseline correction.
|
||||
A preprocessing step for baseline correction. This step estimates and subtracts the
|
||||
background noise (baseline) from the spectral intensities.
|
||||
|
||||
# Arguments
|
||||
- `method::Symbol`: The algorithm to use. Options include `:snip` (Sensitive Nonlinear
|
||||
Iterative Peak clipping), `:convex_hull`, and `:median`.
|
||||
- `iterations::Union{Int, Nothing}`: The number of iterations for the SNIP algorithm.
|
||||
A higher number results in a more aggressive baseline.
|
||||
- `window::Union{Int, Nothing}`: The window size for the `:median` method, determining
|
||||
the local region for median calculation.
|
||||
"""
|
||||
struct BaselineCorrection <: AbstractPreprocessingStep
|
||||
method::Symbol
|
||||
@ -95,7 +131,15 @@ end
|
||||
"""
|
||||
Smoothing(; method=:savitzky_golay, ...)
|
||||
|
||||
A preprocessing step for spectral smoothing.
|
||||
A preprocessing step for spectral smoothing. This helps to reduce high-frequency noise
|
||||
in the intensity data.
|
||||
|
||||
# Arguments
|
||||
- `method::Symbol`: The smoothing algorithm. Options are `:savitzky_golay` and `:moving_average`.
|
||||
- `window::Union{Int, Nothing}`: The size of the smoothing window. For Savitzky-Golay,
|
||||
this must be an odd integer.
|
||||
- `order::Union{Int, Nothing}`: The polynomial order for the Savitzky-Golay filter. Must be
|
||||
less than the window size.
|
||||
"""
|
||||
struct Smoothing <: AbstractPreprocessingStep
|
||||
method::Symbol
|
||||
@ -112,7 +156,15 @@ end
|
||||
"""
|
||||
Normalization(; method=:tic)
|
||||
|
||||
A preprocessing step for intensity normalization.
|
||||
A preprocessing step for intensity normalization. This corrects for variations in total
|
||||
ion current between different spectra, making them more comparable.
|
||||
|
||||
# Arguments
|
||||
- `method::Symbol`: The normalization method. Options include:
|
||||
- `:tic`: Total Ion Current normalization (divides by the sum of intensities).
|
||||
- `:median`: Divides by the median intensity.
|
||||
- `:rms`: Root Mean Square normalization.
|
||||
- `:none`: No normalization is applied.
|
||||
"""
|
||||
struct Normalization <: AbstractPreprocessingStep
|
||||
method::Symbol
|
||||
@ -125,7 +177,25 @@ end
|
||||
"""
|
||||
PeakPicking(; method=nothing, ...)
|
||||
|
||||
A preprocessing step for peak detection.
|
||||
A preprocessing step for peak detection. This step identifies peaks (signals of interest)
|
||||
in the profile or centroided spectra.
|
||||
|
||||
# Arguments
|
||||
- `method::Union{Symbol, Nothing}`: The peak detection algorithm.
|
||||
- `:profile`: For profile-mode data, using local maxima and quality filters.
|
||||
- `:wavelet`: Continuous Wavelet Transform (CWT) based peak detection.
|
||||
- `:centroid`: For centroid-mode data, essentially a filtering step.
|
||||
- `snr_threshold::Union{Float64, Nothing}`: Signal-to-Noise Ratio threshold. Peaks with SNR
|
||||
below this value are discarded.
|
||||
- `half_window::Union{Int, Nothing}`: The number of data points to the left and right of a
|
||||
potential peak to consider for local maximum detection (for `:profile`).
|
||||
- `min_peak_prominence::Union{Float64, Nothing}`: The minimum required prominence of a peak,
|
||||
expressed as a fraction of its height.
|
||||
- `merge_peaks_tolerance::Union{Float64, Nothing}`: The m/z tolerance within which to merge
|
||||
adjacent peaks, keeping the more intense one.
|
||||
- `min_peak_width_ppm, max_peak_width_ppm`: Minimum and maximum acceptable peak width (FWHM) in ppm.
|
||||
- `min_peak_shape_r2`: Minimum R-squared value from a Gaussian fit to the peak, used as a
|
||||
quality measure for peak shape.
|
||||
"""
|
||||
struct PeakPicking <: AbstractPreprocessingStep
|
||||
method::Union{Symbol, Nothing} # :profile, :wavelet, :centroid
|
||||
@ -145,7 +215,20 @@ end
|
||||
"""
|
||||
PeakAlignment(; method=:lowess, ...)
|
||||
|
||||
A preprocessing step for peak alignment.
|
||||
A preprocessing step for peak alignment. This corrects for m/z shifts between spectra,
|
||||
ensuring that the same analyte peak appears at the same m/z across all samples.
|
||||
|
||||
# Arguments
|
||||
- `method::Symbol`: The alignment algorithm. Options: `:lowess`, `:linear`, `:ransac`.
|
||||
- `span::Union{Float64, Nothing}`: The span parameter for LOWESS regression, controlling smoothness.
|
||||
- `tolerance::Union{Float64, Nothing}`: The tolerance for matching peaks between the target
|
||||
and reference spectrum.
|
||||
- `tolerance_unit::Union{Symbol, Nothing}`: The unit for `tolerance`, either `:mz` (absolute)
|
||||
or `:ppm` (relative).
|
||||
- `max_shift_ppm::Union{Float64, Nothing}`: The maximum allowed m/z shift in ppm to prevent
|
||||
spurious peak matches.
|
||||
- `min_matched_peaks::Union{Int, Nothing}`: The minimum number of matching peaks required
|
||||
to perform the alignment.
|
||||
"""
|
||||
struct PeakAlignment <: AbstractPreprocessingStep
|
||||
method::Symbol
|
||||
@ -163,7 +246,19 @@ end
|
||||
"""
|
||||
PeakSelection(; frequency_threshold=nothing, ...)
|
||||
|
||||
A preprocessing step for peak selection (filtering).
|
||||
A preprocessing step for peak selection (filtering). After peak detection, this step
|
||||
filters the detected peaks based on various quality criteria to remove noise and
|
||||
irrelevant signals.
|
||||
|
||||
# Arguments
|
||||
- `frequency_threshold::Union{Float64, Nothing}`: The minimum fraction of spectra in which a
|
||||
peak must be present to be kept.
|
||||
- `min_snr::Union{Float64, Nothing}`: Minimum Signal-to-Noise Ratio.
|
||||
- `min_fwhm_ppm, max_fwhm_ppm`: Minimum and maximum Full Width at Half Maximum in ppm.
|
||||
- `min_shape_r2::Union{Float64, Nothing}`: Minimum R-squared value from a Gaussian fit,
|
||||
filtering for good peak shape.
|
||||
- `correlation_threshold::Union{Float64, Nothing}`: Minimum correlation with neighboring
|
||||
peaks (not yet implemented).
|
||||
"""
|
||||
struct PeakSelection <: AbstractPreprocessingStep
|
||||
frequency_threshold::Union{Float64, Nothing}
|
||||
@ -179,11 +274,24 @@ struct PeakSelection <: AbstractPreprocessingStep
|
||||
end
|
||||
|
||||
"""
|
||||
PeakBinningParams(; method=:adaptive, ...)
|
||||
PeakBinning(; method=:adaptive, ...)
|
||||
|
||||
A preprocessing step for peak binning.
|
||||
A preprocessing step for peak binning. This step groups peaks from all spectra into
|
||||
common m/z bins to generate a feature matrix.
|
||||
|
||||
# Arguments
|
||||
- `method::Symbol`: The binning strategy.
|
||||
- `:adaptive`: Creates bins based on the density of detected peaks.
|
||||
- `:uniform`: Creates a fixed number of equally spaced bins over the m/z range.
|
||||
- `tolerance, tolerance_unit`: Tolerance for grouping peaks into a bin in `:adaptive` mode.
|
||||
- `frequency_threshold`: The minimum fraction of spectra a bin must contain a peak in to be kept.
|
||||
- `min_peak_per_bin`: The minimum number of individual peaks required to form a bin in `:adaptive` mode.
|
||||
- `max_bin_width_ppm`: Maximum width of a bin in ppm for `:adaptive` mode.
|
||||
- `intensity_weighted_centers`: If `true`, calculates bin centers as an intensity-weighted
|
||||
average of the peaks within it.
|
||||
- `num_uniform_bins`: The number of bins to create for the `:uniform` method.
|
||||
"""
|
||||
struct PeakBinningParams <: AbstractPreprocessingStep
|
||||
struct PeakBinning <: AbstractPreprocessingStep
|
||||
method::Symbol
|
||||
tolerance::Union{Float64, Nothing}
|
||||
tolerance_unit::Union{Symbol, Nothing}
|
||||
@ -193,7 +301,7 @@ struct PeakBinningParams <: AbstractPreprocessingStep
|
||||
intensity_weighted_centers::Bool
|
||||
num_uniform_bins::Union{Int, Nothing}
|
||||
|
||||
function PeakBinningParams(; method=:adaptive, tolerance=nothing, tolerance_unit=nothing, frequency_threshold=nothing, min_peak_per_bin=nothing, max_bin_width_ppm=nothing, intensity_weighted_centers=true, num_uniform_bins=nothing)
|
||||
function PeakBinning(; method=:adaptive, tolerance=nothing, tolerance_unit=nothing, frequency_threshold=nothing, min_peak_per_bin=nothing, max_bin_width_ppm=nothing, intensity_weighted_centers=true, num_uniform_bins=nothing)
|
||||
new(method, tolerance, tolerance_unit, frequency_threshold, min_peak_per_bin, max_bin_width_ppm, intensity_weighted_centers, num_uniform_bins)
|
||||
end
|
||||
end
|
||||
@ -237,7 +345,7 @@ Checks include:
|
||||
- `mz` and `intensity` have the same length.
|
||||
- All `m/z` values are finite and non-negative.
|
||||
- All `intensity` values are finite and non-negative.
|
||||
- `m/z` values are strictly increasing (no duplicates or decreasing values).
|
||||
- `m/z` values are monotonically non-decreasing.
|
||||
"""
|
||||
function validate_spectrum(mz::AbstractVector{<:Real}, intensity::AbstractVector{<:Real})::Bool
|
||||
# 1. Check for empty vectors
|
||||
@ -278,11 +386,22 @@ end
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
transform_intensity(intensity; method=:sqrt) -> Vector
|
||||
transform_intensity_core(intensity; method=:sqrt) -> Vector
|
||||
|
||||
Applies a variance-stabilizing transformation to the intensity vector.
|
||||
Applies a variance-stabilizing transformation to the intensity vector. This can
|
||||
help to make the variance of the signal more constant across the intensity range,
|
||||
which is often an assumption of downstream statistical methods.
|
||||
|
||||
# Arguments
|
||||
- `intensity::AbstractVector{<:Real}`: The input intensity values.
|
||||
- `method::Symbol`: The transformation to apply. Options are:
|
||||
- `:sqrt`: Square root transformation.
|
||||
- `:log`: Natural log transformation.
|
||||
- `:log2`: Base-2 log transformation.
|
||||
- `:log10`: Base-10 log transformation.
|
||||
- `:log1p`: Natural log of `1 + x`, useful for data with zeros.
|
||||
"""
|
||||
function transform_intensity(intensity::AbstractVector{<:Real}; method::Symbol=:sqrt)
|
||||
function transform_intensity_core(intensity::AbstractVector{<:Real}; method::Symbol=:sqrt)
|
||||
if method === :sqrt
|
||||
return sqrt.(max.(zero(eltype(intensity)), intensity))
|
||||
elseif method === :log1p
|
||||
@ -299,7 +418,7 @@ function transform_intensity(intensity::AbstractVector{<:Real}; method::Symbol=:
|
||||
end
|
||||
|
||||
"""
|
||||
smooth_spectrum(y::AbstractVector{<:Real}; method::Symbol=:savitzky_golay, window::Int=9, order::Int=2) -> Vector
|
||||
smooth_spectrum_core(y::AbstractVector{<:Real}; method::Symbol=:savitzky_golay, window::Int=9, order::Int=2) -> Vector
|
||||
|
||||
Applies a smoothing filter to the intensity data.
|
||||
|
||||
@ -309,7 +428,7 @@ Applies a smoothing filter to the intensity data.
|
||||
- `window`: The window size for the filter.
|
||||
- `order`: The polynomial order for Savitzky-Golay
|
||||
"""
|
||||
function smooth_spectrum(y::AbstractVector{<:Real}; method::Symbol=:savitzky_golay, window::Int=9, order::Int=2)
|
||||
function smooth_spectrum_core(y::AbstractVector{<:Real}; method::Symbol=:savitzky_golay, window::Int=9, order::Int=2)
|
||||
if window < 3
|
||||
throw(ArgumentError("Window size must be at least 3"))
|
||||
end
|
||||
@ -399,7 +518,9 @@ end
|
||||
"""
|
||||
convex_hull_baseline(y) -> Vector
|
||||
|
||||
Estimates the baseline of a spectrum using the convex hull algorithm.
|
||||
Estimates the baseline of a spectrum using the convex hull algorithm. This method
|
||||
finds the lower convex hull of the spectrum, which is then used as the baseline.
|
||||
It is generally faster than SNIP but can be less flexible.
|
||||
"""
|
||||
function convex_hull_baseline(y::AbstractVector{<:Real})
|
||||
n = length(y)
|
||||
@ -455,7 +576,7 @@ function median_baseline(y::AbstractVector{<:Real}; window::Int=20)
|
||||
end
|
||||
|
||||
"""
|
||||
apply_baseline_correction(y::AbstractVector{<:Real}; method::Symbol=:snip, iterations::Int=100, window::Int=20) -> Vector
|
||||
apply_baseline_correction_core(y::AbstractVector{<:Real}; method::Symbol=:snip, iterations::Int=100, window::Int=20) -> Vector
|
||||
|
||||
Applies a baseline correction algorithm to the intensity data.
|
||||
|
||||
@ -465,7 +586,7 @@ Applies a baseline correction algorithm to the intensity data.
|
||||
- `iterations`: Iterations for SNIP method.
|
||||
- `window`: Window size for median method.
|
||||
"""
|
||||
function apply_baseline_correction(y::AbstractVector{<:Real}; method::Symbol=:snip, iterations::Int=100, window::Int=20)
|
||||
function apply_baseline_correction_core(y::AbstractVector{<:Real}; method::Symbol=:snip, iterations::Int=100, window::Int=20)
|
||||
if method === :snip
|
||||
return _snip_baseline_impl(y, iterations=iterations)
|
||||
elseif method === :convex_hull
|
||||
@ -485,7 +606,9 @@ end
|
||||
"""
|
||||
tic_normalize(y) -> Vector
|
||||
|
||||
Normalizes spectrum intensities to the Total Ion Current (TIC).
|
||||
Normalizes spectrum intensities to the Total Ion Current (TIC). Each intensity value
|
||||
is divided by the sum of all intensities in the spectrum. This method assumes that the
|
||||
total number of ions produced is similar for all samples.
|
||||
"""
|
||||
function tic_normalize(y::AbstractVector{<:Real})
|
||||
s = sum(y)
|
||||
@ -496,6 +619,18 @@ end
|
||||
pqn_normalize(M) -> Matrix
|
||||
|
||||
Performs Probabilistic Quotient Normalization (PQN) on a matrix of spectra.
|
||||
This is a more robust normalization method that is less sensitive to a small
|
||||
number of highly abundant, variable peaks compared to TIC.
|
||||
|
||||
# Steps:
|
||||
1. A reference spectrum is calculated (typically the median spectrum across all samples).
|
||||
2. For each spectrum, the quotients of its intensities and the reference spectrum's
|
||||
intensities are calculated.
|
||||
3. The median of these quotients is found for each spectrum.
|
||||
4. Each spectrum is divided by its median quotient.
|
||||
|
||||
# Arguments
|
||||
- `M::AbstractMatrix{<:Real}`: A matrix where columns are spectra and rows are m/z bins.
|
||||
"""
|
||||
function pqn_normalize(M::AbstractMatrix{<:Real})
|
||||
M_float = collect(float.(M))
|
||||
@ -532,7 +667,7 @@ function rms_normalize(y::AbstractVector{<:Real})
|
||||
end
|
||||
|
||||
"""
|
||||
apply_normalization(y::AbstractVector{<:Real}; method::Symbol=:tic) -> Vector
|
||||
apply_normalization_core(y::AbstractVector{<:Real}; method::Symbol=:tic) -> Vector
|
||||
|
||||
Applies a per-spectrum normalization algorithm to the intensity data.
|
||||
|
||||
@ -540,7 +675,7 @@ Applies a per-spectrum normalization algorithm to the intensity data.
|
||||
- `y`: The intensity data.
|
||||
- `method`: The normalization method (:tic, :median, :rms, or :none).
|
||||
"""
|
||||
function apply_normalization(y::AbstractVector{<:Real}; method::Symbol=:tic)
|
||||
function apply_normalization_core(y::AbstractVector{<:Real}; method::Symbol=:tic)::Vector
|
||||
if method === :tic
|
||||
return tic_normalize(y)
|
||||
elseif method === :median
|
||||
@ -555,188 +690,12 @@ function apply_normalization(y::AbstractVector{<:Real}; method::Symbol=:tic)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
remove_matrix_peaks_from_spectrum(mz::AbstractVector{<:Real}, intensity::AbstractVector{<:Real},
|
||||
matrix_peak_mzs::AbstractVector{<:Real};
|
||||
tolerance::Float64=0.002, tolerance_unit::Symbol=:mz,
|
||||
removal_method::Symbol=:zero_out) -> Vector{Float64}
|
||||
|
||||
Removes or reduces intensity around specified matrix peaks in a single spectrum.
|
||||
|
||||
# Arguments
|
||||
- `mz`: The m/z vector of the spectrum.
|
||||
- `intensity`: The intensity vector of the spectrum.
|
||||
- `matrix_peak_mzs`: A list of m/z values identified as matrix peaks.
|
||||
- `tolerance`: The m/z tolerance for matching matrix peaks.
|
||||
- `tolerance_unit`: Unit of tolerance (:mz or :ppm).
|
||||
- `removal_method`: How to remove the peaks (:zero_out or :subtract).
|
||||
|
||||
# Returns
|
||||
- `Vector{Float64}`: The modified intensity vector.
|
||||
"""
|
||||
function remove_matrix_peaks_from_spectrum(mz::AbstractVector{<:Real}, intensity::AbstractVector{<:Real},
|
||||
matrix_peak_mzs::AbstractVector{<:Real};
|
||||
tolerance::Float64=0.002, tolerance_unit::Symbol=:mz,
|
||||
removal_method::Symbol=:zero_out)
|
||||
modified_intensity = copy(intensity)
|
||||
|
||||
for matrix_mz in matrix_peak_mzs
|
||||
# Calculate dynamic tolerance if in PPM
|
||||
current_tolerance = (tolerance_unit == :ppm) ? (matrix_mz * tolerance / 1e6) : tolerance
|
||||
|
||||
# Find indices within the tolerance window
|
||||
indices_to_modify = findall(m -> abs(m - matrix_mz) <= current_tolerance, mz)
|
||||
|
||||
if !isempty(indices_to_modify)
|
||||
if removal_method == :zero_out
|
||||
modified_intensity[indices_to_modify] .= 0.0
|
||||
elseif removal_method == :subtract
|
||||
# Baseline-aware subtraction: replace peak with a line connecting its "feet"
|
||||
idx_start = indices_to_modify[1]
|
||||
idx_end = indices_to_modify[end]
|
||||
|
||||
# Ensure we are not at the very edge of the spectrum
|
||||
if idx_start > 1 && idx_end < length(modified_intensity)
|
||||
y1 = modified_intensity[idx_start - 1]
|
||||
y2 = modified_intensity[idx_end + 1]
|
||||
x1 = idx_start - 1
|
||||
x2 = idx_end + 1
|
||||
|
||||
# Linearly interpolate the baseline under the peak
|
||||
for i in idx_start:idx_end
|
||||
# y = y1 + (y2 - y1) * (x - x1) / (x2 - x1)
|
||||
baseline_val = y1 + (y2 - y1) * (i - x1) / (x2 - x1)
|
||||
# Set the intensity to the baseline, but don't increase it (e.g., if baseline is above signal)
|
||||
modified_intensity[i] = min(modified_intensity[i], baseline_val)
|
||||
end
|
||||
else
|
||||
# If peak is at the edge, we can't interpolate, so just zero it out
|
||||
modified_intensity[indices_to_modify] .= 0.0
|
||||
end
|
||||
else
|
||||
@warn "Unsupported matrix peak removal method: $removal_method. Skipping."
|
||||
end
|
||||
end
|
||||
end
|
||||
return modified_intensity
|
||||
end
|
||||
|
||||
function identify_matrix_peaks_from_blanks(
|
||||
msi_data::MSIData,
|
||||
blank_spectrum_tag::Symbol,
|
||||
snr_threshold::Float64;
|
||||
frequency_threshold::Float64=0.5, # e.g., peak must be in 50% of blanks
|
||||
bin_tolerance::Float64=0.005, # m/z tolerance for binning blank peaks
|
||||
bin_tolerance_unit::Symbol=:mz
|
||||
)::Vector{Float64}
|
||||
if !(0 < frequency_threshold <= 1.0)
|
||||
throw(ArgumentError("`frequency_threshold` must be between 0 and 1 (exclusive of 0). Got: $frequency_threshold"))
|
||||
end
|
||||
println("Identifying matrix peaks from blank spectra (tag: $blank_spectrum_tag)...")
|
||||
blank_indices = Int[]
|
||||
for (i, meta) in enumerate(msi_data.spectra_metadata)
|
||||
if meta.type == blank_spectrum_tag
|
||||
push!(blank_indices, i)
|
||||
end
|
||||
end
|
||||
|
||||
if isempty(blank_indices)
|
||||
@warn "No blank spectra found with tag: $blank_spectrum_tag. Cannot identify matrix peaks."
|
||||
return Float64[]
|
||||
end
|
||||
|
||||
all_blank_peaks = Vector{NamedTuple}[]
|
||||
num_blank_spectra = 0
|
||||
|
||||
# Collect peaks from each blank spectrum
|
||||
_iterate_spectra_fast(msi_data, blank_indices) do idx, mz, intensity
|
||||
num_blank_spectra += 1
|
||||
if !validate_spectrum(mz, intensity)
|
||||
@warn "Blank spectrum $idx is invalid, skipping peak detection for it."
|
||||
push!(all_blank_peaks, NamedTuple[]) # Add empty list to maintain count
|
||||
return
|
||||
end
|
||||
# Assuming profile mode for matrix peak detection
|
||||
peaks = detect_peaks_profile(mz, intensity; snr_threshold=snr_threshold)
|
||||
push!(all_blank_peaks, peaks)
|
||||
end
|
||||
|
||||
if isempty(all_blank_peaks) || all(isempty, all_blank_peaks)
|
||||
@warn "No peaks detected in any blank spectra. Cannot identify matrix peaks."
|
||||
return Float64[]
|
||||
end
|
||||
|
||||
# Flatten all peaks from blank spectra for initial binning
|
||||
flat_blank_peaks = NamedTuple[]
|
||||
for peaks_in_spec in all_blank_peaks
|
||||
append!(flat_blank_peaks, peaks_in_spec)
|
||||
end
|
||||
sort!(flat_blank_peaks, by=p->p.mz)
|
||||
|
||||
# Bin the detected peaks from all blanks to find common m/z features
|
||||
# This is a simplified binning for matrix peak identification
|
||||
binned_matrix_features = Dict{Float64, Int}() # mz_center => count of spectra it appeared in
|
||||
|
||||
i = 1
|
||||
while i <= length(flat_blank_peaks)
|
||||
current_bin_start_idx = i
|
||||
current_peak = flat_blank_peaks[i]
|
||||
|
||||
# Calculate dynamic tolerance if in PPM
|
||||
current_bin_mz = current_peak.mz
|
||||
tol_val = (bin_tolerance_unit == :ppm) ? (current_bin_mz * bin_tolerance / 1e6) : bin_tolerance
|
||||
|
||||
j = i + 1
|
||||
while j <= length(flat_blank_peaks) && (flat_blank_peaks[j].mz - current_peak.mz) <= tol_val
|
||||
j += 1
|
||||
end
|
||||
current_bin_end_idx = j - 1
|
||||
|
||||
# Calculate a representative m/z for the bin (e.g., intensity-weighted average)
|
||||
peaks_in_bin = flat_blank_peaks[current_bin_start_idx:current_bin_end_idx]
|
||||
if !isempty(peaks_in_bin)
|
||||
sum_intensity = sum(p.intensity for p in peaks_in_bin)
|
||||
if sum_intensity > 0
|
||||
bin_center_mz = sum(p.mz * p.intensity for p in peaks_in_bin) / sum(sum_intensity)
|
||||
else
|
||||
bin_center_mz = mean(p.mz for p in peaks_in_bin)
|
||||
end
|
||||
|
||||
# Check how many blank spectra this feature appeared in
|
||||
spectra_count = 0
|
||||
# For each blank spectrum, check if it contains a peak within the current bin's tolerance
|
||||
for spec_peaks in all_blank_peaks
|
||||
if any(p -> abs(p.mz - bin_center_mz) <= tol_val, spec_peaks)
|
||||
spectra_count += 1
|
||||
end
|
||||
end
|
||||
binned_matrix_features[bin_center_mz] = spectra_count
|
||||
end
|
||||
i = j
|
||||
end
|
||||
|
||||
# Filter based on frequency_threshold
|
||||
matrix_peak_mzs = Float64[]
|
||||
min_spectra_count = ceil(Int, num_blank_spectra * frequency_threshold)
|
||||
|
||||
for (mz_center, count) in binned_matrix_features
|
||||
if count >= min_spectra_count
|
||||
push!(matrix_peak_mzs, mz_center)
|
||||
end
|
||||
end
|
||||
sort!(matrix_peak_mzs)
|
||||
|
||||
println("Identified $(length(matrix_peak_mzs)) potential matrix peaks from $(num_blank_spectra) blank spectra.")
|
||||
return matrix_peak_mzs
|
||||
end
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 4) Peak Detection
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
detect_peaks_profile(mz, y; ...) -> Vector{NamedTuple}
|
||||
detect_peaks_profile_core(mz, y; ...) -> Vector{NamedTuple}
|
||||
|
||||
Enhanced peak detection for profile-mode spectra with advanced filtering and quality metrics.
|
||||
|
||||
@ -748,7 +707,7 @@ Returns a vector of NamedTuples, each representing a detected peak with:
|
||||
- `snr`: Signal-to-Noise Ratio
|
||||
- `prominence`: Peak prominence
|
||||
"""
|
||||
function detect_peaks_profile(mz::AbstractVector{<:Real}, y::AbstractVector{<:Real};
|
||||
function detect_peaks_profile_core(mz::AbstractVector{<:Real}, y::AbstractVector{<:Real};
|
||||
half_window::Int=10,
|
||||
snr_threshold::Float64=2.0,
|
||||
min_peak_prominence::Float64=0.1,
|
||||
@ -762,7 +721,7 @@ function detect_peaks_profile(mz::AbstractVector{<:Real}, y::AbstractVector{<:Re
|
||||
n < 3 && return NamedTuple{(:mz, :intensity, :fwhm, :shape_r2, :snr, :prominence), Tuple{Float64, Float64, Float64, Float64, Float64, Float64}}[]
|
||||
|
||||
noise_level = mad(y, normalize=true) + eps(Float64)
|
||||
ys = smooth_spectrum(y; method=:savitzky_golay, window=max(5, 2*half_window+1), order=2) # Use smoothed data for detection
|
||||
ys = smooth_spectrum_core(y; method=:savitzky_golay, window=max(5, 2*half_window+1), order=2) # Use smoothed data for detection
|
||||
|
||||
candidate_peak_indices = Int[]
|
||||
for i in 2:n-1
|
||||
@ -818,16 +777,30 @@ function detect_peaks_profile(mz::AbstractVector{<:Real}, y::AbstractVector{<:Re
|
||||
end
|
||||
|
||||
"""
|
||||
detect_peaks_wavelet(mz, intensity; ...) -> Vector{NamedTuple}
|
||||
detect_peaks_wavelet_core(mz, intensity; ...) -> Vector{NamedTuple}
|
||||
|
||||
Detects peaks using Continuous Wavelet Transform (CWT).
|
||||
Detects peaks using Continuous Wavelet Transform (CWT). CWT is effective at
|
||||
identifying peaks at different scales (widths), making it robust for complex spectra.
|
||||
|
||||
Returns a vector of NamedTuples, each representing a detected peak with:
|
||||
- `mz`: m/z value of the peak
|
||||
- `intensity`: Intensity of the peak
|
||||
- `snr`: Signal-to-Noise Ratio (simplified)
|
||||
# Arguments
|
||||
- `mz::AbstractVector`: The m/z values of the spectrum.
|
||||
- `intensity::AbstractVector`: The intensity values of the spectrum.
|
||||
- `scales`: A range of scales to use for the CWT. Corresponds to the widths of
|
||||
the features to be detected.
|
||||
- `snr_threshold`: The minimum Signal-to-Noise Ratio for a CWT-detected local maximum
|
||||
in the original spectrum to be considered a peak.
|
||||
- `half_window`: Used for calculating peak quality metrics like FWHM and shape R^2.
|
||||
|
||||
# Returns
|
||||
A vector of `NamedTuple`s, each representing a detected peak with:
|
||||
- `mz`: m/z value of the peak.
|
||||
- `intensity`: Intensity of the peak from the original spectrum.
|
||||
- `fwhm`: Full Width at Half Maximum (in ppm).
|
||||
- `shape_r2`: Goodness-of-fit to a Gaussian shape.
|
||||
- `snr`: Signal-to-Noise Ratio.
|
||||
- `prominence`: Peak prominence (estimated as peak intensity for this method).
|
||||
"""
|
||||
function detect_peaks_wavelet(mz::AbstractVector, intensity::AbstractVector; scales=1:10, snr_threshold=3.0, half_window=10)::Vector{NamedTuple{(:mz, :intensity, :fwhm, :shape_r2, :snr, :prominence), Tuple{Float64, Float64, Float64, Float64, Float64, Float64}}}
|
||||
function detect_peaks_wavelet_core(mz::AbstractVector, intensity::AbstractVector; scales=1:10, snr_threshold=3.0, half_window=10)::Vector{NamedTuple{(:mz, :intensity, :fwhm, :shape_r2, :snr, :prominence), Tuple{Float64, Float64, Float64, Float64, Float64, Float64}}}
|
||||
n = length(intensity)
|
||||
n < 10 && return NamedTuple{(:mz, :intensity, :fwhm, :shape_r2, :snr, :prominence), Tuple{Float64, Float64, Float64, Float64, Float64, Float64}}[]
|
||||
|
||||
@ -876,7 +849,7 @@ function detect_peaks_wavelet(mz::AbstractVector, intensity::AbstractVector; sca
|
||||
end
|
||||
|
||||
"""
|
||||
detect_peaks_centroid(mz, y; ...) -> Vector{NamedTuple}
|
||||
detect_peaks_centroid_core(mz, y; ...) -> Vector{NamedTuple}
|
||||
|
||||
Filters peaks in centroid-mode data based on intensity threshold.
|
||||
|
||||
@ -884,7 +857,7 @@ Returns a vector of NamedTuples, each representing a detected peak with:
|
||||
- `mz`: m/z value of the peak
|
||||
- `intensity`: Intensity of the peak
|
||||
"""
|
||||
function detect_peaks_centroid(mz::AbstractVector{<:Real}, y::AbstractVector{<:Real}; snr_threshold::Float64=0.0)
|
||||
function detect_peaks_centroid_core(mz::AbstractVector{<:Real}, y::AbstractVector{<:Real}; snr_threshold::Float64=0.0)
|
||||
noise_level = mad(y, normalize=true) + eps(Float64)
|
||||
detected_peaks = NamedTuple{(:mz, :intensity, :fwhm, :shape_r2, :snr, :prominence), Tuple{Float64, Float64, Float64, Float64, Float64, Float64}}[]
|
||||
|
||||
@ -903,11 +876,11 @@ end
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
align_peaks_lowess(ref_mz, tgt_mz; ...)
|
||||
align_peaks_lowess_core(ref_mz, tgt_mz; ...)
|
||||
|
||||
Enhanced peak alignment with PPM tolerance and other constraints.
|
||||
"""
|
||||
function align_peaks_lowess(ref_mz::Vector{<:Real}, tgt_mz::Vector{<:Real};
|
||||
function align_peaks_lowess_core(ref_mz::Vector{<:Real}, tgt_mz::Vector{<:Real};
|
||||
method::Symbol=:linear, # :linear, :lowess, or :ransac
|
||||
span::Float64=0.75, # Span for LOWESS
|
||||
tolerance::Float64=0.002,
|
||||
@ -1030,15 +1003,15 @@ function align_peaks_lowess(ref_mz::Vector{<:Real}, tgt_mz::Vector{<:Real};
|
||||
end
|
||||
|
||||
"""
|
||||
find_calibration_peaks(mz, intensity, reference_masses; ...)
|
||||
find_calibration_peaks_core(mz, intensity, reference_masses; ...)
|
||||
|
||||
Finds peaks that match a list of reference masses.
|
||||
"""
|
||||
function find_calibration_peaks(mz::AbstractVector, intensity::AbstractVector, reference_masses::AbstractVector; ppm_tolerance=20.0)
|
||||
function find_calibration_peaks_core(mz::AbstractVector, intensity::AbstractVector, reference_masses::AbstractVector; ppm_tolerance=20.0)
|
||||
matched_peaks = Dict{Float64, Float64}()
|
||||
|
||||
# detect_peaks_profile returns Vector{NamedTuple}, so we need to extract mz values
|
||||
detected_peaks_list = detect_peaks_profile(mz, intensity)
|
||||
# detect_peaks_profile_core returns Vector{NamedTuple}, so we need to extract mz values
|
||||
detected_peaks_list = detect_peaks_profile_core(mz, intensity)
|
||||
|
||||
# Extract only the m/z values into a new vector for easier processing
|
||||
detected_mz_values = [p.mz for p in detected_peaks_list]
|
||||
@ -1057,16 +1030,16 @@ function find_calibration_peaks(mz::AbstractVector, intensity::AbstractVector, r
|
||||
end
|
||||
|
||||
"""
|
||||
calibrate_spectra(spectra, internal_standards; ...)
|
||||
calibrate_spectra_core(spectra, internal_standards; ...)
|
||||
|
||||
Calibrates spectra using internal standards.
|
||||
"""
|
||||
function calibrate_spectra(spectra::Vector, internal_standards::Vector; ppm_tolerance=20.0)
|
||||
function calibrate_spectra_core(spectra::Vector, internal_standards::Vector; ppm_tolerance=20.0)
|
||||
calibrated_spectra = similar(spectra)
|
||||
for (i, spec) in enumerate(spectra)
|
||||
mz, intensity = spec[1], spec[2]
|
||||
|
||||
matched_peaks = find_calibration_peaks(mz, intensity, internal_standards; ppm_tolerance=ppm_tolerance)
|
||||
matched_peaks = find_calibration_peaks_core(mz, intensity, internal_standards; ppm_tolerance=ppm_tolerance)
|
||||
if length(matched_peaks) < 2
|
||||
@warn "Spectrum $i: Not enough calibration peaks found. Skipping."
|
||||
calibrated_spectra[i] = spec
|
||||
@ -1092,16 +1065,16 @@ end
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
bin_peaks(all_pk_mz::Vector{Vector{Float64}},
|
||||
bin_peaks_core(all_pk_mz::Vector{Vector{Float64}},
|
||||
all_pk_int::Vector{Vector{Float64}},
|
||||
params::PeakBinningParams) -> Tuple{FeatureMatrix, Vector{Tuple{Float64,Float64}}}
|
||||
params::PeakBinning) -> Tuple{FeatureMatrix, Vector{Tuple{Float64,Float64}}}
|
||||
|
||||
Enhanced peak binning with adaptive and PPM-based parameters, or uniform binning.
|
||||
|
||||
# Arguments
|
||||
- `all_pk_mz`: A vector of m/z vectors for all spectra.
|
||||
- `all_pk_int`: A vector of intensity vectors for all spectra.
|
||||
- `params`: A `PeakBinningParams` struct.
|
||||
- `params`: A `PeakBinning` struct.
|
||||
|
||||
# Returns
|
||||
- `Tuple{FeatureMatrix, Vector{Tuple{Float64,Float64}}}`: A tuple containing the generated FeatureMatrix and the bin definitions.
|
||||
@ -1111,8 +1084,8 @@ The use of `Threads.@threads` in the `:adaptive` and `:uniform` methods is safe.
|
||||
- In the `:adaptive` method, the loop is over the bins (`j` index). Each thread writes only to its assigned column `X[:, j]`, so there are no write conflicts between threads.
|
||||
- In the `:uniform` method, the loop is over the spectra (`s_idx`). Writes to `X[s_idx, bin_idx]` could theoretically conflict if different peaks from the same spectrum (`s_idx`) are processed by different threads. However, the loop is over `s_idx`, meaning each thread handles a distinct spectrum, making writes to `X[s_idx, :]` exclusive to that thread and thus safe.
|
||||
"""
|
||||
function bin_peaks(spectra::Vector{MutableSpectrum},
|
||||
params::PeakBinningParams)
|
||||
function bin_peaks_core(spectra::Vector{MutableSpectrum},
|
||||
params::PeakBinning)
|
||||
ns = length(spectra) # Number of spectra
|
||||
ns == 0 && return FeatureMatrix(zeros(0,0), Tuple{Float64,Float64}[], Int[]), Tuple{Float64,Float64}[]
|
||||
|
||||
|
||||
455
src/PreprocessingPipeline.jl
Normal file
455
src/PreprocessingPipeline.jl
Normal file
@ -0,0 +1,455 @@
|
||||
# src/PreprocessingPipeline.jl
|
||||
|
||||
using Base.Threads # For multithreading
|
||||
using Printf # For @sprintf
|
||||
using Interpolations # For linear_interpolation
|
||||
using DataFrames # For saving feature matrix
|
||||
using CSV # For saving feature matrix
|
||||
|
||||
# This file provides a set of functions that apply preprocessing steps to a vector of
|
||||
# `MutableSpectrum` objects. Each function takes the vector of spectra and a dictionary
|
||||
# of parameters, modifying the spectra in-place where appropriate. This mirrors the
|
||||
# logic from `test/run_preprocessing.jl` but is intended for use in the main application.
|
||||
|
||||
# ===================================================================
|
||||
# PREPROCESSING PIPELINE FUNCTIONS (IN-PLACE)
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
apply_baseline_correction(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Applies baseline correction to the intensity data of each spectrum. This function
|
||||
modifies the `.intensity` field of each `MutableSpectrum` object in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The algorithm to use. Supports `:snip`, `:convex_hull`, `:median`. Defaults to `:snip`.
|
||||
- `:iterations` (Int): The number of iterations for the SNIP algorithm. Defaults to 100.
|
||||
- `:window` (Int): The window size for the Median algorithm. Defaults to 20.
|
||||
"""
|
||||
function apply_baseline_correction(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
method = get(params, :method, :snip)
|
||||
iterations = get(params, :iterations, 100)
|
||||
window = get(params, :window, 20)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
baseline = apply_baseline_correction_core(s.intensity; method=method, iterations=iterations, window=window)
|
||||
s.intensity = max.(0.0, s.intensity .- baseline)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_intensity_transformation(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Applies an intensity transformation to the intensity data of each spectrum. This function
|
||||
modifies the `.intensity` field of each `MutableSpectrum` object in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The transformation to apply. Supports `:sqrt`, `:log`, `:log2`, `:log10`, `:log1p`. Defaults to `:sqrt`.
|
||||
"""
|
||||
function apply_intensity_transformation(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
method = get(params, :method, :sqrt)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
s.intensity = transform_intensity_core(s.intensity; method=method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_smoothing(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Applies a smoothing filter to the intensity data of each spectrum. This function
|
||||
modifies the `.intensity` field of each `MutableSpectrum` object in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The smoothing algorithm. Supports `:savitzky_golay`, `:moving_average`. Defaults to `:savitzky_golay`.
|
||||
- `:window` (Int): The size of the smoothing window. Defaults to 9.
|
||||
- `:order` (Int): The polynomial order for the Savitzky-Golay filter. Defaults to 2.
|
||||
"""
|
||||
function apply_smoothing(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
method = get(params, :method, :savitzky_golay)
|
||||
window = get(params, :window, 9)
|
||||
order = get(params, :order, 2)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
smoothed_intensity = max.(0.0, smooth_spectrum_core(s.intensity; method=method, window=window, order=order))
|
||||
s.intensity = smoothed_intensity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_peak_picking(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Detects peaks in each spectrum and stores them in the `.peaks` field of each
|
||||
`MutableSpectrum` object, modifying it in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The peak detection algorithm. Supports `:profile`, `:wavelet`, `:centroid`. Defaults to `:profile`.
|
||||
- `:snr_threshold` (Float64): Signal-to-Noise Ratio threshold.
|
||||
- `:half_window` (Int): Half-window size for local maxima detection.
|
||||
- `:min_peak_prominence` (Float64): Minimum required prominence for a peak.
|
||||
- `:merge_peaks_tolerance` (Float64): m/z tolerance to merge adjacent peaks.
|
||||
"""
|
||||
function apply_peak_picking(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
method = get(params, :method, :profile)
|
||||
snr_threshold = get(params, :snr_threshold, 3.0)
|
||||
half_window = get(params, :half_window, 10)
|
||||
min_peak_prominence = get(params, :min_peak_prominence, 0.1)
|
||||
merge_peaks_tolerance = get(params, :merge_peaks_tolerance, 0.002)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
if method == :profile
|
||||
s.peaks = detect_peaks_profile_core(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window, min_peak_prominence=min_peak_prominence, merge_peaks_tolerance=merge_peaks_tolerance)
|
||||
elseif method == :wavelet
|
||||
s.peaks = detect_peaks_wavelet_core(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window)
|
||||
elseif method == :centroid
|
||||
s.peaks = detect_peaks_centroid_core(s.mz, s.intensity; snr_threshold=snr_threshold)
|
||||
else
|
||||
s.peaks = detect_peaks_profile_core(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window)
|
||||
end
|
||||
else
|
||||
s.peaks = []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_peak_selection(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Filters peaks within each spectrum based on quality criteria. This step removes peaks
|
||||
that do not meet the specified thresholds for signal-to-noise ratio (SNR),
|
||||
full width at half maximum (FWHM), and peak shape.
|
||||
|
||||
This function modifies the `.peaks` field of each `MutableSpectrum` object in the `spectra` vector in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:min_snr` (Float64): The minimum Signal-to-Noise Ratio required for a peak to be kept.
|
||||
- `:min_fwhm_ppm` (Float64): The minimum FWHM (in ppm) for a peak.
|
||||
- `:max_fwhm_ppm` (Float64): The maximum FWHM (in ppm) for a peak.
|
||||
- `:min_shape_r2` (Float64): The minimum R² value from a Gaussian fit, measuring peak shape quality.
|
||||
"""
|
||||
function apply_peak_selection(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
min_snr = get(params, :min_snr, 0.0)
|
||||
min_fwhm = get(params, :min_fwhm_ppm, 0.0)
|
||||
max_fwhm = get(params, :max_fwhm_ppm, Inf)
|
||||
min_r2 = get(params, :min_shape_r2, 0.0)
|
||||
|
||||
# Handle `nothing` from params, which can happen if pre-calculation fails.
|
||||
min_snr = isnothing(min_snr) ? 0.0 : min_snr
|
||||
min_fwhm = isnothing(min_fwhm) ? 0.0 : min_fwhm
|
||||
max_fwhm = isnothing(max_fwhm) ? Inf : max_fwhm
|
||||
min_r2 = isnothing(min_r2) ? 0.0 : min_r2
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if !isempty(s.peaks)
|
||||
filter!(p ->
|
||||
p.snr >= min_snr &&
|
||||
(min_fwhm <= p.fwhm <= max_fwhm) &&
|
||||
p.shape_r2 >= min_r2,
|
||||
s.peaks
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_calibration(spectra::Vector{MutableSpectrum}, params::Dict, reference_peaks::Dict)
|
||||
|
||||
Performs mass calibration on each spectrum using a list of internal standards.
|
||||
This function modifies the `.mz` axis of each `MutableSpectrum` object in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The calibration method. Only `:internal_standards` is currently meaningful.
|
||||
- `:ppm_tolerance` (Float64): The tolerance in PPM for matching detected peaks to reference masses.
|
||||
- `:fit_order` (Int): The polynomial order for the calibration fit (not yet used in this implementation, defaults to linear).
|
||||
"""
|
||||
function apply_calibration(spectra::Vector{MutableSpectrum}, params::Dict, reference_peaks::Dict)
|
||||
method = get(params, :method, :none)
|
||||
ppm_tolerance = get(params, :ppm_tolerance, 20.0)
|
||||
|
||||
if method == :none || isempty(reference_peaks)
|
||||
return
|
||||
end
|
||||
|
||||
reference_masses = collect(keys(reference_peaks))
|
||||
|
||||
Threads.@threads for i in 1:length(spectra)
|
||||
s = spectra[i]
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
matched_peaks = find_calibration_peaks_core(s.mz, s.intensity, reference_masses; ppm_tolerance=ppm_tolerance)
|
||||
if length(matched_peaks) >= 2
|
||||
measured = sort(collect(values(matched_peaks)))
|
||||
theoretical = sort(collect(keys(matched_peaks)))
|
||||
itp = linear_interpolation(measured, theoretical, extrapolation_bc=Line())
|
||||
s.mz = itp(s.mz) # Modify mz-axis in-place
|
||||
else
|
||||
@warn "Spectrum $(s.id): insufficient reference peaks ($(length(matched_peaks)) found), skipping calibration."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_peak_alignment(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Aligns the m/z axis of all spectra to a chosen reference spectrum. This function
|
||||
modifies both the `.mz` axis and the m/z values within the `.peaks` field of each
|
||||
`MutableSpectrum` object in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The alignment algorithm. Supports `:lowess`, `:linear`, `:ransac`.
|
||||
- `:tolerance` (Float64): The tolerance for matching peaks between spectra.
|
||||
- `:tolerance_unit` (Symbol): The unit for tolerance, `:mz` or `:ppm`.
|
||||
"""
|
||||
function apply_peak_alignment(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
method = get(params, :method, :none)
|
||||
tolerance = get(params, :tolerance, 0.002)
|
||||
tolerance_unit = get(params, :tolerance_unit, :mz)
|
||||
|
||||
if method == :none
|
||||
return
|
||||
end
|
||||
|
||||
ref_find_idx = findfirst(s -> !isempty(s.peaks), spectra)
|
||||
if ref_find_idx === nothing
|
||||
@warn "Insufficient spectra with peaks for alignment. Skipping."
|
||||
return
|
||||
end
|
||||
|
||||
ref_spectrum = spectra[ref_find_idx]
|
||||
ref_peaks_mz = [p.mz for p in ref_spectrum.peaks]
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if s.id == ref_spectrum.id || isempty(s.peaks)
|
||||
continue
|
||||
end
|
||||
|
||||
current_peaks_mz = [p.mz for p in s.peaks]
|
||||
alignment_func = align_peaks_lowess_core(ref_peaks_mz, current_peaks_mz; method=method, tolerance=tolerance, tolerance_unit=tolerance_unit)
|
||||
|
||||
s.mz = alignment_func.(s.mz) # Update m/z axis
|
||||
|
||||
# Update peak m/z values
|
||||
for i in 1:length(s.peaks)
|
||||
old_peak = s.peaks[i]
|
||||
aligned_peak_mz = alignment_func(old_peak.mz)
|
||||
s.peaks[i] = (mz=aligned_peak_mz, intensity=old_peak.intensity, fwhm=old_peak.fwhm, shape_r2=old_peak.shape_r2, snr=old_peak.snr, prominence=old_peak.prominence)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
apply_normalization(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Applies intensity normalization to each spectrum. This function modifies the
|
||||
`.intensity` field of each `MutableSpectrum` object in-place.
|
||||
|
||||
# Parameters from `params` Dict:
|
||||
- `:method` (Symbol): The normalization method. Supports `:tic`, `:median`, `:rms`, `:none`.
|
||||
"""
|
||||
function apply_normalization(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
method = get(params, :method, :tic)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
s.intensity = apply_normalization_core(s.intensity; method=method)
|
||||
end
|
||||
end
|
||||
end
|
||||
function apply_peak_binning(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
tolerance = get(params, :tolerance, 20.0)
|
||||
tolerance_unit = get(params, :tolerance_unit, :ppm)
|
||||
min_peak_per_bin = get(params, :min_peak_per_bin, 3)
|
||||
|
||||
if isempty(spectra) || all(s -> isempty(s.peaks), spectra)
|
||||
@warn "No peaks found for binning. Returning empty feature matrix."
|
||||
return nothing, nothing
|
||||
end
|
||||
|
||||
all_peaks = Vector{Tuple{Float64, Float64}}()
|
||||
for s in spectra
|
||||
for p in s.peaks
|
||||
push!(all_peaks, (p.mz, p.intensity))
|
||||
end
|
||||
end
|
||||
|
||||
if isempty(all_peaks)
|
||||
@warn "No peaks collected for binning."
|
||||
return nothing, nothing
|
||||
end
|
||||
|
||||
sort!(all_peaks, by=x->x[1])
|
||||
|
||||
bin_centers = Float64[]
|
||||
bin_intensities = Float64[]
|
||||
|
||||
i = 1
|
||||
while i <= length(all_peaks)
|
||||
current_bin_start = i
|
||||
current_peak = all_peaks[i]
|
||||
|
||||
j = i + 1
|
||||
while j <= length(all_peaks)
|
||||
next_peak = all_peaks[j]
|
||||
tol = (tolerance_unit == :ppm) ? (current_peak[1] * tolerance / 1e6) : tolerance
|
||||
|
||||
if (next_peak[1] - current_peak[1]) <= tol
|
||||
j += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
current_bin_end = j - 1
|
||||
bin_size = current_bin_end - current_bin_start + 1
|
||||
|
||||
if bin_size >= min_peak_per_bin
|
||||
bin_peaks = all_peaks[current_bin_start:current_bin_end]
|
||||
|
||||
mz_sum = sum(p[1] for p in bin_peaks)
|
||||
intensity_sum = sum(p[2] for p in bin_peaks)
|
||||
|
||||
mz_center = mz_sum / bin_size
|
||||
avg_intensity = intensity_sum / bin_size
|
||||
|
||||
push!(bin_centers, mz_center)
|
||||
push!(bin_intensities, avg_intensity)
|
||||
end
|
||||
|
||||
i = j
|
||||
end
|
||||
|
||||
if !isempty(bin_centers)
|
||||
n_bins = length(bin_centers)
|
||||
feature_matrix = Matrix{Float64}(undef, 2, n_bins)
|
||||
|
||||
for i in 1:n_bins
|
||||
feature_matrix[1, i] = bin_centers[i]
|
||||
feature_matrix[2, i] = bin_intensities[i]
|
||||
end
|
||||
|
||||
bin_info = [(bin_centers[i], bin_intensities[i]) for i in 1:n_bins]
|
||||
return feature_matrix, bin_info
|
||||
else
|
||||
@warn "No bins created after filtering"
|
||||
return nothing, nothing
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
save_feature_matrix(feature_matrix::Matrix{Float64}, bin_info, output_dir::String) -> Tuple{String, String}
|
||||
|
||||
Saves the aggregated `2 x n_bins` feature matrix into two different CSV formats.
|
||||
|
||||
1. **Simple Format (`feature_matrix_simple.csv`):** A two-column CSV with "mz" and "intensity".
|
||||
2. **Standard Format (`feature_matrix_standard.csv`):** A row-based format where m/z values are headers and there is a single data row for the aggregated spectrum.
|
||||
|
||||
# Arguments
|
||||
- `feature_matrix::Matrix{Float64}`: The `2 x n_bins` matrix from `apply_peak_binning`.
|
||||
- `bin_info`: The associated bin information (currently unused but kept for compatibility).
|
||||
- `output_dir::String`: The directory where the output CSV files will be saved.
|
||||
|
||||
# Returns
|
||||
- A tuple containing the paths to the two saved files.
|
||||
"""
|
||||
function save_feature_matrix(feature_matrix::Matrix{Float64}, bin_info, output_dir::String)
|
||||
# Save as simple CSV with m/z and intensity rows
|
||||
csv_path = joinpath(output_dir, "feature_matrix_simple.csv")
|
||||
|
||||
open(csv_path, "w") do io
|
||||
write(io, "mz,intensity\n")
|
||||
for i in 1:size(feature_matrix, 2)
|
||||
mz = feature_matrix[1, i]
|
||||
intensity = feature_matrix[2, i]
|
||||
write(io, "$mz,$intensity\n")
|
||||
end
|
||||
end
|
||||
@info "Saved simple feature matrix: $csv_path"
|
||||
|
||||
# Also save in a more standard format for MSI
|
||||
csv_path_standard = joinpath(output_dir, "feature_matrix_standard.csv")
|
||||
|
||||
open(csv_path_standard, "w") do io
|
||||
write(io, "sample_type,")
|
||||
mz_headers = [@sprintf("mz_%.4f", feature_matrix[1, i]) for i in 1:size(feature_matrix, 2)]
|
||||
write(io, join(mz_headers, ",") * "\n")
|
||||
|
||||
write(io, "aggregated_spectrum,")
|
||||
intensity_values = [feature_matrix[2, i] for i in 1:size(feature_matrix, 2)]
|
||||
write(io, join(string.(intensity_values), ",") * "\n")
|
||||
end
|
||||
@info "Saved standard format matrix: $csv_path_standard"
|
||||
|
||||
return csv_path, csv_path_standard
|
||||
end
|
||||
|
||||
function execute_full_preprocessing(spectra::Vector{MutableSpectrum}, params::Dict,
|
||||
pipeline_steps::Vector{String}, reference_peaks::Dict,
|
||||
mask_path::Union{String, Nothing}=nothing;
|
||||
progress_callback::Function=(step -> nothing))
|
||||
|
||||
println("Starting preprocessing pipeline with $(length(spectra)) spectra")
|
||||
println("Steps: $(join(pipeline_steps, " -> "))")
|
||||
|
||||
# These variables will be populated by the pipeline steps
|
||||
feature_matrix = nothing
|
||||
bin_definitions = nothing
|
||||
|
||||
# Apply pipeline steps, modifying `spectra` in-place
|
||||
for step in pipeline_steps
|
||||
progress_callback(step)
|
||||
println("\n" * "-"^60)
|
||||
println("PROCESSING STEP: $step")
|
||||
println("-"^60)
|
||||
|
||||
if step == "stabilization"
|
||||
println(" Applying intensity transformation (stabilization)")
|
||||
apply_intensity_transformation(spectra, get(params, :Stabilization, Dict()))
|
||||
|
||||
elseif step == "baseline_correction"
|
||||
println(" Applying baseline correction")
|
||||
apply_baseline_correction(spectra, get(params, :BaselineCorrection, Dict()))
|
||||
|
||||
elseif step == "smoothing"
|
||||
println(" Applying smoothing")
|
||||
apply_smoothing(spectra, get(params, :Smoothing, Dict()))
|
||||
|
||||
elseif step == "peak_picking"
|
||||
println(" Applying peak picking")
|
||||
apply_peak_picking(spectra, get(params, :PeakPicking, Dict()))
|
||||
|
||||
elseif step == "peak_selection"
|
||||
println(" Applying peak selection")
|
||||
apply_peak_selection(spectra, get(params, :PeakSelection, Dict()))
|
||||
|
||||
elseif step == "calibration"
|
||||
println(" Applying calibration")
|
||||
apply_calibration(spectra, get(params, :Calibration, Dict()), reference_peaks)
|
||||
|
||||
elseif step == "peak_alignment"
|
||||
println(" Applying peak alignment")
|
||||
apply_peak_alignment(spectra, get(params, :PeakAlignment, Dict()))
|
||||
|
||||
elseif step == "normalization"
|
||||
println(" Applying normalization")
|
||||
apply_normalization(spectra, get(params, :Normalization, Dict()))
|
||||
|
||||
elseif step == "peak_binning"
|
||||
println(" Applying peak binning")
|
||||
feature_matrix, bin_definitions = apply_peak_binning(spectra, get(params, :PeakBinning, Dict()))
|
||||
|
||||
else
|
||||
@warn "Unknown step: $step, skipping"
|
||||
end
|
||||
|
||||
println("✓ Completed step: $step")
|
||||
end
|
||||
|
||||
return feature_matrix, bin_definitions
|
||||
end
|
||||
43
src/mzML.jl
43
src/mzML.jl
@ -20,6 +20,7 @@ const DATA_FORMAT_ACCESSIONS = Dict{String, DataType}(
|
||||
)
|
||||
|
||||
function parse_instrument_metadata_mzml(stream::IO)
|
||||
println("DEBUG: Starting mzML instrument metadata parsing...")
|
||||
# Initialize with default values from the InstrumentMetadata constructor
|
||||
instrument_meta = InstrumentMetadata()
|
||||
|
||||
@ -67,37 +68,53 @@ function parse_instrument_metadata_mzml(stream::IO)
|
||||
|
||||
if acc == "MS:1000031" # instrument model
|
||||
instrument_model = val
|
||||
#println("DEBUG: Instrument model: $instrument_model")
|
||||
elseif acc == "MS:1001496" # mass resolving power (more specific)
|
||||
resolution = tryparse(Float64, val)
|
||||
#println("DEBUG: Resolution (specific): $resolution")
|
||||
elseif acc == "MS:1000011" && resolution === nothing # resolution (less specific)
|
||||
resolution = tryparse(Float64, val)
|
||||
#println("DEBUG: Resolution (less specific): $resolution")
|
||||
elseif acc == "MS:1000016" # mass accuracy (ppm)
|
||||
mass_accuracy_ppm = tryparse(Float64, val)
|
||||
#println("DEBUG: Mass accuracy (ppm): $mass_accuracy_ppm")
|
||||
elseif acc == "MS:1000130" # positive scan
|
||||
polarity = :positive
|
||||
#println("DEBUG: Polarity: positive")
|
||||
elseif acc == "MS:1000129" # negative scan
|
||||
polarity = :negative
|
||||
#println("DEBUG: Polarity: negative")
|
||||
elseif acc == "MS:1000592" # external calibration
|
||||
calibration_status = :external
|
||||
#println("DEBUG: Calibration status: external")
|
||||
elseif acc == "MS:1000593" # internal calibration
|
||||
calibration_status = :internal
|
||||
#println("DEBUG: Calibration status: internal")
|
||||
elseif acc == "MS:1000747" && calibration_status == :uncalibrated # instrument specific calibration
|
||||
calibration_status = :internal # Assume as a form of internal calibration
|
||||
#println("DEBUG: Calibration status: instrument specific (internal)")
|
||||
elseif acc == "MS:1000867" # laser wavelength
|
||||
laser_settings["wavelength_nm"] = tryparse(Float64, val)
|
||||
#println("DEBUG: Laser wavelength: $(laser_settings["wavelength_nm"]) nm")
|
||||
elseif acc == "MS:1000868" # laser fluence
|
||||
laser_settings["fluence"] = tryparse(Float64, val)
|
||||
#println("DEBUG: Laser fluence: $(laser_settings["fluence"])")
|
||||
elseif acc == "MS:1000869" # laser repetition rate
|
||||
laser_settings["repetition_rate_hz"] = tryparse(Float64, val)
|
||||
#println("DEBUG: Laser repetition rate: $(laser_settings["repetition_rate_hz"]) Hz")
|
||||
# NEW: Vendor Preprocessing terms
|
||||
elseif acc == "MS:1000579" # baseline correction
|
||||
push!(vendor_preprocessing_steps, "Baseline Correction")
|
||||
#println("DEBUG: Vendor preprocessing step: Baseline Correction")
|
||||
elseif acc == "MS:1000580" # smoothing
|
||||
push!(vendor_preprocessing_steps, "Smoothing")
|
||||
#println("DEBUG: Vendor preprocessing step: Smoothing")
|
||||
elseif acc == "MS:1000578" # data transformation (e.g., centroiding)
|
||||
push!(vendor_preprocessing_steps, "Data Transformation: $(name)")
|
||||
#println("DEBUG: Vendor preprocessing step: Data Transformation: $(name)")
|
||||
elseif acc == "MS:1000800" # deisotoping
|
||||
push!(vendor_preprocessing_steps, "Deisotoping")
|
||||
#println("DEBUG: Vendor preprocessing step: Deisotoping")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -106,6 +123,8 @@ function parse_instrument_metadata_mzml(stream::IO)
|
||||
@warn "Could not fully parse instrument metadata from mzML header. Using defaults. Error: $e"
|
||||
end
|
||||
|
||||
println("DEBUG: Finished mzML instrument metadata parsing.")
|
||||
|
||||
# Always return a valid object
|
||||
return InstrumentMetadata(
|
||||
resolution,
|
||||
@ -137,14 +156,15 @@ determine the data type, compression, and axis type.
|
||||
function get_spectrum_asset_metadata(stream::IO)
|
||||
start_pos = position(stream)
|
||||
|
||||
#println("DEBUG: Entering get_spectrum_asset_metadata to parse binaryDataArray...")
|
||||
|
||||
bda_tag = find_tag(stream, r"<binaryDataArray\s+encodedLength=\"(\d+)\"")
|
||||
|
||||
if bda_tag === nothing
|
||||
|
||||
throw(FileFormatError("Cannot find binaryDataArray"))
|
||||
|
||||
end
|
||||
encoded_length = parse(Int32, bda_tag.captures[1])
|
||||
#println("DEBUG: Encoded length: $encoded_length")
|
||||
|
||||
# Initialize parameters as separate variables with concrete types
|
||||
data_format::DataType = Float64
|
||||
@ -166,14 +186,19 @@ function get_spectrum_asset_metadata(stream::IO)
|
||||
# Use constant comparisons and dictionary lookup for better performance
|
||||
if acc_str == MZ_AXIS_ACCESSION
|
||||
axis = :mz
|
||||
#println("DEBUG: Axis type identified as: m/z")
|
||||
elseif acc_str == INTENSITY_AXIS_ACCESSION
|
||||
axis = :intensity
|
||||
#println("DEBUG: Axis type identified as: intensity")
|
||||
elseif haskey(DATA_FORMAT_ACCESSIONS, acc_str)
|
||||
data_format = DATA_FORMAT_ACCESSIONS[acc_str]
|
||||
#println("DEBUG: Data format identified as: $data_format")
|
||||
elseif acc_str == COMPRESSION_ACCESSION
|
||||
compression_flag = true
|
||||
#println("DEBUG: Compression: true")
|
||||
elseif acc_str == NO_COMPRESSION_ACCESSION
|
||||
compression_flag = false
|
||||
#println("DEBUG: Compression: false")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -181,9 +206,11 @@ function get_spectrum_asset_metadata(stream::IO)
|
||||
seek(stream, start_pos)
|
||||
readuntil(stream, "<binary>")
|
||||
binary_offset = position(stream)
|
||||
#println("DEBUG: Binary data offset: $binary_offset")
|
||||
|
||||
# Move stream to the end of the binary data array for the next iteration
|
||||
readuntil(stream, "</binaryDataArray>")
|
||||
#println("DEBUG: Exiting get_spectrum_asset_metadata.")
|
||||
|
||||
# Create SpectrumAsset directly from the variables
|
||||
return SpectrumAsset(data_format, compression_flag, binary_offset, encoded_length, axis)
|
||||
@ -223,13 +250,16 @@ function parse_spectrum_metadata(stream::IO, offset::Int64)
|
||||
|
||||
id_match = match(r"<spectrum\s+index=\"\d+\"\s+id=\"([^\"]+)", spectrum_xml)
|
||||
id = id_match === nothing ? "" : id_match.captures[1]
|
||||
#println("DEBUG: Parsing spectrum ID: $id")
|
||||
|
||||
# Determine mode from the XML block
|
||||
mode = UNKNOWN
|
||||
if occursin("MS:1000127", spectrum_xml)
|
||||
mode = CENTROID
|
||||
#println("DEBUG: Spectrum mode: CENTROID")
|
||||
elseif occursin("MS:1000128", spectrum_xml)
|
||||
mode = PROFILE
|
||||
#println("DEBUG: Spectrum mode: PROFILE")
|
||||
end
|
||||
|
||||
# Find where the binary data list starts to parse assets
|
||||
@ -248,6 +278,10 @@ function parse_spectrum_metadata(stream::IO, offset::Int64)
|
||||
(asset2, asset1)
|
||||
end
|
||||
|
||||
#println("DEBUG: m/z Asset - Format: $(mz_asset.format), Compressed: $(mz_asset.is_compressed), Offset: $(mz_asset.offset), Encoded Length: $(mz_asset.encoded_length)")
|
||||
#println("DEBUG: Intensity Asset - Format: $(int_asset.format), Compressed: $(int_asset.is_compressed), Offset: $(int_asset.offset), Encoded Length: $(int_asset.encoded_length)")
|
||||
#println("DEBUG: Finished parsing spectrum ID: $id metadata.")
|
||||
|
||||
# Create the new unified metadata object
|
||||
# For mzML, x and y coordinates are not applicable, so we use 0.
|
||||
return SpectrumMetadata(Int32(0), Int32(0), id, :sample, mode, mz_asset, int_asset)
|
||||
@ -380,12 +414,14 @@ function load_mzml_lazy(file_path::String; cache_size::Int=100)
|
||||
println("DEBUG: Processed $i/$num_spectra spectra")
|
||||
end
|
||||
end
|
||||
println("DEBUG: Metadata parsing complete.")
|
||||
println("DEBUG: Metadata parsing complete for all $num_spectra spectra.")
|
||||
|
||||
# Assuming uniform data formats, take from the first spectrum
|
||||
first_meta = spectra_metadata[1]
|
||||
mz_format = first_meta.mz_asset.format
|
||||
intensity_format = first_meta.int_asset.format
|
||||
println("DEBUG: Inferred global m/z format: $mz_format")
|
||||
println("DEBUG: Inferred global intensity format: $intensity_format")
|
||||
|
||||
# --- NEW: Determine overall acquisition mode ---
|
||||
modes = [meta.mode for meta in spectra_metadata]
|
||||
@ -401,6 +437,7 @@ function load_mzml_lazy(file_path::String; cache_size::Int=100)
|
||||
else
|
||||
:unknown
|
||||
end
|
||||
println("DEBUG: Inferred overall acquisition mode: $acq_mode_symbol (Centroid: $num_centroid, Profile: $num_profile)")
|
||||
|
||||
final_instrument_meta = InstrumentMetadata(
|
||||
instrument_meta.resolution,
|
||||
|
||||
@ -2,221 +2,322 @@ using CairoMakie
|
||||
using Statistics
|
||||
using Colors
|
||||
|
||||
function create_msi_parameter_plot()
|
||||
# Create the figure with subplots for each preprocessing step
|
||||
fig = Figure(size=(1600, 1200), fontsize=12)
|
||||
"""
|
||||
Plots the effect of baseline correction, showing how different numbers of SNIP
|
||||
iterations affect the estimated baseline.
|
||||
"""
|
||||
function plot_baseline_correction_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Baseline Correction (SNIP)", xlabel="m/z", ylabel="Intensity")
|
||||
|
||||
# Define the preprocessing steps and their parameters
|
||||
steps = [
|
||||
("BaselineCorrection", ["iterations: 10", "method: SNIP", "window: 3.0"]),
|
||||
("Calibration", ["fit_order: 2", "method: internal_standards", "ppm_tolerance: 20.0"]),
|
||||
("Normalization", ["method: rms"]),
|
||||
("PeakAlignment", ["max_shift_ppm: 50.0", "method: linear", "tolerance: 20.0 ppm"]),
|
||||
("PeakBinningParams", ["max_bin_width_ppm: 60.0", "method: adaptive", "min_peak_per_bin: 3"]),
|
||||
("PeakPicking", ["half_window: 5", "method: centroid", "snr_threshold: 15.0"]),
|
||||
("PeakSelection", ["max_fwhm_ppm: 95.39", "min_fwhm_ppm: 19.08", "min_snr: 3.0"]),
|
||||
("Smoothing", ["method: savitzky_golay", "order: 3", "window: 5"])
|
||||
]
|
||||
mz_range = range(200, 1000, length=1500)
|
||||
true_signal = 1.2 .* exp.(-0.001 .* (mz_range .- 450).^2) .+ 0.8 .* exp.(-0.002 .* (mz_range .- 750).^2)
|
||||
baseline_comp = 0.15 .+ 0.1 .* sin.(mz_range ./ 60) .+ 0.0001 .* mz_range
|
||||
noise = 0.04 .* randn(length(mz_range))
|
||||
raw_signal = true_signal .+ baseline_comp .+ noise
|
||||
|
||||
# Create a grid of subplots
|
||||
g = fig[1, 1] = GridLayout()
|
||||
|
||||
# Plot each preprocessing step
|
||||
for (idx, (step_name, params)) in enumerate(steps)
|
||||
row, col = fldmod1(idx, 2)
|
||||
ax = Axis(g[row, col], title=step_name, titlesize=14)
|
||||
|
||||
# Generate simulated m/z values and intensities
|
||||
mz_range = range(100, 1000, length=500)
|
||||
|
||||
if step_name == "BaselineCorrection"
|
||||
# Simulate spectrum with baseline
|
||||
true_signal = 0.5 .* exp.(-0.001 .* (mz_range .- 400).^2) .+
|
||||
0.3 .* exp.(-0.002 .* (mz_range .- 600).^2)
|
||||
baseline = 0.1 .+ 0.05 .* sin.(mz_range ./ 50)
|
||||
noisy_signal = true_signal .+ baseline .+ 0.02 .* randn(length(mz_range))
|
||||
corrected = noisy_signal .- baseline
|
||||
|
||||
lines!(ax, mz_range, noisy_signal, color=:blue, linewidth=2, label="Raw")
|
||||
lines!(ax, mz_range, baseline, color=:red, linewidth=2, linestyle=:dash, label="Baseline")
|
||||
lines!(ax, mz_range, corrected, color=:green, linewidth=2, label="Corrected")
|
||||
|
||||
elseif step_name == "Calibration"
|
||||
# Simulate calibration shift
|
||||
reference_peaks = [200, 400, 600, 800]
|
||||
measured_peaks = reference_peaks .+ 2.0 .* randn(length(reference_peaks))
|
||||
|
||||
scatter!(ax, reference_peaks, fill(0.5, length(reference_peaks)),
|
||||
color=:red, markersize=15, label="Reference")
|
||||
scatter!(ax, measured_peaks, fill(0.3, length(measured_peaks)),
|
||||
color=:blue, markersize=10, label="Measured")
|
||||
|
||||
# Add calibration lines
|
||||
for i in 1:length(reference_peaks)
|
||||
lines!(ax, [measured_peaks[i], reference_peaks[i]], [0.3, 0.5],
|
||||
color=:black, linewidth=1, linestyle=:dash)
|
||||
end
|
||||
|
||||
elseif step_name == "Normalization"
|
||||
# Simulate normalization effect
|
||||
spectra = [
|
||||
0.8 .* exp.(-0.001 .* (mz_range .- 300).^2) .+ 0.2 .* randn(length(mz_range)),
|
||||
1.2 .* exp.(-0.001 .* (mz_range .- 300).^2) .+ 0.2 .* randn(length(mz_range)),
|
||||
0.9 .* exp.(-0.001 .* (mz_range .- 300).^2) .+ 0.2 .* randn(length(mz_range))
|
||||
]
|
||||
|
||||
normalized_spectra = [spec ./ std(spec) for spec in spectra]
|
||||
|
||||
for (i, spec) in enumerate(spectra)
|
||||
lines!(ax, mz_range, spec .+ i*0.3, color=RGBA(1, 0, 0, 0.6), linewidth=2,
|
||||
label=i==1 ? "Before Norm" : "")
|
||||
end
|
||||
for (i, spec) in enumerate(normalized_spectra)
|
||||
lines!(ax, mz_range, spec .+ i*0.3, color=RGBA(0, 0, 1, 0.6), linewidth=2,
|
||||
label=i==1 ? "After Norm" : "")
|
||||
end
|
||||
|
||||
elseif step_name == "PeakAlignment"
|
||||
# Simulate peak alignment
|
||||
base_peaks = [300, 500, 700]
|
||||
shifts = [-15, 5, 10]
|
||||
|
||||
for (i, shift) in enumerate(shifts)
|
||||
shifted_peaks = base_peaks .+ shift
|
||||
aligned_peaks = base_peaks
|
||||
|
||||
scatter!(ax, shifted_peaks, fill(i, length(shifted_peaks)),
|
||||
color=:red, markersize=12, label=i==1 ? "Before Align" : "")
|
||||
scatter!(ax, aligned_peaks, fill(i+0.3, length(aligned_peaks)),
|
||||
color=:green, markersize=12, label=i==1 ? "After Align" : "")
|
||||
|
||||
# Show alignment lines
|
||||
for j in 1:length(base_peaks)
|
||||
lines!(ax, [shifted_peaks[j], aligned_peaks[j]], [i, i+0.3],
|
||||
color=:black, linewidth=1, linestyle=:dash)
|
||||
# Simplified SNIP simulation for visualization
|
||||
function simple_snip(y, iterations)
|
||||
b = copy(y)
|
||||
for _ in 1:iterations
|
||||
for i in 2:length(b)-1
|
||||
b[i] = min(b[i], 0.5 * (b[i-1] + b[i+1]))
|
||||
end
|
||||
end
|
||||
|
||||
elseif step_name == "PeakBinningParams"
|
||||
# Simulate peak binning with centroids
|
||||
raw_peaks_mz = 100:25:900
|
||||
raw_peaks_intensity = rand(length(raw_peaks_mz))
|
||||
|
||||
# Create binned peaks (wider bins)
|
||||
bin_centers = 150:60:850
|
||||
bin_intensities = [sum(raw_peaks_intensity[abs.(raw_peaks_mz .- center) .< 30])
|
||||
for center in bin_centers] .* 0.8
|
||||
|
||||
# Profile mode (continuous)
|
||||
profile_signal = zeros(length(mz_range))
|
||||
for (mz, int) in zip(raw_peaks_mz, raw_peaks_intensity)
|
||||
profile_signal .+= int .* exp.(-0.001 .* (mz_range .- mz).^2)
|
||||
return b
|
||||
end
|
||||
|
||||
lines!(ax, mz_range, profile_signal, color=:blue, linewidth=2, label="Profile")
|
||||
barplot!(ax, bin_centers, bin_intensities, color=RGBA(1, 0, 0, 0.7),
|
||||
width=50, label="Binned Centroids")
|
||||
baseline_iter_20 = simple_snip(raw_signal, 20)
|
||||
baseline_iter_200 = simple_snip(raw_signal, 200)
|
||||
corrected_signal = raw_signal .- baseline_iter_200
|
||||
|
||||
elseif step_name == "PeakPicking"
|
||||
# Simulate peak picking from profile to centroids
|
||||
profile_signal = 0.6 .* exp.(-0.0005 .* (mz_range .- 400).^2) .+
|
||||
0.4 .* exp.(-0.0008 .* (mz_range .- 650).^2) .+
|
||||
0.1 .* randn(length(mz_range))
|
||||
lines!(ax, mz_range, raw_signal, color=(:grey, 0.6), label="Raw Signal")
|
||||
lines!(ax, mz_range, corrected_signal, color=:green, linewidth=2.5, label="Corrected Signal")
|
||||
l1 = lines!(ax, mz_range, baseline_iter_20, color=(:red, 0.7), linestyle=:dash, linewidth=2, label="Baseline (iterations: 20)")
|
||||
l2 = lines!(ax, mz_range, baseline_iter_200, color=:red, linewidth=2.5, label="Baseline (iterations: 200)")
|
||||
|
||||
# Simulate picked peaks (centroids)
|
||||
peak_positions = [380, 405, 640, 660]
|
||||
peak_intensities = [0.5, 0.6, 0.35, 0.4]
|
||||
|
||||
lines!(ax, mz_range, profile_signal, color=:blue, linewidth=2, label="Profile Spectrum")
|
||||
scatter!(ax, peak_positions, peak_intensities, color=:red, markersize=20,
|
||||
label="Picked Centroids", strokewidth=2)
|
||||
|
||||
elseif step_name == "PeakSelection"
|
||||
# Simulate peak selection based on criteria
|
||||
all_peaks_mz = 200:50:800
|
||||
all_peaks_fwhm = rand(length(all_peaks_mz)) .* 100 .+ 10
|
||||
all_peaks_snr = rand(length(all_peaks_mz)) .* 10
|
||||
|
||||
# Selection criteria
|
||||
selected = (all_peaks_fwhm .>= 19.08) .& (all_peaks_fwhm .<= 95.39) .& (all_peaks_snr .>= 3.0)
|
||||
|
||||
scatter!(ax, all_peaks_mz[.!selected], all_peaks_fwhm[.!selected],
|
||||
color=:red, markersize=15, label="Rejected")
|
||||
scatter!(ax, all_peaks_mz[selected], all_peaks_fwhm[selected],
|
||||
color=:green, markersize=15, label="Selected")
|
||||
|
||||
# Add selection criteria lines
|
||||
hlines!(ax, [19.08, 95.39], color=:black, linestyle=:dash, linewidth=2)
|
||||
text!(ax, 850, 50; text="FWHM bounds", color=:black, fontsize=10)
|
||||
|
||||
elseif step_name == "Smoothing"
|
||||
# Simulate smoothing effect
|
||||
true_signal = 0.7 .* exp.(-0.001 .* (mz_range .- 450).^2) .+
|
||||
0.5 .* exp.(-0.0008 .* (mz_range .- 650).^2)
|
||||
noisy_signal = true_signal .+ 0.1 .* randn(length(mz_range))
|
||||
|
||||
# Simple smoothing simulation
|
||||
smoothed = similar(noisy_signal)
|
||||
window = 5
|
||||
for i in 1:length(noisy_signal)
|
||||
start_idx = max(1, i - window ÷ 2)
|
||||
end_idx = min(length(noisy_signal), i + window ÷ 2)
|
||||
smoothed[i] = mean(noisy_signal[start_idx:end_idx])
|
||||
end
|
||||
|
||||
lines!(ax, mz_range, noisy_signal, color=:red, linewidth=1, label="Noisy")
|
||||
lines!(ax, mz_range, smoothed, color=:blue, linewidth=2, label="Smoothed")
|
||||
lines!(ax, mz_range, true_signal, color=:green, linewidth=1, linestyle=:dash, label="True")
|
||||
end
|
||||
|
||||
# Add parameter text using a more reliable approach
|
||||
param_text = join(params, "\n")
|
||||
|
||||
text!(ax, param_text, position=Point2f(0.05, 0.95),
|
||||
space=:relative, align=(:left, :top), color=:black,
|
||||
fontsize=10, font=:regular)
|
||||
|
||||
# Add legend for selected plots
|
||||
if idx <= 4
|
||||
axislegend(ax, position=:rt, framevisible=true, backgroundcolor=RGBA(1,1,1,0.8))
|
||||
end
|
||||
|
||||
# Customize axes
|
||||
ax.xlabel = "m/z"
|
||||
ax.ylabel = idx in [1,3,5,7] ? "Intensity" : ""
|
||||
ax.xgridvisible = false
|
||||
ax.ygridvisible = false
|
||||
end
|
||||
|
||||
# Add overall title
|
||||
Label(fig[0, :], "MSI Preprocessing Pipeline Parameters and Simulations",
|
||||
fontsize=18, font=:bold, padding=(0, 0, 10, 0))
|
||||
|
||||
# Add explanation
|
||||
explanation = """
|
||||
Simulation of MSI preprocessing parameters showing:
|
||||
• Blue lines: Profile/continuous spectra
|
||||
• Red bars/points: Centroid data
|
||||
• Dashed lines: Reference/true signals
|
||||
• Green: Processed/corrected data
|
||||
• Each subplot demonstrates key parameters for the preprocessing step
|
||||
"""
|
||||
|
||||
Label(fig[2, :], explanation, fontsize=12, tellwidth=false, padding=(10, 10, 10, 10))
|
||||
|
||||
# Adjust layout
|
||||
colgap!(g, 20)
|
||||
rowgap!(g, 20)
|
||||
|
||||
fig
|
||||
text!(ax, "Parameter: `iterations`\nMore iterations result in a more aggressive baseline that follows the signal floor more closely.",
|
||||
position=Point2f(0.05, 0.95), space=:relative, align=(:left, :top), fontsize=12)
|
||||
axislegend(ax, position=:rt)
|
||||
save("descriptive_plot_baseline_correction.png", fig)
|
||||
println("Saved: descriptive_plot_baseline_correction.png")
|
||||
end
|
||||
|
||||
# Create and display the plot
|
||||
fig = create_msi_parameter_plot()
|
||||
"""
|
||||
Plots the effect of smoothing, comparing different window sizes.
|
||||
"""
|
||||
function plot_smoothing_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Smoothing (Savitzky-Golay)", xlabel="m/z", ylabel="Intensity")
|
||||
|
||||
# Save the plot
|
||||
save("msi_preprocessing_parameters.png", fig)
|
||||
println("Plot saved as 'msi_preprocessing_parameters.png'")
|
||||
mz_range = range(400, 700, length=1000)
|
||||
true_signal = 0.8 .* exp.(-0.002 .* (mz_range .- 500).^2) .+ 0.6 .* exp.(-0.001 .* (mz_range .- 600).^2)
|
||||
noisy_signal = true_signal .+ 0.1 .* randn(length(mz_range))
|
||||
|
||||
function simple_moving_average(y, window)
|
||||
smoothed = similar(y)
|
||||
for i in 1:length(y)
|
||||
start_idx = max(1, i - window ÷ 2)
|
||||
end_idx = min(length(y), i + window ÷ 2)
|
||||
smoothed[i] = mean(y[start_idx:end_idx])
|
||||
end
|
||||
return smoothed
|
||||
end
|
||||
|
||||
smoothed_small_window = simple_moving_average(noisy_signal, 5)
|
||||
smoothed_large_window = simple_moving_average(noisy_signal, 21)
|
||||
|
||||
lines!(ax, mz_range, noisy_signal, color=(:red, 0.4), label="Noisy Signal")
|
||||
lines!(ax, mz_range, true_signal, color=:black, linestyle=:dash, linewidth=2, label="True Signal")
|
||||
lines!(ax, mz_range, smoothed_small_window, color=:blue, linewidth=2, label="Smoothed (window: 5)")
|
||||
lines!(ax, mz_range, smoothed_large_window, color=:purple, linewidth=2, label="Smoothed (window: 21)")
|
||||
|
||||
text!(ax, "Parameter: `window`\nA larger window increases smoothing but may broaden peaks.",
|
||||
position=Point2f(0.05, 0.95), space=:relative, align=(:left, :top), fontsize=12)
|
||||
axislegend(ax, position=:rt)
|
||||
save("descriptive_plot_smoothing.png", fig)
|
||||
println("Saved: descriptive_plot_smoothing.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Visualizes the peak picking process for profile-mode data, illustrating the
|
||||
effects of SNR threshold and peak prominence.
|
||||
"""
|
||||
function plot_peak_picking_profile_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Peak Picking (Profile Mode)", xlabel="m/z", ylabel="Intensity")
|
||||
|
||||
mz = 1:200
|
||||
base_signal = 10 .* exp.(-((mz .- 50).^2) ./ (2*3^2)) .+ 7 .* exp.(-((mz .- 120).^2) ./ (2*5^2)) .+ 2
|
||||
small_peak_signal = zeros(200)
|
||||
for i in 80:90
|
||||
small_peak_signal[i] = 3 * exp(-((i - 85)^2) / 2.0)
|
||||
end
|
||||
noise = 0.5 .* randn(200)
|
||||
intensity = base_signal .+ small_peak_signal .+ noise
|
||||
|
||||
noise_level = median(abs.(intensity .- median(intensity))) * 1.4826 # MAD
|
||||
snr_threshold_val = 3.0
|
||||
intensity_threshold = noise_level * snr_threshold_val
|
||||
|
||||
picked_peaks_mz = [50, 85, 120]
|
||||
picked_peaks_intensity = intensity[picked_peaks_mz]
|
||||
|
||||
hlines!(ax, [noise_level], color=:gray, linestyle=:dot, label="Est. Noise Level")
|
||||
hlines!(ax, [intensity_threshold], color=:orange, linestyle=:dash, label="SNR Threshold (snr_threshold = 3.0)")
|
||||
lines!(ax, mz, intensity, color=:blue, label="Profile Spectrum")
|
||||
scatter!(ax, picked_peaks_mz, picked_peaks_intensity, color=:green, markersize=15, strokewidth=2, label="Peaks passing SNR")
|
||||
scatter!(ax, [25], [intensity[25]], color=:red, marker=:x, markersize=15, label="Local max below SNR")
|
||||
|
||||
# Illustrate prominence
|
||||
arrows!(ax, [120, 120], [intensity[135], intensity[120]], [0, 0], [intensity[120]-intensity[135], 0], color=:purple)
|
||||
text!(ax, 125, (intensity[120]+intensity[135])/2, text="Prominence", color=:purple)
|
||||
|
||||
axislegend(ax, position=:rt)
|
||||
save("descriptive_plot_peakpicking_profile.png", fig)
|
||||
println("Saved: descriptive_plot_peakpicking_profile.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Visualizes peak picking (filtering) for centroid-mode data based on an SNR
|
||||
(intensity) threshold.
|
||||
"""
|
||||
function plot_peak_picking_centroid_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Peak Picking (Centroid Mode)", xlabel="m/z", ylabel="Intensity")
|
||||
|
||||
mz = [100, 150, 200, 250, 300, 350, 400]
|
||||
intensity = [10, 5, 25, 8, 3, 18, 12]
|
||||
snr_threshold_val = 10.0
|
||||
|
||||
stem!(ax, mz, intensity, color=:gray, label="Input Centroids")
|
||||
|
||||
selected_mask = intensity .>= snr_threshold_val
|
||||
stem!(ax, mz[selected_mask], intensity[selected_mask], color=:green, trunkwidth=3, label="Selected Peaks (intensity >= 10)")
|
||||
stem!(ax, mz[.!selected_mask], intensity[.!selected_mask], color=:red, trunkwidth=3, label="Rejected Peaks (intensity < 10)")
|
||||
|
||||
hlines!(ax, [snr_threshold_val], color=:orange, linestyle=:dash, label="Intensity Threshold (snr_threshold)")
|
||||
|
||||
text!(ax, "Parameter: `snr_threshold`\nIn centroid mode, this acts as a direct intensity filter.",
|
||||
position=Point2f(0.05, 0.95), space=:relative, align=(:left, :top), fontsize=12)
|
||||
axislegend(ax, position=:rt)
|
||||
save("descriptive_plot_peakpicking_centroid.png", fig)
|
||||
println("Saved: descriptive_plot_peakpicking_centroid.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Plots the effect of different normalization methods on a set of spectra.
|
||||
"""
|
||||
function plot_normalization_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
|
||||
mz = range(300, 400, length=500)
|
||||
spec1 = 1.5 .* exp.(-((mz .- 350).^2) ./ 50)
|
||||
spec2 = 0.8 .* exp.(-((mz .- 350).^2) ./ 50)
|
||||
|
||||
ax1 = Axis(fig[1, 1], title="Before Normalization", ylabel="Absolute Intensity")
|
||||
lines!(ax1, mz, spec1, label="Spectrum A (High TIC)")
|
||||
lines!(ax1, mz, spec2, label="Spectrum B (Low TIC)")
|
||||
axislegend(ax1)
|
||||
|
||||
ax2 = Axis(fig[1, 2], title="After Normalization (TIC)", ylabel="Relative Intensity")
|
||||
lines!(ax2, mz, spec1 ./ sum(spec1), label="Spectrum A (Normalized)")
|
||||
lines!(ax2, mz, spec2 ./ sum(spec2), label="Spectrum B (Normalized)")
|
||||
|
||||
text!(ax2, "Effect: Spectra are scaled to have the same total area, making their intensities comparable.",
|
||||
position=Point2f(0.05, 0.95), space=:relative, align=(:left, :top), fontsize=12, justification=:left)
|
||||
|
||||
save("descriptive_plot_normalization.png", fig)
|
||||
println("Saved: descriptive_plot_normalization.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Illustrates mass calibration, showing how a calibration curve corrects measured
|
||||
m/z values based on reference peaks.
|
||||
"""
|
||||
function plot_calibration_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Calibration", xlabel="Measured m/z", ylabel="m/z Error (Measured - Reference)")
|
||||
|
||||
ref_mz = [200, 400, 600, 800, 1000]
|
||||
measured_mz = ref_mz .+ [0.1, 0.15, 0.2, 0.25, 0.3] .+ 0.02 .* randn(5)
|
||||
errors = measured_mz .- ref_mz
|
||||
|
||||
# Fit a linear model to the error
|
||||
A = [ones(5) measured_mz]
|
||||
coeffs = A \ errors
|
||||
correction_func(m) = m - (coeffs[1] .+ coeffs[2] .* m)
|
||||
|
||||
fit_line = coeffs[1] .+ coeffs[2] .* measured_mz
|
||||
|
||||
scatter!(ax, measured_mz, errors, color=:red, markersize=15, label="Measured Error")
|
||||
lines!(ax, measured_mz, fit_line, color=:blue, label="Calibration Curve (fit_order=1)")
|
||||
|
||||
text!(ax, "Parameter: `fit_order`\nA curve is fit to the error of known reference peaks.\nThis curve is then used to correct all m/z values.",
|
||||
position=Point2f(0.05, 0.95), space=:relative, align=(:left, :top), fontsize=12)
|
||||
axislegend(ax, position=:rb)
|
||||
save("descriptive_plot_calibration.png", fig)
|
||||
println("Saved: descriptive_plot_calibration.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Visualizes peak alignment by showing multiple spectra with misaligned peaks
|
||||
before and after the alignment process.
|
||||
"""
|
||||
function plot_alignment_details()
|
||||
fig = Figure(size=(1600, 600), fontsize=14)
|
||||
ax1 = Axis(fig[1, 1], title="Before Alignment", xlabel="m/z", yticklabelsvisible=false, ygridvisible=false)
|
||||
ax2 = Axis(fig[1, 2], title="After Alignment", xlabel="m/z", yticklabelsvisible=false, ygridvisible=false)
|
||||
|
||||
mz = range(490, 510, length=1000)
|
||||
shifts = [-0.5, 0.0, 0.8]
|
||||
colors = [:blue, :green, :purple]
|
||||
|
||||
for (i, shift) in enumerate(shifts)
|
||||
peak_center = 500 + shift
|
||||
spectrum = exp.(-((mz .- peak_center).^2) ./ 0.1)
|
||||
lines!(ax1, mz, spectrum .+ i, color=colors[i])
|
||||
vlines!(ax1, [peak_center], color=(colors[i], 0.5), linestyle=:dash)
|
||||
|
||||
# After alignment, all peaks are at 500
|
||||
aligned_spectrum = exp.(-((mz .- 500).^2) ./ 0.1)
|
||||
lines!(ax2, mz, aligned_spectrum .+ i, color=colors[i])
|
||||
end
|
||||
vlines!(ax2, [500], color=:red, linestyle=:dash, label="Reference m/z")
|
||||
axislegend(ax2)
|
||||
|
||||
save("descriptive_plot_alignment.png", fig)
|
||||
println("Saved: descriptive_plot_alignment.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Illustrates peak selection by filtering a population of peaks based on
|
||||
FWHM and SNR criteria.
|
||||
"""
|
||||
function plot_peak_selection_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Peak Selection", xlabel="FWHM (ppm)", ylabel="Signal-to-Noise Ratio (SNR)")
|
||||
|
||||
n_peaks = 100
|
||||
fwhm = rand(n_peaks) .* 150
|
||||
snr = rand(n_peaks) .* 20
|
||||
|
||||
min_fwhm_ppm = 20.0
|
||||
max_fwhm_ppm = 100.0
|
||||
min_snr = 5.0
|
||||
|
||||
selected_mask = (fwhm .>= min_fwhm_ppm) .& (fwhm .<= max_fwhm_ppm) .& (snr .>= min_snr)
|
||||
|
||||
scatter!(ax, fwhm[.!selected_mask], snr[.!selected_mask], color=(:red, 0.5), label="Rejected Peaks")
|
||||
scatter!(ax, fwhm[selected_mask], snr[selected_mask], color=:green, label="Selected Peaks")
|
||||
|
||||
vlines!(ax, [min_fwhm_ppm, max_fwhm_ppm], color=:blue, linestyle=:dash, label="FWHM bounds")
|
||||
hlines!(ax, [min_snr], color=:orange, linestyle=:dash, label="SNR bound")
|
||||
|
||||
poly!(ax, BBox(min_fwhm_ppm, max_fwhm_ppm, min_snr, 22), color=(:green, 0.1))
|
||||
text!(ax, "Selection Region", position=(60, 12), color=:green, fontsize=14)
|
||||
|
||||
axislegend(ax)
|
||||
save("descriptive_plot_peak_selection.png", fig)
|
||||
println("Saved: descriptive_plot_peak_selection.png")
|
||||
end
|
||||
|
||||
"""
|
||||
Visualizes the adaptive peak binning process, showing how peaks from different
|
||||
spectra are grouped into a common bin based on a PPM tolerance.
|
||||
"""
|
||||
function plot_peak_binning_details()
|
||||
fig = Figure(size=(1200, 700), fontsize=14)
|
||||
ax = Axis(fig[1, 1], title="Detailed View: Adaptive Peak Binning", xlabel="m/z", yticklabelsvisible=false)
|
||||
|
||||
ref_mz = 500.0
|
||||
tolerance_ppm = 50.0
|
||||
tol_mz = ref_mz * tolerance_ppm / 1e6
|
||||
|
||||
bin_start = ref_mz - tol_mz/2
|
||||
bin_end = ref_mz + tol_mz/2
|
||||
|
||||
peaks_mz = [ref_mz - 0.01, ref_mz + 0.005, ref_mz + 0.02, ref_mz - 0.015]
|
||||
peak_intensities = [0.8, 1.0, 0.9, 0.7]
|
||||
peak_colors = [:blue, :green, :purple, :orange]
|
||||
|
||||
vspan!(ax, bin_start, bin_end, color=(:gray, 0.2), label="Bin (tolerance: 50 ppm)")
|
||||
stem!(ax, peaks_mz, peak_intensities, color=peak_colors, markersize=15)
|
||||
|
||||
# Show bin center
|
||||
bin_center = mean(peaks_mz)
|
||||
vlines!(ax, [bin_center], color=:red, linestyle=:dash, label="Calculated Bin Center")
|
||||
|
||||
text!(ax, "Parameter: `tolerance`\nPeaks from different spectra within the tolerance window are grouped into a single feature.",
|
||||
position=Point2f(0.05, 0.95), space=:relative, align=(:left, :top), fontsize=12)
|
||||
axislegend(ax, position=:rt)
|
||||
xlims!(ax, ref_mz - tol_mz*2, ref_mz + tol_mz*2)
|
||||
save("descriptive_plot_peak_binning.png", fig)
|
||||
println("Saved: descriptive_plot_peak_binning.png")
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
Main function to generate and save all descriptive plots.
|
||||
"""
|
||||
function create_and_save_all_plots()
|
||||
println("Generating detailed descriptive plots for preprocessing steps...")
|
||||
|
||||
plot_baseline_correction_details()
|
||||
plot_smoothing_details()
|
||||
plot_peak_picking_profile_details()
|
||||
plot_peak_picking_centroid_details()
|
||||
plot_normalization_details()
|
||||
plot_calibration_details()
|
||||
plot_alignment_details()
|
||||
plot_peak_selection_details()
|
||||
plot_peak_binning_details()
|
||||
|
||||
println("\nAll descriptive plots have been saved in the current directory.")
|
||||
end
|
||||
|
||||
# Execute the plot generation
|
||||
if abspath(PROGRAM_FILE) == @__FILE__
|
||||
create_and_save_all_plots()
|
||||
end
|
||||
|
||||
# Display the plot (if in an interactive environment)
|
||||
fig
|
||||
|
||||
@ -5,6 +5,8 @@ import Pkg
|
||||
using CairoMakie
|
||||
using DataFrames # For creating dataframes
|
||||
using CSV
|
||||
using Statistics
|
||||
using Interpolations
|
||||
|
||||
# --- Load the MSI_src Module ---
|
||||
Pkg.activate(joinpath(@__DIR__, ".."))
|
||||
@ -15,12 +17,13 @@ using MSI_src
|
||||
# CONFIG: PLEASE FILL IN YOUR FILE PATHS HERE
|
||||
# ===================================================================
|
||||
|
||||
const TEST_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/Thricoderma_etc/Imaging_interaccion_trichoderma_vs_streptomyces.imzML"
|
||||
# const TEST_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/Thricoderma_etc/Imaging_interaccion_trichoderma_vs_streptomyces.imzML"
|
||||
# const TEST_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/set de datos MS/Atropina_tuneo_fraq_20ev.mzML"
|
||||
# const TEST_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/salida/Stomach_DHB_uncompressed.imzML"
|
||||
const TEST_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/salida/Stomach_DHB_uncompressed.imzML"
|
||||
# const TEST_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/imzML_AP_SMALDI/HR2MSImouseurinarybladderS096.imzML"
|
||||
|
||||
# const MASK_ROUTE = "/home/pixel/Documents/Cinvestav_2025/JuliaMSI/public/css/masks/Stomach_DHB_uncompressed.png"
|
||||
const MASK_ROUTE = ""
|
||||
const MASK_ROUTE = "/home/pixel/Documents/Cinvestav_2025/JuliaMSI/public/css/masks/Stomach_DHB_uncompressed.png"
|
||||
# const MASK_ROUTE = ""
|
||||
|
||||
const OUTPUT_DIR = "./test/results/preprocessing_results"
|
||||
|
||||
@ -41,6 +44,55 @@ const reference_peaks = Dict(
|
||||
124.0393 => "Tropine [M+H]+",
|
||||
)
|
||||
|
||||
const PIPELINE_STP = [
|
||||
"stabilization",
|
||||
#"baseline_correction",
|
||||
"smoothing",
|
||||
"peak_picking",
|
||||
"peak_selection",
|
||||
#"calibration",
|
||||
"peak_alignment",
|
||||
"normalization",
|
||||
"peak_binning"
|
||||
]
|
||||
|
||||
# ===================================================================
|
||||
# USER OVERRIDES: Manually specify parameters here
|
||||
# ===================================================================
|
||||
# This dictionary allows you to override any auto-detected parameters.
|
||||
# The structure should match the output of `main_precalculation`.
|
||||
# Example: Force a less aggressive peak prominence threshold.
|
||||
|
||||
const USER_OVERRIDES = Dict(
|
||||
:Stabilization => Dict(
|
||||
:method => :sqrt # Default stabilization method
|
||||
),
|
||||
:PeakPicking => Dict(
|
||||
#:snr_threshold => 1.5,
|
||||
#:half_window => 5,
|
||||
:snr_threshold => 8.0,
|
||||
#:merge_peaks_tolerance => 2.5,
|
||||
#:half_window => 2
|
||||
#:half_window => 3
|
||||
),
|
||||
# :Smoothing => Dict(
|
||||
# :window => 11
|
||||
# )
|
||||
#:PeakAlignment => Dict(
|
||||
# :tolerance => 0.01
|
||||
#)
|
||||
#:PeakSelection => Dict(
|
||||
#:frequency_threshold => 0,
|
||||
#:min_shape_r2 => 0.5,
|
||||
#:max_fwhm_ppm => 30.0,
|
||||
#:min_fwhm_ppm => 2.0
|
||||
#),
|
||||
#:PeakBinning => Dict(
|
||||
#:tolerance => 30.0,
|
||||
#:frequency_threshold => 0
|
||||
#)
|
||||
)
|
||||
|
||||
# ===================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ===================================================================
|
||||
@ -79,7 +131,7 @@ end
|
||||
# PREPROCESSING PIPELINE FUNCTIONS (IN-PLACE)
|
||||
# ===================================================================
|
||||
|
||||
function apply_baseline_correction(spectra::Vector{MutableSpectrum}, params::Dict, msi_data::MSIData)
|
||||
function apply_baseline_correction_core(spectra::Vector{MutableSpectrum}, params::Dict, msi_data::MSIData)
|
||||
print_step_header("Baseline Correction")
|
||||
|
||||
method = get(params, :method, :snip)
|
||||
@ -91,7 +143,7 @@ function apply_baseline_correction(spectra::Vector{MutableSpectrum}, params::Dic
|
||||
if !isempty(spectra)
|
||||
s = spectra[1]
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
baseline = MSI_src.apply_baseline_correction(s.intensity; method=method, iterations=iterations, window=window)
|
||||
baseline = MSI_src.apply_baseline_correction_core(s.intensity; method=method, iterations=iterations, window=window)
|
||||
corrected_intensity = max.(0.0, s.intensity .- baseline)
|
||||
plot_spectrum_step(s.mz, corrected_intensity, "baseline_correction")
|
||||
end
|
||||
@ -99,7 +151,7 @@ function apply_baseline_correction(spectra::Vector{MutableSpectrum}, params::Dic
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
baseline = MSI_src.apply_baseline_correction(s.intensity; method=method, iterations=iterations, window=window)
|
||||
baseline = MSI_src.apply_baseline_correction_core(s.intensity; method=method, iterations=iterations, window=window)
|
||||
s.intensity = max.(0.0, s.intensity .- baseline)
|
||||
end
|
||||
end
|
||||
@ -117,14 +169,14 @@ function apply_smoothing(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
if !isempty(spectra)
|
||||
s = spectra[1]
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
smoothed_intensity = max.(0.0, smooth_spectrum(s.intensity; method=method, window=window, order=order))
|
||||
smoothed_intensity = max.(0.0, smooth_spectrum_core(s.intensity; method=method, window=window, order=order))
|
||||
plot_spectrum_step(s.mz, smoothed_intensity, "smoothing", spectrum_index=s.id)
|
||||
end
|
||||
end
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
smoothed_intensity = max.(0.0, smooth_spectrum(s.intensity; method=method, window=window, order=order))
|
||||
smoothed_intensity = max.(0.0, smooth_spectrum_core(s.intensity; method=method, window=window, order=order))
|
||||
s.intensity = smoothed_intensity
|
||||
end
|
||||
end
|
||||
@ -153,13 +205,13 @@ function apply_peak_picking(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
if is_valid
|
||||
if method == :profile
|
||||
s.peaks = detect_peaks_profile(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window, min_peak_prominence=min_peak_prominence, merge_peaks_tolerance=merge_peaks_tolerance)
|
||||
s.peaks = detect_peaks_profile_core(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window, min_peak_prominence=min_peak_prominence, merge_peaks_tolerance=merge_peaks_tolerance)
|
||||
elseif method == :wavelet
|
||||
s.peaks = detect_peaks_wavelet(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window)
|
||||
elseif method == :centroid
|
||||
s.peaks = detect_peaks_centroid(s.mz, s.intensity; snr_threshold=snr_threshold)
|
||||
s.peaks = detect_peaks_centroid_core(s.mz, s.intensity; snr_threshold=snr_threshold)
|
||||
else
|
||||
s.peaks = detect_peaks_profile(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window)
|
||||
s.peaks = detect_peaks_profile_core(s.mz, s.intensity; snr_threshold=snr_threshold, half_window=half_window)
|
||||
end
|
||||
else
|
||||
s.peaks = []
|
||||
@ -176,6 +228,51 @@ function apply_peak_picking(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
end
|
||||
end
|
||||
|
||||
function apply_peak_selection(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
print_step_header("Peak Selection")
|
||||
|
||||
min_snr = get(params, :min_snr, 0.0)
|
||||
min_fwhm = get(params, :min_fwhm_ppm, 0.0)
|
||||
max_fwhm = get(params, :max_fwhm_ppm, Inf)
|
||||
min_r2 = get(params, :min_shape_r2, 0.0)
|
||||
|
||||
# Handle `nothing` values from params, default to non-filtering values
|
||||
min_snr = isnothing(min_snr) ? 0.0 : min_snr
|
||||
min_fwhm = isnothing(min_fwhm) ? 0.0 : min_fwhm
|
||||
max_fwhm = isnothing(max_fwhm) ? Inf : max_fwhm
|
||||
min_r2 = isnothing(min_r2) ? 0.0 : min_r2
|
||||
|
||||
println(" - Min SNR: $min_snr")
|
||||
println(" - FWHM Range (ppm): [$min_fwhm, $max_fwhm]")
|
||||
println(" - Min Shape R²: $min_r2")
|
||||
|
||||
total_peaks_before = sum(s -> length(s.peaks), spectra)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if !isempty(s.peaks)
|
||||
s.peaks = filter(p ->
|
||||
p.snr >= min_snr &&
|
||||
(min_fwhm <= p.fwhm <= max_fwhm) &&
|
||||
p.shape_r2 >= min_r2,
|
||||
s.peaks
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
total_peaks_after = sum(s -> length(s.peaks), spectra)
|
||||
println(" - Peaks before: $total_peaks_before, Peaks after: $total_peaks_after")
|
||||
|
||||
# Safely plot the first spectrum to show effect of filtering
|
||||
if !isempty(spectra)
|
||||
s1_idx = findfirst(s -> s.id == 1, spectra)
|
||||
if s1_idx !== nothing
|
||||
s1 = spectra[s1_idx]
|
||||
plot_spectrum_step(s1.mz, s1.intensity, "peak_selection", peaks=s1.peaks, spectrum_index=s1.id)
|
||||
println(" - Spectrum 1 now has $(length(s1.peaks)) peaks after selection.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function apply_calibration(spectra::Vector{MutableSpectrum}, params::Dict, reference_peaks::Dict)
|
||||
print_step_header("Calibration")
|
||||
|
||||
@ -195,7 +292,7 @@ function apply_calibration(spectra::Vector{MutableSpectrum}, params::Dict, refer
|
||||
s = spectra[i]
|
||||
info_message = ""
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
matched_peaks = find_calibration_peaks(s.mz, s.intensity, reference_masses; ppm_tolerance=ppm_tolerance)
|
||||
matched_peaks = find_calibration_peaks_core(s.mz, s.intensity, reference_masses; ppm_tolerance=ppm_tolerance)
|
||||
if length(matched_peaks) >= 2
|
||||
measured = sort(collect(values(matched_peaks)))
|
||||
theoretical = sort(collect(keys(matched_peaks)))
|
||||
@ -248,7 +345,7 @@ function apply_peak_alignment(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
end
|
||||
|
||||
current_peaks_mz = [p.mz for p in s.peaks]
|
||||
alignment_func = align_peaks_lowess(ref_peaks_mz, current_peaks_mz; method=method, tolerance=tolerance, tolerance_unit=tolerance_unit)
|
||||
alignment_func = align_peaks_lowess_core(ref_peaks_mz, current_peaks_mz; method=method, tolerance=tolerance, tolerance_unit=tolerance_unit)
|
||||
|
||||
s.mz = alignment_func.(s.mz) # Update m/z axis
|
||||
|
||||
@ -261,7 +358,7 @@ function apply_peak_alignment(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
end
|
||||
end
|
||||
|
||||
function apply_normalization(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
function apply_normalization_core(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
print_step_header("Normalization")
|
||||
|
||||
method = get(params, :method, :tic)
|
||||
@ -273,7 +370,7 @@ function apply_normalization(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
if s1 !== nothing
|
||||
s = spectra[s1]
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
normalized_intensity = MSI_src.apply_normalization(s.intensity; method=method)
|
||||
normalized_intensity = MSI_src.apply_normalization_core(s.intensity; method=method)
|
||||
plot_spectrum_step(s.mz, normalized_intensity, "normalization")
|
||||
end
|
||||
end
|
||||
@ -281,7 +378,7 @@ function apply_normalization(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
Threads.@threads for s in spectra
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
s.intensity = MSI_src.apply_normalization(s.intensity; method=method)
|
||||
s.intensity = MSI_src.apply_normalization_core(s.intensity; method=method)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -293,8 +390,6 @@ function apply_peak_binning(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
tolerance = get(params, :tolerance, 20.0)
|
||||
tolerance_unit = get(params, :tolerance_unit, :ppm)
|
||||
min_peak_per_bin = get(params, :min_peak_per_bin, 3)
|
||||
max_bin_width_ppm = get(params, :max_bin_width_ppm, 150.0)
|
||||
intensity_weighted_centers = get(params, :intensity_weighted_centers, true)
|
||||
println(" Method: $method, Tolerance: $tolerance $tolerance_unit")
|
||||
|
||||
if isempty(spectra) || all(s -> isempty(s.peaks), spectra)
|
||||
@ -304,93 +399,130 @@ function apply_peak_binning(spectra::Vector{MutableSpectrum}, params::Dict)
|
||||
|
||||
println(" - Binning peaks from $(length(spectra)) spectra")
|
||||
|
||||
binning_params = PeakBinningParams(
|
||||
method=method,
|
||||
tolerance=tolerance,
|
||||
tolerance_unit=tolerance_unit,
|
||||
min_peak_per_bin=min_peak_per_bin,
|
||||
max_bin_width_ppm=max_bin_width_ppm,
|
||||
intensity_weighted_centers=intensity_weighted_centers
|
||||
)
|
||||
|
||||
feature_matrix, bin_definitions = bin_peaks(spectra, binning_params)
|
||||
|
||||
if feature_matrix !== nothing && bin_definitions !== nothing
|
||||
println(" - Generated feature matrix: $(size(feature_matrix.matrix))")
|
||||
println(" - Number of bins: $(length(bin_definitions))")
|
||||
# Collect all peaks with their intensities
|
||||
all_peaks = Vector{Tuple{Float64, Float64}}() # (mz, intensity)
|
||||
for s in spectra
|
||||
for p in s.peaks
|
||||
push!(all_peaks, (p.mz, p.intensity))
|
||||
end
|
||||
end
|
||||
|
||||
return feature_matrix, bin_definitions
|
||||
if isempty(all_peaks)
|
||||
@warn "No peaks collected for binning."
|
||||
return nothing, nothing
|
||||
end
|
||||
|
||||
sort!(all_peaks, by=x->x[1])
|
||||
|
||||
# Create bins - just store mz_center and intensity
|
||||
bin_centers = Float64[]
|
||||
bin_intensities = Float64[]
|
||||
|
||||
i = 1
|
||||
while i <= length(all_peaks)
|
||||
current_bin_start = i
|
||||
current_peak = all_peaks[i]
|
||||
|
||||
# Find all peaks in this bin
|
||||
j = i + 1
|
||||
while j <= length(all_peaks)
|
||||
next_peak = all_peaks[j]
|
||||
# Calculate tolerance
|
||||
tol = (tolerance_unit == :ppm) ? (current_peak[1] * tolerance / 1e6) : tolerance
|
||||
|
||||
if (next_peak[1] - current_peak[1]) <= tol
|
||||
j += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
current_bin_end = j - 1
|
||||
bin_size = current_bin_end - current_bin_start + 1
|
||||
|
||||
# Check if we have enough peaks in this bin
|
||||
if bin_size >= min_peak_per_bin
|
||||
bin_peaks_core = all_peaks[current_bin_start:current_bin_end]
|
||||
|
||||
# Calculate m/z center and average intensity
|
||||
mz_sum = 0.0
|
||||
intensity_sum = 0.0
|
||||
for peak in bin_peaks_core
|
||||
mz_sum += peak[1]
|
||||
intensity_sum += peak[2]
|
||||
end
|
||||
|
||||
mz_center = mz_sum / bin_size
|
||||
avg_intensity = intensity_sum / bin_size
|
||||
|
||||
push!(bin_centers, mz_center)
|
||||
push!(bin_intensities, avg_intensity)
|
||||
end
|
||||
|
||||
i = j # Move to next potential bin
|
||||
end
|
||||
|
||||
# Create the 2-row matrix
|
||||
if !isempty(bin_centers)
|
||||
n_bins = length(bin_centers)
|
||||
feature_matrix = Matrix{Float64}(undef, 2, n_bins)
|
||||
|
||||
for i in 1:n_bins
|
||||
feature_matrix[1, i] = bin_centers[i]
|
||||
feature_matrix[2, i] = bin_intensities[i]
|
||||
end
|
||||
|
||||
println(" - Created feature matrix: 2 × $n_bins")
|
||||
println(" - Number of bins created: $n_bins")
|
||||
println(" - m/z range: $(round(bin_centers[1], digits=4)) - $(round(bin_centers[end], digits=4))")
|
||||
|
||||
# Return bin info as a vector of tuples for compatibility
|
||||
bin_info = [(bin_centers[i], bin_intensities[i]) for i in 1:n_bins]
|
||||
return feature_matrix, bin_info
|
||||
else
|
||||
@warn "No bins created after filtering"
|
||||
return nothing, nothing
|
||||
end
|
||||
end
|
||||
|
||||
function save_feature_matrix(feature_matrix, bin_definitions)
|
||||
function save_feature_matrix(feature_matrix::Matrix{Float64}, bin_info)
|
||||
print_step_header("Saving Results")
|
||||
|
||||
# Save feature matrix as CSV
|
||||
csv_path = joinpath(OUTPUT_DIR, "feature_matrix.csv")
|
||||
# Save as simple CSV with m/z and intensity rows
|
||||
csv_path = joinpath(OUTPUT_DIR, "feature_matrix_simple.csv")
|
||||
|
||||
# Create column headers
|
||||
bin_headers = ["bin_$(i)_$(round(def[1], digits=4))-$(round(def[2], digits=4))"
|
||||
for (i, def) in enumerate(bin_definitions)]
|
||||
|
||||
# Open file for writing
|
||||
open(csv_path, "w") do io
|
||||
# Write header row
|
||||
write(io, "spectrum_index," * join(bin_headers, ",") * "\n")
|
||||
# Write header
|
||||
write(io, "mz,intensity\n")
|
||||
|
||||
# Write data rows
|
||||
for r_idx in 1:size(feature_matrix.matrix, 1)
|
||||
write(io, "$(feature_matrix.sample_ids[r_idx]),")
|
||||
for c_idx in 1:size(feature_matrix.matrix, 2)
|
||||
write(io, "$(feature_matrix.matrix[r_idx, c_idx])")
|
||||
if c_idx < size(feature_matrix.matrix, 2)
|
||||
write(io, ",")
|
||||
# Write data: m/z values in first column, intensities in second
|
||||
for i in 1:size(feature_matrix, 2)
|
||||
mz = feature_matrix[1, i]
|
||||
intensity = feature_matrix[2, i]
|
||||
write(io, "$mz,$intensity\n")
|
||||
end
|
||||
end
|
||||
write(io, "\n")
|
||||
end
|
||||
end
|
||||
println(" - Saved feature matrix: $csv_path")
|
||||
println(" - Saved simple feature matrix: $csv_path")
|
||||
|
||||
# Save bin definitions (still using DataFrame as it's small)
|
||||
bins_path = joinpath(OUTPUT_DIR, "bin_definitions.csv")
|
||||
bins_df = DataFrame(
|
||||
bin_index = 1:length(bin_definitions),
|
||||
mz_start = [def[1] for def in bin_definitions],
|
||||
mz_end = [def[2] for def in bin_definitions],
|
||||
mz_center = [(def[1] + def[2])/2 for def in bin_definitions]
|
||||
)
|
||||
CSV.write(bins_path, bins_df)
|
||||
println(" - Saved bin definitions: $bins_path")
|
||||
# Also save in a more standard format for MSI
|
||||
csv_path_standard = joinpath(OUTPUT_DIR, "feature_matrix_standard.csv")
|
||||
|
||||
return csv_path, bins_path
|
||||
open(csv_path_standard, "w") do io
|
||||
# Write header with m/z values as column names
|
||||
write(io, "sample_type,")
|
||||
mz_headers = [@sprintf("mz_%.4f", feature_matrix[1, i]) for i in 1:size(feature_matrix, 2)]
|
||||
write(io, join(mz_headers, ",") * "\n")
|
||||
|
||||
# Write the aggregated intensity values
|
||||
write(io, "aggregated_spectrum,")
|
||||
intensity_values = [feature_matrix[2, i] for i in 1:size(feature_matrix, 2)]
|
||||
write(io, join(string.(intensity_values), ",") * "\n")
|
||||
end
|
||||
println(" - Saved standard format matrix: $csv_path_standard")
|
||||
|
||||
return csv_path, csv_path_standard
|
||||
end
|
||||
|
||||
# ===================================================================
|
||||
# USER OVERRIDES: Manually specify parameters here
|
||||
# ===================================================================
|
||||
# This dictionary allows you to override any auto-detected parameters.
|
||||
# The structure should match the output of `main_precalculation`.
|
||||
# Example: Force a less aggressive peak prominence threshold.
|
||||
const USER_OVERRIDES = Dict(
|
||||
:PeakPicking => Dict(
|
||||
#:snr_threshold => 1.5,
|
||||
:half_window => 5,
|
||||
:snr_threshold => 15.0,
|
||||
#:merge_peaks_tolerance => 2.5,
|
||||
#:half_window => 2
|
||||
),
|
||||
# :Smoothing => Dict(
|
||||
# :window => 11
|
||||
# )
|
||||
#:PeakAlignment => Dict(
|
||||
# :tolerance => 0.01
|
||||
#)
|
||||
:PeakBinningParams => Dict(
|
||||
:tolerance => 30.0
|
||||
)
|
||||
)
|
||||
|
||||
# ===================================================================
|
||||
# MAIN PREPROCESSING PIPELINE
|
||||
# ===================================================================
|
||||
@ -446,28 +578,45 @@ function run_preprocessing_pipeline()
|
||||
end
|
||||
|
||||
# Define pipeline steps
|
||||
pipeline_steps = [
|
||||
"baseline_correction",
|
||||
"smoothing",
|
||||
"peak_picking",
|
||||
"calibration",
|
||||
"peak_alignment",
|
||||
"normalization",
|
||||
"peak_binning"
|
||||
]
|
||||
pipeline_steps = PIPELINE_STP
|
||||
|
||||
println("\nPipeline steps: $(join(pipeline_steps, " -> "))")
|
||||
|
||||
# Initialize `current_spectra` as a Vector of mutable structs for in-place modification
|
||||
println("\nInitializing spectra data structure...")
|
||||
num_spectra = length(msi_data.spectra_metadata)
|
||||
current_spectra = Vector{MutableSpectrum}(undef, num_spectra)
|
||||
_iterate_spectra_fast(msi_data) do idx, mz, intensity
|
||||
current_spectra[idx] = MutableSpectrum(idx, mz, intensity, [])
|
||||
local spectrum_indices_to_process::AbstractVector{Int}
|
||||
if !isempty(MASK_ROUTE)
|
||||
println("Applying mask from: $(MASK_ROUTE)")
|
||||
try
|
||||
mask_matrix = MSI_src.load_and_prepare_mask(MASK_ROUTE, msi_data.image_dims)
|
||||
masked_indices_set = MSI_src.get_masked_spectrum_indices(msi_data, mask_matrix)
|
||||
spectrum_indices_to_process = collect(masked_indices_set)
|
||||
println("Mask applied. $(length(spectrum_indices_to_process)) spectra are within the masked region.")
|
||||
catch e
|
||||
@error "Failed to load or apply mask: $e. Proceeding without mask."
|
||||
spectrum_indices_to_process = 1:length(msi_data.spectra_metadata)
|
||||
end
|
||||
else
|
||||
spectrum_indices_to_process = 1:length(msi_data.spectra_metadata)
|
||||
end
|
||||
|
||||
# Plot raw spectrum without masks
|
||||
if idx == 1
|
||||
plot_spectrum_step(mz, intensity, "raw_unmasked_spectrum")
|
||||
if isempty(spectrum_indices_to_process)
|
||||
@warn "No spectra available for processing after applying mask/filter. Exiting pipeline."
|
||||
close(msi_data)
|
||||
return
|
||||
end
|
||||
|
||||
num_spectra_to_process = length(spectrum_indices_to_process)
|
||||
current_spectra = Vector{MutableSpectrum}(undef, num_spectra_to_process)
|
||||
|
||||
Threads.@threads for i in 1:num_spectra_to_process
|
||||
original_idx = spectrum_indices_to_process[i]
|
||||
mz, intensity = MSI_src.GetSpectrum(msi_data, original_idx)
|
||||
current_spectra[i] = MSI_src.MutableSpectrum(original_idx, mz, intensity, [])
|
||||
|
||||
# Plot raw spectrum without masks (only the first processed one)
|
||||
if i == 1
|
||||
plot_spectrum_step(mz, intensity, "raw_unmasked_spectrum", spectrum_index=original_idx)
|
||||
end
|
||||
end
|
||||
|
||||
@ -481,8 +630,33 @@ function run_preprocessing_pipeline()
|
||||
println("PROCESSING STEP: $step")
|
||||
println("-"^60)
|
||||
|
||||
if step == "baseline_correction"
|
||||
@time apply_baseline_correction(current_spectra, auto_params[:BaselineCorrection], msi_data)
|
||||
if step == "stabilization"
|
||||
print_step_header("Intensity Transformation (Stabilization)")
|
||||
method = get(auto_params[:Stabilization], :method, :sqrt)
|
||||
println(" Method: $method")
|
||||
|
||||
# Safely plot the first spectrum before transformation
|
||||
if !isempty(current_spectra)
|
||||
s = current_spectra[1]
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
# Use a temporary spectrum to plot before and after
|
||||
initial_intensity = deepcopy(s.intensity)
|
||||
plot_spectrum_step(s.mz, initial_intensity, "stabilization_before", spectrum_index=s.id)
|
||||
end
|
||||
end
|
||||
|
||||
@time apply_intensity_transformation(current_spectra, auto_params[:Stabilization])
|
||||
|
||||
# Safely plot the first spectrum after transformation
|
||||
if !isempty(current_spectra)
|
||||
s = current_spectra[1]
|
||||
if validate_spectrum(s.mz, s.intensity)
|
||||
plot_spectrum_step(s.mz, s.intensity, "stabilization_after", spectrum_index=s.id)
|
||||
end
|
||||
end
|
||||
|
||||
elseif step == "baseline_correction"
|
||||
@time apply_baseline_correction_core(current_spectra, auto_params[:BaselineCorrection], msi_data)
|
||||
|
||||
elseif step == "smoothing"
|
||||
apply_smoothing(current_spectra, auto_params[:Smoothing])
|
||||
@ -490,6 +664,9 @@ function run_preprocessing_pipeline()
|
||||
elseif step == "peak_picking"
|
||||
@time apply_peak_picking(current_spectra, auto_params[:PeakPicking])
|
||||
|
||||
elseif step == "peak_selection"
|
||||
@time apply_peak_selection(current_spectra, auto_params[:PeakSelection])
|
||||
|
||||
elseif step == "calibration"
|
||||
@time apply_calibration(current_spectra, auto_params[:Calibration], reference_peaks)
|
||||
|
||||
@ -497,14 +674,14 @@ function run_preprocessing_pipeline()
|
||||
@time apply_peak_alignment(current_spectra, auto_params[:PeakAlignment])
|
||||
|
||||
elseif step == "normalization"
|
||||
@time apply_normalization(current_spectra, auto_params[:Normalization])
|
||||
@time apply_normalization_core(current_spectra, auto_params[:Normalization])
|
||||
|
||||
elseif step == "peak_binning"
|
||||
# This step is different as it generates the final matrix, not modifying spectra in-place
|
||||
feature_matrix, bin_definitions = @time apply_peak_binning(current_spectra, auto_params[:PeakBinningParams])
|
||||
feature_matrix, bin_info = @time apply_peak_binning(current_spectra, auto_params[:PeakBinning])
|
||||
|
||||
if feature_matrix !== nothing
|
||||
@time save_feature_matrix(feature_matrix, bin_definitions)
|
||||
@time save_feature_matrix(feature_matrix, bin_info)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user