Skip to content

Commit

Permalink
feat: improved cdf loader to work with hplc close #298 (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
josoriom committed Nov 3, 2022
1 parent 102fd1a commit 0babd2a
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 29 deletions.
10 changes: 10 additions & 0 deletions src/app/data/__tests__/getHPLCMeasurement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect, test } from 'vitest';

import { getHPLCMeasurement } from './getHPLCMeasurement';

test('getHPLCMeasurement', async () => {
const result = await getHPLCMeasurement();
expect(result).toHaveLength(1);
expect(result[0].data[0].variables.y.label).toBe('uv254');
expect(result[0].data[0].variables.y.data).toHaveLength(4651);
});
24 changes: 24 additions & 0 deletions src/app/data/__tests__/getHPLCMeasurement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { join } from 'node:path';

import { fileCollectionFromPath } from 'filelist-utils';

import { getEmptyDataState } from '../DataState';
import { append } from '../append';
import { cdfLoader } from '../loaders/cdfLoader';

export async function getHPLCMeasurement() {
const dataState = getEmptyDataState();

const fileCollection = await fileCollectionFromPath(
join(__dirname, 'data/cdf/'),
);

const filteredFileCollection = fileCollection.filter(
(file) => file.name === 'agilent-hplc.cdf',
);

const appended = await append(filteredFileCollection, dataState, {
loaders: [cdfLoader],
});
return appended.dataState.measurements.gclc.entries;
}
130 changes: 101 additions & 29 deletions src/app/data/loaders/cdfLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,78 +16,150 @@ export const cdfLoader: Loader = async function cdfLoader(
for (const file of fileCollection) {
if (file.name.match(/\.cdf$/i)) {
const reader = new NetCDFReader(await file.arrayBuffer(), { meta: true });
let kind: MeasurementKind | undefined;
if (
!reader.dataVariableExists('mass_values') ||
!reader.dataVariableExists('time_values')
reader.dataVariableExists('mass_values') &&
reader.dataVariableExists('time_values')
) {
kind = 'gclcms';
} else if (
reader.dataVariableExists('ordinate_values') &&
reader.getAttribute('detector_name') &&
reader.getAttribute('detector_name').match(/dad|tic/i)
) {
kind = 'gclc';
} else {
return newMeasurements;
}

const kind: MeasurementKind = 'gclcms';

const times = reader.getDataVariable('scan_acquisition_time');
const tics = reader.getDataVariable('total_intensity');
const pointCount = reader.getDataVariable('point_count');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
addMeta(reader, reader.globalAttributes);

let index = 0;
const allMasses: Float64Array[] = [];
const allIntensities: Float64Array[] = [];
for (let size of pointCount) {
// Taken from: https://github.com/cheminfo/netcdf-gcms
const masses = new Float64Array(size);
const intensities = new Float64Array(size);
for (let j = 0; j < size; j++) {
masses[j] = massValues[index];
intensities[j] = intensityValues[index++];
}
allIntensities.push(intensities);
allMasses.push(masses);
}
newMeasurements[kind].entries.push({
id: v4(),
meta: reader.header.meta,
filename: file.name,
path: file.relativePath || '',
info: {},
title: reader.getAttribute('experiment_title'),
data: normalizeChromatogram(times, allMasses, allIntensities, tics),
data:
kind === 'gclcms'
? chromatogramWithMassSpectra(reader)
: chromatogram(reader),
});
}
}
return newMeasurements;
};

function normalizeChromatogram(time, masses, intensities, tics) {
function chromatogramWithMassSpectra(reader) {
// Taken from: https://github.com/cheminfo/netcdf-gcms
const pointCount = reader.getDataVariable('point_count');
const massValues = reader.getDataVariable('mass_values');
const intensityValues = reader.getDataVariable('intensity_values');
const times = reader.getDataVariable('scan_acquisition_time');
const tics = reader.getDataVariable('total_intensity');

let index = 0;
const allMasses: Float64Array[] = [];
const allIntensities: Float64Array[] = [];
for (const size of pointCount) {
const masses = new Float64Array(size);
const intensities = new Float64Array(size);
for (let j = 0; j < size; j++) {
masses[j] = massValues[index];
intensities[j] = intensityValues[index++];
}
allIntensities.push(intensities);
allMasses.push(masses);
}
let data: any = [];
for (let i = 0; i < time.length; i++) {
for (let i = 0; i < times.length; i++) {
data.push({
meta: {},
info: {
time: { value: time[i], units: 's' },
time: { value: times[i], units: 's' },
tic: tics[i],
},
variables: {
x: {
symbol: 'X',
label: 'm/z',
units: '',
data: masses[i] || [],
data: allMasses[i] || [],
},
y: {
symbol: 'Y',
label: 'relative intensity',
units: '',
data: intensities[i] || [],
data: allIntensities[i] || [],
},
},
});
}
return data;
}

function chromatogram(reader) {
// Taken from: https://github.com/cheminfo/netcdf-gcms
let data: any = [];
const intensities: number[] = reader.getDataVariable('ordinate_values');
const numberPoints = intensities.length;
const detector: string = reader.getAttribute('detector_name');
let channel: string;
if (detector.match(/dad/i)) {
channel = `uv${Number(detector.replace(/.*Sig=(\d+).*/, '$1'))}`;
} else if (detector.match(/tic/i)) {
channel = 'tic';
} else {
channel = 'unknown';
}
const delayTime: number = reader.getDataVariable('actual_delay_time')[0];
const runtimeLength: number = reader.getDataVariable(
'actual_run_time_length',
)[0];
let samplingInterval;
if (reader.dataVariableExists('actual_sampling_interval')) {
samplingInterval = reader.getDataVariable('actual_sampling_interval')[0];

if (
Math.abs(delayTime + samplingInterval * numberPoints - runtimeLength) > 3
) {
throw new Error(
'The expected last time does not correspond to the runtimeLength',
);
}
} else {
samplingInterval = (runtimeLength - delayTime) / numberPoints;
}

let times: number[] = [];
let time = delayTime;
for (let i = 0; i < numberPoints; i++) {
times.push(time);
time += samplingInterval;
}

data.push({
meta: {},
info: {},
variables: {
x: {
symbol: 'X',
label: 'time',
units: 's',
data: times || [],
},
y: {
symbol: 'Y',
label: channel,
units: '',
data: intensities || [],
},
},
});
return data;
}

function addMeta(reader, globalAttributes) {
reader.header.meta = {};
for (const item of globalAttributes) {
Expand Down

0 comments on commit 0babd2a

Please sign in to comment.