Implemented imzml and mzml lazy loading, imzml to load spectra matrix, working on mzml to imzml
This commit is contained in:
parent
710c74a6c4
commit
b1d7838a60
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ public/css/imgOver.png
|
|||||||
!public/css/autogenerated.css
|
!public/css/autogenerated.css
|
||||||
!public/css/LABI_logo.png
|
!public/css/LABI_logo.png
|
||||||
log/*
|
log/*
|
||||||
|
R_original_scripts/
|
||||||
|
|||||||
33
app.jl
33
app.jl
@ -6,7 +6,7 @@ using Libz
|
|||||||
using PlotlyBase
|
using PlotlyBase
|
||||||
using CairoMakie
|
using CairoMakie
|
||||||
using Colors
|
using Colors
|
||||||
using julia_mzML_imzML
|
# using julia_mzML_imzML
|
||||||
using Statistics
|
using Statistics
|
||||||
using NaturalSort
|
using NaturalSort
|
||||||
using Images
|
using Images
|
||||||
@ -15,6 +15,7 @@ using NativeFileDialog # Opens the file explorer depending on the OS
|
|||||||
using StipplePlotly
|
using StipplePlotly
|
||||||
using Base.Filesystem: mv # To rename files in the system
|
using Base.Filesystem: mv # To rename files in the system
|
||||||
include("./julia_imzML_visual.jl")
|
include("./julia_imzML_visual.jl")
|
||||||
|
include("./src/imzML.jl")
|
||||||
@genietools
|
@genietools
|
||||||
|
|
||||||
# == Reactive code ==
|
# == Reactive code ==
|
||||||
@ -151,7 +152,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
),
|
),
|
||||||
margin=attr(l=0,r=0,t=0,b=0,pad=0)
|
margin=attr(l=0,r=0,t=0,b=0,pad=0)
|
||||||
)
|
)
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
@out plotdataImg=[traceImg]
|
@out plotdataImg=[traceImg]
|
||||||
@out plotlayoutImg=layoutImg
|
@out plotlayoutImg=layoutImg
|
||||||
# For the image in the comparative view
|
# For the image in the comparative view
|
||||||
@ -180,14 +181,14 @@ include("./julia_imzML_visual.jl")
|
|||||||
margin=attr(l=0,r=0,t=120,b=0,pad=0)
|
margin=attr(l=0,r=0,t=120,b=0,pad=0)
|
||||||
)
|
)
|
||||||
# Dummy 2D scatter plot
|
# Dummy 2D scatter plot
|
||||||
traceSpectra=PlotlyBase.scatter(x=[], y=[], mode="lines")
|
traceSpectra=PlotlyBase.stem(x=Vector{Float64}(), y=Vector{Float64}(),marker=attr(size=1, color="blue", opacity=0.1))
|
||||||
# Create conection to frontend
|
# Create conection to frontend
|
||||||
@out plotdata=[traceSpectra]
|
@out plotdata=[traceSpectra]
|
||||||
@out plotlayout=layoutSpectra
|
@out plotlayout=layoutSpectra
|
||||||
@in xCoord=0
|
@in xCoord=0
|
||||||
@in yCoord=0
|
@in yCoord=0
|
||||||
@out xSpectraMz=Float64[]
|
@out xSpectraMz = Vector{Float64}()
|
||||||
@out ySpectraMz=Float64[]
|
@out ySpectraMz = Vector{Float64}()
|
||||||
|
|
||||||
# Interactive plot reactions
|
# Interactive plot reactions
|
||||||
@in data_click=Dict{String,Any}()
|
@in data_click=Dict{String,Any}()
|
||||||
@ -207,7 +208,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
margin=attr(l=0,r=0,t=100,b=0,pad=0)
|
margin=attr(l=0,r=0,t=100,b=0,pad=0)
|
||||||
)
|
)
|
||||||
# Dummy 2D surface plot
|
# Dummy 2D surface plot
|
||||||
traceContour=PlotlyBase.scatter(x=[], y=[], mode="lines")
|
traceContour=PlotlyBase.scatter(x=Vector{Float64}(), y=Vector{Float64}(), mode="lines")
|
||||||
# Create conection to frontend
|
# Create conection to frontend
|
||||||
@out plotdataC=[traceContour]
|
@out plotdataC=[traceContour]
|
||||||
@out plotlayoutC=layoutContour
|
@out plotlayoutC=layoutContour
|
||||||
@ -233,7 +234,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
x=1:10
|
x=1:10
|
||||||
y=1:10
|
y=1:10
|
||||||
z=[sin(i * j / 10) for i in x, j in y]
|
z=[sin(i * j / 10) for i in x, j in y]
|
||||||
trace3D=PlotlyBase.surface(x=[], y=[], z=[],
|
trace3D=PlotlyBase.surface(x=Vector{Float64}(), y=Vector{Float64}(), z=Matrix{Float64}(undef, 0, 0),
|
||||||
contours_z=attr(
|
contours_z=attr(
|
||||||
show=true,
|
show=true,
|
||||||
usecolormap=true,
|
usecolormap=true,
|
||||||
@ -362,7 +363,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
if isfile(full_route) && Nmass > 0 && Tol > 0 && Tol <=1 && colorLevel > 1 && colorLevel < 257
|
if isfile(full_route) && Nmass > 0 && Tol > 0 && Tol <=1 && colorLevel > 1 && colorLevel < 257
|
||||||
msg="File exists, Nmass=$(Nmass) Tol=$(Tol). Loading file will begin, please be patient."
|
msg="File exists, Nmass=$(Nmass) Tol=$(Tol). Loading file will begin, please be patient."
|
||||||
try
|
try
|
||||||
spectra=LoadImzml(full_route)
|
spectra=load_imzml(full_route)
|
||||||
msg="File loaded. Creating spectra with the specific mass and tolerance, please be patient."
|
msg="File loaded. Creating spectra with the specific mass and tolerance, please be patient."
|
||||||
slice=GetMzSliceJl(spectra,Nmass,Tol)
|
slice=GetMzSliceJl(spectra,Nmass,Tol)
|
||||||
fig=CairoMakie.Figure(size=(150, 250)) # Container
|
fig=CairoMakie.Figure(size=(150, 250)) # Container
|
||||||
@ -588,7 +589,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt)
|
plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImg=[traceImg]
|
plotdataImg=[traceImg]
|
||||||
msgimg=""
|
msgimg=""
|
||||||
end
|
end
|
||||||
@ -615,7 +616,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt)
|
plotdataImg, plotlayoutImg, imgWidth, imgHeight=loadImgPlot(imgInt)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImg=[traceImg]
|
plotdataImg=[traceImg]
|
||||||
msgimg=""
|
msgimg=""
|
||||||
end
|
end
|
||||||
@ -643,7 +644,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT)
|
plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImgT=[traceImg]
|
plotdataImgT=[traceImg]
|
||||||
msgtriq=""
|
msgtriq=""
|
||||||
end
|
end
|
||||||
@ -670,7 +671,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT)
|
plotdataImgT, plotlayoutImgT, imgWidth, imgHeight=loadImgPlot(imgIntT)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImgT=[traceImg]
|
plotdataImgT=[traceImg]
|
||||||
msgtriq=""
|
msgtriq=""
|
||||||
end
|
end
|
||||||
@ -699,7 +700,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImgComp, plotlayoutImgComp, _, _=loadImgPlot(imgIntComp)
|
plotdataImgComp, plotlayoutImgComp, _, _=loadImgPlot(imgIntComp)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImgComp=[traceImg]
|
plotdataImgComp=[traceImg]
|
||||||
msgimgComp=""
|
msgimgComp=""
|
||||||
end
|
end
|
||||||
@ -727,7 +728,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImgComp, plotlayoutImgComp, _, _=loadImgPlot(imgIntComp)
|
plotdataImgComp, plotlayoutImgComp, _, _=loadImgPlot(imgIntComp)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImgComp=[traceImg]
|
plotdataImgComp=[traceImg]
|
||||||
msgimgComp=""
|
msgimgComp=""
|
||||||
end
|
end
|
||||||
@ -755,7 +756,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImgTComp, plotlayoutImgTComp, _, _=loadImgPlot(imgIntTComp)
|
plotdataImgTComp, plotlayoutImgTComp, _, _=loadImgPlot(imgIntTComp)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImgTComp=[traceImg]
|
plotdataImgTComp=[traceImg]
|
||||||
msgtriqComp=""
|
msgtriqComp=""
|
||||||
end
|
end
|
||||||
@ -783,7 +784,7 @@ include("./julia_imzML_visual.jl")
|
|||||||
plotdataImgTComp, plotlayoutImgTComp, _, _=loadImgPlot(imgIntTComp)
|
plotdataImgTComp, plotlayoutImgTComp, _, _=loadImgPlot(imgIntTComp)
|
||||||
btnOpticalDisable=false
|
btnOpticalDisable=false
|
||||||
else
|
else
|
||||||
traceImg=PlotlyBase.heatmap(x=[], y=[])
|
traceImg=PlotlyBase.heatmap(x=Vector{Float64}(), y=Vector{Float64}())
|
||||||
plotdataImgTComp=[traceImg]
|
plotdataImgTComp=[traceImg]
|
||||||
msgtriqComp=""
|
msgtriqComp=""
|
||||||
end
|
end
|
||||||
|
|||||||
3
config/env/global.jl
vendored
3
config/env/global.jl
vendored
@ -1 +1,2 @@
|
|||||||
ENV["GENIE_ENV"] = "prod"
|
ENV["GENIE_ENV"] = "dev"
|
||||||
|
#ENV["GENIE_ENV"] = "prod"
|
||||||
|
|||||||
@ -473,7 +473,7 @@ function meanSpectrumPlot(mzmlRoute::String)
|
|||||||
xSpectraMz=spectraMz[1,1]
|
xSpectraMz=spectraMz[1,1]
|
||||||
ySpectraMz=spectraMz[2,1]
|
ySpectraMz=spectraMz[2,1]
|
||||||
end
|
end
|
||||||
trace=PlotlyBase.scatter(x=xSpectraMz, y=ySpectraMz, mode="lines")
|
trace=PlotlyBase.stem(x=xSpectraMz, y=ySpectraMz,marker=attr(size=1, color="blue", opacity=0.1))
|
||||||
plotdata=[trace] # We add the data from spectra to the plot
|
plotdata=[trace] # We add the data from spectra to the plot
|
||||||
plotlayout=layout
|
plotlayout=layout
|
||||||
return plotdata, plotlayout, xSpectraMz, ySpectraMz
|
return plotdata, plotlayout, xSpectraMz, ySpectraMz
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
PENDING
|
|
||||||
Rmsi & julia coherence with image creation and colorbar
|
|
||||||
colorbar revamp CURRENTLY ONGOING
|
|
||||||
Create function that makes the imzML from mzML files ?
|
|
||||||
Possibility to add multiple imzML to process at once ?
|
|
||||||
Add comparison image (rotate, transform, translate, transparency) CURRENTLY ONGOING
|
|
||||||
|
|
||||||
DONE
|
|
||||||
Quicker start for julia GUI
|
|
||||||
Better UI
|
|
||||||
refined style of page
|
|
||||||
improved responsiveness
|
|
||||||
UI functionality added
|
|
||||||
Code cleanse
|
|
||||||
better code readability
|
|
||||||
Better blind search for images
|
|
||||||
3d topology plots for images
|
|
||||||
2d contour plots for images
|
|
||||||
Image flip upside down
|
|
||||||
Measure for the time it takes creating image or plot
|
|
||||||
Rotate vertical images
|
|
||||||
Easier input for files
|
|
||||||
TrIQ default values
|
|
||||||
Comparative for two views
|
|
||||||
Even faster initial boot and subsectuential boot
|
|
||||||
Multiple spectra plot types
|
|
||||||
Plot creation for spectra Per pixel of image
|
|
||||||
23
scripts/build.jl
Normal file
23
scripts/build.jl
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# scripts/build.jl
|
||||||
|
using PackageCompiler
|
||||||
|
using Pkg
|
||||||
|
|
||||||
|
project_dir = dirname(@__DIR__)
|
||||||
|
Pkg.activate(project_dir)
|
||||||
|
|
||||||
|
# Get all the direct dependencies from Project.toml
|
||||||
|
deps = keys(Pkg.project().dependencies)
|
||||||
|
sysimage_path = joinpath(project_dir, "build", "JuliaMSI_sysimage.so")
|
||||||
|
precompile_script = joinpath(project_dir, "scripts", "precompile.jl")
|
||||||
|
|
||||||
|
@info "Starting system image compilation. This may take several minutes..."
|
||||||
|
|
||||||
|
create_sysimage(
|
||||||
|
deps;
|
||||||
|
sysimage_path=sysimage_path,
|
||||||
|
precompile_execution_file=precompile_script
|
||||||
|
)
|
||||||
|
|
||||||
|
@info "Compilation finished!"
|
||||||
|
@info "You can now run the fast-starting application using the following command:"
|
||||||
|
@info "julia --project=. -J build/JuliaMSI_sysimage.so start_MSI_GUI.jl"
|
||||||
45
scripts/precompile.jl
Normal file
45
scripts/precompile.jl
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# scripts/precompile.jl
|
||||||
|
using Pkg
|
||||||
|
Pkg.activate(dirname(@__DIR__))
|
||||||
|
|
||||||
|
using Genie
|
||||||
|
using HTTP
|
||||||
|
|
||||||
|
@info "Precompiling application..."
|
||||||
|
|
||||||
|
# Load the app. Mmap=false is recommended for compilation
|
||||||
|
Genie.loadapp(pwd(); Mmap=false)
|
||||||
|
|
||||||
|
@info "Starting server for precompilation."
|
||||||
|
# Start the server in the background
|
||||||
|
server = up(1481, "127.0.0.1", async=true)
|
||||||
|
|
||||||
|
# Give the server a moment to start up
|
||||||
|
sleep(20)
|
||||||
|
|
||||||
|
base_url = "http://127.0.0.1:1481"
|
||||||
|
@info "Hitting routes to precompile..."
|
||||||
|
|
||||||
|
try
|
||||||
|
# Make a GET request to the home page
|
||||||
|
response = HTTP.get(base_url * "/")
|
||||||
|
@info "GET / -> Status: $(response.status)"
|
||||||
|
|
||||||
|
# === IMPORTANT ===
|
||||||
|
# For best performance, you should add more HTTP requests here
|
||||||
|
# to hit ALL your important routes and API endpoints.
|
||||||
|
# This ensures the code for every page gets compiled.
|
||||||
|
# Example:
|
||||||
|
# HTTP.get(base_url * "/contact")
|
||||||
|
# HTTP.post(base_url * "/submit-data", [], "{\"key\":\"value\"}")
|
||||||
|
|
||||||
|
catch e
|
||||||
|
@error "Could not hit routes during precompilation." exception=(e, catch_backtrace())
|
||||||
|
|
||||||
|
finally
|
||||||
|
# Stop the server
|
||||||
|
@info "Shutting down server."
|
||||||
|
down()
|
||||||
|
end
|
||||||
|
|
||||||
|
@info "Precompilation tracing finished."
|
||||||
399
src/Bitmap.jl
Normal file
399
src/Bitmap.jl
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
# ********************************************************************
|
||||||
|
# SaveBitmap
|
||||||
|
# ********************************************************************
|
||||||
|
"""
|
||||||
|
SaveBitmap( name, pixMap, colorTable )
|
||||||
|
|
||||||
|
Save a discretized imzML slice as a bitmap file
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* ` name`: Full path name of target bitmap
|
||||||
|
* ` pixMap`: [UInt8] Bidimensional matrix with image info
|
||||||
|
* `colorTable`: [UInt32] Vector with RGB colors for each gray level
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Saves a bitmap with black and white alternate image squares
|
||||||
|
img = zeros(UInt8, 32, 32)
|
||||||
|
img[17:32,1:16] .= 1
|
||||||
|
img[1:16,17:32] .= 1
|
||||||
|
SaveBitmap( "test.bmp", img, UInt32[0, 0x00FFFFFF] )
|
||||||
|
```
|
||||||
|
|
||||||
|
The following image will be created on your hard disk
|
||||||
|
|
||||||
|

|
||||||
|
"""
|
||||||
|
function SaveBitmap( name,
|
||||||
|
pixMap::Array{UInt8,2},
|
||||||
|
colorTable::Array{UInt32,1} )
|
||||||
|
|
||||||
|
# Get image dimensions
|
||||||
|
dim = size( pixMap )
|
||||||
|
if length( dim ) != 2
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compute row padding
|
||||||
|
padding = ( 4 - dim[1] & 0x3 ) & 0x3
|
||||||
|
|
||||||
|
# Compute file dimensions. Header = 14 + 40 + ( 256 * 4 ) = 1078
|
||||||
|
offset = 1078
|
||||||
|
imgBytes = dim[2] * ( dim[1] + padding )
|
||||||
|
|
||||||
|
# Create file
|
||||||
|
stream = open( name, "w" )
|
||||||
|
|
||||||
|
# Save file header
|
||||||
|
write( stream, UInt16( 0x4D42 ) )
|
||||||
|
write( stream, UInt32[ offset + imgBytes, 0 , offset ] )
|
||||||
|
|
||||||
|
# Save info header
|
||||||
|
write( stream, UInt32[ 40, dim[1], dim[2], 0x80001, 0 ] )
|
||||||
|
write( stream, UInt32[ imgBytes, 0, 0, 256, 0 ] )
|
||||||
|
|
||||||
|
# Save color table
|
||||||
|
write( stream, colorTable )
|
||||||
|
if length( colorTable ) < 256
|
||||||
|
fixTable = zeros( UInt32, 256 - length( colorTable ) )
|
||||||
|
write( stream, fixTable )
|
||||||
|
end
|
||||||
|
|
||||||
|
# Save image pixels
|
||||||
|
if padding == 0
|
||||||
|
for i = 1:dim[2]
|
||||||
|
write( stream, pixMap[:,i] )
|
||||||
|
end
|
||||||
|
else
|
||||||
|
zeroPad = zeros( UInt8, padding )
|
||||||
|
for i in 1:dim[2]
|
||||||
|
write( stream, pixMap[:,i] )
|
||||||
|
write( stream, zeroPad )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Close file
|
||||||
|
close( stream )
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# FindMass
|
||||||
|
# massVector: mz Vector sorted in ascending order
|
||||||
|
# mass: target mz value
|
||||||
|
# tolerance: bi-axial tolerance to find mass
|
||||||
|
# ********************************************************************
|
||||||
|
function FindMass( massVector, mass, tolerance )
|
||||||
|
|
||||||
|
index = Int( 0 )
|
||||||
|
lower = Int( 1 )
|
||||||
|
higher = Int( length( massVector ) )
|
||||||
|
|
||||||
|
while lower <= higher
|
||||||
|
|
||||||
|
# Compute mid element
|
||||||
|
index = ( lower + higher ) ÷ 2
|
||||||
|
|
||||||
|
# Go to lower portion ?
|
||||||
|
if massVector[ index ] > ( mass + tolerance )
|
||||||
|
higher = index - 1
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
# Go to Higher portion?
|
||||||
|
if massVector[ index ] < ( mass - tolerance )
|
||||||
|
lower = index + 1
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
# mass was found
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
# mass was not found
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# GetSlice
|
||||||
|
# imzML: imzML data
|
||||||
|
# mass: mz target value
|
||||||
|
# tolerance: bi-axial tolerance to find mass
|
||||||
|
# ********************************************************************
|
||||||
|
"""
|
||||||
|
GetSlice( imzML, mass, tolerance )
|
||||||
|
|
||||||
|
Extract a mz-image from an imzML image data array loaded with
|
||||||
|
`LoadImzml` function. The resulting image array preserves the same
|
||||||
|
data type of the y-axis vectors in the file.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* ` imzML`: Image array loaded with `LoadImzml` function
|
||||||
|
* ` mass`: mz value for image extraction
|
||||||
|
* `tolerance`: Maximum lateral tolerance for a valid mz search
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Load DESI MSI Carcinoma 885.55 mz slice
|
||||||
|
spectra = LoadImzml( "80TopL, 50TopR, 70BottomL, 60BottomR-centroid.imzML" )
|
||||||
|
slice = GetSlice( spectra, 885.55, 0.005 )
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function GetSlice( imzML, mass, tolerance )
|
||||||
|
|
||||||
|
# Alloc space for slice
|
||||||
|
width = maximum( imzML[1,:] )
|
||||||
|
height = maximum( imzML[2,:] )
|
||||||
|
image = zeros( Float64, width, height )
|
||||||
|
|
||||||
|
for i in 1:size( imzML )[2]
|
||||||
|
index = FindMass( imzML[3,i], mass, tolerance )
|
||||||
|
if index != 0
|
||||||
|
image[ imzML[1,i], imzML[2,i] ] = imzML[4,i][index]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# IntQuant, Discretize image amplitude in 0:255 range
|
||||||
|
# slice: Image matriz in measured arbitrary units
|
||||||
|
# ********************************************************************
|
||||||
|
"""
|
||||||
|
IntQuant( slice )
|
||||||
|
|
||||||
|
Zero Memory intensity quantizer. Discretize the image?s continuos
|
||||||
|
intensity range in discrete bins from 0 to 255 gray levels.
|
||||||
|
Returns a UInt8 bidimensional matrix
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `slice`: slice returned by the `GetSlice` function
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Save DESI MSI Carcinoma 885.55 mz slice as a bitmap file
|
||||||
|
spectra = LoadImzml( "80TopL, 50TopR, 70BottomL, 60BottomR-centroid.imzML" )
|
||||||
|
slice = GetSlice( spectra, 885.55, 0.005
|
||||||
|
SaveBitmap( "Slice.bmp", IntQuant( slice ), ViridisPalette )
|
||||||
|
```
|
||||||
|
|
||||||
|
The following image will be created on your hard disk
|
||||||
|
|
||||||
|

|
||||||
|
"""
|
||||||
|
function IntQuant( slice )
|
||||||
|
|
||||||
|
# Compute scale factor for amplitude discretization
|
||||||
|
lower = minimum( slice )
|
||||||
|
scale = 255 / maximum( slice )
|
||||||
|
dim = size( slice )
|
||||||
|
image = zeros( UInt8, dim[1], dim[2] )
|
||||||
|
|
||||||
|
for i in 1:length( slice )
|
||||||
|
image[i] = convert( UInt8, floor( slice[i] * scale + 0.5 ) )
|
||||||
|
end
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# TrIQ, Discretize image amplitude in 0:255 range, grouping outliers
|
||||||
|
# in the highest bin
|
||||||
|
# slice: Image matrix in measured arbitrary units
|
||||||
|
# ********************************************************************
|
||||||
|
function GetOutlierThres( slice, prob )
|
||||||
|
|
||||||
|
# Get bin width
|
||||||
|
low = minimum( slice )
|
||||||
|
upp = maximum( slice )
|
||||||
|
|
||||||
|
# Compute histogram's bin count & separation
|
||||||
|
if ( upp - low + 1 ) >= 100
|
||||||
|
bins = convert( Int32, 100 )
|
||||||
|
step = ( upp + (upp-low)/(bins-1) - low ) / bins
|
||||||
|
else
|
||||||
|
bins = convert( Int32, ceil( upp ) - floor( low ) + 1 )
|
||||||
|
step = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compute histogram
|
||||||
|
histCount = zeros( bins, 1 )
|
||||||
|
for k in slice
|
||||||
|
index = convert( Int64, floor( ( k - low ) / step ) + 1 )
|
||||||
|
histCount[ index ] += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get bin index, that accounts for desired probability
|
||||||
|
prob = 0.95
|
||||||
|
delta = cumsum( histCount, dims=1 ) / sum( histCount ) .- prob
|
||||||
|
index = findmin( broadcast( abs, delta ) )[2][1]
|
||||||
|
|
||||||
|
# Find max intensity image value
|
||||||
|
key = low + step * index
|
||||||
|
upp = -1
|
||||||
|
|
||||||
|
for k in slice[:]
|
||||||
|
if key > k && k > upp
|
||||||
|
upp = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return [ low, upp ]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# Discretize image's intensity using a given intensity range
|
||||||
|
# slice: Image matrix in measured arbitrary units
|
||||||
|
# bounds: max and min intensity discretizing range
|
||||||
|
# depth: number of intensity discrete steps in final image
|
||||||
|
# ********************************************************************
|
||||||
|
function SetPixelDepth( slice, bounds, depth )
|
||||||
|
|
||||||
|
# Compute intensity bins
|
||||||
|
bins = depth - 1
|
||||||
|
limits = collect(
|
||||||
|
range( bounds[1], stop = bounds[2], length = depth ) )
|
||||||
|
|
||||||
|
# Reserve memory for output image
|
||||||
|
imgBytes = zeros( UInt8, size( slice ) )
|
||||||
|
nPixels = length( slice )
|
||||||
|
step = limits[2] - limits[1]
|
||||||
|
|
||||||
|
# Set intensity depth
|
||||||
|
for k in 1:nPixels
|
||||||
|
|
||||||
|
# Find bin
|
||||||
|
i::Int64 = floor( ( slice[k] - limits[1] ) / step ) + 1
|
||||||
|
if i < bins
|
||||||
|
imgBytes[k] = i + ( slice[k] > limits[i] ) - 1
|
||||||
|
else
|
||||||
|
imgBytes[k] = bins
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return imgBytes
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# Discretize image's intensity removing intensity outliers
|
||||||
|
# slice: Image matrix in measured arbitrary units
|
||||||
|
# depth: Number of intensity discrete steps in final image
|
||||||
|
# prob: Proportion of pixesl to take into account in discretization
|
||||||
|
# ********************************************************************
|
||||||
|
"""
|
||||||
|
TrIQ( slice, depth, prob = 0.98 )
|
||||||
|
|
||||||
|
TrIQ intensity quantizer as described in _DOI 10.7717/peerj-cs.585_.
|
||||||
|
Discretize the image?s continuos intensity range in discrete bins
|
||||||
|
from 0 to 255 gray levels, groupping intensity outiers in the
|
||||||
|
highehst bin. Returns a UInt8 bidimensional matrix
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `slice`: slice returned by the `GetSlice` function
|
||||||
|
* `depth`: Number ob discrete bins for intensity quantization
|
||||||
|
* ` prob`: Cummulative distribute function cutting value
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Save DESI MSI Carcinoma 885.55 mz slice as a bitmap file
|
||||||
|
spectra = LoadImzml( "80TopL, 50TopR, 70BottomL, 60BottomR-centroid.imzML" )
|
||||||
|
slice = GetSlice( spectra, 885.55, 0.005 )
|
||||||
|
SaveBitmap( "TrIQ.bmp", TrIQ( slice, 256, 0.95 ), ViridisPalette )
|
||||||
|
```
|
||||||
|
|
||||||
|
The following image will be created on your hard disk
|
||||||
|
|
||||||
|

|
||||||
|
"""
|
||||||
|
function TrIQ( slice, depth, prob )
|
||||||
|
|
||||||
|
return SetPixelDepth(
|
||||||
|
slice,
|
||||||
|
GetOutlierThres( slice, prob ),
|
||||||
|
depth
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# ********************************************************************
|
||||||
|
# Viridis color palette 256 color levels
|
||||||
|
# ********************************************************************
|
||||||
|
ViridisPalette = UInt32[
|
||||||
|
0x440154, 0x440256, 0x450457, 0x450559,
|
||||||
|
0x46075A, 0x46085C, 0x460A5D, 0x460B5E,
|
||||||
|
0x470D60, 0x470E61, 0x471063, 0x471164,
|
||||||
|
0x471365, 0x481467, 0x481668, 0x481769,
|
||||||
|
0x48186A, 0x481A6C, 0x481B6D, 0x481C6E,
|
||||||
|
0x481D6F, 0x481F70, 0x482071, 0x482173,
|
||||||
|
0x482374, 0x482475, 0x482576, 0x482677,
|
||||||
|
0x482878, 0x482979, 0x472A7A, 0x472C7A,
|
||||||
|
0x472D7B, 0x472E7C, 0x472F7D, 0x46307E,
|
||||||
|
0x46327E, 0x46337F, 0x463480, 0x453581,
|
||||||
|
0x453781, 0x453882, 0x443983, 0x443A83,
|
||||||
|
0x443B84, 0x433D84, 0x433E85, 0x423F85,
|
||||||
|
0x424086, 0x424186, 0x414287, 0x414487,
|
||||||
|
0x404588, 0x404688, 0x3F4788, 0x3F4889,
|
||||||
|
0x3E4989, 0x3E4A89, 0x3E4C8A, 0x3D4D8A,
|
||||||
|
0x3D4E8A, 0x3C4F8A, 0x3C508B, 0x3B518B,
|
||||||
|
0x3B528B, 0x3A538B, 0x3A548C, 0x39558C,
|
||||||
|
0x39568C, 0x38588C, 0x38598C, 0x375A8C,
|
||||||
|
0x375B8D, 0x365C8D, 0x365D8D, 0x355E8D,
|
||||||
|
0x355F8D, 0x34608D, 0x34618D, 0x33628D,
|
||||||
|
0x33638D, 0x32648E, 0x32658E, 0x31668E,
|
||||||
|
0x31678E, 0x31688E, 0x30698E, 0x306A8E,
|
||||||
|
0x2F6B8E, 0x2F6C8E, 0x2E6D8E, 0x2E6E8E,
|
||||||
|
0x2E6F8E, 0x2D708E, 0x2D718E, 0x2C718E,
|
||||||
|
0x2C728E, 0x2C738E, 0x2B748E, 0x2B758E,
|
||||||
|
0x2A768E, 0x2A778E, 0x2A788E, 0x29798E,
|
||||||
|
0x297A8E, 0x297B8E, 0x287C8E, 0x287D8E,
|
||||||
|
0x277E8E, 0x277F8E, 0x27808E, 0x26818E,
|
||||||
|
0x26828E, 0x26828E, 0x25838E, 0x25848E,
|
||||||
|
0x25858E, 0x24868E, 0x24878E, 0x23888E,
|
||||||
|
0x23898E, 0x238A8D, 0x228B8D, 0x228C8D,
|
||||||
|
0x228D8D, 0x218E8D, 0x218F8D, 0x21908D,
|
||||||
|
0x21918C, 0x20928C, 0x20928C, 0x20938C,
|
||||||
|
0x1F948C, 0x1F958B, 0x1F968B, 0x1F978B,
|
||||||
|
0x1F988B, 0x1F998A, 0x1F9A8A, 0x1E9B8A,
|
||||||
|
0x1E9C89, 0x1E9D89, 0x1F9E89, 0x1F9F88,
|
||||||
|
0x1FA088, 0x1FA188, 0x1FA187, 0x1FA287,
|
||||||
|
0x20A386, 0x20A486, 0x21A585, 0x21A685,
|
||||||
|
0x22A785, 0x22A884, 0x23A983, 0x24AA83,
|
||||||
|
0x25AB82, 0x25AC82, 0x26AD81, 0x27AD81,
|
||||||
|
0x28AE80, 0x29AF7F, 0x2AB07F, 0x2CB17E,
|
||||||
|
0x2DB27D, 0x2EB37C, 0x2FB47C, 0x31B57B,
|
||||||
|
0x32B67A, 0x34B679, 0x35B779, 0x37B878,
|
||||||
|
0x38B977, 0x3ABA76, 0x3BBB75, 0x3DBC74,
|
||||||
|
0x3FBC73, 0x40BD72, 0x42BE71, 0x44BF70,
|
||||||
|
0x46C06F, 0x48C16E, 0x4AC16D, 0x4CC26C,
|
||||||
|
0x4EC36B, 0x50C46A, 0x52C569, 0x54C568,
|
||||||
|
0x56C667, 0x58C765, 0x5AC864, 0x5CC863,
|
||||||
|
0x5EC962, 0x60CA60, 0x63CB5F, 0x65CB5E,
|
||||||
|
0x67CC5C, 0x69CD5B, 0x6CCD5A, 0x6ECE58,
|
||||||
|
0x70CF57, 0x73D056, 0x75D054, 0x77D153,
|
||||||
|
0x7AD151, 0x7CD250, 0x7FD34E, 0x81D34D,
|
||||||
|
0x84D44B, 0x86D549, 0x89D548, 0x8BD646,
|
||||||
|
0x8ED645, 0x90D743, 0x93D741, 0x95D840,
|
||||||
|
0x98D83E, 0x9BD93C, 0x9DD93B, 0xA0DA39,
|
||||||
|
0xA2DA37, 0xA5DB36, 0xA8DB34, 0xAADC32,
|
||||||
|
0xADDC30, 0xB0DD2F, 0xB2DD2D, 0xB5DE2B,
|
||||||
|
0xB8DE29, 0xBADE28, 0xBDDF26, 0xC0DF25,
|
||||||
|
0xC2DF23, 0xC5E021, 0xC8E020, 0xCAE11F,
|
||||||
|
0xCDE11D, 0xD0E11C, 0xD2E21B, 0xD5E21A,
|
||||||
|
0xD8E219, 0xDAE319, 0xDDE318, 0xDFE318,
|
||||||
|
0xE2E418, 0xE5E419, 0xE7E419, 0xEAE51A,
|
||||||
|
0xECE51B, 0xEFE51C, 0xF1E51D, 0xF4E61E,
|
||||||
|
0xF6E620, 0xF8E621, 0xFBE723, 0xFDE725 ]
|
||||||
|
|
||||||
94
src/Common.jl
Normal file
94
src/Common.jl
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# *******************************************************************
|
||||||
|
# VectorConfig, structure with axis decoding instructions
|
||||||
|
# *******************************************************************
|
||||||
|
mutable struct SpecDim
|
||||||
|
Format::DataType # Float32, Float64
|
||||||
|
Packed::Bool # 0: none 1: zlib compression
|
||||||
|
Axis::Int32 # 1: m/z 2: Amplitude
|
||||||
|
Skip::Int64 # Bytes to skip
|
||||||
|
end
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Fill VectorConfig fields from "cvParam" tags
|
||||||
|
# *******************************************************************
|
||||||
|
function ConfigureSpecDim( stream )
|
||||||
|
|
||||||
|
# Vector configuration
|
||||||
|
axis = SpecDim( Float64, false, 1, 0 )
|
||||||
|
|
||||||
|
# Initial values
|
||||||
|
offset = position( stream )
|
||||||
|
currLine = ""
|
||||||
|
matchInfo = RegexMatch
|
||||||
|
|
||||||
|
while true
|
||||||
|
|
||||||
|
# Next field
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
index = length( matchInfo.captures[1] )
|
||||||
|
matchInfo = GetAttribute( currLine[index:end], "accession" )
|
||||||
|
|
||||||
|
if matchInfo.captures[1] == "MS:1000515" # intensity array
|
||||||
|
axis.Axis = 2
|
||||||
|
continue
|
||||||
|
|
||||||
|
elseif matchInfo.captures[1] == "MS:1000519" # 32-bit integer
|
||||||
|
axis.Format = Int32
|
||||||
|
continue
|
||||||
|
|
||||||
|
elseif matchInfo.captures[1] == "MS:1000521" # 32-bit float
|
||||||
|
axis.Format = Float32
|
||||||
|
continue
|
||||||
|
|
||||||
|
elseif matchInfo.captures[1] == "MS:1000522" # 64-bit integer
|
||||||
|
axis.Format = Int64
|
||||||
|
continue
|
||||||
|
|
||||||
|
elseif matchInfo.captures[1] == "MS:1000574" # zlib compresion
|
||||||
|
axis.Packed = true
|
||||||
|
continue
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Read lines in file until regex match
|
||||||
|
# stream: Source file stream
|
||||||
|
# regex: target tag e.g. "binaryDataArray"
|
||||||
|
# *******************************************************************
|
||||||
|
function FindTag( stream, regex )
|
||||||
|
|
||||||
|
while true
|
||||||
|
isTag = match( regex, readline( stream ) )
|
||||||
|
if isTag !== nothing
|
||||||
|
return isTag
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Retrieve attribute/value pair
|
||||||
|
# source: String with atr="value" pairs
|
||||||
|
# tag: Optional attribute, e.g. "\\spectrum"
|
||||||
|
# *******************************************************************
|
||||||
|
function GetAttribute( source, tag = "([^=]+)" )
|
||||||
|
|
||||||
|
# Build regex and retrieve attribute/value pair
|
||||||
|
regStr = Regex( "\\s" * tag * "=\"([^\"]*)\"" )
|
||||||
|
return( match( regStr, source ) )
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
82
src/DataAccess.jl
Normal file
82
src/DataAccess.jl
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# src/DataAccess.jl
|
||||||
|
|
||||||
|
"""
|
||||||
|
load_spectra(fileName::String)
|
||||||
|
|
||||||
|
Eagerly loads all spectra from a .mzML or .imzML file into a 2xN matrix.
|
||||||
|
This function follows the eager-loading pattern of the original `LoadMzml`
|
||||||
|
for backward compatibility and for analyses that require all data in memory.
|
||||||
|
|
||||||
|
Each column in the returned matrix represents a single spectrum:
|
||||||
|
- Row 1: m/z array
|
||||||
|
- Row 2: Intensity array
|
||||||
|
|
||||||
|
For .imzML files, the (x,y) spatial coordinates are discarded.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `fileName`: Full path to the .mzML or .imzML file.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- An `Array{Any, 2}` containing all spectra.
|
||||||
|
"""
|
||||||
|
function load_spectra(fileName::String)
|
||||||
|
if endswith(lowercase(fileName), ".mzml")
|
||||||
|
# Detected .mzML file. Using the existing eager loader.
|
||||||
|
return LoadMzml(fileName)
|
||||||
|
|
||||||
|
elseif endswith(lowercase(fileName), ".imzml")
|
||||||
|
# Detected .imzML file. Eagerly loading all spectra from .ibd.
|
||||||
|
|
||||||
|
# Lazily load metadata to get offsets and counts
|
||||||
|
imzml_data = load_imzml(fileName)
|
||||||
|
|
||||||
|
try
|
||||||
|
num_spectra = length(imzml_data.spectra_metadata)
|
||||||
|
if num_spectra == 0
|
||||||
|
return Array{Any}(undef, (2, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
spectra_matrix = Array{Any}(undef, (2, num_spectra))
|
||||||
|
|
||||||
|
hIbd = imzml_data.ibd_handle
|
||||||
|
mz_format = imzml_data.mz_format
|
||||||
|
int_format = imzml_data.intensity_format
|
||||||
|
|
||||||
|
# For performance, pre-allocate one large buffer for reading.
|
||||||
|
# This avoids re-allocating memory for each spectrum inside the loop.
|
||||||
|
max_points = maximum(meta.mz_count for meta in imzml_data.spectra_metadata)
|
||||||
|
mz_array_buffer = Array{mz_format}(undef, max_points)
|
||||||
|
intensity_array_buffer = Array{int_format}(undef, max_points)
|
||||||
|
|
||||||
|
for i in 1:num_spectra
|
||||||
|
meta = imzml_data.spectra_metadata[i]
|
||||||
|
|
||||||
|
# Create a view into the buffer with the correct size for this spectrum
|
||||||
|
current_mz_array = view(mz_array_buffer, 1:meta.mz_count)
|
||||||
|
current_int_array = view(intensity_array_buffer, 1:meta.intensity_count)
|
||||||
|
|
||||||
|
# Read binary data directly into the sized views
|
||||||
|
seek(hIbd, meta.mz_offset)
|
||||||
|
read!(hIbd, current_mz_array)
|
||||||
|
|
||||||
|
seek(hIbd, meta.intensity_offset)
|
||||||
|
read!(hIbd, current_int_array)
|
||||||
|
|
||||||
|
# Store a copy in the final matrix. A copy is necessary because
|
||||||
|
# the buffer is overwritten in the next iteration.
|
||||||
|
spectra_matrix[1, i] = copy(current_mz_array)
|
||||||
|
spectra_matrix[2, i] = copy(current_int_array)
|
||||||
|
end
|
||||||
|
|
||||||
|
return spectra_matrix
|
||||||
|
finally
|
||||||
|
# Ensure the .ibd file handle is closed, as this is an eager load.
|
||||||
|
if isopen(imzml_data.ibd_handle)
|
||||||
|
close(imzml_data.ibd_handle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
error("Unsupported file type. Please provide a .mzML or .imzML file.")
|
||||||
|
end
|
||||||
|
end
|
||||||
270
src/MzmlConverter.jl
Normal file
270
src/MzmlConverter.jl
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
# src/MzmlConverter.jl
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file contains the workflow for converting .mzML files (with one spectrum per pixel)
|
||||||
|
into a proper .imzML/.ibd file pair, using a separate synchronization file.
|
||||||
|
It replicates the functionality of the project's original R scripts that use MALDIquant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
using CSV, DataFrames
|
||||||
|
|
||||||
|
# Note: This file assumes that `LoadSpectra` from DataAccess.jl and the necessary
|
||||||
|
# structs/functions from ParserHelpers.jl, mzML.jl, and imzML.jl are available
|
||||||
|
# in the execution context (e.g., included in the main app.jl).
|
||||||
|
|
||||||
|
# A struct to hold the processed pixel data before exporting
|
||||||
|
struct ProcessedPixel
|
||||||
|
coords::Tuple{Int, Int}
|
||||||
|
mz::Vector{Float64} # Assuming m/z is consistent, can be optimized later
|
||||||
|
intensity::Vector{Float32}
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
GetMzmlScanTime(fileName::String)
|
||||||
|
|
||||||
|
Parses a .mzML file to extract the scan start time for each spectrum.
|
||||||
|
This is a Julia implementation of the `GetMzmlScanTime` function from the R scripts.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `fileName`: Path to the .mzML file.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A `Matrix{Int64}` where each row is `[spectrum_index, time_in_milliseconds]`.
|
||||||
|
"""
|
||||||
|
function GetMzmlScanTime(fileName::String)
|
||||||
|
spec_count = 0
|
||||||
|
times = Tuple{Int64, Int64}[]
|
||||||
|
|
||||||
|
open(fileName, "r") do stream
|
||||||
|
file_content = read(stream, String)
|
||||||
|
|
||||||
|
# First, find the total number of spectra to pre-allocate
|
||||||
|
spectrum_list_match = match(r"<spectrumList count=\"(\d+)\"", file_content)
|
||||||
|
if spectrum_list_match === nothing
|
||||||
|
error("Could not find <spectrumList> tag or count attribute.")
|
||||||
|
end
|
||||||
|
total_spectra = parse(Int, spectrum_list_match.captures[1])
|
||||||
|
sizehint!(times, total_spectra)
|
||||||
|
|
||||||
|
# Iterate over each spectrum block
|
||||||
|
for spectrum_match in eachmatch(r"<spectrum index=\"(\d+)\"[^>]*>.*?<\/spectrum>", file_content, overlay=true)
|
||||||
|
spec_block = spectrum_match.match
|
||||||
|
# The R script appears to treat the 0-based mzML index as 1-based, so we add 1.
|
||||||
|
spec_index = parse(Int, spectrum_match.captures[1]) + 1
|
||||||
|
|
||||||
|
# Find the scan start time within the spectrum block
|
||||||
|
time_match = match(r"<cvParam accession=\"MS:1000016\".*?value=\"([\d\.]+)\"", spec_block)
|
||||||
|
|
||||||
|
if time_match !== nothing
|
||||||
|
# The value is in minutes. Convert to milliseconds and round.
|
||||||
|
time_in_minutes = parse(Float64, time_match.captures[1])
|
||||||
|
time_in_ms = round(Int64, time_in_minutes * 60000)
|
||||||
|
push!(times, (spec_index, time_in_ms))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Normalize times so the first scan is at t=0
|
||||||
|
if !isempty(times)
|
||||||
|
sort!(times, by = x -> x[1]) # Ensure scans are sorted by index
|
||||||
|
first_time = times[1][2]
|
||||||
|
for i in eachindex(times)
|
||||||
|
times[i] = (times[i][1], times[i][2] - first_time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end # open
|
||||||
|
|
||||||
|
# Convert vector of tuples to a matrix
|
||||||
|
return permutedims(hcat(collect.(times)...))
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
MatchAcquireTime(sync_file_path::String, scans::Matrix{Int64})
|
||||||
|
|
||||||
|
Correlates pixel acquisition times from a synchronization file with scan
|
||||||
|
acquisition times from an mzML file.
|
||||||
|
|
||||||
|
This is a Julia implementation of the `MatchAcquireTime` function from the R scripts.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `sync_file_path`: Path to the synchronization file (.txt).
|
||||||
|
* `scans`: A matrix of scan times, as returned by `GetMzmlScanTime`.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A `Matrix{Int64}` where each row is `[pixel_index, pixel_time_ms, first_scan_index, last_scan_index]`.
|
||||||
|
"""
|
||||||
|
function MatchAcquireTime(sync_file_path::String, scans::Matrix{Int64})
|
||||||
|
if !isfile(sync_file_path)
|
||||||
|
error("Synchronization file not found: $sync_file_path")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Load xyz stage pixel delays from the sync file, skipping the first 2 header lines.
|
||||||
|
pixel_df = CSV.read(sync_file_path, DataFrame, header=false, skipto=3)
|
||||||
|
pixel = Matrix(pixel_df)
|
||||||
|
|
||||||
|
# Normalize pixel times so the first is at t=0
|
||||||
|
pixel[:, 2] .-= pixel[1, 2]
|
||||||
|
|
||||||
|
num_pixels = size(pixel, 1)
|
||||||
|
num_scans = size(scans, 1)
|
||||||
|
|
||||||
|
index_matrix = zeros(Int64, num_pixels, 2)
|
||||||
|
|
||||||
|
scan_row = 1
|
||||||
|
for pixel_row in 1:(num_pixels - 1)
|
||||||
|
start_scan = scan_row
|
||||||
|
|
||||||
|
# Find the scan that ends after the *next* pixel starts
|
||||||
|
while scan_row <= num_scans && scans[scan_row, 2] < pixel[pixel_row + 1, 2]
|
||||||
|
scan_row += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
index_matrix[pixel_row, 1] = start_scan
|
||||||
|
index_matrix[pixel_row, 2] = scan_row - 1
|
||||||
|
|
||||||
|
# The next pixel's scans start from the one that crossed the boundary
|
||||||
|
scan_row = max(1, scan_row - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Assign scans for the last pixel
|
||||||
|
index_matrix[num_pixels, 1] = scan_row
|
||||||
|
index_matrix[num_pixels, 2] = num_scans
|
||||||
|
|
||||||
|
# Handle cases where some pixels have no scans by creating an empty range
|
||||||
|
for i in 1:num_pixels
|
||||||
|
if index_matrix[i, 2] < index_matrix[i, 1]
|
||||||
|
index_matrix[i, 2] = index_matrix[i, 1] - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return hcat(pixel, index_matrix)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RenderPixel(pixel_info, scans, spectra, scan_time_deltas, pixel_time_deltas)
|
||||||
|
first_scan = pixel_info[3]
|
||||||
|
last_scan = pixel_info[4]
|
||||||
|
num_actions = last_scan - first_scan
|
||||||
|
|
||||||
|
# Use the m/z array of the first scan as the reference
|
||||||
|
mz_array = spectra[1, first_scan]
|
||||||
|
new_intensity = zeros(Float32, length(mz_array))
|
||||||
|
|
||||||
|
if num_actions == 0 # Single scan contributes to the pixel
|
||||||
|
scale = pixel_time_deltas[pixel_info[1]] / scan_time_deltas[first_scan]
|
||||||
|
new_intensity .+= spectra[2, first_scan] .* scale
|
||||||
|
|
||||||
|
elseif num_actions == 1 # Two partial scans contribute
|
||||||
|
# First partial scan
|
||||||
|
scale1 = (scans[last_scan, 2] - pixel_info[2]) / scan_time_deltas[first_scan]
|
||||||
|
new_intensity .+= spectra[2, first_scan] .* scale1
|
||||||
|
|
||||||
|
# Second partial scan
|
||||||
|
next_pixel_time = pixel_info[2] + pixel_time_deltas[pixel_info[1]]
|
||||||
|
scale2 = (next_pixel_time - scans[last_scan, 2]) / scan_time_deltas[last_scan]
|
||||||
|
new_intensity .+= spectra[2, last_scan] .* scale2
|
||||||
|
|
||||||
|
elseif num_actions > 1 # Multiple scans contribute
|
||||||
|
# First partial scan
|
||||||
|
scale1 = (scans[first_scan + 1, 2] - pixel_info[2]) / scan_time_deltas[first_scan]
|
||||||
|
new_intensity .+= spectra[2, first_scan] .* scale1
|
||||||
|
|
||||||
|
# Full scans in the middle
|
||||||
|
for i in (first_scan + 1):(last_scan - 1)
|
||||||
|
new_intensity .+= spectra[2, i]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Last partial scan
|
||||||
|
next_pixel_time = pixel_info[2] + pixel_time_deltas[pixel_info[1]]
|
||||||
|
scale2 = (next_pixel_time - scans[last_scan, 2]) / scan_time_deltas[last_scan]
|
||||||
|
new_intensity .+= spectra[2, last_scan] .* scale2
|
||||||
|
end
|
||||||
|
|
||||||
|
return (mz_array, new_intensity)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ConvertMzmlToImzml(source_file::String, timing_matrix::Matrix{Int64}, scans::Matrix{Int64})
|
||||||
|
# Get image width from timing info
|
||||||
|
width = findfirst(i -> timing_matrix[i, 1] - timing_matrix[i-1, 1] != 1, 2:size(timing_matrix, 1))
|
||||||
|
height = size(timing_matrix, 1) ÷ width
|
||||||
|
|
||||||
|
# Load the full mzML data
|
||||||
|
spectra = LoadSpectra(source_file)
|
||||||
|
|
||||||
|
# Pre-calculate time deltas
|
||||||
|
scan_time_deltas = diff(scans[:, 2])
|
||||||
|
pixel_time_deltas = diff(timing_matrix[:, 2])
|
||||||
|
# Append a final delta for the last element
|
||||||
|
push!(scan_time_deltas, scan_time_deltas[end])
|
||||||
|
push!(pixel_time_deltas, pixel_time_deltas[end])
|
||||||
|
|
||||||
|
processed_pixels = ProcessedPixel[]
|
||||||
|
sizehint!(processed_pixels, size(timing_matrix, 1))
|
||||||
|
|
||||||
|
for i in 1:size(timing_matrix, 1)
|
||||||
|
pixel_info = timing_matrix[i, :]
|
||||||
|
first_scan = pixel_info[3]
|
||||||
|
last_scan = pixel_info[4]
|
||||||
|
|
||||||
|
# Skip if there are no scans for this pixel
|
||||||
|
(first_scan > last_scan) && continue
|
||||||
|
|
||||||
|
# Calculate coordinates
|
||||||
|
x = ((pixel_info[1] - 1) % width) + 1
|
||||||
|
y = fld(pixel_info[1] - 1, width) + 1
|
||||||
|
|
||||||
|
mz, intensity = RenderPixel(pixel_info, scans, spectra, scan_time_deltas, pixel_time_deltas)
|
||||||
|
push!(processed_pixels, ProcessedPixel((x, y), mz, intensity))
|
||||||
|
end
|
||||||
|
|
||||||
|
return processed_pixels, (width, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ExportImzml(target_file::String, pixels::Vector{ProcessedPixel}, dims::Tuple{Int, Int})
|
||||||
|
@warn "ExportImzml is not yet implemented. The processed pixel data has been generated but not saved to .imzML/.ibd files."
|
||||||
|
# TODO: Implement the logic to write the .imzML (XML) and .ibd (binary) files.
|
||||||
|
# 1. Open target_file.imzML and target_file.ibd for writing.
|
||||||
|
# 2. Write all m/z and intensity arrays sequentially to the .ibd file, tracking offsets and lengths.
|
||||||
|
# 3. Write the .imzML XML structure, including:
|
||||||
|
# - Boilerplate headers
|
||||||
|
# - <fileDescription>, <referenceableParamGroupList>, <scanSettingsList>
|
||||||
|
# - A <spectrumList> with a <spectrum> for each pixel.
|
||||||
|
# - Each <spectrum> must contain cvParams for x/y coords and external data pointers to the .ibd file.
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
ImportMzmlFile(source_file::String, sync_file::String, target_file::String)
|
||||||
|
|
||||||
|
Main workflow function to convert a .mzML file to an .imzML file.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `source_file`: Path to the input .mzML file.
|
||||||
|
* `sync_file`: Path to the synchronization text file.
|
||||||
|
* `target_file`: Path for the output .imzML file (the .ibd will be named accordingly).
|
||||||
|
"""
|
||||||
|
function ImportMzmlFile(source_file::String, sync_file::String, target_file::String)
|
||||||
|
println("Step 1: Getting scan times from .mzML file...")
|
||||||
|
scans = GetMzmlScanTime(source_file)
|
||||||
|
|
||||||
|
println("Step 2: Matching acquisition times...")
|
||||||
|
timing_matrix = MatchAcquireTime(sync_file, scans)
|
||||||
|
|
||||||
|
println("Step 3: Converting spectra...")
|
||||||
|
processed_pixels, (width, height) = ConvertMzmlToImzml(source_file, timing_matrix, scans)
|
||||||
|
|
||||||
|
# Flip image vertically to match R script output
|
||||||
|
for i in eachindex(processed_pixels)
|
||||||
|
x, y = processed_pixels[i].coords
|
||||||
|
processed_pixels[i] = ProcessedPixel((x, height - y + 1), processed_pixels[i].mz, processed_pixels[i].intensity)
|
||||||
|
end
|
||||||
|
|
||||||
|
println("Step 4: Exporting to .imzML/.ibd format...")
|
||||||
|
success = ExportImzml(target_file, processed_pixels, (width, height))
|
||||||
|
|
||||||
|
if success
|
||||||
|
println("Conversion successful: $target_file")
|
||||||
|
else
|
||||||
|
println("Conversion failed. Exporting is not yet implemented.")
|
||||||
|
end
|
||||||
|
return success
|
||||||
|
end
|
||||||
122
src/ParserHelpers.jl
Normal file
122
src/ParserHelpers.jl
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# src/ParserHelpers.jl
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file provides common helper functions for parsing mzML and imzML files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# Data Structures and Low-Level Parser Helpers
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
SpecDim
|
||||||
|
|
||||||
|
A struct to hold configuration for a spectral data axis (e.g., m/z or intensity).
|
||||||
|
"""
|
||||||
|
mutable struct SpecDim
|
||||||
|
Format::Type
|
||||||
|
Packed::Bool
|
||||||
|
Axis::Int
|
||||||
|
Skip::Int
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
find_tag(stream, regex::Regex)
|
||||||
|
|
||||||
|
Reads a stream line-by-line until a line matches the provided regex.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A `RegexMatch` object if a match is found, otherwise throws an error.
|
||||||
|
"""
|
||||||
|
function find_tag(stream, regex::Regex)
|
||||||
|
while !eof(stream)
|
||||||
|
line = readline(stream)
|
||||||
|
isTag = match(regex, line)
|
||||||
|
if isTag !== nothing
|
||||||
|
return isTag
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error("Tag not found for regex: $regex")
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
get_attribute(source::String, tag::String = "([^=]+)")
|
||||||
|
|
||||||
|
Retrieves an attribute's value from an XML tag string.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A `RegexMatch` object containing the attribute and its value.
|
||||||
|
"""
|
||||||
|
function get_attribute(source::AbstractString, tag::String = "([^=]+)")
|
||||||
|
# Construct the regex pattern string
|
||||||
|
pattern_str = "\s" * tag * "=\"([^"]*)\""
|
||||||
|
regStr = Regex(pattern_str)
|
||||||
|
return match(regStr, source)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
configure_spec_dim(stream)
|
||||||
|
|
||||||
|
Fills a `SpecDim` struct by parsing `cvParam` tags from the stream.
|
||||||
|
"""
|
||||||
|
function configure_spec_dim(stream)
|
||||||
|
axis = SpecDim(Float64, false, 1, 0)
|
||||||
|
offset = position(stream)
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
index = length(matchInfo.captures[1])
|
||||||
|
attr_match = get_attribute(currLine[index:end], "accession")
|
||||||
|
|
||||||
|
if attr_match !== nothing
|
||||||
|
accession = attr_match.captures[1]
|
||||||
|
if accession == "MS:1000515" # intensity array
|
||||||
|
axis.Axis = 2
|
||||||
|
elseif accession == "MS:1000519" # 32-bit integer
|
||||||
|
axis.Format = Int32
|
||||||
|
elseif accession == "MS:1000521" # 32-bit float
|
||||||
|
axis.Format = Float32
|
||||||
|
elseif accession == "MS:1000522" # 64-bit integer
|
||||||
|
axis.Format = Int64
|
||||||
|
elseif accession == "MS:1000574" # zlib compression
|
||||||
|
axis.Packed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return axis # Should be unreachable if file is well-formed
|
||||||
|
end
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# Data Structures and Helpers for mzML lazy parsing
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
mutable struct CVParams
|
||||||
|
format::Type
|
||||||
|
is_compressed::Bool
|
||||||
|
axis_type::Symbol
|
||||||
|
end
|
||||||
|
|
||||||
|
function update_cv_params!(params::CVParams, acc::String)
|
||||||
|
if acc == "MS:1000514"; params.axis_type = :mz;
|
||||||
|
elseif acc == "MS:1000515"; params.axis_type = :intensity;
|
||||||
|
elseif acc == "MS:1000519"; params.format = Int32;
|
||||||
|
elseif acc == "MS:1000521"; params.format = Float32;
|
||||||
|
elseif acc == "MS:1000522"; params.format = Int64;
|
||||||
|
elseif acc == "MS:1000523"; params.format = Float64;
|
||||||
|
elseif acc == "MS:1000574"; params.is_compressed = true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
699
src/imzML.jl
Normal file
699
src/imzML.jl
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
|
||||||
|
using Images, ImageFiltering, StatsBase, Statistics, CairoMakie, ColorSchemes, DataFrames, CSV, Printf, Dates
|
||||||
|
include("ParserHelpers.jl")
|
||||||
|
|
||||||
|
# --- Lazy Loading Data Structures ---
|
||||||
|
|
||||||
|
# Struct to hold metadata for a single spectrum, for lazy loading.
|
||||||
|
struct SpectrumMetadata
|
||||||
|
x::Int32
|
||||||
|
y::Int32
|
||||||
|
mz_offset::Int64
|
||||||
|
intensity_offset::Int64
|
||||||
|
mz_count::Int32
|
||||||
|
intensity_count::Int32
|
||||||
|
end
|
||||||
|
|
||||||
|
# Struct to hold the parsed imzML metadata and the handle to the .ibd file.
|
||||||
|
mutable struct ImzMLData
|
||||||
|
ibd_handle::IO
|
||||||
|
mz_format::Type
|
||||||
|
intensity_format::Type
|
||||||
|
spectra_metadata::Vector{SpectrumMetadata}
|
||||||
|
width::Int
|
||||||
|
height::Int
|
||||||
|
mz_is_first::Bool # To know the order in the ibd file
|
||||||
|
|
||||||
|
function ImzMLData(ibd_handle::IO, mz_format::Type, intensity_format::Type, spectra_metadata::Vector{SpectrumMetadata}, width::Int, height::Int, mz_is_first::Bool)
|
||||||
|
obj = new(ibd_handle, mz_format, intensity_format, spectra_metadata, width, height, mz_is_first)
|
||||||
|
finalizer(obj) do o
|
||||||
|
if isopen(o.ibd_handle)
|
||||||
|
close(o.ibd_handle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# --- Extracted from imzML.jl ---
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file provides a library for parsing `.imzML` and `.ibd` files in pure Julia.
|
||||||
|
It is intended to be included by a parent script.
|
||||||
|
|
||||||
|
Core Functions:
|
||||||
|
- `load_imzml`: The main function that orchestrates the parsing.
|
||||||
|
- Helper functions for reading XML metadata and binary spectral data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# imzML Parser Implementation
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
axes_config_img(stream)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
axis = Array{SpecDim,1}(undef, 2)
|
||||||
|
axis[order] = configure_spec_dim(stream)
|
||||||
|
|
||||||
|
find_tag(stream, r"^\s*<(referenceableParamGroup )")
|
||||||
|
axis[xor(order, 3)] = configure_spec_dim(stream)
|
||||||
|
return axis
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
get_img_dimensions(stream)
|
||||||
|
|
||||||
|
Reads the maximum X and Y dimensions and total spectrum count from the metadata.
|
||||||
|
"""
|
||||||
|
function get_img_dimensions(stream)
|
||||||
|
find_tag(stream, r"^\s*<(scanSettings )")
|
||||||
|
n = 2
|
||||||
|
dim = [0, 0, 0]
|
||||||
|
|
||||||
|
while n > 0 && !eof(stream)
|
||||||
|
currLine = readline(stream)
|
||||||
|
if occursin("<cvParam", currLine)
|
||||||
|
accession_match = get_attribute(currLine, "accession")
|
||||||
|
if accession_match !== nothing
|
||||||
|
accession = accession_match.captures[1]
|
||||||
|
if accession == "IMS:1000042" || accession == "IMS:1000043"
|
||||||
|
value_match = get_attribute(currLine, "value")
|
||||||
|
if value_match !== nothing
|
||||||
|
axis_idx = (accession == "IMS:1000042") ? 1 : 2
|
||||||
|
dim[axis_idx] = parse(Int32, value_match.captures[1])
|
||||||
|
n -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
count_tag = find_tag(stream, r"^\s*<spectrumList(.+)")
|
||||||
|
count_match = get_attribute(count_tag.captures[1], "count")
|
||||||
|
dim[3] = parse(Int32, count_match.captures[1])
|
||||||
|
return dim
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
get_spectrum_tag_offset(stream)
|
||||||
|
|
||||||
|
Calculates the character offset within a `<spectrum>` tag, ignoring attribute values.
|
||||||
|
"""
|
||||||
|
function get_spectrum_tag_offset(stream)
|
||||||
|
offset = position(stream)
|
||||||
|
tag = find_tag(stream, r"^\s*<spectrum (.+)")
|
||||||
|
first = 1
|
||||||
|
|
||||||
|
while true
|
||||||
|
value = match(r"[^=]+\"([^\"]+)\"", tag.captures[1][first:end])
|
||||||
|
if value === nothing
|
||||||
|
break
|
||||||
|
end
|
||||||
|
first += value.offsets[1] + length(value.captures[1])
|
||||||
|
offset += length(value.captures[1])
|
||||||
|
end
|
||||||
|
return offset
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
get_spectrum_attributes(stream, hIbd)
|
||||||
|
|
||||||
|
Reads metadata to determine the byte offsets and data types for reading spectra.
|
||||||
|
"""
|
||||||
|
function get_spectrum_attributes(stream, hIbd)
|
||||||
|
skip = Vector{UInt32}(undef, 8)
|
||||||
|
offset = get_spectrum_tag_offset(stream)
|
||||||
|
|
||||||
|
tag = find_tag(stream, r" accession=\"IMS:100005(\d)\"(.+)")
|
||||||
|
skip[1] = tag.captures[1][1] - '0' + 1
|
||||||
|
skip[2] = xor(skip[1], 3)
|
||||||
|
|
||||||
|
value = match(r" value=\"(\d+)\".+", tag.captures[2])
|
||||||
|
skip[5] = position(stream) - offset - length(value.match) - 2
|
||||||
|
value = match(r" value=\"\d+\".+", readline(stream))
|
||||||
|
skip[6] = value.offset
|
||||||
|
|
||||||
|
offset = position(stream)
|
||||||
|
tag = find_tag(stream, r"^\s*<referenceableParamGroupRef(.+)")
|
||||||
|
value = get_attribute(tag.captures[1], "ref")
|
||||||
|
skip[3] = (value.captures[1] == "intensityArray") + 3
|
||||||
|
skip[4] = xor(skip[3], 7)
|
||||||
|
|
||||||
|
k = 2
|
||||||
|
while k != 0
|
||||||
|
tag = find_tag(stream, r" accession=\"IMS:100010(\d)\"(.+)")
|
||||||
|
accession_val = tag.captures[1][1]
|
||||||
|
if accession_val == '2'
|
||||||
|
value = get_attribute(tag.match, "value")
|
||||||
|
seek(hIbd, parse(Int64, value.captures[1]))
|
||||||
|
k -= 1
|
||||||
|
elseif accession_val == '3'
|
||||||
|
skip[7] = position(stream) - offset - length(tag.match)
|
||||||
|
offset = position(stream)
|
||||||
|
k -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
find_tag(stream, r"^\s*</spectrum>")
|
||||||
|
skip[8] = position(stream) - offset
|
||||||
|
return skip
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
load_imzml(file_path::String)
|
||||||
|
Main function to parse an `.imzML`/.ibd file pair and prepare for lazy loading.
|
||||||
|
It parses the metadata from the `.imzML` file, including spectrum coordinates and
|
||||||
|
byte offsets for data in the `.ibd` file, but does not load the spectral data itself.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- An `ImzMLData` struct containing the open `.ibd` file handle and all necessary metadata
|
||||||
|
for on-demand data loading.
|
||||||
|
"""
|
||||||
|
function load_imzml(file_path::String)
|
||||||
|
if !isfile(file_path)
|
||||||
|
error("Provided path is not a file: $(file_path)")
|
||||||
|
end
|
||||||
|
|
||||||
|
ibd_path = replace(file_path, ".imzML" => ".ibd")
|
||||||
|
if !isfile(ibd_path)
|
||||||
|
error("Corresponding .ibd file not found for: $(file_path)")
|
||||||
|
end
|
||||||
|
|
||||||
|
stream = open(file_path)
|
||||||
|
hIbd = open(ibd_path)
|
||||||
|
|
||||||
|
# We don't use a try/finally block because we need to return the open hIbd handle.
|
||||||
|
# The finalizer on the ImzMLData struct will be responsible for closing it.
|
||||||
|
|
||||||
|
axis = axes_config_img(stream)
|
||||||
|
imgDim = get_img_dimensions(stream)
|
||||||
|
width, height, num_spectra = imgDim
|
||||||
|
|
||||||
|
# Determine which format is for m/z and which for intensity from the referenceableParamGroups
|
||||||
|
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
|
||||||
|
|
||||||
|
# The original parsing logic is highly optimized and relies on fixed offsets within the XML structure.
|
||||||
|
# We reuse it to get the initial .ibd file offset and the structural attributes of the <spectrum> tags.
|
||||||
|
start_of_spectra_xml = position(stream)
|
||||||
|
attr = get_spectrum_attributes(stream, hIbd)
|
||||||
|
|
||||||
|
# get_spectrum_attributes moves the hIbd pointer to the start of the first spectrum's binary data.
|
||||||
|
current_ibd_offset = position(hIbd)
|
||||||
|
|
||||||
|
# We need to re-scan the spectra list from the beginning to get metadata for each spectrum.
|
||||||
|
seek(stream, start_of_spectra_xml)
|
||||||
|
|
||||||
|
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
|
||||||
|
|
||||||
|
# attr[3] is 4 if the first binary data array is intensity, 3 if it's m/z.
|
||||||
|
mz_is_first = attr[3] == 3
|
||||||
|
|
||||||
|
for k in 1:num_spectra
|
||||||
|
# These skips are based on the structure of the first spectrum tag, assuming all are identical.
|
||||||
|
skip(stream, attr[5]) # Skip to X coordinate value
|
||||||
|
val_tag = find_tag(stream, r"value=\"(\d+)\"")
|
||||||
|
x = parse(Int32, val_tag.captures[1])
|
||||||
|
|
||||||
|
skip(stream, attr[6]) # Skip to Y coordinate value
|
||||||
|
val_tag = find_tag(stream, r"value=\"(\d+)\"")
|
||||||
|
y = parse(Int32, val_tag.captures[1])
|
||||||
|
|
||||||
|
skip(stream, attr[7]) # Skip to array length value
|
||||||
|
val_tag = find_tag(stream, r"value=\"(\d+)\"")
|
||||||
|
nPoints = parse(Int32, val_tag.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
|
||||||
|
end
|
||||||
|
|
||||||
|
spectra_metadata[k] = SpectrumMetadata(x, y, mz_offset, int_offset, nPoints, nPoints)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Close the .imzML file stream, but leave the .ibd stream open for lazy reading.
|
||||||
|
close(stream)
|
||||||
|
|
||||||
|
return ImzMLData(hIbd, mz_format, intensity_format, spectra_metadata, width, height, mz_is_first)
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- End of content from imzML.jl ---
|
||||||
|
|
||||||
|
|
||||||
|
# --- Start of content from Imaging_Normalization.jl ---
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Image Slice Extraction
|
||||||
|
#
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
find_mass(mz_array, intensity_array, target_mass, tolerance)
|
||||||
|
|
||||||
|
Finds the intensity of the most intense peak within a mass tolerance window.
|
||||||
|
This modernized version is more robust than a simple binary search as it
|
||||||
|
correctly handles multiple peaks within the tolerance window.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The intensity (`Float64`) of the peak if found, otherwise `0.0`.
|
||||||
|
"""
|
||||||
|
function find_mass(mz_array, intensity_array, target_mass, tolerance)
|
||||||
|
lower_bound = target_mass - tolerance
|
||||||
|
upper_bound = target_mass + tolerance
|
||||||
|
|
||||||
|
max_intensity = 0.0
|
||||||
|
found = false
|
||||||
|
|
||||||
|
# Iterate through the spectrum to find the highest intensity peak in the window
|
||||||
|
for i in eachindex(mz_array)
|
||||||
|
if lower_bound <= mz_array[i] <= upper_bound
|
||||||
|
if intensity_array[i] > max_intensity
|
||||||
|
max_intensity = intensity_array[i]
|
||||||
|
found = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return found ? max_intensity : 0.0
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
load_slices(folder, masses, tolerance)
|
||||||
|
|
||||||
|
Loads image slices for multiple masses from all `.imzML` files in a directory.
|
||||||
|
This function is optimized to read the binary data file (`.ibd`) only once per file.
|
||||||
|
It iterates through each spectrum, reads its data, and finds all target masses
|
||||||
|
before proceeding to the next spectrum, minimizing disk I/O.
|
||||||
|
"""
|
||||||
|
function load_slices(folder, masses, tolerance)
|
||||||
|
files = filter(f -> endswith(f, ".imzML"), readdir(folder, join=true))
|
||||||
|
if isempty(files)
|
||||||
|
@warn "No .imzML files found in the specified directory: $folder"
|
||||||
|
return (Array{Any}(undef, 0, 0), String[])
|
||||||
|
end
|
||||||
|
n_files = length(files)
|
||||||
|
n_slices = length(masses)
|
||||||
|
|
||||||
|
img_list = Array{Any}(undef, n_files, n_slices)
|
||||||
|
names = String[]
|
||||||
|
|
||||||
|
for (i, file) in enumerate(files)
|
||||||
|
name = basename(file)
|
||||||
|
push!(names, name)
|
||||||
|
@info "Processing $(i)/$(n_files): $(name)"
|
||||||
|
|
||||||
|
imzML_data = @time load_imzml(file) # This is fast, it's fine.
|
||||||
|
|
||||||
|
# Create empty images for all slices for the current file
|
||||||
|
current_file_slices = [zeros(Float64, imzML_data.width, imzML_data.height) for _ in 1:n_slices]
|
||||||
|
|
||||||
|
hIbd = imzML_data.ibd_handle
|
||||||
|
mz_format = imzML_data.mz_format
|
||||||
|
int_format = imzML_data.intensity_format
|
||||||
|
|
||||||
|
# Pre-allocate arrays to be reused for each spectrum
|
||||||
|
mz_array = Array{mz_format}(undef, 0)
|
||||||
|
intensity_array = Array{int_format}(undef, 0)
|
||||||
|
|
||||||
|
# Iterate through each spectrum ONCE
|
||||||
|
for meta in imzML_data.spectra_metadata
|
||||||
|
# Resize arrays if the number of points in the spectrum has changed
|
||||||
|
if length(mz_array) != meta.mz_count
|
||||||
|
resize!(mz_array, meta.mz_count)
|
||||||
|
resize!(intensity_array, meta.intensity_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Read spectrum data ONCE from the .ibd file
|
||||||
|
seek(hIbd, meta.mz_offset)
|
||||||
|
read!(hIbd, mz_array)
|
||||||
|
seek(hIbd, meta.intensity_offset)
|
||||||
|
read!(hIbd, intensity_array)
|
||||||
|
|
||||||
|
# Now, check for all masses of interest in this single spectrum
|
||||||
|
for (j, mass) in enumerate(masses)
|
||||||
|
intensity = find_mass(mz_array, intensity_array, mass, tolerance)
|
||||||
|
if intensity > 0.0
|
||||||
|
if 1 <= meta.x <= imzML_data.width && 1 <= meta.y <= imzML_data.height
|
||||||
|
current_file_slices[j][meta.x, meta.y] = intensity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end # end of spectra loop
|
||||||
|
|
||||||
|
# Assign the generated images to the main list
|
||||||
|
for j in 1:n_slices
|
||||||
|
img_list[i, j] = current_file_slices[j]
|
||||||
|
end
|
||||||
|
|
||||||
|
end # end of files loop
|
||||||
|
|
||||||
|
return (img_list, names)
|
||||||
|
end
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Image Processing and Normalization
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
function get_outlier_thres(img, prob=0.98)
|
||||||
|
# DO NOT filter zeros. Use all pixel values like R does.
|
||||||
|
int_values = vec(img)
|
||||||
|
low = minimum(int_values)
|
||||||
|
upp = maximum(int_values)
|
||||||
|
|
||||||
|
# Create histogram bins
|
||||||
|
if upp - low + 1 >= 100
|
||||||
|
bins = 100
|
||||||
|
brk = range(low, stop=upp + (upp - low)/(bins - 1), length=bins + 1)
|
||||||
|
else
|
||||||
|
brk = collect(floor(low):(ceil(upp) + 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compute histogram with RIGHT-CLOSED = FALSE to mimic R's right=FALSE
|
||||||
|
# This makes intervals [a, b) instead of the default [a, b]
|
||||||
|
h = fit(Histogram, int_values, brk, closed=:left) # Key change: closed=:left
|
||||||
|
|
||||||
|
# Check if histogram is valid
|
||||||
|
if isempty(h.weights) || sum(h.weights) == 0
|
||||||
|
return (low, upp)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Replicate R's algorithm exactly
|
||||||
|
cum_counts = cumsum(h.weights) / sum(h.weights)
|
||||||
|
# Calculate the difference from the target probability
|
||||||
|
top = prob .- cum_counts
|
||||||
|
delta = abs.(top)
|
||||||
|
# Find the index where the difference is minimized
|
||||||
|
min_delta_index = findfirst(x -> x == minimum(delta), delta)
|
||||||
|
|
||||||
|
# R adds 1 to this index: index <- 1 + which(...)[1]
|
||||||
|
target_bin_index = min_delta_index + 1
|
||||||
|
# Get the upper edge of the target bin
|
||||||
|
target_bin_upper_edge = h.edges[1][target_bin_index]
|
||||||
|
|
||||||
|
# Find the maximum data value that is strictly less than this edge
|
||||||
|
# This mimics: max( intMap[ intMap < h$breaks[index] ] )
|
||||||
|
values_below_edge = filter(x -> x < target_bin_upper_edge, int_values)
|
||||||
|
actual_threshold = isempty(values_below_edge) ? low : maximum(values_below_edge)
|
||||||
|
|
||||||
|
return (low, actual_threshold)
|
||||||
|
end
|
||||||
|
|
||||||
|
function set_pixel_depth(img, bounds, depth)
|
||||||
|
min_val, max_val = bounds
|
||||||
|
bins = depth - 1
|
||||||
|
|
||||||
|
if min_val >= max_val
|
||||||
|
return zeros(UInt8, size(img))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create intensity bins
|
||||||
|
range_vals = range(min_val, stop=max_val, length=depth)[2:depth]
|
||||||
|
|
||||||
|
# Assign each pixel to a bin
|
||||||
|
result = similar(img, UInt8) # Use similar to create an array of the same type and size
|
||||||
|
for i in eachindex(img)
|
||||||
|
if img[i] <= min_val
|
||||||
|
result[i] = 0
|
||||||
|
else
|
||||||
|
bin_idx = findfirst(x -> img[i] <= x, range_vals)
|
||||||
|
result[i] = bin_idx === nothing ? bins : bin_idx - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
norm_slices_hist(slices, bins; prob=0.98)
|
||||||
|
|
||||||
|
Normalizes a set of image slices based on a shared histogram range.
|
||||||
|
"""
|
||||||
|
function norm_slices_hist(slices, bins; prob=0.98)
|
||||||
|
n_files, n_masses = size(slices)
|
||||||
|
norm_img = similar(slices)
|
||||||
|
mass_bounds = [] # This will store bounds for EACH mass
|
||||||
|
|
||||||
|
# Calculate bounds for each mass across all files
|
||||||
|
for mass_idx in 1:n_masses
|
||||||
|
# Get all slices for this specific mass across all files
|
||||||
|
mass_slices = [slices[i, mass_idx] for i in 1:n_files]
|
||||||
|
all_vals = reduce(vcat, [vec(s) for s in mass_slices])
|
||||||
|
|
||||||
|
# Calculate global bounds for this specific mass
|
||||||
|
mass_global_bounds = get_outlier_thres(all_vals, prob)
|
||||||
|
push!(mass_bounds, mass_global_bounds)
|
||||||
|
|
||||||
|
# Normalize each file's slice for this mass using its specific bounds
|
||||||
|
for file_idx in 1:n_files
|
||||||
|
norm_img[file_idx, mass_idx] = set_pixel_depth(slices[file_idx, mass_idx], mass_global_bounds, bins)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return (norm_img=norm_img, bounds=mass_bounds) # bounds is now a vector, one per mass
|
||||||
|
end
|
||||||
|
|
||||||
|
function median_filter(img)
|
||||||
|
# 3x3 median filter implementation
|
||||||
|
return mapwindow(median, img, (3, 3))
|
||||||
|
end
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Analysis and Visualization
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
display_statistics(slices)
|
||||||
|
|
||||||
|
Calculates and prints key statistics for each slice.
|
||||||
|
"""
|
||||||
|
function display_statistics(slices, names, masses)
|
||||||
|
if isempty(slices)
|
||||||
|
@warn "Cannot display statistics for empty slice list."
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
n_files, n_masses = size(slices)
|
||||||
|
#stats_to_calc = Dict("Mean" => mean, "Max" => maximum, "Min" => minimum, "Sum" => sum, "Std" => std)
|
||||||
|
stats_to_calc = Dict("Mean" => mean)
|
||||||
|
all_dfs = Dict{String, DataFrame}()
|
||||||
|
|
||||||
|
for (stat_name, stat_func) in stats_to_calc
|
||||||
|
# Create a matrix to hold the statistic for each slice
|
||||||
|
stat_matrix = zeros(Float64, n_files, n_masses)
|
||||||
|
for i in 1:n_files, j in 1:n_masses
|
||||||
|
flat_slice = vec(slices[i, j])
|
||||||
|
if !isempty(flat_slice)
|
||||||
|
stat_matrix[i, j] = stat_func(flat_slice)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create the DataFrame with masses as column headers
|
||||||
|
df = DataFrame(stat_matrix, Symbol.(masses))
|
||||||
|
# Insert the file names as the first column
|
||||||
|
insertcols!(df, 1, :Data => names)
|
||||||
|
mz_row = ["m/z"; masses...]
|
||||||
|
push!(df, mz_row)
|
||||||
|
|
||||||
|
println("\n--- Statistics: $(stat_name) ---")
|
||||||
|
println(df)
|
||||||
|
all_dfs[stat_name] = df
|
||||||
|
end
|
||||||
|
return all_dfs
|
||||||
|
end
|
||||||
|
|
||||||
|
function plot_slices(slices, names, masses, output_dir; stage_name, bins=256, dpi=150, global_bounds=nothing)
|
||||||
|
n_files, n_masses = size(slices)
|
||||||
|
|
||||||
|
mkpath(output_dir)
|
||||||
|
|
||||||
|
# If global_bounds is provided, it should now be a VECTOR of bounds (one per mass)
|
||||||
|
# If not provided, calculate per-mass bounds
|
||||||
|
if global_bounds === nothing
|
||||||
|
global_bounds = []
|
||||||
|
for mass_idx in 1:n_masses
|
||||||
|
mass_slices = [slices[i, mass_idx] for i in 1:n_files]
|
||||||
|
all_vals = reduce(vcat, [vec(s) for s in mass_slices])
|
||||||
|
filter!(isfinite, all_vals)
|
||||||
|
mass_bounds = isempty(all_vals) ? (0.0, 1.0) : extrema(all_vals)
|
||||||
|
push!(global_bounds, mass_bounds)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# With Makie, we define a Figure and a layout.
|
||||||
|
fig = Figure(size = (400 * n_masses, 330 * n_files)) # Adjusted size calculation
|
||||||
|
|
||||||
|
# Add a title for the entire figure.
|
||||||
|
Label(fig[0, 1:(2*n_masses)], "Image Slices - $(stage_name)", fontsize=24, font=:bold, tellwidth=false, padding=(0,0,10,0))
|
||||||
|
|
||||||
|
for file_idx in 1:n_files
|
||||||
|
for mass_idx in 1:n_masses
|
||||||
|
img = slices[file_idx, mass_idx]
|
||||||
|
mass = masses[mass_idx]
|
||||||
|
name = names[file_idx]
|
||||||
|
mass_global_bounds = global_bounds[mass_idx] # Get bounds for THIS specific mass
|
||||||
|
|
||||||
|
# --- Calculate tick properties for THIS mass ---
|
||||||
|
min_val, max_val = mass_global_bounds
|
||||||
|
levels = range(min_val, stop=max_val, length=bins + 1)
|
||||||
|
level_range = levels[end] - levels[1]
|
||||||
|
|
||||||
|
if level_range == 0
|
||||||
|
levels = range(min_val - 0.1, stop=max_val + 0.1, length=bins + 1)
|
||||||
|
level_range = 0.2
|
||||||
|
end
|
||||||
|
|
||||||
|
exponent = level_range > 0 ? floor(log10(level_range)) / 3 : 0
|
||||||
|
scale = 10^(3 * exponent)
|
||||||
|
scaled_levels = levels ./ scale
|
||||||
|
|
||||||
|
format_num = level_range > 0 ? floor(log10(level_range)) % 3 : 0
|
||||||
|
labels = if format_num == 0
|
||||||
|
[@sprintf("%3.2f", lvl) for lvl in scaled_levels]
|
||||||
|
elseif format_num == 1
|
||||||
|
[@sprintf("%3.2f", lvl) for lvl in scaled_levels]
|
||||||
|
else
|
||||||
|
[@sprintf("%3.2f", lvl) for lvl in scaled_levels]
|
||||||
|
end
|
||||||
|
|
||||||
|
divisors = 2:7
|
||||||
|
remainders = (bins - 1) .% divisors
|
||||||
|
best_divisor = divisors[findlast(x -> x == minimum(remainders), remainders)]
|
||||||
|
tick_indices = round.(Int, range(1, stop=bins + 1, length=best_divisor + 1))
|
||||||
|
|
||||||
|
if !(1 in tick_indices)
|
||||||
|
pushfirst!(tick_indices, 1)
|
||||||
|
end
|
||||||
|
if !((bins + 1) in tick_indices)
|
||||||
|
push!(tick_indices, bins + 1)
|
||||||
|
end
|
||||||
|
unique!(sort!(tick_indices))
|
||||||
|
|
||||||
|
tick_positions = levels[tick_indices]
|
||||||
|
tick_labels = labels[tick_indices]
|
||||||
|
# --- End of mass-specific tick calculation ---
|
||||||
|
|
||||||
|
# Create an Axis for the heatmap
|
||||||
|
ax = CairoMakie.Axis(fig[file_idx, 2*mass_idx-1],
|
||||||
|
aspect=DataAspect(),
|
||||||
|
title=@sprintf("%s\nm/z: %.2f", basename(name), mass),
|
||||||
|
titlesize=14
|
||||||
|
)
|
||||||
|
hidedecorations!(ax)
|
||||||
|
|
||||||
|
# Use mass-specific bounds for colorrange
|
||||||
|
hm = heatmap!(ax, transpose(img),
|
||||||
|
colormap=cgrad(ColorSchemes.viridis, bins),
|
||||||
|
colorrange=mass_global_bounds # ← This is mass-specific
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add a colorbar with mass-specific scale
|
||||||
|
cb = Colorbar(fig[file_idx, 2*mass_idx], hm,
|
||||||
|
label=(scale == 1 ? "" : "×10^$(round(Int, 3 * exponent))"),
|
||||||
|
labelpadding=2,
|
||||||
|
labelsize=12,
|
||||||
|
ticks=(tick_positions, tick_labels),
|
||||||
|
ticklabelsize=10
|
||||||
|
)
|
||||||
|
colsize!(fig.layout, 2*mass_idx, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
colgap!(fig.layout, 5)
|
||||||
|
rowgap!(fig.layout, 10)
|
||||||
|
|
||||||
|
# Save in multiple formats.
|
||||||
|
formats = ["png", "pdf"]
|
||||||
|
|
||||||
|
for fmt in formats
|
||||||
|
filename = "$(stage_name)_overview.$(fmt)"
|
||||||
|
save_path = joinpath(output_dir, filename)
|
||||||
|
save(save_path, fig, px_per_unit = dpi / 96.0)
|
||||||
|
@info "Saved $(fmt) overview plot to $save_path"
|
||||||
|
end
|
||||||
|
return fig
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Main Workflow
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
function load_config(path)
|
||||||
|
config = Dict{String, Any}()
|
||||||
|
if !isfile(path)
|
||||||
|
@warn "Config file not found at $(path). Using default parameters."
|
||||||
|
# Define defaults here in case the file is missing
|
||||||
|
config["masses"] = [100]
|
||||||
|
config["tolerance"] = 0.1
|
||||||
|
config["color_depth"] = 255
|
||||||
|
config["outlier_prob"] = 0.98
|
||||||
|
return config
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in eachline(path)
|
||||||
|
line = strip(line)
|
||||||
|
if isempty(line) || startswith(line, "#")
|
||||||
|
continue # Skip empty lines and comments
|
||||||
|
end
|
||||||
|
parts = split(line, '=', limit=2)
|
||||||
|
if length(parts) != 2
|
||||||
|
@warn "Skipping malformed line in config: $(line)"
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
key = strip(parts[1])
|
||||||
|
value_str = strip(parts[2])
|
||||||
|
|
||||||
|
try
|
||||||
|
if key == "masses"
|
||||||
|
values = [parse(Float64, s) for s in split(value_str, ',')]
|
||||||
|
config[key] = values
|
||||||
|
elseif key == "tolerance"
|
||||||
|
config[key] = parse(Float64, value_str)
|
||||||
|
elseif key == "color_depth"
|
||||||
|
config[key] = parse(Int, value_str)
|
||||||
|
elseif key == "outlier_prob"
|
||||||
|
config[key] = parse(Float64, value_str)
|
||||||
|
end
|
||||||
|
catch e
|
||||||
|
@error "Could not parse value for key '$(key)': $(value_str)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return config
|
||||||
|
end
|
||||||
246
src/imzML_old.jl
Normal file
246
src/imzML_old.jl
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
# include( "Common.jl" );
|
||||||
|
# *******************************************************************
|
||||||
|
# Load Spectra and return a matrix
|
||||||
|
# fileName: Full name path
|
||||||
|
# *******************************************************************
|
||||||
|
"""
|
||||||
|
LoadImzml( fileName )
|
||||||
|
|
||||||
|
Load an imzML file as a matrix. Each column stores x-pixel position,
|
||||||
|
y-pixel position, x-axis data and y-axis data.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `fileName`: Full path name of the imzML file
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Load DESI MSI Carcinoma image data
|
||||||
|
spectra = LoadImzml( "80TopL, 50TopR, 70BottomL, 60BottomR-centroid.imzML" )
|
||||||
|
size( spectra )
|
||||||
|
(4, 18632)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function LoadImzml( fileName )
|
||||||
|
|
||||||
|
# Open file handles
|
||||||
|
if !isfile(fileName)
|
||||||
|
error("provided path is not a file")
|
||||||
|
end
|
||||||
|
|
||||||
|
if endswith(fileName, ".imzML")
|
||||||
|
stream = open(fileName)
|
||||||
|
hIbd = open(replace(fileName, ".imzML" => ".ibd"))
|
||||||
|
else
|
||||||
|
stream = open(fileName * ".imzML")
|
||||||
|
hIbd = open(fileName * ".ibd")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get axes types and image dimensions
|
||||||
|
axis = AxesConfigImg( stream )
|
||||||
|
imgDim = GetImgDimensions( stream )
|
||||||
|
format = [ axis[1].Format, axis[2].Format ]
|
||||||
|
|
||||||
|
# Locate spectrum attributes
|
||||||
|
start = position( stream )
|
||||||
|
attr = GetSpectrumAttributes( stream, hIbd )
|
||||||
|
|
||||||
|
# Load spectra
|
||||||
|
seek( stream, start )
|
||||||
|
spectra = LoadImgData( stream, hIbd, attr, imgDim[3], format )
|
||||||
|
|
||||||
|
close( stream )
|
||||||
|
close( hIbd )
|
||||||
|
return spectra
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Get axes value type
|
||||||
|
# *******************************************************************
|
||||||
|
function AxesConfigImg( stream )
|
||||||
|
|
||||||
|
# Locate which axis is defined at first
|
||||||
|
tag = FindTag( stream, r"^\s*<(referenceableParamGroup )" )
|
||||||
|
value = GetAttribute( tag.captures[1], "intensityArray" )
|
||||||
|
order = 1 + ( value !== nothing )
|
||||||
|
|
||||||
|
# Read first axis configuration
|
||||||
|
axis = Array{ SpecDim, 1 }( undef, 2 )
|
||||||
|
axis[ order ] = ConfigureSpecDim( stream )
|
||||||
|
|
||||||
|
# Read second axis configuration
|
||||||
|
FindTag( stream, r"^\s*<(referenceableParamGroup )" )
|
||||||
|
axis[ xor( order,3 ) ] = ConfigureSpecDim( stream )
|
||||||
|
return axis
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Get vector's storage options and image dimensions
|
||||||
|
# *******************************************************************
|
||||||
|
function GetImgDimensions( stream )
|
||||||
|
|
||||||
|
# Looks for "scanSettings" tag
|
||||||
|
# FindImgTag( stream, "scanSettings" )
|
||||||
|
FindTag( stream, r"^\s*<(scanSettings )" )
|
||||||
|
|
||||||
|
# Initial values for dimension retrieve
|
||||||
|
n = 2
|
||||||
|
dim = [ 0, 0, 0 ]
|
||||||
|
currLine = ""
|
||||||
|
matchInfo = RegexMatch
|
||||||
|
|
||||||
|
while( n > 0 )
|
||||||
|
|
||||||
|
# Next field
|
||||||
|
currLine = readline( stream )
|
||||||
|
matchInfo = match( r"^\s*<(cvParam)", currLine )
|
||||||
|
index = length( matchInfo.captures[1] )
|
||||||
|
matchInfo = GetAttribute( currLine[index:end], "accession" )
|
||||||
|
|
||||||
|
# Get axis identity
|
||||||
|
if( matchInfo.captures[1] == "IMS:1000042" # max X
|
||||||
|
|| matchInfo.captures[1] == "IMS:1000043" ) # max Y
|
||||||
|
|
||||||
|
# Read dimension's pixels
|
||||||
|
axis = matchInfo.captures[1][end] - '1'
|
||||||
|
index += matchInfo.offsets[1] + length( matchInfo.captures[1] )
|
||||||
|
matchInfo = GetAttribute( currLine[index:end], "value" )
|
||||||
|
dim[axis] = parse( Int32, matchInfo.captures[1] )
|
||||||
|
n -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Load stored spectra counter
|
||||||
|
matchInfo = FindTag( stream, r"^\s*<spectrumList(.+)" )
|
||||||
|
matchInfo = GetAttribute( matchInfo.captures[1], "count" )
|
||||||
|
dim[ 3 ] = parse( Int32, matchInfo.captures[1] )
|
||||||
|
|
||||||
|
return dim
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Length of "spectrum" tag without attribute values
|
||||||
|
# *******************************************************************
|
||||||
|
function GetSpectrumTag( stream )
|
||||||
|
|
||||||
|
offset = position( stream )
|
||||||
|
tag = FindTag( stream, r"^\s*<spectrum (.+)" )
|
||||||
|
first = 1
|
||||||
|
|
||||||
|
while true
|
||||||
|
|
||||||
|
value = match( r"[^=]+=\"([^\"]+)\"", tag.captures[1][first:end] )
|
||||||
|
if value === nothing
|
||||||
|
break
|
||||||
|
end
|
||||||
|
first += value.offsets[1] + length( value.captures[1] )
|
||||||
|
offset += length( value.captures[1] )
|
||||||
|
end
|
||||||
|
|
||||||
|
return offset
|
||||||
|
end
|
||||||
|
|
||||||
|
# seek( stream, 7141 )
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Locate spectrum attribute's
|
||||||
|
# [1] 1: x position stored first 2: y position stored first
|
||||||
|
# [2] Second stored position
|
||||||
|
# [3] 1: mzArray stored first 2: intensityArray stored first
|
||||||
|
# [4] Second stored axis
|
||||||
|
# [5] skip chars to find first dimension
|
||||||
|
# [6] skip chars to find second dimension
|
||||||
|
# [7] skip chars to find vector length
|
||||||
|
# [8] skip chars to find next spectrum
|
||||||
|
# *******************************************************************
|
||||||
|
function GetSpectrumAttributes( stream, hIbd )
|
||||||
|
|
||||||
|
# Reserve memory for vector configuration
|
||||||
|
skip = Vector{ UInt32 }( undef, 8 )
|
||||||
|
offset = GetSpectrumTag( stream )
|
||||||
|
|
||||||
|
# Get axis order and position of pixel coordinate values
|
||||||
|
tag = FindTag( stream, r" accession=\"IMS:100005(\d)\"(.+)" )
|
||||||
|
skip[1] = tag.captures[1][1] + 1 - '0'
|
||||||
|
skip[2] = xor( skip[1], 3 )
|
||||||
|
|
||||||
|
value = match( r" value=\"(\d+)\".+", tag.captures[2] )
|
||||||
|
skip[5] = position( stream ) - offset - length( value.match ) - 2
|
||||||
|
value = match( r" value=\"\d+\".+", readline( stream ) )
|
||||||
|
skip[6] = value.offset
|
||||||
|
|
||||||
|
# Get axis order
|
||||||
|
offset = position( stream )
|
||||||
|
tag = FindTag( stream, r"^\s*<referenceableParamGroupRef(.+)" )
|
||||||
|
value = GetAttribute( tag.captures[1], "ref" )
|
||||||
|
skip[3] = ( value.captures[1] == "intensityArray" ) + 3
|
||||||
|
skip[4] = xor( skip[3], 7 )
|
||||||
|
|
||||||
|
k = 2
|
||||||
|
while k != 0
|
||||||
|
|
||||||
|
tag = FindTag( stream, r" accession=\"IMS:100010(\d)\"(.+)" )
|
||||||
|
|
||||||
|
# Set IBD offset for first spectrum
|
||||||
|
if tag.captures[1][1] == '2'
|
||||||
|
value = GetAttribute( tag.match, "value" )
|
||||||
|
seek( hIbd, parse( Int32, value.captures[1] ) )
|
||||||
|
k -= 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
elseif tag.captures[1][1] == '3'
|
||||||
|
skip[7] = position( stream ) - offset - length( tag.match )
|
||||||
|
offset = position( stream )
|
||||||
|
k -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compute characters to skip
|
||||||
|
FindTag( stream, r"^\s*</spectrum" )
|
||||||
|
skip[8] = position( stream ) - offset
|
||||||
|
return skip
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Read spectral data
|
||||||
|
# *******************************************************************
|
||||||
|
function LoadImgData( stream, hIbd, attr, imgDim, format )
|
||||||
|
|
||||||
|
# Reserve spectra memory & update IBD seek offset
|
||||||
|
spectra = Array{Any}( undef, ( 4, imgDim ) )
|
||||||
|
contador = 0;
|
||||||
|
|
||||||
|
for k in 1:imgDim
|
||||||
|
|
||||||
|
# Load image coordinates
|
||||||
|
skip( stream, attr[5] )
|
||||||
|
value = FindTag( stream, r"value=\"(\d+)\"" )
|
||||||
|
spectra[ attr[1],k ] = parse( Int32, value.captures[1] )
|
||||||
|
|
||||||
|
skip( stream, attr[6] )
|
||||||
|
value = FindTag( stream, r"value=\"(\d+)\"" )
|
||||||
|
spectra[ attr[2],k ] = parse( Int32, value.captures[1] )
|
||||||
|
|
||||||
|
# Get vector length
|
||||||
|
skip( stream, attr[7] )
|
||||||
|
value = FindTag( stream, r"value=\"(\d+)\"" )
|
||||||
|
nPoints = parse( Int32, value.captures[1] )
|
||||||
|
|
||||||
|
# Reserve memory and read spectrum
|
||||||
|
spectra[ 3,k ] = Array{ format[1] }( undef, nPoints )
|
||||||
|
spectra[ 4,k ] = Array{ format[2] }( undef, nPoints )
|
||||||
|
read!( hIbd, spectra[ attr[3],k ] )
|
||||||
|
read!( hIbd, spectra[ attr[4],k ] )
|
||||||
|
skip( stream, attr[8] )
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return spectra
|
||||||
|
|
||||||
|
end
|
||||||
191
src/mzML.jl
Normal file
191
src/mzML.jl
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
# /home/pixel/Documents/Cinvestav_2025/JuliaMSI/src/mzML_lazy.jl
|
||||||
|
|
||||||
|
using Base64, Libz
|
||||||
|
include("ParserHelpers.jl")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Data Structures for Lazy Loading
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
struct SpectrumAsset
|
||||||
|
format::Type
|
||||||
|
is_compressed::Bool
|
||||||
|
offset::Int64
|
||||||
|
encoded_length::Int32
|
||||||
|
axis_type::Symbol # :mz or :intensity
|
||||||
|
end
|
||||||
|
|
||||||
|
struct MzMLSpectrumMetadata
|
||||||
|
id::String
|
||||||
|
mz_asset::SpectrumAsset
|
||||||
|
intensity_asset::SpectrumAsset
|
||||||
|
end
|
||||||
|
|
||||||
|
mutable struct MzMLData
|
||||||
|
file_handle::IO
|
||||||
|
spectra_metadata::Vector{MzMLSpectrumMetadata}
|
||||||
|
|
||||||
|
function MzMLData(file_handle::IO, spectra_metadata::Vector{MzMLSpectrumMetadata})
|
||||||
|
obj = new(file_handle, spectra_metadata)
|
||||||
|
finalizer(obj) do o
|
||||||
|
if isopen(o.file_handle)
|
||||||
|
close(o.file_handle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# mzML Parser Implementation
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
function get_spectrum_asset_metadata(stream)
|
||||||
|
start_pos = position(stream)
|
||||||
|
|
||||||
|
bda_tag = find_tag(stream, r"<binaryDataArray\s+encodedLength=\"(\d+)\"")
|
||||||
|
if bda_tag === nothing; error("Cannot find binaryDataArray"); end
|
||||||
|
encoded_length = parse(Int32, bda_tag.captures[1])
|
||||||
|
|
||||||
|
params = CVParams(Float64, false, :mz)
|
||||||
|
|
||||||
|
while true
|
||||||
|
line = readline(stream)
|
||||||
|
if occursin("</binaryDataArray>", line); break; end
|
||||||
|
if occursin("<cvParam", line)
|
||||||
|
accession = get_attribute(line, "accession")
|
||||||
|
if accession === nothing; continue; end
|
||||||
|
update_cv_params!(params, accession.captures[1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
seek(stream, start_pos)
|
||||||
|
binary_tag = find_tag(stream, r"<binary>")
|
||||||
|
if binary_tag === nothing; error("Cannot find binary tag"); end
|
||||||
|
binary_offset = position(stream)
|
||||||
|
|
||||||
|
find_tag(stream, r"</binaryDataArray>") # Position for next call
|
||||||
|
|
||||||
|
return SpectrumAsset(params.format, params.is_compressed, binary_offset, encoded_length, params.axis_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
function parse_spectrum_metadata(stream, offset::Int64)
|
||||||
|
seek(stream, offset)
|
||||||
|
|
||||||
|
id_match = find_tag(stream, r"<spectrum\s+index=\"\d+\"\s+id=\"([^"]+)\"")
|
||||||
|
id = id_match === nothing ? "" : id_match.captures[1]
|
||||||
|
|
||||||
|
asset1 = get_spectrum_asset_metadata(stream)
|
||||||
|
asset2 = get_spectrum_asset_metadata(stream)
|
||||||
|
|
||||||
|
mz_asset = asset1.axis_type == :mz ? asset1 : asset2
|
||||||
|
int_asset = asset1.axis_type == :intensity ? asset1 : asset2
|
||||||
|
|
||||||
|
return MzMLSpectrumMetadata(id, mz_asset, int_asset)
|
||||||
|
end
|
||||||
|
|
||||||
|
function parse_offset_list(stream)
|
||||||
|
offsets = Int64[]
|
||||||
|
while true
|
||||||
|
line = readline(stream)
|
||||||
|
m = match(r"<offset\s+idRef=\"\S+\">(\d+)</offset>", line)
|
||||||
|
if m !== nothing
|
||||||
|
push!(offsets, parse(Int64, m.captures[1]))
|
||||||
|
elseif occursin("</index>", line) || occursin("</indexedmzML>", line)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return offsets
|
||||||
|
end
|
||||||
|
|
||||||
|
function load_mzml_lazy(file_path::String)
|
||||||
|
stream = open(file_path)
|
||||||
|
|
||||||
|
try
|
||||||
|
seekend(stream)
|
||||||
|
skip(stream, -4096) # Read last 4KB
|
||||||
|
footer = read(stream, String)
|
||||||
|
|
||||||
|
index_offset_match = match(r"<indexListOffset>(\d+)</indexListOffset>", footer)
|
||||||
|
if index_offset_match === nothing
|
||||||
|
error("Could not find <indexListOffset>. File may not be an indexed mzML.")
|
||||||
|
end
|
||||||
|
|
||||||
|
index_offset = parse(Int64, index_offset_match.captures[1])
|
||||||
|
seek(stream, index_offset)
|
||||||
|
|
||||||
|
if find_tag(stream, r"<index\s+name=\"spectrum\"") === nothing
|
||||||
|
error("Could not find spectrum index.")
|
||||||
|
end
|
||||||
|
|
||||||
|
spectrum_offsets = parse_offset_list(stream)
|
||||||
|
if isempty(spectrum_offsets)
|
||||||
|
error("No spectrum offsets found.")
|
||||||
|
end
|
||||||
|
|
||||||
|
spectra_metadata = [parse_spectrum_metadata(stream, offset) for offset in spectrum_offsets]
|
||||||
|
|
||||||
|
# We return the stream inside MzMLData, so we don't close it here.
|
||||||
|
# The finalizer on MzMLData will handle it.
|
||||||
|
return MzMLData(stream, spectra_metadata)
|
||||||
|
|
||||||
|
catch e
|
||||||
|
close(stream) # Close file on error
|
||||||
|
rethrow(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Data Access Functions
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
function read_binary_vector(io::IO, asset::SpectrumAsset)
|
||||||
|
seek(io, asset.offset)
|
||||||
|
|
||||||
|
# Read the raw base64 data. The regex is slow. Reading bytes is better.
|
||||||
|
# The old code did `Vector{UInt8}(base64Vec.captures[1])`. This implies ASCII.
|
||||||
|
raw_b64 = read(io, asset.encoded_length)
|
||||||
|
|
||||||
|
decoded_bytes = base64decode(String(raw_b64))
|
||||||
|
|
||||||
|
bytes_io = IOBuffer(asset.is_compressed ? Libz.inflate(decoded_bytes) : decoded_bytes)
|
||||||
|
|
||||||
|
n_elements = Int(bytes_io.size / sizeof(asset.format))
|
||||||
|
out_array = Array{asset.format}(undef, n_elements)
|
||||||
|
read!(bytes_io, out_array)
|
||||||
|
|
||||||
|
return out_array
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_spectrum(data::MzMLData, index::Int)
|
||||||
|
meta = data.spectra_metadata[index]
|
||||||
|
|
||||||
|
mz_array = read_binary_vector(data.file_handle, meta.mz_asset)
|
||||||
|
intensity_array = read_binary_vector(data.file_handle, meta.intensity_asset)
|
||||||
|
|
||||||
|
return (id=meta.id, mz=mz_array, intensity=intensity_array)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
LoadMzml(fileName::String)
|
||||||
|
|
||||||
|
Eagerly loads all spectra from a .mzML file.
|
||||||
|
Provided for backward compatibility.
|
||||||
|
"""
|
||||||
|
function LoadMzml(fileName::String)
|
||||||
|
mzml_data = load_mzml_lazy(fileName)
|
||||||
|
|
||||||
|
num_spectra = length(mzml_data.spectra_metadata)
|
||||||
|
spectra_matrix = Array{Any}(undef, (2, num_spectra))
|
||||||
|
|
||||||
|
for i in 1:num_spectra
|
||||||
|
spectrum = get_spectrum(mzml_data, i)
|
||||||
|
spectra_matrix[1, i] = spectrum.mz
|
||||||
|
spectra_matrix[2, i] = spectrum.intensity
|
||||||
|
end
|
||||||
|
|
||||||
|
# Eager load, so we can close the handle now.
|
||||||
|
close(mzml_data.file_handle)
|
||||||
|
|
||||||
|
return spectra_matrix
|
||||||
|
end
|
||||||
350
src/mzML_old.jl
Normal file
350
src/mzML_old.jl
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
# *******************************************************************
|
||||||
|
# Load scans from mzML file
|
||||||
|
# fileName: Full name path
|
||||||
|
# *******************************************************************
|
||||||
|
"""
|
||||||
|
`LoadMzml( fileName )`
|
||||||
|
|
||||||
|
Load the mzML file as a Matrix. Each column stores a single scan,
|
||||||
|
x-axis and y-axis scan data are stored in the first and second
|
||||||
|
row respectively.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
* `fileName`: Full path name of the mzML file
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
spectra = LoadMzml( "T9_A1.mzML" )
|
||||||
|
size( spectra )
|
||||||
|
(2, 10)
|
||||||
|
using Plots
|
||||||
|
plot( spectra[1,4], spectra[2,4] )
|
||||||
|
```
|
||||||
|
|
||||||
|
The following image will appear in an auxiliary window
|
||||||
|
|
||||||
|

|
||||||
|
"""
|
||||||
|
function LoadMzml( fileName )
|
||||||
|
|
||||||
|
# Open file hFile = open( joinpath( data_dir, "Col_1.mzML" ), "r+" )
|
||||||
|
stream = open( fileName, "r+" )
|
||||||
|
intensity = []
|
||||||
|
|
||||||
|
# Load footer
|
||||||
|
seekend( stream )
|
||||||
|
skip( stream, -160 )
|
||||||
|
raw = read( stream, 160 )
|
||||||
|
footer = String( raw )
|
||||||
|
|
||||||
|
# Search "indexListOffset" tag
|
||||||
|
index = match( r"<indexListOffset>(?<Ofst>\d+)</indexListOffset>", footer )
|
||||||
|
|
||||||
|
if index !== nothing
|
||||||
|
|
||||||
|
# Jump to index table
|
||||||
|
seek( stream, parse( Int64, index.captures[1] ) )
|
||||||
|
|
||||||
|
# Find first "index" tag
|
||||||
|
index = FindTag( stream, r"<index +name=\"spectrum" )
|
||||||
|
|
||||||
|
# Load offset list and load spectra
|
||||||
|
if index !== nothing
|
||||||
|
intensity = LoadSpectra( stream, ParseOffsetList( stream ) )
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Close file handle
|
||||||
|
close( stream )
|
||||||
|
|
||||||
|
# Return spectral matrix
|
||||||
|
return intensity
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Parse indexList offsets
|
||||||
|
# stream: Source file stream
|
||||||
|
# *******************************************************************
|
||||||
|
function ParseOffsetList( stream )
|
||||||
|
|
||||||
|
# List of 8 elements
|
||||||
|
entry = zeros( Int64, 8 )
|
||||||
|
used = 0
|
||||||
|
unused = 8
|
||||||
|
|
||||||
|
while true
|
||||||
|
|
||||||
|
# Get offset string
|
||||||
|
raw = readline( stream )
|
||||||
|
index = match( r">(?<Ofst>\d+)<", String( raw ) )
|
||||||
|
|
||||||
|
# Exit condition
|
||||||
|
if index === nothing
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
# Save index
|
||||||
|
used += 1
|
||||||
|
unused -= 1
|
||||||
|
entry[used] = parse( Int64, index.captures[1] )
|
||||||
|
|
||||||
|
# Grow list when needed
|
||||||
|
if unused == 0
|
||||||
|
append!( entry, zeros( Int64, 8 ) )
|
||||||
|
unused = 8
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return entry[1:used]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Read vector data
|
||||||
|
# stream: Source file stream
|
||||||
|
# *******************************************************************
|
||||||
|
function ReadVector( stream, axis )
|
||||||
|
|
||||||
|
# Read base64 vector
|
||||||
|
base64Vec = FindTag( stream, r"<binary>([^<]+)<" )
|
||||||
|
base64Vec = Vector{ UInt8 }( base64Vec.captures[1] )
|
||||||
|
|
||||||
|
# Fill a IO stream with decoded data
|
||||||
|
if axis.Packed == 1
|
||||||
|
io = IOBuffer( Libz.inflate( Decode64( base64Vec ) ) )
|
||||||
|
else
|
||||||
|
io = IOBuffer( Decode64( base64Vec ) )
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convert IO stream content as dataType vector
|
||||||
|
nElem = Int32( io.size / sizeof( axis.Format ) )
|
||||||
|
out = Array{ axis.Format }( undef, nElem )
|
||||||
|
read!( io, out )
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Get the decoding options for the first vector in spectrum
|
||||||
|
# stream: Valid file stream
|
||||||
|
# lastTag: Last tag to skip i.e. "binaryDataArray"
|
||||||
|
# *******************************************************************
|
||||||
|
function AxisConfig( stream )
|
||||||
|
|
||||||
|
offset = position( stream )
|
||||||
|
skip = 0
|
||||||
|
while true
|
||||||
|
|
||||||
|
# Load line
|
||||||
|
raw = readline( stream )
|
||||||
|
tag = match( r"^\s*<([^\s]+)", raw )
|
||||||
|
start = length( tag.match ) + 1
|
||||||
|
|
||||||
|
# Is a "cvParam" tag?
|
||||||
|
if tag.captures[1] == "cvParam"
|
||||||
|
|
||||||
|
# Get accesion field value
|
||||||
|
attribute = GetAttribute( raw[ start:end ], "accession" )
|
||||||
|
|
||||||
|
# Subtract file name length from skip field
|
||||||
|
if attribute.captures[1] != "MS:1000796"
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
start += attribute.offset + length( attribute.match )
|
||||||
|
value = GetAttribute( raw[ start:end ], "value" )
|
||||||
|
skip += length( value.captures[1] )
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
# Exit condition
|
||||||
|
elseif tag.captures[1] == "binaryDataArray"
|
||||||
|
value = GetAttribute( raw[ start:end ], "encodedLength" )
|
||||||
|
offset = position( stream ) - offset
|
||||||
|
front = ConfigureSpecDim( stream )
|
||||||
|
front.Skip += offset - length( value.captures[1] ) - skip
|
||||||
|
return ( front )
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Load Spectra and return a matrix
|
||||||
|
# stream: Source file stream
|
||||||
|
# *******************************************************************
|
||||||
|
function LoadSpectra( stream, offset )
|
||||||
|
|
||||||
|
# Move file pointer towards first spectrum tag
|
||||||
|
seek( stream, offset[1] )
|
||||||
|
|
||||||
|
# Decodes axis information and skip counter
|
||||||
|
firstAxis = AxisConfig( stream )
|
||||||
|
secondAxis = AxisConfig( stream )
|
||||||
|
|
||||||
|
# Allocates memory for spectra storage
|
||||||
|
specCount = length( offset )
|
||||||
|
spectrum = Array{ Any }( undef, ( 2, specCount ) )
|
||||||
|
index = 1
|
||||||
|
order = 1 + ( firstAxis.Axis == 2 )
|
||||||
|
|
||||||
|
for i in 1:specCount
|
||||||
|
|
||||||
|
# Load first vector
|
||||||
|
seek( stream, offset[ i ] )
|
||||||
|
skip( stream, firstAxis.Skip )
|
||||||
|
spectrum[ order, i ] = ReadVector( stream, firstAxis )
|
||||||
|
|
||||||
|
# Load second vector
|
||||||
|
skip( stream, secondAxis.Skip )
|
||||||
|
spectrum[ xor( order,3 ), i ] = ReadVector( stream, secondAxis )
|
||||||
|
end
|
||||||
|
|
||||||
|
return( spectrum )
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# *******************************************************************
|
||||||
|
# Decoding base64 blocks
|
||||||
|
# *******************************************************************
|
||||||
|
|
||||||
|
# Decoding exchange table
|
||||||
|
base64dec = Vector{UInt8}( [
|
||||||
|
62, # (01) + -> 43d, 0x2B
|
||||||
|
0, # (02) ,
|
||||||
|
0, # (03) -
|
||||||
|
0, # (04) .
|
||||||
|
63, # (05) /
|
||||||
|
52, # (06) 0
|
||||||
|
53, # (07) 1
|
||||||
|
54, # (08) 2
|
||||||
|
55, # (09) 3
|
||||||
|
56, # (10) 4
|
||||||
|
57, # (11) 5
|
||||||
|
58, # (12) 6
|
||||||
|
59, # (13) 7
|
||||||
|
60, # (14) 8
|
||||||
|
61, # (15) 9
|
||||||
|
0, # (16) :
|
||||||
|
0, # (17) ;
|
||||||
|
0, # (18) <
|
||||||
|
0, # (19) =
|
||||||
|
0, # (20) >
|
||||||
|
0, # (21) ?
|
||||||
|
0, # (22) @
|
||||||
|
0, # (23) A
|
||||||
|
1, # (24) B
|
||||||
|
2, # (25) C
|
||||||
|
3, # (26) D
|
||||||
|
4, # (27) E
|
||||||
|
5, # (28) F
|
||||||
|
6, # (29) G
|
||||||
|
7, # (40) H
|
||||||
|
8, # (41) I
|
||||||
|
9, # (42) J
|
||||||
|
10, # (43) K
|
||||||
|
11, # (44) L
|
||||||
|
12, # (45) M
|
||||||
|
13, # (46) N
|
||||||
|
14, # (47) O
|
||||||
|
15, # (48) P
|
||||||
|
16, # (49) Q
|
||||||
|
17, # (40) R
|
||||||
|
18, # (41) S
|
||||||
|
19, # (42) T
|
||||||
|
20, # (43) U
|
||||||
|
21, # (44) V
|
||||||
|
22, # (45) W
|
||||||
|
23, # (46) X
|
||||||
|
24, # (47) Y
|
||||||
|
25, # (48) Z
|
||||||
|
0, # (49) [
|
||||||
|
0, # (50) \
|
||||||
|
0, # (51) ]
|
||||||
|
0, # (52) ^
|
||||||
|
0, # (53) _
|
||||||
|
0, # (54) `
|
||||||
|
26, # (55) a
|
||||||
|
27, # (56) b
|
||||||
|
28, # (57) c
|
||||||
|
29, # (58) d
|
||||||
|
30, # (59) e
|
||||||
|
31, # (60) f
|
||||||
|
32, # (61) g
|
||||||
|
33, # (62) h
|
||||||
|
34, # (63) i
|
||||||
|
35, # (64) j
|
||||||
|
36, # (65) k
|
||||||
|
37, # (66) l
|
||||||
|
38, # (67) m
|
||||||
|
39, # (68) n
|
||||||
|
40, # (69) o
|
||||||
|
41, # (70) p
|
||||||
|
42, # (71) q
|
||||||
|
43, # (72) r
|
||||||
|
44, # (73) s
|
||||||
|
45, # (74) t
|
||||||
|
46, # (75) u
|
||||||
|
47, # (76) v
|
||||||
|
48, # (77) w
|
||||||
|
49, # (78) x
|
||||||
|
50, # (79) y
|
||||||
|
51, # (80) z
|
||||||
|
] )
|
||||||
|
|
||||||
|
# base64 decoding
|
||||||
|
function DecodeTriplet( data, base64 )
|
||||||
|
|
||||||
|
# Character to binary equivalent
|
||||||
|
data = data .- 0x2A
|
||||||
|
data[1] = base64[ data[1] ]
|
||||||
|
data[2] = base64[ data[2] ]
|
||||||
|
data[3] = base64[ data[3] ]
|
||||||
|
data[4] = base64[ data[4] ]
|
||||||
|
|
||||||
|
# Get Triplet
|
||||||
|
decode = Array{UInt8}( undef,3 )
|
||||||
|
decode[1] = data[1] << 2 + data[2] >> 4
|
||||||
|
decode[2] = data[2] << 4 + data[3] >> 2
|
||||||
|
decode[3] = data[3] << 6 + data[4]
|
||||||
|
return decode
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Decode64( data )
|
||||||
|
|
||||||
|
# Reserve uninitialized memory
|
||||||
|
block = divrem( sizeof( data ), 4 )
|
||||||
|
decode = Array{UInt8}( undef, 3*block[1] )
|
||||||
|
|
||||||
|
# Recover binary from Chars
|
||||||
|
index = 1
|
||||||
|
first = 1
|
||||||
|
last = 3
|
||||||
|
|
||||||
|
for i in 1:block[1]
|
||||||
|
decode[first:last] = DecodeTriplet( data[index:index+3], base64dec )
|
||||||
|
index += 4
|
||||||
|
first += 3
|
||||||
|
last += 3
|
||||||
|
end
|
||||||
|
|
||||||
|
# Adjust block size
|
||||||
|
if data[end-1] == 0x3D
|
||||||
|
decode = decode[1:end-2]
|
||||||
|
elseif data[end] == 0x3D
|
||||||
|
decode = decode[1:end-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
return decode
|
||||||
|
|
||||||
|
end
|
||||||
@ -1,7 +1,11 @@
|
|||||||
using Pkg
|
using Pkg
|
||||||
sTime=time()
|
sTime=time()
|
||||||
Pkg.activate(".")
|
Pkg.activate(".")
|
||||||
Pkg.instantiate()
|
# Only instantiate in development mode
|
||||||
|
if get(ENV, "GENIE_ENV", "dev") != "prod"
|
||||||
|
@info "Development environment detected. Instantiating packages..."
|
||||||
|
Pkg.instantiate()
|
||||||
|
end
|
||||||
Pkg.gc()
|
Pkg.gc()
|
||||||
|
|
||||||
using Genie
|
using Genie
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user