use std::convert::TryFrom;
use std::{borrow::Cow, ops::Index};
use mzpeaks::Mass;
use thiserror::Error;
use mzpeaks::{
peak_set::PeakSetVec, prelude::*, CentroidLike, CentroidPeak, DeconvolutedCentroidLike,
DeconvolutedPeak, MZPeakSetType, MassPeakSetType, PeakCollection, PeakSet, MZ,
};
#[cfg(feature = "mzsignal")]
use mzsignal::{
denoise::{denoise, DenoisingError},
peak_picker::{PeakFitType, PeakPicker, PeakPickerError},
reprofile::{self, PeakShape, PeakShapeModel},
FittedPeak,
};
use crate::params::{ParamDescribed, Unit, Value};
#[allow(unused)]
use crate::spectrum::bindata::{ArrayType, BinaryArrayMap, BinaryDataArrayType};
use crate::spectrum::peaks::{PeakDataLevel, RefPeakDataLevel, SpectrumSummary};
use crate::spectrum::scan_properties::{
Acquisition, IonMobilityMeasure, Precursor, ScanPolarity, SignalContinuity, SpectrumDescription,
};
use super::bindata::{ArrayRetrievalError, BuildArrayMapFrom, BuildFromArrayMap};
#[allow(unused)]
use super::DataArray;
use super::HasIonMobility;
pub trait CentroidPeakAdapting: CentroidLike + From<CentroidPeak> {}
impl<C: CentroidLike + From<CentroidPeak>> CentroidPeakAdapting for C {}
pub trait DeconvolutedPeakAdapting: DeconvolutedCentroidLike + From<DeconvolutedPeak> {}
impl<D: DeconvolutedCentroidLike + From<DeconvolutedPeak>> DeconvolutedPeakAdapting for D {}
pub trait SpectrumLike<
C: CentroidLike = CentroidPeak,
D: DeconvolutedCentroidLike = DeconvolutedPeak,
>: ParamDescribed
{
fn description(&self) -> &SpectrumDescription;
fn description_mut(&mut self) -> &mut SpectrumDescription;
#[inline]
fn acquisition(&self) -> &Acquisition {
&self.description().acquisition
}
#[inline]
fn precursor(&self) -> Option<&Precursor> {
let desc = self.description();
desc.precursor.first()
}
fn precursor_iter(&self) -> impl Iterator<Item = &Precursor> {
let desc = self.description();
desc.precursor.iter()
}
fn precursor_mut(&mut self) -> Option<&mut Precursor> {
let desc = self.description_mut();
desc.precursor.first_mut()
}
fn precursor_iter_mut(&mut self) -> impl Iterator<Item = &mut Precursor> {
let desc = self.description_mut();
desc.precursor.iter_mut()
}
fn add_precursor(&mut self, precursor: Precursor) {
self.description_mut().precursor.push(precursor);
}
fn remove_precursor(&mut self, index: usize) -> Precursor {
self.description_mut().precursor.remove(index)
}
#[inline]
fn start_time(&self) -> f64 {
let acq = self.acquisition();
match acq.scans.first() {
Some(evt) => evt.start_time,
None => 0.0,
}
}
#[inline]
fn ms_level(&self) -> u8 {
self.description().ms_level
}
#[inline]
fn id(&self) -> &str {
&self.description().id
}
#[inline]
fn index(&self) -> usize {
self.description().index
}
#[inline]
fn signal_continuity(&self) -> SignalContinuity {
self.description().signal_continuity
}
#[inline]
fn polarity(&self) -> ScanPolarity {
self.description().polarity
}
fn ion_mobility(&self) -> Option<f64> {
self.acquisition()
.iter()
.flat_map(|s| s.ion_mobility())
.next()
}
fn has_ion_mobility(&self) -> bool {
self.ion_mobility().is_some()
}
fn peaks(&'_ self) -> RefPeakDataLevel<'_, C, D>;
fn into_peaks_and_description(self) -> (PeakDataLevel<C, D>, SpectrumDescription);
fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap>;
fn has_ion_mobility_dimension(&self) -> bool {
self.raw_arrays()
.map(|a| a.has_ion_mobility())
.unwrap_or_default()
}
fn has_ion_mobility_class(&self) -> HasIonMobility {
if self.has_ion_mobility_dimension() {
HasIonMobility::Dimension
} else if self.has_ion_mobility() {
HasIonMobility::Point
} else {
HasIonMobility::None
}
}
fn spectrum_type(&self) -> Option<crate::meta::SpectrumType> {
self.description().spectrum_type()
}
fn set_spectrum_type(&mut self, spectrum_type: crate::meta::SpectrumType) {
self.description_mut().set_spectrum_type(spectrum_type);
}
fn update_summaries(&mut self) {
let SpectrumSummary {
tic,
base_peak: bp,
mz_range,
count: _,
} = self.peaks().fetch_summaries();
let desc = self.description_mut();
let params = desc.params_mut();
let tic_curie = curie!(MS:1000285);
if let Some(p) = params.iter_mut().find(|p| **p == tic_curie) {
p.value = Value::Float(tic as f64);
} else {
let mut p = tic_curie.as_param();
p.name = "total ion current".to_string();
p.value = Value::Float(tic as f64);
p.unit = Unit::DetectorCounts;
params.push(p)
}
let bpmz_curie = curie!(MS:1000504);
if let Some(p) = params.iter_mut().find(|p| **p == bpmz_curie) {
p.value = Value::Float(bp.mz());
} else {
let mut p = bpmz_curie.as_param();
p.name = "base peak m/z".to_string();
p.value = Value::Float(bp.mz());
p.unit = Unit::MZ;
params.push(p);
}
let lowest_mz_curie = curie!(MS:1000528);
if let Some(p) = params.iter_mut().find(|p| **p == lowest_mz_curie) {
p.value = Value::Float(mz_range.0);
} else {
let mut p = lowest_mz_curie.as_param();
p.name = "lowest observed m/z".to_string();
p.value = Value::Float(mz_range.0);
p.unit = Unit::MZ;
params.push(p);
}
let highest_mz_curie = curie!(MS:1000527);
if let Some(p) = params.iter_mut().find(|p| **p == highest_mz_curie) {
p.value = Value::Float(mz_range.0);
} else {
let mut p = highest_mz_curie.as_param();
p.name = "highest observed m/z".to_string();
p.value = Value::Float(mz_range.1);
p.unit = Unit::MZ;
params.push(p);
}
let bpint_curie = curie!(MS:1000505);
if let Some(p) = params.iter_mut().find(|p| **p == bpint_curie) {
p.value = Value::Float(bp.intensity() as f64);
} else {
let mut p = bpint_curie.as_param();
p.name = "base peak intensity".to_string();
p.value = Value::Float(bp.intensity() as f64);
p.unit = Unit::DetectorCounts;
params.push(p);
}
}
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RawSpectrum {
pub description: SpectrumDescription,
pub arrays: BinaryArrayMap,
}
impl ParamDescribed for RawSpectrum {
fn params(&self) -> &[crate::params::Param] {
<SpectrumDescription as ParamDescribed>::params(&self.description)
}
fn params_mut(&mut self) -> &mut crate::params::ParamList {
<SpectrumDescription as ParamDescribed>::params_mut(&mut self.description)
}
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum SpectrumConversionError {
#[error("m/z array does not match size of intensity array")]
MZIntensityArraySizeMismatch,
#[error("Operation expected charge-deconvolved data but did not find it")]
NotDeconvoluted,
#[error("Operation expected centroided data but did not find it")]
NotCentroided,
#[error("No peak data of any kind was found")]
NoPeakData,
#[error("An error occurred while accessing raw data arrays: {0}")]
ArrayRetrievalError(
#[from]
#[source]
ArrayRetrievalError,
),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum SpectrumProcessingError {
#[cfg(feature = "mzsignal")]
#[error("An error occurred while denoising: {0:?}")]
DenoisingError(
#[from]
#[source]
DenoisingError,
),
#[cfg(feature = "mzsignal")]
#[error("An error occurred while peak picking: {0:?}")]
PeakPickerError(
#[from]
#[source]
PeakPickerError,
),
#[error("An error occurred while trying to convert spectrum types: {0}")]
SpectrumConversionError(
#[from]
#[source]
SpectrumConversionError,
),
#[error("An error occurred while accessing raw data arrays: {0}")]
ArrayRetrievalError(
#[from]
#[source]
ArrayRetrievalError,
),
}
impl<'transient, 'lifespan: 'transient> RawSpectrum {
pub fn new(description: SpectrumDescription, arrays: BinaryArrayMap) -> Self {
Self {
description,
arrays,
}
}
pub fn into_centroid<C>(self) -> Result<CentroidSpectrumType<C>, SpectrumConversionError>
where
C: BuildFromArrayMap + CentroidLike,
{
if !matches!(
self.description.signal_continuity,
SignalContinuity::Centroid
) {
return Err(SpectrumConversionError::NotCentroided);
}
let peaks = match C::try_from_arrays(&self.arrays) {
Ok(peaks) => peaks.into(),
Err(e) => return Err(e.into()),
};
let mut centroid = CentroidSpectrumType::<C> {
description: self.description,
peaks,
};
centroid.description.signal_continuity = SignalContinuity::Centroid;
Ok(centroid)
}
pub fn mzs(&'lifespan self) -> Cow<'transient, [f64]> {
self.arrays.mzs().unwrap()
}
pub fn intensities(&'lifespan self) -> Cow<'transient, [f32]> {
self.arrays.intensities().unwrap()
}
pub fn mzs_mut(&mut self) -> Result<&mut [f64], ArrayRetrievalError> {
self.arrays.mzs_mut()
}
pub fn intensities_mut(&mut self) -> Result<&mut [f32], ArrayRetrievalError> {
self.arrays.intensities_mut()
}
pub fn decode_all_arrays(&mut self) -> Result<(), ArrayRetrievalError> {
self.arrays.decode_all_arrays()
}
pub fn into_spectrum<C, D>(self) -> Result<MultiLayerSpectrum<C, D>, SpectrumConversionError>
where
C: BuildFromArrayMap,
C: CentroidLike,
D: DeconvolutedCentroidLike,
{
Ok(MultiLayerSpectrum::<C, D> {
arrays: Some(self.arrays),
description: self.description,
..Default::default()
})
}
#[cfg(feature = "mzsignal")]
pub fn denoise(&mut self, scale: f32) -> Result<(), SpectrumProcessingError> {
let mut intensities_copy = self.arrays.intensities()?.into_owned();
let mz_array = self.arrays.mzs()?;
match denoise(&mz_array, &mut intensities_copy, scale) {
Ok(_) => {
let view = self.arrays.get_mut(&ArrayType::IntensityArray).unwrap();
view.store_as(BinaryDataArrayType::Float32)
.expect("Failed to reformat intensity array");
view.update_buffer(&intensities_copy)
.expect("Failed to update intensity array buffer");
Ok(())
}
Err(err) => Err(SpectrumProcessingError::DenoisingError(err)),
}
}
#[cfg(feature = "mzsignal")]
pub fn pick_peaks_with_into<
C: CentroidPeakAdapting + BuildFromArrayMap + From<FittedPeak>,
D: DeconvolutedPeakAdapting + BuildFromArrayMap,
>(
self,
peak_picker: &PeakPicker,
) -> Result<MultiLayerSpectrum<C, D>, SpectrumProcessingError> {
let mut result = self.into_spectrum()?;
match result.pick_peaks_with(peak_picker) {
Ok(_) => Ok(result),
Err(err) => Err(err),
}
}
#[cfg(feature = "mzsignal")]
pub fn pick_peaks_into<
C: CentroidPeakAdapting + BuildFromArrayMap + From<FittedPeak>,
D: DeconvolutedPeakAdapting + BuildFromArrayMap,
>(
self,
signal_to_noise_threshold: f32,
) -> Result<MultiLayerSpectrum<C, D>, SpectrumProcessingError> {
let peak_picker = PeakPicker {
fit_type: PeakFitType::Quadratic,
signal_to_noise_threshold,
..Default::default()
};
self.pick_peaks_with_into(&peak_picker)
}
#[cfg(feature = "mzsignal")]
pub fn pick_peaks_in_intervals_into<
C: CentroidPeakAdapting + BuildFromArrayMap + From<FittedPeak>,
D: DeconvolutedPeakAdapting + BuildFromArrayMap,
>(
self,
signal_to_noise_threshold: f32,
intervals: &[(f64, f64)],
) -> Result<MultiLayerSpectrum<C, D>, SpectrumProcessingError> {
let mut result = self.into_spectrum()?;
match result.pick_peaks_in_intervals(signal_to_noise_threshold, intervals) {
Ok(_) => Ok(result),
Err(err) => Err(err),
}
}
}
impl RawSpectrum {
pub fn description(&self) -> &SpectrumDescription {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::description(self)
}
pub fn description_mut(&mut self) -> &mut SpectrumDescription {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::description_mut(self)
}
#[inline]
pub fn acquisition(&self) -> &Acquisition {
&self.description().acquisition
}
#[inline]
pub fn precursor(&self) -> Option<&Precursor> {
let desc = self.description();
desc.precursor.first()
}
pub fn precursor_iter(&self) -> impl Iterator<Item = &Precursor> {
let desc = self.description();
desc.precursor.iter()
}
pub fn precursor_mut(&mut self) -> Option<&mut Precursor> {
let desc = self.description_mut();
desc.precursor.first_mut()
}
pub fn precursor_iter_mut(&mut self) -> impl Iterator<Item = &mut Precursor> {
let desc = self.description_mut();
desc.precursor.iter_mut()
}
#[inline]
pub fn start_time(&self) -> f64 {
let acq = self.acquisition();
match acq.scans.first() {
Some(evt) => evt.start_time,
None => 0.0,
}
}
#[inline]
pub fn ms_level(&self) -> u8 {
self.description().ms_level
}
#[inline]
pub fn id(&self) -> &str {
&self.description().id
}
#[inline]
pub fn index(&self) -> usize {
self.description().index
}
#[inline]
pub fn signal_continuity(&self) -> SignalContinuity {
self.description().signal_continuity
}
#[inline]
pub fn polarity(&self) -> ScanPolarity {
self.description().polarity
}
pub fn ion_mobility(&self) -> Option<f64> {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::ion_mobility(self)
}
pub fn has_ion_mobility(&self) -> bool {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::ion_mobility(self).is_some()
}
pub fn peaks(&'_ self) -> RefPeakDataLevel<'_, CentroidPeak, DeconvolutedPeak> {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::peaks(self)
}
pub fn into_peaks_and_description(
self,
) -> (
PeakDataLevel<CentroidPeak, DeconvolutedPeak>,
SpectrumDescription,
) {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::into_peaks_and_description(
self,
)
}
pub fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap> {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::raw_arrays(self)
}
pub fn has_ion_mobility_dimension(&self) -> bool {
self.raw_arrays()
.map(|a| a.has_ion_mobility())
.unwrap_or_default()
}
pub fn update_summaries(&mut self) {
<RawSpectrum as SpectrumLike<CentroidPeak, DeconvolutedPeak>>::update_summaries(self)
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> SpectrumLike<C, D> for RawSpectrum {
#[inline]
fn description(&self) -> &SpectrumDescription {
&self.description
}
fn description_mut(&mut self) -> &mut SpectrumDescription {
&mut self.description
}
fn peaks(&'_ self) -> RefPeakDataLevel<'_, C, D> {
RefPeakDataLevel::RawData(&self.arrays)
}
fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap> {
Some(&self.arrays)
}
fn into_peaks_and_description(self) -> (PeakDataLevel<C, D>, SpectrumDescription) {
(PeakDataLevel::RawData(self.arrays), self.description)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CentroidSpectrumType<C: CentroidLike> {
pub description: SpectrumDescription,
pub peaks: MZPeakSetType<C>,
}
impl<C: CentroidLike> Default for CentroidSpectrumType<C> {
fn default() -> Self {
Self {
description: Default::default(),
peaks: PeakSetVec::empty(),
}
}
}
#[cfg(feature = "mzsignal")]
impl<C: CentroidLike + BuildArrayMapFrom + BuildFromArrayMap> CentroidSpectrumType<C> {
pub fn reprofile_with_shape_into(
self,
dx: f64,
fwhm: f32,
) -> Result<MultiLayerSpectrum<C, DeconvolutedPeak>, SpectrumProcessingError> {
let mut spectrum = self.into_spectrum()?;
spectrum.reprofile_with_shape(dx, fwhm)?;
Ok(spectrum)
}
}
impl<C: CentroidLike> ParamDescribed for CentroidSpectrumType<C> {
fn params(&self) -> &[crate::params::Param] {
<SpectrumDescription as ParamDescribed>::params(&self.description)
}
fn params_mut(&mut self) -> &mut crate::params::ParamList {
<SpectrumDescription as ParamDescribed>::params_mut(&mut self.description)
}
}
impl<C: CentroidLike> SpectrumLike<C> for CentroidSpectrumType<C> {
#[inline]
fn description(&self) -> &SpectrumDescription {
&self.description
}
fn description_mut(&mut self) -> &mut SpectrumDescription {
&mut self.description
}
fn peaks(&'_ self) -> RefPeakDataLevel<'_, C, DeconvolutedPeak> {
RefPeakDataLevel::Centroid(&self.peaks)
}
fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap> {
None
}
fn into_peaks_and_description(
self,
) -> (PeakDataLevel<C, DeconvolutedPeak>, SpectrumDescription) {
(PeakDataLevel::Centroid(self.peaks), self.description)
}
}
impl<C: CentroidLike> CentroidSpectrumType<C> {
pub fn new(description: SpectrumDescription, peaks: MZPeakSetType<C>) -> Self {
Self { description, peaks }
}
pub fn into_spectrum<D>(self) -> Result<MultiLayerSpectrum<C, D>, SpectrumConversionError>
where
D: DeconvolutedCentroidLike,
{
let val = MultiLayerSpectrum::<C, D> {
peaks: Some(self.peaks),
description: self.description,
..Default::default()
};
Ok(val)
}
pub fn reinterpret<C1, D1>(self) -> Result<MultiLayerSpectrum<C1, D1>, SpectrumConversionError>
where
C1: CentroidLike + BuildArrayMapFrom + BuildFromArrayMap,
D1: DeconvolutedCentroidLike + BuildArrayMapFrom + BuildFromArrayMap,
C: BuildArrayMapFrom
{
let arrays = C::as_arrays(self.peaks.as_slice());
Ok(MultiLayerSpectrum::new(self.description, Some(arrays), None, None))
}
}
pub type CentroidSpectrum = CentroidSpectrumType<CentroidPeak>;
impl<C: CentroidPeakAdapting> Index<usize> for CentroidSpectrumType<C> {
type Output = <MZPeakSetType<C> as Index<usize>>::Output;
fn index(&self, index: usize) -> &Self::Output {
self.peaks.index(index)
}
}
impl<C: CentroidPeakAdapting> PeakCollection<C, MZ> for CentroidSpectrumType<C>
where
MZPeakSetType<C>: PeakCollection<C, MZ>,
<MZPeakSetType<C> as Index<usize>>::Output: CoordinateLike<MZ>,
{
fn len(&self) -> usize {
self.peaks.len()
}
fn get_item(&self, i: usize) -> &C {
self.peaks.get_item(i)
}
fn get_slice(&self, i: std::ops::Range<usize>) -> &[C] {
self.peaks.get_slice(i)
}
fn search_by(&self, query: f64) -> Result<usize, usize> {
self.peaks.search_by(query)
}
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a C>
where
C: 'a,
{
self.peaks.iter()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeconvolutedSpectrumType<D: DeconvolutedCentroidLike> {
pub description: SpectrumDescription,
pub deconvoluted_peaks: MassPeakSetType<D>,
}
impl<D: DeconvolutedCentroidLike> Default for DeconvolutedSpectrumType<D> {
fn default() -> Self {
Self {
description: Default::default(),
deconvoluted_peaks: PeakSetVec::empty(),
}
}
}
impl<D: DeconvolutedCentroidLike> DeconvolutedSpectrumType<D> {
pub fn new(description: SpectrumDescription, deconvoluted_peaks: MassPeakSetType<D>) -> Self {
Self {
description,
deconvoluted_peaks,
}
}
pub fn into_spectrum<C>(self) -> Result<MultiLayerSpectrum<C, D>, SpectrumConversionError>
where
C: CentroidLike,
{
let val = MultiLayerSpectrum::<C, D> {
deconvoluted_peaks: Some(self.deconvoluted_peaks),
description: self.description,
..Default::default()
};
Ok(val)
}
pub fn reinterpret<C1, D1>(self) -> MultiLayerSpectrum<C1, D1>
where
C1: CentroidLike + BuildArrayMapFrom + BuildFromArrayMap,
D1: DeconvolutedCentroidLike + BuildArrayMapFrom + BuildFromArrayMap,
D: BuildArrayMapFrom
{
let arrays = D::as_arrays(self.deconvoluted_peaks.as_slice());
MultiLayerSpectrum::new(self.description, Some(arrays), None, None)
}
}
impl<D: DeconvolutedCentroidLike> ParamDescribed for DeconvolutedSpectrumType<D> {
fn params(&self) -> &[crate::params::Param] {
<SpectrumDescription as ParamDescribed>::params(&self.description)
}
fn params_mut(&mut self) -> &mut crate::params::ParamList {
<SpectrumDescription as ParamDescribed>::params_mut(&mut self.description)
}
}
impl<D: DeconvolutedCentroidLike> SpectrumLike<CentroidPeak, D> for DeconvolutedSpectrumType<D> {
#[inline]
fn description(&self) -> &SpectrumDescription {
&self.description
}
fn description_mut(&mut self) -> &mut SpectrumDescription {
&mut self.description
}
fn peaks(&'_ self) -> RefPeakDataLevel<'_, CentroidPeak, D> {
RefPeakDataLevel::Deconvoluted(&self.deconvoluted_peaks)
}
fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap> {
None
}
fn into_peaks_and_description(self) -> (PeakDataLevel<CentroidPeak, D>, SpectrumDescription) {
(
PeakDataLevel::Deconvoluted(self.deconvoluted_peaks),
self.description,
)
}
}
pub type DeconvolutedSpectrum = DeconvolutedSpectrumType<DeconvolutedPeak>;
impl<D: DeconvolutedPeakAdapting> Index<usize> for DeconvolutedSpectrumType<D> {
type Output = <MassPeakSetType<D> as Index<usize>>::Output;
fn index(&self, index: usize) -> &Self::Output {
self.deconvoluted_peaks.index(index)
}
}
impl<D: DeconvolutedPeakAdapting> PeakCollection<D, Mass> for DeconvolutedSpectrumType<D>
where
MassPeakSetType<D>: PeakCollection<D, Mass>,
<MassPeakSetType<D> as Index<usize>>::Output: CoordinateLike<Mass>,
{
fn len(&self) -> usize {
self.deconvoluted_peaks.len()
}
fn get_item(&self, i: usize) -> &D {
self.deconvoluted_peaks.get_item(i)
}
fn get_slice(&self, i: std::ops::Range<usize>) -> &[D] {
self.deconvoluted_peaks.get_slice(i)
}
fn search_by(&self, query: f64) -> Result<usize, usize> {
self.deconvoluted_peaks.search_by(query)
}
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a D>
where
D: 'a,
{
self.deconvoluted_peaks.iter()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MultiLayerSpectrum<
C: CentroidLike = CentroidPeak,
D: DeconvolutedCentroidLike = DeconvolutedPeak,
> {
pub description: SpectrumDescription,
pub arrays: Option<BinaryArrayMap>,
pub peaks: Option<MZPeakSetType<C>>,
pub deconvoluted_peaks: Option<MassPeakSetType<D>>,
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> Default for MultiLayerSpectrum<C, D> {
fn default() -> Self {
Self {
description: Default::default(),
arrays: None,
peaks: None,
deconvoluted_peaks: None,
}
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> ParamDescribed for MultiLayerSpectrum<C, D> {
fn params(&self) -> &[crate::params::Param] {
<SpectrumDescription as ParamDescribed>::params(&self.description)
}
fn params_mut(&mut self) -> &mut crate::params::ParamList {
<SpectrumDescription as ParamDescribed>::params_mut(&mut self.description)
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> SpectrumLike<C, D> for MultiLayerSpectrum<C, D> {
#[inline]
fn description(&self) -> &SpectrumDescription {
&self.description
}
fn description_mut(&mut self) -> &mut SpectrumDescription {
&mut self.description
}
fn peaks(&'_ self) -> RefPeakDataLevel<'_, C, D> {
if let Some(peaks) = &self.deconvoluted_peaks {
RefPeakDataLevel::Deconvoluted(peaks)
} else if let Some(peaks) = &self.peaks {
RefPeakDataLevel::Centroid(peaks)
} else if let Some(arrays) = &self.arrays {
RefPeakDataLevel::RawData(arrays)
} else {
RefPeakDataLevel::Missing
}
}
fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap> {
self.arrays.as_ref()
}
fn into_peaks_and_description(self) -> (PeakDataLevel<C, D>, SpectrumDescription) {
if let Some(peaks) = self.deconvoluted_peaks {
(PeakDataLevel::Deconvoluted(peaks), self.description)
} else if let Some(peaks) = self.peaks {
(PeakDataLevel::Centroid(peaks), self.description)
} else if let Some(arrays) = self.arrays {
(PeakDataLevel::RawData(arrays), self.description)
} else {
(PeakDataLevel::Missing, self.description)
}
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> MultiLayerSpectrum<C, D>
where
C: BuildFromArrayMap + BuildArrayMapFrom,
D: BuildFromArrayMap + BuildArrayMapFrom,
{
pub fn new(
description: SpectrumDescription,
arrays: Option<BinaryArrayMap>,
peaks: Option<MZPeakSetType<C>>,
deconvoluted_peaks: Option<MassPeakSetType<D>>,
) -> Self {
Self {
description,
arrays,
peaks,
deconvoluted_peaks,
}
}
pub fn into_centroid(self) -> Result<CentroidSpectrumType<C>, SpectrumConversionError> {
if let Some(peaks) = self.peaks {
let mut result = CentroidSpectrumType::<C> {
peaks,
description: self.description,
};
result.description.signal_continuity = SignalContinuity::Centroid;
return Ok(result);
} else if self.signal_continuity() == SignalContinuity::Centroid {
if let Some(arrays) = &self.arrays {
let peaks = C::try_from_arrays(arrays)?.into();
let mut centroid = CentroidSpectrumType::<C> {
description: self.description,
peaks,
};
centroid.description.signal_continuity = SignalContinuity::Centroid;
return Ok(centroid);
} else {
let mut result = CentroidSpectrumType::<C> {
peaks: MZPeakSetType::<C>::empty(),
description: self.description,
};
result.description.signal_continuity = SignalContinuity::Centroid;
return Ok(result);
}
}
Err(SpectrumConversionError::NotCentroided)
}
pub fn into_raw(self) -> Result<RawSpectrum, SpectrumConversionError> {
if let Some(arrays) = self.arrays {
Ok(RawSpectrum {
arrays,
description: self.description,
})
} else if let Some(peaks) = self.deconvoluted_peaks {
let arrays = D::as_arrays(&peaks[0..]);
let mut result = RawSpectrum {
arrays,
description: self.description,
};
result.description.signal_continuity = SignalContinuity::Centroid;
Ok(result)
} else if let Some(peaks) = self.peaks {
let arrays = C::as_arrays(&peaks[0..]);
let mut result = RawSpectrum {
arrays,
description: self.description,
};
result.description.signal_continuity = SignalContinuity::Centroid;
Ok(result)
} else {
let arrays = BinaryArrayMap::from(&PeakSet::empty());
Ok(RawSpectrum {
arrays,
description: self.description,
})
}
}
pub fn from_arrays_and_description(
arrays: BinaryArrayMap,
description: SpectrumDescription,
) -> Self {
Self {
description,
arrays: Some(arrays),
..Default::default()
}
}
pub fn from_description(description: SpectrumDescription) -> Self {
Self {
description,
..Default::default()
}
}
pub fn from_peaks_data_levels_and_description(
peaks: PeakDataLevel<C, D>,
description: SpectrumDescription,
) -> Self {
match peaks {
PeakDataLevel::Missing => Self::new(description, None, None, None),
PeakDataLevel::RawData(arrays) => Self::new(description, Some(arrays), None, None),
PeakDataLevel::Centroid(peaks) => Self::new(description, None, Some(peaks), None),
PeakDataLevel::Deconvoluted(peaks) => Self::new(description, None, None, Some(peaks)),
}
}
pub fn from_spectrum_like<S: SpectrumLike<C, D>>(spectrum: S) -> Self {
let (peaks, description) = spectrum.into_peaks_and_description();
Self::from_peaks_data_levels_and_description(peaks, description)
}
pub fn reinterpret<C1, D1>(self) -> MultiLayerSpectrum<C1, D1>
where
C1: CentroidLike + BuildArrayMapFrom + BuildFromArrayMap,
D1: DeconvolutedCentroidLike + BuildArrayMapFrom + BuildFromArrayMap,
C: BuildArrayMapFrom,
D: BuildArrayMapFrom
{
let arrays = if let Some(peaks) = self.deconvoluted_peaks {
D::as_arrays(peaks.as_slice())
} else if let Some(peaks) = self.peaks {
C::as_arrays(peaks.as_slice())
} else if let Some(arrays) = self.arrays {
arrays
} else {
BinaryArrayMap::new()
};
MultiLayerSpectrum::new(self.description, Some(arrays), None, None)
}
#[cfg(feature = "mzsignal")]
pub fn denoise(&mut self, scale: f32) -> Result<(), SpectrumProcessingError> {
match &mut self.arrays {
Some(arrays) => {
let mut intensities_copy = arrays.intensities()?.into_owned();
let mz_array = arrays.mzs()?;
match denoise(&mz_array, &mut intensities_copy, scale) {
Ok(_) => {
let view = arrays.get_mut(&ArrayType::IntensityArray).unwrap();
view.store_as(BinaryDataArrayType::Float32)?;
view.update_buffer(&intensities_copy)?;
Ok(())
}
Err(err) => Err(SpectrumProcessingError::DenoisingError(err)),
}
}
None => Err(SpectrumProcessingError::SpectrumConversionError(
SpectrumConversionError::NoPeakData,
)),
}
}
#[cfg(feature = "mzsignal")]
pub fn reprofile_with_shape(
&mut self,
dx: f64,
fwhm: f32,
) -> Result<(), SpectrumProcessingError> {
if let Some(peaks) = self.peaks.as_ref() {
let reprofiler = reprofile::PeakSetReprofiler::new(
peaks.first().map(|p| p.mz() - 1.0).unwrap_or_default(),
peaks.last().map(|p| p.mz() + 1.0).unwrap_or_default(),
dx,
);
let models: Vec<_> = peaks
.iter()
.map(|p| {
PeakShapeModel::from_centroid(p.mz(), p.intensity(), fwhm, PeakShape::Gaussian)
})
.collect();
let pair = reprofiler.reprofile_from_models(&models);
let arrays = match self.arrays.as_mut() {
Some(arrays) => arrays,
None => {
self.arrays = Some(BinaryArrayMap::new());
self.arrays.as_mut().unwrap()
}
};
arrays.add(DataArray::wrap(
&ArrayType::MZArray,
BinaryDataArrayType::Float64,
pair.mz_array.iter().flat_map(|i| i.to_le_bytes()).collect(),
));
arrays.add(DataArray::wrap(
&ArrayType::IntensityArray,
BinaryDataArrayType::Float32,
pair.intensity_array
.iter()
.flat_map(|i| i.to_le_bytes())
.collect(),
));
Ok(())
} else {
Err(SpectrumConversionError::NoPeakData.into())
}
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> MultiLayerSpectrum<C, D>
where
C: BuildFromArrayMap,
D: BuildFromArrayMap,
{
pub fn try_build_peaks(
&mut self,
) -> Result<RefPeakDataLevel<'_, C, D>, SpectrumConversionError> {
if self.peaks.is_some() || self.deconvoluted_peaks.is_some() {
return Ok(self.peaks());
}
if matches!(self.signal_continuity(), SignalContinuity::Centroid) {
if let Some(arrays) = self.arrays.as_ref() {
let peak_data: PeakDataLevel<C, D> = PeakDataLevel::try_from(arrays)?;
match peak_data {
PeakDataLevel::Missing => {
Ok(RefPeakDataLevel::Missing)
},
PeakDataLevel::RawData(_) => panic!("not possible"),
PeakDataLevel::Centroid(peak_set_vec) => {
self.peaks = Some(peak_set_vec);
Ok(self.peaks())
},
PeakDataLevel::Deconvoluted(peak_set_vec) => {
self.deconvoluted_peaks = Some(peak_set_vec);
Ok(self.peaks())
},
}
} else {
Ok(self.peaks())
}
} else {
Ok(self.peaks())
}
}
pub fn try_build_centroids(&mut self) -> Result<&MZPeakSetType<C>, SpectrumConversionError> {
if self.peaks.is_some() {
Ok(self.peaks.as_ref().unwrap())
} else if let Some(arrays) = self.arrays.as_ref() {
match self.signal_continuity() {
SignalContinuity::Centroid => {
let peaks = C::try_from_arrays(arrays)?.into();
self.peaks = Some(peaks);
Ok(self.peaks.as_ref().unwrap())
}
_ => Err(SpectrumConversionError::NotCentroided),
}
} else {
Err(SpectrumConversionError::NoPeakData)
}
}
pub fn try_build_deconvoluted_centroids(
&mut self,
) -> Result<&MassPeakSetType<D>, SpectrumConversionError> {
if let Some(ref peaks) = self.deconvoluted_peaks {
Ok(peaks)
} else if let Some(ref arrays) = self.arrays {
match self.signal_continuity() {
SignalContinuity::Centroid => {
let peaks = D::try_from_arrays(arrays)?.into();
self.deconvoluted_peaks = Some(peaks);
Ok(self.deconvoluted_peaks.as_ref().unwrap())
}
_ => Err(SpectrumConversionError::NotCentroided),
}
} else {
Err(SpectrumConversionError::NoPeakData)
}
}
}
#[cfg(feature = "mzsignal")]
impl<C: CentroidLike + From<FittedPeak>, D: DeconvolutedCentroidLike> MultiLayerSpectrum<C, D> {
pub fn pick_peaks_with(
&mut self,
peak_picker: &PeakPicker,
) -> Result<(), SpectrumProcessingError> {
if let Some(arrays) = &self.arrays {
let mz_array = arrays.mzs()?;
let intensity_array = arrays.intensities()?;
if matches!(self.signal_continuity(), SignalContinuity::Centroid) {
let mut peaks: MZPeakSetType<C> = mz_array
.iter()
.zip(intensity_array.iter())
.map(|(mz, inten)| FittedPeak::new(*mz, *inten, 0, 0.0, 0.0).into())
.collect();
peaks.sort();
self.peaks = Some(peaks);
Ok(())
} else {
let mut acc = Vec::new();
match peak_picker.discover_peaks(&mz_array, &intensity_array, &mut acc) {
Ok(_) => {
let peaks: MZPeakSetType<C> = acc.into_iter().map(|p| C::from(p)).collect();
self.peaks = Some(peaks);
Ok(())
}
Err(err) => Err(SpectrumProcessingError::PeakPickerError(err)),
}
}
} else {
Err(SpectrumProcessingError::SpectrumConversionError(
SpectrumConversionError::NoPeakData,
))
}
}
pub fn pick_peaks(
&mut self,
signal_to_noise_threshold: f32,
) -> Result<(), SpectrumProcessingError> {
let peak_picker = PeakPicker {
fit_type: PeakFitType::Quadratic,
signal_to_noise_threshold,
..Default::default()
};
self.pick_peaks_with(&peak_picker)
}
pub fn pick_peaks_in_intervals(
&mut self,
signal_to_noise_threshold: f32,
intervals: &[(f64, f64)],
) -> Result<(), SpectrumProcessingError> {
let peak_picker = PeakPicker {
fit_type: PeakFitType::Quadratic,
signal_to_noise_threshold,
..Default::default()
};
if matches!(self.signal_continuity(), SignalContinuity::Centroid) {
self.pick_peaks_with(&peak_picker)?;
let peaks = self
.peaks
.take()
.unwrap()
.into_iter()
.filter(|p| {
let mz = p.mz();
intervals.iter().any(|(lo, hi)| *lo <= mz && mz < *hi)
})
.collect();
self.peaks = Some(peaks);
Ok(())
} else if let Some(arrays) = &self.arrays {
let mz_array = arrays.mzs()?;
let intensity_array = arrays.intensities()?;
let mut acc = Vec::new();
for (mz_start, mz_end) in intervals {
match peak_picker.discover_peaks_in_interval(
&mz_array,
&intensity_array,
&mut acc,
*mz_start,
*mz_end,
) {
Ok(_) => {}
Err(err) => return Err(SpectrumProcessingError::PeakPickerError(err)),
}
}
let peaks: MZPeakSetType<C> = acc.into_iter().map(|p| C::from(p)).collect();
self.peaks = Some(peaks);
return Ok(());
} else {
return Err(SpectrumProcessingError::SpectrumConversionError(
SpectrumConversionError::NoPeakData,
));
}
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> TryFrom<MultiLayerSpectrum<C, D>>
for CentroidSpectrumType<C>
where
C: BuildFromArrayMap + BuildArrayMapFrom,
D: BuildFromArrayMap + BuildArrayMapFrom,
{
type Error = SpectrumConversionError;
fn try_from(
spectrum: MultiLayerSpectrum<C, D>,
) -> Result<CentroidSpectrumType<C>, Self::Error> {
spectrum.into_centroid()
}
}
pub type Spectrum = MultiLayerSpectrum<CentroidPeak, DeconvolutedPeak>;
impl<C: CentroidLike, D: DeconvolutedCentroidLike> From<CentroidSpectrumType<C>>
for MultiLayerSpectrum<C, D>
{
fn from(spectrum: CentroidSpectrumType<C>) -> MultiLayerSpectrum<C, D> {
spectrum.into_spectrum().unwrap()
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> From<RawSpectrum> for MultiLayerSpectrum<C, D>
where
C: BuildFromArrayMap,
D: BuildFromArrayMap,
{
fn from(spectrum: RawSpectrum) -> MultiLayerSpectrum<C, D> {
spectrum.into_spectrum().unwrap()
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> From<MultiLayerSpectrum<C, D>> for RawSpectrum
where
C: BuildFromArrayMap + BuildArrayMapFrom,
D: BuildFromArrayMap + BuildArrayMapFrom,
{
fn from(spectrum: MultiLayerSpectrum<C, D>) -> RawSpectrum {
spectrum.into_raw().unwrap()
}
}
impl<C: CentroidLike> TryFrom<RawSpectrum> for CentroidSpectrumType<C>
where
C: BuildFromArrayMap,
{
type Error = SpectrumConversionError;
fn try_from(spectrum: RawSpectrum) -> Result<CentroidSpectrumType<C>, Self::Error> {
spectrum.into_centroid()
}
}
impl<C: CentroidLike, D: DeconvolutedCentroidLike> TryFrom<MultiLayerSpectrum<C, D>>
for DeconvolutedSpectrumType<D>
where
C: BuildFromArrayMap + BuildArrayMapFrom,
D: BuildFromArrayMap + BuildArrayMapFrom,
{
type Error = SpectrumConversionError;
fn try_from(value: MultiLayerSpectrum<C, D>) -> Result<Self, Self::Error> {
match value.peaks() {
RefPeakDataLevel::Deconvoluted(_) => {
let peaks = value.deconvoluted_peaks.unwrap();
Ok(DeconvolutedSpectrumType {
description: value.description,
deconvoluted_peaks: peaks,
})
}
RefPeakDataLevel::RawData(arrays) => {
let peaks = match D::try_from_arrays(arrays) {
Ok(peaks) => peaks,
Err(e) => return Err(SpectrumConversionError::ArrayRetrievalError(e)),
};
Ok(DeconvolutedSpectrumType {
description: value.description,
deconvoluted_peaks: PeakSetVec::new(peaks),
})
}
_ => Err(SpectrumConversionError::NotDeconvoluted),
}
}
}
#[cfg(all(test, feature = "mzml"))]
mod test {
use std::io;
use super::*;
use crate::io::mzml::MzMLReader;
use crate::io::DetailLevel;
use crate::prelude::*;
#[test_log::test]
fn test_peakdata_lazy() -> io::Result<()> {
let mut reader = MzMLReader::open_path("./test/data/small.mzML")?;
reader.detail_level = DetailLevel::Lazy;
let spec = reader.get_spectrum_by_index(0).unwrap();
let peaks = spec.peaks();
let n = peaks.len();
assert_eq!(n, 19913);
let iter = peaks.iter();
let data: Vec<_> = iter.collect();
let n2 = data.len();
assert_eq!(n2, 19913);
let p1 = peaks.get(5000);
let p2 = data.get(5000).cloned();
assert_eq!(p1, p2);
Ok(())
}
macro_rules! behaviors {
($spec:ident) => {
assert_eq!($spec.id(), "controllerType=0 controllerNumber=1 scan=10014");
assert_eq!($spec.start_time(), 22.12829);
assert_eq!($spec.ms_level(), 1);
assert_eq!($spec.polarity(), ScanPolarity::Positive);
assert!($spec.precursor().is_none());
assert_eq!($spec.precursor_iter().count(), 0);
assert!(!$spec.has_ion_mobility());
assert!(!$spec.has_ion_mobility_dimension());
assert!(!$spec.params().is_empty());
};
}
#[allow(unused)]
fn test_spectrum_behavior<T: SpectrumLike>(spec: &T) {
assert_eq!(spec.spectrum_type(), Some(crate::meta::SpectrumType::MS1Spectrum));
behaviors!(spec);
}
#[test_log::test]
fn test_peakdata() -> io::Result<()> {
let mut reader = MzMLReader::open_path("./test/data/small.mzML")?;
let spec = reader.get_spectrum_by_index(0).unwrap();
let peaks = spec.peaks();
let n = peaks.len();
assert_eq!(n, 19913);
let iter = peaks.iter();
let data: Vec<_> = iter.collect();
let n2 = data.len();
assert_eq!(n2, 19913);
let p1 = peaks.get(5000);
let p2 = data.get(5000).cloned();
assert_eq!(p1, p2);
Ok(())
}
#[cfg(feature = "mzsignal")]
#[test_log::test]
fn test_profile_read() {
let mut reader = MzMLReader::open_path("./test/data/three_test_scans.mzML")
.expect("Failed to open test file");
reader.reset();
let mut scan = reader.next().expect("Failed to read spectrum");
assert_eq!(scan.signal_continuity(), SignalContinuity::Profile);
test_spectrum_behavior(&scan);
if let Err(err) = scan.pick_peaks(1.0) {
panic!("Should not have an error! {}", err);
}
if let Some(peaks) = &scan.peaks {
assert_eq!(peaks.len(), 2107);
let n = peaks.len();
for i in 1..n {
let diff = peaks[i].get_index() - peaks[i - 1].get_index();
assert_eq!(diff, 1);
}
peaks
.has_peak(562.741, Tolerance::PPM(3f64))
.expect("Expected to find peak");
peaks
.has_peak(563.240, Tolerance::PPM(3f64))
.expect("Expected to find peak");
let p = peaks
.has_peak(563.739, Tolerance::PPM(1f64))
.expect("Expected to find peak");
assert!((p.mz() - 563.739).abs() < 1e-3)
}
scan.pick_peaks_with(&PeakPicker::default()).unwrap();
if let Some(peaks) = &scan.peaks {
assert_eq!(peaks.len(), 2107);
let n = peaks.len();
for i in 1..n {
let diff = peaks[i].get_index() - peaks[i - 1].get_index();
assert_eq!(diff, 1);
}
peaks
.has_peak(562.741, Tolerance::PPM(3f64))
.expect("Expected to find peak");
peaks
.has_peak(563.240, Tolerance::PPM(3f64))
.expect("Expected to find peak");
let p = peaks
.has_peak(563.739, Tolerance::PPM(1f64))
.expect("Expected to find peak");
assert!((p.mz() - 563.739).abs() < 1e-3)
}
let mut cent_scan = scan.clone().into_centroid().unwrap();
assert_eq!(cent_scan.signal_continuity(), SignalContinuity::Centroid);
assert_eq!(SpectrumLike::index(¢_scan), 0);
behaviors!(cent_scan);
cent_scan.update_summaries();
let tmp: Spectrum = cent_scan.into_spectrum().unwrap();
let raw_scan = tmp.into_raw().unwrap();
behaviors!(raw_scan);
assert_eq!(raw_scan.index(), 0);
assert_eq!(raw_scan.signal_continuity(), SignalContinuity::Centroid);
}
#[cfg(feature = "mzsignal")]
#[test]
fn test_reprofile() {
let mut reader = MzMLReader::open_path("./test/data/three_test_scans.mzML")
.expect("Failed to open test file");
reader.reset();
let mut scan = reader.next().expect("Failed to read spectrum");
assert_eq!(scan.signal_continuity(), SignalContinuity::Profile);
assert_eq!(scan.ms_level(), 1);
assert_eq!(scan.polarity(), ScanPolarity::Positive);
assert!(scan.precursor().is_none());
if let Err(err) = scan.pick_peaks(1.0) {
panic!("Should not have an error! {}", err);
}
let mut duplicate = scan.clone();
duplicate.reprofile_with_shape(0.001, 0.01).unwrap();
duplicate.peaks = None;
duplicate.pick_peaks(1.0).unwrap();
let peak = duplicate.peaks.as_ref().unwrap().base_peak().unwrap();
eprintln!("{}", peak);
}
}