Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add options to mean, median and variance #471

Merged
merged 25 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ebc2811
feat: add option to find mean from array of points
EscapedGibbon Jun 7, 2024
6e0e572
fix: add options to mean method in Image and add variance option
EscapedGibbon Jun 7, 2024
19f35a6
fix: minor fix in mean
EscapedGibbon Jun 7, 2024
77cb1ca
feat: add median option for points
EscapedGibbon Jun 7, 2024
796e015
fix: add options to median method
EscapedGibbon Jun 7, 2024
2d9e9b8
test: add test cases for mean function
EscapedGibbon Jun 7, 2024
98332dc
test: add test cases for median function
EscapedGibbon Jun 7, 2024
24b1b1c
fix: fix mean value calculation
EscapedGibbon Jun 7, 2024
bc9a433
fix: minor fix of variance function
EscapedGibbon Jun 7, 2024
e4769ae
test: add and fix testing cases
EscapedGibbon Jun 7, 2024
86d1988
feat: add variance to index
EscapedGibbon Jun 7, 2024
33fd253
refactor: rename variables in mean function
EscapedGibbon Jun 12, 2024
1471fcd
feat wip add throw if number of coordinates is invalid
EscapedGibbon Jun 12, 2024
ee2a932
refactor: remove ternary operator
EscapedGibbon Jun 12, 2024
e3ead66
feat wip add a check in median
EscapedGibbon Jun 12, 2024
5b7475c
feat: add a check if the coordinate is valid
EscapedGibbon Jun 12, 2024
9ca87e4
feat: add check for invalid coordinates in mean
EscapedGibbon Jun 12, 2024
185aad4
feat: add check for invalid coordinates in median
EscapedGibbon Jun 12, 2024
5aa80ae
test: add testing cases for mean
EscapedGibbon Jun 12, 2024
af89746
test: add testing cases for median
EscapedGibbon Jun 12, 2024
1a848c8
test: add testing cases
EscapedGibbon Jun 12, 2024
1ac1ff6
refactor: remplace getValueByPoint with getValue
EscapedGibbon Jun 12, 2024
d0812e7
Merge remote-tracking branch 'origin/main' into 470-imagemean-accept-…
EscapedGibbon Jun 14, 2024
9928bfd
refactor: change check for invalid points and change function to get …
EscapedGibbon Jun 14, 2024
9d1c023
test: add testing cases
EscapedGibbon Jun 14, 2024
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
24 changes: 16 additions & 8 deletions src/Image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { Mask } from './Mask';
import { add, subtract, SubtractImageOptions } from './compare';
import { divide, DivideOptions } from './compare/divide';
import { multiply, MultiplyOptions } from './compare/multiply';
import { median } from './compute';
import { variance } from './compute/variance';
import {
MedianOptions,
MeanOptions,
median,
VarianceOptions,
variance,
} from './compute';
import { correctColor } from './correctColor';
import {
drawCircleOnImage,
Expand Down Expand Up @@ -707,26 +712,29 @@ export class Image {

/**
* Compute the mean pixel of an image.
* @param options - Mean options.
* @returns The mean pixel.
*/
public mean(): number[] {
return mean(this);
public mean(options?: MeanOptions): number[] {
return mean(this, options);
}

/**
* Compute the median pixel of an image.
* @param options - Median options.
* @returns The median pixel.
*/
public median(): number[] {
return median(this);
public median(options?: MedianOptions): number[] {
return median(this, options);
}

/**
* Compute the variance of each channel of an image.
* @param options - Variance options.
* @returns The variance of the channels of the image.
*/
public variance(): number[] {
return variance(this);
public variance(options?: VarianceOptions): number[] {
return variance(this, options);
}

// DRAW
Expand Down
76 changes: 76 additions & 0 deletions src/compute/__tests__/mean.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Point } from '../../geometry';
import { mean } from '../mean';

test('5x1 RGB image', () => {
Expand Down Expand Up @@ -38,3 +39,78 @@ test('2x4 GREY image', () => {

expect(result).toStrictEqual([1.5]);
});
test('mean from points', () => {
const image = testUtils.createGreyImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points = [
{ column: 0, row: 0 },
{ column: 1, row: 0 },
{ column: 2, row: 1 },
];
const result = image.mean({ points });

expect(result).toStrictEqual([2]);
});
test('mean from points in rgba image', () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points = [
{ column: 0, row: 0 },
{ column: 0, row: 1 },
];
const result = image.mean({ points });

expect(result).toStrictEqual([1, 2, 3, 0]);
});
test('must throw if array is empty', () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points: Point[] = [];

expect(() => {
const result = image.mean({ points });
return result;
}).toThrow('Array of coordinates is empty.');
});
test("must throw if point's row is invalid.", () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points: Point[] = [{ column: 0, row: 2 }];
EscapedGibbon marked this conversation as resolved.
Show resolved Hide resolved

expect(() => {
const result = image.mean({ points });
return result;
}).toThrow('Invalid coordinate: {column: 0, row: 2}');
});
test("must throw if point's column is invalid.", () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points: Point[] = [{ column: 4, row: 1 }];

expect(() => {
const result = image.mean({ points });
return result;
}).toThrow('Invalid coordinate: {column: 4, row: 1}');
});
test('must throw if point has negative values.', () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points: Point[] = [{ column: -14, row: 0 }];

expect(() => {
const result = image.mean({ points });
return result;
}).toThrow('Invalid coordinate: {column: -14, row: 0}');
});
80 changes: 80 additions & 0 deletions src/compute/__tests__/median.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Point } from '../../geometry';
import { median } from '../median';

test('5x1 RGB image', () => {
Expand Down Expand Up @@ -38,3 +39,82 @@ test('2x4 GREY image', () => {

expect(result).toStrictEqual([2]);
});

test('median from points', () => {
const image = testUtils.createGreyImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points = [
{ column: 0, row: 0 },
{ column: 2, row: 1 },
{ column: 1, row: 0 },
];

const result = image.median({ points });

expect(result).toStrictEqual([2]);
});

test('median from points on rgba image', () => {
const image = testUtils.createRgbaImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points = [
{ column: 0, row: 0 },
{ column: 0, row: 1 },
];

const result = image.median({ points });

expect(result).toStrictEqual([1, 2, 2, 2]);
});

test('must throw if array is empty', () => {
const image = testUtils.createRgbaImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points: Point[] = [];
expect(() => {
const result = image.median({ points });
return result;
}).toThrow('Array of coordinates is empty.');
});

test("must throw if point's row is invalid", () => {
const image = testUtils.createRgbaImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points: Point[] = [{ column: 0, row: 2 }];
expect(() => {
const result = image.median({ points });
return result;
}).toThrow('Invalid coordinate: {column: 0, row: 2}');
});

test("must throw if point's column is invalid", () => {
const image = testUtils.createRgbaImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points: Point[] = [{ column: 4, row: 1 }];
expect(() => {
const result = image.median({ points });
return result;
}).toThrow('Invalid coordinate: {column: 4, row: 1}');
});
test('must throw if point has negative values.', () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points: Point[] = [{ column: -14, row: 0 }];

expect(() => {
const result = image.mean({ points });
return result;
}).toThrow('Invalid coordinate: {column: -14, row: 0}');
});
64 changes: 64 additions & 0 deletions src/compute/__tests__/variance.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Point } from '../../geometry';
import { variance } from '../variance';

test('1x1 RGB image', () => {
Expand All @@ -16,3 +17,66 @@ test('GREY image', () => {

expect(result).toStrictEqual([525]);
});

test('variance from points', () => {
const image = testUtils.createGreyImage([
[10, 20, 30, 40],
[50, 60, 70, 80],
]);

const points = [
{ column: 0, row: 0 },
{ column: 1, row: 0 },
{ column: 2, row: 0 },
{ column: 3, row: 0 },
];

const result = image.variance({ points });

expect(result).toStrictEqual([125]);
});
test('must throw if array is empty', () => {
const image = testUtils.createRgbaImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points: Point[] = [];
expect(() => {
const result = image.median({ points });
return result;
}).toThrow('Array of coordinates is empty.');
});
test("must throw if point's coordinates are invalid", () => {
const image = testUtils.createGreyImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points: Point[] = [{ column: 0, row: 2 }];
expect(() => {
const result = image.median({ points });
return result;
}).toThrow('Invalid coordinate: {column: 0, row: 2}');
});
test("must throw if point's coordinates are invalid", () => {
const image = testUtils.createGreyImage([
[1, 2, 2, 2],
[1, 2, 3, 2],
]);
const points: Point[] = [{ column: 4, row: 1 }];
expect(() => {
const result = image.median({ points });
return result;
}).toThrow('Invalid coordinate: {column: 4, row: 1}');
});
test('must throw if point has negative values.', () => {
const image = testUtils.createRgbaImage([
[1, 2, 3, 0],
[1, 2, 3, 0],
]);
const points: Point[] = [{ column: -14, row: 0 }];

expect(() => {
const result = image.mean({ points });
return result;
}).toThrow('Invalid coordinate: {column: -14, row: 0}');
});
1 change: 1 addition & 0 deletions src/compute/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './mean';
export * from './histogram';
export * from './median';
export * from './getExtrema';
export * from './variance';
41 changes: 35 additions & 6 deletions src/compute/mean.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,48 @@
import { Image } from '../Image';
import { Point } from '../geometry';

export interface MeanOptions {
/**
* Points to calculate mean from.
*/
points: Point[];
}

/**
* Compute the mean of an image. The mean can be either computed on each channel
* individually or on the whole image.
* @param image - Image to process.
* @param options - Mean options.
* @returns The mean pixel.
*/
export function mean(image: Image): number[] {
const pixel = new Array<number>(image.channels).fill(0);
for (let row = 0; row < image.height; row++) {
for (let column = 0; column < image.width; column++) {
export function mean(image: Image, options?: MeanOptions): number[] {
const pixelSum = new Array<number>(image.channels).fill(0);
const nbValues = options ? options.points.length : image.size;
if (nbValues === 0) throw new RangeError('Array of coordinates is empty.');
if (options) {
for (const point of options.points) {
for (let channel = 0; channel < image.channels; channel++) {
pixel[channel] += image.getValue(column, row, channel);
if (
point.column < 0 ||
point.column >= image.width ||
point.row < 0 ||
point.row >= image.height
) {
throw new RangeError(
`Invalid coordinate: {column: ${point.column}, row: ${point.row}}.`,
);
}
pixelSum[channel] += image.getValueByPoint(point, channel);
}
}
} else {
for (let row = 0; row < image.height; row++) {
for (let column = 0; column < image.width; column++) {
for (let channel = 0; channel < image.channels; channel++) {
pixelSum[channel] += image.getValue(column, row, channel);
}
}
}
}
return pixel.map((channel) => channel / image.size);
return pixelSum.map((channelSum) => channelSum / nbValues);
}
Loading
Loading