From 0164e54d77c42c2c9a8e49b1032c145d147bd961 Mon Sep 17 00:00:00 2001 From: Pixelguy14 Date: Mon, 8 Dec 2025 11:25:42 -0600 Subject: [PATCH] Fixed issues in the UI, added benchmark to compare with old juliaMSI library with the new custom one, incorporating platform detection and memory for amplifying cache pools --- Project.toml | 11 +- app.jl | 253 +++++++++--- app.jl.html | 8 +- config/env/global.jl | 4 +- julia_imzML_visual.jl | 16 +- src/Common.jl | 2 +- src/MSIData.jl | 38 +- src/Platform.jl | 29 ++ start_MSI_GUI.jl | 4 +- test/benchmark.jl | 932 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1226 insertions(+), 71 deletions(-) create mode 100644 src/Platform.jl create mode 100644 test/benchmark.jl diff --git a/Project.toml b/Project.toml index 94a4fe2..c34e178 100644 --- a/Project.toml +++ b/Project.toml @@ -37,6 +37,7 @@ NativeFileDialog = "e1fe445b-aa65-4df4-81c1-2041507f0fd4" NaturalSort = "c020b1a1-e9b0-503a-9c33-f039bfc54a85" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5" +Polyester = "f401cd3a-86c5-430c-a3c3-63b7194639e4" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" SavitzkyGolay = "c4bf5708-b6a6-4fbe-bcd0-6850ed671584" @@ -54,6 +55,8 @@ CSV = "0.10" CairoMakie = "0.13" ColorSchemes = "3.30" Colors = "0.12" +CodecBase = "0.3" +ContinuousWavelets = "1.1" DataFrames = "1.7" Dates = "1.11" FileIO = "1.17" @@ -61,14 +64,16 @@ GLMakie = "0.11" Genie = "5.31" GenieFramework = "2.8" HistogramThresholding = "0.3" +Interpolations = "0.15" ImageBinarization = "0.3" ImageComponentAnalysis = "0.2" ImageContrastAdjustment = "0.3" -ImageCore = "0.10.5" +ImageCore = "0.10" ImageFiltering = "0.7" ImageMorphology = "0.4" ImageSegmentation = "1.9" Images = "0.26" +JLD2 = "0.6" JSON = "0.21" Libz = "1.0" LinearAlgebra = "1.11" @@ -76,13 +81,15 @@ Loess = "0.6" Mmap = "1.11" NativeFileDialog = "0.2" NaturalSort = "1.0" +Parameters = "0.12" PlotlyBase = "0.8" Printf = "1.11" ProgressMeter = "1.11" SavitzkyGolay = "0.9" Serialization = "1.11" +Setfield = "1.1" Statistics = "1.11" StatsBase = "0.34" StipplePlotly = "0.13" UUIDs = "1.11" -julia = "1.11" +julia = "1.11, 1.12" diff --git a/app.jl b/app.jl index 224aeb4..a99a9b4 100644 --- a/app.jl +++ b/app.jl @@ -352,6 +352,7 @@ end # Preprocessing results @in selected_spectrum_id_for_plot = 1 @in last_plot_type = "single" + @in last_plot_mode = "lines" @in feature_matrix_result::Union{Nothing, Matrix{Float64}} = nothing @in bin_info_result::Union{Nothing, Vector} = nothing @@ -625,6 +626,17 @@ end return end + # --- Close previous dataset if one is open --- + if msi_data !== nothing + println("DEBUG: Closing previously loaded dataset before opening new one: $(basename(full_route))") + close(msi_data) + msi_data = nothing + GC.gc() + if Sys.islinux() + ccall(:malloc_trim, Int32, (Int32,), 0) + end + end + msg = "Opening file: $(basename(picked_route))..." try @@ -679,7 +691,7 @@ end loaded_data = OpenMSIData(local_full_route) is_imzML = loaded_data.source isa ImzMLSource - if isempty(get(existing_entry, "metadata", Dict())) + if existing_entry == nothing msg = "Performing first-time metadata analysis for: $(basename(picked_route))..." precompute_analytics(loaded_data) end @@ -889,6 +901,18 @@ end selected_folder_main = dataset_name msi_data = loaded_data + + # Determine plot mode from loaded data + df = msi_data.spectrum_stats_df + if df !== nothing && "Mode" in names(df) + profile_count = count(==(MSI_src.PROFILE), df.Mode) + total_count = length(df.Mode) + last_plot_mode = profile_count > total_count / 2 ? "lines" : "stem" + println("DEBUG: Auto-detected plot mode: $(last_plot_mode)") + else + last_plot_mode = "lines" # Default + end + log_memory_usage("Full Load", msi_data) eTime = round(time() - sTime, digits=3) @@ -1093,7 +1117,7 @@ end try # --- 1. Initial Checks and Data Loading --- println("DEBUG: Performing initial checks and data loading...") - if msi_data === nothing || isempty(selected_folder_main) + if isempty(selected_folder_main) msg = "No dataset loaded. Please load a file using 'Select an imzMl / mzML file'." warning_msg = true println("DEBUG: $msg") @@ -1111,13 +1135,24 @@ end end target_path = entry["source_path"] - # Ensure msi_data is for the currently selected file - if full_route != target_path - println("DEBUG: Active file path changed. Reloading MSI data: $(basename(target_path))") + # Ensure msi_data is for the currently selected file and load if needed + if msi_data === nothing || full_route != target_path + println("DEBUG: Active file path changed or data not in memory. Reloading MSI data: $(basename(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) + + # Determine plot mode from loaded data + df = msi_data.spectrum_stats_df + if df !== nothing && "Mode" in names(df) + profile_count = count(==(MSI_src.PROFILE), df.Mode) + total_count = length(df.Mode) + last_plot_mode = profile_count > total_count / 2 ? "lines" : "stem" + println("DEBUG: Auto-detected plot mode for pipeline: $(last_plot_mode)") + else + last_plot_mode = "lines" # Default + end else println("DEBUG: Using already loaded MSI data for $(basename(target_path)).") end @@ -1422,7 +1457,7 @@ end # 4. Update Results Display current_pipeline_step = "Updating results..." subset_label = enable_subset_processing ? " (from subset of $(length(current_spectra)) spectra)" : "" - println("DEBUG: Updating results display after pipeline completion for plot type: $(last_plot_type)") + println("DEBUG: Updating results display after pipeline completion for plot type: $(last_plot_type), mode: $(last_plot_mode)") if last_plot_type == "single" display_spectrum_idx = findfirst(s -> s.id == selected_spectrum_id_for_plot, current_spectra) @@ -1430,12 +1465,35 @@ end processed_spectrum = current_spectra[display_spectrum_idx] println("DEBUG: Displaying spectrum $(selected_spectrum_id_for_plot) after processing.") - after_trace = PlotlyBase.scatter( - x=processed_spectrum.mz, - y=processed_spectrum.intensity, - mode="lines", - name="Processed Spectrum" - ) + # Determine plot mode for this specific spectrum + spectrum_mode_for_plot = "lines" # Default to lines + if msi_data.spectrum_stats_df !== nothing && "Mode" in names(msi_data.spectrum_stats_df) + if selected_spectrum_id_for_plot > 0 && selected_spectrum_id_for_plot <= length(msi_data.spectrum_stats_df.Mode) + mode = msi_data.spectrum_stats_df.Mode[selected_spectrum_id_for_plot] + if mode == MSI_src.CENTROID + spectrum_mode_for_plot = "stem" + end + end + end + + mz_down, int_down = downsample_spectrum(processed_spectrum.mz, processed_spectrum.intensity) + + local after_trace + if spectrum_mode_for_plot == "stem" + after_trace = PlotlyBase.stem( + x=mz_down, + y=int_down, + name="Processed Spectrum", + marker=attr(size=1, color="blue", opacity=0) + ) + else # lines + after_trace = PlotlyBase.scatter( + x=mz_down, + y=int_down, + mode="lines", + name="Processed Spectrum" + ) + end traces_after = [after_trace] @@ -1454,9 +1512,25 @@ end plotdata_after = traces_after plotlayout_after = PlotlyBase.Layout( - title="After Preprocessing (Spectrum $(selected_spectrum_id_for_plot))$(subset_label)", - xaxis_title="m/z", - yaxis_title="Intensity", + title=PlotlyBase.attr( + text="After Preprocessing (Spectrum $(selected_spectrum_id_for_plot))$(subset_label)", + font=PlotlyBase.attr( + family="Roboto, Lato, sans-serif", + size=18, + color="black" + ) + ), + hovermode="closest", + xaxis=PlotlyBase.attr( + title="m/z", + 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=0.98, y=0.98, xanchor="right", yanchor="top") ) else @@ -1464,21 +1538,97 @@ end end elseif last_plot_type == "mean" mz, intensity = get_processed_mean_spectrum(current_spectra) - plotdata_after = [PlotlyBase.scatter(x=mz, y=intensity, mode="lines", name="Processed Mean Spectrum")] + mz_down, int_down = downsample_spectrum(mz, intensity) + local trace + if last_plot_mode == "stem" + trace = PlotlyBase.stem( + x=mz_down, + y=int_down, + name="Processed Mean Spectrum", + marker=attr(size=1, color="blue", opacity=0.5), + hoverinfo="x", + hovertemplate="m/z: %{x:.4f}" + ) + else + trace = PlotlyBase.scatter( + x=mz_down, + y=int_down, + mode="lines", + name="Processed Mean Spectrum", + marker=attr(size=1, color="blue", opacity=0.5), + hoverinfo="x", + hovertemplate="m/z: %{x:.4f}" + ) + end + plotdata_after = [trace] plotlayout_after = PlotlyBase.Layout( - title="After Preprocessing (Mean Spectrum)$(subset_label)", - xaxis_title="m/z", - yaxis_title="Average Intensity", - legend=attr(x=0.98, y=0.98, xanchor="right", yanchor="top") + title=PlotlyBase.attr( + text="After Preprocessing (Mean Spectrum)$(subset_label)", + font=PlotlyBase.attr( + family="Roboto, Lato, sans-serif", + size=18, + color="black" + ) + ), + hovermode="closest", + xaxis=PlotlyBase.attr( + title="m/z", + showgrid=true + ), + yaxis=PlotlyBase.attr( + title="Average 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") ) elseif last_plot_type == "sum" mz, intensity = get_processed_sum_spectrum(current_spectra) - plotdata_after = [PlotlyBase.scatter(x=mz, y=intensity, mode="lines", name="Processed Sum Spectrum")] + mz_down, int_down = downsample_spectrum(mz, intensity) + local trace + if last_plot_mode == "stem" + trace = PlotlyBase.stem( + x=mz_down, + y=int_down, + name="Processed Sum Spectrum", + marker=attr(size=1, color="blue", opacity=0.5), + hoverinfo="x", + hovertemplate="m/z: %{x:.4f}" + ) + else + trace = PlotlyBase.scatter( + x=mz_down, + y=int_down, + mode="lines", + name="Processed Sum Spectrum", + marker=attr(size=1, color="blue", opacity=0.5), + hoverinfo="x", + hovertemplate="m/z: %{x:.4f}" + ) + end + plotdata_after = [trace] plotlayout_after = PlotlyBase.Layout( - title="After Preprocessing (Sum Spectrum)$(subset_label)", - xaxis_title="m/z", - yaxis_title="Total Intensity", - legend=attr(x=0.98, y=0.98, xanchor="right", yanchor="top") + title=PlotlyBase.attr( + text="After Preprocessing (Sum Spectrum)$(subset_label)", + font=PlotlyBase.attr( + family="Roboto, Lato, sans-serif", + size=18, + color="black" + ) + ), + hovermode="closest", + xaxis=PlotlyBase.attr( + title="m/z", + showgrid=true + ), + yaxis=PlotlyBase.attr( + title="Total 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") ) end @@ -2670,15 +2820,8 @@ end # This handler will now correctly load the first image from the newly selected folder. @onchange selected_folder_main begin - if msi_data !== nothing - close(msi_data) - end - msi_data = nothing - log_memory_usage("Folder Changed (msi_data cleared)", msi_data) - GC.gc() # Trigger garbage collection - if Sys.islinux() - ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS - end + # The msi_data object lifecycle is managed by the btnSearch handler. + # This handler is now only for updating the UI images when the folder changes. if !isempty(selected_folder_main) folder_path = joinpath("public", selected_folder_main) @@ -2914,7 +3057,7 @@ end warning_msg = true @error "3D plot generation failed" exception=(e, catch_backtrace()) finally - is_processing = true + is_processing = false SpectraEnabled=true GC.gc() # Trigger garbage collection if Sys.islinux() @@ -2971,7 +3114,7 @@ end warning_msg = true @error "3D TrIQ plot generation failed" exception=(e, catch_backtrace()) finally - is_processing = true + is_processing = false SpectraEnabled=true GC.gc() # Trigger garbage collection if Sys.islinux() @@ -3010,7 +3153,7 @@ end msg="Failed to load and process image: $e" warning_msg=true finally - is_processing = true + is_processing = false SpectraEnabled=true GC.gc() if Sys.islinux() @@ -3048,7 +3191,7 @@ end msg="Failed to load and process image: $e" warning_msg=true finally - is_processing = true + is_processing = false SpectraEnabled=true GC.gc() if Sys.islinux() @@ -3065,16 +3208,21 @@ end @onchange Nmass begin if !isempty(xSpectraMz) df = msi_data.spectrum_stats_df - profile_count = 0 - if df !== nothing && hasproperty(df, :Mode) + plot_as_lines = false # Default to stem + if df !== nothing && hasproperty(df, :Mode) && !isempty(df.Mode) profile_count = count(==(MSI_src.PROFILE), df.Mode) + plot_as_lines = profile_count > length(df.Mode) / 2 end - if profile_count > 0 + # Downsample for plotting performance + mz_down, int_down = MSI_src.downsample_spectrum(xSpectraMz, ySpectraMz) + + local traceSpectra + if plot_as_lines # Main spectrum trace traceSpectra = PlotlyBase.scatter( - x=xSpectraMz, - y=ySpectraMz, + x=mz_down, + y=int_down, marker=attr(size=1, color="blue", opacity=0.5), name="Spectrum", hoverinfo="x", @@ -3084,8 +3232,8 @@ end else # Main spectrum trace traceSpectra = PlotlyBase.stem( - x=xSpectraMz, - y=ySpectraMz, + x=mz_down, + y=int_down, marker=attr(size=1, color="blue", opacity=0.5), name="Spectrum", hoverinfo="x", @@ -3252,14 +3400,15 @@ end @mounted watchplots() - @onchange isready @time begin + @onchange isready begin # is_processing = true if isready && !registry_init_done - initialization_message = "Pre-compiling functions at startup..." + sTime=time() + msg = "Pre-compiling functions at startup..." warmup_init() - initialization_message = "Pre-compilation finished." + msg = "Pre-compilation finished." try - initialization_message = "Synchronizing registry with filesystem on backend init..." + msg = "Synchronizing registry with filesystem on backend init..." reg_path = abspath(joinpath(@__DIR__, "public", "registry.json")) registry = isfile(reg_path) ? load_registry(reg_path) : Dict{String, Any}() @@ -3288,7 +3437,7 @@ end end if !isempty(new_folders) || !isempty(removed_folders) - initialization_message = "Registry changed, saving..." + msg = "Registry changed, saving..." save_registry(reg_path, registry) end @@ -3309,8 +3458,10 @@ end is_initializing = false # Hide loading screen when initialization is complete end end + fTime=time() + eTime=round(fTime-sTime,digits=3) is_initializing = false # Hide loading screen when initialization is complete (current code is hidden due to incompatibility) - initialization_message = "Done." + msg = "The app took $(eTime) seconds to get ready." log_memory_usage("App Ready", msi_data) end # is_processing = false diff --git a/app.jl.html b/app.jl.html index 38fcd8a..736c96c 100644 --- a/app.jl.html +++ b/app.jl.html @@ -708,7 +708,7 @@
Image visualizer
+ class="q-ma-sm" style="min-width: 200px;" v-on:focus="refetch_folders = true"> @@ -731,7 +731,7 @@
TrIQ visualizer
+ class="q-ma-sm" style="min-width: 200px;" v-on:focus="refetch_folders = true"> @@ -752,7 +752,7 @@
+ class="q-ma-sm" style="min-width: 200px;" v-on:focus="refetch_folders = true">