use std::convert::TryFrom;
use std::io;
use std::marker::PhantomData;
use log::warn;
use thiserror::Error;
use mzpeaks::{
feature::{ChargedFeature, Feature, FeatureLike},
DeconvolutedCentroidLike, IonMobility, KnownCharge, Mass, MZ,
CentroidLike,
};
use crate::spectrum::group::IonMobilityFrameGroupingIterator;
use crate::spectrum::spectrum_types::MultiLayerSpectrum;
use crate::spectrum::{
IonMobilityFrameLike,
MultiLayerIonMobilityFrame,
};
use crate::{
io::{DetailLevel, OffsetIndex},
prelude::{MSDataFileMetadata, SpectrumLike},
spectrum::{HasIonMobility, IonMobilityFrameGroup},
};
use super::{
util::DetailLevelGuard, IonMobilityFrameGrouping, RandomAccessSpectrumIterator,
SpectrumAccessError, SpectrumSource,
};
pub trait IonMobilityFrameSource<
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
S: IonMobilityFrameLike<C, D> = MultiLayerIonMobilityFrame<C, D>,
>: Iterator<Item = S>
{
fn reset(&mut self);
fn detail_level(&self) -> &DetailLevel;
fn set_detail_level(&mut self, detail_level: DetailLevel);
fn get_frame_by_id(&mut self, id: &str) -> Option<S>;
fn get_frame_by_index(&mut self, index: usize) -> Option<S>;
fn get_frame_by_time(&mut self, time: f64) -> Option<S> {
let n = self.len();
let mut lo: usize = 0;
let mut hi: usize = n;
let mut best_error: f64 = f64::INFINITY;
let mut best_match: Option<S> = None;
if lo == hi {
return None;
}
while hi != lo {
let mid = (hi + lo) / 2;
let scan = self.get_frame_by_index(mid)?;
let scan_time = scan.start_time();
let err = (scan_time - time).abs();
if err < best_error {
best_error = err;
best_match = Some(scan);
}
if hi.saturating_sub(1) == lo {
return best_match;
} else if scan_time > time {
hi = mid;
} else {
lo = mid;
}
}
best_match
}
fn len(&self) -> usize {
self.get_index().len()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn get_index(&self) -> &OffsetIndex;
fn set_index(&mut self, index: OffsetIndex);
fn _offset_of_id(&self, id: &str) -> Option<u64> {
self.get_index().get(id)
}
fn _offset_of_index(&self, index: usize) -> Option<u64> {
self.get_index()
.get_index(index)
.map(|(_id, offset)| offset)
}
fn _offset_of_time(&mut self, time: f64) -> Option<u64> {
match self.get_frame_by_time(time) {
Some(scan) => self._offset_of_index(scan.index()),
None => None,
}
}
fn get_group_by_index(&mut self, index: usize) -> Option<IonMobilityFrameGroup<C, D, S>>
where
Self: Sized
{
self.groups().nth(index)
}
fn iter(&mut self) -> IonMobilityFrameIterator<'_, C, D, S, Self>
where
Self: Sized,
{
IonMobilityFrameIterator::new(self)
}
fn groups(
&mut self,
) -> IonMobilityFrameGroupingIterator<IonMobilityFrameIterator<'_, C, D, S, Self>, C, D, S>
where
Self: Sized,
{
IonMobilityFrameGroupingIterator::new(self.iter())
}
fn into_groups(self) -> IonMobilityFrameGroupingIterator<Self, C, D, S>
where
Self: Sized,
{
IonMobilityFrameGroupingIterator::new(self)
}
}
pub struct IonMobilityFrameIterator<
'lifespan,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> {
source: &'lifespan mut R,
frame_type: PhantomData<S>,
centroid_type: PhantomData<C>,
deconvoluted_type: PhantomData<D>,
index: usize,
back_index: usize,
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> IonMobilityFrameIterator<'_, C, D, S, R>
{
pub fn new(source: &mut R) -> IonMobilityFrameIterator<'_, C, D, S, R> {
IonMobilityFrameIterator::<C, D, S, R> {
source,
index: 0,
back_index: 0,
frame_type: PhantomData,
centroid_type: PhantomData,
deconvoluted_type: PhantomData,
}
}
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> Iterator for IonMobilityFrameIterator<'_, C, D, S, R>
{
type Item = S;
fn next(&mut self) -> Option<Self::Item> {
if ExactSizeIterator::len(self) == 0 {
return None;
}
let result = self.source.get_frame_by_index(self.index);
self.index += 1;
result
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.index += n.min(ExactSizeIterator::len(self));
self.next()
}
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> ExactSizeIterator for IonMobilityFrameIterator<'_, C, D, S, R>
{
fn len(&self) -> usize {
self.source
.len()
.saturating_sub(self.index)
.saturating_sub(self.back_index)
}
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> DoubleEndedIterator for IonMobilityFrameIterator<'_, C, D, S, R>
{
fn next_back(&mut self) -> Option<Self::Item> {
if ExactSizeIterator::len(self) == 0 {
return None;
};
let i = self.source.len() - (self.back_index + 1);
let result = self.source.get_frame_by_index(i);
self.back_index += 1;
result
}
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> IonMobilityFrameSource<C, D, S> for IonMobilityFrameIterator<'_, C, D, S, R>
{
fn reset(&mut self) {
self.index = 0;
self.back_index = 0;
}
fn get_frame_by_id(&mut self, id: &str) -> Option<S> {
self.source.get_frame_by_id(id)
}
fn get_frame_by_index(&mut self, index: usize) -> Option<S> {
self.source.get_frame_by_index(index)
}
fn get_frame_by_time(&mut self, time: f64) -> Option<S> {
self.source.get_frame_by_time(time)
}
fn get_index(&self) -> &OffsetIndex {
self.source.get_index()
}
fn set_index(&mut self, index: OffsetIndex) {
self.source.set_index(index);
}
fn detail_level(&self) -> &DetailLevel {
self.source.detail_level()
}
fn set_detail_level(&mut self, detail_level: DetailLevel) {
self.source.set_detail_level(detail_level);
}
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> MSDataFileMetadata for IonMobilityFrameIterator<'_, C, D, S, R>
where
R: MSDataFileMetadata,
{
crate::delegate_impl_metadata_trait!(source);
}
#[derive(Debug)]
pub struct Generic3DIonMobilityFrameSource<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
> {
source: R,
_cp: PhantomData<CP>,
_dp: PhantomData<DP>,
_c: PhantomData<C>,
_d: PhantomData<D>,
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> MSDataFileMetadata for Generic3DIonMobilityFrameSource<CP, DP, R, C, D>
where
R: MSDataFileMetadata,
{
crate::delegate_impl_metadata_trait!(source);
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> IonMobilityFrameSource<C, D, MultiLayerIonMobilityFrame<C, D>>
for Generic3DIonMobilityFrameSource<CP, DP, R, C, D>
{
fn detail_level(&self) -> &DetailLevel {
self.source.detail_level()
}
fn set_detail_level(&mut self, detail_level: DetailLevel) {
self.source.set_detail_level(detail_level);
}
fn reset(&mut self) {
self.source.reset()
}
fn get_frame_by_id(&mut self, id: &str) -> Option<MultiLayerIonMobilityFrame<C, D>> {
self.source.get_spectrum_by_id(id).and_then(|s| {
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {id} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
})
}
fn get_frame_by_index(&mut self, index: usize) -> Option<MultiLayerIonMobilityFrame<C, D>> {
self.source.get_spectrum_by_index(index).and_then(|s| {
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {index} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
})
}
fn get_frame_by_time(&mut self, time: f64) -> Option<MultiLayerIonMobilityFrame<C, D>> {
self.source.get_spectrum_by_time(time).and_then(|s| {
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {time} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
})
}
fn get_index(&self) -> &OffsetIndex {
self.source.get_index()
}
fn set_index(&mut self, index: OffsetIndex) {
self.source.set_index(index)
}
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> Iterator for Generic3DIonMobilityFrameSource<CP, DP, R, C, D>
{
type Item = MultiLayerIonMobilityFrame<C, D>;
fn next(&mut self) -> Option<Self::Item> {
self.source
.by_ref()
.filter_map(|s| MultiLayerIonMobilityFrame::try_from(s).ok())
.next()
}
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> Generic3DIonMobilityFrameSource<CP, DP, R, C, D>
{
pub fn new(source: R) -> Self {
Self {
source,
_cp: PhantomData,
_dp: PhantomData,
_c: PhantomData,
_d: PhantomData,
}
}
pub fn get_inner(&self) -> &R {
&self.source
}
pub fn get_mut(&mut self) -> &mut R {
&mut self.source
}
pub fn into_inner(self) -> R {
self.source
}
}
#[derive(Debug, Error)]
pub enum IonMobilityFrameAccessError {
#[error("The requested frame was not found")]
FrameNotFound,
#[error("The requested frame native ID {0} was not found")]
FrameIdNotFound(String),
#[error("The requested frame index {0} was not found")]
FrameIndexNotFound(usize),
#[error("I/O error occurred while reading: {0:?}")]
IOError(#[source] Option<io::Error>),
}
impl From<IonMobilityFrameAccessError> for io::Error {
fn from(value: IonMobilityFrameAccessError) -> Self {
let s = value.to_string();
match value {
IonMobilityFrameAccessError::FrameNotFound => {
io::Error::new(io::ErrorKind::NotFound, s)
}
IonMobilityFrameAccessError::FrameIdNotFound(_) => {
io::Error::new(io::ErrorKind::NotFound, s)
}
IonMobilityFrameAccessError::FrameIndexNotFound(_) => {
io::Error::new(io::ErrorKind::NotFound, s)
}
IonMobilityFrameAccessError::IOError(e) => match e {
Some(e) => e,
None => io::Error::other(s),
},
}
}
}
impl From<IonMobilityFrameAccessError> for SpectrumAccessError {
fn from(value: IonMobilityFrameAccessError) -> Self {
match value {
IonMobilityFrameAccessError::FrameNotFound => SpectrumAccessError::SpectrumNotFound,
IonMobilityFrameAccessError::FrameIdNotFound(id) => {
SpectrumAccessError::SpectrumIdNotFound(id)
}
IonMobilityFrameAccessError::FrameIndexNotFound(i) => {
SpectrumAccessError::SpectrumIndexNotFound(i)
}
IonMobilityFrameAccessError::IOError(error) => SpectrumAccessError::IOError(error),
}
}
}
impl From<SpectrumAccessError> for IonMobilityFrameAccessError {
fn from(value: SpectrumAccessError) -> Self {
match value {
SpectrumAccessError::SpectrumNotFound => IonMobilityFrameAccessError::FrameNotFound,
SpectrumAccessError::SpectrumIdNotFound(id) => {
IonMobilityFrameAccessError::FrameIdNotFound(id)
}
SpectrumAccessError::SpectrumIndexNotFound(i) => {
IonMobilityFrameAccessError::FrameIndexNotFound(i)
}
SpectrumAccessError::IOError(error) => IonMobilityFrameAccessError::IOError(error),
}
}
}
pub trait RandomAccessIonMobilityFrameIterator<
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
S: IonMobilityFrameLike<C, D> = MultiLayerIonMobilityFrame<C, D>,
>: IonMobilityFrameSource<C, D, S>
{
fn start_from_id(&mut self, id: &str) -> Result<&mut Self, IonMobilityFrameAccessError>;
fn start_from_index(&mut self, index: usize) -> Result<&mut Self, IonMobilityFrameAccessError>;
fn start_from_time(&mut self, time: f64) -> Result<&mut Self, IonMobilityFrameAccessError>;
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> RandomAccessIonMobilityFrameIterator<C, D, MultiLayerIonMobilityFrame<C, D>>
for Generic3DIonMobilityFrameSource<CP, DP, R, C, D>
where
R: RandomAccessSpectrumIterator<CP, DP, MultiLayerSpectrum<CP, DP>>,
{
fn start_from_id(&mut self, id: &str) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_id(id) {
Ok(_) => Ok(self),
Err(e) => Err(IonMobilityFrameAccessError::from(e)),
}
}
fn start_from_index(&mut self, index: usize) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_index(index) {
Ok(_) => Ok(self),
Err(e) => Err(IonMobilityFrameAccessError::from(e)),
}
}
fn start_from_time(&mut self, time: f64) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_time(time) {
Ok(_) => Ok(self),
Err(e) => Err(IonMobilityFrameAccessError::from(e)),
}
}
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
R: IonMobilityFrameSource<C, D, S>,
> RandomAccessIonMobilityFrameIterator<C, D, S> for IonMobilityFrameIterator<'_, C, D, S, R>
{
fn start_from_id(&mut self, id: &str) -> Result<&mut Self, IonMobilityFrameAccessError> {
if let Some(scan) = self.get_frame_by_id(id) {
self.index = scan.index();
self.back_index = 0;
Ok(self)
} else if self.get_index().contains_key(id) {
Err(IonMobilityFrameAccessError::IOError(None))
} else {
Err(IonMobilityFrameAccessError::FrameIdNotFound(id.to_string()))
}
}
fn start_from_index(&mut self, index: usize) -> Result<&mut Self, IonMobilityFrameAccessError> {
if index < self.source.len() {
self.index = index;
self.back_index = 0;
Ok(self)
} else {
Err(IonMobilityFrameAccessError::FrameIndexNotFound(index))
}
}
fn start_from_time(&mut self, time: f64) -> Result<&mut Self, IonMobilityFrameAccessError> {
if let Some(scan) = self.get_frame_by_time(time) {
self.index = scan.index();
self.back_index = 0;
Ok(self)
} else {
let Some(last_index) = self.source.len().checked_sub(1) else {
return Err(IonMobilityFrameAccessError::FrameNotFound);
};
match self.get_frame_by_index(last_index) {
Some(scan) if scan.start_time() < time => {
Err(IonMobilityFrameAccessError::FrameNotFound)
}
_ => Err(IonMobilityFrameAccessError::IOError(None)),
}
}
}
}
pub trait IonMobilityFrameWriter<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
>
{
fn write_frame<S: IonMobilityFrameLike<C, D> + 'static>(
&mut self,
spectrum: &S,
) -> io::Result<usize>;
fn write_frame_owned<S: IonMobilityFrameLike<C, D> + 'static>(
&mut self,
spectrum: S,
) -> io::Result<usize> {
self.write_frame(&spectrum)
}
fn flush_frame(&mut self) -> io::Result<()>;
fn write_all_frames<'b, S: IonMobilityFrameLike<C, D> + 'static, T: Iterator<Item = &'b S>>(
&mut self,
iterator: T,
) -> io::Result<usize> {
let mut n = 0;
for spectrum in iterator {
n += self.write_frame(spectrum)?;
}
Ok(n)
}
fn write_all_frames_owned<S: IonMobilityFrameLike<C, D> + 'static, T: Iterator<Item = S>>(
&mut self,
iterator: T,
) -> io::Result<usize> {
let mut n = 0;
for spectrum in iterator {
n += self.write_frame_owned(spectrum)?;
}
Ok(n)
}
fn write_frame_group<
S: IonMobilityFrameLike<C, D> + 'static,
G: IonMobilityFrameGrouping<C, D, S> + 'static,
>(
&mut self,
group: &G,
) -> io::Result<usize> {
let mut n = 0;
if let Some(precursor) = group.precursor() {
n += self.write_frame(precursor)?;
}
for product in group.products() {
n += self.write_frame(product)?;
}
Ok(n)
}
fn write_frame_group_owned<
S: IonMobilityFrameLike<C, D> + 'static,
G: IonMobilityFrameGrouping<C, D, S> + 'static,
>(
&mut self,
group: G,
) -> io::Result<usize> {
let (precursor, products) = group.into_parts();
let mut n = 0;
if let Some(precursor) = precursor {
n += self.write_frame_owned(precursor)?;
}
for product in products {
n += self.write_frame_owned(product)?;
}
Ok(n)
}
fn write_all_frame_groups<
'b,
S: IonMobilityFrameLike<C, D> + 'static,
G: IonMobilityFrameGrouping<C, D, S> + 'static,
T: Iterator<Item = &'b G>,
>(
&mut self,
iterator: T,
) -> io::Result<usize> {
let mut n = 0;
for group in iterator {
n += self.write_frame_group(group)?;
}
Ok(n)
}
fn write_all_frame_groups_owned<
S: IonMobilityFrameLike<C, D> + 'static,
G: IonMobilityFrameGrouping<C, D, S> + 'static,
T: Iterator<Item = G>,
>(
&mut self,
iterator: T,
) -> io::Result<usize> {
let mut n = 0;
for group in iterator {
n += self.write_frame_group_owned(group)?;
}
Ok(n)
}
fn close_frames(&mut self) -> io::Result<()>;
}
#[derive(Debug)]
pub struct BorrowedGeneric3DIonMobilityFrameSource<
'a,
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
> {
source: &'a mut R,
_cp: PhantomData<CP>,
_dp: PhantomData<DP>,
_c: PhantomData<C>,
_d: PhantomData<D>,
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> MSDataFileMetadata for BorrowedGeneric3DIonMobilityFrameSource<'_, CP, DP, R, C, D>
where
R: MSDataFileMetadata,
{
crate::delegate_impl_metadata_trait!(source);
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> IonMobilityFrameSource<C, D, MultiLayerIonMobilityFrame<C, D>>
for BorrowedGeneric3DIonMobilityFrameSource<'_, CP, DP, R, C, D>
{
fn detail_level(&self) -> &DetailLevel {
self.source.detail_level()
}
fn set_detail_level(&mut self, detail_level: DetailLevel) {
self.source.set_detail_level(detail_level);
}
fn reset(&mut self) {
self.source.reset()
}
fn get_frame_by_id(&mut self, id: &str) -> Option<MultiLayerIonMobilityFrame<C, D>> {
self.source.get_spectrum_by_id(id).and_then(|s| {
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {id} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
})
}
fn get_frame_by_index(&mut self, index: usize) -> Option<MultiLayerIonMobilityFrame<C, D>> {
self.source.get_spectrum_by_index(index).and_then(|s| {
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {index} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
})
}
fn get_frame_by_time(&mut self, time: f64) -> Option<MultiLayerIonMobilityFrame<C, D>> {
self.source.get_spectrum_by_time(time).and_then(|s| {
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {time} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
})
}
fn get_index(&self) -> &OffsetIndex {
self.source.get_index()
}
fn set_index(&mut self, index: OffsetIndex) {
self.source.set_index(index)
}
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> Iterator for BorrowedGeneric3DIonMobilityFrameSource<'_, CP, DP, R, C, D>
{
type Item = MultiLayerIonMobilityFrame<C, D>;
fn next(&mut self) -> Option<Self::Item> {
self.source
.by_ref()
.filter_map(|s| MultiLayerIonMobilityFrame::try_from(s).ok())
.next()
}
}
impl<
'a,
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> BorrowedGeneric3DIonMobilityFrameSource<'a, CP, DP, R, C, D>
{
#[allow(unused)]
pub fn new(source: &'a mut R) -> Self {
Self {
source,
_cp: PhantomData,
_dp: PhantomData,
_c: PhantomData,
_d: PhantomData,
}
}
}
impl<
CP: CentroidLike,
DP: DeconvolutedCentroidLike,
R: SpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
> RandomAccessIonMobilityFrameIterator<C, D, MultiLayerIonMobilityFrame<C, D>>
for BorrowedGeneric3DIonMobilityFrameSource<'_, CP, DP, R, C, D>
where
R: RandomAccessSpectrumIterator<CP, DP, MultiLayerSpectrum<CP, DP>>,
{
fn start_from_id(&mut self, id: &str) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_id(id) {
Ok(_) => Ok(self),
Err(e) => Err(match e {
SpectrumAccessError::SpectrumIdNotFound(id) => {
IonMobilityFrameAccessError::FrameIdNotFound(id)
}
SpectrumAccessError::SpectrumNotFound => IonMobilityFrameAccessError::FrameNotFound,
SpectrumAccessError::IOError(e) => IonMobilityFrameAccessError::IOError(e),
_ => todo!(),
}),
}
}
fn start_from_index(&mut self, index: usize) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_index(index) {
Ok(_) => Ok(self),
Err(e) => Err(match e {
SpectrumAccessError::SpectrumIndexNotFound(i) => {
IonMobilityFrameAccessError::FrameIndexNotFound(i)
}
SpectrumAccessError::SpectrumNotFound => IonMobilityFrameAccessError::FrameNotFound,
SpectrumAccessError::IOError(e) => IonMobilityFrameAccessError::IOError(e),
_ => todo!(),
}),
}
}
fn start_from_time(&mut self, time: f64) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_time(time) {
Ok(_) => Ok(self),
Err(e) => Err(match e {
SpectrumAccessError::SpectrumNotFound => IonMobilityFrameAccessError::FrameNotFound,
SpectrumAccessError::IOError(e) => IonMobilityFrameAccessError::IOError(e),
_ => todo!(),
}),
}
}
}
pub trait RandomAccessIonMobilityFrameGroupingIterator<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D> = MultiLayerIonMobilityFrame<C, D>,
G: IonMobilityFrameGrouping<C, D, S> = IonMobilityFrameGroup<
C,
D,
MultiLayerIonMobilityFrame<C, D>,
>,
>: Iterator<Item = G>
{
fn start_from_id(&mut self, id: &str) -> Result<&mut Self, IonMobilityFrameAccessError>;
fn start_from_index(&mut self, index: usize) -> Result<&mut Self, IonMobilityFrameAccessError>;
fn start_from_time(&mut self, time: f64) -> Result<&mut Self, IonMobilityFrameAccessError>;
fn reset_state(&mut self);
}
#[derive(Debug, Error)]
pub enum IntoIonMobilityFrameSourceError {
#[error("No ion mobility frames were found")]
NoIonMobilityFramesFound,
#[error("Cannot convert to requested frame type")]
ConversionNotPossible,
}
pub trait IntoIonMobilityFrameSource<C: CentroidLike, D: DeconvolutedCentroidLike>:
SpectrumSource<C, D, MultiLayerSpectrum<C, D>> + Sized
{
type IonMobilityFrameSource<CF: FeatureLike<MZ, IonMobility>, DF: FeatureLike<Mass, IonMobility> + KnownCharge>: IonMobilityFrameSource<CF, DF, MultiLayerIonMobilityFrame<CF, DF>>;
fn try_into_frame_source<
CF: FeatureLike<MZ, IonMobility>,
DF: FeatureLike<Mass, IonMobility> + KnownCharge,
>(
self,
) -> Result<Self::IonMobilityFrameSource<CF, DF>, IntoIonMobilityFrameSourceError>;
fn into_frame_source<
CF: FeatureLike<MZ, IonMobility>,
DF: FeatureLike<Mass, IonMobility> + KnownCharge,
>(
self,
) -> Self::IonMobilityFrameSource<CF, DF> {
self.try_into_frame_source().unwrap()
}
fn has_ion_mobility(&mut self) -> Option<HasIonMobility> {
let details = *self.detail_level();
let reader = DetailLevelGuard::new(self, details, Self::set_detail_level);
reader.source.set_detail_level(DetailLevel::Lazy);
let n = reader.source.len();
if n == 0 {
return Some(HasIonMobility::None);
}
let mut handle = reader.source.iter();
let mut status = HasIonMobility::None;
let step_size = if n > 100 { n / 100 } else { n };
for i in (0..n).step_by(step_size) {
let spec = handle.get_spectrum_by_index(i)?;
let cls = spec.has_ion_mobility_class();
status = status.max(cls);
if status > HasIonMobility::None {
break;
}
}
Some(status)
}
}
#[cfg(feature = "async_partial")]
mod async_traits {
use std::future::Future;
use futures::{
stream::{self, FusedStream},
Stream,
};
use super::*;
use crate::io::traits::{AsyncRandomAccessSpectrumIterator, AsyncSpectrumSource};
pub trait AsyncIonMobilityFrameSource<
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
S: IonMobilityFrameLike<C, D> = MultiLayerIonMobilityFrame<C, D>,
>: Send
{
fn reset(&mut self) -> impl Future<Output = ()>;
fn detail_level(&self) -> &DetailLevel;
fn set_detail_level(&mut self, detail_level: DetailLevel);
fn get_frame_by_id(&mut self, id: &str) -> impl Future<Output = Option<S>>;
fn get_frame_by_index(&mut self, index: usize) -> impl Future<Output = Option<S>>;
#[allow(async_fn_in_trait)]
async fn get_frame_by_time(&mut self, time: f64) -> Option<S> {
{
let n = self.len();
if n == 0 && !self.get_index().init {
warn!("Attempting to use `get_frame_by_time` when the frame index has not been initialized.");
return None;
}
let mut lo: usize = 0;
let mut hi: usize = n;
let mut best_error: f64 = f64::INFINITY;
let mut best_match: Option<(usize, S)> = None;
if lo == hi {
return None;
}
let original_detail_level = *self.detail_level();
let reader =
DetailLevelGuard::new(self, original_detail_level, Self::set_detail_level);
reader.source.set_detail_level(DetailLevel::MetadataOnly);
let search_detail_level = *reader.source.detail_level();
while hi != lo {
let mid = (hi + lo) / 2;
let scan = reader.source.get_frame_by_index(mid).await?;
let scan_time = scan.start_time();
let err = (scan_time - time).abs();
if err < best_error {
best_error = err;
best_match = Some((mid, scan));
}
if hi.saturating_sub(1) == lo {
break;
} else if scan_time > time {
hi = mid;
} else {
lo = mid;
}
}
drop(reader);
let (index, scan) = best_match?;
if original_detail_level == search_detail_level {
Some(scan)
} else {
self.get_frame_by_index(index).await
}
}
}
fn len(&self) -> usize {
self.get_index().len()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn get_index(&self) -> &OffsetIndex;
fn set_index(&mut self, index: OffsetIndex);
fn _offset_of_id(&self, id: &str) -> Option<u64> {
self.get_index().get(id)
}
fn _offset_of_index(&self, index: usize) -> Option<u64> {
self.get_index()
.get_index(index)
.map(|(_id, offset)| offset)
}
#[allow(async_fn_in_trait)]
async fn _offset_of_time(&mut self, time: f64) -> Option<u64> {
{
let saved = *self.detail_level();
let reader = DetailLevelGuard::new(self, saved, Self::set_detail_level);
reader.source.set_detail_level(DetailLevel::MetadataOnly);
match reader.source.get_frame_by_time(time).await {
Some(scan) => reader.source._offset_of_index(scan.index()),
None => None,
}
}
}
fn read_next_frame(&mut self) -> impl Future<Output = Option<S>>;
fn as_stream(&mut self) -> impl IonMobilityFrameStream<C, D, S> + Unpin + '_ {
Box::pin(stream::unfold(self, |reader| async {
let frame = reader.read_next_frame();
frame.await.map(|val| (val, reader))
}))
}
}
pub trait AsyncRandomAccessIonMobilityFrameIterator<
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
S: IonMobilityFrameLike<C, D> = MultiLayerIonMobilityFrame<C, D>,
>: AsyncIonMobilityFrameSource<C, D, S> + Sized
{
fn start_from_id(
&mut self,
id: &str,
) -> impl Future<Output = Result<&mut Self, IonMobilityFrameAccessError>>;
fn start_from_index(
&mut self,
index: usize,
) -> impl Future<Output = Result<&mut Self, IonMobilityFrameAccessError>>;
fn start_from_time(
&mut self,
time: f64,
) -> impl Future<Output = Result<&mut Self, IonMobilityFrameAccessError>>;
}
pub trait IonMobilityFrameStream<
C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
S: IonMobilityFrameLike<C, D> = MultiLayerIonMobilityFrame<C, D>,
>: Stream<Item = S> + FusedStream
{
}
impl<
C: FeatureLike<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge,
S: IonMobilityFrameLike<C, D>,
T: Stream<Item = S> + FusedStream,
> IonMobilityFrameStream<C, D, S> for T
{
}
#[derive(Debug)]
pub struct AsyncGeneric3DIonMobilityFrameSource<
CP: CentroidLike + Send + Sync,
DP: DeconvolutedCentroidLike + Send + Sync,
R: AsyncSpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> + Send + Sync = Feature<MZ, IonMobility>,
D: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync = ChargedFeature<
Mass,
IonMobility,
>,
> {
source: R,
_cp: PhantomData<CP>,
_dp: PhantomData<DP>,
_c: PhantomData<C>,
_d: PhantomData<D>,
}
impl<
CP: CentroidLike + Send + Sync,
DP: DeconvolutedCentroidLike + Send + Sync,
R: AsyncSpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> + Send + Sync,
D: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
> AsyncGeneric3DIonMobilityFrameSource<CP, DP, R, C, D>
{
pub fn new(source: R) -> Self {
Self {
source,
_cp: PhantomData,
_dp: PhantomData,
_c: PhantomData,
_d: PhantomData,
}
}
pub fn get_inner(&self) -> &R {
&self.source
}
pub fn get_mut(&mut self) -> &mut R {
&mut self.source
}
pub fn into_inner(self) -> R {
self.source
}
}
impl<
CP: CentroidLike + Send + Sync,
DP: DeconvolutedCentroidLike + Send + Sync,
R: AsyncSpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> + Send + Sync,
D: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
> MSDataFileMetadata for AsyncGeneric3DIonMobilityFrameSource<CP, DP, R, C, D>
where
R: MSDataFileMetadata,
{
crate::delegate_impl_metadata_trait!(source);
}
impl<
CP: CentroidLike + Send + Sync,
DP: DeconvolutedCentroidLike + Send + Sync,
R: AsyncSpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> + Send + Sync,
D: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
> AsyncIonMobilityFrameSource<C, D, MultiLayerIonMobilityFrame<C, D>>
for AsyncGeneric3DIonMobilityFrameSource<CP, DP, R, C, D>
{
fn detail_level(&self) -> &DetailLevel {
self.source.detail_level()
}
fn set_detail_level(&mut self, detail_level: DetailLevel) {
self.source.set_detail_level(detail_level);
}
async fn reset(&mut self) {
self.source.reset().await
}
async fn get_frame_by_id(&mut self, id: &str) -> Option<MultiLayerIonMobilityFrame<C, D>> {
let s = self.source.get_spectrum_by_id(id).await?;
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {id} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
}
async fn get_frame_by_index(
&mut self,
index: usize,
) -> Option<MultiLayerIonMobilityFrame<C, D>> {
let s = self.source.get_spectrum_by_index(index).await?;
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {index} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
}
async fn get_frame_by_time(
&mut self,
time: f64,
) -> Option<MultiLayerIonMobilityFrame<C, D>> {
let s = self.source.get_spectrum_by_time(time).await?;
MultiLayerIonMobilityFrame::try_from(s).map_or_else(
|err| {
warn!("Failed to convert {time} to MultiLayerIonMobilityFrame: {err}");
None
},
Some,
)
}
async fn read_next_frame(&mut self) -> Option<MultiLayerIonMobilityFrame<C, D>> {
while let Some(s) = self.source.read_next().await {
match MultiLayerIonMobilityFrame::try_from(s) {
Ok(frame) => return Some(frame),
Err(err) => {
warn!("Failed to convert spectrum to MultiLayerIonMobilityFrame: {err}")
}
}
}
None
}
fn get_index(&self) -> &OffsetIndex {
self.source.get_index()
}
fn set_index(&mut self, index: OffsetIndex) {
self.source.set_index(index)
}
}
impl<
CP: CentroidLike + Send + Sync,
DP: DeconvolutedCentroidLike + Send + Sync,
R: AsyncSpectrumSource<CP, DP, MultiLayerSpectrum<CP, DP>>,
C: FeatureLike<MZ, IonMobility> + Send + Sync,
D: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
> AsyncRandomAccessIonMobilityFrameIterator<C, D, MultiLayerIonMobilityFrame<C, D>>
for AsyncGeneric3DIonMobilityFrameSource<CP, DP, R, C, D>
where
R: AsyncRandomAccessSpectrumIterator<CP, DP, MultiLayerSpectrum<CP, DP>>,
{
async fn start_from_id(
&mut self,
id: &str,
) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_id(id).await {
Ok(_) => Ok(self),
Err(e) => Err(IonMobilityFrameAccessError::from(e)),
}
}
async fn start_from_index(
&mut self,
index: usize,
) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_index(index).await {
Ok(_) => Ok(self),
Err(e) => Err(IonMobilityFrameAccessError::from(e)),
}
}
async fn start_from_time(
&mut self,
time: f64,
) -> Result<&mut Self, IonMobilityFrameAccessError> {
match self.source.start_from_time(time).await {
Ok(_) => Ok(self),
Err(e) => Err(IonMobilityFrameAccessError::from(e)),
}
}
}
pub trait AsyncIntoIonMobilityFrameSource<
C: CentroidLike + Send + Sync,
D: DeconvolutedCentroidLike + Send + Sync,
>: AsyncSpectrumSource<C, D, MultiLayerSpectrum<C, D>> + Sized
{
type IonMobilityFrameSource<
CF: FeatureLike<MZ, IonMobility> + Send + Sync,
DF: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
>: AsyncIonMobilityFrameSource<CF, DF, MultiLayerIonMobilityFrame<CF, DF>>;
fn try_into_frame_source<
CF: FeatureLike<MZ, IonMobility> + Send + Sync,
DF: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
>(
self,
) -> impl Future<
Output = Result<Self::IonMobilityFrameSource<CF, DF>, IntoIonMobilityFrameSourceError>,
>;
#[allow(async_fn_in_trait)]
async fn into_frame_source<
CF: FeatureLike<MZ, IonMobility> + Send + Sync,
DF: FeatureLike<Mass, IonMobility> + KnownCharge + Send + Sync,
>(
self,
) -> Self::IonMobilityFrameSource<CF, DF> {
self.try_into_frame_source().await.unwrap()
}
#[allow(async_fn_in_trait)]
async fn has_ion_mobility(&mut self) -> Option<HasIonMobility> {
let details = *self.detail_level();
let reader = DetailLevelGuard::new(self, details, Self::set_detail_level);
reader.source.set_detail_level(DetailLevel::Lazy);
let n = reader.source.len();
if n == 0 {
return Some(HasIonMobility::None);
}
let mut status = HasIonMobility::None;
let step_size = if n > 100 { n / 100 } else { n };
for i in (0..n).step_by(step_size) {
let spec = reader.source.get_spectrum_by_index(i).await?;
let cls = spec.has_ion_mobility_class();
status = status.max(cls);
if status > HasIonMobility::None {
break;
}
}
Some(status)
}
}
}
#[cfg(feature = "async_partial")]
pub use async_traits::{
AsyncGeneric3DIonMobilityFrameSource, AsyncIntoIonMobilityFrameSource,
AsyncIonMobilityFrameSource, AsyncRandomAccessIonMobilityFrameIterator, IonMobilityFrameStream,
};
#[cfg(all(test, feature = "async_partial"))]
mod async_trait_tests {
use super::*;
use crate::io::traits::{AsyncRandomAccessSpectrumIterator, AsyncSpectrumSource};
use futures::StreamExt;
use mzpeaks::{CentroidPeak, DeconvolutedPeak};
struct TestFrames {
level: DetailLevel,
index: OffsetIndex,
fail_on: Option<DetailLevel>,
pending_on: Option<DetailLevel>,
}
impl AsyncIonMobilityFrameSource for TestFrames {
async fn reset(&mut self) {}
fn detail_level(&self) -> &DetailLevel {
&self.level
}
fn set_detail_level(&mut self, level: DetailLevel) {
self.level = level;
}
async fn get_frame_by_id(&mut self, _id: &str) -> Option<MultiLayerIonMobilityFrame> {
self.get_frame_by_index(0).await
}
async fn get_frame_by_index(&mut self, index: usize) -> Option<MultiLayerIonMobilityFrame> {
if self.pending_on == Some(self.level) {
std::future::pending::<()>().await;
}
if self.fail_on == Some(self.level) || index != 0 {
return None;
}
let description = crate::spectrum::IonMobilityFrameDescription {
index: 42,
..Default::default()
};
let features = (self.level != DetailLevel::MetadataOnly).then(|| {
mzpeaks::feature_map::FeatureMap::new(vec![Feature::new(
vec![100.0],
vec![1.0],
vec![10.0],
)])
});
Some(MultiLayerIonMobilityFrame {
description,
features,
arrays: None,
deconvoluted_features: None,
})
}
fn get_index(&self) -> &OffsetIndex {
&self.index
}
fn set_index(&mut self, index: OffsetIndex) {
self.index = index;
}
async fn read_next_frame(&mut self) -> Option<MultiLayerIonMobilityFrame> {
self.get_frame_by_index(0).await
}
}
#[tokio::test]
async fn test_time_lookup_detail_level() {
use std::future::Future;
use std::task::{Context, Poll};
let mut index = OffsetIndex::new("frame".into());
index.insert("frame", 0);
index.init = true;
let mut reader = TestFrames {
level: DetailLevel::Full,
index,
fail_on: None,
pending_on: None,
};
for level in [
DetailLevel::Full,
DetailLevel::Lazy,
DetailLevel::MetadataOnly,
] {
reader.level = level;
let frame = reader.get_frame_by_time(0.0).await.unwrap();
assert_eq!(frame.description.index, 42);
assert_eq!(frame.features.is_some(), level != DetailLevel::MetadataOnly);
assert_eq!(reader.level, level);
}
reader.level = DetailLevel::Full;
for level in [DetailLevel::Full, DetailLevel::MetadataOnly] {
reader.fail_on = Some(level);
assert!(reader.get_frame_by_time(0.0).await.is_none());
assert_eq!(reader.level, DetailLevel::Full);
}
reader.fail_on = None;
for level in [DetailLevel::Full, DetailLevel::MetadataOnly] {
reader.pending_on = Some(level);
let mut future = Box::pin(reader.get_frame_by_time(0.0));
let mut context = Context::from_waker(futures::task::noop_waker_ref());
assert!(matches!(future.as_mut().poll(&mut context), Poll::Pending));
drop(future);
assert_eq!(reader.level, DetailLevel::Full);
}
}
#[allow(unused)]
async fn provided_methods_are_callable<S: AsyncIonMobilityFrameSource>(src: &mut S) {
let _ = src.get_frame_by_time(1.0).await;
let _ = src._offset_of_time(1.0).await;
let _ = src.len();
let mut stream = src.as_stream();
let _first = stream.next().await;
}
#[allow(unused)]
async fn random_access_methods_are_callable<S: AsyncRandomAccessIonMobilityFrameIterator>(
src: &mut S,
) {
let _ = src.start_from_id("x").await;
let _ = src.start_from_index(0).await;
let _ = src.start_from_time(0.0).await;
}
#[allow(unused)]
async fn adapter_satisfies_frame_source<R>(source: R)
where
R: AsyncSpectrumSource<
CentroidPeak,
DeconvolutedPeak,
MultiLayerSpectrum<CentroidPeak, DeconvolutedPeak>,
>,
{
fn assert_frame_source<T: AsyncIonMobilityFrameSource>(_: &T) {}
let mut adapter =
AsyncGeneric3DIonMobilityFrameSource::<CentroidPeak, DeconvolutedPeak, R>::new(source);
assert_frame_source(&adapter);
let _ = adapter.get_frame_by_id("x").await;
let _ = adapter.get_frame_by_index(0).await;
let _ = adapter.get_frame_by_time(0.0).await;
let _ = adapter.read_next_frame().await;
adapter.reset().await;
let mut stream = adapter.as_stream();
let _first = stream.next().await;
}
#[allow(unused)]
async fn adapter_satisfies_random_access<R>(source: R)
where
R: AsyncRandomAccessSpectrumIterator<
CentroidPeak,
DeconvolutedPeak,
MultiLayerSpectrum<CentroidPeak, DeconvolutedPeak>,
>,
{
fn assert_random_access<T: AsyncRandomAccessIonMobilityFrameIterator>(_: &T) {}
let mut adapter =
AsyncGeneric3DIonMobilityFrameSource::<CentroidPeak, DeconvolutedPeak, R>::new(source);
assert_random_access(&adapter);
let _ = adapter.start_from_id("x").await;
let _ = adapter.start_from_index(0).await;
let _ = adapter.start_from_time(0.0).await;
}
}