Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions cypress/e2e/app.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,21 @@ describe('/mock', () => {
}
});

it('visualize datasets with fill value', () => {
cy.selectExplorerNode('nD_datasets');
it('visualize dataset with fill value', () => {
cy.selectExplorerNode('netcdf');

cy.selectExplorerNode('oneD_fillvalue');
cy.findByRole('figure', { name: 'oneD_fillvalue' }).should('be.visible');
cy.selectExplorerNode('_FillValue');
cy.findByRole('figure', { name: '_FillValue' }).should('be.visible');

if (Cypress.env('TAKE_SNAPSHOTS')) {
cy.matchImageSnapshot('fillvalue_1D');
cy.matchImageSnapshot('fillvalue_2D');
}

cy.selectExplorerNode('twoD_fillvalue');
cy.findByRole('figure', { name: 'twoD_fillvalue' }).should('be.visible');
cy.selectVisTab('Line');
cy.waitForStableDOM();

if (Cypress.env('TAKE_SNAPSHOTS')) {
cy.matchImageSnapshot('fillvalue_2D');
cy.matchImageSnapshot('fillvalue_1D');
}
});

Expand Down
Binary file modified cypress/snapshots/app.cy.ts/auximage.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/auxspectrum.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/bgr_image.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/compound_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/default_slice.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/fillvalue_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/fillvalue_2D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_zeros.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_domain.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_flip.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_complex_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_complex_constant.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_constant.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/logspectrum.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/matrix_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nxheatmap.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nxheatmap_complex_2d.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nxheatmap_complex_2d_float_aux.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nxline.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxline_complex_2d_aux.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxrgb.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxscatter.snap.png
Binary file modified cypress/snapshots/app.cy.ts/rgb_image.snap.png
43 changes: 43 additions & 0 deletions packages/app/src/__tests__/NetCDFPack.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { screen, within } from '@testing-library/react';
import { expect, test } from 'vitest';

import { renderApp } from '../test-utils';

test('visualize dataset with `valid_min` and/or `valid_max` attributes', async () => {
const { selectExplorerNode } = await renderApp('/netcdf/valid_min');

const fig1 = screen.getByRole('figure', { name: 'valid_min' });
expect(within(fig1).getByText('4e+2')).toBeVisible(); // data max
expect(within(fig1).getByText('5e+0')).toBeVisible(); // valid_min

await selectExplorerNode('valid_max');
const fig2 = screen.getByRole('figure', { name: 'valid_max' });
expect(within(fig2).getByText('2e+2')).toBeVisible(); // valid_max
expect(within(fig2).getByText('−9.5e+1')).toBeVisible(); // data min

await selectExplorerNode('valid_min_max');
const fig3 = screen.getByRole('figure', { name: 'valid_min_max' });
expect(within(fig3).getByText('2e+2')).toBeVisible(); // valid_max
expect(within(fig3).getByText('5e+0')).toBeVisible(); // valid_min
});

test('visualize dataset with `valid_range` attribute', async () => {
await renderApp('/netcdf/valid_range');

const fig = screen.getByRole('figure', { name: 'valid_range' });
expect(within(fig).getByText('2e+2')).toBeVisible(); // valid_range[1]
expect(within(fig).getByText('5e+0')).toBeVisible(); // valid_range[0]
});

test('visualize dataset with `_FillValue` attribute', async () => {
const { selectExplorerNode } = await renderApp('/netcdf/_FillValue');

const fig1 = screen.getByRole('figure', { name: '_FillValue' });
expect(within(fig1).getByText('9.9e+1')).toBeVisible(); // closest data value lower than _FillValue
expect(within(fig1).getByText('−9.5e+1')).toBeVisible(); // data min

await selectExplorerNode('_FillValue (negative)', true);
const fig2 = screen.getByRole('figure', { name: '_FillValue (negative)' });
expect(within(fig2).getByText('4e+2')).toBeVisible(); // data max
expect(within(fig2).getByText('−6e+0')).toBeVisible(); // closest data value greater than _FillValue
});
37 changes: 29 additions & 8 deletions packages/app/src/providers/mock/mock-file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { H5T_CSET, H5T_STR } from '@h5web/shared/h5t';
import { type GroupWithChildren } from '@h5web/shared/hdf5-models';
import {
arrayShape,
arrayType,
boolType,
compoundType,
Expand Down Expand Up @@ -116,10 +117,6 @@ export function makeMockFile(): GroupWithChildren {
group('nD_datasets', [
array('oneD_linear'),
array('oneD'),
array('oneD_fillvalue', {
valueId: 'oneD',
attributes: [scalar('_FillValue', 400)],
}),
array('oneD_bigint'),
array('oneD_cplx'),
array('oneD_compound', {
Expand All @@ -136,10 +133,6 @@ export function makeMockFile(): GroupWithChildren {
type: enumType(intType(false, 8), ENUM_MAPPING),
}),
array('twoD'),
array('twoD_fillvalue', {
valueId: 'twoD',
attributes: [scalar('_FillValue', 400)],
}),
array('twoD_neg'),
array('twoD_bigint'),
array('twoD_cplx'),
Expand Down Expand Up @@ -435,6 +428,34 @@ export function makeMockFile(): GroupWithChildren {
],
}),
]),
group('netcdf', [
array('valid_min', {
valueId: 'twoD',
attributes: [scalar('valid_min', 5)],
}),
array('valid_max', {
valueId: 'twoD',
attributes: [scalar('valid_max', 200)],
}),
array('valid_min_max', {
valueId: 'twoD',
attributes: [scalar('valid_min', 5), scalar('valid_max', 200)],
}),
array('valid_range', {
valueId: 'twoD',
attributes: [
dataset('valid_range', arrayShape([2]), floatType(), [5, 200]),
],
}),
array('_FillValue', {
valueId: 'twoD',
attributes: [scalar('_FillValue', 100)],
}),
array('_FillValue (negative)', {
valueId: 'twoD',
attributes: [scalar('_FillValue', -9)],
}),
]),
group('resilience', [
scalar('error_value', 0, { attributes: [scalarAttr('attr', 1)] }),
scalar('slow_value', 42),
Expand Down
8 changes: 5 additions & 3 deletions packages/app/src/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { type NxDataVis } from './vis-packs/nexus/visualizations';

interface RenderAppResult extends RenderResult {
user: ReturnType<typeof userEvent.setup>;
selectExplorerNode: (name: string) => Promise<void>;
selectExplorerNode: (name: string, exact?: boolean) => Promise<void>;
selectVisTab: (name: Vis | NxDataVis) => Promise<void>;
}

Expand Down Expand Up @@ -70,9 +70,11 @@ export async function renderApp(
user,
...renderResult,

selectExplorerNode: async (name) => {
selectExplorerNode: async (name, exact = false) => {
const item = await screen.findByRole('treeitem', {
name: new RegExp(String.raw`^${name}(?: \(NeXus group\))?$`, 'u'), // account for potential NeXus badge
name: exact
? name
: new RegExp(String.raw`^${name}(?: \(NeXus group\))?$`, 'u'), // account for potential NeXus badge
});

await user.click(item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { useDimMappingState } from '../../../dim-mapping-store';
import { useValuesInCache } from '../../../hooks';
import visualizerStyles from '../../../visualizer/Visualizer.module.css';
import { type VisContainerProps } from '../../models';
import { useNcIgnoreValue } from '../../netcdf/hooks';
import VisBoundary from '../../VisBoundary';
import { useIgnoreFillValue } from '../hooks';
import ValueFetcher from '../ValueFetcher';
import { useHeatmapConfig } from './config';
import MappedHeatmapVis from './MappedHeatmapVis';
Expand All @@ -30,9 +30,8 @@ function HeatmapVisContainer(props: VisContainerProps) {
});

const config = useHeatmapConfig();

const selection = getSliceSelection(dimMapping);
const ignoreValue = useIgnoreFillValue(entity);
const ignoreValue = useNcIgnoreValue(entity);

return (
<>
Expand Down
38 changes: 0 additions & 38 deletions packages/app/src/vis-packs/core/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,13 @@ import {
type BuiltInExporter,
type ExportEntry,
type ExportFormat,
type IgnoreValue,
type NumArray,
} from '@h5web/shared/vis-models';
import { useToggle } from '@react-hookz/web';
import { type NdArray } from 'ndarray';
import { useCallback, useMemo } from 'react';

import { useDataContext } from '../../providers/DataProvider';
import {
bigIntTypedArrayFromDType,
typedArrayFromDType,
} from '../../providers/utils';
import { findScalarNumAttr, getAttributeValue } from '../../utils';
import { applyMapping, getBaseArray, toNumArray } from './utils';

export const useToNumArray = createMemo(toNumArray);
Expand Down Expand Up @@ -97,38 +91,6 @@ export function useMappedArrays(
);
}

export function useIgnoreFillValue(dataset: Dataset): IgnoreValue | undefined {
const { attrValuesStore } = useDataContext();

return useMemo(() => {
const fillValueAttr = findScalarNumAttr(dataset, '_FillValue');
if (!fillValueAttr) {
return undefined;
}

const rawFillValue = getAttributeValue(
dataset,
fillValueAttr,
attrValuesStore,
);

const DTypedArray = bigIntTypedArrayFromDType(dataset.type)
? Float64Array // matches `useToNumArray` logic
: typedArrayFromDType(dataset.type);

// Cast fillValue in the type of the dataset values to be able to use `===` for the comparison
const fillValue = DTypedArray
? new DTypedArray([Number(rawFillValue)])[0]
: undefined;

if (fillValue === undefined) {
return undefined;
}

return (val) => val === fillValue;
}, [dataset, attrValuesStore]);
}

export function useExportEntries<F extends ExportFormat[]>(
formats: F,
dataset: Dataset,
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/vis-packs/core/line/LineVisContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { useDimMappingState } from '../../../dim-mapping-store';
import { useValuesInCache } from '../../../hooks';
import visualizerStyles from '../../../visualizer/Visualizer.module.css';
import { type VisContainerProps } from '../../models';
import { useNcIgnoreValue } from '../../netcdf/hooks';
import VisBoundary from '../../VisBoundary';
import { useIgnoreFillValue } from '../hooks';
import ValueFetcher from '../ValueFetcher';
import { useLineConfig } from './config';
import MappedLineVis from './MappedLineVis';
Expand All @@ -28,8 +28,8 @@ function LineVisContainer(props: VisContainerProps) {
});

const config = useLineConfig();
const ignoreValue = useIgnoreFillValue(entity);
const selection = getSliceSelection(dimMapping);
const ignoreValue = useNcIgnoreValue(entity);

return (
<>
Expand Down
35 changes: 35 additions & 0 deletions packages/app/src/vis-packs/netcdf/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type IgnoreValue } from '@h5web/lib';
import { hasNumericType } from '@h5web/shared/guards';
import { type ArrayShape, type Dataset } from '@h5web/shared/hdf5-models';
import { useMemo } from 'react';

import { useDataContext } from '../../providers/DataProvider';
import { createIgnoreFillValue, getFillValue, getValidRange } from './utils';

/* Priority order: `valid_min` and/or `valid_max`, then `valid_range`, then `_FillValue`.
* Note that `_FillValue` acts as an invalid upper or lower bound (if positive or negative respectively).
* See NetCDF `valid_range` convention: https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html */
export function useNcIgnoreValue(
dataset: Dataset<ArrayShape>,
): IgnoreValue | undefined {
const { attrValuesStore } = useDataContext();

return useMemo(() => {
if (!hasNumericType(dataset)) {
return undefined;
}

const validRange = getValidRange(dataset, attrValuesStore);
if (validRange) {
const [validMin, validMax] = validRange;
return (val) => val < validMin || val > validMax;
}

const fillValue = getFillValue(dataset, attrValuesStore);
if (fillValue !== undefined) {
return createIgnoreFillValue(fillValue, dataset.type);
}

return undefined;
}, [dataset, attrValuesStore]);
}
54 changes: 54 additions & 0 deletions packages/app/src/vis-packs/netcdf/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { floatType, intType } from '@h5web/shared/hdf5-utils';
import { describe, expect, it } from 'vitest';

import { createIgnoreFillValue } from './utils';

describe('createIgnoreFillValue', () => {
describe('with integers', () => {
it('should ignore values greater or equal to positive fill value', () => {
const ignoreValue = createIgnoreFillValue(10, intType());
expect(ignoreValue(9)).toBe(false);
expect(ignoreValue(10)).toBe(true);
expect(ignoreValue(11)).toBe(true);
});

it('should ignore values lower or equal to negative fill value', () => {
const ignoreValue = createIgnoreFillValue(-10, intType());
expect(ignoreValue(-9)).toBe(false);
expect(ignoreValue(-10)).toBe(true);
expect(ignoreValue(-11)).toBe(true);
});
});

describe('with floats', () => {
it('should ignore values greater than positive fill value', () => {
const ignoreValue = createIgnoreFillValue(0.3, floatType(64));
expect(ignoreValue(0.2)).toBe(false);
expect(ignoreValue(0.3005)).toBe(true);
});

it('should ignore values close-enough to positive fill value', () => {
const ignoreValue = createIgnoreFillValue(0.3, floatType(64));
expect(ignoreValue(0.3 - 2 * Number.EPSILON)).toBe(false);
expect(ignoreValue(0.3 - Number.EPSILON)).toBe(true);
expect(ignoreValue(0.3)).toBe(true);
expect(ignoreValue(0.3 + Number.EPSILON)).toBe(true);
expect(ignoreValue(0.3 + 2 * Number.EPSILON)).toBe(true);
});

it('should ignore values lower than negative fill value', () => {
const ignoreValue = createIgnoreFillValue(-0.3, floatType(64));
expect(ignoreValue(-0.2)).toBe(false);
expect(ignoreValue(-0.3005)).toBe(true);
});

it('should ignore values close-enough to negative fill value', () => {
const ignoreValue = createIgnoreFillValue(-0.3, floatType(64));
expect(ignoreValue(-0.3 + 2 * Number.EPSILON)).toBe(false);
expect(ignoreValue(-0.3 + Number.EPSILON)).toBe(true);
expect(ignoreValue(-0.3)).toBe(true);
expect(ignoreValue(-0.3 - Number.EPSILON)).toBe(true);
expect(ignoreValue(-0.3 - 2 * Number.EPSILON)).toBe(true);
});
});
});
Loading