From 59630bdd95aff3367c34d0889fed6dbda4d8d7d8 Mon Sep 17 00:00:00 2001 From: Pixelguy14 Date: Sat, 8 Feb 2025 18:36:49 -0600 Subject: [PATCH] Now the comparative view offers more options to compare to, fixed small mistakes --- .gitignore | 4 +- app.jl | 623 +++++++++++++++--------------------------- app.jl.html | 33 ++- julia_imzML_visual.jl | 306 +++++++++++++++++++++ 4 files changed, 543 insertions(+), 423 deletions(-) diff --git a/.gitignore b/.gitignore index 7ddf6cd..6832527 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ public/* +public/css/imgOver.png !public/css/ !public/css/autogenerated.css !public/css/LABI_logo.png log/* - - -Manifest.toml diff --git a/app.jl b/app.jl index 972c6cc..45f3ecd 100644 --- a/app.jl +++ b/app.jl @@ -13,341 +13,9 @@ using Images using LinearAlgebra using NativeFileDialog # Opens the file explorer depending on the OS using StipplePlotly -using Base64 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 - ), - margin=attr(l=0,r=0,t=0,b=0,pad=0) - ) - - # 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 - -function loadImgPlot(interfaceImg::String, overlayImg::String, imgTrans::Float64) - timestamp=string(time_ns()) - # Load the main image - cleaned_img = replace(interfaceImg, r"\?.*" => "") - cleaned_img = lstrip(cleaned_img, '/') - var = joinpath("./public", cleaned_img) - img = load(var) - # Convert to grayscale - img_gray = Gray.(img) - img_array = Array(img_gray) - elevation = Float32.(Array(img_array)) ./ 255.0 - # 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) - #println("height: $(X), width: $(-Y)") - - # Create the layout with overlay image - layoutImg = PlotlyBase.Layout( - images = [attr( - #source = "data:image/png;base64,$overlayImg", - #source = "https://images.plot.ly/language-icons/api-home/python-logo.png", - source = "$(overlayImg)?t=$(timestamp)", - xref = "x", - yref = "y", - x = 0, - y = 0, - sizex = width, - sizey = -height, - sizing = "stretch", - opacity = imgTrans, - layer = "above" # Place the overlay image in the foreground - )], - xaxis = PlotlyBase.attr( - visible = false, - scaleanchor = "y", - range = [1, width] - ), - yaxis = PlotlyBase.attr( - visible = false, - range = [-height,-1] - ), - margin = attr(l = 0, r = 0, t = 0, b = 0, pad = 0) - ) - - # Create the trace for the main image - trace = PlotlyBase.heatmap( - z = elevation, - x = X, - y = -Y, - name = "", - showlegend = false, - colorscale = "Viridis", - showscale = false - ) - - plotdata = [trace] - plotlayout = layoutImg - 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) - # parse matrix to int32 to closely resemble similarity to original bitmap - elevation=Float32.(Array(img_array))./ 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 of $cleaned_img", - xaxis=PlotlyBase.attr( - title="X", - scaleanchor="y" - ), - yaxis=PlotlyBase.attr( - title="Y" - ), - margin=attr(l=0,r=0,t=120,b=0,pad=0) - ) - 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_array)) ./ 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 of $cleaned_img", - 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 - ), - margin=attr(l=0,r=0,t=120,b=0,pad=0) - ) - # 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 - -function log_tick_formatter(values::Vector{Float64}) - #println("values: $(values) + type: $(typeof(values))") - # Initialize exponents dictionary - exponents=zeros(Int, length(values)) - formValues=zeros(Float64, length(values)) - for i in 1:length(values) - value = values[i] - #println("value: $(value)") - if value >= 1000 # positive formatting for notation - while value >= 1000 - value /= 10 - exponents[i] += 1 - end - #println("value: $(value) x10^$(exponents[i])") - elseif value > 0 && value < 1 # negative formatting for notation - while value < 1 - value *= 10 - exponents[i] -= 1 - end - #values[i] = round(values[i], digits=2) - #println("value: $(value) x10^$(exponents[i])") - end - #formatted_values[i] = values[i] != 0 ? "$(round(values[i], digits=2))x10" * Makie.UnicodeFun.to_superscript(exponents[i]) : "0" - formValues[i]=value - end - #return formatted_values - #return map(v -> values * "x10" * Makie.UnicodeFun.to_superscript(round(Int64, v)), exponents) - #println("formated values: $(formValues) + type: $(typeof(formValues))") - #println("exponents: $(exponents) + type: $(typeof(exponents))") - #return map((v, e) -> "$(round(v, digits=2))x10" * Makie.UnicodeFun.to_superscript(e), formValues, exponents) - return map((v, e) -> e == 0 ? "$(round(v, sigdigits=2))" : "$(round(v, sigdigits=2))x10" * Makie.UnicodeFun.to_superscript(e), formValues, exponents) - -end - # == Reactive code == # Reactive code to make the UI interactive @app begin @@ -356,7 +24,6 @@ end # @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 @@ -396,6 +63,11 @@ end @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"] @@ -410,6 +82,11 @@ end @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 @@ -418,7 +95,8 @@ end @in progressOptical = false @out btnOpticalDisable = true @in btnOptical = false - @out opticalImg = "" + @in btnOpticalT = false + @in opticalOverTriq = false # Messages to interface variables @out msg="" @@ -444,6 +122,11 @@ end @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() @@ -465,9 +148,16 @@ end traceImg=PlotlyBase.heatmap(x=[], y=[]) @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="SUM Spectrum plot", @@ -592,21 +282,22 @@ 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 && colorLevel > 1 && colorLevel < 257 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) - slice=GetMzSliceJl(spectra,Nmass,Tol) + try + slice=GetMzSliceJl(spectra,Nmass,Tol) + catch e + println("the error is in the slice: $e") + end fig=CairoMakie.Figure(size=(150, 250)) # Container # Append a query string to force the image to refresh timestamp=string(time_ns()) @@ -616,11 +307,18 @@ end warning_msg=true else image_path=joinpath("./public", "TrIQ_$(text_nmass).bmp") - sliceTriq=TrIQ(slice, colorLevel, triqProb) - #println("slice raw: $(typeof(slice))") - #println("TriQ matrix: $(typeof(sliceTriq))") + valid_slice = false + while Tol <= 1.0 && !valid_slice + try + slice = GetMzSliceJl(spectra, Nmass, Tol) + sliceTriq = TrIQ(slice, colorLevel, triqProb) + valid_slice = true + catch e + msg="Warning: insufficient tolerance, inputs modified to allow the creation of an image regardless = $Tol: $e" + Tol += 0.1 + end + end 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)" @@ -631,15 +329,8 @@ end # Create colorbar bound = julia_mzML_imzML.GetOutlierThres(slice, triqProb) levels = range(bound[1],stop=bound[2], length=8) - #println("range of the levels 1: $(levels)") levels = vcat(levels, 2*levels[end]-levels[end-1]) - #println("range of the levels 2: $(levels) + $(length(levels))") - #ticks=round.(range(0, (stop=maximum(slice)*triqProb), length=10), sigdigits=3) - #ticks=range(0, (stop=maximum(slice)*triqProb), length=5) - #println("ticks: $(typeof(ticks))") Colorbar(fig[1, 1], colormap=cgrad(:viridis, colorLevel, categorical=true), limits=(0, bound[2]),ticks=levels,tickformat=log_tick_formatter, label="Intensity", size = 25) - #Colorbar(fig[1, 1], colormap=cgrad(:viridis, colorLevel, categorical=true), limits=(0, maximum(slice)*triqProb),ticks=ticks, label="Intensity", size = 25) - #println("Colorbar: $(typeof(fig))") save("public/colorbar_TrIQ_$(text_nmass).png", fig) colorbarT="/colorbar_TrIQ_$(text_nmass).png?t=$(timestamp)" # Get current colorbar @@ -651,17 +342,16 @@ end 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,Int(colorLevel-1)) - #println("slice raw: $(typeof(slice))") - #println("slice in intQuant: $(typeof(sliceQuant))") + try + sliceQuant=IntQuantCl(slice,Int(colorLevel-1)) + catch e + println("the error is in the non triq slice: $e") + end 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)" @@ -671,9 +361,6 @@ end msgimg="Image with the Nmass of $(replace(text_nmass, "_" => "."))" # Create colorbar levels=range(0,maximum(slice),length=8) - #ticks=round.(range(0, stop=maximum(slice), length=10), sigdigits=3) - #ticks=range(0, stop=maximum(slice), length=5) - #Colorbar(fig[1, 1], colormap=cgrad(:viridis, colorLevel, categorical=true), limits=(0, maximum(slice)),ticks=ticks, label="Intensity", size = 25) Colorbar(fig[1, 1], colormap=cgrad(:viridis, colorLevel, categorical=true), limits=(0, maximum(slice)),ticks=levels,tickformat=log_tick_formatter, label="Intensity", size = 25) save("public/colorbar_MSI_$(text_nmass).png", fig) colorbar="/colorbar_MSI_$(text_nmass).png?t=$(timestamp)" @@ -686,8 +373,6 @@ end 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)" @@ -717,7 +402,6 @@ 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 @@ -737,19 +421,11 @@ end autosize=false, margin=attr(l=0,r=0,t=120,b=0,pad=0) ) - # dims=size(spectraMz) - # scansMax=dims[2] # we get the total of scansMax - #spectraMz = convert(Array{Float64,2}, spectraMz) - #min_length = min(length(spectraMz[1,:]), length(spectraMz[2,:])) try xSpectraMz=mean(spectraMz[1,:]) ySpectraMz=mean(spectraMz[2,:]) - #println("length of xSpectraMz: $(length(xSpectraMz))") - #println("length of ySpectraMz: $(length(ySpectraMz))") catch e #println("an error was found: $e") - msg="there was an error with the mzML, please try again: $e" - warning_msg=true xSpectraMz=spectraMz[1,1] ySpectraMz=spectraMz[2,1] end @@ -783,7 +459,6 @@ 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 @@ -851,7 +526,7 @@ end # 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 @@ -955,6 +630,119 @@ end 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" => "") + msgimg="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=[], y=[]) + plotdataImgComp=[traceImg] + msgimg="" + 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" => "") + msgimg="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=[], y=[]) + plotdataImgComp=[traceImg] + msgimg="" + 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" => "") + msgtriq="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=[], y=[]) + plotdataImgTComp=[traceImg] + msgtriq="" + 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" => "") + msgtriq="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=[], y=[]) + plotdataImgTComp=[traceImg] + msgtriq="" + end + end + # 3d plot @onbutton image3dPlot begin msg="Image 3D plot selected" @@ -1125,64 +913,49 @@ 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 - + x_value = cursor_data["x"] + y_value = cursor_data["y"] + + # Find the minimum x-value in spectracoords + min_x_value = minimum([minimum(val[:x]) for val in spectracoords if !isempty(val[:x])]) + # Adjust x_value and spectracoords x-values to start from 0 + adjusted_x_value = x_value - min_x_value + + 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]) + adjusted_x = val[:x] .- min_x_value + start_idx = findfirst(x -> x >= adjusted_x_value - 20, adjusted_x) + end_idx = findlast(x -> x <= adjusted_x_value + 20, adjusted_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 + spectra_x = adjusted_x[i] + spectra_y = val[:y][i] + distance = sqrt((spectra_x - adjusted_x_value)^2 + (spectra_y - y_value)^2) if distance < closest_distance - closest_distance=distance - Nmass=round(spectra_x, digits=2) + closest_distance = distance + Nmass = round(spectra_x + min_x_value, 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, - margin=attr(l=0,r=0,t=120,b=0,pad=0) - ) 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"])) + yCoord=Int32(round(cursor_data["y"])) if xCoord < 1 xCoord=1 elseif xCoord > imgWidth @@ -1193,14 +966,13 @@ end 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"])) + yCoord=Int32(round(cursor_data["y"])) if xCoord < 1 xCoord=1 elseif xCoord > imgWidth @@ -1211,7 +983,6 @@ end 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","Optical"]), plotdataImg) trace1, trace2=crossLinesPlot(xCoord, yCoord, imgWidth, -imgHeight) plotdataImg=append!(plotdataImg, [trace1, trace2]) @@ -1219,19 +990,51 @@ end end @onbutton btnOptical begin - timestamp=string(time_ns()) imgRoute=pick_file(; filterlist="png,bmp,jpg,jpeg") + selectedTab="tab0" + plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT) if isnothing(imgRoute) - println("No file selected") + msg="No optical image selected" else 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") + selectedTab="tab1" + plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt) + if isnothing(imgRoute) + msg="No optical image selected" + else + 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 - plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt,"/css/imgOver.png",imgTrans) + if !opticalOverTriq + plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt,"/css/imgOver.png",imgTrans) + else + plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT,"/css/imgOver.png",imgTrans) + end + end + + @onchange opticalOverTriq begin + if !opticalOverTriq + plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt,"/css/imgOver.png",imgTrans) + plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT) + selectedTab="tab0" + else + plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt) + plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT,"/css/imgOver.png",imgTrans) + selectedTab="tab1" + end end @mounted watchplots() diff --git a/app.jl.html b/app.jl.html index 5e56d7c..aa53148 100644 --- a/app.jl.html +++ b/app.jl.html @@ -143,8 +143,19 @@ - + + + + Over normal image + + + + + Over TrIQ image + + +
Transparency: {{ imgTrans }} @@ -245,6 +256,8 @@ Transparency: {{ imgTrans }}
+ @@ -313,17 +326,17 @@
- - + +
- +
- +

{{msgimg}}

@@ -333,17 +346,17 @@
- - + +
- +
- +

{{msgtriq}}

diff --git a/julia_imzML_visual.jl b/julia_imzML_visual.jl index 7be02f1..b1fe4a7 100644 --- a/julia_imzML_visual.jl +++ b/julia_imzML_visual.jl @@ -78,3 +78,309 @@ function GetMzSliceJl(imzML, mass, tolerance) replace!(image, NaN => 0.0) return image end + +# == Search functions == +# Functions that recieve a list to update, and the current direction both as string for +# searching in the directory the position the list is going +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) + # Convert to grayscale + img_gray=Gray.(img) + img_array=Array(img_gray) + elevation=Float32.(Array(img_array)) ./ 255.0 + # Get the X, Y coordinates of the image + height, width=size(img_array) + 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 + ), + margin=attr(l=0,r=0,t=0,b=0,pad=0) + ) + + # 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 +# loadImgPlot recieves the local directory of the image as a string, the local directory o the overlay image +# and the transparency its required to have. Returns the layout and data for the heatmap plotly plot +# this function loads the image into a plot +function loadImgPlot(interfaceImg::String, overlayImg::String, imgTrans::Float64) + timestamp=string(time_ns()) + # Load the main image + cleaned_img = replace(interfaceImg, r"\?.*" => "") + cleaned_img = lstrip(cleaned_img, '/') + var = joinpath("./public", cleaned_img) + img = load(var) + # Convert to grayscale + img_gray = Gray.(img) + img_array = Array(img_gray) + elevation = Float32.(Array(img_array)) ./ 255.0 + # Get the X, Y coordinates of the image + height, width = size(img_array) + X = collect(1:width) + Y = collect(1:height) + + # Create the layout with overlay image + layoutImg = PlotlyBase.Layout( + images = [attr( + source = "$(overlayImg)?t=$(timestamp)", + xref = "x", + yref = "y", + x = 0, + y = 0, + sizex = width, + sizey = -height, + sizing = "stretch", + opacity = imgTrans, + layer = "above" # Place the overlay image in the foreground + )], + xaxis = PlotlyBase.attr( + visible = false, + scaleanchor = "y", + range = [0, width] + ), + yaxis = PlotlyBase.attr( + visible = false, + range = [-height,0] + ), + margin = attr(l = 0, r = 0, t = 0, b = 0, pad = 0) + ) + + # Create the trace for the main image + trace = PlotlyBase.heatmap( + z = elevation, + x = X, + y = -Y, + name = "", + showlegend = false, + colorscale = "Viridis", + showscale = false + ) + + plotdata = [trace] + plotlayout = layoutImg + 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_array))./ 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 of $cleaned_img", + xaxis=PlotlyBase.attr( + title="X", + scaleanchor="y" + ), + yaxis=PlotlyBase.attr( + title="Y" + ), + margin=attr(l=0,r=0,t=120,b=0,pad=0) + ) + 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) + img_gray=Gray.(img) # Convert to grayscale + img_array=Array(img_gray) + elevation=Float32.(Array(img_array)) ./ 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)) + # 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 of $cleaned_img", + 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 + ), + margin=attr(l=0,r=0,t=120,b=0,pad=0) + ) + # 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 +# This function recieves the x and y coords currently selected, and the dimentions of +# the image to create two traces that will display in a cross section +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 + +# This function is used for giving colorbar values a visual format +# that shortens long values giving them scientific notation +function log_tick_formatter(values::Vector{Float64}) + # Initialize exponents dictionary + exponents=zeros(Int, length(values)) + formValues=zeros(Float64, length(values)) + for i in 1:length(values) + value = values[i] + if value >= 1000 # positive formatting for notation + while value >= 1000 + value /= 10 + exponents[i] += 1 + end + elseif value > 0 && value < 1 # negative formatting for notation + while value < 1 + value *= 10 + exponents[i] -= 1 + end + end + formValues[i]=value + end + return map((v, e) -> e == 0 ? "$(round(v, sigdigits=2))" : "$(round(v, sigdigits=2))x10" * Makie.UnicodeFun.to_superscript(e), formValues, exponents) + +end \ No newline at end of file