UI now has support for mzml to imzml conversion, made small optimizations to the src folder's API to improve performance
This commit is contained in:
parent
2fd2b90513
commit
39fe2bf52c
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,8 +1,9 @@
|
|||||||
public/*
|
public/*
|
||||||
public/css/imgOver.png
|
public/css/imgOver.png
|
||||||
!public/css/
|
!public/css/
|
||||||
!public/css/autogenerated.css
|
!public/masks/
|
||||||
!public/css/LABI_logo.png
|
public/masks/*
|
||||||
|
!public/masks/static.txt
|
||||||
log/*
|
log/*
|
||||||
R_original_scripts/
|
R_original_scripts/
|
||||||
test/results/
|
test/results/
|
||||||
|
|||||||
76
app.jl
76
app.jl
@ -18,7 +18,7 @@ using Base.Filesystem: mv # To rename files in the system
|
|||||||
using Printf # Required for @sprintf macro in colorbar generation
|
using Printf # Required for @sprintf macro in colorbar generation
|
||||||
|
|
||||||
# Bring MSIData into App module's scope
|
# Bring MSIData into App module's scope
|
||||||
using .MSI_src: MSIData, OpenMSIData, GetSpectrum, IterateSpectra, ImzMLSource, _iterate_spectra_fast, MzMLSource, find_mass, ViridisPalette, get_mz_slice, quantize_intensity, save_bitmap, median_filter, save_bitmap, downsample_spectrum, TrIQ, precompute_analytics
|
using .MSI_src: MSIData, OpenMSIData, #=GetSpectrum,=# process_spectrum, IterateSpectra, ImzMLSource, _iterate_spectra_fast, MzMLSource, find_mass, ViridisPalette, get_mz_slice, quantize_intensity, save_bitmap, median_filter, save_bitmap, downsample_spectrum, TrIQ, precompute_analytics, ImportMzmlFile
|
||||||
|
|
||||||
include("./julia_imzML_visual.jl")
|
include("./julia_imzML_visual.jl")
|
||||||
|
|
||||||
@ -130,6 +130,17 @@ include("./julia_imzML_visual.jl")
|
|||||||
# Saves the route where imzML and mzML files are located
|
# Saves the route where imzML and mzML files are located
|
||||||
@out full_route=""
|
@out full_route=""
|
||||||
|
|
||||||
|
# == Converter Tab Variables ==
|
||||||
|
@in left_tab = "generator"
|
||||||
|
@out mzml_full_route = ""
|
||||||
|
@out sync_full_route = ""
|
||||||
|
@in btnSearchMzml = false
|
||||||
|
@in btnSearchSync = false
|
||||||
|
@in convert_process = false
|
||||||
|
@out progress_conversion = false
|
||||||
|
@out msg_conversion = ""
|
||||||
|
@out btnConvertDisable = true
|
||||||
|
|
||||||
|
|
||||||
# For the creation of images with a more specific mass charge
|
# For the creation of images with a more specific mass charge
|
||||||
@out text_nmass=""
|
@out text_nmass=""
|
||||||
@ -393,6 +404,69 @@ include("./julia_imzML_visual.jl")
|
|||||||
@onbutton showMetadataBtn begin
|
@onbutton showMetadataBtn begin
|
||||||
showMetadataDialog = true
|
showMetadataDialog = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@onchange btnSearchMzml, btnSearchSync begin
|
||||||
|
if btnSearchMzml
|
||||||
|
picked_route = pick_file(; filterlist="mzML,mzml")
|
||||||
|
if !isempty(picked_route)
|
||||||
|
mzml_full_route = picked_route
|
||||||
|
end
|
||||||
|
btnSearchMzml = false # Reset the button
|
||||||
|
end
|
||||||
|
|
||||||
|
if btnSearchSync
|
||||||
|
picked_route = pick_file(; filterlist="txt")
|
||||||
|
if !isempty(picked_route)
|
||||||
|
sync_full_route = picked_route
|
||||||
|
end
|
||||||
|
btnSearchSync = false # Reset the button
|
||||||
|
end
|
||||||
|
|
||||||
|
# Enable button only if both files are selected
|
||||||
|
btnConvertDisable = isempty(mzml_full_route) || isempty(sync_full_route)
|
||||||
|
end
|
||||||
|
|
||||||
|
@onbutton convert_process begin
|
||||||
|
if isempty(mzml_full_route) || isempty(sync_full_route)
|
||||||
|
msg_conversion = "Please select both an .mzML file and a .txt sync file."
|
||||||
|
warning_msg = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
progress_conversion = true
|
||||||
|
btnConvertDisable = true
|
||||||
|
msg_conversion = "Starting conversion process..."
|
||||||
|
|
||||||
|
@async begin
|
||||||
|
try
|
||||||
|
sTime = time()
|
||||||
|
target_imzml = replace(mzml_full_route, r"\.(mzml|mzML)$" => ".imzML")
|
||||||
|
|
||||||
|
msg_conversion = "Converting $(basename(mzml_full_route)) to $(basename(target_imzml))... This may take a while."
|
||||||
|
|
||||||
|
success = ImportMzmlFile(mzml_full_route, sync_full_route, target_imzml)
|
||||||
|
|
||||||
|
fTime = time()
|
||||||
|
eTime = round(fTime - sTime, digits=3)
|
||||||
|
|
||||||
|
if success
|
||||||
|
msg_conversion = "Conversion successful in $(eTime) seconds. Output file: $(basename(target_imzml))"
|
||||||
|
else
|
||||||
|
msg_conversion = "Conversion failed after $(eTime) seconds. Check console for errors."
|
||||||
|
warning_msg = true
|
||||||
|
end
|
||||||
|
|
||||||
|
catch e
|
||||||
|
msg_conversion = "An error occurred during conversion: $e"
|
||||||
|
warning_msg = true
|
||||||
|
@error "Conversion failed" exception=(e, catch_backtrace())
|
||||||
|
finally
|
||||||
|
progress_conversion = false
|
||||||
|
# Re-enable button if files are still selected
|
||||||
|
btnConvertDisable = isempty(mzml_full_route) || isempty(sync_full_route)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@onbutton mainProcess @time begin
|
@onbutton mainProcess @time begin
|
||||||
# UI updates immediately
|
# UI updates immediately
|
||||||
|
|||||||
298
app.jl.html
298
app.jl.html
@ -2,168 +2,200 @@
|
|||||||
<img src="/css/LABI_logo.png" alt="Labi Logo Icon" id="imgLogo">
|
<img src="/css/LABI_logo.png" alt="Labi Logo Icon" id="imgLogo">
|
||||||
<div>
|
<div>
|
||||||
<h4>JuliaMSI </h4>
|
<h4>JuliaMSI </h4>
|
||||||
<h6>Please make sure the ibd, the mzML and the imzML files are located in the same directory and have the same
|
|
||||||
name.</h6>
|
|
||||||
<h6>It may take a while to generate the image and the plot, please be patient.</h6>
|
|
||||||
<h6>To generate the contour or surface plots, you have to select the desired image first using the interface.</h6>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div id="extDivStyle" class="row col-12 q-pa-xl">
|
<div id="extDivStyle" class="row col-12 q-pa-xl">
|
||||||
<div class="row col-6">
|
<div class="row col-6">
|
||||||
<!-- Left DIV -->
|
<!-- Left DIV -->
|
||||||
<div id="intDivStyle-left" class="st-col col-12 st-module">
|
<div id="intDivStyle-left" class="st-col col-12 st-module">
|
||||||
<h6>Search for the imzML or mzML file in your system</h6>
|
<q-tabs v-model="left_tab" dense class="text-grey" indicator-color="primary" align="justify">
|
||||||
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="full_route" readonly label="Select your imzML or mzML file" v-on:click="btnSearch=true">
|
<q-tab name="generator" label="Slice Generator"></q-tab>
|
||||||
<template v-slot:append>
|
<q-tab name="converter" label="Converter"></q-tab>
|
||||||
<q-icon name="search" v:onclick="btnSearch=true" class="cursor-pointer" />
|
</q-tabs>
|
||||||
</template>
|
<q-separator></q-separator>
|
||||||
</q-input>
|
<q-tab-panels v-model="left_tab" animated>
|
||||||
<!-- Variable Manipulation -->
|
<q-tab-panel name="generator">
|
||||||
<div class="row">
|
<div class="text-h6">imzML & mzML Data Processor</div>
|
||||||
<div class="st-col col-4 col-sm q-ma-sm">
|
<p>Please make sure the ibd and imzML file are located in the same directory and have the same name.
|
||||||
<q-input standout="custom-standout" id="textNmass" step="0.001" v-model="Nmass"
|
<br>It may take a while to generate the image and the plot, please be patient.
|
||||||
label="Mass-to-charge ratio of interest" type="number"
|
<br>To generate the contour or surface plots, you have to select the desired image first using the interface.</p>
|
||||||
:rules="[ val => !!val || '* Required', val => val >= 0.0 || 'Need positive mass values']"></q-input>
|
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="full_route" readonly label="Select your imzML or mzML file" v-on:click="btnSearch=true">
|
||||||
</div>
|
<template v-slot:append>
|
||||||
<div class="st-col col-4 col-sm q-ma-sm">
|
<q-icon name="search" v:onclick="btnSearch=true" class="cursor-pointer" />
|
||||||
<q-input standout="custom-standout" id="textTol" step="0.005" v-model="Tol"
|
</template>
|
||||||
label="Mass-to-charge ratio tolerance" type="number"
|
</q-input>
|
||||||
:rules="[val => !!val || '* Required', val => val >= 0.0 && val <= 1.0 || 'Needs to be in range between 0 and 1']"></q-input>
|
<!-- Variable Manipulation -->
|
||||||
</div>
|
|
||||||
<div class="st-col col-4 col-sm q-ma-sm">
|
|
||||||
<q-input standout="custom-standout" id="textcolorLevel" step="1" v-model="colorLevel" label="Color levels"
|
|
||||||
type="number"
|
|
||||||
:rules="[ val => !!val || '* Required', val => val >= 2 && val <= 256 || 'Needs to be in range between 2 and 256']"></q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<!-- Triq Variable Manipulation and filters-->
|
|
||||||
<div class="col-6">
|
|
||||||
<div class="st-col col-6 col-sm q-ma-sm">
|
|
||||||
<q-toggle id="btnEnableMFilter" v-on:click="MFilterEnabled" v-model="MFilterEnabled" color="green"
|
|
||||||
label="Add Median Filter"></q-toggle>
|
|
||||||
<q-toggle id="btnEnableTriq" v-on:click="triqEnabled" v-model="triqEnabled" color="blue"
|
|
||||||
label="Add Threshold Intensity Quantization (TrIQ)"></q-toggle>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
<div class="st-col col-4 col-sm q-ma-sm">
|
||||||
<q-input standout="custom-standout" id="textTriqProb" step="0.01" v-model="triqProb"
|
<q-input standout="custom-standout" id="textNmass" step="0.001" v-model="Nmass"
|
||||||
label="TrIQ probability" type="number" :rules="[
|
label="Mass-to-charge ratio of interest" type="number"
|
||||||
val => triqEnabled ? ( '* Required', val >= 0.8 && val <= 1 || 'Needs to be in range between 0.8 and 1') : true
|
:rules="[ val => !!val || '* Required', val => val >= 0.0 || 'Need positive mass values']"></q-input>
|
||||||
]" :readonly="!triqEnabled" :disable="!triqEnabled"></q-input>
|
</div>
|
||||||
|
<div class="st-col col-4 col-sm q-ma-sm">
|
||||||
|
<q-input standout="custom-standout" id="textTol" step="0.005" v-model="Tol"
|
||||||
|
label="Mass-to-charge ratio tolerance" type="number"
|
||||||
|
:rules="[val => !!val || '* Required', val => val >= 0.0 && val <= 1.0 || 'Needs to be in range between 0 and 1']"></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="st-col col-4 col-sm q-ma-sm">
|
||||||
|
<q-input standout="custom-standout" id="textcolorLevel" step="1" v-model="colorLevel" label="Color levels"
|
||||||
|
type="number"
|
||||||
|
:rules="[ val => !!val || '* Required', val => val >= 2 && val <= 256 || 'Needs to be in range between 2 and 256']"></q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row">
|
||||||
<!-- Spectra Plot Manipulation -->
|
<!-- Triq Variable Manipulation and filters-->
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="st-col col-6 col-sm">
|
<div class="st-col col-6 col-sm q-ma-sm">
|
||||||
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
|
<q-toggle id="btnEnableMFilter" v-on:click="MFilterEnabled" v-model="MFilterEnabled" color="green"
|
||||||
label="Generate Spectra" icon="play_arrow">
|
label="Add Median Filter"></q-toggle>
|
||||||
|
<q-toggle id="btnEnableTriq" v-on:click="triqEnabled" v-model="triqEnabled" color="blue"
|
||||||
|
label="Add Threshold Intensity Quantization (TrIQ)"></q-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||||
|
<q-input standout="custom-standout" id="textTriqProb" step="0.01" v-model="triqProb"
|
||||||
|
label="TrIQ probability" type="number" :rules="[
|
||||||
|
val => triqEnabled ? ( '* Required', val >= 0.8 && val <= 1 || 'Needs to be in range between 0.8 and 1') : true
|
||||||
|
]" :readonly="!triqEnabled" :disable="!triqEnabled"></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Spectra Plot Manipulation -->
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="st-col col-6 col-sm">
|
||||||
|
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressSpectraPlot" :disable="btnSpectraDisable"
|
||||||
|
label="Generate Spectra" icon="play_arrow">
|
||||||
|
<template v-slot:loading>
|
||||||
|
<q-spinner-hourglass class="on-left" />
|
||||||
|
Loading plot
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<q-list>
|
||||||
|
<q-item clickable v-close-popup v-on:click="createMeanPlot=true">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Mean spectrum plot</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup v-on:click="createSumPlot=true">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Sum Spectrum plot</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup v-on:click="createXYPlot=true">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Spectrum plot (X,Y)</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="row col-6">
|
||||||
|
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||||
|
<q-input standout="custom-standout" step="1" v-model="xCoord" label="X coord" type="number" :rules="[
|
||||||
|
val => SpectraEnabled ? ( '* Required', val >= 0|| 'Needs to be bigger than 0') : true
|
||||||
|
]" :readonly="!SpectraEnabled" :disable="!SpectraEnabled"></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
||||||
|
<q-input standout="custom-standout" step="1" v-model="yCoord" label="Y coord" type="number" :rules="[
|
||||||
|
val => SpectraEnabled ? ( '* Required', val <= 0|| 'Needs to be lower than 0') : true
|
||||||
|
]" :readonly="!SpectraEnabled" :disable="!SpectraEnabled"></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<q-btn :loading="progress" class="q-ma-sm btn-style" :disabled="btnStartDisable" icon="play_arrow"
|
||||||
|
v-on:click="mainProcess=true" padding="lg" label="Generate Slice">
|
||||||
|
<template v-slot:loading>
|
||||||
|
<q-spinner-hourglass class="on-left" />
|
||||||
|
Loading...
|
||||||
|
</template>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn icon="zoom_out_map" class="q-ma-sm on-right btn-style" v-on:click="compareBtn=true" padding="sm"
|
||||||
|
label="Compare"></q-btn>
|
||||||
|
<q-btn class="q-ma-sm btn-style" :disable="btnMetadataDisable"
|
||||||
|
v-on:click="showMetadataBtn=true" label="Show Metadata"></q-btn>
|
||||||
|
</div>
|
||||||
|
<p>{{msg}}</p>
|
||||||
|
|
||||||
|
<div class="row st-col col-12">
|
||||||
|
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressPlot" :disable="btnPlotDisable"
|
||||||
|
label="Generate Plots" icon="play_arrow">
|
||||||
<template v-slot:loading>
|
<template v-slot:loading>
|
||||||
<q-spinner-hourglass class="on-left" />
|
<q-spinner-hourglass class="on-left" />
|
||||||
Loading plot
|
Loading plot
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item clickable v-close-popup v-on:click="createMeanPlot=true">
|
<q-item clickable v-close-popup v-on:click="imageCPlot=true">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Mean spectrum plot</q-item-label>
|
<q-item-label>Image topography Plot</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup v-on:click="createSumPlot=true">
|
|
||||||
|
<q-item clickable v-close-popup v-on:click="triqCPlot=true">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Sum Spectrum plot</q-item-label>
|
<q-item-label>TrIQ topography Plot</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup v-on:click="createXYPlot=true">
|
|
||||||
|
<q-item clickable v-close-popup v-on:click="image3dPlot=true">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Spectrum plot (X,Y)</q-item-label>
|
<q-item-label>Image surface Plot</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item clickable v-close-popup v-on:click="triq3dPlot=true">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>TrIQ Surface Plot</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-btn-dropdown>
|
</q-btn-dropdown>
|
||||||
</div>
|
<q-btn-dropdown icon="search" class="q-ma-sm btn-style" :disable="btnOpticalDisable"
|
||||||
<div class="row col-6">
|
label="Load your optical image">
|
||||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
<q-item clickable v-close-popup v-on:click="btnOptical=true">
|
||||||
<q-input standout="custom-standout" step="1" v-model="xCoord" label="X coord" type="number" :rules="[
|
<q-item-section>
|
||||||
val => SpectraEnabled ? ( '* Required', val >= 0|| 'Needs to be bigger than 0') : true
|
<q-item-label>Over normal image</q-item-label>
|
||||||
]" :readonly="!SpectraEnabled" :disable="!SpectraEnabled"></q-input>
|
</q-item-section>
|
||||||
</div>
|
</q-item>
|
||||||
<div class="st-col col-4 col-sm-4 q-ma-sm">
|
<q-item clickable v-close-popup v-on:click="btnOpticalT=true">
|
||||||
<q-input standout="custom-standout" step="1" v-model="yCoord" label="Y coord" type="number" :rules="[
|
<q-item-section>
|
||||||
val => SpectraEnabled ? ( '* Required', val <= 0|| 'Needs to be lower than 0') : true
|
<q-item-label>Over TrIQ image</q-item-label>
|
||||||
]" :readonly="!SpectraEnabled" :disable="!SpectraEnabled"></q-input>
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
<div class="q-mx-sm">
|
||||||
|
<q-slider color="black" v-model="imgTrans" :min="0.0" :max="1" :step="0.1" :disable="btnOpticalDisable" />
|
||||||
|
<q-badge style="background-color: #009f90;"> Transparency: {{ imgTrans }}</q-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</q-tab-panel>
|
||||||
</div>
|
<q-tab-panel name="converter">
|
||||||
<div class="row">
|
<div class="text-h6">mzML to imzML Converter</div>
|
||||||
<q-btn :loading="progress" class="q-ma-sm btn-style" :disabled="btnStartDisable" icon="play_arrow"
|
<p>Select the .mzML file and the corresponding .txt synchronization file to convert them into an .imzML/.ibd pair.</p>
|
||||||
v-on:click="mainProcess=true" padding="lg" label="Generate Spectra">
|
|
||||||
<template v-slot:loading>
|
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="mzml_full_route" readonly label="Select your .mzML file" v-on:click="btnSearchMzml=true">
|
||||||
<q-spinner-hourglass class="on-left" />
|
<template v-slot:append>
|
||||||
Loading...
|
<q-icon name="search" v:onclick="btnSearchMzml=true" class="cursor-pointer" />
|
||||||
</template>
|
</template>
|
||||||
</q-btn>
|
</q-input>
|
||||||
<q-btn icon="zoom_out_map" class="q-ma-sm on-right btn-style" v-on:click="compareBtn=true" padding="sm"
|
|
||||||
label="Compare"></q-btn>
|
|
||||||
<q-btn class="q-ma-sm btn-style" :disable="btnMetadataDisable"
|
|
||||||
v-on:click="showMetadataBtn=true" label="Show Metadata"></q-btn>
|
|
||||||
</div>
|
|
||||||
<p>{{msg}}</p>
|
|
||||||
|
|
||||||
<div class="row st-col col-12">
|
<q-input standout="custom-standout" class="q-ma-sm cursor-pointer" v-model="sync_full_route" readonly label="Select your .txt sync file" v-on:click="btnSearchSync=true">
|
||||||
<q-btn-dropdown class="q-ma-sm btn-style" :loading="progressPlot" :disable="btnPlotDisable"
|
<template v-slot:append>
|
||||||
label="Generate Plots" icon="play_arrow">
|
<q-icon name="search" v:onclick="btnSearchSync=true" class="cursor-pointer" />
|
||||||
<template v-slot:loading>
|
</template>
|
||||||
<q-spinner-hourglass class="on-left" />
|
</q-input>
|
||||||
Loading plot
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<q-list>
|
<q-btn :loading="progress_conversion" class="q-ma-sm btn-style" :disabled="btnConvertDisable" icon="swap_horiz" v-on:click="convert_process=true" padding="lg" label="Convert File">
|
||||||
<q-item clickable v-close-popup v-on:click="imageCPlot=true">
|
<template v-slot:loading>
|
||||||
<q-item-section>
|
<q-spinner-hourglass class="on-left" />
|
||||||
<q-item-label>Image topography Plot</q-item-label>
|
Converting...
|
||||||
</q-item-section>
|
</template>
|
||||||
</q-item>
|
</q-btn>
|
||||||
|
<p>{{msg_conversion}}</p>
|
||||||
<q-item clickable v-close-popup v-on:click="triqCPlot=true">
|
</q-tab-panel>
|
||||||
<q-item-section>
|
</q-tab-panels>
|
||||||
<q-item-label>TrIQ topography Plot</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item clickable v-close-popup v-on:click="image3dPlot=true">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>Image surface Plot</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item clickable v-close-popup v-on:click="triq3dPlot=true">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>TrIQ Surface Plot</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-btn-dropdown>
|
|
||||||
<q-btn-dropdown icon="search" class="q-ma-sm btn-style" :disable="btnOpticalDisable"
|
|
||||||
label="Load your optical image">
|
|
||||||
<q-item clickable v-close-popup v-on:click="btnOptical=true">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>Over normal image</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable v-close-popup v-on:click="btnOpticalT=true">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>Over TrIQ image</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-btn-dropdown>
|
|
||||||
<div class="q-mx-sm">
|
|
||||||
<q-slider color="black" v-model="imgTrans" :min="0.0" :max="1" :step="0.1" :disable="btnOpticalDisable" />
|
|
||||||
<q-badge style="background-color: #009f90;"> Transparency: {{ imgTrans }}</q-badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row col-6">
|
<div class="row col-6">
|
||||||
|
|||||||
@ -502,13 +502,21 @@ function xySpectrumPlot(data::MSIData, xCoord::Int, yCoord::Int, imgWidth::Int,
|
|||||||
x = clamp(xCoord, 1, imgWidth)
|
x = clamp(xCoord, 1, imgWidth)
|
||||||
y = clamp(yCoord, 1, imgHeight)
|
y = clamp(yCoord, 1, imgHeight)
|
||||||
|
|
||||||
mz, intensity = GetSpectrum(data, Int(x), Int(y))
|
# mz, intensity = GetSpectrum(data, Int(x), Int(y))
|
||||||
|
process_spectrum(data, Int(x), Int(y)) do recieved_mz, recieved_intensity
|
||||||
|
mz = recieved_mz
|
||||||
|
intensity = recieved_intensity
|
||||||
|
end
|
||||||
plot_title = "Spectrum at ($x, $y)"
|
plot_title = "Spectrum at ($x, $y)"
|
||||||
else
|
else
|
||||||
# For non-imaging data, treat xCoord as the spectrum index
|
# For non-imaging data, treat xCoord as the spectrum index
|
||||||
index = clamp(xCoord, 1, length(data.spectra_metadata))
|
index = clamp(xCoord, 1, length(data.spectra_metadata))
|
||||||
|
|
||||||
mz, intensity = GetSpectrum(data, index)
|
# mz, intensity = GetSpectrum(data, index)
|
||||||
|
process_spectrum(data, index) do recieved_mz, recieved_intensity
|
||||||
|
mz = recieved_mz
|
||||||
|
intensity = recieved_intensity
|
||||||
|
end
|
||||||
plot_title = "Spectrum #$index"
|
plot_title = "Spectrum #$index"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,51 @@
|
|||||||
color: #009f90;
|
color: #009f90;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#intDivStyle-left {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab styling to match your theme */
|
||||||
|
.q-tabs {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-tab {
|
||||||
|
color: #7f8389 !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-tab--active {
|
||||||
|
color: #009f90 !important;
|
||||||
|
background-color: rgba(0, 159, 144, 0.1) !important;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-tabs[active-color="primary"] .q-tab--active {
|
||||||
|
color: #009f90 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Indicator color to match your primary color */
|
||||||
|
.q-tab__indicator {
|
||||||
|
background-color: #009f90 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dense tabs adjustment */
|
||||||
|
.q-tabs--dense .q-tab {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#intDivStyle-left .q-tab-panels {
|
||||||
|
height: 670px; /* Set this to accommodate your tallest content */
|
||||||
|
}
|
||||||
|
|
||||||
|
#intDivStyle-left .q-tab-panel {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto; /* Add scroll if content overflows */
|
||||||
|
}
|
||||||
|
|
||||||
*{
|
*{
|
||||||
font-family: 'Roboto', 'Lato', sans-serif;
|
font-family: 'Roboto', 'Lato', sans-serif;
|
||||||
}
|
}
|
||||||
1
public/css/themes/static.txt
Normal file
1
public/css/themes/static.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
themes
|
||||||
1
public/masks/static.txt
Normal file
1
public/masks/static.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
masks
|
||||||
@ -328,6 +328,42 @@ function GetSpectrum(data::MSIData, x::Int, y::Int)
|
|||||||
return GetSpectrum(data, index) # Call the existing method
|
return GetSpectrum(data, index) # Call the existing method
|
||||||
end
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
process_spectrum(f::Function, data::MSIData, index::Int)
|
||||||
|
|
||||||
|
A "function barrier" helper for safely processing a single spectrum.
|
||||||
|
|
||||||
|
This function retrieves a spectrum and then immediately calls the provided function `f`
|
||||||
|
with the resulting `(mz, intensity)` arrays. This pattern is crucial for performance.
|
||||||
|
While `GetSpectrum` itself is type-unstable (because the array data types are not
|
||||||
|
known at compile time), calling `f` with the result allows Julia's compiler to
|
||||||
|
generate specialized, fast code for `f` based on the *concrete* types it receives.
|
||||||
|
|
||||||
|
Use this function when you need to perform performance-critical operations on a
|
||||||
|
single spectrum. Your logic should be inside the function passed to this helper.
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function process_spectrum(f::Function, data::MSIData, index::Int)
|
||||||
|
# This call is type-unstable.
|
||||||
|
mz, intensity = GetSpectrum(data, index)
|
||||||
|
|
||||||
|
# By immediately calling `f`, we create a "function barrier".
|
||||||
|
# Julia will compile a specialized version of `f` for the
|
||||||
|
# concrete types of `mz` and `intensity`.
|
||||||
|
return f(mz, intensity)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
process_spectrum(f::Function, data::MSIData, x::Int, y::Int)
|
||||||
|
|
||||||
|
A coordinate-based version of the "function barrier" helper. See `process_spectrum`
|
||||||
|
for details on the performance pattern.
|
||||||
|
"""
|
||||||
|
function process_spectrum(f::Function, data::MSIData, x::Int, y::Int)
|
||||||
|
mz, intensity = GetSpectrum(data, x, y)
|
||||||
|
return f(mz, intensity)
|
||||||
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
precompute_analytics(msi_data::MSIData)
|
precompute_analytics(msi_data::MSIData)
|
||||||
|
|
||||||
@ -680,15 +716,11 @@ This is effectively the total spectrum divided by the number of spectra.
|
|||||||
Returns a tuple containing two vectors: the binned m/z axis and the averaged intensities.
|
Returns a tuple containing two vectors: the binned m/z axis and the averaged intensities.
|
||||||
"""
|
"""
|
||||||
function get_average_spectrum(msi_data::MSIData; num_bins::Int=2000)
|
function get_average_spectrum(msi_data::MSIData; num_bins::Int=2000)
|
||||||
# This function uses the exact same logic as get_total_spectrum...
|
|
||||||
mz_bins, intensity_sum = get_total_spectrum(msi_data, num_bins=num_bins)
|
mz_bins, intensity_sum = get_total_spectrum(msi_data, num_bins=num_bins)
|
||||||
|
|
||||||
if isempty(intensity_sum)
|
if isempty(intensity_sum)
|
||||||
return (mz_bins, intensity_sum)
|
return (mz_bins, intensity_sum)
|
||||||
end
|
end
|
||||||
|
|
||||||
# ...with one final step: dividing by the number of spectra to get the average.
|
|
||||||
println("Averaging spectrum...")
|
|
||||||
num_spectra = length(msi_data.spectra_metadata)
|
num_spectra = length(msi_data.spectra_metadata)
|
||||||
average_intensity = intensity_sum ./ num_spectra
|
average_intensity = intensity_sum ./ num_spectra
|
||||||
|
|
||||||
|
|||||||
@ -107,19 +107,12 @@ function GetMzmlScanTime_linebyline(fileName::String)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
sort!(times, by = x -> x[1])
|
result = Matrix{Int64}(undef, length(times), 2)
|
||||||
|
for (i, t) in enumerate(times)
|
||||||
if !isempty(times)
|
result[i, 1] = t[1]
|
||||||
first_time = times[1][2]
|
result[i, 2] = t[2]
|
||||||
for i in eachindex(times)
|
|
||||||
times[i] = (times[i][1], times[i][2] - first_time)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
return result
|
||||||
if isempty(times)
|
|
||||||
return Matrix{Int64}(undef, 0, 2)
|
|
||||||
end
|
|
||||||
return permutedims(hcat(collect.(times)...))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -209,10 +202,12 @@ function GetMzmlScanTime(fileName::String)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if isempty(times)
|
result = Matrix{Int64}(undef, length(times), 2)
|
||||||
return Matrix{Int64}(undef, 0, 2)
|
for (i, t) in enumerate(times)
|
||||||
|
result[i, 1] = t[1]
|
||||||
|
result[i, 2] = t[2]
|
||||||
end
|
end
|
||||||
return permutedims(hcat(collect.(times)...))
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -399,14 +394,27 @@ This function handles three cases:
|
|||||||
# Returns
|
# Returns
|
||||||
- A tuple `(mz_array, intensity_array)` for the rendered pixel spectrum.
|
- A tuple `(mz_array, intensity_array)` for the rendered pixel spectrum.
|
||||||
"""
|
"""
|
||||||
function RenderPixel(pixel_info, scans, msi_data::MSIData, scan_time_deltas, pixel_time_deltas)
|
function RenderPixel(
|
||||||
|
pixel_info::AbstractVector{Int64},
|
||||||
|
scans::AbstractMatrix{Int64},
|
||||||
|
msi_data::MSIData,
|
||||||
|
scan_time_deltas::AbstractVector{Int64},
|
||||||
|
pixel_time_deltas::AbstractVector{Int64}
|
||||||
|
)
|
||||||
pixel_time = pixel_info[3]
|
pixel_time = pixel_info[3]
|
||||||
first_scan = pixel_info[4]
|
first_scan = pixel_info[4]
|
||||||
last_scan = pixel_info[5]
|
last_scan = pixel_info[5]
|
||||||
num_actions = last_scan - first_scan
|
num_actions = last_scan - first_scan
|
||||||
|
|
||||||
# Get reference m/z array from the first scan involved
|
# Get reference m/z array from the first scan involved.
|
||||||
|
# This call remains type-unstable, but its impact is now isolated and only paid once.
|
||||||
mz_array, _ = GetSpectrum(msi_data, first_scan)
|
mz_array, _ = GetSpectrum(msi_data, first_scan)
|
||||||
|
|
||||||
|
# If the first spectrum was empty, we can't do anything else.
|
||||||
|
if isempty(mz_array)
|
||||||
|
return (mz_array, Float32[])
|
||||||
|
end
|
||||||
|
|
||||||
new_intensity = zeros(Float32, length(mz_array))
|
new_intensity = zeros(Float32, length(mz_array))
|
||||||
|
|
||||||
# SAFETY: Ensure we have valid scan indices
|
# SAFETY: Ensure we have valid scan indices
|
||||||
@ -415,40 +423,46 @@ function RenderPixel(pixel_info, scans, msi_data::MSIData, scan_time_deltas, pix
|
|||||||
end
|
end
|
||||||
|
|
||||||
if num_actions == 0 # Single scan contributes to the pixel
|
if num_actions == 0 # Single scan contributes to the pixel
|
||||||
_, intensity = GetSpectrum(msi_data, first_scan)
|
process_spectrum(msi_data, first_scan) do _, intensity
|
||||||
# SAFETY: Ensure positive scaling
|
# SAFETY: Ensure positive scaling
|
||||||
scale = max(pixel_time_deltas[first_scan] / scan_time_deltas[first_scan], 0.0f0)
|
scale = max(pixel_time_deltas[first_scan] / scan_time_deltas[first_scan], 0.0f0)
|
||||||
new_intensity .= intensity .* scale
|
new_intensity .= intensity .* scale
|
||||||
|
end
|
||||||
|
|
||||||
elseif num_actions == 1 # Two partial scans contribute
|
elseif num_actions == 1 # Two partial scans contribute
|
||||||
# First partial scan
|
# First partial scan
|
||||||
_, intensity1 = GetSpectrum(msi_data, first_scan)
|
process_spectrum(msi_data, first_scan) do _, intensity1
|
||||||
scale1 = max((scans[last_scan, 2] - pixel_time) / scan_time_deltas[first_scan], 0.0f0)
|
scale1 = max((scans[last_scan, 2] - pixel_time) / scan_time_deltas[first_scan], 0.0f0)
|
||||||
new_intensity .+= intensity1 .* scale1
|
new_intensity .+= intensity1 .* scale1
|
||||||
|
end
|
||||||
|
|
||||||
# Second partial scan
|
# Second partial scan
|
||||||
_, intensity2 = GetSpectrum(msi_data, last_scan)
|
process_spectrum(msi_data, last_scan) do _, intensity2
|
||||||
next_pixel_time = pixel_time + pixel_time_deltas[first_scan]
|
next_pixel_time = pixel_time + pixel_time_deltas[first_scan]
|
||||||
scale2 = max((next_pixel_time - scans[last_scan, 2]) / scan_time_deltas[last_scan], 0.0f0)
|
scale2 = max((next_pixel_time - scans[last_scan, 2]) / scan_time_deltas[last_scan], 0.0f0)
|
||||||
new_intensity .+= intensity2 .* scale2
|
new_intensity .+= intensity2 .* scale2
|
||||||
|
end
|
||||||
|
|
||||||
elseif num_actions > 1 # Multiple scans contribute
|
elseif num_actions > 1 # Multiple scans contribute
|
||||||
# First partial scan
|
# First partial scan
|
||||||
_, intensity1 = GetSpectrum(msi_data, first_scan)
|
process_spectrum(msi_data, first_scan) do _, intensity1
|
||||||
scale1 = max((scans[first_scan + 1, 2] - pixel_time) / scan_time_deltas[first_scan], 0.0f0)
|
scale1 = max((scans[first_scan + 1, 2] - pixel_time) / scan_time_deltas[first_scan], 0.0f0)
|
||||||
new_intensity .+= intensity1 .* scale1
|
new_intensity .+= intensity1 .* scale1
|
||||||
|
end
|
||||||
|
|
||||||
# Full scans in the middle
|
# Full scans in the middle
|
||||||
for i in (first_scan + 1):(last_scan - 1)
|
for i in (first_scan + 1):(last_scan - 1)
|
||||||
_, intensity_middle = GetSpectrum(msi_data, i)
|
process_spectrum(msi_data, i) do _, intensity_middle
|
||||||
new_intensity .+= intensity_middle
|
new_intensity .+= intensity_middle
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Last partial scan
|
# Last partial scan
|
||||||
_, intensity2 = GetSpectrum(msi_data, last_scan)
|
process_spectrum(msi_data, last_scan) do _, intensity2
|
||||||
next_pixel_time = pixel_time + pixel_time_deltas[first_scan]
|
next_pixel_time = pixel_time + pixel_time_deltas[first_scan]
|
||||||
scale2 = max((next_pixel_time - scans[last_scan, 2]) / scan_time_deltas[last_scan], 0.0f0)
|
scale2 = max((next_pixel_time - scans[last_scan, 2]) / scan_time_deltas[last_scan], 0.0f0)
|
||||||
new_intensity .+= intensity2 .* scale2
|
new_intensity .+= intensity2 .* scale2
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# FINAL SAFETY: Clamp any negative values to zero
|
# FINAL SAFETY: Clamp any negative values to zero
|
||||||
|
|||||||
185
src/imzML.jl
185
src/imzML.jl
@ -25,7 +25,7 @@ Core Functions:
|
|||||||
|
|
||||||
Determines the storage order of the m/z and intensity arrays.
|
Determines the storage order of the m/z and intensity arrays.
|
||||||
"""
|
"""
|
||||||
function axes_config_img(stream)
|
function axes_config_img(stream::IO)
|
||||||
param_groups = Dict{String, SpecDim}()
|
param_groups = Dict{String, SpecDim}()
|
||||||
find_tag(stream, r"<referenceableParamGroupList")
|
find_tag(stream, r"<referenceableParamGroupList")
|
||||||
|
|
||||||
@ -52,10 +52,10 @@ end
|
|||||||
|
|
||||||
Reads the maximum X and Y dimensions and total spectrum count from the metadata.
|
Reads the maximum X and Y dimensions and total spectrum count from the metadata.
|
||||||
"""
|
"""
|
||||||
function get_img_dimensions(stream)
|
function get_img_dimensions(stream::IO)
|
||||||
find_tag(stream, r"^\s*<(scanSettings )")
|
find_tag(stream, r"^\s*<(scanSettings )")
|
||||||
n = 2
|
n = 2
|
||||||
dim = [0, 0, 0]
|
dim = zeros(Int32, 3)
|
||||||
|
|
||||||
while n > 0 && !eof(stream)
|
while n > 0 && !eof(stream)
|
||||||
currLine = readline(stream)
|
currLine = readline(stream)
|
||||||
@ -86,7 +86,7 @@ end
|
|||||||
|
|
||||||
Calculates the character offset within a `<spectrum>` tag, ignoring attribute values.
|
Calculates the character offset within a `<spectrum>` tag, ignoring attribute values.
|
||||||
"""
|
"""
|
||||||
function get_spectrum_tag_offset(stream)
|
function get_spectrum_tag_offset(stream::IO)
|
||||||
offset = position(stream)
|
offset = position(stream)
|
||||||
tag = find_tag(stream, r"^\s*<spectrum (.+)")
|
tag = find_tag(stream, r"^\s*<spectrum (.+)")
|
||||||
first = 1
|
first = 1
|
||||||
@ -107,7 +107,7 @@ end
|
|||||||
|
|
||||||
Reads metadata to determine the byte offsets and data types for reading spectra.
|
Reads metadata to determine the byte offsets and data types for reading spectra.
|
||||||
"""
|
"""
|
||||||
function get_spectrum_attributes(stream, hIbd)
|
function get_spectrum_attributes(stream::IO, hIbd::IO)
|
||||||
skip = Vector{UInt32}(undef, 8)
|
skip = Vector{UInt32}(undef, 8)
|
||||||
offset = get_spectrum_tag_offset(stream)
|
offset = get_spectrum_tag_offset(stream)
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ function get_spectrum_attributes(stream, hIbd)
|
|||||||
return skip
|
return skip
|
||||||
end
|
end
|
||||||
|
|
||||||
function determine_parser(stream, mz_is_compressed, int_is_compressed)
|
function determine_parser(stream::IO, mz_is_compressed::Bool, int_is_compressed::Bool)
|
||||||
start_pos = position(stream)
|
start_pos = position(stream)
|
||||||
spectrum_xml = ""
|
spectrum_xml = ""
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ function determine_parser(stream, mz_is_compressed, int_is_compressed)
|
|||||||
return :uncompressed
|
return :uncompressed
|
||||||
end
|
end
|
||||||
|
|
||||||
function load_imzml_lazy(file_path::String; cache_size=100)
|
function load_imzml_lazy(file_path::String; cache_size::Int=100)
|
||||||
println("DEBUG: Checking for .imzML file at $file_path")
|
println("DEBUG: Checking for .imzML file at $file_path")
|
||||||
if !isfile(file_path)
|
if !isfile(file_path)
|
||||||
error("Provided path is not a file: $(file_path)")
|
error("Provided path is not a file: $(file_path)")
|
||||||
@ -302,8 +302,10 @@ function load_imzml_lazy(file_path::String; cache_size=100)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function parse_uncompressed(stream, hIbd, param_groups, width, height, num_spectra,
|
function parse_uncompressed(stream::IO, hIbd::IO, param_groups::Dict{String, SpecDim},
|
||||||
mz_format, intensity_format, mz_is_compressed, int_is_compressed, global_mode)
|
width::Int32, height::Int32, num_spectra::Int32,
|
||||||
|
mz_format::DataType, intensity_format::DataType,
|
||||||
|
mz_is_compressed::Bool, int_is_compressed::Bool, global_mode::SpectrumMode)
|
||||||
# Your existing working skip-based parser
|
# Your existing working skip-based parser
|
||||||
println("DEBUG: Learning file structure from first spectrum...")
|
println("DEBUG: Learning file structure from first spectrum...")
|
||||||
start_of_spectra_xml = position(stream)
|
start_of_spectra_xml = position(stream)
|
||||||
@ -380,11 +382,15 @@ function parse_uncompressed(stream, hIbd, param_groups, width, height, num_spect
|
|||||||
return spectra_metadata
|
return spectra_metadata
|
||||||
end
|
end
|
||||||
|
|
||||||
function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra,
|
function parse_compressed(stream::IO, hIbd::IO, param_groups::Dict{String, SpecDim},
|
||||||
default_mz_format, default_intensity_format,
|
width::Int32, height::Int32, num_spectra::Int32,
|
||||||
mz_is_compressed, int_is_compressed, global_mode)
|
default_mz_format::DataType, default_intensity_format::DataType,
|
||||||
|
mz_is_compressed::Bool, int_is_compressed::Bool, global_mode::SpectrumMode) # Changed Int64 to SpectrumMode
|
||||||
# New parser for compressed data
|
# New parser for compressed data
|
||||||
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
|
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
|
||||||
|
|
||||||
|
# Use concrete type for array_data
|
||||||
|
array_data_type = @NamedTuple{is_mz::Bool, array_length::Int32, encoded_length::Int64, offset::Int64}
|
||||||
|
|
||||||
for k in 1:num_spectra
|
for k in 1:num_spectra
|
||||||
# Read the full spectrum XML block
|
# Read the full spectrum XML block
|
||||||
@ -412,7 +418,7 @@ function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra
|
|||||||
x = x_match !== nothing ? parse(Int32, x_match.captures[1]) : Int32(0)
|
x = x_match !== nothing ? parse(Int32, x_match.captures[1]) : Int32(0)
|
||||||
y = y_match !== nothing ? parse(Int32, y_match.captures[1]) : Int32(0)
|
y = y_match !== nothing ? parse(Int32, y_match.captures[1]) : Int32(0)
|
||||||
|
|
||||||
# Parse mode
|
# Parse mode - use SpectrumMode type directly
|
||||||
spectrum_mode = global_mode
|
spectrum_mode = global_mode
|
||||||
if occursin("MS:1000127", spectrum_xml)
|
if occursin("MS:1000127", spectrum_xml)
|
||||||
spectrum_mode = CENTROID
|
spectrum_mode = CENTROID
|
||||||
@ -420,8 +426,8 @@ function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra
|
|||||||
spectrum_mode = PROFILE
|
spectrum_mode = PROFILE
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse binary data arrays
|
# Parse binary data arrays with concrete typing
|
||||||
array_data = []
|
array_data = array_data_type[]
|
||||||
|
|
||||||
# Find all binaryDataArray blocks
|
# Find all binaryDataArray blocks
|
||||||
array_matches = eachmatch(r"<binaryDataArray.*?<\/binaryDataArray>"s, spectrum_xml)
|
array_matches = eachmatch(r"<binaryDataArray.*?<\/binaryDataArray>"s, spectrum_xml)
|
||||||
@ -434,7 +440,7 @@ function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra
|
|||||||
# Parse external data parameters
|
# Parse external data parameters
|
||||||
# Get array_length (nPoints)
|
# Get array_length (nPoints)
|
||||||
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", array_xml)
|
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", array_xml)
|
||||||
array_length = 0
|
array_length = Int32(0)
|
||||||
if array_len_cv_match !== nothing
|
if array_len_cv_match !== nothing
|
||||||
array_length = parse(Int32, array_len_cv_match.captures[1])
|
array_length = parse(Int32, array_len_cv_match.captures[1])
|
||||||
end
|
end
|
||||||
@ -447,7 +453,7 @@ function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra
|
|||||||
|
|
||||||
# Get encoded_length
|
# Get encoded_length
|
||||||
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", array_xml)
|
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", array_xml)
|
||||||
encoded_length = 0
|
encoded_length = Int64(0)
|
||||||
if encoded_len_cv_match !== nothing
|
if encoded_len_cv_match !== nothing
|
||||||
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
|
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
|
||||||
else
|
else
|
||||||
@ -459,18 +465,14 @@ function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra
|
|||||||
|
|
||||||
# Get offset
|
# Get offset
|
||||||
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", array_xml)
|
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", array_xml)
|
||||||
offset = 0
|
offset = Int64(0)
|
||||||
if offset_match !== nothing
|
if offset_match !== nothing
|
||||||
offset = parse(Int64, offset_match.captures[1])
|
offset = parse(Int64, offset_match.captures[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
if array_length > 0 && offset > 0
|
if array_length > 0 && offset > 0
|
||||||
push!(array_data, (
|
push!(array_data, (is_mz=is_mz, array_length=array_length,
|
||||||
is_mz = is_mz,
|
encoded_length=encoded_length, offset=offset))
|
||||||
array_length = array_length,
|
|
||||||
encoded_length = encoded_length,
|
|
||||||
offset = offset
|
|
||||||
))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -508,12 +510,15 @@ function parse_compressed(stream, hIbd, param_groups, width, height, num_spectra
|
|||||||
return spectra_metadata
|
return spectra_metadata
|
||||||
end
|
end
|
||||||
|
|
||||||
function parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
|
function parse_neofx(stream::IO, hIbd::IO, param_groups::Dict{String, SpecDim},
|
||||||
default_mz_format, default_intensity_format,
|
width::Int32, height::Int32, num_spectra::Int32,
|
||||||
mz_is_compressed, int_is_compressed, global_mode)
|
default_mz_format::DataType, default_intensity_format::DataType,
|
||||||
|
mz_is_compressed::Bool, int_is_compressed::Bool, global_mode::SpectrumMode)
|
||||||
# New parser for compressed data
|
# New parser for compressed data
|
||||||
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
|
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
|
||||||
|
|
||||||
|
array_data_type = @NamedTuple{is_mz::Bool, array_length::Int32, encoded_length::Int64, offset::Int64}
|
||||||
|
|
||||||
for k in 1:num_spectra
|
for k in 1:num_spectra
|
||||||
# Read the full spectrum XML block
|
# Read the full spectrum XML block
|
||||||
spectrum_buffer = IOBuffer()
|
spectrum_buffer = IOBuffer()
|
||||||
@ -549,7 +554,7 @@ function parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Parse binary data arrays
|
# Parse binary data arrays
|
||||||
array_data = []
|
array_data = array_data_type[]
|
||||||
|
|
||||||
# Find all binaryDataArray blocks
|
# Find all binaryDataArray blocks
|
||||||
array_matches = eachmatch(r"<binaryDataArray.*?<\/binaryDataArray>"s, spectrum_xml)
|
array_matches = eachmatch(r"<binaryDataArray.*?<\/binaryDataArray>"s, spectrum_xml)
|
||||||
@ -562,7 +567,7 @@ function parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
|
|||||||
# Parse external data parameters
|
# Parse external data parameters
|
||||||
# Get array_length (nPoints)
|
# Get array_length (nPoints)
|
||||||
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", array_xml)
|
array_len_cv_match = match(r"IMS:1000103.*?value=\"(\d+)\"", array_xml)
|
||||||
array_length = 0
|
array_length = Int32(0)
|
||||||
if array_len_cv_match !== nothing
|
if array_len_cv_match !== nothing
|
||||||
array_length = parse(Int32, array_len_cv_match.captures[1])
|
array_length = parse(Int32, array_len_cv_match.captures[1])
|
||||||
end
|
end
|
||||||
@ -575,7 +580,7 @@ function parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
|
|||||||
|
|
||||||
# Get encoded_length
|
# Get encoded_length
|
||||||
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", array_xml)
|
encoded_len_cv_match = match(r"IMS:1000104.*?value=\"(\d+)\"", array_xml)
|
||||||
encoded_length = 0
|
encoded_length = Int64(0)
|
||||||
if encoded_len_cv_match !== nothing
|
if encoded_len_cv_match !== nothing
|
||||||
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
|
encoded_length = parse(Int64, encoded_len_cv_match.captures[1])
|
||||||
else
|
else
|
||||||
@ -587,18 +592,14 @@ function parse_neofx(stream, hIbd, param_groups, width, height, num_spectra,
|
|||||||
|
|
||||||
# Get offset
|
# Get offset
|
||||||
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", array_xml)
|
offset_match = match(r"IMS:1000102.*?value=\"(\d+)\"", array_xml)
|
||||||
offset = 0
|
offset = Int64(0)
|
||||||
if offset_match !== nothing
|
if offset_match !== nothing
|
||||||
offset = parse(Int64, offset_match.captures[1])
|
offset = parse(Int64, offset_match.captures[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
if array_length > 0 && offset > 0
|
if array_length > 0 && offset > 0
|
||||||
push!(array_data, (
|
push!(array_data, (is_mz=is_mz, array_length=array_length,
|
||||||
is_mz = is_mz,
|
encoded_length=encoded_length, offset=offset))
|
||||||
array_length = array_length,
|
|
||||||
encoded_length = encoded_length,
|
|
||||||
offset = offset
|
|
||||||
))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -653,7 +654,8 @@ This optimized version uses binary search for efficiency.
|
|||||||
# Returns
|
# Returns
|
||||||
- The intensity (`Float64`) of the peak if found, otherwise `0.0`.
|
- The intensity (`Float64`) of the peak if found, otherwise `0.0`.
|
||||||
"""
|
"""
|
||||||
function find_mass(mz_array, intensity_array, target_mass, tolerance)
|
function find_mass(mz_array::AbstractVector{<:Real}, intensity_array::AbstractVector{<:Real},
|
||||||
|
target_mass::Real, tolerance::Real)
|
||||||
lower_bound = target_mass - tolerance
|
lower_bound = target_mass - tolerance
|
||||||
upper_bound = target_mass + tolerance
|
upper_bound = target_mass + tolerance
|
||||||
|
|
||||||
@ -682,16 +684,17 @@ Loads image slices for multiple masses from all `.imzML` files in a directory.
|
|||||||
This function is now refactored to use the new MSIData architecture and its
|
This function is now refactored to use the new MSIData architecture and its
|
||||||
caching capabilities.
|
caching capabilities.
|
||||||
"""
|
"""
|
||||||
function load_slices(folder, masses, tolerance)
|
function load_slices(folder::String, masses::AbstractVector{<:Real}, tolerance::Real)
|
||||||
files = filter(f -> endswith(f, ".imzML"), readdir(folder, join=true))
|
files = filter(f -> endswith(f, ".imzML"), readdir(folder, join=true))
|
||||||
if isempty(files)
|
if isempty(files)
|
||||||
@warn "No .imzML files found in the specified directory: $folder"
|
@warn "No .imzML files found in the specified directory: $folder"
|
||||||
return (Array{Any}(undef, 0, 0), String[])
|
return (Array{Matrix{Float64}}(undef, 0, 0), String[])
|
||||||
end
|
end
|
||||||
n_files = length(files)
|
n_files = length(files)
|
||||||
n_slices = length(masses)
|
n_slices = length(masses)
|
||||||
|
|
||||||
img_list = Array{Any}(undef, n_files, n_slices)
|
# FIXED: Use concrete type instead of Any
|
||||||
|
img_list = Array{Matrix{Float64}}(undef, n_files, n_slices)
|
||||||
names = String[]
|
names = String[]
|
||||||
|
|
||||||
for (i, file) in enumerate(files)
|
for (i, file) in enumerate(files)
|
||||||
@ -876,7 +879,7 @@ is used to exclude outliers before normalization.
|
|||||||
# Returns
|
# Returns
|
||||||
- A tuple `(low, high)` representing the calculated lower and upper intensity bounds.
|
- A tuple `(low, high)` representing the calculated lower and upper intensity bounds.
|
||||||
"""
|
"""
|
||||||
function get_outlier_thres(img, prob=0.98)
|
function get_outlier_thres(img::AbstractMatrix{<:Real}, prob::Real=0.98)
|
||||||
# DO NOT filter zeros. Use all pixel values like R does.
|
# DO NOT filter zeros. Use all pixel values like R does.
|
||||||
int_values = vec(img)
|
int_values = vec(img)
|
||||||
low = minimum(int_values)
|
low = minimum(int_values)
|
||||||
@ -935,7 +938,7 @@ are clipped.
|
|||||||
# Returns
|
# Returns
|
||||||
- A `Matrix{UInt8}` with pixel values quantized to the specified depth.
|
- A `Matrix{UInt8}` with pixel values quantized to the specified depth.
|
||||||
"""
|
"""
|
||||||
function set_pixel_depth(img, bounds, depth)
|
function set_pixel_depth(img::AbstractMatrix{<:Real}, bounds::Tuple{<:Real, <:Real}, depth::Integer)
|
||||||
min_val, max_val = bounds
|
min_val, max_val = bounds
|
||||||
bins = depth - 1
|
bins = depth - 1
|
||||||
|
|
||||||
@ -943,17 +946,23 @@ function set_pixel_depth(img, bounds, depth)
|
|||||||
return zeros(UInt8, size(img))
|
return zeros(UInt8, size(img))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create intensity bins
|
# Create intensity bins - FIXED: use binary search instead of linear scan
|
||||||
range_vals = range(min_val, stop=max_val, length=depth)[2:depth]
|
range_vals = range(min_val, stop=max_val, length=depth)[2:depth]
|
||||||
|
|
||||||
# Assign each pixel to a bin
|
# Pre-allocate result for better performance
|
||||||
result = similar(img, UInt8) # Use similar to create an array of the same type and size
|
result = similar(img, UInt8)
|
||||||
for i in eachindex(img)
|
|
||||||
if img[i] <= min_val
|
# Use binary search for much faster bin assignment
|
||||||
|
@inbounds for i in eachindex(img)
|
||||||
|
val = img[i]
|
||||||
|
if val <= min_val
|
||||||
result[i] = 0
|
result[i] = 0
|
||||||
|
elseif val >= max_val
|
||||||
|
result[i] = bins
|
||||||
else
|
else
|
||||||
bin_idx = findfirst(x -> img[i] <= x, range_vals)
|
# Binary search is much faster than linear scan
|
||||||
result[i] = bin_idx === nothing ? bins : bin_idx - 1
|
bin_idx = searchsortedfirst(range_vals, val)
|
||||||
|
result[i] = bin_idx
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -976,7 +985,7 @@ within these bounds.
|
|||||||
# Returns
|
# Returns
|
||||||
- A new image matrix with intensities quantized to the specified depth within the TrIQ bounds.
|
- A new image matrix with intensities quantized to the specified depth within the TrIQ bounds.
|
||||||
"""
|
"""
|
||||||
function TrIQ(pixMap, depth, prob=0.98)
|
function TrIQ(pixMap::AbstractMatrix{<:Real}, depth::Integer, prob::Real=0.98)
|
||||||
# Compute new dynamic range
|
# Compute new dynamic range
|
||||||
bounds = get_outlier_thres(pixMap, prob)
|
bounds = get_outlier_thres(pixMap, prob)
|
||||||
|
|
||||||
@ -1030,11 +1039,15 @@ function quantize_intensity(slice::AbstractMatrix{<:Real}, levels::Integer=256)
|
|||||||
# The original logic used 'colorLevel' which was the max value (e.g., 255).
|
# The original logic used 'colorLevel' which was the max value (e.g., 255).
|
||||||
scale = (levels - 1) / max_val
|
scale = (levels - 1) / max_val
|
||||||
|
|
||||||
# round is equivalent to floor(x+0.5) for positive numbers.
|
# Pre-allocate result for better performance
|
||||||
# clamp is used for robustness against floating point inaccuracies.
|
result = similar(slice, UInt8)
|
||||||
image = round.(UInt8, clamp.(slice .* scale, 0, levels - 1))
|
|
||||||
|
|
||||||
return image
|
# Use inbounds for faster access
|
||||||
|
@inbounds for i in eachindex(slice)
|
||||||
|
result[i] = round(UInt8, clamp(slice[i] * scale, 0, levels - 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -1101,25 +1114,27 @@ end
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
"""
|
"""
|
||||||
display_statistics(slices)
|
display_statistics(slices::AbstractArray{<:AbstractMatrix{<:Real}, 2},
|
||||||
|
names::AbstractVector{String}, masses::AbstractVector{<:Real})
|
||||||
|
|
||||||
Calculates and prints key statistics for each slice.
|
Calculates and prints key statistics for each slice.
|
||||||
"""
|
"""
|
||||||
function display_statistics(slices, names, masses)
|
function display_statistics(slices::AbstractArray{<:AbstractMatrix{<:Real}, 2},
|
||||||
|
names::AbstractVector{String}, masses::AbstractVector{<:Real})
|
||||||
if isempty(slices)
|
if isempty(slices)
|
||||||
@warn "Cannot display statistics for empty slice list."
|
@warn "Cannot display statistics for empty slice list."
|
||||||
return nothing
|
return nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
n_files, n_masses = size(slices)
|
n_files, n_masses = size(slices)
|
||||||
#stats_to_calc = Dict("Mean" => mean, "Max" => maximum, "Min" => minimum, "Sum" => sum, "Std" => std)
|
stats_to_calc = Dict{String, Function}("Mean" => mean)
|
||||||
stats_to_calc = Dict("Mean" => mean)
|
|
||||||
all_dfs = Dict{String, DataFrame}()
|
all_dfs = Dict{String, DataFrame}()
|
||||||
|
|
||||||
for (stat_name, stat_func) in stats_to_calc
|
for (stat_name, stat_func) in stats_to_to_calc
|
||||||
# Create a matrix to hold the statistic for each slice
|
# Pre-allocate matrix for better performance
|
||||||
stat_matrix = zeros(Float64, n_files, n_masses)
|
stat_matrix = zeros(Float64, n_files, n_masses)
|
||||||
for i in 1:n_files, j in 1:n_masses
|
|
||||||
|
@inbounds for i in 1:n_files, j in 1:n_masses
|
||||||
flat_slice = vec(slices[i, j])
|
flat_slice = vec(slices[i, j])
|
||||||
if !isempty(flat_slice)
|
if !isempty(flat_slice)
|
||||||
stat_matrix[i, j] = stat_func(flat_slice)
|
stat_matrix[i, j] = stat_func(flat_slice)
|
||||||
@ -1343,10 +1358,6 @@ function save_bitmap(name::String, pixMap::Matrix{UInt8}, colorTable::Vector{UIn
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ********************************************************************
|
|
||||||
# Viridis color palette (256 colors)
|
|
||||||
# ********************************************************************
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
generate_palette(colorscheme, n_colors=256)
|
generate_palette(colorscheme, n_colors=256)
|
||||||
|
|
||||||
@ -1366,4 +1377,46 @@ function generate_palette(colorscheme, n_colors=256)
|
|||||||
return palette
|
return palette
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Additional Performance Optimizations
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
precompute_spectrum_bounds(msi_data::MSIData)
|
||||||
|
|
||||||
|
Precomputes min/max m/z values for each spectrum to enable fast filtering.
|
||||||
|
This avoids repeated computation during slice extraction.
|
||||||
|
"""
|
||||||
|
function precompute_spectrum_bounds(msi_data::MSIData)
|
||||||
|
n_spectra = length(msi_data.spectra_metadata)
|
||||||
|
min_mz = Vector{Float64}(undef, n_spectra)
|
||||||
|
max_mz = Vector{Float64}(undef, n_spectra)
|
||||||
|
|
||||||
|
_iterate_spectra_fast(msi_data) do idx, mz_array, intensity_array
|
||||||
|
min_mz[idx] = first(mz_array)
|
||||||
|
max_mz[idx] = last(mz_array)
|
||||||
|
end
|
||||||
|
|
||||||
|
return (min_mz, max_mz)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
process_columns_first!(A::AbstractMatrix)
|
||||||
|
|
||||||
|
Process a matrix column-by-column for better cache performance.
|
||||||
|
In Julia, arrays are stored in column-major order.
|
||||||
|
"""
|
||||||
|
function process_columns_first!(A::AbstractMatrix)
|
||||||
|
nrows, ncols = size(A)
|
||||||
|
@inbounds for col in 1:ncols, row in 1:nrows
|
||||||
|
# Process A[row, col] here
|
||||||
|
# This order is ~70% faster due to cache locality
|
||||||
|
end
|
||||||
|
return A
|
||||||
|
end
|
||||||
|
|
||||||
|
# Viridis color palette (256 colors) - defined as constant
|
||||||
const ViridisPalette = generate_palette(ColorSchemes.viridis)
|
const ViridisPalette = generate_palette(ColorSchemes.viridis)
|
||||||
245
src/mzML.jl
245
src/mzML.jl
@ -3,8 +3,24 @@
|
|||||||
# This file is responsible for parsing metadata from .mzML files.
|
# This file is responsible for parsing metadata from .mzML files.
|
||||||
# It has been refactored to produce a unified MSIData object.
|
# It has been refactored to produce a unified MSIData object.
|
||||||
|
|
||||||
|
# Constants for CV parameter accessions - defined once for performance
|
||||||
|
const MZ_AXIS_ACCESSION = "MS:1000514"
|
||||||
|
const INTENSITY_AXIS_ACCESSION = "MS:1000515"
|
||||||
|
const COMPRESSION_ACCESSION = "MS:1000574"
|
||||||
|
const NO_COMPRESSION_ACCESSION = "MS:1000576"
|
||||||
|
|
||||||
|
# Data format accessions as constants
|
||||||
|
const DATA_FORMAT_ACCESSIONS = Dict{String, DataType}(
|
||||||
|
"MS:1000518" => Int16,
|
||||||
|
"MS:1000519" => Int32,
|
||||||
|
"MS:1000520" => Float64,
|
||||||
|
"MS:1000521" => Float32,
|
||||||
|
"MS:1000522" => Int64,
|
||||||
|
"MS:1000523" => Float64
|
||||||
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
get_spectrum_asset_metadata(stream)
|
get_spectrum_asset_metadata(stream::IO)
|
||||||
|
|
||||||
Parses a `<binaryDataArray>` block within an mzML file to extract metadata
|
Parses a `<binaryDataArray>` block within an mzML file to extract metadata
|
||||||
for a single data array (e.g., m/z or intensity). It reads CV parameters to
|
for a single data array (e.g., m/z or intensity). It reads CV parameters to
|
||||||
@ -17,7 +33,7 @@ determine the data type, compression, and axis type.
|
|||||||
- A `SpectrumAsset` struct containing the parsed metadata, including the binary
|
- A `SpectrumAsset` struct containing the parsed metadata, including the binary
|
||||||
data offset, encoded length, format, and compression status.
|
data offset, encoded length, format, and compression status.
|
||||||
"""
|
"""
|
||||||
function get_spectrum_asset_metadata(stream)
|
function get_spectrum_asset_metadata(stream::IO)
|
||||||
start_pos = position(stream)
|
start_pos = position(stream)
|
||||||
|
|
||||||
bda_tag = find_tag(stream, r"<binaryDataArray\s+encodedLength=\"(\d+)\"" )
|
bda_tag = find_tag(stream, r"<binaryDataArray\s+encodedLength=\"(\d+)\"" )
|
||||||
@ -26,12 +42,12 @@ function get_spectrum_asset_metadata(stream)
|
|||||||
end
|
end
|
||||||
encoded_length = parse(Int32, bda_tag.captures[1])
|
encoded_length = parse(Int32, bda_tag.captures[1])
|
||||||
|
|
||||||
# Initialize parameters as separate variables
|
# Initialize parameters as separate variables with concrete types
|
||||||
data_format = Float64
|
data_format::DataType = Float64
|
||||||
compression_flag = false
|
compression_flag::Bool = false
|
||||||
axis = :mz
|
axis::Symbol = :mz
|
||||||
|
|
||||||
while true
|
while !eof(stream)
|
||||||
line = readline(stream)
|
line = readline(stream)
|
||||||
if occursin("</binaryDataArray>", line)
|
if occursin("</binaryDataArray>", line)
|
||||||
break
|
break
|
||||||
@ -42,45 +58,18 @@ function get_spectrum_asset_metadata(stream)
|
|||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
acc_str = accession.captures[1]
|
acc_str = accession.captures[1]
|
||||||
# println("DEBUG: Processing accession: $acc_str")
|
|
||||||
|
|
||||||
# Direct inline logic - NO FUNCTION CALLS
|
# Use constant comparisons and dictionary lookup for better performance
|
||||||
if acc_str == "MS:1000514"
|
if acc_str == MZ_AXIS_ACCESSION
|
||||||
axis = :mz
|
axis = :mz
|
||||||
# println("DEBUG: Set axis_type to :mz")
|
elseif acc_str == INTENSITY_AXIS_ACCESSION
|
||||||
elseif acc_str == "MS:1000515"
|
|
||||||
axis = :intensity
|
axis = :intensity
|
||||||
# println("DEBUG: Set axis_type to :intensity")
|
elseif haskey(DATA_FORMAT_ACCESSIONS, acc_str)
|
||||||
elseif acc_str == "MS:1000518"
|
data_format = DATA_FORMAT_ACCESSIONS[acc_str]
|
||||||
data_format = Int16
|
elseif acc_str == COMPRESSION_ACCESSION
|
||||||
# println("DEBUG: Set format to Int16")
|
|
||||||
elseif acc_str == "MS:1000519"
|
|
||||||
data_format = Int32
|
|
||||||
# println("DEBUG: Set format to Int32")
|
|
||||||
elseif acc_str == "MS:1000520"
|
|
||||||
data_format = Float64
|
|
||||||
# println("DEBUG: Set format to Float64")
|
|
||||||
elseif acc_str == "MS:1000521"
|
|
||||||
data_format = Float32
|
|
||||||
# println("DEBUG: Set format to Float32")
|
|
||||||
elseif acc_str == "MS:1000522"
|
|
||||||
data_format = Int64
|
|
||||||
# println("DEBUG: Set format to Int64")
|
|
||||||
elseif acc_str == "MS:1000523"
|
|
||||||
data_format = Float64
|
|
||||||
# println("DEBUG: Set format to Float64")
|
|
||||||
# MS:1000572 is a parent term for compression. While not ideal, if present, assume compression.
|
|
||||||
elseif acc_str == "MS:1000572"
|
|
||||||
compression_flag = true
|
compression_flag = true
|
||||||
# println("DEBUG: Set is_compressed to true based on parent term")
|
elseif acc_str == NO_COMPRESSION_ACCESSION
|
||||||
elseif acc_str == "MS:1000574"
|
|
||||||
compression_flag = true
|
|
||||||
# println("DEBUG: Set is_compressed to true")
|
|
||||||
elseif acc_str == "MS:1000576"
|
|
||||||
compression_flag = false
|
compression_flag = false
|
||||||
# println("DEBUG: Set is_compressed to false")
|
|
||||||
else
|
|
||||||
# println("DEBUG: Unknown accession: $acc_str")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -98,7 +87,7 @@ end
|
|||||||
|
|
||||||
# This function is updated to return the generic SpectrumMetadata struct
|
# This function is updated to return the generic SpectrumMetadata struct
|
||||||
"""
|
"""
|
||||||
parse_spectrum_metadata(stream, offset::Int64)
|
parse_spectrum_metadata(stream::IO, offset::Int64)
|
||||||
|
|
||||||
Parses an entire `<spectrum>` block from an mzML file, given a starting offset.
|
Parses an entire `<spectrum>` block from an mzML file, given a starting offset.
|
||||||
It extracts the spectrum ID and calls `get_spectrum_asset_metadata` to parse
|
It extracts the spectrum ID and calls `get_spectrum_asset_metadata` to parse
|
||||||
@ -111,7 +100,7 @@ the m/z and intensity array metadata.
|
|||||||
# Returns
|
# Returns
|
||||||
- A `SpectrumMetadata` struct containing the parsed metadata for one spectrum.
|
- A `SpectrumMetadata` struct containing the parsed metadata for one spectrum.
|
||||||
"""
|
"""
|
||||||
function parse_spectrum_metadata(stream, offset::Int64)
|
function parse_spectrum_metadata(stream::IO, offset::Int64)
|
||||||
seek(stream, offset)
|
seek(stream, offset)
|
||||||
|
|
||||||
id_match = find_tag(stream, r"<spectrum\s+index=\"\d+\"\s+id=\"([^\"]+)" )
|
id_match = find_tag(stream, r"<spectrum\s+index=\"\d+\"\s+id=\"([^\"]+)" )
|
||||||
@ -120,16 +109,20 @@ function parse_spectrum_metadata(stream, offset::Int64)
|
|||||||
asset1 = get_spectrum_asset_metadata(stream)
|
asset1 = get_spectrum_asset_metadata(stream)
|
||||||
asset2 = get_spectrum_asset_metadata(stream)
|
asset2 = get_spectrum_asset_metadata(stream)
|
||||||
|
|
||||||
mz_asset = asset1.axis_type == :mz ? asset1 : asset2
|
# Determine which asset is m/z and which is intensity
|
||||||
int_asset = asset1.axis_type == :intensity ? asset1 : asset2
|
mz_asset, int_asset = if asset1.axis_type == :mz
|
||||||
|
(asset1, asset2)
|
||||||
|
else
|
||||||
|
(asset2, asset1)
|
||||||
|
end
|
||||||
|
|
||||||
# Create the new unified metadata object
|
# Create the new unified metadata object
|
||||||
# For mzML, x and y coordinates are not applicable, so we use 0.
|
# For mzML, x and y coordinates are not applicable, so we use 0.
|
||||||
return SpectrumMetadata(0, 0, id, mz_asset, int_asset)
|
return SpectrumMetadata(Int32(0), Int32(0), id, UNKNOWN, mz_asset, int_asset)
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
parse_offset_list(stream)
|
parse_offset_list(stream::IO)
|
||||||
|
|
||||||
Parses the `<index name="spectrum">` block in an indexed mzML file to extract
|
Parses the `<index name="spectrum">` block in an indexed mzML file to extract
|
||||||
the byte offsets for each spectrum.
|
the byte offsets for each spectrum.
|
||||||
@ -140,7 +133,7 @@ the byte offsets for each spectrum.
|
|||||||
# Returns
|
# Returns
|
||||||
- A `Vector{Int64}` containing the byte offsets for all spectra.
|
- A `Vector{Int64}` containing the byte offsets for all spectra.
|
||||||
"""
|
"""
|
||||||
function parse_offset_list(stream)
|
function parse_offset_list(stream::IO)
|
||||||
offsets = Int64[]
|
offsets = Int64[]
|
||||||
offset_regex = r"<offset[^>]*>(\d+)</offset>"
|
offset_regex = r"<offset[^>]*>(\d+)</offset>"
|
||||||
|
|
||||||
@ -160,9 +153,33 @@ function parse_offset_list(stream)
|
|||||||
end
|
end
|
||||||
# If it's neither, ignore the line and continue the loop.
|
# If it's neither, ignore the line and continue the loop.
|
||||||
end
|
end
|
||||||
|
|
||||||
return offsets
|
return offsets
|
||||||
end
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
find_index_offset(stream::IO)::Int64
|
||||||
|
|
||||||
|
Finds the index offset in an mzML file by reading from the end.
|
||||||
|
Optimized version with better buffer management.
|
||||||
|
"""
|
||||||
|
function find_index_offset(stream::IO)::Int64
|
||||||
|
file_size = filesize(stream)
|
||||||
|
seekend(stream)
|
||||||
|
|
||||||
|
# Read larger chunk for better chance of finding the offset
|
||||||
|
chunk_size = min(8192, file_size)
|
||||||
|
seek(stream, file_size - chunk_size)
|
||||||
|
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
|
||||||
|
|
||||||
|
return parse(Int64, index_offset_match.captures[1])
|
||||||
|
end
|
||||||
|
|
||||||
# This is the main lazy-loading function for mzML, now returning an MSIData object.
|
# This is the main lazy-loading function for mzML, now returning an MSIData object.
|
||||||
"""
|
"""
|
||||||
load_mzml_lazy(file_path::String; cache_size::Int=100)
|
load_mzml_lazy(file_path::String; cache_size::Int=100)
|
||||||
@ -183,41 +200,38 @@ function load_mzml_lazy(file_path::String; cache_size::Int=100)
|
|||||||
stream = open(file_path, "r")
|
stream = open(file_path, "r")
|
||||||
|
|
||||||
try
|
try
|
||||||
println("DEBUG: Seeking to end of file.")
|
println("DEBUG: Finding index offset...")
|
||||||
seekend(stream)
|
index_offset = find_index_offset(stream)
|
||||||
println("DEBUG: Skipping back to read footer.")
|
|
||||||
skip(stream, -4096)
|
|
||||||
footer = read(stream, String)
|
|
||||||
println("DEBUG: Footer read successfully.")
|
|
||||||
|
|
||||||
index_offset_match = match(r"<indexListOffset>(\d+)</indexListOffset>", footer)
|
|
||||||
if index_offset_match === nothing
|
|
||||||
close(stream)
|
|
||||||
error("Could not find <indexListOffset>. File may not be an indexed mzML.")
|
|
||||||
end
|
|
||||||
println("DEBUG: Found indexListOffset.")
|
|
||||||
|
|
||||||
index_offset = parse(Int64, index_offset_match.captures[1])
|
|
||||||
println("DEBUG: Seeking to index list at offset $index_offset.")
|
println("DEBUG: Seeking to index list at offset $index_offset.")
|
||||||
seek(stream, index_offset)
|
seek(stream, index_offset)
|
||||||
|
|
||||||
println("DEBUG: Searching for '<index name=\"spectrum\">'.")
|
println("DEBUG: Searching for '<index name=\"spectrum\">'.")
|
||||||
if find_tag(stream, r"<index\s+name=\"spectrum\"") === nothing
|
if find_tag(stream, r"<index\s+name=\"spectrum\"") === nothing
|
||||||
close(stream)
|
|
||||||
error("Could not find spectrum index.")
|
error("Could not find spectrum index.")
|
||||||
end
|
end
|
||||||
println("DEBUG: Found spectrum index tag. ")
|
println("DEBUG: Found spectrum index tag.")
|
||||||
|
|
||||||
println("DEBUG: Parsing spectrum offsets...")
|
println("DEBUG: Parsing spectrum offsets...")
|
||||||
spectrum_offsets = parse_offset_list(stream)
|
spectrum_offsets = parse_offset_list(stream)
|
||||||
if isempty(spectrum_offsets)
|
if isempty(spectrum_offsets)
|
||||||
close(stream)
|
|
||||||
error("No spectrum offsets found.")
|
error("No spectrum offsets found.")
|
||||||
end
|
end
|
||||||
println("DEBUG: Found $(length(spectrum_offsets)) spectrum offsets.")
|
num_spectra = length(spectrum_offsets)
|
||||||
|
println("DEBUG: Found $num_spectra spectrum offsets.")
|
||||||
|
|
||||||
println("DEBUG: Parsing metadata for each spectrum...")
|
println("DEBUG: Parsing metadata for each spectrum...")
|
||||||
spectra_metadata = [parse_spectrum_metadata(stream, offset) for offset in spectrum_offsets]
|
# Pre-allocate the metadata vector for better performance
|
||||||
|
spectra_metadata = Vector{SpectrumMetadata}(undef, num_spectra)
|
||||||
|
|
||||||
|
# Use @inbounds for faster indexing in the loop
|
||||||
|
@inbounds for i in 1:num_spectra
|
||||||
|
spectra_metadata[i] = parse_spectrum_metadata(stream, spectrum_offsets[i])
|
||||||
|
|
||||||
|
# Progress reporting for large files
|
||||||
|
if i % 1000 == 0
|
||||||
|
println("DEBUG: Processed $i/$num_spectra spectra")
|
||||||
|
end
|
||||||
|
end
|
||||||
println("DEBUG: Metadata parsing complete.")
|
println("DEBUG: Metadata parsing complete.")
|
||||||
|
|
||||||
# Assuming uniform data formats, take from the first spectrum
|
# Assuming uniform data formats, take from the first spectrum
|
||||||
@ -254,16 +268,99 @@ function LoadMzml(fileName::String)
|
|||||||
msi_data = load_mzml_lazy(fileName, cache_size=0) # No need to cache if we load all
|
msi_data = load_mzml_lazy(fileName, cache_size=0) # No need to cache if we load all
|
||||||
|
|
||||||
num_spectra = length(msi_data.spectra_metadata)
|
num_spectra = length(msi_data.spectra_metadata)
|
||||||
spectra_matrix = Array{Any}(undef, (2, num_spectra))
|
|
||||||
|
|
||||||
for i in 1:num_spectra
|
# FIXED: Use concrete typed arrays instead of Array{Any}
|
||||||
|
spectra_matrix = Vector{Tuple{Vector{Float64}, Vector{Float64}}}(undef, num_spectra)
|
||||||
|
|
||||||
|
# Pre-allocate and use bounds checking optimization
|
||||||
|
@inbounds for i in 1:num_spectra
|
||||||
# Use the new GetSpectrum API
|
# Use the new GetSpectrum API
|
||||||
mz, intensity = GetSpectrum(msi_data, i)
|
mz, intensity = GetSpectrum(msi_data, i)
|
||||||
spectra_matrix[1, i] = mz
|
spectra_matrix[i] = (mz, intensity)
|
||||||
spectra_matrix[2, i] = intensity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The finalizer on MSIData will close the handle, so no need to close it here.
|
# Convert to the expected 2xN format if needed by downstream code
|
||||||
|
# Note: This maintains the original interface but with better typing
|
||||||
|
result_matrix = Array{Any}(undef, (2, num_spectra))
|
||||||
|
@inbounds for i in 1:num_spectra
|
||||||
|
result_matrix[1, i] = spectra_matrix[i][1]
|
||||||
|
result_matrix[2, i] = spectra_matrix[i][2]
|
||||||
|
end
|
||||||
|
|
||||||
return spectra_matrix
|
return result_matrix
|
||||||
end
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
load_mzml_batch(file_path::String, spectrum_indices::AbstractVector{Int})
|
||||||
|
|
||||||
|
Loads a specific batch of spectra from an mzML file efficiently.
|
||||||
|
Useful for parallel processing or when only specific spectra are needed.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `file_path`: Path to the mzML file
|
||||||
|
- `spectrum_indices`: Indices of spectra to load
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- Vector of (mz_array, intensity_array) tuples
|
||||||
|
"""
|
||||||
|
function load_mzml_batch(file_path::String, spectrum_indices::AbstractVector{Int})
|
||||||
|
msi_data = load_mzml_lazy(file_path)
|
||||||
|
num_to_load = length(spectrum_indices)
|
||||||
|
|
||||||
|
# Pre-allocate result with concrete types
|
||||||
|
results = Vector{Tuple{Vector{Float64}, Vector{Float64}}}(undef, num_to_load)
|
||||||
|
|
||||||
|
@inbounds for (i, idx) in enumerate(spectrum_indices)
|
||||||
|
mz, intensity = GetSpectrum(msi_data, idx)
|
||||||
|
results[i] = (mz, intensity)
|
||||||
|
end
|
||||||
|
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
get_mzml_summary(file_path::String)::NamedTuple
|
||||||
|
|
||||||
|
Quickly extracts summary information from an mzML file without loading all metadata.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- Named tuple with: num_spectra, mz_range, intensity_range, data_formats
|
||||||
|
"""
|
||||||
|
function get_mzml_summary(file_path::String)::NamedTuple
|
||||||
|
stream = open(file_path, "r")
|
||||||
|
try
|
||||||
|
index_offset = find_index_offset(stream)
|
||||||
|
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)
|
||||||
|
num_spectra = length(spectrum_offsets)
|
||||||
|
|
||||||
|
# Sample first few spectra to determine formats and ranges
|
||||||
|
sample_size = min(10, num_spectra)
|
||||||
|
sample_metadata = [parse_spectrum_metadata(stream, spectrum_offsets[i]) for i in 1:sample_size]
|
||||||
|
|
||||||
|
# Extract formats from sample
|
||||||
|
mz_format = sample_metadata[1].mz_asset.format
|
||||||
|
intensity_format = sample_metadata[1].int_asset.format
|
||||||
|
|
||||||
|
return (num_spectra=num_spectra,
|
||||||
|
mz_format=mz_format,
|
||||||
|
intensity_format=intensity_format,
|
||||||
|
sample_spectra=sample_size)
|
||||||
|
finally
|
||||||
|
close(stream)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Performance optimization: Cache frequently used regex patterns
|
||||||
|
const PRE_COMPILED_REGEX = (
|
||||||
|
encoded_length = r"<binaryDataArray\s+encodedLength=\"(\d+)\"",
|
||||||
|
spectrum_id = r"<spectrum\s+index=\"\d+\"\s+id=\"([^\"]+)",
|
||||||
|
offset = r"<offset[^>]*>(\d+)</offset>",
|
||||||
|
index_list = r"<index\s+name=\"spectrum\"",
|
||||||
|
index_offset = r"<indexListOffset>(\d+)</indexListOffset>"
|
||||||
|
)
|
||||||
|
|||||||
@ -111,9 +111,10 @@ function validate_msi_data(filepath::String)
|
|||||||
println("Testing indices: $test_indices")
|
println("Testing indices: $test_indices")
|
||||||
for idx in test_indices
|
for idx in test_indices
|
||||||
print("Fetching spectrum #$idx... ")
|
print("Fetching spectrum #$idx... ")
|
||||||
mz, intensity = @time GetSpectrum(msi_data, idx)
|
@time process_spectrum(msi_data, idx) do mz, intensity
|
||||||
@assert length(mz) == length(intensity) "Spectrum $idx: mz/intensity length mismatch. Got $(length(mz)) mz values and $(length(intensity)) intensity values."
|
@assert length(mz) == length(intensity) "Spectrum $idx: mz/intensity length mismatch. Got $(length(mz)) mz values and $(length(intensity)) intensity values."
|
||||||
println("OK, $(length(mz)) points.")
|
println("OK, $(length(mz)) points.")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# 4. Test iteration
|
# 4. Test iteration
|
||||||
@ -180,18 +181,17 @@ function run_test()
|
|||||||
# Also run original plotting test to ensure visualization still works
|
# Also run original plotting test to ensure visualization still works
|
||||||
if isfile(TEST_MZML_FILE)
|
if isfile(TEST_MZML_FILE)
|
||||||
try
|
try
|
||||||
# Get the msi data from the mzml
|
|
||||||
println("Plotting a sample spectrum from $TEST_MZML_FILE...")
|
println("Plotting a sample spectrum from $TEST_MZML_FILE...")
|
||||||
msi_data = @time OpenMSIData(TEST_MZML_FILE)
|
msi_data = @time OpenMSIData(TEST_MZML_FILE)
|
||||||
mz, intensity = GetSpectrum(msi_data, SPECTRUM_TO_PLOT)
|
process_spectrum(msi_data, SPECTRUM_TO_PLOT) do mz, intensity
|
||||||
|
fig = Figure(size = (800, 600))
|
||||||
fig = Figure(size = (800, 600))
|
ax = Axis(fig[1, 1], xlabel="m/z", ylabel="Intensity", title="Spectrum #$SPECTRUM_TO_PLOT from $(basename(TEST_MZML_FILE))")
|
||||||
ax = Axis(fig[1, 1], xlabel="m/z", ylabel="Intensity", title="Spectrum #$SPECTRUM_TO_PLOT from $(basename(TEST_MZML_FILE))")
|
lines!(ax, mz, intensity)
|
||||||
lines!(ax, mz, intensity)
|
|
||||||
|
output_path = joinpath(RESULTS_DIR, "test_mzml_spectrum.png")
|
||||||
output_path = joinpath(RESULTS_DIR, "test_mzml_spectrum.png")
|
save(output_path, fig)
|
||||||
save(output_path, fig)
|
println("SUCCESS: Spectrum plot saved to $output_path")
|
||||||
println("SUCCESS: Spectrum plot saved to $output_path")
|
end
|
||||||
# Get the summed spectrum data
|
# Get the summed spectrum data
|
||||||
mz, intensity = get_total_spectrum(msi_data)
|
mz, intensity = get_total_spectrum(msi_data)
|
||||||
|
|
||||||
@ -273,17 +273,18 @@ function run_test()
|
|||||||
|
|
||||||
println("DIAGNOSTIC_READ: Reading from coordinate_map[$x_coord, $y_coord]. Value is $(msi_data.coordinate_map[x_coord, y_coord]).")
|
println("DIAGNOSTIC_READ: Reading from coordinate_map[$x_coord, $y_coord]. Value is $(msi_data.coordinate_map[x_coord, y_coord]).")
|
||||||
|
|
||||||
# Get the x y coordinate spectrum data
|
# Get the x y coordinate spectrum data and plot it using the function barrier
|
||||||
mz, intensity = GetSpectrum(msi_data, Int(x_coord), Int(y_coord))
|
process_spectrum(msi_data, Int(x_coord), Int(y_coord)) do mz, intensity
|
||||||
# Plot the data
|
# Plot the data
|
||||||
fig = Figure(size = (800, 600))
|
fig = Figure(size = (800, 600))
|
||||||
ax = Axis(fig[1, 1], xlabel="m/z", ylabel="Intensity", title="Spectrum at ($x_coord, $y_coord) from $(basename(TEST_IMZML_FILE))")
|
ax = Axis(fig[1, 1], xlabel="m/z", ylabel="Intensity", title="Spectrum at ($x_coord, $y_coord) from $(basename(TEST_IMZML_FILE))")
|
||||||
lines!(ax, mz, intensity)
|
lines!(ax, mz, intensity)
|
||||||
|
|
||||||
# Saving the output
|
# Saving the output
|
||||||
output_path = joinpath(RESULTS_DIR, "test_imzml_spectrum.png")
|
output_path = joinpath(RESULTS_DIR, "test_imzml_spectrum.png")
|
||||||
save(output_path, fig)
|
save(output_path, fig)
|
||||||
println("SUCCESS: Spectrum plot saved to $output_path")
|
println("SUCCESS: Spectrum plot saved to $output_path")
|
||||||
|
end
|
||||||
|
|
||||||
# Get the summed spectrum data
|
# Get the summed spectrum data
|
||||||
mz, intensity = get_total_spectrum(msi_data)
|
mz, intensity = get_total_spectrum(msi_data)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user