simplified imzML parsing, finished interactivity for preprocessing in frontend / backend, and processing spectrums is now possible. added n Spectrum plotting

This commit is contained in:
Pixelguy14 2025-11-30 18:12:31 -06:00
parent 11c181b3b3
commit 8b1c2d2722
7 changed files with 1040 additions and 2559 deletions

767
app.jl
View File

@ -22,7 +22,7 @@ using Dates
using Base.Threads using Base.Threads
# Bring MSIData into App module's scope # 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!, main_precalculation, MutableSpectrum 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, execute_full_preprocessing
if !@isdefined(increment_image) if !@isdefined(increment_image)
include("./julia_imzML_visual.jl") include("./julia_imzML_visual.jl")
@ -67,6 +67,30 @@ else
log_memory_usage(context::String, msi_data_val) = nothing # No-op for production log_memory_usage(context::String, msi_data_val) = nothing # No-op for production
end end
function validate_parse(validation_errors::Vector{String}, param_str::String, param_name::String, target_type::Type, step_name::String)
println("DEBUG: Validating ($step_name) Parameter '$param_name'. Received value: '$param_str'")
if isempty(param_str)
push!(validation_errors, "($step_name) Parameter '$param_name' is empty.")
return nothing
end
val = tryparse(target_type, param_str)
if val === nothing
push!(validation_errors, "($step_name) Parameter '$param_name' ('$param_str') is not a valid $(target_type).")
return nothing
end
return val
end
# Helper function to check if a pipeline step is enabled
function is_step_enabled(step_name::String, pipeline_order::Vector{Dict{String, Any}})
for step in pipeline_order
if get(step, "name", "") == step_name
return get(step, "enabled", false)
end
end
return false # Default to disabled if step not found
end
@genietools @genietools
# == Reactive code == # == Reactive code ==
@ -116,6 +140,7 @@ end
@in compareBtn=false # To open dialog @in compareBtn=false # To open dialog
@in createMeanPlot=false # To generate mean spectrum plot @in createMeanPlot=false # To generate mean spectrum plot
@in createXYPlot=false # To generate an spectrum plot according to the xy values inputed @in createXYPlot=false # To generate an spectrum plot according to the xy values inputed
@in createNSpectrumPlot=false # To generate an spectrum plot according to spectrum order
@in createSumPlot=false # To generate a sum of all the spectrum plots @in createSumPlot=false # To generate a sum of all the spectrum plots
@in image3dPlot=false # To generate 3d plot based on current image @in image3dPlot=false # To generate 3d plot based on current image
@in triq3dPlot=false # To generate 3d plot based on current triq image @in triq3dPlot=false # To generate 3d plot based on current triq image
@ -264,7 +289,6 @@ end
Dict("name" => "normalization", "label" => "Normalization", "enabled" => true), Dict("name" => "normalization", "label" => "Normalization", "enabled" => true),
Dict("name" => "peak_binning", "label" => "Peak Binning", "enabled" => true) Dict("name" => "peak_binning", "label" => "Peak Binning", "enabled" => true)
] ]
@in preprocessing_mask_route = ""
@in selected_spectrum_id_for_plot = 1 @in selected_spectrum_id_for_plot = 1
@in feature_matrix_result = nothing @in feature_matrix_result = nothing
@in bin_info_result = nothing @in bin_info_result = nothing
@ -274,41 +298,21 @@ end
Dict("mz" => 155.0349, "label" => "DHB_M+H"), 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 @in save_feature_matrix_btn = false
@in recalculate_suggestions_btn = false
@in addReferencePeak = false
@in removeReferencePeak = false
@in moveStepUp = false
@in moveStepDown = false
# Trigger for running the full pipeline # Trigger for running the full pipeline
@in run_full_pipeline = false @in run_full_pipeline = false
@in enable_standards = true
@in action_index = -1
@in remove_peak_trigger = false
@in move_step_up_trigger = false
@in move_step_down_trigger = false
@in toggle_step_trigger = false
@out current_pipeline_step = "" # To indicate which step is currently running in the full pipeline @out current_pipeline_step = "" # To indicate which step is currently running in the full pipeline
@in export_params_btn = false @in export_params_btn = false
@ -453,7 +457,13 @@ end
showgrid=true, showgrid=true,
tickformat = ".3g" tickformat = ".3g"
), ),
margin=attr(l=0,r=0,t=120,b=0,pad=0) margin=attr(l=0,r=0,t=120,b=0,pad=0),
legend=attr(
x=1.0,
y=1.0,
xanchor="right",
yanchor="top"
)
) )
# Dummy 2D scatter plot # Dummy 2D scatter plot
traceSpectra=PlotlyBase.scatter(x=Vector{Float64}(), y=Vector{Float64}(), mode="lines", marker=attr(size=1, color="blue", opacity=0.1)) traceSpectra=PlotlyBase.scatter(x=Vector{Float64}(), y=Vector{Float64}(), mode="lines", marker=attr(size=1, color="blue", opacity=0.1))
@ -558,7 +568,7 @@ end
msg = "Opening file: $(basename(picked_route))..." msg = "Opening file: $(basename(picked_route))..."
try 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) registry = load_registry(registry_path)
existing_entry = get(registry, dataset_name, nothing) existing_entry = get(registry, dataset_name, nothing)
@ -938,119 +948,339 @@ end
@onbutton run_full_pipeline begin @onbutton run_full_pipeline begin
progressPrep = true progressPrep = true
current_pipeline_step = "Initializing..." current_pipeline_step = "Initializing..."
println("DEBUG: run_full_pipeline started.")
# println("DEBUG: Current pipeline_step_order configuration: $pipeline_step_order")
try try
# 1. Data Preparation # --- 1. Initial Checks and Data Loading ---
if isempty(selected_folder_main) println("DEBUG: Performing initial checks and data loading...")
msg = "No dataset selected. Please process a file first." if msi_data === nothing || isempty(selected_folder_main)
msg = "No dataset loaded. Please load a file using 'Select an imzMl / mzML file'."
warning_msg = true warning_msg = true
println("DEBUG: $msg")
return return
end end
registry = load_registry(registry_path) registry = load_registry(registry_path)
entry = registry[selected_folder_main] entry = get(registry, selected_folder_main, nothing)
if entry === nothing
msg = "Selected dataset '$(selected_folder_main)' not found in registry. Please reload the file."
warning_msg = true
println("DEBUG: $msg")
return
end
target_path = entry["source_path"] target_path = entry["source_path"]
if msi_data === nothing || full_route != target_path # Ensure msi_data is for the currently selected file
if msi_data !== nothing if full_route != target_path
close(msi_data) println("DEBUG: Active file path changed. Reloading MSI data: $(basename(target_path))")
end if msi_data !== nothing; close(msi_data); end
msg = "Reloading $(basename(target_path)) for analysis..."
full_route = target_path full_route = target_path
msi_data = OpenMSIData(target_path) msi_data = OpenMSIData(target_path)
else
println("DEBUG: Using already loaded MSI data for $(basename(target_path)).")
end end
# Apply mask if enabled # Mask path retrieval from registry
local mask_path_for_pipeline::Union{String, Nothing} = nothing
if maskEnabled
println("DEBUG: Masking is ENABLED.")
if get(entry, "has_mask", false)
mask_path_candidate = get(entry, "mask_path", "")
if isfile(mask_path_candidate)
mask_path_for_pipeline = mask_path_candidate
println("DEBUG: Using mask for pipeline: $(mask_path_for_pipeline)")
else
msg = "Mask enabled but file not found: $(mask_path_candidate). Aborting pipeline."
warning_msg = true
@warn msg
println("DEBUG: $msg")
return
end
else
msg = "Mask enabled but no valid mask entry found for: $(selected_folder_main). Aborting pipeline."
warning_msg = true
@warn msg
println("DEBUG: $msg")
return
end
else
println("DEBUG: Masking is DISABLED. No mask will be applied.")
end
# Apply mask if enabled to get indices to process
spectrum_indices_to_process = collect(1:length(msi_data.spectra_metadata)) spectrum_indices_to_process = collect(1:length(msi_data.spectra_metadata))
if maskEnabled && !isempty(preprocessing_mask_route) && isfile(preprocessing_mask_route) if mask_path_for_pipeline !== nothing
current_pipeline_step = "Loading mask..." current_pipeline_step = "Applying mask..."
mask_matrix = load_and_prepare_mask(preprocessing_mask_route, msi_data.image_dims) println("DEBUG: Applying mask matrix to filter spectra...")
mask_matrix = load_and_prepare_mask(mask_path_for_pipeline, msi_data.image_dims)
masked_indices_set = get_masked_spectrum_indices(msi_data, mask_matrix) masked_indices_set = get_masked_spectrum_indices(msi_data, mask_matrix)
spectrum_indices_to_process = collect(masked_indices_set) spectrum_indices_to_process = collect(masked_indices_set)
if isempty(spectrum_indices_to_process)
msg = "No spectra remaining after applying mask. Aborting pipeline."
warning_msg = true
println("DEBUG: $msg")
return
end
println("DEBUG: $(length(spectrum_indices_to_process)) spectra remaining after mask application.")
else
println("DEBUG: No mask applied. Processing all $(length(msi_data.spectra_metadata)) spectra.")
end end
# Initialize spectra data structure # Initialize spectra data structure
current_pipeline_step = "Loading spectra..." current_pipeline_step = "Loading spectra..."
println("DEBUG: Loading $(length(spectrum_indices_to_process)) spectra into MutableSpectrum objects...")
current_spectra = Vector{MutableSpectrum}(undef, length(spectrum_indices_to_process)) current_spectra = Vector{MutableSpectrum}(undef, length(spectrum_indices_to_process))
Threads. @threads for i in 1:length(spectrum_indices_to_process) Threads.@threads for i in 1:length(spectrum_indices_to_process)
original_idx = spectrum_indices_to_process[i] original_idx = spectrum_indices_to_process[i]
mz, intensity = GetSpectrum(msi_data, original_idx) mz, intensity = GetSpectrum(msi_data, original_idx) # Fetch mz and intensity for the current spectrum
current_spectra[i] = MutableSpectrum(original_idx, mz, intensity, []) current_spectra[i] = MutableSpectrum(original_idx, Float64.(mz), Float64.(intensity), NamedTuple{(:mz, :intensity, :fwhm, :shape_r2, :snr, :prominence), NTuple{6, Float64}}[])
end
println("DEBUG: All spectra loaded into temporary structure for processing.")
# --- 2. Parameter Assembly with Validation ---
current_pipeline_step = "Configuring parameters..."
println("DEBUG: Configuring parameters and validating enabled steps...")
ref_peaks = Dict{Float64, String}(p["mz"] => p["label"] for p in reference_peaks_list)
final_params = Dict{Symbol, Dict{Symbol, Any}}()
validation_errors = String[]
# --- Stabilization ---
println("DEBUG: Checking Stabilization step (name: stabilization)")
if is_step_enabled("stabilization", pipeline_step_order)
println("DEBUG: Stabilization step is ENABLED. Setting method: $(stabilization_method).")
final_params[:Stabilization] = Dict{Symbol, Any}(:method => Symbol(stabilization_method))
else
println("DEBUG: Stabilization step is DISABLED. Skipping.")
end end
# 2. Parameter Assembly # --- Smoothing ---
current_pipeline_step = "Configuring parameters..." println("DEBUG: Checking Smoothing step (name: smoothing)")
final_params = Dict( if is_step_enabled("smoothing", pipeline_step_order)
:Stabilization => Dict(:method => Symbol(stabilization_method)), println("DEBUG: Smoothing step is ENABLED. Validating parameters.")
:Smoothing => Dict( window_val = validate_parse(validation_errors, smoothing_window, "Window", Int, "Smoothing")
order_val = validate_parse(validation_errors, smoothing_order, "Order", Int, "Smoothing")
final_params[:Smoothing] = Dict{Symbol, Any}(
:method => Symbol(smoothing_method), :method => Symbol(smoothing_method),
:window => parse(Int, smoothing_window), :window => something(window_val, 9),
:order => parse(Int, smoothing_order) :order => something(order_val, 2)
),
: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)
) )
) if window_val !== nothing && window_val < 1
push!(validation_errors, "(Smoothing) Window must be positive.")
end
if order_val !== nothing && order_val < 0
push!(validation_errors, "(Smoothing) Order must be non-negative.")
end
println("DEBUG: Smoothing parameters set: method=$(smoothing_method), window=$(something(window_val, 9)), order=$(something(order_val, 2)).")
else
println("DEBUG: Smoothing step is DISABLED. Skipping parameter validation.")
end
# --- Baseline Correction ---
println("DEBUG: Checking Baseline Correction step (name: baseline_correction)")
if is_step_enabled("baseline_correction", pipeline_step_order)
println("DEBUG: Baseline Correction step is ENABLED. Validating parameters.")
iterations_val = validate_parse(validation_errors, baseline_iterations, "Iterations", Int, "Baseline Correction")
baseline_window_val = validate_parse(validation_errors, baseline_window, "Window", Int, "Baseline Correction")
final_params[:BaselineCorrection] = Dict{Symbol, Any}(
:method => Symbol(baseline_method),
:iterations => something(iterations_val, 100),
:window => something(baseline_window_val, 20)
)
if iterations_val !== nothing && iterations_val < 0
push!(validation_errors, "(Baseline Correction) Iterations must be non-negative.")
end
if baseline_window_val !== nothing && baseline_window_val < 1
push!(validation_errors, "(Baseline Correction) Window must be positive.")
end
println("DEBUG: Baseline Correction parameters set: method=$(baseline_method), iterations=$(something(iterations_val, 100)), window=$(something(baseline_window_val, 20)).")
else
println("DEBUG: Baseline Correction step is DISABLED. Skipping parameter validation.")
end
# --- Normalization ---
println("DEBUG: Checking Normalization step (name: normalization)")
if is_step_enabled("normalization", pipeline_step_order)
println("DEBUG: Normalization step is ENABLED. Setting method: $(normalization_method).")
final_params[:Normalization] = Dict{Symbol, Any}(:method => Symbol(normalization_method))
else
println("DEBUG: Normalization step is DISABLED. Skipping.")
end
# --- Peak Picking ---
println("DEBUG: Checking Peak Picking step (name: peak_picking)")
if is_step_enabled("peak_picking", pipeline_step_order)
println("DEBUG: Peak Picking step is ENABLED. Validating parameters.")
snr_threshold_val = validate_parse(validation_errors, peak_picking_snr_threshold, "SNR Threshold", Float64, "Peak Picking")
half_window_val = validate_parse(validation_errors, peak_picking_half_window, "Half Window", Int, "Peak Picking")
min_peak_prominence_val = validate_parse(validation_errors, peak_picking_min_peak_prominence, "Min Prominence", Float64, "Peak Picking")
merge_peaks_tolerance_val = validate_parse(validation_errors, peak_picking_merge_peaks_tolerance, "Merge Tolerance", Float64, "Peak Picking")
final_params[:PeakPicking] = Dict{Symbol, Any}(
:method => Symbol(peak_picking_method),
:snr_threshold => something(snr_threshold_val, 3.0),
:half_window => something(half_window_val, 10),
:min_peak_prominence => something(min_peak_prominence_val, 0.1),
:merge_peaks_tolerance => something(merge_peaks_tolerance_val, 0.002)
)
if snr_threshold_val !== nothing && snr_threshold_val < 0
push!(validation_errors, "(Peak Picking) SNR Threshold must be non-negative.")
end
if half_window_val !== nothing && half_window_val < 1
push!(validation_errors, "(Peak Picking) Half Window must be positive.")
end
if min_peak_prominence_val !== nothing && (min_peak_prominence_val < 0 || min_peak_prominence_val > 1)
push!(validation_errors, "(Peak Picking) Min Prominence must be between 0 and 1.")
end
if merge_peaks_tolerance_val !== nothing && merge_peaks_tolerance_val < 0
push!(validation_errors, "(Peak Picking) Merge Tolerance must be non-negative.")
end
println("DEBUG: Peak Picking parameters set: method=$(peak_picking_method), snr_threshold=$(something(snr_threshold_val, 3.0)), half_window=$(something(half_window_val, 10))...")
else
println("DEBUG: Peak Picking step is DISABLED. Skipping parameter validation.")
end
# --- Peak Selection ---
println("DEBUG: Checking Peak Selection step (name: peak_selection)")
if is_step_enabled("peak_selection", pipeline_step_order)
println("DEBUG: Peak Selection step is ENABLED. Validating parameters.")
min_snr_val = validate_parse(validation_errors, peak_selection_min_snr, "Min SNR", Float64, "Peak Selection")
min_fwhm_ppm_val = validate_parse(validation_errors, peak_selection_min_fwhm_ppm, "Min FWHM", Float64, "Peak Selection")
max_fwhm_ppm_val = validate_parse(validation_errors, peak_selection_max_fwhm_ppm, "Max FWHM", Float64, "Peak Selection")
min_shape_r2_val = validate_parse(validation_errors, peak_selection_min_shape_r2, "Min Shape R2", Float64, "Peak Selection")
final_params[:PeakSelection] = Dict{Symbol, Any}(
:min_snr => something(min_snr_val, 0.0),
:min_fwhm_ppm => something(min_fwhm_ppm_val, 0.0),
:max_fwhm_ppm => something(max_fwhm_ppm_val, Inf),
:min_shape_r2 => something(min_shape_r2_val, 0.0)
)
if min_snr_val !== nothing && min_snr_val < 0
push!(validation_errors, "(Peak Selection) Min SNR must be non-negative.")
end
if min_fwhm_ppm_val !== nothing && min_fwhm_ppm_val < 0
push!(validation_errors, "(Peak Selection) Min FWHM must be non-negative.")
end
if max_fwhm_ppm_val !== nothing && max_fwhm_ppm_val < 0
push!(validation_errors, "(Peak Selection) Max FWHM must be non-negative.")
end
if min_shape_r2_val !== nothing && (min_shape_r2_val < 0 || min_shape_r2_val > 1)
push!(validation_errors, "(Peak Selection) Min Shape R2 must be between 0 and 1.")
end
println("DEBUG: Peak Selection parameters set: min_snr=$(something(min_snr_val, 0.0)), min_fwhm_ppm=$(something(min_fwhm_ppm_val, 0.0))...")
else
println("DEBUG: Peak Selection step is DISABLED. Skipping parameter validation.")
end
# --- Calibration ---
println("DEBUG: Checking Calibration step (name: calibration)")
if is_step_enabled("calibration", pipeline_step_order)
println("DEBUG: Calibration step is ENABLED. Validating parameters.")
ppm_tolerance_cal_val = validate_parse(validation_errors, calibration_ppm_tolerance, "PPM Tolerance", Float64, "Calibration")
fit_order_val = validate_parse(validation_errors, calibration_fit_order, "Fit Order", Int, "Calibration")
final_params[:Calibration] = Dict{Symbol, Any}(
:method => :internal_standards, # Fixed method
:ppm_tolerance => something(ppm_tolerance_cal_val, 20.0),
:fit_order => something(fit_order_val, 1) # Default to linear
)
if ppm_tolerance_cal_val !== nothing && ppm_tolerance_cal_val < 0
push!(validation_errors, "(Calibration) PPM Tolerance must be non-negative.")
end
if fit_order_val !== nothing && (fit_order_val < 0 || fit_order_val > 2)
push!(validation_errors, "(Calibration) Fit Order must be 0, 1, or 2.")
end
if enable_standards && isempty(ref_peaks)
push!(validation_errors, "(Calibration) Internal Standards are enabled, but no reference peaks are defined.")
end
println("DEBUG: Calibration parameters set: ppm_tolerance=$(something(ppm_tolerance_cal_val, 20.0)), fit_order=$(something(fit_order_val, 1)).")
else
println("DEBUG: Calibration step is DISABLED. Skipping parameter validation.")
end
# --- Peak Alignment ---
println("DEBUG: Checking Peak Alignment step (name: peak_alignment)")
if is_step_enabled("peak_alignment", pipeline_step_order)
println("DEBUG: Peak Alignment step is ENABLED. Validating parameters.")
alignment_tolerance_val = validate_parse(validation_errors, alignment_tolerance, "Tolerance", Float64, "Peak Alignment")
final_params[:PeakAlignment] = Dict{Symbol, Any}(
:method => Symbol(alignment_method),
:tolerance => something(alignment_tolerance_val, 0.002),
:tolerance_unit => Symbol(alignment_tolerance_unit)
)
if alignment_tolerance_val !== nothing && alignment_tolerance_val < 0
push!(validation_errors, "(Peak Alignment) Tolerance must be non-negative.")
end
println("DEBUG: Peak Alignment parameters set: method=$(alignment_method), tolerance=$(something(alignment_tolerance_val, 0.002)), tolerance_unit=$(alignment_tolerance_unit).")
else
println("DEBUG: Peak Alignment step is DISABLED. Skipping parameter validation.")
end
# --- Peak Binning ---
println("DEBUG: Checking Peak Binning step (name: peak_binning)")
if is_step_enabled("peak_binning", pipeline_step_order)
println("DEBUG: Peak Binning step is ENABLED. Validating parameters.")
binning_tolerance_val = validate_parse(validation_errors, binning_tolerance, "Tolerance", Float64, "Peak Binning")
min_peak_per_bin_val = validate_parse(validation_errors, binning_min_peak_per_bin, "Min Peaks Per Bin", Int, "Peak Binning")
final_params[:PeakBinning] = Dict{Symbol, Any}(
:method => Symbol(binning_method),
:tolerance => something(binning_tolerance_val, 20.0),
:tolerance_unit => Symbol(binning_tolerance_unit),
:min_peak_per_bin => something(min_peak_per_bin_val, 3)
)
if binning_tolerance_val !== nothing && binning_tolerance_val < 0
push!(validation_errors, "(Peak Binning) Tolerance must be non-negative.")
end
if min_peak_per_bin_val !== nothing && min_peak_per_bin_val < 1
push!(validation_errors, "(Peak Binning) Min Peaks Per Bin must be positive.")
end
println("DEBUG: Peak Binning parameters set: method=$(binning_method), tolerance=$(something(binning_tolerance_val, 20.0)), min_peak_per_bin=$(something(min_peak_per_bin_val, 3))...")
else
println("DEBUG: Peak Binning step is DISABLED. Skipping parameter validation.")
end
if !isempty(validation_errors)
msg = "Pipeline setup errors:\n" * join(validation_errors, "\n")
warning_msg = true
println("DEBUG: Validation errors encountered: $validation_errors")
return
end
# Build pipeline steps from enabled steps in order # Build pipeline steps from enabled steps in order
pipeline_stp = [step["name"] for step in pipeline_step_order if step["enabled"]] pipeline_stp = [step["name"] for step in pipeline_step_order if step["enabled"]]
println("DEBUG: Final enabled pipeline steps to execute: $pipeline_stp")
# Convert reference peaks
ref_peaks = Dict(p["mz"] => p["label"] for p in reference_peaks_list)
# 3. Execute Pipeline # 3. Execute Pipeline
current_pipeline_step = "Running preprocessing pipeline..." current_pipeline_step = "Running preprocessing pipeline..."
println("DEBUG: Starting pipeline execution with $(length(pipeline_stp)) enabled steps.")
feature_matrix_result, bin_info_result = execute_full_preprocessing( feature_matrix_result, bin_info_result = execute_full_preprocessing(
current_spectra, current_spectra,
final_params, final_params,
pipeline_stp, pipeline_stp,
ref_peaks, ref_peaks,
maskEnabled ? preprocessing_mask_route : nothing mask_path_for_pipeline
) do step ) do step
current_pipeline_step = "Processing: $step" current_pipeline_step = "Processing: $step"
println("DEBUG: Processing step: $step")
end end
println("DEBUG: Pipeline execution finished.")
# 4. Update Results Display # 4. Update Results Display
current_pipeline_step = "Updating results..." current_pipeline_step = "Updating results..."
println("DEBUG: Updating results display after pipeline completion.")
# Find the spectrum to display in "after" plot # Find the spectrum to display in "after" plot
display_spectrum_idx = findfirst(s -> s.id == selected_spectrum_id_for_plot, current_spectra) display_spectrum_idx = findfirst(s -> s.id == selected_spectrum_id_for_plot, current_spectra)
if display_spectrum_idx !== nothing if display_spectrum_idx !== nothing
processed_spectrum = current_spectra[display_spectrum_idx] processed_spectrum = current_spectra[display_spectrum_idx]
println("DEBUG: Displaying spectrum $(selected_spectrum_id_for_plot) after processing.")
# Create "after" plot data
after_trace = PlotlyBase.scatter( after_trace = PlotlyBase.scatter(
x=processed_spectrum.mz, x=processed_spectrum.mz,
y=processed_spectrum.intensity, y=processed_spectrum.intensity,
@ -1060,10 +1290,9 @@ end
traces_after = [after_trace] traces_after = [after_trace]
# Add peaks if they exist
if !isempty(processed_spectrum.peaks) if !isempty(processed_spectrum.peaks)
peak_mzs = [p.mz for p in 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_intensities = [p.intensity for p in processed_spectrum.peaks]
peak_trace = PlotlyBase.scatter( peak_trace = PlotlyBase.scatter(
x=peak_mzs, x=peak_mzs,
y=peak_intensities, y=peak_intensities,
@ -1072,14 +1301,19 @@ end
marker=attr(color="red", size=8) marker=attr(color="red", size=8)
) )
push!(traces_after, peak_trace) push!(traces_after, peak_trace)
println("DEBUG: $(length(processed_spectrum.peaks)) peaks picked for spectrum $(selected_spectrum_id_for_plot).")
else
println("DEBUG: No peaks picked for spectrum $(selected_spectrum_id_for_plot).")
end end
plotdata_after = traces_after plotdata_after = traces_after
plotlayout_after = PlotlyBase.Layout( plotlayout_after = PlotlyBase.Layout(
title="After Preprocessing (Spectrum $selected_spectrum_id_for_plot)", title="After Preprocessing (Spectrum $(selected_spectrum_id_for_plot))",
xaxis_title="m/z", xaxis_title="m/z",
yaxis_title="Intensity" yaxis_title="Intensity"
) )
else
println("DEBUG: Selected spectrum for display ($(selected_spectrum_id_for_plot)) not found in processed spectra.")
end end
# Save feature matrix if binning was performed # Save feature matrix if binning was performed
@ -1088,18 +1322,254 @@ end
mkpath(output_dir) mkpath(output_dir)
save_feature_matrix(feature_matrix_result, bin_info_result, output_dir) save_feature_matrix(feature_matrix_result, bin_info_result, output_dir)
msg = "Pipeline completed successfully. Feature matrix saved." msg = "Pipeline completed successfully. Feature matrix saved."
println("DEBUG: Feature matrix saved to $output_dir")
else else
msg = "Pipeline completed successfully." msg = "Pipeline completed successfully. No feature matrix generated (binning step not enabled)."
println("DEBUG: $msg")
end end
catch e catch e
msg = "Error during pipeline execution: $e" msg = "Error during pipeline execution: $e"
warning_msg = true warning_msg = true
@error "Pipeline failed" exception=(e, catch_backtrace()) @error "Pipeline failed" exception=(e, catch_backtrace())
println("DEBUG: Pipeline caught an exception: $e")
finally finally
progressPrep = false progressPrep = false
current_pipeline_step = "" current_pipeline_step = ""
GC.gc() println("DEBUG: run_full_pipeline finished (finally block).")
GC.gc() # Trigger garbage collection
if Sys.islinux()
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
end
end
end
@onbutton recalculate_suggestions_btn begin
if msi_data === nothing
msg = "Please load a file first."
warning_msg = true
return
end
try
msg = "Recalculating suggestions..."
progressPrep = true
ref_peaks = Dict(p["mz"] => p["label"] for p in reference_peaks_list)
recommended_params = main_precalculation(msi_data, reference_peaks=ref_peaks)
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
""
elseif value isa Tuple
@warn "Skipping invalid parameter suggestion (tuple): $value for $param_key"
"" # Set to empty string for safety
elseif value isa Number
string(value)
else
string(value)
end
if isempty(processed_value) && !(processed_value isa Number)
continue # Skip if processed_value is an empty string and not a number type
end
# Map recommended parameters to suggested_* reactive variables
if step_name == :Smoothing
if param_key == :window
suggested_smoothing_window = processed_value
smoothing_window = processed_value
elseif param_key == :order
suggested_smoothing_order = processed_value
smoothing_order = processed_value
end
elseif step_name == :BaselineCorrection
if param_key == :iterations
suggested_baseline_iterations = processed_value
baseline_iterations = processed_value
elseif param_key == :window
suggested_baseline_window = processed_value
baseline_window = processed_value
end
elseif step_name == :PeakAlignment
if param_key == :span
suggested_alignment_span = processed_value
alignment_span = processed_value
elseif param_key == :tolerance
suggested_alignment_tolerance = processed_value
alignment_tolerance = processed_value
elseif param_key == :max_shift_ppm
suggested_alignment_max_shift_ppm = processed_value
alignment_max_shift_ppm = processed_value
elseif param_key == :min_matched_peaks
suggested_alignment_min_matched_peaks = processed_value
alignment_min_matched_peaks = processed_value
end
elseif step_name == :Calibration
if param_key == :fit_order
suggested_calibration_fit_order = processed_value
calibration_fit_order = processed_value
elseif param_key == :ppm_tolerance
suggested_calibration_ppm_tolerance = processed_value
calibration_ppm_tolerance = processed_value
end
elseif step_name == :PeakPicking
if param_key == :snr_threshold
suggested_peak_picking_snr_threshold = processed_value
peak_picking_snr_threshold = processed_value
elseif param_key == :half_window
suggested_peak_picking_half_window = processed_value
peak_picking_half_window = processed_value
elseif param_key == :min_peak_prominence
suggested_peak_picking_min_peak_prominence = processed_value
peak_picking_min_peak_prominence = processed_value
elseif param_key == :merge_peaks_tolerance
suggested_peak_picking_merge_peaks_tolerance = processed_value
peak_picking_merge_peaks_tolerance = processed_value
elseif param_key == :min_peak_width_ppm
suggested_peak_picking_min_peak_width_ppm = processed_value
peak_picking_min_peak_width_ppm = processed_value
elseif param_key == :max_peak_width_ppm
suggested_peak_picking_max_peak_width_ppm = processed_value
peak_picking_max_peak_width_ppm = processed_value
elseif param_key == :min_peak_shape_r2
suggested_peak_picking_min_peak_shape_r2 = processed_value
peak_picking_min_peak_shape_r2 = processed_value
end
elseif step_name == :PeakSelection
if param_key == :min_snr
suggested_peak_selection_min_snr = processed_value
peak_selection_min_snr = processed_value
elseif param_key == :min_fwhm_ppm
suggested_peak_selection_min_fwhm_ppm = processed_value
peak_selection_min_fwhm_ppm = processed_value
elseif param_key == :max_fwhm_ppm
suggested_peak_selection_max_fwhm_ppm = processed_value
peak_selection_max_fwhm_ppm = processed_value
elseif param_key == :min_shape_r2
suggested_peak_selection_min_shape_r2 = processed_value
peak_selection_min_shape_r2 = processed_value
elseif param_key == :frequency_threshold
suggested_peak_selection_frequency_threshold = processed_value
peak_selection_frequency_threshold = processed_value
elseif param_key == :correlation_threshold
suggested_peak_selection_correlation_threshold = processed_value
peak_selection_correlation_threshold = processed_value
end
elseif step_name == :PeakBinning
if param_key == :tolerance
suggested_binning_tolerance = processed_value
binning_tolerance = processed_value
elseif param_key == :frequency_threshold
suggested_binning_frequency_threshold = processed_value
binning_frequency_threshold = processed_value
elseif param_key == :min_peak_per_bin
suggested_binning_min_peak_per_bin = processed_value
binning_min_peak_per_bin = processed_value
elseif param_key == :max_bin_width_ppm
suggested_binning_max_bin_width_ppm = processed_value
binning_max_bin_width_ppm = processed_value
elseif param_key == :num_uniform_bins
suggested_binning_num_uniform_bins = processed_value
binning_num_uniform_bins = processed_value
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 = "Suggestions have been recalculated."
catch e
msg = "Failed to recalculate suggestions: $e"
warning_msg = true
@error "Recalculation failed" exception=(e, catch_backtrace())
finally
progressPrep = false
end
end
@onbutton addReferencePeak begin
new_list = deepcopy(reference_peaks_list)
push!(new_list, Dict("mz" => 0.0, "label" => ""))
reference_peaks_list = new_list # Assign new list to trigger reactivity
end
@onbutton remove_peak_trigger begin
if action_index > -1
julia_index = action_index + 1
new_list = deepcopy(reference_peaks_list)
if 1 <= julia_index <= length(new_list)
deleteat!(new_list, julia_index)
reference_peaks_list = new_list
end
action_index = -1 # Reset
end
end
@onbutton move_step_up_trigger begin
if action_index > -1
julia_index = action_index + 1
if julia_index > 1
new_order = deepcopy(pipeline_step_order)
temp = new_order[julia_index]
new_order[julia_index] = new_order[julia_index - 1]
new_order[julia_index - 1] = temp
pipeline_step_order = new_order
end
action_index = -1 # Reset
end
end
@onbutton move_step_down_trigger begin
if action_index > -1
julia_index = action_index + 1
if julia_index < length(pipeline_step_order)
new_order = deepcopy(pipeline_step_order)
temp = new_order[julia_index]
new_order[julia_index] = new_order[julia_index + 1]
new_order[julia_index + 1] = temp
pipeline_step_order = new_order
end
action_index = -1 # Reset
end
end
@onbutton toggle_step_trigger begin
if action_index > -1
julia_index = action_index + 1
if 1 <= julia_index <= length(pipeline_step_order)
new_order = deepcopy(pipeline_step_order)
new_order[julia_index]["enabled"] = !new_order[julia_index]["enabled"]
pipeline_step_order = new_order
end
action_index = -1 # Reset
end end
end end
@ -1633,9 +2103,11 @@ end
# Convert to positive coordinates for processing # Convert to positive coordinates for processing
y_positive = yCoord < 0 ? abs(yCoord) : yCoord 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, plotlayout, xSpectraMz, ySpectraMz, spectrum_id = xySpectrumPlot(msi_data, xCoord, y_positive, imgWidth, imgHeight, selected_folder_main, mask_path=mask_path_for_plot)
plotdata_before = plotdata plotdata_before = plotdata
plotlayout_before = plotlayout plotlayout_before = plotlayout
selected_spectrum_id_for_plot = spectrum_id
idSpectrum = spectrum_id # we set the same obtained spectrum id to the UI
# Update coordinates based on actual plot title # Update coordinates based on actual plot title
# Extract title text from the Dict safely # Extract title text from the Dict safely
@ -1688,6 +2160,93 @@ end
end end
end end
@onbutton createNSpectrumPlot @time begin
if isempty(selected_folder_main)
msg = "No dataset selected. Please process a file and select a folder first."
warning_msg = true
return
end
progressSpectraPlot = true
btnStartDisable = true
btnPlotDisable = true
btnSpectraDisable = true
msg = "Loading plot for $(selected_folder_main)..."
try
sTime = time()
registry = load_registry(registry_path)
# Add error handling for registry access
if !haskey(registry, selected_folder_main)
msg = "Dataset '$(selected_folder_main)' not found in registry."
warning_msg = true
return
end
entry = registry[selected_folder_main]
target_path = entry["source_path"]
if target_path == "unknown (manually added)"
msg = "Dataset selected contained no route."
warning_msg = true
return
end
if msi_data === nothing || full_route != target_path
if msi_data !== nothing
close(msi_data)
end
msg = "Reloading $(basename(target_path)) for analysis..."
full_route = target_path
msi_data = OpenMSIData(target_path)
if haskey(get(entry, "metadata", Dict()), "global_min_mz") && entry["metadata"]["global_min_mz"] !== nothing
raw_min = entry["metadata"]["global_min_mz"]
raw_max = entry["metadata"]["global_max_mz"]
min_val = isa(raw_min, Dict) ? get(raw_min, "value", raw_min) : raw_min
max_val = isa(raw_max, Dict) ? get(raw_max, "value", raw_max) : raw_max
set_global_mz_range!(msi_data, convert(Float64, min_val), convert(Float64, max_val))
else
precompute_analytics(msi_data)
end
end
local mask_path_for_plot::Union{String, Nothing} = nothing
if maskEnabled && get(entry, "has_mask", false)
mask_path_for_plot = get(entry, "mask_path", "")
if !isfile(mask_path_for_plot)
@warn "Mask not found for plotting: $(mask_path_for_plot). Plotting without mask."
mask_path_for_plot = nothing
end
end
# Call the new nSpectrumPlot function
plotdata, plotlayout, xSpectraMz, ySpectraMz, spectrum_id = nSpectrumPlot(msi_data, idSpectrum, selected_folder_main, mask_path=mask_path_for_plot)
plotdata_before = plotdata
plotlayout_before = plotlayout
selected_spectrum_id_for_plot = spectrum_id
selectedTab = "tab2"
fTime = time()
eTime = round(fTime - sTime, digits=3)
msg = "Plot loaded in $(eTime) seconds"
log_memory_usage("nSpectrum Plot Generated", msi_data)
catch e
msg = "Could not retrieve spectrum: $e"
warning_msg = true
@error "nSpectrum plotting failed" exception=(e, catch_backtrace())
finally
progressSpectraPlot = false
btnPlotDisable = false
btnSpectraDisable = false
btnStartDisable = false
GC.gc() # Trigger garbage collection
if Sys.islinux()
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS
end
end
end
# --- Main View Handlers --- # --- Main View Handlers ---
@onbutton imgMinus begin @onbutton imgMinus begin
if isempty(selected_folder_main) return end if isempty(selected_folder_main) return end

View File

@ -62,6 +62,11 @@
<q-item clickable v-close-popup v-on:click="createXYPlot=true"> <q-item clickable v-close-popup v-on:click="createXYPlot=true">
<q-item-label>Spectrum plot (X,Y)</q-item-label> <q-item-label>Spectrum plot (X,Y)</q-item-label>
</q-item> </q-item>
<q-item clickable v-close-popup v-on:click="createNSpectrumPlot=true">
<q-item-section>
<q-item-label>Spectrum plot (ID)</q-item-label>
</q-item-section>
</q-item>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
<div class="row col-6"> <div class="row col-6">
@ -86,20 +91,21 @@
<q-card class="q-mb-md"> <q-card class="q-mb-md">
<q-card-section> <q-card-section>
<div class="text-h6">Internal Standards</div> <div class="text-h6">Internal Standards</div>
<div class="text-caption">Reference peaks for calibration and alignment</div> <div class="text-caption">Reference peaks for calibration, alignment and precise suggestion calculations</div>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<q-toggle v-model="enable_standards" v-on:click="enable_standards" label="Use Internal Standards" color="primary" class="q-mb-md" />
<!-- Keep your existing reference_peaks_list implementation --> <!-- Keep your existing reference_peaks_list implementation -->
<q-list bordered separator class="q-mt-md"> <q-list bordered separator class="q-mt-md">
<q-item v-for="(peak, index) in reference_peaks_list" :key="index"> <q-item v-for="(peak, index) in reference_peaks_list" :key="index">
<q-item-section avatar> <q-item-section avatar>
<q-btn flat round icon="delete" color="negative" @click="removeReferencePeak(index)"></q-btn> <q-btn flat round icon="delete" color="negative" v-on:click="action_index = index; remove_peak_trigger = true"></q-btn>
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="m/z" type="number" step="0.0001" <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> v-model="peak.mz" :rules="[val => !!val || 'Required', val => val > 0 || 'Must be positive']"></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Label (optional)" v-model="peak.label"></q-input> <q-input standout="custom-standout" label="Label (optional)" v-model="peak.label"></q-input>
@ -109,13 +115,15 @@
</q-item> </q-item>
<q-item> <q-item>
<q-item-section> <q-item-section>
<q-btn class="q-ma-sm btn-style" icon="add" label="Add Reference Peak" @click="addReferencePeak"></q-btn> <q-btn class="q-ma-sm btn-style" icon="add" label="Add Reference Peak" v-on:click="addReferencePeak=true"></q-btn>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-card-section> <div class="row justify-end q-mt-sm">
</q-card> <q-btn class="q-ma-sm" icon="functions" v-on:click="recalculate_suggestions_btn=true" label="Recalculate Suggestions" outline hint="Re-run automatic parameter suggestion using the current list of internal standards."/>
</div>
</q-card-section>
</q-card>
<!-- Reorderable Preprocessing Steps --> <!-- Reorderable Preprocessing Steps -->
<div class="text-h6 q-mb-md">Preprocessing Pipeline</div> <div class="text-h6 q-mb-md">Preprocessing Pipeline</div>
<q-list bordered> <q-list bordered>
@ -128,9 +136,9 @@
<q-item-section avatar> <q-item-section avatar>
<div class="row no-wrap"> <div class="row no-wrap">
<q-btn flat round icon="arrow_upward" size="sm" <q-btn flat round icon="arrow_upward" size="sm"
:disable="index === 0" @click.stop="moveStepUp(index)"></q-btn> :disable="index === 0" v-on:click.stop="action_index = index; move_step_up_trigger = true"></q-btn>
<q-btn flat round icon="arrow_downward" size="sm" <q-btn flat round icon="arrow_downward" size="sm"
:disable="index === pipeline_step_order.length - 1" @click.stop="moveStepDown(index)"></q-btn> :disable="index === pipeline_step_order.length - 1" v-on:click.stop="action_index = index; move_step_down_trigger = true"></q-btn>
</div> </div>
</q-item-section> </q-item-section>
@ -139,7 +147,7 @@
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<q-toggle v-model="step.enabled" color="green" @click.stop /> <q-toggle v-model="step.enabled" color="green" v-on:click.stop="action_index = index; toggle_step_trigger = true" />
</q-item-section> </q-item-section>
</template> </template>
@ -160,10 +168,10 @@
<q-radio v-model="smoothing_method" val="ma" label="Moving Average" /> <q-radio v-model="smoothing_method" val="ma" label="Moving Average" />
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Window Size" v-model.number="smoothing_window" type="number" /> <q-input standout="custom-standout" label="Window Size" v-model="smoothing_window" type="number" />
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Order (Savitzky-Golay)" v-model.number="smoothing_order" type="number" /> <q-input standout="custom-standout" label="Order (Savitzky-Golay)" v-model="smoothing_order" type="number" />
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
@ -186,11 +194,11 @@
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Iterations (for SNIP)" type="number" <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> :placeholder="suggested_baseline_iterations" v-model="baseline_iterations" hint="The number of iterations for the SNIP algorithm. A higher number results in a more aggressive baseline."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Window (for Median)" type="number" <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> :placeholder="suggested_baseline_window" v-model="baseline_window" hint="The window size for the median method, determining the local region for median calculation."></q-input>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
@ -226,11 +234,11 @@
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Span (for LOWESS)" type="number" step="0.01" <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> :placeholder="suggested_alignment_span" v-model="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>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Tolerance" type="number" step="0.001" <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> :placeholder="suggested_alignment_tolerance" v-model="alignment_tolerance" hint="The tolerance for matching peaks between the target and reference spectrum."></q-input>
</div> </div>
</div> </div>
<q-select standout="custom-standout" label="Tolerance Unit" v-model="alignment_tolerance_unit" <q-select standout="custom-standout" label="Tolerance Unit" v-model="alignment_tolerance_unit"
@ -238,11 +246,11 @@
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Max Shift PPM" type="number" <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> :placeholder="suggested_alignment_max_shift_ppm" v-model="alignment_max_shift_ppm" hint="The maximum allowed m/z shift in ppm to prevent spurious peak matches."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min Matched Peaks" type="number" <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> :placeholder="suggested_alignment_min_matched_peaks" v-model="alignment_min_matched_peaks" hint="The minimum number of matching peaks required to perform the alignment."></q-input>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
@ -256,12 +264,12 @@
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Fit Order" type="number" <q-input standout="custom-standout" label="Fit Order" type="number"
:placeholder="suggested_calibration_fit_order" v-model.number="calibration_fit_order" :placeholder="suggested_calibration_fit_order" v-model="calibration_fit_order"
hint="Polynomial order for the calibration curve (e.g., 1 or 2)."></q-input> hint="Polynomial order for the calibration curve (e.g., 1 or 2)."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="PPM Tolerance" type="number" <q-input standout="custom-standout" label="PPM Tolerance" type="number"
:placeholder="suggested_calibration_ppm_tolerance" v-model.number="calibration_ppm_tolerance" :placeholder="suggested_calibration_ppm_tolerance" v-model="calibration_ppm_tolerance"
hint="PPM tolerance for matching reference peaks to internal standards."></q-input> hint="PPM tolerance for matching reference peaks to internal standards."></q-input>
</div> </div>
</div> </div>
@ -285,35 +293,35 @@
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Signal to Noise Threshold" type="number" step="0.1" <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> :placeholder="suggested_peak_picking_snr_threshold" v-model="peak_picking_snr_threshold" hint="Signal-to-Noise Ratio threshold. Peaks with SNR below this value are discarded."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Half Window Size" type="number" <q-input standout="custom-standout" label="Half Window Size" type="number"
: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> :placeholder="suggested_peak_picking_half_window" v-model="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> </div>
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min Peak Prominence" type="number" step="0.01" <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> :placeholder="suggested_peak_picking_min_peak_prominence" v-model="peak_picking_min_peak_prominence" hint="Minimum required prominence of a peak, expressed as a fraction of its height."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Merge Peaks Tolerance (m/z)" type="number" step="0.001" <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> :placeholder="suggested_peak_picking_merge_peaks_tolerance" v-model="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> </div>
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min Peak Width (PPM)" type="number" <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> :placeholder="suggested_peak_picking_min_peak_width_ppm" v-model="peak_picking_min_peak_width_ppm" hint="Minimum acceptable peak width (FWHM) in ppm."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Max Peak Width (PPM)" type="number" <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> :placeholder="suggested_peak_picking_max_peak_width_ppm" v-model="peak_picking_max_peak_width_ppm" hint="Maximum acceptable peak width (FWHM) in ppm."></q-input>
</div> </div>
</div> </div>
<q-input standout="custom-standout" label="Min Peak Shape R2" type="number" step="0.01" class="q-mt-md" <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> :placeholder="suggested_peak_picking_min_peak_shape_r2" v-model="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-section>
</q-card> </q-card>
@ -325,32 +333,32 @@
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min SNR" type="number" step="0.1" <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> :placeholder="suggested_peak_selection_min_snr" v-model="peak_selection_min_snr" hint="Minimum Signal-to-Noise Ratio for a peak to be kept."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min FWHM (PPM)" type="number" <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> :placeholder="suggested_peak_selection_min_fwhm_ppm" v-model="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> </div>
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Max FWHM (PPM)" type="number" <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> :placeholder="suggested_peak_selection_max_fwhm_ppm" v-model="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>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min Peak Shape R2" type="number" step="0.01" <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> :placeholder="suggested_peak_selection_min_shape_r2" v-model="peak_selection_min_shape_r2" hint="Minimum R-squared value from a Gaussian fit, filtering for good peak shape."></q-input>
</div> </div>
</div> </div>
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Frequency Threshold" type="number" step="0.01" <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" :placeholder="suggested_peak_selection_frequency_threshold" v-model="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> hint="The minimum fraction of spectra a peak must be present in to be kept (0.0 to 1.0)."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Correlation Threshold" type="number" step="0.01" <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" :placeholder="suggested_peak_selection_correlation_threshold" v-model="peak_selection_correlation_threshold"
hint="Minimum correlation with neighboring peaks (not yet implemented)."></q-input> hint="Minimum correlation with neighboring peaks (not yet implemented)."></q-input>
</div> </div>
</div> </div>
@ -373,7 +381,7 @@
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Tolerance (for Adaptive)" type="number" step="0.001" <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> :placeholder="suggested_binning_tolerance" v-model="binning_tolerance" hint="Tolerance for grouping peaks into a bin in adaptive mode."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-select standout="custom-standout" label="Tolerance Unit" v-model="binning_tolerance_unit" <q-select standout="custom-standout" label="Tolerance Unit" v-model="binning_tolerance_unit"
@ -383,24 +391,24 @@
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Frequency Threshold" type="number" step="0.01" <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> :placeholder="suggested_binning_frequency_threshold" v-model="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> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Min Peaks Per Bin" type="number" <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> :placeholder="suggested_binning_min_peak_per_bin" v-model="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> </div>
<div class="row q-col-gutter-sm q-mt-md"> <div class="row q-col-gutter-sm q-mt-md">
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Max Bin Width (PPM)" type="number" <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> :placeholder="suggested_binning_max_bin_width_ppm" v-model="binning_max_bin_width_ppm" hint="Maximum width of a bin in ppm for adaptive mode."></q-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<q-input standout="custom-standout" label="Number of Uniform Bins" type="number" <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> :placeholder="suggested_binning_num_uniform_bins" v-model="binning_num_uniform_bins" hint="The number of bins to create for the uniform method."></q-input>
</div> </div>
</div> </div>
<q-toggle v-model="binning_intensity_weighted_centers" label="Intensity Weighted Centers" <q-toggle v-model="binning_intensity_weighted_centers" v-on:click="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> 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-section>
</q-card> </q-card>
@ -410,8 +418,8 @@
<!-- Pipeline Controls --> <!-- Pipeline Controls -->
<div class="row justify-end items-center q-mt-md"> <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="save" v-on: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 class="q-ma-sm" icon="upload_file" v-on:click="import_params_btn=true" label="Import Params" outline />
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" <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" /> v-on:click="run_full_pipeline=true" padding="lg" label="Run Pipeline" />
</div> </div>
@ -422,8 +430,8 @@
<!-- Pipeline Controls --> <!-- Pipeline Controls -->
<div class="row justify-end items-center q-mt-md"> <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="save" v-on: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 class="q-ma-sm" icon="upload_file" v-on:click="import_params_btn=true" label="Import Params" outline />
<q-btn :loading="progressPrep" class="q-ma-sm btn-style" icon="play_arrow" <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" /> v-on:click="run_full_pipeline=true" padding="lg" label="Run Pipeline" />
</div> </div>
@ -525,6 +533,11 @@
<q-item-label>Spectrum plot (X,Y)</q-item-label> <q-item-label>Spectrum plot (X,Y)</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item clickable v-close-popup v-on:click="createNSpectrumPlot=true">
<q-item-section>
<q-item-label>Spectrum plot (ID)</q-item-label>
</q-item-section>
</q-item>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
</div> </div>
@ -688,7 +701,7 @@
<div id="image-container-normal" class="row st-col col-12"> <div id="image-container-normal" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none"> <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" <plotly id="plotImg" :data="plotdataImg" :layout="plotlayoutImg" class="q-pa-none q-ma-none sync_data"
@click="data_click"></plotly> v-on:click="data_click"></plotly>
</div> </div>
<div class="col-2 q-pa-none q-ma-none"> <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> <q-img id="colorbar-normal" class="q-ma-none q-pa-none" :src="colorbar"></q-img>
@ -711,7 +724,7 @@
<div id="image-container-triq" class="row st-col col-12"> <div id="image-container-triq" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none "> <div class="col-10 q-pa-none q-ma-none ">
<plotly id="plotImgT" :data="plotdataImgT" :layout="plotlayoutImgT" <plotly id="plotImgT" :data="plotdataImgT" :layout="plotlayoutImgT"
class="q-pa-none q-ma-none sync_data" @click="data_click"></plotly> class="q-pa-none q-ma-none sync_data" v-on:click="data_click"></plotly>
</div> </div>
<div class="col-2 q-pa-none q-ma-none "> <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> <q-img id="colorbar-triq" class="q-ma-none q-pa-none" :src="colorbarT"></q-img>
@ -747,6 +760,11 @@
<q-item-label>Spectrum plot (X,Y)</q-item-label> <q-item-label>Spectrum plot (X,Y)</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item clickable v-close-popup v-on:click="createNSpectrumPlot=true">
<q-item-section>
<q-item-label>Spectrum plot (ID)</q-item-label>
</q-item-section>
</q-item>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
</div> </div>

View File

@ -1,665 +0,0 @@
<header id="header">
<img src="/css/LABI_logo.png" alt="Labi Logo Icon" id="imgLogo">
<div>
<h4>JuliaMSI&nbsp;</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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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>

File diff suppressed because it is too large Load Diff

View File

@ -531,7 +531,13 @@ function meanSpectrumPlot(data::MSIData, dataset_name::String=""; mask_path::Uni
showgrid=true, showgrid=true,
tickformat=".3g" tickformat=".3g"
), ),
margin=attr(l=0, r=0, t=120, b=0, pad=0) margin=attr(l=0, r=0, t=120, b=0, pad=0),
legend=attr(
x=1.0,
y=1.0,
xanchor="right",
yanchor="top"
)
) )
# Use the new, efficient function from the backend # Use the new, efficient function from the backend
@ -582,22 +588,25 @@ Generates a plot for the spectrum at a specific coordinate (for imaging data) or
""" """
function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int, imgHeight::Int, dataset_name::String=""; mask_path::Union{String, Nothing}=nothing) function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int, imgHeight::Int, dataset_name::String=""; mask_path::Union{String, Nothing}=nothing)
local mz::AbstractVector, intensity::AbstractVector local mz::AbstractVector, intensity::AbstractVector
local plot_title::String local spectrum_id::Int = -1 # Initialize spectrum_id
local spectrum_mode = MSI_src.CENTROID # Default to centroid mz, intensity = Float64[], Float64[]
base_title = ""
spectrum_mode = MSI_src.PROFILE
is_imaging = data.source isa ImzMLSource if data.source isa ImzMLSource
w, h = data.image_dims
x = clamp(xCoord, 1, w)
y = clamp(yCoord, 1, h)
spectrum_id = (y - 1) * w + x # Calculate spectrum_id for imaging data
if is_imaging # Check if spectrum stats are available and retrieve the mode
x = clamp(xCoord, 1, imgWidth)
y = clamp(yCoord, 1, imgHeight)
# Get spectrum mode
if data.spectrum_stats_df !== nothing && hasproperty(data.spectrum_stats_df, :Mode) if data.spectrum_stats_df !== nothing && hasproperty(data.spectrum_stats_df, :Mode)
w, h = data.image_dims if spectrum_id <= length(data.spectrum_stats_df.Mode)
if y > 0 && x > 0 && y <= h && x <= w try
idx = (y - 1) * w + x spectrum_mode = data.spectrum_stats_df.Mode[spectrum_id]
if idx <= length(data.spectrum_stats_df.Mode) catch e
spectrum_mode = data.spectrum_stats_df.Mode[idx] @warn "Could not retrieve spectrum mode for index $spectrum_id. Defaulting to PROFILE."
spectrum_mode = MSI_src.PROFILE
end end
end end
end end
@ -625,6 +634,7 @@ function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int,
else else
# For non-imaging data, treat xCoord as the spectrum index # For non-imaging data, treat xCoord as the spectrum index
index = clamp(xCoord, 1, length(data.spectra_metadata)) index = clamp(xCoord, 1, length(data.spectra_metadata))
spectrum_id = index # Assign index to spectrum_id for non-imaging data
if data.spectrum_stats_df !== nothing && hasproperty(data.spectrum_stats_df, :Mode) if data.spectrum_stats_df !== nothing && hasproperty(data.spectrum_stats_df, :Mode)
if index <= length(data.spectrum_stats_df.Mode) if index <= length(data.spectrum_stats_df.Mode)
@ -660,7 +670,13 @@ function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int,
showgrid=true, showgrid=true,
tickformat=".3g" tickformat=".3g"
), ),
margin=attr(l=0, r=0, t=120, b=0, pad=0) margin=attr(l=0, r=0, t=120, b=0, pad=0),
legend=attr(
x=1.0,
y=1.0,
xanchor="right",
yanchor="top"
)
) )
# Downsample for plotting performance # Downsample for plotting performance
@ -675,7 +691,98 @@ function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int,
plotdata = [trace] plotdata = [trace]
plotlayout = layout plotlayout = layout
return plotdata, plotlayout, mz, intensity return plotdata, plotlayout, mz, intensity, spectrum_id
end
"""
nSpectrumPlot(data::MSIData, id::Int, dataset_name::String=""; mask_path::Union{String, Nothing}=nothing)
Generates a plot for a spectrum specified by its linear `id` for any type of MSIData.
# Arguments
- `data`: The `MSIData` object.
- `id`: The linear index (ID) of the spectrum to plot.
- `dataset_name`: Optional name of the dataset for the plot title.
- `mask_path`: Optional path to a mask file (currently not used for plotting by ID, but kept for signature consistency).
# Returns
- `plotdata`: A vector containing the Plotly trace.
- `plotlayout`: The Plotly layout for the plot.
- `mz`: The m/z values of the spectrum.
- `intensity`: The intensity values of the spectrum.
- `spectrum_id`: The linear index of the spectrum (same as input `id`).
"""
function nSpectrumPlot(data::MSIData, id::Int, dataset_name::String=""; mask_path::Union{String, Nothing}=nothing)
local mz::AbstractVector, intensity::AbstractVector
local plot_title::String
local spectrum_mode = MSI_src.CENTROID # Default to centroid
local spectrum_id::Int = id # Spectrum ID is the input id
# Validate ID
if id < 1 || id > length(data.spectra_metadata)
@warn "Spectrum ID $id is out of bounds."
mz, intensity = Float64[], Float64[]
base_title = "Spectrum ID $id (Out of Bounds)"
spectrum_id = 0 # Indicate invalid spectrum ID
else
# Get spectrum mode
if data.spectrum_stats_df !== nothing && hasproperty(data.spectrum_stats_df, :Mode)
if id <= length(data.spectrum_stats_df.Mode)
spectrum_mode = data.spectrum_stats_df.Mode[id]
end
end
# Retrieve spectrum data
process_spectrum(data, id) do recieved_mz, recieved_intensity
mz = recieved_mz
intensity = recieved_intensity
end
base_title = "Spectrum #$id"
end
plot_title = isempty(dataset_name) ? base_title : "$base_title for: $dataset_name"
layout = PlotlyBase.Layout(
title=PlotlyBase.attr(
text=plot_title,
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
hovermode="closest",
xaxis=PlotlyBase.attr(
title="<i>m/z</i>",
showgrid=true
),
yaxis=PlotlyBase.attr(
title="Intensity",
showgrid=true,
tickformat=".3g"
),
margin=attr(l=0, r=0, t=120, b=0, pad=0),
legend=attr(
x=1.0,
y=1.0,
xanchor="right",
yanchor="top"
)
)
# Downsample for plotting performance
mz_down, int_down = MSI_src.downsample_spectrum(mz, intensity)
trace = if spectrum_mode == MSI_src.CENTROID
PlotlyBase.stem(x=mz_down, y=int_down, marker=attr(size=1, color="blue", opacity=0.5), name="Spectrum", hoverinfo="x", hovertemplate="<b>m/z</b>: %{x:.4f}<extra></extra>")
else
PlotlyBase.scatter(x=mz_down, y=int_down, mode="lines", marker=attr(size=1, color="blue", opacity=0.5), name="Spectrum", hoverinfo="x", hovertemplate="<b>m/z</b>: %{x:.4f}<extra></extra>")
end
plotdata = [trace]
plotlayout = layout
return plotdata, plotlayout, mz, intensity, spectrum_id
end end
""" """
@ -722,7 +829,13 @@ function sumSpectrumPlot(data::MSIData, dataset_name::String=""; mask_path::Unio
showgrid=true, showgrid=true,
tickformat=".3g" tickformat=".3g"
), ),
margin=attr(l=0, r=0, t=120, b=0, pad=0) margin=attr(l=0, r=0, t=120, b=0, pad=0),
legend=attr(
x=1.0,
y=1.0,
xanchor="right",
yanchor="top"
)
) )
# Use the get_total_spectrum function from the backend # Use the get_total_spectrum function from the backend

View File

@ -389,10 +389,11 @@ function save_feature_matrix(feature_matrix::Matrix{Float64}, bin_info, output_d
return csv_path, csv_path_standard return csv_path, csv_path_standard
end end
function execute_full_preprocessing(spectra::Vector{MutableSpectrum}, params::Dict, function execute_full_preprocessing(progress_callback::Function, # Now first positional
spectra::Vector{MutableSpectrum}, params::Dict,
pipeline_steps::Vector{String}, reference_peaks::Dict, pipeline_steps::Vector{String}, reference_peaks::Dict,
mask_path::Union{String, Nothing}=nothing; mask_path::Union{String, Nothing} # Positional argument
progress_callback::Function=(step -> nothing)) )
println("Starting preprocessing pipeline with $(length(spectra)) spectra") println("Starting preprocessing pipeline with $(length(spectra)) spectra")
println("Steps: $(join(pipeline_steps, " -> "))") println("Steps: $(join(pipeline_steps, " -> "))")

View File

@ -229,53 +229,189 @@ function get_spectrum_attributes(stream::IO, hIbd::IO)
return [x_skip, y_skip, mz_first, array_len_skip, spectrum_end_skip] return [x_skip, y_skip, mz_first, array_len_skip, spectrum_end_skip]
end end
function determine_parser(stream::IO, mz_is_compressed::Bool, int_is_compressed::Bool)
start_pos = position(stream)
spectrum_xml = ""
try # Helper function to read an entire spectrum block from the stream
# Find the start of the first spectrum tag function read_spectrum_block(stream::IO)
while !eof(stream) lines = String[]
line = readline(stream) in_spectrum = false
if occursin("<spectrum ", line)
spectrum_buffer = IOBuffer() # Store the stream's position before starting to read this block.
write(spectrum_buffer, line) # This is important for determining if a block was successfully read.
# Read until the end of the spectrum tag start_pos = position(stream)
while !eof(stream)
line = readline(stream) while !eof(stream)
write(spectrum_buffer, line) line = readline(stream)
if occursin("</spectrum>", line)
break if occursin("<spectrum ", line)
end in_spectrum = true
end push!(lines, line)
spectrum_xml = String(take!(spectrum_buffer)) elseif in_spectrum
break # Found the first spectrum, so we can stop push!(lines, line)
if occursin("</spectrum>", line)
return join(lines, "\n")
end end
end end
finally
seek(stream, start_pos) # Always reset stream position
end end
if isempty(spectrum_xml) # If we reach EOF without finding </spectrum> for a started block,
# Fallback based on compression flags if no spectrum tag found # or if no <spectrum> was found at all, return empty.
return (mz_is_compressed || int_is_compressed) ? :compressed : :uncompressed if isempty(lines)
seek(stream, start_pos) # Reset stream position if no block was found
return ""
else
# Should not happen if XML is well-formed, but just in case
@warn "Reached EOF while parsing spectrum block without finding </spectrum> tag. Returning partial block."
return join(lines, "\n")
end end
end
# Inspect the XML content # Helper function to parse coordinates from a spectrum block
has_neofx_markers = occursin("encodedLength=\"0\"", spectrum_xml) && function parse_coordinates(spectrum_block::String)
occursin("external encoded length", spectrum_xml) x = Int32(0)
y = Int32(0)
has_external_data_markers = occursin("IMS:1000101", spectrum_xml) && x_match = match(r"IMS:1000050[^>]*value=\"(\d+)\"", spectrum_block)
occursin("IMS:1000102", spectrum_xml) && y_match = match(r"IMS:1000051[^>]*value=\"(\d+)\"", spectrum_block)
occursin("IMS:1000103", spectrum_xml)
if has_neofx_markers x = x_match !== nothing ? parse(Int32, x_match.captures[1]) : x
return :neofx y = y_match !== nothing ? parse(Int32, y_match.captures[1]) : y
end
# If not neofx, it must be compressed (as uncompressed path is no longer supported) return x, y
return :compressed end
# Helper function to parse spectrum mode from a spectrum block
function parse_spectrum_mode(spectrum_block::String, global_mode::SpectrumMode)
if occursin("MS:1000127", spectrum_block)
return CENTROID
elseif occursin("MS:1000128", spectrum_block)
return PROFILE
else
return global_mode
end
end
# Helper function to parse binary data arrays
function parse_binary_data_arrays(spectrum_block::String, spectrum_start_line::String,
default_mz_format::DataType, default_intensity_format::DataType,
mz_is_compressed::Bool, int_is_compressed::Bool)
array_data_type = @NamedTuple{is_mz::Bool, array_length::Int32, encoded_length::Int64, offset::Int64}
current_array_data = array_data_type[]
for bda_match in eachmatch(r"<binaryDataArray.*?>(.*?)</binaryDataArray>"sm, spectrum_block)
bda_content = bda_match.match # This will be the full <binaryDataArray> block
is_mz = occursin("MS:1000514", bda_content) || occursin("mzArray", bda_content)
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", bda_content)
array_length = Int32(0)
if array_len_cv_match !== nothing
array_length = parse(Int32, array_len_cv_match.captures[1])
end
# Fallback for defaultArrayLength from the spectrum_start_line
if array_length == 0
default_match = match(r"defaultArrayLength=\"(\d+)\"", spectrum_start_line)
if default_match !== nothing
array_length = parse(Int32, default_match.captures[1])
end end
end
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", bda_content)
encoded_length = Int64(0)
if encoded_len_cv_match !== nothing
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
else
encoded_len_attr_match = match(r"encodedLength=\"(\d+)\"", bda_content)
if encoded_len_attr_match !== nothing
encoded_length = parse(Int64, encoded_len_attr_match.captures[1])
end
end
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", bda_content)
offset = Int64(0)
if offset_match !== nothing
offset = parse(Int64, offset_match.captures[1])
end
if array_length > 0 && offset > 0
push!(current_array_data, (is_mz=is_mz, array_length=array_length,
encoded_length=encoded_length, offset=offset))
end
end
return current_array_data
end
# Unified function to parse spectrum metadata
function parse_imzml_spectrum_block(stream::IO, hIbd::Union{IO, ThreadSafeFileHandle}, param_groups::Dict{String, SpecDim},
width::Int32, height::Int32, num_spectra::Int32,
default_mz_format::DataType, default_intensity_format::DataType,
mz_is_compressed::Bool, int_is_compressed::Bool, global_mode::SpectrumMode)
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
# Store the initial position of the stream, in case read_spectrum_block fails to find a spectrum.
# This helps reset the stream for subsequent debugging if needed, though typically it should find a spectrum.
initial_stream_pos = position(stream)
for k in 1:num_spectra
spectrum_block_content = read_spectrum_block(stream)
if isempty(spectrum_block_content)
@warn "Expected spectrum block $k but found none or reached EOF prematurely. Stopping parsing."
# Fill remaining spectra_metadata with placeholder or error.
for j in k:num_spectra
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, Int64(0), 0, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, Int64(0), 0, :intensity)
spectra_metadata[j] = SpectrumMetadata(Int32(0), Int32(0), "", :sample, global_mode, mz_asset, int_asset)
end
break
end
x, y = parse_coordinates(spectrum_block_content)
spectrum_mode = parse_spectrum_mode(spectrum_block_content, global_mode)
# The spectrum_start_line is needed for defaultArrayLength fallback in parse_binary_data_arrays
# We need to extract it from spectrum_block_content or pass the first line of the spectrum_block_content
# Let's assume the first line of spectrum_block_content is the spectrum_start_line for this purpose
spectrum_start_line_from_block = String(split(spectrum_block_content, '\n')[1])
current_array_data = parse_binary_data_arrays(spectrum_block_content, spectrum_start_line_from_block,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed)
# Separate m/z and intensity arrays
mz_data = filter(d -> d.is_mz, current_array_data)
int_data = filter(d -> !d.is_mz, current_array_data)
if length(mz_data) != 1 || length(int_data) != 1
println("DEBUG: Spectrum $k is empty or invalid - creating placeholder metadata")
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, Int64(0), 0, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, Int64(0), 0, :intensity)
else
mz_info = mz_data[1]
int_info = int_data[1]
if k == 1 # Only print debug for the first spectrum
println("DEBUG First spectrum parsed (unified):")
println(" Coordinates: x=$x, y=$y")
println(" Mode: $spectrum_mode")
println(" m/z array: array_length=$(mz_info.array_length), encoded_length=$(mz_info.encoded_length), offset=$(mz_info.offset)")
println(" intensity array: array_length=$(int_info.array_length), encoded_length=$(int_info.encoded_length), offset=$(int_info.offset)")
end
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, mz_info.offset,
mz_is_compressed ? mz_info.encoded_length : mz_info.array_length, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, int_info.offset,
int_is_compressed ? int_info.encoded_length : int_info.array_length, :intensity)
end
spectra_metadata[k] = SpectrumMetadata(x, y, "", :sample, spectrum_mode, mz_asset, int_asset)
end
return spectra_metadata
end
function load_imzml_lazy(file_path::String; cache_size::Int=100) function load_imzml_lazy(file_path::String; cache_size::Int=100)
println("DEBUG: Checking for .imzML file at $file_path") println("DEBUG: Checking for .imzML file at $file_path")
if !isfile(file_path) if !isfile(file_path)
@ -342,22 +478,9 @@ function load_imzml_lazy(file_path::String; cache_size::Int=100)
println("DEBUG: m/z compressed: $mz_is_compressed, Intensity compressed: $int_is_compressed") println("DEBUG: m/z compressed: $mz_is_compressed, Intensity compressed: $int_is_compressed")
println("DEBUG: Global mode: $global_mode") println("DEBUG: Global mode: $global_mode")
# --- Parser Selection --- local spectra_metadata = parse_imzml_spectrum_block(stream, ts_hIbd, param_groups, width, height, num_spectra,
parser_type = determine_parser(stream, mz_is_compressed, int_is_compressed) default_mz_format, default_intensity_format,
println("DEBUG: Selected parser: $parser_type") mz_is_compressed, int_is_compressed, global_mode)
local spectra_metadata
if parser_type == :neofx
println("DEBUG: Using neofx parser.")
spectra_metadata = parse_neofx(stream, ts_hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
else
println("DEBUG: Using compressed parser.")
spectra_metadata = parse_compressed(stream, ts_hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
end
println("DEBUG: Metadata parsing complete.") println("DEBUG: Metadata parsing complete.")
@ -554,150 +677,6 @@ function parse_compressed(stream::IO, hIbd::Union{IO, ThreadSafeFileHandle}, par
return spectra_metadata return spectra_metadata
end end
function parse_neofx(stream::IO, hIbd::Union{IO, ThreadSafeFileHandle}, param_groups::Dict{String, SpecDim},
width::Int32, height::Int32, num_spectra::Int32,
default_mz_format::DataType, default_intensity_format::DataType,
mz_is_compressed::Bool, int_is_compressed::Bool, global_mode::SpectrumMode)
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
array_data_type = @NamedTuple{is_mz::Bool, array_length::Int32, encoded_length::Int64, offset::Int64}
for k in 1:num_spectra
# Initialize variables for this spectrum
x = Int32(0)
y = Int32(0)
spectrum_mode = global_mode
current_array_data = array_data_type[]
spectrum_start_line = ""
# Find the start of the spectrum tag
line = ""
while !eof(stream)
line = readline(stream)
if occursin("<spectrum ", line)
spectrum_start_line = line
break
end
end
# Parse lines within the spectrum block
while !eof(stream)
if !occursin("<spectrum ", line) # Avoid re-reading the first line
line = readline(stream)
end
if occursin("</spectrum>", line)
break
end
# Parse coordinates
x_match = match(r"IMS:1000050.*?value=\"(\d+)\"", line)
if x_match !== nothing
x = parse(Int32, x_match.captures[1])
end
y_match = match(r"IMS:1000051.*?value=\"(\d+)\"", line)
if y_match !== nothing
y = parse(Int32, y_match.captures[1])
end
# Parse mode
if occursin("MS:1000127", line)
spectrum_mode = CENTROID
elseif occursin("MS:1000128", line)
spectrum_mode = PROFILE
end
# More robust binaryDataArray detection
if occursin("<binaryDataArray", line)
bda_lines = [line]
if !occursin("</binaryDataArray>", line)
while !eof(stream)
bda_line = readline(stream)
push!(bda_lines, bda_line)
if occursin("</binaryDataArray>", bda_line)
break
end
end
end
bda_content = join(bda_lines, "\n")
# Parse from bda_content
is_mz = occursin("MS:1000514", bda_content) || occursin("mzArray", bda_content)
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", bda_content)
array_length = Int32(0)
if array_len_cv_match !== nothing
array_length = parse(Int32, array_len_cv_match.captures[1])
end
# Fallback for defaultArrayLength
if array_length == 0
default_match = match(r"defaultArrayLength=\"(\d+)\"", spectrum_start_line)
if default_match !== nothing
array_length = parse(Int32, default_match.captures[1])
end
end
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", bda_content)
encoded_length = Int64(0)
if encoded_len_cv_match !== nothing
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
else
encoded_len_attr_match = match(r"encodedLength=\"(\d+)\"", bda_content)
if encoded_len_attr_match !== nothing
encoded_length = parse(Int64, encoded_len_attr_match.captures[1])
end
end
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", bda_content)
offset = Int64(0)
if offset_match !== nothing
offset = parse(Int64, offset_match.captures[1])
end
if array_length > 0 && offset > 0
push!(current_array_data, (is_mz=is_mz, array_length=array_length,
encoded_length=encoded_length, offset=offset))
end
end
# Reset line to continue loop
line = ""
end
# Separate m/z and intensity arrays
mz_data = filter(d -> d.is_mz, current_array_data)
int_data = filter(d -> !d.is_mz, current_array_data)
if length(mz_data) != 1 || length(int_data) != 1
println("DEBUG: Spectrum $k is empty or invalid - creating placeholder metadata")
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, Int64(0), 0, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, Int64(0), 0, :intensity)
else
mz_info = mz_data[1]
int_info = int_data[1]
if k == 1
println("DEBUG First spectrum parsed:")
println(" Coordinates: x=$x, y=$y")
println(" Mode: $spectrum_mode")
println(" m/z array: array_length=$(mz_info.array_length), encoded_length=$(mz_info.encoded_length), offset=$(mz_info.offset)")
println(" intensity array: array_length=$(int_info.array_length), encoded_length=$(int_info.encoded_length), offset=$(int_info.offset)")
end
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, mz_info.offset,
mz_is_compressed ? mz_info.encoded_length : mz_info.array_length, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, int_info.offset,
int_is_compressed ? int_info.encoded_length : int_info.array_length, :intensity)
end
spectra_metadata[k] = SpectrumMetadata(x, y, "", :sample, spectrum_mode, mz_asset, int_asset)
end
return spectra_metadata
end
# --- End of content from imzML.jl ---
# ============================================================================= # =============================================================================
# #