module App # ==Packages == using GenieFramework # Set up Genie development environment. using Pkg using Libz using PlotlyBase using CairoMakie using Colors # using julia_mzML_imzML using MSI_src # Import the new MSIData library using Statistics using NaturalSort using Images using LinearAlgebra using NativeFileDialog # Opens the file explorer depending on the OS using StipplePlotly using Base.Filesystem: mv # To rename files in the system using Printf # Required for @sprintf macro in colorbar generation # Bring MSIData into App module's scope using .MSI_src: MSIData, OpenMSIData, GetSpectrum, IterateSpectra, ImzMLSource, _iterate_spectra_fast, MzMLSource, find_mass, ViridisPalette, get_mz_slice, quantize_intensity, save_bitmap, median_filter, save_bitmap, downsample_spectrum, TrIQ include("./julia_imzML_visual.jl") @genietools # == Reactive code == # Reactive code to make the UI interactive @app begin # == Reactive variables == # reactive variables exist in both the Julia backend and the browser with two-way synchronization # @out variables can only be modified by the backend # @in variables can be modified by both the backend and the browser # variables must be initialized with constant values, or variables defined outside of the @app block ## Interface non Variables @out btnStartDisable=true @out btnPlotDisable=false @out btnSpectraDisable=true # Loading animations @in progress=false @in progressPlot=false @in progressSpectraPlot=false # Text field validations @in triqEnabled=false @in SpectraEnabled=false @in MFilterEnabled=false # Dialogs @in warning_msg=false @in CompareDialog=false ## Interface Variables @in file_route="" @in file_name="" @in Nmass=0.0 @in Tol=0.1 @in triqProb=0.98 @in colorLevel=20 ## Interface Buttons @in btnSearch=false # To search for files in your device @in mainProcess=false # To generate images @in compareBtn=false # To open dialog @in createMeanPlot=false # To generate mean spectrum plot @in createXYPlot=false # To generate an spectrum plot according to the xy values inputed @in createSumPlot=false # To generate a sum of all the spectrum plots @in image3dPlot=false # To generate 3d plot based on current image @in triq3dPlot=false # To generate 3d plot based on current triq image @in imageCPlot=false # To generate contour plots of current image @in triqCPlot=false # To generate contour plots of current triq image # Image change buttons @in imgPlus=false @in imgMinus=false @in imgPlusT=false @in imgMinusT=false # Image change comparative buttons @in imgPlusComp=false @in imgMinusComp=false @in imgPlusTComp=false @in imgMinusTComp=false ## Tabulation variables @out tabIDs=["tab0","tab1","tab2","tab3","tab4"] @out tabLabels=["Image", "TrIQ", "Spectrum Plot", "Topography Plot","Surface Plot"] @in selectedTab="tab0" @out CompTabIDs=["tab0","tab1","tab2","tab3","tab4"] @out CompTabLabels=["Image", "TrIQ", "Spectrum Plot", "Topography Plot","Surface Plot"] @in CompSelectedTab="tab0" # Interface Images @out imgInt="/.bmp" # image Interface @out imgIntT="/.bmp" # image Interface TrIQ @out colorbar="/.png" @out colorbarT="/.png" # Interface controlling for the comparative view @out imgIntComp="/.bmp" # image Interface @out imgIntTComp="/.bmp" # image Interface TrIQ @out colorbarComp="/.png" @out colorbarTComp="/.png" @out imgWidth=0 @out imgHeight=0 # Optical Image Overlay & Transparency @in imgTrans=1.0 @in progressOptical=false @out btnOpticalDisable=true @in btnOptical=false @in btnOpticalT=false @in opticalOverTriq=false @out imgRoute="" # Messages to interface variables @out msg="" @out msgimg="" @out msgtriq="" # Reiteration of the messages under the image to know which spectra is being visualized @out msgimgComp="" @out msgtriqComp="" # Centralized MSIData object @out msi_data::Union{MSIData, Nothing} = nothing # Saves the route where imzML and mzML files are located @out full_route="" # For the creation of images with a more specific mass charge @out text_nmass="" # For image search image lists we apply a filter that searches specific type of images into our public folder, then we sort it in a "numerical" order @in msi_bmp=sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) @in col_msi_png=sort(filter(filename -> startswith(filename, "colorbar_MSI_") && endswith(filename, ".png"), readdir("public")),lt=natural) @in triq_bmp=sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) @in col_triq_png=sort(filter(filename -> startswith(filename, "colorbar_TrIQ_") && endswith(filename, ".png"), readdir("public")),lt=natural) # Set current image for the list to display @out current_msi="" @out current_col_msi="" @out current_triq="" @out current_col_triq="" # We reiterate the process to display in the comparative view @out current_msiComp="" @out current_col_msiComp="" @out current_triqComp="" @out current_col_triqComp="" ## Time measurement variables @out sTime=time() @out fTime=time() @out eTime=time() ## Plots # Local image to plot layoutImg=PlotlyBase.Layout( xaxis=PlotlyBase.attr( visible=false, scaleanchor="y", range=[0, 0] ), yaxis=PlotlyBase.attr( visible=false, range=[0, 0] ), margin=attr(l=0,r=0,t=0,b=0,pad=0) ) traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) @out plotdataImg=[traceImg] @out plotlayoutImg=layoutImg # For the image in the comparative view @out plotdataImgComp=[traceImg] @out plotlayoutImgComp=layoutImg # For triq image @out plotdataImgT=[traceImg] @out plotlayoutImgT=layoutImg # For the triq image in the comparative view @out plotdataImgTComp=[traceImg] @out plotlayoutImgTComp=layoutImg # Interface Plot Spectrum layoutSpectra=PlotlyBase.Layout( title="Spectrum plot", 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) ) # Dummy 2D scatter plot traceSpectra=PlotlyBase.stem(x=Vector{Float64}(), y=Vector{Float64}(),marker=attr(size=1, color="blue", opacity=0.1)) # Create conection to frontend @out plotdata=[traceSpectra] @out plotlayout=layoutSpectra @in xCoord=0 @in yCoord=0 @out xSpectraMz = Vector{Float64}() @out ySpectraMz = Vector{Float64}() # Interactive plot reactions @in data_click=Dict{String,Any}() #@in data_selected=Dict{String,Any}() # Selected is for areas, this can work for the masks # # Interface Plot Surface layoutContour=PlotlyBase.Layout( title="2D Topographic map", xaxis=PlotlyBase.attr( visible=false, scaleanchor="y" ), yaxis=PlotlyBase.attr( visible=false ), margin=attr(l=0,r=0,t=100,b=0,pad=0) ) # Dummy 2D surface plot traceContour=PlotlyBase.scatter(x=Vector{Float64}(), y=Vector{Float64}(), mode="lines") # Create conection to frontend @out plotdataC=[traceContour] @out plotlayoutC=layoutContour # Interface Plot 3d # Define the layout for the 3D plot layout3D=PlotlyBase.Layout( title="3D Surface plot", scene=attr( xaxis_title="X", yaxis_title="Y", zaxis_title="Z", xaxis_nticks=20, yaxis_nticks=20, zaxis_nticks=4, camera=attr(eye=attr(x=0, y=-1, z=0.5)), aspectratio=attr(x=1, y=1, z=0.2) ), margin=attr(l=0,r=0,t=120,b=0,pad=0) ) # Dummy 3D surface plot x=1:10 y=1:10 z=[sin(i * j / 10) for i in x, j in y] trace3D=PlotlyBase.surface(x=Vector{Float64}(), y=Vector{Float64}(), z=Matrix{Float64}(undef, 0, 0), contours_z=attr( show=true, usecolormap=true, highlightcolor="limegreen", project_z=true ), colorscale="Viridis") # Create conection to frontend @out plotdata3d=[trace3D] @out plotlayout3d=layout3D # == Reactive handlers == # Reactive handlers watch a variable and execute a block of code when its value changes # The onbutton handler will set the variable to false after the block is executed @onbutton btnSearch @time begin # This part is synchronous and blocking, which is unavoidable picked_route = pick_file(; filterlist="imzML,imzml,mzML,mzml") if isempty(picked_route) msg = "No file selected." warning_msg = true return end # UI updates immediately progress = true msg = "Opening file: $(basename(picked_route))..." @async begin try # --- Normalize file extension and path --- if endswith(picked_route, "imzml") full_route = replace(picked_route, r"\.imzml$"i => ".imzML") mv(picked_route, full_route, force=true) elseif endswith(picked_route, "mzml") full_route = replace(picked_route, r"\.mzml$"i => ".mzML") mv(picked_route, full_route, force=true) else full_route = picked_route end # --- Load data using the new MSIData library --- sTime = time() msi_data = OpenMSIData(full_route) w, h = msi_data.image_dims imgWidth, imgHeight = w > 0 ? (w, h) : (500, 500) fTime = time() eTime = round(fTime - sTime, digits=3) # msg = "File loaded in $(eTime) seconds. Calculating total spectrum..." msg = "File loaded in $(eTime) seconds." # Enable UI controls btnStartDisable = !(msi_data.source isa ImzMLSource) btnPlotDisable = false btnSpectraDisable = false SpectraEnabled = true """ # --- Automatically generate and display the sum spectrum plot --- progressSpectraPlot = true sTime = time() plotdata, plotlayout, xSpectraMz, ySpectraMz = sumSpectrumPlot(msi_data) selectedTab = "tab2" fTime = time() eTime = round(fTime - sTime, digits=3) msg = "Total spectrum plot loaded in $(eTime) seconds." """ catch e msi_data = nothing msg = "Error loading file: $e" warning_msg = true btnStartDisable = true btnSpectraDisable = true SpectraEnabled = false @error "File loading failed" exception=(e, catch_backtrace()) finally # This block will always run at the end of the async task GC.gc() if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) end progress = false progressSpectraPlot = false end end end @onbutton mainProcess @time begin # UI updates immediately progress = true btnStartDisable = true btnPlotDisable = true btnSpectraDisable = true @async begin try text_nmass = replace(string(Nmass), "." => "_") sTime = time() if msi_data === nothing || !(msi_data.source isa ImzMLSource) msg = "No .imzML file loaded or selected file is not an .imzML. Please select a valid file." warning_msg = true elseif Nmass > 0 && Tol > 0 && Tol <= 1 && colorLevel > 1 && colorLevel < 257 msg = "Creating image for m/z=$(Nmass) Tol=$(Tol). Please be patient." try # Use the new get_mz_slice with the centralized MSIData object slice = get_mz_slice(msi_data, Nmass, Tol) fig = CairoMakie.Figure(size=(150, 250)) # Container timestamp = string(time_ns()) if triqEnabled # If we have TrIQ if triqProb < 0.8 || triqProb > 1 msg = "Incorrect TrIQ values, please adjust accordingly and try again." warning_msg = true else sliceTriq = TrIQ(slice, colorLevel, triqProb) if MFilterEnabled sliceTriq = round.(UInt8, median_filter(sliceTriq)) end sliceTriq = reverse(sliceTriq, dims=2) save_bitmap(joinpath("public", "TrIQ_$(text_nmass).bmp"), sliceTriq, ViridisPalette) imgIntT = "/TrIQ_$(text_nmass).bmp?t=$(timestamp)" plotdataImgT, plotlayoutImgT, imgWidth, imgHeight = loadImgPlot(imgIntT) current_triq = "TrIQ_$(text_nmass).bmp" msgtriq = "TrIQ image with the Nmass of $(replace(text_nmass, "_" => "."))" colorbar_path = joinpath("public", "colorbar_TrIQ_$(text_nmass).png") generate_colorbar_image(slice, colorLevel, colorbar_path, use_triq=true, triq_prob=triqProb) colorbarT = "/colorbar_TrIQ_$(text_nmass).png?t=$(timestamp)" current_col_triq = "colorbar_TrIQ_$(text_nmass).png" triq_bmp = sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")), lt=natural) col_triq_png = sort(filter(filename -> startswith(filename, "colorbar_TrIQ_") && endswith(filename, ".png"), readdir("public")), lt=natural) fTime = time() eTime = round(fTime - sTime, digits=3) msg = "The TrIQ image has been created in $(eTime) seconds successfully inside the 'public' folder of the app" selectedTab = "tab1" end else # If we don't use TrIQ sliceQuant = quantize_intensity(slice, colorLevel) if MFilterEnabled sliceQuant = round.(UInt8, median_filter(sliceQuant)) end sliceQuant = reverse(sliceQuant, dims=2) save_bitmap(joinpath("public", "MSI_$(text_nmass).bmp"), sliceQuant, ViridisPalette) imgInt = "/MSI_$(text_nmass).bmp?t=$(timestamp)" plotdataImg, plotlayoutImg, imgWidth, imgHeight = loadImgPlot(imgInt) current_msi = "MSI_$(text_nmass).bmp" msgimg = "Image with the Nmass of $(replace(text_nmass, "_" => "."))" colorbar_path = joinpath("public", "colorbar_MSI_$(text_nmass).png") generate_colorbar_image(slice, colorLevel, colorbar_path) colorbar = "/colorbar_MSI_$(text_nmass).png?t=$(timestamp)" current_col_msi = "colorbar_MSI_$(text_nmass).png" msi_bmp = sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")), lt=natural) col_msi_png = sort(filter(filename -> startswith(filename, "colorbar_MSI_") && endswith(filename, ".png"), readdir("public")), lt=natural) selectedTab = "tab0" fTime = time() eTime = round(fTime - sTime, digits=3) msg = "The image has been created in $(eTime) seconds successfully inside the 'public' folder of the app" end catch e msg = "There was an error creating the image: $e" warning_msg = true @error "Image creation failed" exception=(e, catch_backtrace()) end else msg = "Invalid parameters. Nmass, Tol, or colorLevel are incorrect." warning_msg = true @error msg end finally # This block will always run at the end of the async task GC.gc() if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) end btnStartDisable = false btnPlotDisable = false btnOpticalDisable = false progress = false btnSpectraDisable = false SpectraEnabled = true end end end @onbutton createMeanPlot begin if msi_data === nothing msg = "No data loaded. Please select a file first." warning_msg = true return end # UI updates immediately progressSpectraPlot = true btnPlotDisable = true btnStartDisable = true msg = "Loading plot..." @async begin try sTime = time() plotdata, plotlayout, xSpectraMz, ySpectraMz = meanSpectrumPlot(msi_data) selectedTab = "tab2" fTime = time() eTime = round(fTime - sTime, digits=3) msg = "Plot loaded in $(eTime) seconds" catch e msg = "Could not generate mean spectrum plot: $e" warning_msg = true @error "Mean spectrum plotting failed" exception=(e, catch_backtrace()) finally # This runs after the async task is finished progressSpectraPlot = false btnPlotDisable = false btnSpectraDisable = false if msi_data !== nothing && msi_data.source isa ImzMLSource btnStartDisable = false end end end end @onbutton createSumPlot begin if msi_data === nothing msg = "No data loaded. Please select a file first." warning_msg = true return end # UI updates immediately progressSpectraPlot = true btnPlotDisable = true btnStartDisable = true msg = "Loading total spectrum plot..." @async begin try sTime = time() plotdata, plotlayout, xSpectraMz, ySpectraMz = sumSpectrumPlot(msi_data) selectedTab = "tab2" fTime = time() eTime = round(fTime - sTime, digits=3) msg = "Total plot loaded in $(eTime) seconds" catch e msg = "Could not generate total spectrum plot: $e" warning_msg = true @error "Total spectrum plotting failed" exception=(e, catch_backtrace()) finally # This runs after the async task is finished progressSpectraPlot = false btnPlotDisable = false btnSpectraDisable = false if msi_data !== nothing && msi_data.source isa ImzMLSource btnStartDisable = false end end end end @onbutton createXYPlot begin if msi_data === nothing msg = "No data loaded. Please select a file first." warning_msg = true return end # UI updates immediately progressSpectraPlot = true btnStartDisable = true btnPlotDisable = true btnSpectraDisable = true msg = "Loading plot..." @async begin try sTime = time() # The UI uses negative Y values, so we adjust before calling the plot function y = yCoord < 0 ? abs(yCoord) : yCoord plotdata, plotlayout, xSpectraMz, ySpectraMz = xySpectrumPlot(msi_data, xCoord, y, imgWidth, imgHeight) # Update UI coordinates xCoord = plotlayout.title == "Spectrum #$(xCoord)" ? xCoord : clamp(xCoord, 1, imgWidth) yCoord = plotlayout.title == "Spectrum #$(xCoord)" ? 0 : -clamp(y, 1, imgHeight) selectedTab = "tab2" fTime = time() eTime = round(fTime - sTime, digits=3) msg = "Plot loaded in $(eTime) seconds" catch e msg = "Could not retrieve spectrum: $e" warning_msg = true @error "Spectrum plotting failed" exception=(e, catch_backtrace()) finally # This runs after the async task is finished progressSpectraPlot = false btnPlotDisable = false btnSpectraDisable = false if msi_data !== nothing && msi_data.source isa ImzMLSource btnStartDisable = false end end end end # Image loaders based on the position of the current image (increment and decrement for both normal and filter) # And a pre-generated list from all image files from /public folder @onbutton imgMinus begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images listed in the public folder msi_bmp=sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_msi_png=sort(filter(filename -> startswith(filename, "colorbar_MSI_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=decrement_image(current_msi, msi_bmp) new_col_msi=decrement_image(current_col_msi, col_msi_png) if new_msi!=nothing || new_col_msi!=nothing current_msi=new_msi current_col_msi=new_col_msi imgInt="/$(current_msi)?t=$(timestamp)" colorbar="/$(current_col_msi)?t=$(timestamp)" text_nmass=replace(current_msi, "MSI_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgimg="Image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImg=[traceImg] msgimg="" end end @onbutton imgPlus begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images listed in the public folder msi_bmp=sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_msi_png=sort(filter(filename -> startswith(filename, "colorbar_MSI_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=increment_image(current_msi, msi_bmp) new_col_msi=increment_image(current_col_msi, col_msi_png) if new_msi!=nothing || new_col_msi!=nothing current_msi=new_msi current_col_msi=new_col_msi imgInt="/$(current_msi)?t=$(timestamp)" colorbar="/$(current_col_msi)?t=$(timestamp)" text_nmass=replace(current_msi, "MSI_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgimg="Image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImg=[traceImg] msgimg="" end end @onbutton imgMinusT begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images with TrIQ filter listed in the public folder triq_bmp=sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_triq_png=sort(filter(filename -> startswith(filename, "colorbar_TrIQ_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=decrement_image(current_triq, triq_bmp) new_col_msi=decrement_image(current_col_triq, col_triq_png) if new_msi!=nothing || new_col_msi!=nothing current_triq=new_msi current_col_triq=new_col_msi imgIntT="/$(current_triq)?t=$(timestamp)" colorbarT="/$(current_col_triq)?t=$(timestamp)" text_nmass=replace(current_triq, "TrIQ_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgtriq="TrIQ image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImgT=[traceImg] msgtriq="" end end @onbutton imgPlusT begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images with TrIQ filter listed in the public folder triq_bmp=sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_triq_png=sort(filter(filename -> startswith(filename, "colorbar_TrIQ_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=increment_image(current_triq, triq_bmp) new_col_msi=increment_image(current_col_triq, col_triq_png) if new_msi!=nothing || new_col_msi!=nothing current_triq=new_msi current_col_triq=new_col_msi imgIntT="/$(current_triq)?t=$(timestamp)" colorbarT="/$(current_col_triq)?t=$(timestamp)" text_nmass=replace(current_triq, "TrIQ_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgtriq="TrIQ image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImgT=[traceImg] msgtriq="" end end # Image loaders for the comparative view @onbutton imgMinusComp begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images listed in the public folder msi_bmp=sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_msi_png=sort(filter(filename -> startswith(filename, "colorbar_MSI_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=decrement_image(current_msiComp, msi_bmp) new_col_msi=decrement_image(current_col_msiComp, col_msi_png) if new_msi!=nothing || new_col_msi!=nothing current_msiComp=new_msi current_col_msiComp=new_col_msi imgIntComp="/$(current_msiComp)?t=$(timestamp)" colorbarComp="/$(current_col_msiComp)?t=$(timestamp)" text_nmass=replace(current_msiComp, "MSI_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgimgComp="Image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImgComp, plotlayoutImgComp, _, _=loadImgPlot(imgIntComp) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImgComp=[traceImg] msgimgComp="" end end @onbutton imgPlusComp begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images listed in the public folder msi_bmp=sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_msi_png=sort(filter(filename -> startswith(filename, "colorbar_MSI_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=increment_image(current_msiComp, msi_bmp) new_col_msi=increment_image(current_col_msiComp, col_msi_png) if new_msi!=nothing || new_col_msi!=nothing current_msiComp=new_msi current_col_msiComp=new_col_msi imgIntComp="/$(current_msiComp)?t=$(timestamp)" colorbarComp="/$(current_col_msiComp)?t=$(timestamp)" text_nmass=replace(current_msiComp, "MSI_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgimgComp="Image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImgComp, plotlayoutImgComp, _, _=loadImgPlot(imgIntComp) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImgComp=[traceImg] msgimgComp="" end end @onbutton imgMinusTComp begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images with TrIQ filter listed in the public folder triq_bmp=sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_triq_png=sort(filter(filename -> startswith(filename, "colorbar_TrIQ_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=decrement_image(current_triqComp, triq_bmp) new_col_msi=decrement_image(current_col_triqComp, col_triq_png) if new_msi!=nothing || new_col_msi!=nothing current_triqComp=new_msi current_col_triqComp=new_col_msi imgIntTComp="/$(current_triqComp)?t=$(timestamp)" colorbarTComp="/$(current_col_triqComp)?t=$(timestamp)" text_nmass=replace(current_triqComp, "TrIQ_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgtriqComp="TrIQ image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImgTComp, plotlayoutImgTComp, _, _=loadImgPlot(imgIntTComp) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImgTComp=[traceImg] msgtriqComp="" end end @onbutton imgPlusTComp begin # Append a query string to force the image to refresh timestamp=string(time_ns()) # Update the array of images with TrIQ filter listed in the public folder triq_bmp=sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")),lt=natural) col_triq_png=sort(filter(filename -> startswith(filename, "colorbar_TrIQ_") && endswith(filename, ".png"), readdir("public")),lt=natural) new_msi=increment_image(current_triqComp, triq_bmp) new_col_msi=increment_image(current_col_triqComp, col_triq_png) if new_msi!=nothing || new_col_msi!=nothing current_triqComp=new_msi current_col_triqComp=new_col_msi imgIntTComp="/$(current_triqComp)?t=$(timestamp)" colorbarTComp="/$(current_col_triqComp)?t=$(timestamp)" text_nmass=replace(current_triqComp, "TrIQ_" => "") text_nmass=replace(text_nmass, ".bmp" => "") msgtriqComp="TrIQ image with the Nmass of $(replace(text_nmass, "_" => "."))" # Process the image in the function plotdataImgTComp, plotlayoutImgTComp, _, _=loadImgPlot(imgIntTComp) btnOpticalDisable=false else traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}()) plotdataImgTComp=[traceImg] msgtriqComp="" end end # 3d plot @onbutton image3dPlot begin msg="Image 3D plot selected" cleaned_imgInt=replace(imgInt, r"\?.*" => "") cleaned_imgInt=lstrip(cleaned_imgInt, '/') var=joinpath( "./public", cleaned_imgInt ) if !isfile(var) msg="Image could not be 3d plotted" warning_msg=true return end progressPlot=true btnPlotDisable=true btnStartDisable=true btnSpectraDisable=true @async begin try sTime=time() plotdata3d, plotlayout3d=loadSurfacePlot(imgInt) GC.gc() # Trigger garbage collection if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure julia returns the freed memory to OS end selectedTab="tab4" fTime=time() eTime=round(fTime-sTime,digits=3) msg="Plot loaded in $(eTime) seconds" catch e msg="Failed to load and process image: $e" warning_msg=true finally progressPlot=false btnPlotDisable=false btnStartDisable=false if msi_data !== nothing # We enable coord search and spectra plot creation btnSpectraDisable=false SpectraEnabled=true end end end end # 3d plot for TrIQ @onbutton triq3dPlot begin msg="TrIQ 3D plot selected" cleaned_imgIntT=replace(imgIntT, r"\?.*" => "") cleaned_imgIntT=lstrip(cleaned_imgIntT, '/') var=joinpath( "./public", cleaned_imgIntT ) if !isfile(var) msg="Image could not be 3d plotted" warning_msg=true return end progressPlot=true btnPlotDisable=true btnStartDisable=true btnSpectraDisable=true @async begin try sTime=time() plotdata3d, plotlayout3d=loadSurfacePlot(imgIntT) GC.gc() # Trigger garbage collection if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure julia returns the freed memory to OS end selectedTab="tab4" fTime=time() eTime=round(fTime-sTime,digits=3) msg="Plot loaded in $(eTime) seconds" catch e msg="Failed to load and process image: $e" warning_msg=true finally progressPlot=false btnPlotDisable=false btnStartDisable=false if msi_data !== nothing # We enable coord search and spectra plot creation btnSpectraDisable=false SpectraEnabled=true end end end end # Contour 2d plot @onbutton imageCPlot begin msg="Image 2D plot selected" cleaned_imgInt=replace(imgInt, r"\?.*" => "") cleaned_imgInt=lstrip(cleaned_imgInt, '/') var=joinpath("./public", cleaned_imgInt) if !isfile(var) msg="Image could not be 2D plotted" warning_msg=true return end progressPlot=true btnPlotDisable=true btnStartDisable=true btnSpectraDisable=true @async begin try sTime=time() plotdataC,plotlayoutC=loadContourPlot(imgInt) GC.gc() # Trigger garbage collection if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS end selectedTab="tab3" fTime=time() eTime=round(fTime-sTime,digits=3) msg="Plot loaded in $(eTime) seconds" catch e msg="Failed to load and process image: $e" warning_msg=true finally progressPlot=false btnPlotDisable=false btnStartDisable=false if msi_data !== nothing # We enable coord search and spectra plot creation btnSpectraDisable=false SpectraEnabled=true end end end end # Contour 2d plot for TrIQ @onbutton triqCPlot begin msg="Image 2D plot selected" cleaned_imgIntT=replace(imgIntT, r"\?.*" => "") cleaned_imgIntT=lstrip(cleaned_imgIntT, '/') var=joinpath("./public", cleaned_imgIntT) if !isfile(var) msg="Image could not be 2D plotted" warning_msg=true return end progressPlot=true btnPlotDisable=true btnStartDisable=true btnSpectraDisable=true @async begin try sTime=time() plotdataC,plotlayoutC=loadContourPlot(imgIntT) GC.gc() # Trigger garbage collection if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure Julia returns the freed memory to OS end selectedTab="tab3" fTime=time() eTime=round(fTime-sTime,digits=3) msg="Plot loaded in $(eTime) seconds" catch e msg="Failed to load and process image: $e" warning_msg=true finally progressPlot=false btnPlotDisable=false btnStartDisable=false if msi_data !== nothing # We enable coord search and spectra plot creation btnSpectraDisable=false SpectraEnabled=true end end end end @onbutton compareBtn begin CompareDialog=true end # To include a visualization in the spectrum plot indicating where is the selected mass @onchange Nmass begin if !isempty(xSpectraMz) # Use a stem plot for the main spectrum for consistency traceSpectra = PlotlyBase.stem(x=xSpectraMz, y=ySpectraMz, marker=attr(size=1, color="blue", opacity=0.5), name="Spectrum", hoverinfo="x", hovertemplate="m/z: %{x:.4f}", showlegend=false) # Keep this as a scatter plot to draw the vertical line trace2 = PlotlyBase.scatter(x=[Nmass, Nmass], y=[0, maximum(ySpectraMz)], mode="lines", line=attr(color="red", width=0.5), name="m/z selected", showlegend=false) plotdata = [traceSpectra, trace2] end end # Event detection for clicking on the images @onchange data_click begin if selectedTab == "tab1" || selectedTab == "tab0" # This is for the image heatmaps cursor_data = data_click["cursor"] x = Int32(round(cursor_data["x"])) y = Int32(round(cursor_data["y"])) # y is negative in the UI # Update the reactive coordinates, which will trigger the crosshair update xCoord = clamp(x, 1, imgWidth) yCoord = clamp(y, -imgHeight, -1) end end @onchange xCoord, yCoord begin if selectedTab == "tab1" plotdataImgT = filter(trace -> !(get(trace, :name, "") in ["Line X", "Line Y"]), plotdataImgT) trace1, trace2 = crossLinesPlot(xCoord, yCoord, imgWidth, -imgHeight) plotdataImgT = append!(plotdataImgT, [trace1, trace2]) elseif selectedTab == "tab0" plotdataImg = filter(trace -> !(get(trace, :name, "") in ["Line X", "Line Y", "Optical"]), plotdataImg) trace1, trace2 = crossLinesPlot(xCoord, yCoord, imgWidth, -imgHeight) plotdataImg = append!(plotdataImg, [trace1, trace2]) end end @onbutton btnOptical begin imgRoute=pick_file(; filterlist="png,bmp,jpg,jpeg") if imgRoute=="" msg="No optical image selected" else selectedTab="tab0" plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT) img=load(imgRoute) save("./public/css/imgOver.png",img) plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt,"/css/imgOver.png",imgTrans) end end @onbutton btnOpticalT begin imgRoute=pick_file(; filterlist="png,bmp,jpg,jpeg") if imgRoute=="" msg="No optical image selected" else selectedTab="tab1" plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt) img=load(imgRoute) save("./public/css/imgOver.png",img) plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT,"/css/imgOver.png",imgTrans) opticalOverTriq=true end end @onchange imgTrans begin if !opticalOverTriq && imgRoute!="" plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt,"/css/imgOver.png",imgTrans) elseif opticalOverTriq && imgRoute!="" plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT,"/css/imgOver.png",imgTrans) end end @onchange opticalOverTriq begin if !opticalOverTriq && imgRoute!="" plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt,"/css/imgOver.png",imgTrans) plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT) selectedTab="tab0" elseif opticalOverTriq && imgRoute!="" plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt) plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT,"/css/imgOver.png",imgTrans) selectedTab="tab1" end end @mounted watchplots() GC.gc() # Trigger garbage collection if Sys.islinux() ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure julia returns the freed memory to OS end end # == Pages == # Register a new route and the page that will be loaded on access @page("/", "app.jl.html") end