Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit 36a01cd

Browse files
Create CONTRIBUTING.md
basic guide for contributors
1 parent 4b948f2 commit 36a01cd

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed

CONTRIBUTING.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
First of all, any kind of contribution is highly appreciated, you don't have to be a pro in C++, neither am I. If you are totally new to native Node.js development and would like to get started, you can have a look at my article series as a quick introduction:
2+
<a href="https://medium.com/netscape/tutorial-building-native-c-modules-for-node-js-using-nan-part-1-755b07389c7c"><b>Tutorial to Native Node.js Modules with C++</b></a>
3+
4+
5+
Oftentimes adding bindings is done similarly to what already exists in the codebase. Thus, you can take the existing stuff as an example to help you to get started. In the following, you can find some basic guidelines for adding new OpenCV function bindings to the package.
6+
7+
## API Design
8+
The API is designed such that
9+
10+
A: Parameters passed to a function call are type checked and appropriate messages are displayed to the user in case an error occured. Nobody wants passing garbage to a function by coincidence to fail silently, which may produce unexpected results.
11+
12+
B: A function, which takes more than a single parameter with default values, can conveniently be invoked by passing a JSON object with named parameters in substitution of the optional parameters. For example consider the following function signature from the official OpenCV 3 docs:
13+
14+
``` c++
15+
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT)
16+
```
17+
18+
The function should be invokable in the following ways:
19+
``` javascript
20+
const mat = new cv.Mat(...)
21+
22+
// required arguments
23+
const size = new cv.Size(...)
24+
const sigmaX = 1.2
25+
26+
// optional arguments
27+
const sigmaY = 1.2
28+
const borderType = cv.BORDER_CONSTANT
29+
30+
let dst
31+
32+
// with required arguments
33+
dst = mat.gaussianBlur(size, sigmaX)
34+
35+
// with optional arguments
36+
dst = mat.gaussianBlur(size, sigmaX, sigmaY)
37+
dst = mat.gaussianBlur(size, sigmaX, sigmaY, borderType)
38+
39+
// with named optional arguments as JSON object
40+
dst = mat.gaussianBlur(size, sigmaX, { sigmaY: 1.2 })
41+
dst = mat.gaussianBlur(size, sigmaX, { borderType: cv.BORDER_CONSTANT })
42+
dst = mat.gaussianBlur(size, sigmaX, { sigmaY: 1.2, borderType: cv.BORDER_CONSTANT })
43+
```
44+
45+
Passing optional arguments as named parameters shall provide the convenience of being able to pass single optional parameters without having to pass every other optional paramater.
46+
47+
## Adding Function Bindings
48+
49+
With the Worker struct you can easily implement the sync and async bindings for a function. If you go conform with the struct pattern, it will automatically handle any typechecking of arguments and unwrapping them via Converters for you so you don't have to worry about checking them manually.
50+
51+
In the .h file add the declaration of the bindings its' worker to the class definition:
52+
53+
``` c++
54+
class Mat : public Nan::ObjectWrap {
55+
56+
...
57+
58+
struct GaussianBlurWorker;
59+
NAN_METHOD(GaussianBlur);
60+
NAN_METHOD(GaussianBlurAsync);
61+
62+
}
63+
64+
```
65+
66+
In the .cc file add the implementation of the worker:
67+
68+
``` c++
69+
struct Mat::GaussianBlurWorker : public SimpleWorker {
70+
public:
71+
// instance of the class exposing the method
72+
cv::Mat mat;
73+
GaussianBlurWorker(cv::Mat mat) {
74+
this->mat = mat;
75+
}
76+
77+
// required function arguments
78+
cv::Size2d kSize;
79+
double sigmaX;
80+
// optional function arguments
81+
double sigmaY = 0;
82+
int borderType = cv::BORDER_CONSTANT;
83+
84+
// function return value
85+
cv::Mat blurMat;
86+
87+
// here the main work is performed, the async worker will execute
88+
// this in a different thread
89+
const char* execute() {
90+
cv::GaussianBlur(mat, blurMat, kSize, sigmaX, sigmaY, borderType);
91+
// if you need to handle errors, you can return an error message here, which
92+
// will trigger the error callback if message is not empty
93+
return "";
94+
}
95+
96+
// return the native objects, handle all object wrapping stuff here
97+
v8::Local<v8::Value> getReturnValue() {
98+
return Mat::Converter::wrap(blurMat);
99+
}
100+
101+
// implement this method if function takes any required arguments
102+
bool unwrapRequiredArgs(Nan::NAN_METHOD_ARGS_TYPE info) {
103+
return (
104+
Size::Converter::arg(0, &kSize, info) ||
105+
DoubleConverter::arg(1, &sigmaX, info)
106+
);
107+
}
108+
109+
// implement this method if function takes any optional arguments
110+
bool unwrapOptionalArgs(Nan::NAN_METHOD_ARGS_TYPE info) {
111+
return (
112+
DoubleConverter::optArg(2, &sigmaY, info) ||
113+
IntConverter::optArg(3, &borderType, info)
114+
);
115+
}
116+
117+
// implement the following methods if function takes more than a single optional parameter
118+
119+
// check if a JSON object as the first argument after the required arguments
120+
bool hasOptArgsObject(Nan::NAN_METHOD_ARGS_TYPE info) {
121+
return FF_ARG_IS_OBJECT(2);
122+
}
123+
124+
// get the values from named properties of the JSON object
125+
bool unwrapOptionalArgsFromOpts(Nan::NAN_METHOD_ARGS_TYPE info) {
126+
FF_OBJ opts = info[2]->ToObject();
127+
return (
128+
DoubleConverter::optProp(&sigmaY, "sigmaY", opts) ||
129+
IntConverter::optProp(&borderType, "borderType", opts)
130+
);
131+
}
132+
};
133+
```
134+
135+
After you have set up the worker, implementing the bindings is as easy as follows:
136+
137+
``` c++
138+
NAN_METHOD(Mat::GaussianBlur) {
139+
GaussianBlurWorker worker(Mat::Converter::unwrap(info.This()));
140+
FF_WORKER_SYNC("Mat::GaussianBlur", worker);
141+
info.GetReturnValue().Set(worker.getReturnValue());
142+
}
143+
144+
NAN_METHOD(Mat::GaussianBlurAsync) {
145+
GaussianBlurWorker worker(Mat::Converter::unwrap(info.This()));
146+
FF_WORKER_ASYNC("Mat::GaussianBlurAsync", GaussianBlurWorker, worker);
147+
}
148+
```
149+
150+
## Using converters
151+
152+
For converting native types to v8 types and unwrapping/ wrapping objects and instances you can use the Converters. A Converter will perform type checking and throw an error if converting a value or unwrapping an object failed. If a converter returns true, an error was thrown. You should use the Converters in conjunction with a worker struct. Otherwise you will have to handle rethrowing the error manually. There are Converters for conversion of primitive types, for unwrapping/ wrapping class instances as well as arrays of primitive types and arrays of instances. For representation of a JS array in c++ we are using std::vector.
153+
154+
Some Usage examples:
155+
``` c++
156+
// require arg 0 to be a Mat
157+
cv::Mat img;
158+
Mat::Converter::arg(0, &img, info);
159+
160+
// require arg 0 to be a Mat if arg is passed to the function
161+
cv::Mat img = // some default value
162+
Mat::Converter::optArg(0, &img, info);
163+
164+
// get the the property "image" of an object and convert its value to Mat
165+
cv::Mat img = // some default value
166+
Mat::Converter::optProp(&img, "image", optPropsObject);
167+
168+
// wrapping the Mat object
169+
cv::Mat img = // some mat
170+
v8::Local<v8::Value> jsMat = Mat::Converter::wrap(img);
171+
172+
// primitive type converters
173+
bool aBool;
174+
BoolConverter::arg(0, &aBool, info);
175+
double aDouble;
176+
DoubleConverter::arg(0, &aDouble, info);
177+
float aFloat;
178+
FloatConverter::arg(0, &aFloat, info);
179+
int anInt;
180+
IntConverter::arg(0, &anInt, info);
181+
uint anUint;
182+
UintConverter::arg(0, &anUint, info);
183+
std::string aString;
184+
StringConverter::arg(0, &aString, info);
185+
186+
// converting a std::vector of Points to a JS Array
187+
std::vector<cv::Point2d> points;
188+
v8::Local<v8::Array> jsPoints = ObjectArrayConverter<Point2, cv::Point2d>::wrap(points);
189+
190+
// for simplicity the Point2 class stores cv Points as cv::Point2d, in case you need to wrap a
191+
// std::vector<cv::Point2i> you can use the third template parameter to specify a conversion type
192+
std::vector<cv::Point2i> points;
193+
v8::Local<v8::Array> jsPoints = ObjectArrayConverter<Point2, cv::Point2d, cv::Point2i>::wrap(points);
194+
ObjectArrayConverter<Point2, cv::Point2d, cv::Point2i>::wrap(points);
195+
```
196+
197+
A class can be made convertable if you you add the typedef for an InstanceConverter the class definition. Example for the Mat class wrapper:
198+
``` c++
199+
class Mat : public Nan::ObjectWrap {
200+
public:
201+
cv::Mat mat;
202+
203+
...
204+
205+
cv::Mat* getNativeObjectPtr() { return &mat; }
206+
cv::Mat getNativeObject() { return mat; }
207+
208+
typedef InstanceConverter<Mat, cv::Mat> Converter;
209+
210+
static const char* getClassName() {
211+
return "Mat";
212+
}
213+
};
214+
```
215+
216+
## Unit Tests
217+
218+
We test the bindings directly from JS with a classic mocha + chai setup. The purpose of unit testing is not to ensure correct behaviour of OpenCV function calls as OpenCV functionality is tested and none of our business. However, we want to ensure that our bindings can be called without crashing, that all parameters are passed and objects unwrapped correctly and that the function call returns what we expect it to.
219+
220+
You can use 'generateAPITests' to easily generate default tests for a function binding that is implemented sync and async. This will generate the tests which ensure that the synchronous as well as the callbacked and promisified async bindings are called correctly. However, you are welcome to write additional tests. For the 'gaussianBlur' example generating unit tests can be done as follows:
221+
222+
``` javascript
223+
describe('gaussianBlur', () => {
224+
const matData = [
225+
[0, 0, 128],
226+
[0, 128, 255],
227+
[128, 255, 255]
228+
]
229+
const mat = new cv.Mat(matData, cv.CV_8U)
230+
231+
const expectOutput = (blurred) => {
232+
assertMetaData(blurred)(mat.rows, mat.cols, mat.type);
233+
expect(dangerousDeepEquals(blurred.getDataAsArray(), matData)).to.be.false;
234+
};
235+
236+
const kSize = new cv.Size(3, 3);
237+
const sigmaX = 1.2;
238+
239+
generateAPITests({
240+
getDut: () => rgbMat,
241+
methodName: 'gaussianBlur',
242+
methodNameSpace: 'Mat',
243+
getRequiredArgs: () => ([
244+
kSize,
245+
sigmaX
246+
]),
247+
getOptionalArgsMap: () => ([
248+
['sigmaY', 1.2],
249+
['borderType', cv.BORDER_CONSTANT]
250+
]),
251+
expectOutput
252+
});
253+
});
254+
```
255+
256+
## CI
257+
258+
For continous integration we use Travis CI, which will run a rebuild of the package and run the unit tests for each of the different OpenCV minor versions with and without contrib (opencv3.0, opencv3.1, opencv3.2, opencv3.3, opencv3.3.1, opencv3.0-contrib, opencv3.1-contrib, opencv3.2-contrib, opencv3.3-contrib, opencv3.3.1-contrib). This ensures compatibility across the OpenCV 3 versions as in some minor cases the OpenCV interface may have changed or new features have been added.
259+
260+
The build task will be executed on every push to your working branch as well as every pull request before merging to the master branch. If you have docker set up on your local machine you can run the build tasks on your local machine via the provided npm scripts. For example from the root directory run:
261+
262+
``` bash
263+
npm run build-opencv3.0
264+
# or
265+
npm run build-opencv3.0-contrib
266+
```
267+
268+
## Docs
269+
270+
In the corresponding markdown file in the doc folder add some docs, so that people know how to use the new binding:
271+
272+
<a name="gaussianBlur"></a>
273+
274+
### gaussianBlur
275+
``` javascript
276+
Mat : mat.gaussianBlur(Size kSize, Number sigmaX, Number sigmaY = 0.0, Uint borderType = BORDER_CONSTANT)
277+
```
278+
279+
<a name="gaussianBlurAsync"></a>
280+
281+
### gaussianBlurAsync
282+
``` javascript
283+
mat.gaussianBlurAsync(Size kSize, Number sigmaX, callback(Error err, Mat result))
284+
mat.gaussianBlurAsync(Size kSize, Number sigmaX, ...opts, callback(Error err, Mat result))
285+
mat.gaussianBlurAsync(Size kSize, Number sigmaX, { opts }, callback(Error err, Mat result))
286+
```

0 commit comments

Comments
 (0)