From f7fc9f8552a5aac85aa6291439103184c5d8c6f2 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 13 Sep 2024 10:54:45 +0200 Subject: [PATCH 1/5] wip: testing conrect --- src/data/data2d/Spectrum2D/contours.ts | 41 ++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/data/data2d/Spectrum2D/contours.ts b/src/data/data2d/Spectrum2D/contours.ts index 75a409f74c..8a2ecc15e6 100644 --- a/src/data/data2d/Spectrum2D/contours.ts +++ b/src/data/data2d/Spectrum2D/contours.ts @@ -5,6 +5,7 @@ import type { Spectrum2D } from 'nmr-load-save'; import { calculateSanPlot } from '../../utilities/calculateSanPlot.js'; +const cache = new Map(); interface Level { positive: ContourItem; negative: ContourItem; @@ -228,11 +229,47 @@ function getContours(options: ContoursCalcOptions) { }; } - return conrec.drawContour({ + // assuming there is a global variable cache; + + const contoursInCache = []; + const levelsToCalculate = []; + for (const level of _range) { + if (cache.has(level)) { + contoursInCache.push(cache.get(level)); + } else { + levelsToCalculate.push(level); + } + } + + console.log({ contoursInCache, levelsToCalculate }); + + console.time('drawContour'); + const conrecResult = conrec.drawContour({ contourDrawer: 'basic', - levels: Array.from(_range), + levels: levelsToCalculate, timeout, }); + console.timeEnd('drawContour'); + console.log(conrecResult); + + console.time('dummy'); + // dummy calculation + // I don't understand why it takes any time (in my case 50ms) + const dummyResult = conrec.drawContour({ + contourDrawer: 'basic', + levels: [], + }); + console.timeEnd('dummy'); + console.log(dummyResult); + + for (const contour of conrecResult.contours) { + cache.set(contour.zValue, contour); + } + + return { + contours: [...contoursInCache, ...conrecResult.contours], + timeout: conrecResult.timeout, + }; } /** From ac16bd16e88cf4fd0d875563d3b40a735087b43d Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 13 Sep 2024 13:14:29 +0200 Subject: [PATCH 2/5] wip: add contour plot cache --- src/data/data2d/Spectrum2D/contours.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/data/data2d/Spectrum2D/contours.ts b/src/data/data2d/Spectrum2D/contours.ts index 8a2ecc15e6..fd0a33dab2 100644 --- a/src/data/data2d/Spectrum2D/contours.ts +++ b/src/data/data2d/Spectrum2D/contours.ts @@ -241,27 +241,15 @@ function getContours(options: ContoursCalcOptions) { } } - console.log({ contoursInCache, levelsToCalculate }); - - console.time('drawContour'); const conrecResult = conrec.drawContour({ contourDrawer: 'basic', levels: levelsToCalculate, timeout, }); - console.timeEnd('drawContour'); - console.log(conrecResult); - - console.time('dummy'); - // dummy calculation - // I don't understand why it takes any time (in my case 50ms) - const dummyResult = conrec.drawContour({ - contourDrawer: 'basic', - levels: [], - }); - console.timeEnd('dummy'); - console.log(dummyResult); + // TODO + // cache may of course not be Global ! + // moreover cache must be by spectrum !!! for (const contour of conrecResult.contours) { cache.set(contour.zValue, contour); } From fcbb635dbd693aba04ac7a88ab8f5d04682d5605 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Mon, 16 Sep 2024 11:05:48 +0200 Subject: [PATCH 3/5] refactor: cache contours per spectrum --- src/component/2d/ft/Contours.tsx | 29 +++++++++++++++----------- src/data/data2d/Spectrum2D/contours.ts | 23 +++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/component/2d/ft/Contours.tsx b/src/component/2d/ft/Contours.tsx index ecf15e3598..8f798c0104 100644 --- a/src/component/2d/ft/Contours.tsx +++ b/src/component/2d/ft/Contours.tsx @@ -75,19 +75,8 @@ function ContoursPaths({ }: ContoursPathsProps) { const activeSpectrum = useActiveSpectrum(); const preferences = usePreferences(); - const level = useContoursLevel(spectrum, sign); - const contours = useMemo(() => { - const { contours, timeout } = drawContours( - level, - spectrum, - sign === 'negative', - ); - if (timeout) { - onTimeout(); - } - return contours; - }, [spectrum, level, onTimeout, sign]); + const contours = useContours(spectrum, sign, onTimeout); const path = usePath(spectrum, contours); @@ -171,3 +160,19 @@ export default function Contours() { return ; } + +function useContours(spectrum: Spectrum2D, sign, onTimeout) { + const cache = useRef(new Map()); + const level = useContoursLevel(spectrum, sign); + + return useMemo(() => { + const { contours, timeout } = drawContours(spectrum, level, { + negative: sign === 'negative', + cache: cache.current, + }); + if (timeout) { + onTimeout(); + } + return contours; + }, [spectrum, level, onTimeout, sign]); +} diff --git a/src/data/data2d/Spectrum2D/contours.ts b/src/data/data2d/Spectrum2D/contours.ts index fd0a33dab2..05a0d7830d 100644 --- a/src/data/data2d/Spectrum2D/contours.ts +++ b/src/data/data2d/Spectrum2D/contours.ts @@ -1,11 +1,11 @@ import type { NmrData2DFt } from 'cheminfo-types'; import { Conrec } from 'ml-conrec'; +import { BasicContour } from 'ml-conrec/lib/BasicContourDrawer'; import { xMaxAbsoluteValue } from 'ml-spectra-processing'; import type { Spectrum2D } from 'nmr-load-save'; import { calculateSanPlot } from '../../utilities/calculateSanPlot.js'; -const cache = new Map(); interface Level { positive: ContourItem; negative: ContourItem; @@ -175,12 +175,18 @@ function range(from: number, to: number, step: number) { return result; } +interface DrawContoursOptions { + negative?: boolean; + quadrant?: 'rr' | 'ri' | 'ir' | 'ii'; + cache: Map; +} + function drawContours( - level: ContourItem, spectrum: Spectrum2D, - negative = false, - quadrant = 'rr', + level: ContourItem, + options: DrawContoursOptions, ) { + const { negative = false, quadrant = 'rr', cache } = options; const { contourLevels, numberOfLayers } = level; return getContours({ @@ -188,6 +194,7 @@ function drawContours( boundary: contourLevels, nbLevels: numberOfLayers, data: spectrum.data[quadrant], + cache, }); } @@ -197,6 +204,7 @@ interface ContoursCalcOptions { timeout?: number; nbLevels: number; data: NmrData2DFt['rr']; + cache: Map; } function getContours(options: ContoursCalcOptions) { @@ -206,6 +214,7 @@ function getContours(options: ContoursCalcOptions) { timeout = 2000, nbLevels, data, + cache, } = options; const xs = getRange(data.minX, data.maxX, data.z[0].length); const ys = getRange(data.minY, data.maxY, data.z.length); @@ -231,11 +240,11 @@ function getContours(options: ContoursCalcOptions) { // assuming there is a global variable cache; - const contoursInCache = []; - const levelsToCalculate = []; + const contoursInCache: BasicContour[] = []; + const levelsToCalculate: number[] = []; for (const level of _range) { if (cache.has(level)) { - contoursInCache.push(cache.get(level)); + contoursInCache.push(cache.get(level) as BasicContour); } else { levelsToCalculate.push(level); } From 19a7575a8f31a2716e176dcace3e108034a2e379 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Mon, 16 Sep 2024 11:10:05 +0200 Subject: [PATCH 4/5] test: fix parameters for datum 2D drawContours --- src/data/data2d/__tests__/Datum2D.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/data/data2d/__tests__/Datum2D.test.ts b/src/data/data2d/__tests__/Datum2D.test.ts index 9a0f499c2f..2008dbac4f 100644 --- a/src/data/data2d/__tests__/Datum2D.test.ts +++ b/src/data/data2d/__tests__/Datum2D.test.ts @@ -1,7 +1,8 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; -import { expect, test } from 'vitest'; +import type { BasicContour } from 'ml-conrec/lib/BasicContourDrawer'; +import { test, expect } from 'vitest'; import { addJcamp } from '../../SpectraManager.js'; import { drawContours } from '../Spectrum2D/contours.js'; @@ -9,6 +10,7 @@ import { drawContours } from '../Spectrum2D/contours.js'; test('Datum2D', () => { const jcamp = readFileSync(path.join(__dirname, './data/cosy.jdx'), 'utf8'); const spectra: any[] = []; + const cache = new Map(); addJcamp( spectra, @@ -26,13 +28,14 @@ test('Datum2D', () => { ); const positive = drawContours( - { contourLevels: [10, 100], numberOfLayers: 10 }, spectra[0], + { contourLevels: [10, 100], numberOfLayers: 10 }, + { cache }, ); const negative = drawContours( - { contourLevels: [10, 100], numberOfLayers: 10 }, spectra[0], - true, + { contourLevels: [10, 100], numberOfLayers: 10 }, + { cache, negative: true }, ); expect(positive.contours).toHaveLength(10); expect(positive.timeout).toBeFalsy(); From 2b2e920d007b67e1a512e0c3664599b10babdcfe Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Mon, 16 Sep 2024 13:04:38 +0200 Subject: [PATCH 5/5] refactor: draw contours --- src/data/data2d/Spectrum2D/contours.ts | 86 ++++++++++++++------------ 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/data/data2d/Spectrum2D/contours.ts b/src/data/data2d/Spectrum2D/contours.ts index 05a0d7830d..1bb7f329f8 100644 --- a/src/data/data2d/Spectrum2D/contours.ts +++ b/src/data/data2d/Spectrum2D/contours.ts @@ -178,7 +178,7 @@ function range(from: number, to: number, step: number) { interface DrawContoursOptions { negative?: boolean; quadrant?: 'rr' | 'ri' | 'ir' | 'ii'; - cache: Map; + cache: Map; } function drawContours( @@ -204,7 +204,7 @@ interface ContoursCalcOptions { timeout?: number; nbLevels: number; data: NmrData2DFt['rr']; - cache: Map; + cache: Map; } function getContours(options: ContoursCalcOptions) { @@ -216,57 +216,31 @@ function getContours(options: ContoursCalcOptions) { data, cache, } = options; - const xs = getRange(data.minX, data.maxX, data.z[0].length); - const ys = getRange(data.minY, data.maxY, data.z.length); - const conrec = new Conrec(data.z, { xs, ys, swapAxes: false }); - const max = Math.max(Math.abs(data.minZ), Math.abs(data.maxZ)); - const minLevel = calculateValueOfLevel(boundary[0], max); - const maxLevel = calculateValueOfLevel(boundary[1], max); - const diffRange = boundary[1] - boundary[0]; + const range = calculateRange(boundary, data, nbLevels, negative); - let _range = getRange(minLevel, maxLevel, Math.min(nbLevels, diffRange), 2); - if (negative) { - _range = _range.map((value) => -value); + if (isZeroRange(range)) { + return createEmptyResult(range); } - if (_range.every((r) => r === 0)) { - const emptyLine: number[] = []; + const diffRange = boundary[1] - boundary[0]; + + if (cache.has(diffRange)) { return { - contours: _range.map((r) => ({ zValue: r, lines: emptyLine })), + contours: cache.get(diffRange) ?? [], timeout: false, }; } - // assuming there is a global variable cache; - - const contoursInCache: BasicContour[] = []; - const levelsToCalculate: number[] = []; - for (const level of _range) { - if (cache.has(level)) { - contoursInCache.push(cache.get(level) as BasicContour); - } else { - levelsToCalculate.push(level); - } - } - - const conrecResult = conrec.drawContour({ + const conrec = initializeConrec(data); + const result = conrec.drawContour({ contourDrawer: 'basic', - levels: levelsToCalculate, + levels: range, timeout, }); + cache.set(diffRange, result.contours); - // TODO - // cache may of course not be Global ! - // moreover cache must be by spectrum !!! - for (const contour of conrecResult.contours) { - cache.set(contour.zValue, contour); - } - - return { - contours: [...contoursInCache, ...conrecResult.contours], - timeout: conrecResult.timeout, - }; + return result; } /** @@ -285,6 +259,38 @@ function calculateValueOfLevel(level: number, max: number, invert = false) { return (max * (2 ** (level / 10) - 1)) / (2 ** 10 - 1); } +function calculateRange( + boundary: [number, number], + data: ContoursCalcOptions['data'], + nbLevels: number, + negative: boolean, +): number[] { + const max = Math.max(Math.abs(data.minZ), Math.abs(data.maxZ)); + const minLevel = calculateValueOfLevel(boundary[0], max); + const maxLevel = calculateValueOfLevel(boundary[1], max); + const diffRange = boundary[1] - boundary[0]; + + const range = getRange(minLevel, maxLevel, Math.min(nbLevels, diffRange), 2); + return negative ? range.map((value) => -value) : range; +} + +function isZeroRange(range: number[]): boolean { + return range.every((r) => r === 0); +} + +function createEmptyResult(range: number[]) { + return { + contours: range.map((r) => ({ zValue: r, lines: [] })), + timeout: false, + }; +} + +function initializeConrec(data: ContoursCalcOptions['data']): Conrec { + const xs = getRange(data.minX, data.maxX, data.z[0].length); + const ys = getRange(data.minY, data.maxY, data.z.length); + return new Conrec(data.z, { xs, ys, swapAxes: false }); +} + export { drawContours, contoursManager,