altered imzML parser to be compatible with timsconverter, small improvements on the UI, other small bug fixes done

This commit is contained in:
Pixelguy14 2025-10-13 22:05:56 -06:00
parent f35acd1d0f
commit 2fd2b90513
9 changed files with 916 additions and 240 deletions

View File

@ -22,7 +22,7 @@ https://codeberg.org/LabABI/JuliaMSI
~/Downloads/JuliaMSI-main/juliamsi
2. Without entering the Julia environment, launch the project in your terminal with the following command (which works for all operating systems):
```
julia --project=. start_MSI_GUI.jl
julia --threads auto --project=. start_MSI_GUI.jl
```
3. After the script has finished loading, you can open a [page](http://127.0.0.1:1481/) in your browser with the web app running.

245
app.jl
View File

@ -159,6 +159,14 @@ include("./julia_imzML_visual.jl")
## Plots
# Local image to plot
layoutImg=PlotlyBase.Layout(
title=PlotlyBase.attr(
text="",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=14,
color="black"
)
),
xaxis=PlotlyBase.attr(
visible=false,
scaleanchor="y",
@ -185,7 +193,14 @@ include("./julia_imzML_visual.jl")
@out plotlayoutImgTComp=layoutImg
# Interface Plot Spectrum
layoutSpectra=PlotlyBase.Layout(
title="Spectrum plot",
title=PlotlyBase.attr(
text="Spectrum plot",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
hovermode="closest",
xaxis=PlotlyBase.attr(
title="<i>m/z</i>",
@ -215,7 +230,14 @@ include("./julia_imzML_visual.jl")
# Interface Plot Surface
layoutContour=PlotlyBase.Layout(
title="2D Topographic map",
title=PlotlyBase.attr(
text="2D Topographic map",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
xaxis=PlotlyBase.attr(
visible=false,
scaleanchor="y"
@ -226,7 +248,7 @@ include("./julia_imzML_visual.jl")
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")
traceContour=PlotlyBase.contour(x=Vector{Float64}(), y=Vector{Float64}(), mode="lines")
# Create conection to frontend
@out plotdataC=[traceContour]
@out plotlayoutC=layoutContour
@ -234,7 +256,14 @@ include("./julia_imzML_visual.jl")
# Interface Plot 3d
# Define the layout for the 3D plot
layout3D=PlotlyBase.Layout(
title="3D Surface plot",
title=PlotlyBase.attr(
text="3D Surface plot",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
scene=attr(
xaxis_title="X",
yaxis_title="Y",
@ -386,69 +415,107 @@ include("./julia_imzML_visual.jl")
# Use the new get_mz_slice with the centralized MSIData object
println("get_mz_slice time:")
slice = @time 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
println("TrIQ time:")
sliceTriq = @time TrIQ(slice, colorLevel, triqProb)
if MFilterEnabled
sliceTriq = round.(UInt8, median_filter(sliceTriq))
end
sliceTriq = reverse(sliceTriq, dims=2)
println("save_bitmap time:")
@time save_bitmap(joinpath("public", "TrIQ_$(text_nmass).bmp"), sliceTriq, ViridisPalette)
# Failsafe check for empty slice
if all(iszero, slice)
msg = "No intensity data found for m/z = $Nmass with tolerance = $Tol. The resulting image is black. Please consider using a larger tolerance."
warning_msg = true
# Generate a black image but skip the colorbar that would crash
timestamp = string(time_ns())
text_nmass = replace(string(Nmass), "." => "_")
sliceQuant = zeros(UInt8, size(slice))
if triqEnabled
@time save_bitmap(joinpath("public", "TrIQ_$(text_nmass).bmp"), sliceQuant, 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")
println("generate_colorbar_image time:")
@time 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"
msgtriq = "TrIQ image with the Nmass of $(replace(text_nmass, "_" => ".")) (No data)"
colorbarT = "" # Clear colorbar
current_col_triq = ""
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"
else
@time 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, "_" => ".")) (No data)"
colorbar = "" # Clear colorbar
current_col_msi = ""
msi_bmp = sort(filter(filename -> startswith(filename, "MSI_") && endswith(filename, ".bmp"), readdir("public")), lt=natural)
selectedTab = "tab0"
end
else # If we don't use TrIQ
println("quantize_intensity time:")
sliceQuant = @time quantize_intensity(slice, colorLevel)
if MFilterEnabled
sliceQuant = round.(UInt8, median_filter(sliceQuant))
end
sliceQuant = reverse(sliceQuant, dims=2)
println("save_bitmap time:")
@time 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")
println("generate_colorbar_image time:")
@time 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"
# The warning message is already set above
else
# Original path for when data is found
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
println("TrIQ time:")
sliceTriq = @time TrIQ(slice, colorLevel, triqProb)
if MFilterEnabled
sliceTriq = round.(UInt8, median_filter(sliceTriq))
end
println("save_bitmap time:")
@time 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")
println("generate_colorbar_image time:")
@time 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
println("quantize_intensity time:")
sliceQuant = @time quantize_intensity(slice, colorLevel)
if MFilterEnabled
sliceQuant = round.(UInt8, median_filter(sliceQuant))
end
println("save_bitmap time:")
@time 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")
println("generate_colorbar_image time:")
@time 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
end
catch e
msg = "There was an error creating the image: $e"
@ -624,6 +691,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImg=[traceImg]
plotlayoutImg=layoutImg
msgimg=""
end
end
@ -651,6 +719,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImg=[traceImg]
plotlayoutImg=layoutImg
msgimg=""
end
end
@ -679,6 +748,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImgT=[traceImg]
plotlayoutImgT=layoutImg
msgtriq=""
end
end
@ -706,6 +776,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImgT=[traceImg]
plotlayoutImgT=layoutImg
msgtriq=""
end
end
@ -735,6 +806,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImgComp=[traceImg]
plotlayoutImgComp=layoutImg
msgimgComp=""
end
end
@ -763,6 +835,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImgComp=[traceImg]
plotlayoutImgComp=layoutImg
msgimgComp=""
end
end
@ -791,6 +864,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImgTComp=[traceImg]
plotlayoutImgTComp=layoutImg
msgtriqComp=""
end
end
@ -819,6 +893,7 @@ include("./julia_imzML_visual.jl")
else
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
plotdataImgTComp=[traceImg]
plotlayoutImgTComp=layoutImg
msgtriqComp=""
end
end
@ -1026,9 +1101,20 @@ include("./julia_imzML_visual.jl")
@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
cursor_data = get(data_click, "cursor", nothing)
if cursor_data === nothing
return
end
x_val = get(cursor_data, "x", nothing)
y_val = get(cursor_data, "y", nothing)
if x_val === nothing || y_val === nothing
return # Do nothing if coordinates are not provided by the event
end
x = Int32(round(x_val))
y = Int32(round(y_val)) # y is negative in the UI
# Update the reactive coordinates, which will trigger the crosshair update
xCoord = clamp(x, 1, imgWidth)
@ -1107,3 +1193,40 @@ end
# Register a new route and the page that will be loaded on access
@page("/", "app.jl.html")
end
#=
function __init__()
@async begin
println("Pre-compiling functions at startup...")
# Create a dummy MSIData object to be used for pre-compilation
dummy_source = ImzMLSource("dummy.ibd", Float32, Float32)
dummy_meta = MSI_src.SpectrumMetadata(0,0,"",MSI_src.UNKNOWN, MSI_src.SpectrumAsset(Float32,false,0,0,:mz), MSI_src.SpectrumAsset(Float32,false,0,0,:intensity))
dummy_msi_data = MSIData(dummy_source, [dummy_meta], (1,1), zeros(Int,1,1), 0)
# Pre-compile functions from btnSearch
try OpenMSIData("dummy.imzML") catch end
try precompute_analytics(dummy_msi_data) catch end
# Pre-compile functions from mainProcess
try get_mz_slice(dummy_msi_data, 1.0, 1.0) catch end
try TrIQ(zeros(10,10), 256, 0.98) catch end
try quantize_intensity(zeros(10,10), 256) catch end
dummy_bmp_path = joinpath("public", "dummy.bmp")
dummy_png_path = joinpath("public", "dummy.png")
try
save_bitmap(dummy_bmp_path, zeros(UInt8, 10, 10), ViridisPalette)
loadImgPlot("/dummy.bmp")
generate_colorbar_image(zeros(10,10), 256, dummy_png_path)
catch e
@warn "Pre-compilation step failed (this is expected if dummy files can't be created/read)"
finally
rm(dummy_bmp_path, force=true)
rm(dummy_png_path, force=true)
end
println("Pre-compilation finished.")
end
end
=#

View File

@ -1,5 +1,4 @@
<template>
<header id="header">
<header id="header">
<img src="/css/LABI_logo.png" alt="Labi Logo Icon" id="imgLogo">
<div>
<h4>JuliaMSI&nbsp;</h4>
@ -12,7 +11,7 @@
<div id="extDivStyle" class="row col-12 q-pa-xl">
<div class="row col-6">
<!-- Left DIV -->
<div id="intDivStyle" class="st-col col-12 st-module">
<div id="intDivStyle-left" class="st-col col-12 st-module">
<h6>Search for the imzML or mzML file in your system</h6>
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="full_route" readonly label="Select your imzML or mzML file" v-on:click="btnSearch=true">
<template v-slot:append>
@ -58,7 +57,7 @@
<!-- Spectra Plot Manipulation -->
<div class="col-6">
<div class="st-col col-6 col-sm">
<q-btn-dropdown id="btnStyle" class="q-ma-sm" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
label="Generate Spectra" icon="play_arrow">
<template v-slot:loading>
<q-spinner-hourglass class="on-left" />
@ -99,22 +98,22 @@
</div>
</div>
<div class="row">
<q-btn id="btnStyle" :loading="progress" class="q-ma-sm" :disabled="btnStartDisable" icon="play_arrow"
v-on:click="mainProcess=true" padding="lg" label="Main process">
<q-btn :loading="progress" class="q-ma-sm btn-style" :disabled="btnStartDisable" icon="play_arrow"
v-on:click="mainProcess=true" padding="lg" label="Generate Spectra">
<template v-slot:loading>
<q-spinner-hourglass class="on-left" />
Loading...
</template>
</q-btn>
<q-btn id="btnStyle" icon="zoom_out_map" class="q-ma-sm on-right" v-on:click="compareBtn=true" padding="sm"
<q-btn icon="zoom_out_map" class="q-ma-sm on-right btn-style" v-on:click="compareBtn=true" padding="sm"
label="Compare"></q-btn>
<q-btn id="btnStyle" class="q-ma-sm" :disable="btnMetadataDisable"
<q-btn class="q-ma-sm btn-style" :disable="btnMetadataDisable"
v-on:click="showMetadataBtn=true" label="Show Metadata"></q-btn>
</div>
<p>{{msg}}</p>
<div class="row st-col col-12">
<q-btn-dropdown id="btnStyle" class="q-ma-sm" :loading="progressPlot" :disable="btnPlotDisable"
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressPlot" :disable="btnPlotDisable"
label="Generate Plots" icon="play_arrow">
<template v-slot:loading>
<q-spinner-hourglass class="on-left" />
@ -147,7 +146,7 @@
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn-dropdown id="btnStyle" icon="search" class="q-ma-sm" :disable="btnOpticalDisable"
<q-btn-dropdown icon="search" class="q-ma-sm btn-style" :disable="btnOpticalDisable"
label="Load your optical image">
<q-item clickable v-close-popup v-on:click="btnOptical=true">
<q-item-section>
@ -169,25 +168,25 @@
</div>
<div class="row col-6">
<!-- Right DIV -->
<div id="intDivStyle" class="st-col col-12 col-sm st-module">
<st-tabs id="tabHeader" :ids="tabIDs" :labels="tabLabels" v-model="selectedTab" no-arrows></st-tabs>
<div id="intDivStyle-right" class="st-col col-12 col-sm st-module">
<st-tabs id="tabHeader-main" :ids="tabIDs" :labels="tabLabels" v-model="selectedTab" no-arrows></st-tabs>
<q-tab-panels v-model="selectedTab">
<q-tab-panel name="tab0">
<!-- Content for Tab 0 -->
<!-- Btn image changer -->
<h6>Image visualizer</h6>
<div>
<q-btn id="btnStyle" icon="arrow_back" class="q-my-sm" v-on:click="imgMinus=true"></q-btn>
<q-btn id="btnStyle" icon="arrow_forward" class="q-my-sm on-right" v-on:click="imgPlus=true"></q-btn>
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinus=true"></q-btn>
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style" v-on:click="imgPlus=true"></q-btn>
</div>
<!-- Image manager -->
<div id="image-container" class="row st-col col-12">
<div id="image-container-normal" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none">
<plotly id="plotStyle" :data="plotdataImg" :layout="plotlayoutImg" class="q-pa-none q-ma-none sync_data"
<plotly id="plotImg" :data="plotdataImg" :layout="plotlayoutImg" class="q-pa-none q-ma-none sync_data"
@click="data_click"></plotly>
</div>
<div class="col-2 q-pa-none q-ma-none">
<q-img id="colorbar" class="q-ma-none q-pa-none" :src="colorbar"></q-img>
<q-img id="colorbar-normal" class="q-ma-none q-pa-none" :src="colorbar"></q-img>
</div>
</div>
<p>{{msgimg}}</p>
@ -198,17 +197,17 @@
<!-- Triq Btn image changer -->
<h6>TrIQ visualizer</h6>
<div>
<q-btn id="btnStyle" icon="arrow_back" class="q-my-sm" v-on:click="imgMinusT=true"></q-btn>
<q-btn id="btnStyle" icon="arrow_forward" class="q-my-sm on-right" v-on:click="imgPlusT=true"></q-btn>
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinusT=true"></q-btn>
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style" v-on:click="imgPlusT=true"></q-btn>
</div>
<!-- Triq Image manager -->
<div id="image-container" class="row st-col col-12">
<div id="image-container-triq" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none ">
<plotly id="plotStyle" :data="plotdataImgT" :layout="plotlayoutImgT"
<plotly id="plotImgT" :data="plotdataImgT" :layout="plotlayoutImgT"
class="q-pa-none q-ma-none sync_data" @click="data_click"></plotly>
</div>
<div class="col-2 q-pa-none q-ma-none ">
<q-img id="colorbar" class="q-ma-none q-pa-none" :src="colorbarT"></q-img>
<q-img id="colorbar-triq" class="q-ma-none q-pa-none" :src="colorbarT"></q-img>
</div>
</div>
<p>{{msgtriq}}</p>
@ -216,16 +215,16 @@
<q-tab-panel name="tab2">
<!-- Content for Tab 2 -->
<plotly id="plotStyle" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
<plotly id="plotSpectra" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
<q-tab-panel name="tab3">
<!-- Content for Tab 3 -->
<plotly id="plotStyle" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
<plotly id="plotTopo" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
<q-tab-panel name="tab4">
<!-- Content for Tab 4 -->
<plotly id="plotStyle3d" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
<plotly id="plot3d" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
</q-tab-panels>
</div>
@ -263,23 +262,23 @@
<q-card-section class="q-pt-none col-12">
<div class="row">
<div class="col-6">
<st-tabs id="tabHeader" :ids="tabIDs" :labels="tabLabels" v-model="selectedTab"></st-tabs>
<st-tabs id="tabHeaderCompareLeft" :ids="tabIDs" :labels="tabLabels" v-model="selectedTab"></st-tabs>
<q-tab-panels v-model="selectedTab">
<q-tab-panel name="tab0">
<!-- Content for Tab 0 -->
<!-- Btn image changer -->
<div>
<q-btn id="btnStyle" icon="arrow_back" class="q-my-sm" v-on:click="imgMinus=true"></q-btn>
<q-btn id="btnStyle" icon="arrow_forward" class="q-my-sm on-right" v-on:click="imgPlus=true"></q-btn>
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinus=true"></q-btn>
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style" v-on:click="imgPlus=true"></q-btn>
</div>
<!-- Image manager -->
<div id="image-container" class="row st-col col-12">
<div id="image-container-compare-left-normal" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none ">
<plotly id="plotStyle" :data="plotdataImg" :layout="plotlayoutImg" class="q-pa-none q-ma-none">
<plotly id="plotImgCompareLeft" :data="plotdataImg" :layout="plotlayoutImg" class="q-pa-none q-ma-none">
</plotly>
</div>
<div class="col-2">
<q-img id="colorbar" class="q-ma-none q-pa-none" :src="colorbar"></q-img>
<q-img id="colorbarCompareLeft" class="q-ma-none q-pa-none" :src="colorbar"></q-img>
</div>
</div>
<p>{{msgimg}}</p>
@ -289,56 +288,56 @@
<!-- Content for Tab 1 -->
<!-- Triq Btn image changer -->
<div>
<q-btn id="btnStyle" icon="arrow_back" class="q-my-sm" v-on:click="imgMinusT=true"></q-btn>
<q-btn id="btnStyle" icon="arrow_forward" class="q-my-sm on-right" v-on:click="imgPlusT=true"></q-btn>
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinusT=true"></q-btn>
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style" v-on:click="imgPlusT=true"></q-btn>
</div>
<!-- Triq Image manager -->
<div id="image-container" class="row st-col col-12">
<div id="image-container-compare-left-triq" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none ">
<plotly id="plotStyle" :data="plotdataImgT" :layout="plotlayoutImgT" class="q-pa-none q-ma-none">
<plotly id="plotImgTCompareLeft" :data="plotdataImgT" :layout="plotlayoutImgT" class="q-pa-none q-ma-none">
</plotly>
</div>
<div class="col-2">
<q-img id="colorbar" class="q-ma-none q-pa-none" :src="colorbarT"></q-img>
<q-img id="colorbarTCompareLeft" class="q-ma-none q-pa-none" :src="colorbarT"></q-img>
</div>
</div>
<p>{{msgtriq}}</p>
</q-tab-panel>
<q-tab-panel name="tab2">
<!-- Content for Tab 2 -->
<plotly id="plotStyle" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
<plotly id="plotSpectraCompareLeft" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
<q-tab-panel name="tab3">
<!-- Content for Tab 3 -->
<plotly id="plotStyle" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
<plotly id="plotTopoCompareLeft" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
<q-tab-panel name="tab4">
<!-- Content for Tab 4 -->
<plotly id="plotStyle3d" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
<plotly id="plot3dCompareLeft" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
</q-tab-panels>
</div>
<div class="col-6">
<st-tabs id="tabHeader" :ids="CompTabIDs" :labels="CompTabLabels" v-model="CompSelectedTab" outside-arrows
<st-tabs id="tabHeaderCompareRight" :ids="CompTabIDs" :labels="CompTabLabels" v-model="CompSelectedTab" outside-arrows
mobile-arrows></st-tabs>
<q-tab-panels v-model="CompSelectedTab">
<q-tab-panel name="tab0">
<!-- Content for Tab 0 -->
<!-- Btn image changer -->
<div>
<q-btn id="btnStyle" icon="arrow_back" class="q-my-sm" v-on:click="imgMinusComp=true"></q-btn>
<q-btn id="btnStyle" icon="arrow_forward" class="q-my-sm on-right"
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinusComp=true"></q-btn>
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style"
v-on:click="imgPlusComp=true"></q-btn>
</div>
<!-- Image manager -->
<div id="image-container" class="row st-col col-12">
<div id="image-container-compare-right-normal" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none ">
<plotly id="plotStyle" :data="plotdataImgComp" :layout="plotlayoutImgComp"
<plotly id="plotImgCompareRight" :data="plotdataImgComp" :layout="plotlayoutImgComp"
class="q-pa-none q-ma-none">
</plotly>
</div>
<div class="col-2 q-pa-none q-ma-none ">
<q-img id="colorbar" class="q-ma-none q-pa-none" :src="colorbarComp"></q-img>
<q-img id="colorbarComp" class="q-ma-none q-pa-none" :src="colorbarComp"></q-img>
</div>
</div>
<p>{{msgimgComp}}</p>
@ -348,34 +347,34 @@
<!-- Content for Tab 1 -->
<!-- Triq Btn image changer -->
<div>
<q-btn id="btnStyle" icon="arrow_back" class="q-my-sm" v-on:click="imgMinusTComp=true"></q-btn>
<q-btn id="btnStyle" icon="arrow_forward" class="q-my-sm on-right"
<q-btn icon="arrow_back" class="q-my-sm btn-style" v-on:click="imgMinusTComp=true"></q-btn>
<q-btn icon="arrow_forward" class="q-my-sm on-right btn-style"
v-on:click="imgPlusTComp=true"></q-btn>
</div>
<!-- Triq Image manager -->
<div id="image-container" class="row st-col col-12">
<div id="image-container-compare-right-triq" class="row st-col col-12">
<div class="col-10 q-pa-none q-ma-none ">
<plotly id="plotStyle" :data="plotdataImgTComp" :layout="plotlayoutImgTComp"
<plotly id="plotImgTCompareRight" :data="plotdataImgTComp" :layout="plotlayoutImgTComp"
class="q-pa-none q-ma-none">
</plotly>
</div>
<div class="col-2 q-pa-none q-ma-none ">
<q-img id="colorbar" class="q-ma-none q-pa-none" :src="colorbarTComp"></q-img>
<q-img id="colorbarTComp" class="q-ma-none q-pa-none" :src="colorbarTComp"></q-img>
</div>
</div>
<p>{{msgtriqComp}}</p>
</q-tab-panel>
<q-tab-panel name="tab2">
<!-- Content for Tab 2 -->
<plotly id="plotStyle" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
<plotly id="plotSpectraCompareRight" :data="plotdata" :layout="plotlayout" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
<q-tab-panel name="tab3">
<!-- Content for Tab 3 -->
<plotly id="plotStyle" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
<plotly id="plotTopoCompareRight" :data="plotdataC" :layout="plotlayoutC" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
<q-tab-panel name="tab4">
<!-- Content for Tab 4 -->
<plotly id="plotStyle3d" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
<plotly id="plot3dCompareRight" :data="plotdata3d" :layout="plotlayout3d" class="q-pa-none q-ma-none"></plotly>
</q-tab-panel>
</q-tab-panels>
</div>
@ -411,6 +410,4 @@
<q-btn flat label="Close" style="color:#009f90" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
</template>
</q-dialog>

View File

@ -26,6 +26,26 @@ function decrement_image(current_image, image_list)
end
## Plot Image functions
# Downsample an image matrix to a maximum dimension while preserving aspect ratio
function downsample_image(img_matrix, max_dim::Int)
h, w = size(img_matrix)
if h <= max_dim && w <= max_dim
return img_matrix # No downsampling needed
end
aspect_ratio = w / h
if w > h
new_w = max_dim
new_h = round(Int, max_dim / aspect_ratio)
else
new_h = max_dim
new_w = round(Int, max_dim * aspect_ratio)
end
# imresize from Images.jl is perfect for this
return imresize(img_matrix, (new_h, new_w))
end
# 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
@ -46,6 +66,14 @@ function loadImgPlot(interfaceImg::String)
# Create the layout
layout=PlotlyBase.Layout(
title=PlotlyBase.attr(
text="",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=14,
color="black"
)
),
xaxis=PlotlyBase.attr(
visible=false,
scaleanchor="y",
@ -64,6 +92,7 @@ function loadImgPlot(interfaceImg::String)
x=X,
y=-Y,
name="",
hoverinfo="x+y",
showlegend=false,
colorscale="Viridis",
showscale=false,
@ -110,6 +139,14 @@ function loadImgPlot(interfaceImg::String, overlayImg::String, imgTrans::Float64
# Create the layout with overlay image
layoutImg = PlotlyBase.Layout(
title=PlotlyBase.attr(
text="",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=14,
color="black"
)
),
images = [attr(
source = "$(overlayImg)?t=$(timestamp)",
xref = "x",
@ -140,6 +177,7 @@ function loadImgPlot(interfaceImg::String, overlayImg::String, imgTrans::Float64
x = X,
y = -Y,
name = "",
hoverinfo = "x+y",
showlegend = false,
colorscale = "Viridis",
showscale = false
@ -168,6 +206,10 @@ function loadContourPlot(interfaceImg::String)
sigma=3.0
kernel=Kernel.gaussian(sigma)
elevation_smoothed=imfilter(elevation, kernel)
# --- DOWNSAMPLING FOR PERFORMANCE ---
elevation_smoothed = downsample_image(elevation_smoothed, 512)
# ---
# Create the X, Y meshgrid coordinates
x=1:size(elevation_smoothed, 2)
@ -182,7 +224,14 @@ function loadContourPlot(interfaceImg::String)
tickT = log_tick_formatter(collect(tickV))
layout=PlotlyBase.Layout(
title="2D topographic map of $cleaned_img",
title=PlotlyBase.attr(
text="2D topographic map of $cleaned_img (downsampled)",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
xaxis=PlotlyBase.attr(
visible=false,
scaleanchor="y"
@ -227,6 +276,10 @@ function loadSurfacePlot(interfaceImg::String)
sigma=3.0
kernel=Kernel.gaussian(sigma)
elevation_smoothed=imfilter(elevation, kernel)
# --- DOWNSAMPLING FOR PERFORMANCE ---
elevation_smoothed = downsample_image(elevation_smoothed, 256)
# ---
# Create the X, Y meshgrid coordinates
x=1:size(elevation_smoothed, 2)
@ -247,7 +300,14 @@ function loadSurfacePlot(interfaceImg::String)
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",
title=PlotlyBase.attr(
text="3D surface plot of $cleaned_img (downsampled)",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
scene=attr(
xaxis_nticks=x_nticks,
yaxis_nticks=y_nticks,
@ -377,8 +437,9 @@ function generate_colorbar_image(slice_data::AbstractMatrix, color_levels::Int,
# 3. Create and save the colorbar image
fig = Figure(size=(150, 250))
Colorbar(fig[1, 1],
colormap=cgrad(:viridis, bins),
limits=(min_val, max_val),
colormap=cgrad(:viridis, bins, categorical=true),
# limits=(min_val, max_val),
limits=(levels[1], levels[end]),
label=(scale == 1 ? "Intensity" : "Intensity ×10^$(round(Int, 3 * exponent))"),
ticks=(tick_positions, tick_labels),
labelsize=20,
@ -393,7 +454,14 @@ end
# its values in the spectrum plot
function meanSpectrumPlot(data::MSIData)
layout = PlotlyBase.Layout(
title="Average Spectrum Plot",
title=PlotlyBase.attr(
text="Average Spectrum Plot",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
hovermode="closest",
xaxis=PlotlyBase.attr(
title="<i>m/z</i>",
@ -434,7 +502,7 @@ function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int,
x = clamp(xCoord, 1, imgWidth)
y = clamp(yCoord, 1, imgHeight)
mz, intensity = GetSpectrum(data, x, y)
mz, intensity = GetSpectrum(data, Int(x), Int(y))
plot_title = "Spectrum at ($x, $y)"
else
# For non-imaging data, treat xCoord as the spectrum index
@ -445,7 +513,14 @@ function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int,
end
layout = PlotlyBase.Layout(
title=plot_title,
title=PlotlyBase.attr(
text=plot_title,
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
hovermode="closest",
xaxis=PlotlyBase.attr(
title="<i>m/z</i>",
@ -472,7 +547,14 @@ end
function sumSpectrumPlot(data::MSIData)
layout = PlotlyBase.Layout(
title="Total Spectrum Plot",
title=PlotlyBase.attr(
text="Total Spectrum Plot",
font=PlotlyBase.attr(
family="Roboto, Lato, sans-serif",
size=18,
color="black"
)
),
hovermode="closest",
xaxis=PlotlyBase.attr(
title="<i>m/z</i>",

View File

@ -1,11 +1,11 @@
#btnStyle {
background: #009f90;
color: rgb(229, 236, 246);
.btn-style {
background: #009f90 !important;
color: rgb(229, 236, 246) !important;
transition: background-color 0.3s ease;
}
#btnStyle:hover {
background-color: #007f72;
.btn-style:hover {
background-color: #007f72 !important;
}
#imgInt {

View File

@ -61,6 +61,8 @@ struct SpectrumAsset
axis_type::Symbol
end
@enum SpectrumMode CENTROID=1 PROFILE=2 UNKNOWN=3
"""
SpectrumMetadata
@ -69,6 +71,7 @@ Contains all metadata for a single spectrum, common to both imzML and mzML forma
# Fields
- `x`, `y`: The spatial coordinates of the spectrum (for imzML only).
- `id`: The unique identifier string for the spectrum (for mzML only).
- `mode`: The spectrum mode (`CENTROID` or `PROFILE`).
- `mz_asset`: A `SpectrumAsset` for the m/z array.
- `int_asset`: A `SpectrumAsset` for the intensity array.
"""
@ -80,6 +83,7 @@ struct SpectrumMetadata
# For mzML
id::String
mode::SpectrumMode
# Common binary data info
mz_asset::SpectrumAsset
int_asset::SpectrumAsset
@ -345,25 +349,28 @@ as they can use this cached data. This function modifies the `MSIData` object in
and is idempotent.
"""
function precompute_analytics(msi_data::MSIData)
# Idempotency check: If already computed, do nothing.
# Idempotency check
if msi_data.spectrum_stats_df !== nothing && hasproperty(msi_data.spectrum_stats_df, :MinMZ)
println("Analytics have already been pre-computed.")
return
end
"""
meta = msi_data.spectra_metadata[1]
println("First spectrum:")
println(" mz compressed: $(meta.mz_asset.is_compressed)")
println(" int compressed: $(meta.int_asset.is_compressed)")
println(" mz encoded_length: $(meta.mz_asset.encoded_length)")
println(" int encoded_length: $(meta.int_asset.encoded_length)")
println("Pre-computing analytics (single pass)...")
"""
start_time = time_ns()
num_spectra = length(msi_data.spectra_metadata)
# DEBUG: Check the first spectrum's metadata
if num_spectra > 0
first_meta = msi_data.spectra_metadata[1]
println("DEBUG First spectrum metadata:")
println(" mz_asset: format=$(first_meta.mz_asset.format), compressed=$(first_meta.mz_asset.is_compressed)")
println(" mz_asset: offset=$(first_meta.mz_asset.offset), encoded_length=$(first_meta.mz_asset.encoded_length)")
println(" int_asset: format=$(first_meta.int_asset.format), compressed=$(first_meta.int_asset.is_compressed)")
println(" int_asset: offset=$(first_meta.int_asset.offset), encoded_length=$(first_meta.int_asset.encoded_length)")
println(" mode: $(first_meta.mode)")
end
# Initialize variables for global stats
g_min_mz = Inf
g_max_mz = -Inf
@ -375,9 +382,21 @@ function precompute_analytics(msi_data::MSIData)
num_points = Vector{Int}(undef, num_spectra)
min_mzs = Vector{Float64}(undef, num_spectra)
max_mzs = Vector{Float64}(undef, num_spectra)
modes = Vector{SpectrumMode}(undef, num_spectra)
is_compressed = Vector{Bool}(undef, num_spectra)
# DEBUG: Add counter to see how many spectra have data
spectra_with_data = 0
empty_spectra = 0
_iterate_spectra_fast(msi_data) do idx, mz, intensity
# Store metadata
modes[idx] = msi_data.spectra_metadata[idx].mode
is_compressed[idx] = msi_data.spectra_metadata[idx].mz_asset.is_compressed ||
msi_data.spectra_metadata[idx].int_asset.is_compressed
if isempty(mz)
empty_spectra += 1
tics[idx] = 0.0
bpis[idx] = 0.0
bp_mzs[idx] = 0.0
@ -385,6 +404,8 @@ function precompute_analytics(msi_data::MSIData)
min_mzs[idx] = Inf
max_mzs[idx] = -Inf
return
else
spectra_with_data += 1
end
# Update global m/z range
@ -402,6 +423,27 @@ function precompute_analytics(msi_data::MSIData)
num_points[idx] = length(mz)
end
# Add mode statistics
centroid_count = count(==(CENTROID), modes)
profile_count = count(==(PROFILE), modes)
unknown_count = count(==(UNKNOWN), modes)
println("DEBUG Mode Statistics:")
println(" Centroid spectra: $centroid_count")
println(" Profile spectra: $profile_count")
println(" Unknown mode: $unknown_count")
# DEBUG: Print summary
println("DEBUG Analytics Summary:")
println(" Total spectra: $num_spectra")
println(" Spectra with data: $spectra_with_data")
println(" Empty spectra: $empty_spectra")
println(" Global m/z range: [$g_min_mz, $g_max_mz]")
println(" Centroid spectra: $(count(==(CENTROID), modes))")
println(" Profile spectra: $(count(==(PROFILE), modes))")
println(" Compressed spectra: $(sum(is_compressed))")
println(" Average points per spectrum: $(mean(num_points))")
# Populate the MSIData object
msi_data.global_min_mz = g_min_mz
msi_data.global_max_mz = g_max_mz
@ -412,7 +454,9 @@ function precompute_analytics(msi_data::MSIData)
BasePeakMZ = bp_mzs,
NumPoints = num_points,
MinMZ = min_mzs,
MaxMZ = max_mzs
MaxMZ = max_mzs,
Mode = modes,
IsCompressed = is_compressed
)
duration = (time_ns() - start_time) / 1e9
@ -731,12 +775,15 @@ function read_compressed_array(io::IO, asset::SpectrumAsset, format::Type)
seek(io, asset.offset)
if asset.is_compressed
# Read compressed bytes
# Read compressed bytes - use encoded_length as the number of compressed bytes
compressed_bytes = read(io, asset.encoded_length)
println("DEBUG: Decompressing data - offset=$(asset.offset), compressed_bytes=$(length(compressed_bytes))")
local decompressed_bytes
try
decompressed_bytes = Libz.inflate(compressed_bytes)
println("DEBUG: Decompression successful - decompressed_bytes=$(length(decompressed_bytes))")
catch e
@error "ZLIB DECOMPRESSION FAILED. This is likely due to an incorrect offset or corrupt data in the .ibd file."
@error "Asset offset: $(asset.offset), Encoded length: $(asset.encoded_length)"
@ -747,8 +794,7 @@ function read_compressed_array(io::IO, asset::SpectrumAsset, format::Type)
rethrow(e)
end
# Use an IOBuffer to safely read the data, avoiding reinterpret errors
# if the decompressed size is not a perfect multiple of the element size.
# Use an IOBuffer to safely read the data
bytes_io = IOBuffer(decompressed_bytes)
n_elements = bytes_io.size ÷ sizeof(format)
array = Array{format}(undef, n_elements)

View File

@ -20,6 +20,7 @@ mutable struct SpecDim
Packed::Bool
Axis::Int
Skip::Int
Mode::SpectrumMode
end
"""
@ -51,7 +52,7 @@ Retrieves an attribute's value from an XML tag string.
"""
function get_attribute(source::AbstractString, tag::String = "([^=]+)")
# Construct the regex pattern string
pattern_str = "\\s" * tag * "=\"([^\"]*)\""
pattern_str = "\\s*" * tag * "=\"([^\"]*)\""
regStr = Regex(pattern_str)
return match(regStr, source)
end
@ -70,17 +71,18 @@ compression status (`zlib`), and axis type (m/z vs. intensity).
- A `SpecDim` struct populated with the parsed configuration.
"""
function configure_spec_dim(stream)
axis = SpecDim(Float64, false, 1, 0)
offset = position(stream)
axis = SpecDim(Float64, false, 1, 0, UNKNOWN) # Add UNKNOWN as default mode
while !eof(stream)
currLine = readline(stream)
matchInfo = match(r"^\s*<(cvParam)", currLine)
if matchInfo === nothing
matchInfo = match(r"^\s*", currLine)
axis.Skip = position(stream) - offset - length(currLine) + length(matchInfo.match)
return axis
# Check for end of cvParam block
if match(r"^\s*</", currLine) !== nothing
return axis
end
continue
end
index = length(matchInfo.captures[1])
@ -98,10 +100,14 @@ function configure_spec_dim(stream)
axis.Format = Int64
elseif accession == "MS:1000574" # zlib compression
axis.Packed = true
elseif accession == "MS:1000127" # centroid spectrum
axis.Mode = CENTROID
elseif accession == "MS:1000128" # profile spectrum
axis.Mode = PROFILE
end
end
end
return axis # Should be unreachable if file is well-formed
return axis
end
# ============================================================================

View File

@ -26,16 +26,25 @@ Core Functions:
Determines the storage order of the m/z and intensity arrays.
"""
function axes_config_img(stream)
tag = find_tag(stream, r"^\s*<(referenceableParamGroup )")
value = get_attribute(tag.captures[1], "intensityArray")
order = 1 + (value !== nothing)
param_groups = Dict{String, SpecDim}()
find_tag(stream, r"<referenceableParamGroupList")
axis = Array{SpecDim,1}(undef, 2)
axis[order] = configure_spec_dim(stream)
while true
pos = position(stream)
line = readline(stream)
find_tag(stream, r"^\s*<(referenceableParamGroup )")
axis[xor(order, 3)] = configure_spec_dim(stream)
return axis
if eof(stream) || occursin("</referenceableParamGroupList>", line)
break
end
id_match = match(r"<referenceableParamGroup id=\"([^\"]+)\"", line)
if id_match !== nothing
id = id_match.captures[1]
spec_dim = configure_spec_dim(stream)
param_groups[id] = spec_dim
end
end
return param_groups
end
"""
@ -137,13 +146,57 @@ function get_spectrum_attributes(stream, hIbd)
return skip
end
function determine_parser(stream, mz_is_compressed, int_is_compressed)
start_pos = position(stream)
spectrum_xml = ""
try
# Find the start of the first spectrum tag
while !eof(stream)
line = readline(stream)
if occursin("<spectrum ", line)
spectrum_buffer = IOBuffer()
write(spectrum_buffer, line)
# Read until the end of the spectrum tag
while !eof(stream)
line = readline(stream)
write(spectrum_buffer, line)
if occursin("</spectrum>", line)
break
end
end
spectrum_xml = String(take!(spectrum_buffer))
break # Found the first spectrum, so we can stop
end
end
finally
seek(stream, start_pos) # Always reset stream position
end
"""
load_imzml_lazy(file_path::String; cache_size=100)
if isempty(spectrum_xml)
# Fallback based on compression flags if no spectrum tag found
return (mz_is_compressed || int_is_compressed) ? :compressed : :uncompressed
end
# Inspect the XML content
has_neofx_markers = occursin("encodedLength=\"0\"", spectrum_xml) &&
occursin("external encoded length", spectrum_xml)
has_external_data_markers = occursin("IMS:1000101", spectrum_xml) &&
occursin("IMS:1000102", spectrum_xml) &&
occursin("IMS:1000103", spectrum_xml)
if has_neofx_markers
return :neofx
end
if mz_is_compressed || int_is_compressed || has_external_data_markers
return :compressed
end
return :uncompressed
end
Main function to parse an `.imzML`/.ibd file pair and prepare for lazy loading.
It now returns a unified MSIData object.
"""
function load_imzml_lazy(file_path::String; cache_size=100)
println("DEBUG: Checking for .imzML file at $file_path")
if !isfile(file_path)
@ -162,71 +215,64 @@ function load_imzml_lazy(file_path::String; cache_size=100)
try
println("DEBUG: Configuring axes...")
axis = axes_config_img(stream)
param_groups = axes_config_img(stream)
println("DEBUG: Getting image dimensions...")
imgDim = get_img_dimensions(stream)
width, height, num_spectra = imgDim
println("DEBUG: Image dimensions: $(width)x$(height), $num_spectra spectra.")
mz_config_idx = findfirst(a -> a.Axis == 1, axis)
int_config_idx = findfirst(a -> a.Axis == 2, axis)
mz_format = axis[mz_config_idx].Format
intensity_format = axis[int_config_idx].Format
mz_is_compressed = axis[mz_config_idx].Packed
int_is_compressed = axis[int_config_idx].Packed
println("DEBUG: m/z format: $mz_format, Intensity format: $intensity_format")
println("DEBUG: m/z compressed: $mz_is_compressed, Intensity compressed: $int_is_compressed")
# Extract default formats from the parsed param_groups
mz_group = nothing
int_group = nothing
# --- NEW PARSING LOGIC based on the old, working code ---
println("DEBUG: Learning file structure from first spectrum...")
start_of_spectra_xml = position(stream)
attr = get_spectrum_attributes(stream, hIbd)
current_ibd_offset = position(hIbd)
seek(stream, start_of_spectra_xml)
println("DEBUG: Initial IBD offset: $current_ibd_offset")
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
mz_is_first = attr[3] == 3
println("DEBUG: Parsing metadata for $num_spectra spectra using skip-based method...")
for k in 1:num_spectra
# Use skip values learned from the first spectrum, assuming all are identical.
skip(stream, attr[5]) # Skip to X coordinate value
val_tag_x = find_tag(stream, r"value=\"(\d+)\"")
x = parse(Int32, val_tag_x.captures[1])
skip(stream, attr[6]) # Skip to Y coordinate value
val_tag_y = find_tag(stream, r"value=\"(\d+)\"")
y = parse(Int32, val_tag_y.captures[1])
skip(stream, attr[7]) # Skip to array length value
val_tag_len = find_tag(stream, r"value=\"(\d+)\"")
nPoints = parse(Int32, val_tag_len.captures[1])
mz_len_bytes = nPoints * sizeof(mz_format)
int_len_bytes = nPoints * sizeof(intensity_format)
local mz_offset, int_offset
if mz_is_first
mz_offset = current_ibd_offset
int_offset = mz_offset + mz_len_bytes
else
int_offset = current_ibd_offset
mz_offset = int_offset + int_len_bytes
for group in values(param_groups)
if group.Axis == 1
mz_group = group
elseif group.Axis == 2
int_group = group
end
# Create modern SpectrumAsset objects
mz_asset = SpectrumAsset(mz_format, mz_is_compressed, mz_offset, nPoints, :mz)
int_asset = SpectrumAsset(intensity_format, int_is_compressed, int_offset, nPoints, :intensity)
spectra_metadata[k] = SpectrumMetadata(x, y, "", mz_asset, int_asset)
# Advance the offset for the next spectrum's data.
current_ibd_offset += mz_len_bytes + int_len_bytes
skip(stream, attr[8]) # Skip to the end of the spectrum tag
end
# --- END OF NEW PARSING LOGIC ---
if mz_group === nothing || int_group === nothing
@warn "Could not find global definitions for m/z and intensity arrays. Using hardcoded defaults (Float64)."
default_mz_format = Float64
default_intensity_format = Float64
mz_is_compressed = false
int_is_compressed = false
global_mode = UNKNOWN
else
default_mz_format = mz_group.Format
default_intensity_format = int_group.Format
mz_is_compressed = mz_group.Packed
int_is_compressed = int_group.Packed
global_mode = mz_group.Mode != UNKNOWN ? mz_group.Mode : int_group.Mode
end
println("DEBUG: m/z format: $default_mz_format, Intensity format: $default_intensity_format")
println("DEBUG: m/z compressed: $mz_is_compressed, Intensity compressed: $int_is_compressed")
println("DEBUG: Global mode: $global_mode")
# --- Parser Selection ---
parser_type = determine_parser(stream, mz_is_compressed, int_is_compressed)
println("DEBUG: Selected parser: $parser_type")
local spectra_metadata
if parser_type == :neofx
println("DEBUG: Using neofx parser.")
spectra_metadata = parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
elseif parser_type == :compressed
println("DEBUG: Using compressed parser.")
spectra_metadata = parse_compressed(stream, hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
else # :uncompressed
println("DEBUG: Using uncompressed parser.")
spectra_metadata = parse_uncompressed(stream, hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
end
println("DEBUG: Metadata parsing complete.")
@ -234,6 +280,9 @@ function load_imzml_lazy(file_path::String; cache_size=100)
println("DEBUG: Building coordinate map...")
coordinate_map = zeros(Int, width, height)
for (idx, meta) in enumerate(spectra_metadata)
if idx == 1
println("DIAGNOSTIC_WRITE: For index 1, attempting to write to coordinate_map[$(meta.x), $(meta.y)]")
end
if 1 <= meta.x <= width && 1 <= meta.y <= height
coordinate_map[meta.x, meta.y] = idx
end
@ -242,7 +291,7 @@ function load_imzml_lazy(file_path::String; cache_size=100)
close(stream)
source = ImzMLSource(hIbd, mz_format, intensity_format)
source = ImzMLSource(hIbd, default_mz_format, default_intensity_format)
println("DEBUG: Creating MSIData object.")
return MSIData(source, spectra_metadata, (width, height), coordinate_map, cache_size)
@ -253,11 +302,342 @@ function load_imzml_lazy(file_path::String; cache_size=100)
end
end
function parse_uncompressed(stream, hIbd, param_groups, width, height, num_spectra,
mz_format, intensity_format, mz_is_compressed, int_is_compressed, global_mode)
# Your existing working skip-based parser
println("DEBUG: Learning file structure from first spectrum...")
start_of_spectra_xml = position(stream)
attr = get_spectrum_attributes(stream, hIbd)
current_ibd_offset = position(hIbd)
seek(stream, start_of_spectra_xml)
println("DEBUG: Initial IBD offset: $current_ibd_offset")
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
mz_is_first = attr[3] == 3
for k in 1:num_spectra
# Store the start position of this spectrum for mode detection
spectrum_start_pos = position(stream)
# Use skip values learned from the first spectrum
skip(stream, attr[5]) # Skip to X coordinate value
val_tag_x = find_tag(stream, r"value=\"(\d+)\"")
x = parse(Int32, val_tag_x.captures[1])
skip(stream, attr[6]) # Skip to Y coordinate value
val_tag_y = find_tag(stream, r"value=\"(\d+)\"")
y = parse(Int32, val_tag_y.captures[1])
skip(stream, attr[7]) # Skip to array length value
val_tag_len = find_tag(stream, r"value=\"(\d+)\"")
nPoints = parse(Int32, val_tag_len.captures[1])
# For uncompressed data, use simple calculation
mz_len_bytes = nPoints * sizeof(mz_format)
int_len_bytes = nPoints * sizeof(intensity_format)
local mz_offset, int_offset
if mz_is_first
mz_offset = current_ibd_offset
int_offset = mz_offset + mz_len_bytes
else
int_offset = current_ibd_offset
mz_offset = int_offset + int_len_bytes
end
# Mode detection from spectrum XML
current_pos = position(stream)
seek(stream, spectrum_start_pos)
spectrum_buffer = IOBuffer()
line = ""
while !eof(stream)
line = readline(stream)
write(spectrum_buffer, line)
if occursin("</spectrum>", line)
break
end
end
spectrum_xml = String(take!(spectrum_buffer))
spectrum_mode = global_mode
if occursin("MS:1000127", spectrum_xml)
spectrum_mode = CENTROID
elseif occursin("MS:1000128", spectrum_xml)
spectrum_mode = PROFILE
end
seek(stream, current_pos)
# Create SpectrumAsset objects
mz_asset = SpectrumAsset(mz_format, mz_is_compressed, mz_offset, nPoints, :mz)
int_asset = SpectrumAsset(intensity_format, int_is_compressed, int_offset, nPoints, :intensity)
spectra_metadata[k] = SpectrumMetadata(x, y, "", spectrum_mode, mz_asset, int_asset)
current_ibd_offset += mz_len_bytes + int_len_bytes
skip(stream, attr[8]) # Skip to the end of the spectrum tag
end
return spectra_metadata
end
function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
# New parser for compressed data
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
for k in 1:num_spectra
# Read the full spectrum XML block
spectrum_buffer = IOBuffer()
line = ""
while !eof(stream)
line = readline(stream)
if occursin("<spectrum ", line)
write(spectrum_buffer, line)
break
end
end
while !eof(stream)
line = readline(stream)
write(spectrum_buffer, line)
if occursin("</spectrum>", line)
break
end
end
spectrum_xml = String(take!(spectrum_buffer))
# Parse coordinates
x_match = match(r"IMS:1000050.*?value=\"(\d+)\"", spectrum_xml)
y_match = match(r"IMS:1000051.*?value=\"(\d+)\"", spectrum_xml)
x = x_match !== nothing ? parse(Int32, x_match.captures[1]) : Int32(0)
y = y_match !== nothing ? parse(Int32, y_match.captures[1]) : Int32(0)
# Parse mode
spectrum_mode = global_mode
if occursin("MS:1000127", spectrum_xml)
spectrum_mode = CENTROID
elseif occursin("MS:1000128", spectrum_xml)
spectrum_mode = PROFILE
end
# Parse binary data arrays
array_data = []
# Find all binaryDataArray blocks
array_matches = eachmatch(r"<binaryDataArray.*?<\/binaryDataArray>"s, spectrum_xml)
for array_match in array_matches
array_xml = array_match.match
# Determine if this is m/z or intensity array
is_mz = occursin("MS:1000514", array_xml) || occursin("mzArray", array_xml)
# Parse external data parameters
# Get array_length (nPoints)
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", array_xml)
array_length = 0
if array_len_cv_match !== nothing
array_length = parse(Int32, array_len_cv_match.captures[1])
end
if array_length == 0
nPoints_match = match(r"defaultArrayLength=\"(\d+)\"", spectrum_xml)
if nPoints_match !== nothing
array_length = parse(Int32, nPoints_match.captures[1])
end
end
# Get encoded_length
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", array_xml)
encoded_length = 0
if encoded_len_cv_match !== nothing
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
else
encoded_len_attr_match = match(r"encodedLength=\"(\d+)\"", array_xml)
if encoded_len_attr_match !== nothing
encoded_length = parse(Int64, encoded_len_attr_match.captures[1])
end
end
# Get offset
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", array_xml)
offset = 0
if offset_match !== nothing
offset = parse(Int64, offset_match.captures[1])
end
if array_length > 0 && offset > 0
push!(array_data, (
is_mz = is_mz,
array_length = array_length,
encoded_length = encoded_length,
offset = offset
))
end
end
# Separate m/z and intensity arrays
mz_data = filter(d -> d.is_mz, array_data)
int_data = filter(d -> !d.is_mz, array_data)
if length(mz_data) != 1 || length(int_data) != 1
error("Spectrum $k: Expected exactly one m/z and one intensity array")
end
mz_info = mz_data[1]
int_info = int_data[1]
# DEBUG: Print first spectrum details
if k == 1
println("DEBUG First spectrum parsed:")
println(" Coordinates: x=$x, y=$y")
println(" Mode: $spectrum_mode")
println(" m/z array: array_length=$(mz_info.array_length), encoded_length=$(mz_info.encoded_length), offset=$(mz_info.offset)")
println(" intensity array: array_length=$(int_info.array_length), encoded_length=$(int_info.encoded_length), offset=$(int_info.offset)")
println(" Expected m/z bytes: $(mz_info.array_length * sizeof(default_mz_format))")
println(" Expected intensity bytes: $(int_info.array_length * sizeof(default_intensity_format))")
end
# Create SpectrumAsset objects
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, mz_info.offset,
mz_is_compressed ? mz_info.encoded_length : mz_info.array_length, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, int_info.offset,
int_is_compressed ? int_info.encoded_length : int_info.array_length, :intensity)
spectra_metadata[k] = SpectrumMetadata(x, y, "", spectrum_mode, mz_asset, int_asset)
end
return spectra_metadata
end
function parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
default_mz_format, default_intensity_format,
mz_is_compressed, int_is_compressed, global_mode)
# New parser for compressed data
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
for k in 1:num_spectra
# Read the full spectrum XML block
spectrum_buffer = IOBuffer()
line = ""
while !eof(stream)
line = readline(stream)
if occursin("<spectrum ", line)
write(spectrum_buffer, line)
break
end
end
while !eof(stream)
line = readline(stream)
write(spectrum_buffer, line)
if occursin("</spectrum>", line)
break
end
end
spectrum_xml = String(take!(spectrum_buffer))
# Parse coordinates
x_match = match(r"IMS:1000050.*?value=\"(\d+)\"", spectrum_xml)
y_match = match(r"IMS:1000051.*?value=\"(\d+)\"", spectrum_xml)
x = x_match !== nothing ? parse(Int32, x_match.captures[1]) : Int32(0)
y = y_match !== nothing ? parse(Int32, y_match.captures[1]) : Int32(0)
# Parse mode
spectrum_mode = global_mode
if occursin("MS:1000127", spectrum_xml)
spectrum_mode = CENTROID
elseif occursin("MS:1000128", spectrum_xml)
spectrum_mode = PROFILE
end
# Parse binary data arrays
array_data = []
# Find all binaryDataArray blocks
array_matches = eachmatch(r"<binaryDataArray.*?<\/binaryDataArray>"s, spectrum_xml)
for array_match in array_matches
array_xml = array_match.match
# Determine if this is m/z or intensity array
is_mz = occursin("MS:1000514", array_xml) || occursin("mzArray", array_xml)
# Parse external data parameters
# Get array_length (nPoints)
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", array_xml)
array_length = 0
if array_len_cv_match !== nothing
array_length = parse(Int32, array_len_cv_match.captures[1])
end
if array_length == 0
nPoints_match = match(r"defaultArrayLength=\"(\d+)\"", spectrum_xml)
if nPoints_match !== nothing
array_length = parse(Int32, nPoints_match.captures[1])
end
end
# Get encoded_length
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", array_xml)
encoded_length = 0
if encoded_len_cv_match !== nothing
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
else
encoded_len_attr_match = match(r"encodedLength=\"(\d+)\"", array_xml)
if encoded_len_attr_match !== nothing
encoded_length = parse(Int64, encoded_len_attr_match.captures[1])
end
end
# Get offset
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", array_xml)
offset = 0
if offset_match !== nothing
offset = parse(Int64, offset_match.captures[1])
end
if array_length > 0 && offset > 0
push!(array_data, (
is_mz = is_mz,
array_length = array_length,
encoded_length = encoded_length,
offset = offset
))
end
end
# Separate m/z and intensity arrays
mz_data = filter(d -> d.is_mz, array_data)
int_data = filter(d -> !d.is_mz, array_data)
if length(mz_data) != 1 || length(int_data) != 1
error("Spectrum $k: Expected exactly one m/z and one intensity array")
end
mz_info = mz_data[1]
int_info = int_data[1]
# DEBUG: Print first spectrum details
if k == 1
println("DEBUG First spectrum parsed:")
println(" Coordinates: x=$x, y=$y")
println(" Mode: $spectrum_mode")
println(" m/z array: array_length=$(mz_info.array_length), encoded_length=$(mz_info.encoded_length), offset=$(mz_info.offset)")
println(" intensity array: array_length=$(int_info.array_length), encoded_length=$(int_info.encoded_length), offset=$(int_info.offset)")
println(" Expected m/z bytes: $(mz_info.array_length * sizeof(default_mz_format))")
println(" Expected intensity bytes: $(int_info.array_length * sizeof(default_intensity_format))")
end
# Create SpectrumAsset objects
mz_asset = SpectrumAsset(default_mz_format, mz_is_compressed, mz_info.offset,
mz_is_compressed ? mz_info.encoded_length : mz_info.array_length, :mz)
int_asset = SpectrumAsset(default_intensity_format, int_is_compressed, int_info.offset,
int_is_compressed ? int_info.encoded_length : int_info.array_length, :intensity)
spectra_metadata[k] = SpectrumMetadata(x, y, "", spectrum_mode, mz_asset, int_asset)
end
return spectra_metadata
end
# --- End of content from imzML.jl ---
# --- Start of content from Imaging_Normalization.jl ---
# =============================================================================
#
# Image Slice Extraction
@ -446,7 +826,8 @@ function plot_slice(msi_data::MSIData, mass::Real, tolerance::Real, output_dir::
fig = Figure(size = (600, 500))
ax = CairoMakie.Axis(fig[1, 1],
aspect=DataAspect(),
title=@sprintf("Slice for m/z: %.2f", mass)
title=@sprintf("Slice for m/z: %.2f", mass),
yreversed=true
)
hidedecorations!(ax)
@ -985,4 +1366,4 @@ function generate_palette(colorscheme, n_colors=256)
return palette
end
const ViridisPalette = generate_palette(ColorSchemes.viridis)
const ViridisPalette = generate_palette(ColorSchemes.viridis)

View File

@ -52,15 +52,19 @@ const CONVERSION_TARGET_IMZML = "test/results/converted_mzml.imzML"
# --- Test Case 3: Standard .imzML file ---
# An existing imzML file (can be the one generated from Case 2).
const TEST_IMZML_FILE = CONVERSION_TARGET_IMZML # The output from case 2
# const TEST_IMZML_FILE = CONVERSION_TARGET_IMZML # The output from case 2
# const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/imzML_AP_SMALDI/HR2MSImouseurinarybladderS096.imzML"
# const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/Imaging_paper_spray/Imaging_paper_spray.imzML"
# const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/Imaging_paper_spray/Imaging_paper_spray.imzML" #profile
# const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/Imaging prueba Roya 1/royaimg.imzML"
# const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/salida/ltpmsi-chilli.imzML" # centroid aparently?
# const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/salida/Stomach_DHB_compressed.imzML" # centroid compressed
const TEST_IMZML_FILE = "/home/pixel/Documents/Cinvestav_2025/Analisis/salida/Stomach_DHB_uncompressed.imzML" # centroid
# The m/z value to use for creating an image slice.
# const MZ_VALUE_FOR_SLICE = 309.06 # BF
# const MZ_VALUE_FOR_SLICE = 896.0 # HR2MSI
# const MZ_VALUE_FOR_SLICE = 76.03 # I PS
const MZ_VALUE_FOR_SLICE = 313 # ROYA
# const MZ_VALUE_FOR_SLICE = 313 # ROYA
const MZ_VALUE_FOR_SLICE = 100 # advanced processing
# const MZ_TOLERANCE = 0.1
# const MZ_TOLERANCE = 1
const MZ_TOLERANCE = 0.1
@ -71,8 +75,8 @@ const COORDS_TO_PLOT = (50, 50) # Example coordinates (X, Y)
# --- Output Directory ---
const RESULTS_DIR = "test/results"
test1 = true
test2 = true
test1 = false
test2 = false
test3 = true
# ===================================================================
@ -135,6 +139,30 @@ function validate_msi_data(filepath::String)
end
end
function debug_xml_parsing(file_path::String)
println("=== DEBUG XML PARSING ===")
stream = open(file_path, "r")
# Find and print the first spectrum
while !eof(stream)
line = readline(stream)
if occursin("<spectrum", line)
println("FOUND FIRST SPECTRUM:")
spectrum_xml = line
# Read until end of spectrum
while !eof(stream) && !occursin("</spectrum>", line)
line = readline(stream)
spectrum_xml *= line
end
println("SPECTRUM XML:")
println(spectrum_xml)
break
end
end
close(stream)
println("=== END DEBUG ===")
end
# ===================================================================
# TEST RUNNER
@ -229,16 +257,24 @@ function run_test()
# Also run tests for plotting spectrum and image slice
if isfile(TEST_IMZML_FILE)
debug_xml_parsing(TEST_IMZML_FILE)
msi_data = @time OpenMSIData(TEST_IMZML_FILE)
precompute_analytics(msi_data)
# Add spectrum plotting for imzML to match Test Case 1
try
# Get the msi data from the imzml
println("Plotting a sample spectrum from $TEST_IMZML_FILE...")
msi_data = @time OpenMSIData(TEST_IMZML_FILE)
x_coord, y_coord = COORDS_TO_PLOT
# Use coordinates from the first spectrum in the metadata
first_spectrum_meta = msi_data.spectra_metadata[1]
x_coord = first_spectrum_meta.x
y_coord = first_spectrum_meta.y
println("DIAGNOSTIC_READ: Reading from coordinate_map[$x_coord, $y_coord]. Value is $(msi_data.coordinate_map[x_coord, y_coord]).")
# Get the x y coordinate spectrum data
mz, intensity = GetSpectrum(msi_data, x_coord, y_coord)
mz, intensity = GetSpectrum(msi_data, Int(x_coord), Int(y_coord))
# Plot the data
fig = Figure(size = (800, 600))
ax = Axis(fig[1, 1], xlabel="m/z", ylabel="Intensity", title="Spectrum at ($x_coord, $y_coord) from $(basename(TEST_IMZML_FILE))")
@ -276,7 +312,7 @@ function run_test()
output_path = joinpath(RESULTS_DIR, "test_imzml_average_spectrum.png")
save(output_path, fig)
println("SUCCESS: Total spectrum plot saved to $output_path")
println("No spectrums tested on this try.")
# println("No spectrums tested on this try.")
catch e
println("ERROR during spectrum plotting in Test Case 3: $e")
end
@ -284,8 +320,13 @@ function run_test()
# Test the plot_slice function
try
println("Testing plot_slice function on $TEST_IMZML_FILE...")
msi_data = @time OpenMSIData(TEST_IMZML_FILE)
@time plot_slice(msi_data, MZ_VALUE_FOR_SLICE, MZ_TOLERANCE, RESULTS_DIR, stage_name="test_imzml_single_slice")
# Use the base peak m/z from the first spectrum for the slice
# Or a more general approach: use the global max intensity m/z
# For now, let's use the base peak m/z of the first spectrum
slice_mz_value = msi_data.spectrum_stats_df.BasePeakMZ[1]
@time plot_slice(msi_data, slice_mz_value, MZ_TOLERANCE, RESULTS_DIR, stage_name="test_imzml_single_slice")
# The success message is now inside plot_slice
catch e
println("ERROR during plot_slice test in Test Case 3: $e")