Skip to content

stride and slice#50

Closed
lazarusA wants to merge 1 commit intomainfrom
la/show_data
Closed

stride and slice#50
lazarusA wants to merge 1 commit intomainfrom
la/show_data

Conversation

@lazarusA
Copy link
Copy Markdown
Member

@lazarusA lazarusA commented Feb 22, 2026

WIP:

This adds the nc_get_vars_* which will allow us to use strides when reading variable data.

  • A new slice class is added
  • And a new method to get the data, e.g. get("temperature", [slice(0,1), null, null]), which mirrors fully the functionality from zarrita.

See: https://docs.unidata.ucar.edu/netcdf-c/current/group__variables.html#details

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @lazarusA, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the data access capabilities of the NetCDF4-WASM library by introducing advanced slicing and striding functionality. It allows users to precisely select and retrieve subsets of variable data using a familiar Python-like syntax, including support for negative steps and custom strides. This improvement makes the library more powerful and intuitive for data manipulation, aligning its data access patterns with common data science tools and improving the overall user experience for interacting with NetCDF data.

Highlights

  • Strided and Sliced Data Access: Introduced a new get method on the NetCDF4 class, enabling flexible data slicing and indexing for variables, including support for strides and negative steps, mirroring Python's slicing syntax.
  • New Slice Class and Utilities: Added a dedicated Slice class and slice factory function to define dimension selections, along with resolveDim for parsing and resolving these selections against actual dimension sizes.
  • WebAssembly Integration for Strided Reads: Integrated nc_get_vars_* functions into the WebAssembly module, exposing them to JavaScript to perform efficient strided data reads directly from NetCDF files.
  • Interactive Slice Tester UI: Developed a new SliceTester React component within the documentation application, providing an interactive interface to demonstrate and test the new variable slicing and indexing capabilities.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • docs/next-js/components/loading/LocalNetCDFMeta.tsx
    • Imported SliceTester component and related types.
    • Added state management for slice selections, results, and errors.
    • Implemented runSliceTest callback to execute variable slicing.
    • Integrated the SliceTesterSection into the UI for interactive testing.
    • Removed redundant comments and simplified conditional expressions for cleaner code.
  • docs/next-js/components/loading/SliceTester.tsx
    • Added a new React component SliceTesterSection for interactive variable slicing and indexing.
    • Implemented UI elements for selecting slice modes (all, scalar, slice) and inputting start, stop, and step values.
    • Included helper functions defaultSelection and buildSelection for managing slice state and converting UI input to DimSelection objects.
    • Displayed selection preview, run button, loading spinner, error messages, and result preview.
  • scripts/build-wasm.sh
    • Added EMSCRIPTEN_KEEPALIVE wrappers for nc_get_vars_* functions (e.g., nc_get_vars_schar, nc_get_vars_double, nc_get_vars_string) to enable strided data access from JavaScript.
    • Included a generic nc_get_vars_wrapper to dispatch strided reads based on variable type.
  • src/index.ts
    • Exported the new slice function and Slice class.
    • Exported DimSelection and ResolvedDim types for external use.
  • src/netcdf-getters.ts
    • Imported DimSelection, ResolvedDim, and resolveDim for dimension selection logic.
    • Added getVariableArrayWithSelection function to handle complex dimension selections (scalar, slice, stride, negative step).
    • Implemented applyNegativeStepReversal function to correctly handle negative steps by reversing the output array client-side.
    • Integrated nc_get_vars_* and nc_get_vara_* calls based on whether strides are used.
  • src/netcdf-worker.ts
    • Added a new message case 'getVariableArrayWithSelection' to process slicing and striding requests within the web worker.
  • src/netcdf4.ts
    • Imported DimSelection type.
    • Added a new asynchronous get method to the NetCDF4 class, providing a high-level API for variable slicing and indexing.
    • Delegated the implementation of the get method to getVariableArrayWithSelection, supporting both main thread and web worker execution.
  • src/slice.ts
    • Added a new file defining the Slice class to represent dimension slices.
    • Implemented the slice factory function for creating Slice objects with various argument signatures.
    • Defined DimSelection type for representing null, scalar index, or Slice selections.
    • Introduced ResolvedDim interface to store concrete read parameters for a dimension.
    • Developed resolveDim function to convert DimSelection into ResolvedDim by resolving against actual dimension sizes, handling BigInts and negative indices.
  • src/types.ts
    • Added new type definitions for nc_get_vars_* functions in the NetCDF4Module interface to support strided data access.
  • src/wasm-module.ts
    • Added stridedLength helper function to calculate the total length of a strided array.
    • Integrated the new nc_get_vars_*_wrapper functions into the WasmModuleLoader.
    • Implemented memory allocation and data conversion logic for strided reads, similar to existing nc_get_vara_* functions.
Activity
  • The pull request is currently a Work In Progress (WIP), indicating ongoing development.
  • The author, lazarusA, is actively adding core functionality for strided data access and a new slice class.
  • The changes span both the core WebAssembly binding logic and a React-based UI for demonstration, suggesting a comprehensive feature implementation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces strided data reading capabilities, a significant enhancement. The implementation includes new C wrappers, TypeScript logic for slicing, and a new UI component for testing slices. The code is generally well-structured, especially the new slice.ts file. I've identified a few issues: a bug in the new slice tester UI, some opportunities to improve React hook usage for better maintainability, and an inconsistency in pointer types within the WASM module wrapper that could lead to issues with large datasets. My detailed comments and suggestions are below.


{/* Selection preview */}
<div className="text-xs font-mono text-muted-foreground bg-muted/50 rounded px-2 py-1.5 break-all">
get("{info.name}", [{selectionPreview}])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The selection preview string is not using a template literal, so it will display {info.name} literally instead of interpolating the variable's name. This should be converted to a template literal to correctly display the preview.

Suggested change
get("{info.name}", [{selectionPreview}])
get(`"${info.name}"`, [${selectionPreview}])

Comment on lines +1513 to +1539
nc_get_vars_generic: (ncid: number, varid: number, start: number[], count: number[], stride: number[], nctype: number) => {
const elementSize = DATA_TYPE_SIZE[nctype];
const totalLength = stridedLength(count, stride);
const dataPtr = module._malloc(totalLength * elementSize);
const startPtr = module._malloc(start.length * 4);
const countPtr = module._malloc(count.length * 4);
const stridePtr = module._malloc(stride.length * 4);
start .forEach((v, i) => module.setValue(startPtr + i * 4, v, 'i32'));
count .forEach((v, i) => module.setValue(countPtr + i * 4, v, 'i32'));
stride.forEach((v, i) => module.setValue(stridePtr + i * 4, v, 'i32'));
const result = nc_get_vars_wrapper(ncid, varid, startPtr, countPtr, stridePtr, dataPtr);
let data;
if (result === NC_CONSTANTS.NC_NOERR) {
switch (nctype) {
case NC_CONSTANTS.NC_BYTE: data = new Int8Array(module.HEAP8.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_UBYTE: data = new Uint8Array(module.HEAPU8.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_SHORT: data = new Int16Array(module.HEAP16.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_USHORT: data = new Uint16Array(module.HEAPU16.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_INT: data = new Int32Array(module.HEAP32.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_UINT: data = new Uint32Array(module.HEAPU32.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_INT64: data = new BigInt64Array(module.HEAP64.buffer, dataPtr, totalLength).slice(); break;
case NC_CONSTANTS.NC_UINT64: data = new BigUint64Array(module.HEAPU64.buffer, dataPtr, totalLength).slice(); break;
}
}
module._free(dataPtr); module._free(startPtr); module._free(countPtr); module._free(stridePtr);
return { result, data };
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The comment on line 1512 raises a valid concern. This generic function uses i32 for start, count, and stride pointers, which is inconsistent with the typed nc_get_vars_* functions that use i64. Using i32 can limit array indexing to 2GB elements and may cause issues with large datasets.

For consistency and to prevent potential overflow issues, this function should also use i64 for these pointers, similar to the other nc_get_vars_* and nc_get_vara_* wrappers.

            nc_get_vars_generic: (ncid: number, varid: number, start: number[], count: number[], stride: number[], nctype: number) => {
                const elementSize = DATA_TYPE_SIZE[nctype];
                const totalLength = stridedLength(count, stride);
                const dataPtr   = module._malloc(totalLength * elementSize);
                const startPtr  = module._malloc(start.length * 8);
                const countPtr  = module._malloc(count.length * 8);
                const stridePtr = module._malloc(stride.length * 8);
                start .forEach((v, i) => module.setValue(startPtr  + i * 8, v, 'i64'));
                count .forEach((v, i) => module.setValue(countPtr  + i * 8, v, 'i64'));
                stride.forEach((v, i) => module.setValue(stridePtr + i * 8, v, 'i64'));
                const result = nc_get_vars_wrapper(ncid, varid, startPtr, countPtr, stridePtr, dataPtr);
                let data;
                if (result === NC_CONSTANTS.NC_NOERR) {
                    switch (nctype) {
                        case NC_CONSTANTS.NC_BYTE:   data = new Int8Array(module.HEAP8.buffer,     dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_UBYTE:  data = new Uint8Array(module.HEAPU8.buffer,   dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_SHORT:  data = new Int16Array(module.HEAP16.buffer,   dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_USHORT: data = new Uint16Array(module.HEAPU16.buffer, dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_INT:    data = new Int32Array(module.HEAP32.buffer,   dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_UINT:   data = new Uint32Array(module.HEAPU32.buffer, dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_INT64:  data = new BigInt64Array(module.HEAP64.buffer,  dataPtr, totalLength).slice(); break;
                        case NC_CONSTANTS.NC_UINT64: data = new BigUint64Array(module.HEAPU64.buffer, dataPtr, totalLength).slice(); break;
                    }
                }
                module._free(dataPtr); module._free(startPtr); module._free(countPtr); module._free(stridePtr);
                return { result, data };
            },

Comment on lines +84 to +108
useEffect(() => {
setSliceResult(null);
setSliceError(null);
setExpandedSliceTester(false);
if (selectedVariable && variables[selectedVariable]?.info?.shape) {
setSliceSelections(
(variables[selectedVariable].info.shape as any[]).map(() => defaultSelection())
);
} else {
setSliceSelections([]);
}
}, [selectedVariable]); // eslint-disable-line react-hooks/exhaustive-deps

// Init slice selections when info first loads for the selected variable
useEffect(() => {
if (
selectedVariable &&
variables[selectedVariable]?.info?.shape &&
sliceSelections.length === 0
) {
setSliceSelections(
(variables[selectedVariable].info.shape as any[]).map(() => defaultSelection())
);
}
}, [variables, selectedVariable]); // eslint-disable-line react-hooks/exhaustive-deps
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These two useEffect hooks manage related state and have disabled ESLint warnings for exhaustive dependencies, which can lead to subtle bugs and make the code harder to maintain. They can be combined into a single, more robust useEffect hook with a complete dependency array. This simplifies the logic for resetting and initializing the slice selections.

Suggested change
useEffect(() => {
setSliceResult(null);
setSliceError(null);
setExpandedSliceTester(false);
if (selectedVariable && variables[selectedVariable]?.info?.shape) {
setSliceSelections(
(variables[selectedVariable].info.shape as any[]).map(() => defaultSelection())
);
} else {
setSliceSelections([]);
}
}, [selectedVariable]); // eslint-disable-line react-hooks/exhaustive-deps
// Init slice selections when info first loads for the selected variable
useEffect(() => {
if (
selectedVariable &&
variables[selectedVariable]?.info?.shape &&
sliceSelections.length === 0
) {
setSliceSelections(
(variables[selectedVariable].info.shape as any[]).map(() => defaultSelection())
);
}
}, [variables, selectedVariable]); // eslint-disable-line react-hooks/exhaustive-deps
// Reset and initialize slice tester state
useEffect(() => {
setSliceResult(null);
setSliceError(null);
setExpandedSliceTester(false);
if (selectedVariable && variables[selectedVariable]?.info?.shape) {
setSliceSelections(
(variables[selectedVariable].info.shape as any[]).map(() => defaultSelection())
);
} else {
setSliceSelections([]);
}
}, [selectedVariable, variables]);

size="sm"
onClick={onRun}
disabled={loadingSlice}
style={{ backgroundColor: '#644FF0', color: 'white' }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using inline styles with hardcoded color values like backgroundColor: '#644FF0'. This makes the component harder to theme and maintain. It's better to use CSS classes or variants from your design system (e.g., variant="primary") to ensure consistency.

@lazarusA lazarusA mentioned this pull request Feb 27, 2026
3 tasks
@lazarusA
Copy link
Copy Markdown
Member Author

lazarusA commented Mar 5, 2026

done in #57

@lazarusA lazarusA closed this Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant