altered imzML parser to be compatible with timsconverter, small improvements on the UI, other small bug fixes done
This commit is contained in:
parent
f35acd1d0f
commit
2fd2b90513
@ -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.
|
||||
|
||||
|
||||
141
app.jl
141
app.jl
@ -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,6 +415,43 @@ 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)
|
||||
|
||||
# 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, "_" => ".")) (No data)"
|
||||
colorbarT = "" # Clear colorbar
|
||||
current_col_triq = ""
|
||||
triq_bmp = sort(filter(filename -> startswith(filename, "TrIQ_") && endswith(filename, ".bmp"), readdir("public")), lt=natural)
|
||||
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
|
||||
fTime = time()
|
||||
eTime = round(fTime - sTime, digits=3)
|
||||
# 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())
|
||||
|
||||
@ -399,7 +465,7 @@ include("./julia_imzML_visual.jl")
|
||||
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)
|
||||
|
||||
@ -428,7 +494,7 @@ include("./julia_imzML_visual.jl")
|
||||
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)
|
||||
|
||||
@ -450,6 +516,7 @@ include("./julia_imzML_visual.jl")
|
||||
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"
|
||||
warning_msg = true
|
||||
@ -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
|
||||
=#
|
||||
|
||||
105
app.jl.html
105
app.jl.html
@ -1,4 +1,3 @@
|
||||
<template>
|
||||
<header id="header">
|
||||
<img src="/css/LABI_logo.png" alt="Labi Logo Icon" id="imgLogo">
|
||||
<div>
|
||||
@ -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>
|
||||
@ -412,5 +411,3 @@
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
</template>
|
||||
@ -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
|
||||
@ -169,6 +207,10 @@ function loadContourPlot(interfaceImg::String)
|
||||
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)
|
||||
y=1:size(elevation_smoothed, 1)
|
||||
@ -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"
|
||||
@ -228,6 +277,10 @@ function loadSurfacePlot(interfaceImg::String)
|
||||
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)
|
||||
y=1:size(elevation_smoothed, 1)
|
||||
@ -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>",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,18 +71,19 @@ 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)
|
||||
# Check for end of cvParam block
|
||||
if match(r"^\s*</", currLine) !== nothing
|
||||
return axis
|
||||
end
|
||||
continue
|
||||
end
|
||||
|
||||
index = length(matchInfo.captures[1])
|
||||
attr_match = get_attribute(currLine[index:end], "accession")
|
||||
@ -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
|
||||
|
||||
# ============================================================================
|
||||
|
||||
487
src/imzML.jl
487
src/imzML.jl
@ -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 = ""
|
||||
|
||||
"""
|
||||
load_imzml_lazy(file_path::String; cache_size=100)
|
||||
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
|
||||
|
||||
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,22 +215,96 @@ 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 ---
|
||||
for group in values(param_groups)
|
||||
if group.Axis == 1
|
||||
mz_group = group
|
||||
elseif group.Axis == 2
|
||||
int_group = group
|
||||
end
|
||||
end
|
||||
|
||||
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.")
|
||||
|
||||
# Build coordinate map for imzML files
|
||||
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
|
||||
end
|
||||
println("DEBUG: Coordinate map built.")
|
||||
|
||||
close(stream)
|
||||
|
||||
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)
|
||||
|
||||
catch e
|
||||
close(stream)
|
||||
close(hIbd)
|
||||
rethrow(e)
|
||||
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)
|
||||
@ -188,9 +315,11 @@ function load_imzml_lazy(file_path::String; cache_size=100)
|
||||
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.
|
||||
# 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])
|
||||
@ -203,6 +332,7 @@ function load_imzml_lazy(file_path::String; cache_size=100)
|
||||
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)
|
||||
|
||||
@ -215,49 +345,299 @@ function load_imzml_lazy(file_path::String; cache_size=100)
|
||||
mz_offset = int_offset + int_len_bytes
|
||||
end
|
||||
|
||||
# Create modern SpectrumAsset objects
|
||||
# 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, "", mz_asset, int_asset)
|
||||
spectra_metadata[k] = SpectrumMetadata(x, y, "", spectrum_mode, 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 ---
|
||||
|
||||
println("DEBUG: Metadata parsing complete.")
|
||||
return spectra_metadata
|
||||
end
|
||||
|
||||
# Build coordinate map for imzML files
|
||||
println("DEBUG: Building coordinate map...")
|
||||
coordinate_map = zeros(Int, width, height)
|
||||
for (idx, meta) in enumerate(spectra_metadata)
|
||||
if 1 <= meta.x <= width && 1 <= meta.y <= height
|
||||
coordinate_map[meta.x, meta.y] = idx
|
||||
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
|
||||
println("DEBUG: Coordinate map built.")
|
||||
|
||||
close(stream)
|
||||
|
||||
source = ImzMLSource(hIbd, mz_format, intensity_format)
|
||||
println("DEBUG: Creating MSIData object.")
|
||||
return MSIData(source, spectra_metadata, (width, height), coordinate_map, cache_size)
|
||||
|
||||
catch e
|
||||
close(stream)
|
||||
close(hIbd)
|
||||
rethrow(e)
|
||||
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)
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user