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 Statistics
using NaturalSort
using Images
using LinearAlgebra
using NativeFileDialog # Opens the file explorer depending on the OS
using StipplePlotly
include("./julia_imzML_visual.jl")
@genietools
# == Code import ==
# add your data analysis code here or in the lib folder. Code in lib/ will be
# automatically loaded
# == Search functions ==
function increment_image(current_image, image_list)
if isempty(image_list)
return nothing
end
current_index=findfirst(isequal(current_image), image_list)
if current_index==nothing || current_index==length(image_list) || current_image ===""
return image_list[length(image_list)] # Return the current image if it's the last one or not found
else
return image_list[current_index + 1] # Move to the next image
end
end
function decrement_image(current_image, image_list)
if isempty(image_list)
return nothing
end
current_index=findfirst(isequal(current_image), image_list)
if current_index==nothing || current_index==1 || current_image===""
return image_list[1] # Return the current image if it's the first one or not found
else
return image_list[current_index - 1] # Move to the previous image
end
end
## Plot Image functions
# loadImgPlot recieves the local directory of the image as a string,
# returns the layout and data for the heatmap plotly plot
# this function loads the image into a plot
function loadImgPlot(interfaceImg::String)
# Load the image
cleaned_img=replace(interfaceImg, r"\?.*" => "")
cleaned_img=lstrip(cleaned_img, '/')
var=joinpath("./public", cleaned_img)
img=load(var)
#println("type of img: $(typeof(img))")
# Convert to grayscale
img_gray=Gray.(img)
img_array=Array(img_gray)
#println(typeof(img_array))
elevation=Float32.(Array(img_array)) ./ 255.0
#println("type of elevation: $(typeof(elevation))")
# Get the X, Y coordinates of the image
height, width=size(img_array)
#println("height: $(height), width: $(width)")
X=collect(1:width)
Y=collect(1:height)
# Create the layout
layout=PlotlyBase.Layout(
xaxis=PlotlyBase.attr(
visible=false,
scaleanchor="y"
),
yaxis=PlotlyBase.attr(
visible=false
)
)
# Create the trace for the image
trace=PlotlyBase.heatmap(
z=elevation,
x=X,
y=-Y,
name="",
showlegend=false,
colorscale="Viridis",
showscale=false,
colorbar=attr(
title=attr(
text="Intensity",
font=attr(
size=14,
color="black"
),
side="right"
),
ticks="outside",
ticklen=2,
tickwidth=0.5,
nticks=5,
tickformat=".2g"
)
)
plotdata=[trace]
plotlayout=layout
return plotdata, plotlayout, width, height
end
# loadContourPlot recieves the local directory of the image as a string,
# returns the layout and data for the contour plotly plot
# this function loads the image and applies a gaussian filter
# to smoothen it and loads it into a plot
function loadContourPlot(interfaceImg::String)
# Load the image
cleaned_img=replace(interfaceImg, r"\?.*" => "")
cleaned_img=lstrip(cleaned_img, '/')
var=joinpath("./public", cleaned_img)
img=load(var)
img_gray=Gray.(img)
img_array=Array(img_gray)
elevation=Float32.(Array(img_gray)) ./ 255.0 # Normalize between 0 and 1
# Smooth the image
sigma=3.0
kernel=Kernel.gaussian(sigma)
elevation_smoothed=imfilter(elevation, kernel)
# Create the X, Y meshgrid coordinates
x=1:size(elevation_smoothed, 2)
y=1:size(elevation_smoothed, 1)
X=repeat(reshape(x, 1, length(x)), length(y), 1)
Y=repeat(reshape(y, length(y), 1), 1, length(x))
layout=PlotlyBase.Layout(
title="2D Topographic Map",
xaxis=PlotlyBase.attr(
title="X",
scaleanchor="y"
),
yaxis=PlotlyBase.attr(
title="Y"
),
)
trace=PlotlyBase.contour(
z=elevation_smoothed,
x=X[1, :], # Use the first row
y=-Y[:, 1], # Use the first column
contours_coloring="Viridis",
colorscale="Viridis",
colorbar=attr(
tickformat=".2g"
)
)
plotdata=[trace]
plotlayout=layout
return plotdata, plotlayout
end
# loadSurfacePlot recieves the local directory of the image as a string,
# returns the layout and data for the surface plotly plot
# this function loads the image and applies a gaussian filter
# to smoothen it and loads it into a 3D plot
function loadSurfacePlot(interfaceImg::String)
# Load the image
cleaned_img=replace(interfaceImg, r"\?.*" => "")
cleaned_img=lstrip(cleaned_img, '/')
var=joinpath("./public", cleaned_img)
img=load(var)
#println("Image type:", typeof(img))
img_gray=Gray.(img) # Convert to grayscale
#println("Grayscale image type:", typeof(img_gray))
img_array=Array(img_gray)
elevation=Float32.(Array(img_gray)) ./ 255.0 # Normalize between 0 and 1
#println("Elevation size:", size(elevation))
# Smooth the image
sigma=3.0
kernel=Kernel.gaussian(sigma)
#println(size(kernel))
elevation_smoothed=imfilter(elevation, kernel)
#println("Smoothed elevation size:", size(elevation_smoothed))
# Transpose the elevation_smoothed array
# Create the X, Y meshgrid coordinates
x=1:size(elevation_smoothed, 2)
y=1:size(elevation_smoothed, 1)
X=repeat(reshape(x, 1, length(x)), length(y), 1)
#println("Size of X:", size(X))
Y=repeat(reshape(y, length(y), 1), 1, length(x))
#println("Size of Y:", size(Y))
# Calculate the number of ticks and aspect ratio for the 3d plot
x_nticks=min(20, length(x))
y_nticks=min(20, length(y))
z_nticks=5
aspect_ratio=attr(x=1, y=length(y) / length(x), z=0.5)
# Define the layout for the 3D plot
layout3D=PlotlyBase.Layout(
title="3D Surface Plot",
scene=attr(
xaxis_nticks=x_nticks,
yaxis_nticks=y_nticks,
zaxis_nticks=z_nticks,
camera=attr(eye=attr(x=0, y=-1, z=0.5)),
aspectratio=aspect_ratio
)
)
# Transpose the elevation_smoothed array if Y axis is longer than X axis to fix chopping
elevation_smoothed=transpose(elevation_smoothed)
if size(elevation_smoothed, 1) < size(elevation_smoothed, 2)
Y=-Y
else
X=-X
end
trace3D=PlotlyBase.surface(
x=X[1, :],
y=Y[:, 1],
z=elevation_smoothed,
contours_z=attr(
show=true,
usecolormap=true,
highlightcolor="limegreen",
project_z=true
),
colorscale="Viridis",
colorbar=attr(
tickformat=".2g"
)
)
plotdata=[trace3D]
plotlayout=layout3D
return plotdata, plotlayout
end
function crossLinesPlot(x, y, maxwidth, maxheight)
# Define the coordinates for the two lines
l1_x=[0, maxwidth]
l1_y=[y, y]
l2_x=[x, x]
l2_y=[0, maxheight]
# Create the line traces
trace1=PlotlyBase.scatter(x=l1_x, y=l1_y, mode="lines",line=attr(color="red", width=0.5),name="Line X",showlegend=false)
trace2=PlotlyBase.scatter(x=l2_x, y=l2_y, mode="lines",line=attr(color="red", width=0.5),name="Line Y",showlegend=false)
return trace1, trace2
end
# == 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
#@out test="/test.bmp" #slash means it's getting the info from 'public' folder
## 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
# 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 createSumPlot=false # To generate sum spectrum plot
@in createXYPlot=false # To generate an spectrum plot according to the xy values inputed
@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
## Tabulation variables
@out tabIDs=["tab0","tab1","tab2","tab3","tab4"]
@out tabLabels=["Image", "TrIQ", "Spectrum Plot", "Topology Plot","Surface Plot"]
@in selectedTab="tab0"
@out CompTabIDs=["tab0","tab1","tab2","tab3","tab4"]
@out CompTabLabels=["Image", "TrIQ", "Spectrum Plot", "Topology 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"
@out imgWidth=0
@out imgHeight=0
# Messages to interface variables
@out msg=""
@out msgimg=""
@out msgtriq=""
# Saves the route where imzML and mzML files are located
@out full_route=""
@out full_routeMz=""
@out full_routeMz2=""
# 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=""
## 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"
),
yaxis=PlotlyBase.attr(
visible=false
)
)
traceImg=PlotlyBase.heatmap(x=[], y=[])
@out plotdataImg=[traceImg]
@out plotlayoutImg=layoutImg
# For triq image
@out plotdataImgT=[traceImg]
@out plotlayoutImgT=layoutImg
# Interface Plot Spectrum
layoutSpectra=PlotlyBase.Layout(
title="SUM Spectrum plot",
xaxis=PlotlyBase.attr(
title="m/z",
showgrid=true
),
yaxis=PlotlyBase.attr(
title="Intensity",
showgrid=true
)
)
# Dummy 2D scatter plot
traceSpectra=PlotlyBase.scatter(x=[], y=[], mode="lines")
# Create conection to frontend
@out plotdata=[traceSpectra]
@out plotlayout=layoutSpectra
@in xCoord=0
@in yCoord=0
@out xSpectraMz=Float64[]
@out ySpectraMz=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(
title="X",
scaleanchor="y"
),
yaxis=PlotlyBase.attr(
title="Y"
),
)
# Dummy 2D surface plot
traceContour=PlotlyBase.scatter(x=[], y=[], 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)
)
)
# 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=[], y=[], z=[],
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 begin
full_route=pick_file(; filterlist="imzML,mzML")
if isnothing(full_route)
#println("No file selected")
msg="No file selected"
warning_msg=true
btnStartDisable=true
else
if endswith(full_route, "imzML") # Case if the file loaded is imzML
#println("Selected file path: ", full_route)
btnStartDisable=false
btnPlotDisable=false
# Splitting the route with regex from imzml to mzml so the plotting can work
full_routeMz=replace(full_route, r"\.[^.]*$" => ".mzML")
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
else
# If there's no MzML file, we deny access again
btnSpectraDisable=true
SpectraEnabled=false
end
else # Case if the file loaded is mzML
full_routeMz=full_route
btnSpectraDisable=false
SpectraEnabled=true
# Splitting the route the same way
full_route=replace(full_route, r"\.[^.]*$" => ".imzML")
if isfile(full_route)
btnStartDisable=false
else
btnStartDisable=true
full_route=full_routeMz
end
end
xCoord=0
yCoord=0
end
end
@onbutton mainProcess begin
#@onchange Nmass begin
progress=true # Start progress button animation
btnStartDisable=true # We disable the button to avoid multiple requests
btnPlotDisable=true
btnSpectraDisable=true
text_nmass=replace(string(Nmass), "." => "_")
sTime=time()
#full_route=joinpath(file_route, file_name)
if isfile(full_route) && Nmass > 0 && Tol > 0 && Tol <=1
msg="File exists, Nmass=$(Nmass) Tol=$(Tol). Loading file will begin, please be patient."
try
spectra=LoadImzml(full_route)
msg="File loaded. Creating Spectra with the specific mass and tolerance, please be patient."
slice=GetSlice(spectra, Nmass, Tol)
fig=CairoMakie.Figure(size=(128, 256)) # Container
# Append a query string to force the image to refresh
timestamp=string(time_ns())
if triqEnabled # If we have TrIQ
if colorLevel < 2 || colorLevel > 256 || triqProb < 0.8 || triqProb > 1
msg="Incorrect TrIQ values, please adjust accordingly and try again."
warning_msg=true
else
image_path=joinpath("./public", "TrIQ_$(text_nmass).bmp")
sliceTriq=TrIQ(slice, Int(colorLevel), triqProb)
#println("slice raw: $(typeof(slice))")
#println("TriQ matrix: $(typeof(sliceTriq))")
sliceTriq=reverse(sliceTriq, dims=2)
##SaveBitmap(joinpath("public", "TrIQ_$(text_nmass).bmp"),sliceTriq,ViridisPalette)
SaveBitmapCl(joinpath("public", "TrIQ_$(text_nmass).bmp"),sliceTriq,ViridisPalette)
# Use timestamp to refresh image interface container
imgIntT="/TrIQ_$(text_nmass).bmp?t=$(timestamp)"
plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT)
# Get current image
current_triq="TrIQ_$(text_nmass).bmp"
msgtriq="TrIQ image with the Nmass of $(replace(text_nmass, "_" => "."))"
# Create colorbar
ticks=round.(range(0, (stop=maximum(sliceTriq)*triqProb), length=15), sigdigits=3)
Colorbar(fig[1, 1], colormap=cgrad(:viridis, colorLevel, categorical=true), limits=(0, maximum(sliceTriq)*triqProb),ticks=ticks, label="Intensity", size = 25)
save("public/colorbar_TrIQ_$(text_nmass).png", fig)
colorbarT="/colorbar_TrIQ_$(text_nmass).png?t=$(timestamp)"
# Get current colorbar
current_col_triq="colorbar_TrIQ_$(text_nmass).png"
# We update the directory to include the new placed images.
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 file has been created in $(eTime) seconds successfully inside the 'public' folder of the app"
selectedTab="tab1"
#println("all msi in folder=",triq_bmp)
#println("all col msi in folder=",col_triq_png)
end
else # If we don't use TrIQ
image_path=joinpath("./public", "MSI_$(text_nmass).bmp")
##sliceQuant=IntQuant(slice)
sliceQuant=IntQuantCl(slice,colorLevel)
#println("slice raw: $(typeof(slice))")
#println("slice in intQuant: $(typeof(sliceQuant))")
sliceQuant=reverse(sliceQuant, dims=2)
##SaveBitmap(joinpath("public", "MSI_$(text_nmass).bmp"),sliceQuant,ViridisPalette)
SaveBitmapCl(joinpath("public", "MSI_$(text_nmass).bmp"),sliceQuant,ViridisPalette)
# Use timestamp to refresh image interface container
imgInt="/MSI_$(text_nmass).bmp?t=$(timestamp)"
plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt)
# Get current image
current_msi="MSI_$(text_nmass).bmp"
msgimg="Image with the Nmass of $(replace(text_nmass, "_" => "."))"
# Create colorbar
#ticks=round.(range(0, stop=maximum(slice), length=10), sigdigits=3)
ticks=round.(range(0, stop=maximum(sliceQuant), length=15), sigdigits=3)
Colorbar(fig[1, 1], colormap=cgrad(:viridis, colorLevel, categorical=true), limits=(0, maximum(sliceQuant)),ticks=ticks, label="Intensity", size = 25)
save("public/colorbar_MSI_$(text_nmass).png", fig)
colorbar="/colorbar_MSI_$(text_nmass).png?t=$(timestamp)"
# Get current colorbar
current_col_msi="colorbar_MSI_$(text_nmass).png"
# We update the directory to include the new placed images.
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 file has been created in $(eTime) seconds successfully inside the 'public' folder of the app"
#println("all msi in folder=",msi_bmp)
#println("all col msi in folder=",col_msi_png)
end
catch e
msg="There was an error loading the ImzML file, please verify the file accordingly and try again. $(e)"
warning_msg=true
end
else
msg="File does not exist or a parameter is incorrect, please try again."
warning_msg=true
end
spectra=nothing # Important for memory cleaning
slice=nothing
GC.gc() # Trigger garbage collection
if Sys.islinux()
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure julia returns the freed memory to OS
end
btnStartDisable=false
btnPlotDisable=false
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
end
progress=false
end
@onbutton createSumPlot begin
msg="Sum spectrum plot selected"
sTime=time()
#full_route=joinpath( file_route, file_name )
if isfile(full_routeMz) # Check if the file exists
progressSpectraPlot=true
btnPlotDisable=true
btnStartDisable=true
msg="Loading plot..."
spectraMz=LoadMzml(full_routeMz)
layoutSpectra=PlotlyBase.Layout(
title="SUM Spectrum plot",
xaxis=PlotlyBase.attr(
title="m/z",
showgrid=true
),
yaxis=PlotlyBase.attr(
title="Intensity",
showgrid=true
),
autosize=false
)
# dims=size(spectraMz)
# scansMax=dims[2] # we get the total of scansMax
xSpectraMz=mean(spectraMz[1,:])
ySpectraMz=mean(spectraMz[2,:])
traceSpectra=PlotlyBase.scatter(x=xSpectraMz, y=ySpectraMz, mode="lines")
plotdata=[traceSpectra] # We add the data from spectra to the plot
plotlayout=layoutSpectra
GC.gc() # Trigger garbage collection
if Sys.islinux()
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure julia returns the freed memory to OS
end
selectedTab="tab2"
fTime=time()
eTime=round(fTime-sTime,digits=3)
msg="Plot loaded in $(eTime) seconds"
else
msg="there was an error with the mzML, please try again"
warning_msg=true
end
progressSpectraPlot=false
btnPlotDisable=false
if endswith(full_route, "imzML")
btnStartDisable=false
end
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
end
end
@onbutton createXYPlot begin
msg="Sum spectrum plot selected"
sTime=time()
#full_route=joinpath( file_route, file_name )
if isfile(full_routeMz) # Check if the file exists
progressSpectraPlot=true
btnStartDisable=true
btnPlotDisable=true
btnSpectraDisable=true
msg="Loading plot..."
spectraMz=LoadMzml(full_routeMz)
layoutSpectra=PlotlyBase.Layout(
title="($xCoord, $yCoord) Specific spectrum plot",
xaxis=PlotlyBase.attr(
title="m/z",
showgrid=true
),
yaxis=PlotlyBase.attr(
title="Intensity",
showgrid=true
),
autosize=false
)
xSpectraMz=spectraMz[1,abs(xCoord)]
ySpectraMz=spectraMz[2,abs(yCoord)]
traceSpectra=PlotlyBase.scatter(x=xSpectraMz, y=ySpectraMz, mode="lines")
plotdata=[traceSpectra] # We add the data from spectra to the plot
plotlayout=layoutSpectra
GC.gc() # Trigger garbage collection
if Sys.islinux()
ccall(:malloc_trim, Int32, (Int32,), 0) # Ensure julia returns the freed memory to OS
end
selectedTab="tab2"
fTime=time()
eTime=round(fTime-sTime,digits=3)
msg="Plot loaded in $(eTime) seconds"
else
msg="there was an error with the mzML or the coordenates, please try again"
warning_msg=true
end
progressSpectraPlot=false
btnPlotDisable=false
if endswith(full_route, "imzML")
btnStartDisable=false
end
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
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)
else
traceImg=PlotlyBase.heatmap(x=[], y=[])
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)
else
traceImg=PlotlyBase.heatmap(x=[], y=[])
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)
else
traceImg=PlotlyBase.heatmap(x=[], y=[])
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)
else
traceImg=PlotlyBase.heatmap(x=[], y=[])
plotdataImgT=[traceImg]
msgtriq=""
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 )
sTime=time()
if isfile(var)
progressPlot=true
btnPlotDisable=true
btnStartDisable=true
btnSpectraDisable=true
try
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
println(msg)
end
else
msg="Image could not be 3d plotted"
warning_msg=true
end
progressPlot=false
btnPlotDisable=false
btnStartDisable=false
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
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 )
sTime=time()
if isfile(var)
progressPlot=true
btnPlotDisable=true
btnStartDisable=true
btnSpectraDisable=true
try
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
println(msg)
end
else
msg="Image could not be 3d plotted"
warning_msg=true
end
progressPlot=false
btnPlotDisable=false
btnStartDisable=false
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
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)
sTime=time()
if isfile(var)
progressPlot=true
btnPlotDisable=true
btnStartDisable=true
btnSpectraDisable=true
try
img=load(var)
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
println(msg)
end
else
msg="Image could not be 2D plotted"
warning_msg=true
end
progressPlot=false
btnPlotDisable=false
btnStartDisable=false
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
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)
sTime=time()
if isfile(var)
progressPlot=true
btnPlotDisable=true
btnStartDisable=true
btnSpectraDisable=true
try
img=load(var)
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
println(msg)
end
else
msg="Image could not be 2D plotted"
warning_msg=true
end
progressPlot=false
btnPlotDisable=false
btnStartDisable=false
if isfile(full_routeMz)
# We enable coord search and spectra plot creation
btnSpectraDisable=false
SpectraEnabled=true
end
end
@onbutton compareBtn begin
CompareDialog=true
# We remove the red lines
traceSpectra=PlotlyBase.scatter(x=xSpectraMz, y=ySpectraMz, mode="lines",name="Spectra",showlegend=false)
plotdata=[traceSpectra]
end
# Event detection for clicking on the spectrum plot
@onchange data_click begin
if selectedTab == "tab2"
if !isempty(xSpectraMz)
#println("Clicked data on sum spectrum plot : ", data_click)
spectracoords=reshape(plotdata, 1, length(plotdata))
#println("Spectra: $(ndims(spectracoords))")
# Extract x and y values from data_click
cursor_data=data_click["cursor"]
x_value=cursor_data["x"]
y_value=cursor_data["y"] # Get the x and y values from the click of the cursor
closest_distance=Inf
for val in spectracoords
# Find the index where x is within a range
start_idx=findfirst(x -> x >= x_value - 10, val[:x])
end_idx=findlast(x -> x <= x_value + 10, val[:x])
# Ensure the index are valid and within range
if start_idx !== nothing && end_idx !== nothing
for i in start_idx:end_idx
spectra_x=val[:x][i]
spectra_y=val[:y][i]
distance=sqrt((spectra_x - x_value)^2 + (spectra_y - y_value)^2) # Calculate distance
if distance < closest_distance
closest_distance=distance
Nmass=round(spectra_x, digits=2)
end
end
end
end
layoutSpectra=PlotlyBase.Layout(
title="SUM Spectrum plot",
xaxis=PlotlyBase.attr(
title="m/z",
showgrid=true
),
yaxis=PlotlyBase.attr(
title="Intensity",
showgrid=true
),
autosize=false
)
traceSpectra=PlotlyBase.scatter(x=xSpectraMz, y=ySpectraMz, mode="lines",name="Spectra",showlegend=false)
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] # We add the data from spectra and the red line to the plot
plotlayout=layoutSpectra
end
elseif selectedTab == "tab1"
#println("you have clicked the triq image")
cursor_data=data_click["cursor"]
xCoord=Int32(round(cursor_data["x"]))
if xCoord < 0
xCoord=0
elseif xCoord > imgWidth
xCoord=imgWidth
end
yCoord=Int32(round(cursor_data["y"]))
if yCoord > 0
yCoord=0
elseif yCoord < -imgHeight
yCoord=-imgHeight
end # Get the x and y values from the click of the cursor and make sure they don't exceed image proportions
#plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT)
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"
#println("you have clicked the normal image")
cursor_data=data_click["cursor"]
xCoord=Int32(round(cursor_data["x"]))
if xCoord < 0
xCoord=0
elseif xCoord > imgWidth
xCoord=imgWidth
end
yCoord=Int32(round(cursor_data["y"]))
if yCoord > 0
yCoord=0
elseif yCoord < -imgHeight
yCoord=-imgHeight
end # Get the x and y values from the click of the cursor and make sure they don't exceed image proportions
#plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt)
plotdataImg=filter(trace -> !(get(trace, :name, "") in ["Line X", "Line Y"]), plotdataImg)
trace1, trace2=crossLinesPlot(xCoord, yCoord, imgWidth, -imgHeight)
plotdataImg=append!(plotdataImg, [trace1, trace2])
end
end
# WIP add an x and y input and a plot to select pixels in an image and then calculate a plot (not the sum of plots)
# optional: red lines to signal which pixel is selected in the image plot
@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
# == Advanced features ==
#=
- The @private macro defines a reactive variable that is not sent to the browser.
This is useful for storing data that is unique to each user session but is not needed
in the UI.
@private table=DataFrame(a=1:10, b=10:19, c=20:29)
=#