Skip to content
This repository was archived by the owner on Jul 26, 2025. It is now read-only.

Commit f597aea

Browse files
authored
feat: add option to specify the number of slots for the histogram (#448)
feat: add option to specify the number of slots for the histogram refactor!: group optional arguments in computeThreshold
1 parent f1832a3 commit f597aea

File tree

4 files changed

+51
-22
lines changed

4 files changed

+51
-22
lines changed

src/operations/__tests__/threshold.test.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ test('computeThreshold with OTSU', () => {
2626
const testImage = testUtils.load('opencv/test.png');
2727

2828
const grey = testImage.convertColor('GREY');
29-
const thresholdValue = computeThreshold(grey, 'otsu');
29+
const thresholdValue = computeThreshold(grey, { algorithm: 'otsu' });
3030
expect(thresholdValue).toBe(127);
3131
});
3232

3333
test('computeThreshold with OTSU (2)', () => {
3434
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
35-
const thresholdValue = computeThreshold(img, 'otsu');
35+
const thresholdValue = computeThreshold(img, { algorithm: 'otsu' });
3636
expect(thresholdValue).toBe(135);
3737
});
3838

@@ -116,3 +116,27 @@ test('16 bits image', () => {
116116

117117
expect(threshold).toMatchImageSnapshot();
118118
});
119+
120+
test('16 bits image simple with default number of slots 2**16', () => {
121+
const image = new Image(2, 2, {
122+
colorModel: ImageColorModel.GREY,
123+
bitDepth: 16,
124+
data: new Uint16Array([0, 100, 20000, 30000]),
125+
});
126+
const threshold = image.threshold({ slots: 2 ** image.bitDepth });
127+
const defaultThreshold = image.threshold();
128+
129+
expect(threshold).toEqual(defaultThreshold);
130+
});
131+
132+
test('16 bits image simple with custom number of slots 2**8', () => {
133+
const image = new Image(2, 2, {
134+
colorModel: ImageColorModel.GREY,
135+
bitDepth: 16,
136+
data: new Uint16Array([0, 100, 20000, 30000]),
137+
});
138+
const threshold = image.threshold({ slots: 2 ** 8 });
139+
const defaultThreshold = image.threshold();
140+
141+
expect(threshold).not.toEqual(defaultThreshold);
142+
});

src/operations/threshold.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export type ThresholdAlgorithm =
4040
(typeof ThresholdAlgorithm)[keyof typeof ThresholdAlgorithm];
4141

4242
interface ThresholdOptionsBase {
43+
/**
44+
* Number of slots that histogram can have. Slots must be a power of 2.
45+
*/
46+
slots?: number;
4347
/**
4448
* Image to use as the output.
4549
*/
@@ -69,19 +73,20 @@ export type ThresholdOptions =
6973
/**
7074
* Compute threshold value for an image using the specified algorithm.
7175
* @param image - The grey image.
72-
* @param algorithm - Algorithm that defines the threshold.
76+
* @param options - Threshold options.
7377
* @returns The threshold value for the image.
7478
*/
7579
export function computeThreshold(
7680
image: Image,
77-
algorithm: ThresholdAlgorithm = 'otsu',
81+
options: ThresholdOptionsAlgorithm = {},
7882
): number {
83+
const { algorithm = 'otsu', slots } = options;
7984
if (image.channels !== 1) {
8085
throw new TypeError(
8186
'threshold can only be computed on images with one channel',
8287
);
8388
}
84-
const histogram = image.histogram();
89+
const histogram = image.histogram({ slots });
8590

8691
switch (algorithm) {
8792
case 'huang':
@@ -136,7 +141,7 @@ export function threshold(image: Image, options: ThresholdOptions = {}): Mask {
136141
}
137142
thresholdValue = threshold * image.maxValue;
138143
} else {
139-
thresholdValue = computeThreshold(image, options.algorithm);
144+
thresholdValue = computeThreshold(image, options);
140145
}
141146
const result = imageToOutputMask(image, options);
142147
for (let i = 0; i < image.size; i++) {

src/operations/thresholds/__tests__/thresholdAlgorithms.test.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,75 @@ import { computeThreshold } from '../../threshold';
22

33
test('Huang should work similarily to ImageJ', () => {
44
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
5-
expect(computeThreshold(img, 'huang')).toBe(132);
5+
expect(computeThreshold(img, { algorithm: 'huang' })).toBe(132);
66
});
77

88
test('Intermodes should work like ImageJ', () => {
99
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
10-
expect(computeThreshold(img, 'intermodes')).toBe(166);
10+
expect(computeThreshold(img, { algorithm: 'intermodes' })).toBe(166);
1111
});
1212

1313
test('Isodata should work like ImageJ', () => {
1414
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
15-
expect(computeThreshold(img, 'isodata')).toBe(135);
15+
expect(computeThreshold(img, { algorithm: 'isodata' })).toBe(135);
1616
});
1717

1818
test('Percentile should work like ImageJ', () => {
1919
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
20-
expect(computeThreshold(img, 'percentile')).toBe(90);
20+
expect(computeThreshold(img, { algorithm: 'percentile' })).toBe(90);
2121
});
2222

2323
test('Li should work similarily to ImageJ', () => {
2424
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
25-
expect(computeThreshold(img, 'li')).toBe(117);
25+
expect(computeThreshold(img, { algorithm: 'li' })).toBe(117);
2626
});
2727

2828
test('MaxEntropy should work like ImageJ', () => {
2929
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
30-
expect(computeThreshold(img, 'maxEntropy')).toBe(126);
30+
expect(computeThreshold(img, { algorithm: 'maxEntropy' })).toBe(126);
3131
});
3232

3333
test('Mean should work like ImageJ', () => {
3434
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
35-
expect(computeThreshold(img, 'mean')).toBe(106);
35+
expect(computeThreshold(img, { algorithm: 'mean' })).toBe(106);
3636
});
3737

3838
test('MinError should work like ImageJ', () => {
3939
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
40-
expect(computeThreshold(img, 'minError')).toBe(101);
40+
expect(computeThreshold(img, { algorithm: 'minError' })).toBe(101);
4141
});
4242

4343
test('Minimum should work like ImageJ', () => {
4444
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
45-
expect(computeThreshold(img, 'minimum')).toBe(234);
45+
expect(computeThreshold(img, { algorithm: 'minimum' })).toBe(234);
4646
});
4747

4848
test('Moments should work like ImageJ', () => {
4949
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
50-
expect(computeThreshold(img, 'moments')).toBe(127);
50+
expect(computeThreshold(img, { algorithm: 'moments' })).toBe(127);
5151
});
5252

5353
test('Otsu should work like ImageJ', () => {
5454
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
55-
expect(computeThreshold(img, 'otsu')).toBe(135);
55+
expect(computeThreshold(img, { algorithm: 'otsu' })).toBe(135);
5656
});
5757

5858
test('RenyiEntropy should work similarily to ImageJ', () => {
5959
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
60-
expect(computeThreshold(img, 'renyiEntropy')).toBe(116);
60+
expect(computeThreshold(img, { algorithm: 'renyiEntropy' })).toBe(116);
6161
});
6262

6363
test('Shanbhag should work like ImageJ', () => {
6464
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
65-
expect(computeThreshold(img, 'shanbhag')).toBe(116);
65+
expect(computeThreshold(img, { algorithm: 'shanbhag' })).toBe(116);
6666
});
6767

6868
test('Triangle should work like ImageJ', () => {
6969
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
70-
expect(computeThreshold(img, 'triangle')).toBe(87);
70+
expect(computeThreshold(img, { algorithm: 'triangle' })).toBe(87);
7171
});
7272

7373
test('Yen should work like ImageJ', () => {
7474
const img = testUtils.load('various/grayscale_by_zimmyrose.png');
75-
expect(computeThreshold(img, 'yen')).toBe(108);
75+
expect(computeThreshold(img, { algorithm: 'yen' })).toBe(108);
7676
});

src/roi/__tests__/waterShed.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('Test WaterShed Roi generation', () => {
114114
[4, 4, 4, 4, 3, 2, 2, 2, 2, 3],
115115
[4, 4, 4, 4, 3, 3, 3, 3, 2, 3],
116116
]);
117-
const threshold = computeThreshold(image, 'otsu');
117+
const threshold = computeThreshold(image, { algorithm: 'otsu' });
118118

119119
const roiMapManager = waterShed(image, {
120120
threshold: threshold / image.maxValue,

0 commit comments

Comments
 (0)