1074 lines
43 KiB
Julia
1074 lines
43 KiB
Julia
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="<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)
|
|
)
|
|
# 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
|
|
#<plotly id="plotStyle" :data="plotdata" :layout="plotlayout" @click="data_selected" class="q-pa-none q-ma-none sync_data"></plotly>
|
|
|
|
# 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="<b>m/z</b>: %{x:.4f}<extra></extra>", 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="<i>m/z</i> 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
|