Skip to content

Commit 69f1649

Browse files
authored
Implement decodeStack (#422)
* feat(stack): decode stack from tiff Closes: #421 * chore: run prettier * fix: export decodeStack
1 parent f37d4e4 commit 69f1649

File tree

11 files changed

+100
-8
lines changed

11 files changed

+100
-8
lines changed

Diff for: src/load/decodeTiff.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ type TiffIfd = ReturnType<typeof decode>[number];
1414
export function decodeTiff(buffer: Uint8Array): Image {
1515
const result = decode(buffer);
1616
return getImageFromIFD(result[0]);
17-
// TODO: handle stacks (many IFDs)
17+
// TODO: optimize not to decode whole file
1818
}
1919

2020
/**
2121
* Create image from a single IFD.
2222
* @param ifd - The IFD.
2323
* @returns The decoded image.
2424
*/
25-
function getImageFromIFD(ifd: TiffIfd): Image {
25+
export function getImageFromIFD(ifd: TiffIfd): Image {
2626
if (ifd.type === 3) {
2727
// Palette
2828
const data = new Uint16Array(3 * ifd.width * ifd.height);

Diff for: src/stack/compute/index.ts

-6
This file was deleted.

Diff for: src/stack/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * from './compute/histogram';
2+
export * from './compute/maxImage';
3+
export * from './compute/meanImage';
4+
export * from './compute/medianImage';
5+
export * from './load/decodeStack';
6+
export * from './compute/minImage';
7+
export * from './compute/sum';

Diff for: src/stack/load/__tests__/decodeStack.test.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { TestImagePath } from '../../../../test/TestImagePath';
2+
import { decodeStack } from '../decodeStack';
3+
4+
test.each([
5+
{
6+
name: 'formats/tif/grey8-multi.tif',
7+
colorModel: 'GREY',
8+
bitDepth: 8,
9+
pages: 2,
10+
},
11+
{
12+
name: 'formats/tif/grey16-multi.tif',
13+
colorModel: 'GREY',
14+
bitDepth: 16,
15+
pages: 2,
16+
},
17+
{
18+
name: 'formats/tif/color8-multi.tif',
19+
colorModel: 'RGB',
20+
bitDepth: 8,
21+
pages: 2,
22+
},
23+
{
24+
name: 'formats/tif/color16-multi.tif',
25+
colorModel: 'RGB',
26+
bitDepth: 16,
27+
pages: 2,
28+
},
29+
])('stacks with 2 images ($colorModel, bitDepth = $bitDepth)', (data) => {
30+
const buffer = testUtils.loadBuffer(data.name as TestImagePath);
31+
const stack = decodeStack(buffer);
32+
expect(stack.size).toBe(data.pages);
33+
for (const image of stack) {
34+
expect(image.colorModel).toBe(data.colorModel);
35+
expect(image.bitDepth).toBe(data.bitDepth);
36+
}
37+
});
38+
39+
test('invalid data format', () => {
40+
const buffer = testUtils.loadBuffer('formats/grey8.png');
41+
expect(() => decodeStack(buffer)).toThrow('invalid data format: image/png');
42+
});

Diff for: src/stack/load/decodeStack.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import imageType from 'image-type';
2+
3+
import { Stack } from '../../Stack';
4+
5+
import { decodeStackFromTiff } from './decodeTiff';
6+
7+
/**
8+
* Decode input data and create stack. Data format is automatically detected.
9+
* Possible formats: tiff.
10+
* @param data - Data to decode.
11+
* @returns The decoded image.
12+
*/
13+
export function decodeStack(data: ArrayBufferView): Stack {
14+
const typedArray = new Uint8Array(
15+
data.buffer,
16+
data.byteOffset,
17+
data.byteLength,
18+
);
19+
const type = imageType(typedArray);
20+
switch (type?.mime) {
21+
case 'image/tiff':
22+
return decodeStackFromTiff(typedArray);
23+
default:
24+
throw new RangeError(`invalid data format: ${type?.mime}`);
25+
}
26+
}

Diff for: src/stack/load/decodeTiff.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { decode } from 'tiff';
2+
3+
import { Stack } from '../../Stack';
4+
import { getImageFromIFD } from '../../load/decodeTiff';
5+
6+
/**
7+
* Decode a TIFF and create a stack of images.
8+
* @param buffer - The data to decode.
9+
* @returns The stack of images.
10+
*/
11+
export function decodeStackFromTiff(buffer: Uint8Array): Stack {
12+
const decoded = decode(buffer);
13+
const images = [];
14+
for (const IFD of decoded) {
15+
images.push(getImageFromIFD(IFD));
16+
}
17+
18+
return new Stack(images);
19+
}

Diff for: test/TestImagePath.ts

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export type TestImagePath =
2121
| 'formats/tif/greya32.tif'
2222
| 'formats/tif/rgb16.tif'
2323
| 'formats/tif/rgba8.tif'
24+
| 'formats/tif/grey8-multi.tif'
25+
| 'formats/tif/grey16-multi.tif'
26+
| 'formats/tif/rgb16-multi.tif'
27+
| 'formats/tif/rgba8-multi.tif'
2428
| 'formats/tif/palette.tif'
2529
| 'opencv/test.png'
2630
| 'opencv/testBlur.png'

Diff for: test/img/formats/tif/color16-multi.tif

260 KB
Binary file not shown.

Diff for: test/img/formats/tif/color8-multi.tif

148 KB
Binary file not shown.

Diff for: test/img/formats/tif/grey16-multi.tif

48.1 KB
Binary file not shown.

Diff for: test/img/formats/tif/grey8-multi.tif

42.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)