Skip to content

Commit

Permalink
docs: add documentation on OpenCV reference (#455)
Browse files Browse the repository at this point in the history
Plus reorder TestImagePath type alphabetically.
  • Loading branch information
targos authored May 3, 2024
1 parent d80f4a7 commit 2535f4f
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 71 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pids
*.seed
*.pid.lock

# Python stuff
.venv

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

Expand Down
26 changes: 26 additions & 0 deletions Development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Development documentation

## Writing unit tests

### Tests that compare with OpenCV

[OpenCV](https://opencv.org/) is a popular image processing library for C++ and Python.
We use it as a reference for some of the functions implemented in ImageJS.

All images used for comparison with OpenCV are generated by the Python script [`test/img/opencv/generate.py`](./test/img/opencv/generate.py).

To add a new test reference file:

- Update [`generate.py`](./test/img/opencv/generate.py) with the OpenCV code that creates the file.
- Run `generate.py` (see following paragraph).
- Add the new filename to the [`TestImagePath`](./test/TestImagePath.ts) type (alphabetical order).

To run the generation script, use the following steps:

- Install Python 3.x
- Run `python3 -m venv .venv` to create a virtual environment for the project
- Activate the venv or run the local `pip` and `python` commands.
- `source .venv/bin/activate` (UNIX)
- `.venv/Scripts/Activate.ps1` (Windows)
- Run `pip install opencv-python`
- Run `python test/img/opencv/generate.py`
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Image processing and manipulation in JavaScript.

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

## Development

See [Development documentation](./Development.md).

## License

[MIT](./LICENSE)
Expand Down
106 changes: 53 additions & 53 deletions test/TestImagePath.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
// Update this type when test/img content changes.
export type TestImagePath =
| 'align/cropped.png'
| 'align/cropped1.png'
| 'align/croppedRef.png'
| 'align/croppedRef1.png'
| 'correctColor/color-balance.png'
| 'correctColor/exposure-minus-1.png'
| 'correctColor/exposure-plus-1.png'
| 'correctColor/inverted.png'
| 'correctColor/offsets.png'
| 'correctColor/test.png'
| 'featureMatching/alphabet.jpg'
| 'featureMatching/alphabetRotated2.jpg'
| 'featureMatching/alphabetRotated5.jpg'
| 'featureMatching/alphabetRotated10.jpg'
| 'featureMatching/alphabetRotated-2.jpg'
| 'featureMatching/alphabetRotated-5.jpg'
| 'featureMatching/alphabetRotated-10.jpg'
| 'featureMatching/alphabetTranslated10.jpg'
| 'featureMatching/alphabetTranslated20.jpg'
| 'featureMatching/alphabetTranslated50.jpg'
| 'featureMatching/id-crops/crop1.png'
| 'featureMatching/id-crops/crop2.png'
| 'featureMatching/id-crops/crop3.png'
| 'featureMatching/patch.png'
| 'featureMatching/polygons/star.png'
| 'featureMatching/polygons/scaleneTriangle.png'
| 'featureMatching/polygons/scaleneTriangle2.png'
| 'featureMatching/polygons/scaleneTriangle10.png'
| 'featureMatching/polygons/scaleneTriangle90.png'
| 'featureMatching/polygons/scaleneTriangle180.png'
| 'featureMatching/polygons/polygon.png'
| 'featureMatching/polygons/polygon2.png'
| 'featureMatching/polygons/polygonRotated10degrees.png'
| 'featureMatching/polygons/polygonRotated180degrees.png'
| 'formats/grey6.jpg'
| 'formats/grey8.png'
| 'formats/grey12.jpg'
Expand All @@ -15,74 +49,40 @@ export type TestImagePath =
| 'formats/rgba32.png'
| 'formats/rgba64.png'
| 'formats/tif/grey8.tif'
| 'formats/tif/grey8-multi.tif'
| 'formats/tif/grey16.tif'
| 'formats/tif/grey16-multi.tif'
| 'formats/tif/grey32.tif'
| 'formats/tif/greya16.tif'
| 'formats/tif/greya32.tif'
| 'formats/tif/palette.tif'
| 'formats/tif/rgb16.tif'
| 'formats/tif/rgba8.tif'
| 'formats/tif/grey8-multi.tif'
| 'formats/tif/grey16-multi.tif'
| 'formats/tif/rgb16-multi.tif'
| 'formats/tif/rgba8.tif'
| 'formats/tif/rgba8-multi.tif'
| 'formats/tif/palette.tif'
| 'morphology/alphabetCannyEdge.png'
| 'morphology/grayscaleCannyEdge.png'
| 'morphology/grayscaleClearBorder.png'
| 'opencv/test.png'
| 'opencv/testAffineTransform.png'
| 'opencv/testAntiClockwiseRot90.png'
| 'opencv/testBlur.png'
| 'opencv/testClockwiseRot90.png'
| 'opencv/testConvolution.png'
| 'opencv/testGaussianBlur.png'
| 'opencv/testInterpolate.png'
| 'opencv/testRotateBicubic.png'
| 'opencv/testRotateBilinear.png'
| 'opencv/testTranslate.png'
| 'opencv/testReflect.png'
| 'opencv/testResizeBilinear.png'
| 'opencv/testResizeNearest.png'
| 'opencv/testAntiClockwiseRot90.png'
| 'opencv/testClockwiseRot90.png'
| 'opencv/testRotateBicubic.png'
| 'opencv/testRotateBilinear.png'
| 'opencv/testScale.png'
| 'opencv/testReflect.png'
| 'various/grayscale_by_zimmyrose.png'
| 'various/alphabet.jpg'
| 'various/without-metadata.jpg'
| 'morphology/alphabetCannyEdge.png'
| 'morphology/grayscaleCannyEdge.png'
| 'morphology/grayscaleClearBorder.png'
| 'correctColor/test.png'
| 'correctColor/color-balance.png'
| 'correctColor/exposure-minus-1.png'
| 'correctColor/exposure-plus-1.png'
| 'correctColor/inverted.png'
| 'correctColor/offsets.png'
| 'ssim/ssim-original.png'
| 'ssim/ssim-contrast.png'
| 'ssim/ssim-saltPepper.png'
| 'opencv/testTranslate.png'
| 'ssim/ssim-blurry.png'
| 'ssim/ssim-compressed.png'
| 'featureMatching/alphabet.jpg'
| 'featureMatching/alphabetRotated2.jpg'
| 'featureMatching/alphabetRotated5.jpg'
| 'featureMatching/alphabetRotated10.jpg'
| 'featureMatching/alphabetRotated-2.jpg'
| 'featureMatching/alphabetRotated-5.jpg'
| 'featureMatching/alphabetRotated-10.jpg'
| 'featureMatching/alphabetTranslated10.jpg'
| 'featureMatching/alphabetTranslated20.jpg'
| 'featureMatching/alphabetTranslated50.jpg'
| 'featureMatching/id-crops/crop1.png'
| 'featureMatching/id-crops/crop2.png'
| 'featureMatching/id-crops/crop3.png'
| 'featureMatching/patch.png'
| 'featureMatching/polygons/star.png'
| 'featureMatching/polygons/scaleneTriangle.png'
| 'featureMatching/polygons/scaleneTriangle2.png'
| 'featureMatching/polygons/scaleneTriangle10.png'
| 'featureMatching/polygons/scaleneTriangle90.png'
| 'featureMatching/polygons/scaleneTriangle180.png'
| 'featureMatching/polygons/polygon.png'
| 'featureMatching/polygons/polygon2.png'
| 'featureMatching/polygons/polygonRotated180degrees.png'
| 'featureMatching/polygons/polygonRotated10degrees.png'
| 'align/cropped.png'
| 'align/croppedRef.png'
| 'align/cropped1.png'
| 'align/croppedRef1.png';
| 'ssim/ssim-contrast.png'
| 'ssim/ssim-original.png'
| 'ssim/ssim-saltPepper.png'
| 'various/alphabet.jpg'
| 'various/grayscale_by_zimmyrose.png'
| 'various/without-metadata.jpg';
40 changes: 22 additions & 18 deletions test/img/opencv/generate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import numpy as np
import cv2 as cv
from os import path

img = cv.imread('./test.png')
dirname = path.dirname(path.abspath(__file__))

def writeImg(name, img):
cv.imwrite(path.join(dirname, name), img)

img = cv.imread(path.join(dirname, 'test.png'))
assert img is not None, "file could not be read, check with os.path.exists()"
rows, cols = img.shape[0], img.shape[1]

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

# Image resizing by 10.
# Image resizing.
dst = cv.resize(img, (80, 100), interpolation=cv.INTER_NEAREST)
cv.imwrite('testResizeNearest.png', dst)
writeImg('testResizeNearest.png', dst)
dst = cv.resize(img, (80, 100), interpolation=cv.INTER_LINEAR)
cv.imwrite('testResizeBilinear.png', dst)
writeImg('testResizeBilinear.png', dst)

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

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

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

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

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

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

# Image translation
M = np.float32([[1, 0, 2], [0, 1, 4]])
dst = cv.warpAffine(img, M, (16, 20))
cv.imwrite('testTranslate.png', dst)
writeImg('testTranslate.png', dst)

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

# Image blur
dst = cv.blur(img, (3, 5), borderType=cv.BORDER_REFLECT)
cv.imwrite('testBlur.png', dst)
writeImg('testBlur.png', dst)

# Image gaussian blur
kernel = cv.getGaussianKernel(3, 1)
dst = cv.sepFilter2D(img, -1, kernel, kernel, borderType=cv.BORDER_REFLECT)
cv.imwrite('testGaussianBlur.png', dst)
writeImg('testGaussianBlur.png', dst)

# Image convolution
kernelX = np.float32([[0.1, 0.2, 0.3]])

kernelY = np.float32([[0.4, 0.5, 0.6, -0.3, -0.4]])

dst = cv.sepFilter2D(img, ddepth=-1, kernelX=kernelX, kernelY=kernelY, borderType=cv.BORDER_REFLECT)
cv.imwrite('testConvolution.png', dst)
writeImg('testConvolution.png', dst)

0 comments on commit 2535f4f

Please sign in to comment.