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

Commit 2535f4f

Browse files
authored
docs: add documentation on OpenCV reference (#455)
Plus reorder TestImagePath type alphabetically.
1 parent d80f4a7 commit 2535f4f

File tree

5 files changed

+108
-71
lines changed

5 files changed

+108
-71
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ pids
1515
*.seed
1616
*.pid.lock
1717

18+
# Python stuff
19+
.venv
20+
1821
# Directory for instrumented libs generated by jscoverage/JSCover
1922
lib-cov
2023

Development.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Development documentation
2+
3+
## Writing unit tests
4+
5+
### Tests that compare with OpenCV
6+
7+
[OpenCV](https://opencv.org/) is a popular image processing library for C++ and Python.
8+
We use it as a reference for some of the functions implemented in ImageJS.
9+
10+
All images used for comparison with OpenCV are generated by the Python script [`test/img/opencv/generate.py`](./test/img/opencv/generate.py).
11+
12+
To add a new test reference file:
13+
14+
- Update [`generate.py`](./test/img/opencv/generate.py) with the OpenCV code that creates the file.
15+
- Run `generate.py` (see following paragraph).
16+
- Add the new filename to the [`TestImagePath`](./test/TestImagePath.ts) type (alphabetical order).
17+
18+
To run the generation script, use the following steps:
19+
20+
- Install Python 3.x
21+
- Run `python3 -m venv .venv` to create a virtual environment for the project
22+
- Activate the venv or run the local `pip` and `python` commands.
23+
- `source .venv/bin/activate` (UNIX)
24+
- `.venv/Scripts/Activate.ps1` (Windows)
25+
- Run `pip install opencv-python`
26+
- Run `python test/img/opencv/generate.py`

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Image processing and manipulation in JavaScript.
1515

1616
Look at the [examples](./examples) directory for how the API is being designed.
1717

18+
## Development
19+
20+
See [Development documentation](./Development.md).
21+
1822
## License
1923

2024
[MIT](./LICENSE)

test/TestImagePath.ts

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
// Update this type when test/img content changes.
22
export type TestImagePath =
3+
| 'align/cropped.png'
4+
| 'align/cropped1.png'
5+
| 'align/croppedRef.png'
6+
| 'align/croppedRef1.png'
7+
| 'correctColor/color-balance.png'
8+
| 'correctColor/exposure-minus-1.png'
9+
| 'correctColor/exposure-plus-1.png'
10+
| 'correctColor/inverted.png'
11+
| 'correctColor/offsets.png'
12+
| 'correctColor/test.png'
13+
| 'featureMatching/alphabet.jpg'
14+
| 'featureMatching/alphabetRotated2.jpg'
15+
| 'featureMatching/alphabetRotated5.jpg'
16+
| 'featureMatching/alphabetRotated10.jpg'
17+
| 'featureMatching/alphabetRotated-2.jpg'
18+
| 'featureMatching/alphabetRotated-5.jpg'
19+
| 'featureMatching/alphabetRotated-10.jpg'
20+
| 'featureMatching/alphabetTranslated10.jpg'
21+
| 'featureMatching/alphabetTranslated20.jpg'
22+
| 'featureMatching/alphabetTranslated50.jpg'
23+
| 'featureMatching/id-crops/crop1.png'
24+
| 'featureMatching/id-crops/crop2.png'
25+
| 'featureMatching/id-crops/crop3.png'
26+
| 'featureMatching/patch.png'
27+
| 'featureMatching/polygons/star.png'
28+
| 'featureMatching/polygons/scaleneTriangle.png'
29+
| 'featureMatching/polygons/scaleneTriangle2.png'
30+
| 'featureMatching/polygons/scaleneTriangle10.png'
31+
| 'featureMatching/polygons/scaleneTriangle90.png'
32+
| 'featureMatching/polygons/scaleneTriangle180.png'
33+
| 'featureMatching/polygons/polygon.png'
34+
| 'featureMatching/polygons/polygon2.png'
35+
| 'featureMatching/polygons/polygonRotated10degrees.png'
36+
| 'featureMatching/polygons/polygonRotated180degrees.png'
337
| 'formats/grey6.jpg'
438
| 'formats/grey8.png'
539
| 'formats/grey12.jpg'
@@ -15,74 +49,40 @@ export type TestImagePath =
1549
| 'formats/rgba32.png'
1650
| 'formats/rgba64.png'
1751
| 'formats/tif/grey8.tif'
52+
| 'formats/tif/grey8-multi.tif'
1853
| 'formats/tif/grey16.tif'
54+
| 'formats/tif/grey16-multi.tif'
1955
| 'formats/tif/grey32.tif'
2056
| 'formats/tif/greya16.tif'
2157
| 'formats/tif/greya32.tif'
58+
| 'formats/tif/palette.tif'
2259
| 'formats/tif/rgb16.tif'
23-
| 'formats/tif/rgba8.tif'
24-
| 'formats/tif/grey8-multi.tif'
25-
| 'formats/tif/grey16-multi.tif'
2660
| 'formats/tif/rgb16-multi.tif'
61+
| 'formats/tif/rgba8.tif'
2762
| 'formats/tif/rgba8-multi.tif'
28-
| 'formats/tif/palette.tif'
63+
| 'morphology/alphabetCannyEdge.png'
64+
| 'morphology/grayscaleCannyEdge.png'
65+
| 'morphology/grayscaleClearBorder.png'
2966
| 'opencv/test.png'
3067
| 'opencv/testAffineTransform.png'
68+
| 'opencv/testAntiClockwiseRot90.png'
3169
| 'opencv/testBlur.png'
70+
| 'opencv/testClockwiseRot90.png'
3271
| 'opencv/testConvolution.png'
3372
| 'opencv/testGaussianBlur.png'
3473
| 'opencv/testInterpolate.png'
35-
| 'opencv/testRotateBicubic.png'
36-
| 'opencv/testRotateBilinear.png'
37-
| 'opencv/testTranslate.png'
74+
| 'opencv/testReflect.png'
3875
| 'opencv/testResizeBilinear.png'
3976
| 'opencv/testResizeNearest.png'
40-
| 'opencv/testAntiClockwiseRot90.png'
41-
| 'opencv/testClockwiseRot90.png'
77+
| 'opencv/testRotateBicubic.png'
78+
| 'opencv/testRotateBilinear.png'
4279
| 'opencv/testScale.png'
43-
| 'opencv/testReflect.png'
44-
| 'various/grayscale_by_zimmyrose.png'
45-
| 'various/alphabet.jpg'
46-
| 'various/without-metadata.jpg'
47-
| 'morphology/alphabetCannyEdge.png'
48-
| 'morphology/grayscaleCannyEdge.png'
49-
| 'morphology/grayscaleClearBorder.png'
50-
| 'correctColor/test.png'
51-
| 'correctColor/color-balance.png'
52-
| 'correctColor/exposure-minus-1.png'
53-
| 'correctColor/exposure-plus-1.png'
54-
| 'correctColor/inverted.png'
55-
| 'correctColor/offsets.png'
56-
| 'ssim/ssim-original.png'
57-
| 'ssim/ssim-contrast.png'
58-
| 'ssim/ssim-saltPepper.png'
80+
| 'opencv/testTranslate.png'
5981
| 'ssim/ssim-blurry.png'
6082
| 'ssim/ssim-compressed.png'
61-
| 'featureMatching/alphabet.jpg'
62-
| 'featureMatching/alphabetRotated2.jpg'
63-
| 'featureMatching/alphabetRotated5.jpg'
64-
| 'featureMatching/alphabetRotated10.jpg'
65-
| 'featureMatching/alphabetRotated-2.jpg'
66-
| 'featureMatching/alphabetRotated-5.jpg'
67-
| 'featureMatching/alphabetRotated-10.jpg'
68-
| 'featureMatching/alphabetTranslated10.jpg'
69-
| 'featureMatching/alphabetTranslated20.jpg'
70-
| 'featureMatching/alphabetTranslated50.jpg'
71-
| 'featureMatching/id-crops/crop1.png'
72-
| 'featureMatching/id-crops/crop2.png'
73-
| 'featureMatching/id-crops/crop3.png'
74-
| 'featureMatching/patch.png'
75-
| 'featureMatching/polygons/star.png'
76-
| 'featureMatching/polygons/scaleneTriangle.png'
77-
| 'featureMatching/polygons/scaleneTriangle2.png'
78-
| 'featureMatching/polygons/scaleneTriangle10.png'
79-
| 'featureMatching/polygons/scaleneTriangle90.png'
80-
| 'featureMatching/polygons/scaleneTriangle180.png'
81-
| 'featureMatching/polygons/polygon.png'
82-
| 'featureMatching/polygons/polygon2.png'
83-
| 'featureMatching/polygons/polygonRotated180degrees.png'
84-
| 'featureMatching/polygons/polygonRotated10degrees.png'
85-
| 'align/cropped.png'
86-
| 'align/croppedRef.png'
87-
| 'align/cropped1.png'
88-
| 'align/croppedRef1.png';
83+
| 'ssim/ssim-contrast.png'
84+
| 'ssim/ssim-original.png'
85+
| 'ssim/ssim-saltPepper.png'
86+
| 'various/alphabet.jpg'
87+
| 'various/grayscale_by_zimmyrose.png'
88+
| 'various/without-metadata.jpg';

test/img/opencv/generate.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import numpy as np
22
import cv2 as cv
3+
from os import path
34

4-
img = cv.imread('./test.png')
5+
dirname = path.dirname(path.abspath(__file__))
6+
7+
def writeImg(name, img):
8+
cv.imwrite(path.join(dirname, name), img)
9+
10+
img = cv.imread(path.join(dirname, 'test.png'))
511
assert img is not None, "file could not be read, check with os.path.exists()"
612
rows, cols = img.shape[0], img.shape[1]
713

@@ -10,67 +16,65 @@
1016
M = np.float32([[scale, 0, 0], [0, scale, 0]])
1117
dst = cv.warpAffine(img, M, dsize=(cols * scale, rows * scale), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_CONSTANT,
1218
borderValue=0)
13-
cv.imwrite('testScale.png', dst)
19+
writeImg('testScale.png', dst)
1420

15-
# Image resizing by 10.
21+
# Image resizing.
1622
dst = cv.resize(img, (80, 100), interpolation=cv.INTER_NEAREST)
17-
cv.imwrite('testResizeNearest.png', dst)
23+
writeImg('testResizeNearest.png', dst)
1824
dst = cv.resize(img, (80, 100), interpolation=cv.INTER_LINEAR)
19-
cv.imwrite('testResizeBilinear.png', dst)
25+
writeImg('testResizeBilinear.png', dst)
2026

2127
# Image rotate counter-clockwise by 90 degrees
2228
M = np.float32([[0, 1, 0], [-1, 0, cols - 1]])
2329
dst = cv.warpAffine(img, M, (rows, cols), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_CONSTANT)
24-
cv.imwrite('testAntiClockwiseRot90.png', dst)
30+
writeImg('testAntiClockwiseRot90.png', dst)
2531

2632
# Image rotate clockwise by 90 degrees
2733
M = np.float32([[0, -1, cols + 1], [1, 0, 0]])
2834
dst = cv.warpAffine(img, M, (rows, cols), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_CONSTANT)
29-
cv.imwrite('testClockwiseRot90.png', dst)
35+
writeImg('testClockwiseRot90.png', dst)
3036

3137
# Image interpolation
3238
matrix = cv.getRotationMatrix2D((2, 4), angle=30, scale=0.8)
3339
dst = cv.warpAffine(img, matrix, dsize=(cols, rows), flags=cv.INTER_NEAREST, borderMode=cv.BORDER_REFLECT)
34-
cv.imwrite('testInterpolate.png', dst)
40+
writeImg('testInterpolate.png', dst)
3541

3642
# Image bilinear interpolation
3743
matrix = cv.getRotationMatrix2D((2, 4), angle=30, scale=1.4)
3844
dst = cv.warpAffine(img, matrix, dsize=(cols, rows), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_REFLECT)
39-
cv.imwrite('testRotateBilinear.png', dst)
45+
writeImg('testRotateBilinear.png', dst)
4046

4147
# Image bicubic interpolation
4248
matrix = cv.getRotationMatrix2D((2, 4), angle=30, scale=1.4)
4349
dst = cv.warpAffine(img, matrix, dsize=(cols, rows), flags=cv.INTER_CUBIC, borderMode=cv.BORDER_REFLECT)
44-
cv.imwrite('testRotateBicubic.png', dst)
50+
writeImg('testRotateBicubic.png', dst)
4551

4652
# Image reflection
4753
M = np.float32([[1, 0, 0], [0, -1, rows - 1]])
4854
dst = cv.warpAffine(img, M, (cols, rows), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_CONSTANT)
49-
cv.imwrite('testReflect.png', dst)
55+
writeImg('testReflect.png', dst)
5056

5157
# Image translation
5258
M = np.float32([[1, 0, 2], [0, 1, 4]])
5359
dst = cv.warpAffine(img, M, (16, 20))
54-
cv.imwrite('testTranslate.png', dst)
60+
writeImg('testTranslate.png', dst)
5561

5662
# Image affine transformation
5763
M = np.float32([[2, 1, 2], [-1, 1, 2]])
5864
dst = cv.warpAffine(img, M, (cols, rows), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_CONSTANT)
59-
cv.imwrite('testAffineTransform.png', dst)
65+
writeImg('testAffineTransform.png', dst)
6066

6167
# Image blur
6268
dst = cv.blur(img, (3, 5), borderType=cv.BORDER_REFLECT)
63-
cv.imwrite('testBlur.png', dst)
69+
writeImg('testBlur.png', dst)
6470

6571
# Image gaussian blur
6672
kernel = cv.getGaussianKernel(3, 1)
6773
dst = cv.sepFilter2D(img, -1, kernel, kernel, borderType=cv.BORDER_REFLECT)
68-
cv.imwrite('testGaussianBlur.png', dst)
74+
writeImg('testGaussianBlur.png', dst)
6975

7076
# Image convolution
7177
kernelX = np.float32([[0.1, 0.2, 0.3]])
72-
7378
kernelY = np.float32([[0.4, 0.5, 0.6, -0.3, -0.4]])
74-
7579
dst = cv.sepFilter2D(img, ddepth=-1, kernelX=kernelX, kernelY=kernelY, borderType=cv.BORDER_REFLECT)
76-
cv.imwrite('testConvolution.png', dst)
80+
writeImg('testConvolution.png', dst)

0 commit comments

Comments
 (0)