Skip to content

Commit

Permalink
added iter-clip
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielJDufour committed Nov 16, 2021
1 parent 3397ed7 commit 6096c72
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 10 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The layout could be described as `"[row,column,band]"`.


# usage
This library provides the following functions: [select](#select), [prepareSelect](#prepareSelect), [clip](#clip), [transform](#transform), [prepareData](#prepareData), [update](#update), and [prepareUpdate](#prepareUpdate).
This library provides the following functions: [select](#select), [prepareSelect](#prepareSelect), [clip](#clip), [iterClip](#iterClip), [transform](#transform), [prepareData](#prepareData), [update](#update), and [prepareUpdate](#prepareUpdate).

## select
Select is used to get the value at a given multi-dimensional point. The point is an object where each key is the name of a dimension with an index number. Index numbers start at zero and increase until we reach the end of the length in the dimension.
Expand Down Expand Up @@ -189,7 +189,49 @@ result is an object
}
```

## iterClip
Like [clip](#clip), but returns a flat iterator of values. Useful if you want to minimize memory usage and creating a new array.
```javascript
import { iterClip } from 'xdim';

// satellite imagery data broken down by band
const data = [
[0, 123, 123, 162, ...], // red band
[213, 41, 62, 124, ...], // green band
[84, 52, 124, 235, ...] // blue band
];

const result = iterClip({
data,

// each band is a separate array
// the values in a band are in row-major order
layout: "[band][row,column]",

sizes: {
band: 3, // image has 3 bands (red, green, and blue)
column: 100 // image is 100 pixels wide
},

rect: {
band: [2, 2], // 3rd band (blue), where band index starts at zero
row: [55, 74], // from the 56th to the 75th row (counting from the top)
column: [60, 62] // from the 61st to the 63rd column (counting from the left)
}
});
```
result is an iterator object
```js

// call the first value
result.next();
// { done: false, next: 64}

// you can also use for of syntax
for (let n of result) {
// n is a number 27, then 19, then 23
}
```

## transform
If your data is a one dimensional array, you can transform to another using the transform function.
Expand Down
52 changes: 43 additions & 9 deletions src/xdim.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,35 @@ function prepareUpdate({ useLayoutCache = true, data, layout, sizes = {} }) {
};
}

function clip({ useLayoutCache = true, data, layout, rect, sizes = {}, flat = false }) {
function iterClip({ data, layout, rect = {}, sizes = {}, useLayoutCache = true }) {
if (!data) throw new Error("[xdim] must specify data");
if (!layout) throw new Error("[xdim] must specify layout");
const points = iterPoints({ sizes, rect });
return wrapNextFunction(function next() {
const { value: point, done } = points.next();
if (done) {
return { done: true };
} else {
const { value } = select({ data, layout, point, sizes, useLayoutCache });
return { done: false, value };
}
});
}

function validateRect({ rect = {} }) {
if (rect) {
for (let key in rect) {
const value = rect[key];
if (value.length !== 2) throw new Error(`[xdim] uh oh. invalid hyper-rectangle`);
const [start, end] = value;
if (start > end) throw new Error(`[xdim] uh oh. invalid range for "${key}". Start of ${start} can't be greater than end of ${end}.`);
}
}
}

function clip({ useLayoutCache = true, data, layout, rect, sizes = {}, flat = false, validate = true }) {
if (validate) validateRect({ rect });

if (typeof layout === "string") layout = parse(layout, { useLayoutCache });

let datas = [data];
Expand Down Expand Up @@ -409,20 +437,22 @@ function iterRange({ start = 0, end = 100 }) {
}

// iterate over all the points, saving memory vs array
function iterPoints({ sizes }) {
function iterPoints({ sizes, rect = {} }) {
// names sorted by shortest dimension to longest dimension
const names = Object.keys(sizes).sort((a, b) => sizes[a] - sizes[b]);

const iters = new Array(names.length);
const current = {};
for (let i = 0; i < names.length - 1; i++) {
const name = names[i];
iters[i] = iterRange({ start: 1, end: sizes[name] - 1 });
current[name] = 0;
const [start, end] = rect[name] || [0, sizes[name] - 1];
iters[i] = iterRange({ start: start + 1, end });
current[name] = start;
}
const lastName = names[names.length - 1];
iters[iters.length - 1] = iterRange({ start: 0, end: sizes[lastName] - 1 });
current[lastName] = -1;
const [start, end] = rect[lastName] || [0, sizes[lastName] - 1];
iters[iters.length - 1] = iterRange({ start: start, end });
current[lastName] = start - 1;

// permutate
return wrapNextFunction(function next() {
Expand All @@ -437,8 +467,10 @@ function iterPoints({ sizes }) {
} else {
// add iters for the remaining dims
for (let ii = i + 1; ii < iters.length; ii++) {
iters[ii] = iterRange({ start: 1, end: sizes[names[ii]] - 1 });
current[names[ii]] = 0;
const nameii = names[ii];
const [start, end] = rect[nameii] || [0, sizes[nameii] - 1];
iters[ii] = iterRange({ start: start + 1, end });
current[nameii] = start;
}

current[names[i]] = value;
Expand Down Expand Up @@ -481,6 +513,7 @@ function transform({ data, fill, from, to, sizes, useLayoutCache = true }) {
module.exports = {
checkValidity,
createMatrix,
iterClip,
iterRange,
iterPoints,
matchSequences,
Expand All @@ -496,5 +529,6 @@ module.exports = {
select,
transform,
update,
clip
clip,
validateRect
};
28 changes: 28 additions & 0 deletions tests/test.iter-clip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const test = require("flug");
const { iterClip } = require("../src/xdim");

const range = ct => new Array(ct).fill(0).map((_, i) => i);

test("iter clip with rect", ({ eq }) => {
// band row column
const pixelDepth = 4;
const height = 768;
const width = 1024;
const data = range(pixelDepth).map(band => {
return range(height).map(row => {
return range(width).map(column => {
return { b: band, r: row, c: column };
});
});
});
const iter = iterClip({ data, layout: "[band][row][column]", sizes: { band: 4, row: 768, column: 1024 }, rect: { band: [1, 3] } });
eq(iter.next().value, { b: 1, r: 0, c: 0 });
eq(iter.next().value, { b: 1, r: 0, c: 1 });
for (let i = 0; i < 1021; i++) iter.next();
eq(iter.next().value, { b: 1, r: 0, c: 1023 });
eq(iter.next().value, { b: 1, r: 1, c: 0 });

let last;
for (last of iter);
eq(last, { b: 3, r: 767, c: 1023 });
});
13 changes: 13 additions & 0 deletions tests/test.iter-points.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,16 @@ test("iter points", ({ eq }) => {
for (last of iter);
eq(last, { band: 3, row: 767, column: 1023 });
});

test("iter points with rect", ({ eq }) => {
const iter = iterPoints({ sizes: { band: 4, row: 768, column: 1024 }, rect: { band: [1, 3] } });
eq(iter.next().value, { band: 1, row: 0, column: 0 });
eq(iter.next().value, { band: 1, row: 0, column: 1 });
for (let i = 0; i < 1021; i++) iter.next();
eq(iter.next().value, { band: 1, row: 0, column: 1023 });
eq(iter.next().value, { band: 1, row: 1, column: 0 });

let last;
for (last of iter);
eq(last, { band: 3, row: 767, column: 1023 });
});
27 changes: 27 additions & 0 deletions tests/validate-rect.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const test = require("flug");
const { validateRect } = require("../src/xdim");

test("validating invalid rectangle", ({ eq }) => {
const rect = { band: [0, 0], row: [6, 5], column: [1, 1] };

let threw = false;
try {
validateRect({ rect });
} catch (error) {
threw = true;
}
eq(threw, true);
});

test("validating valid rectangle", ({ eq }) => {
const rect = { band: [0, 0], row: [6, 6], column: [1, 1] };

let threw = false;
try {
validateRect({ rect });
} catch (error) {
console.error(error);
threw = true;
}
eq(threw, false);
});

0 comments on commit 6096c72

Please sign in to comment.