nodejs native c++ npm module memory error, cairo image processing - c++

I've been bugging TJ on node-canvas about a code speed up I'm working on in a fork of a node module he authored and maintains.
I found Canvas.toBuffer() to be killing our pipeline resources and created an alternative that would simply convert from Canvas into an Image without going through a png buffer/media url. The problem is that cairo is a mysterious beast, and there's an additional level of concern about memory allocated within node modules as not to get GC'd by mother v8. I've added the proper HandleScopes to all required functions which access V8 data.
I was able to test the Canvas.loadImage(image) method thousands of times on my mac setup (6.18), as well as stand alone tests on our ubuntu/production servers running the same version of node. But when the code is run as a background process/server and coordinated by Gearman I'm getting some "interesting" memory/segfaults.
In addition I'm having trouble calling any of the methods of classes defined in node-canvas that aren't inline within header files. As a side question What's the best way to create common native source code packages that other node modules can rely on?
I've tried recreating the problem and running it with gdb, node_g, and all the node modules built with symbols and debug flags. But the error crops up in a lib outside of the source I can get a stack trace for.
for reference here's where I call loadImageData and while it runs locally under a variety of conditions, in our production environment when carefully tucked away within a frame server it appears to be causing segfaults (spent the day yesterday trying to gdb node_g our server code but the frame servers are kicked off by gearman... TL;DR didn't get a root cause stack trace)
https://github.com/victusfate/node-canvas/blob/master/src/Canvas.cc#L497
Handle<Value>
Canvas::LoadImage(const Arguments &args) {
HandleScope scope;
LogStream mout(LOG_DEBUG,"node-canvas.paint.ccode.Canvas.LoadImage");
mout << "Canvas::LoadImage top " << LogStream::endl;
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
if (args.Length() < 1) {
mout << "Canvas::LoadImage Error requires one argument of Image type " << LogStream::endl;
return ThrowException(Exception::TypeError(String::New("Canvas::LoadImage requires one argument of Image type")));
}
Local<Object> obj = args[0]->ToObject();
Image *img = ObjectWrap::Unwrap<Image>(obj);
canvas->loadImageData(img);
return Undefined();
}
void Canvas::loadImageData(Image *img) {
LogStream mout(LOG_DEBUG,"node-canvas.paint.ccode.Canvas.loadImageData");
if (this->isPDF()) {
mout << "Canvas::loadImageData pdf canvas type " << LogStream::endl;
cairo_surface_finish(this->surface());
closure_t *closure = (closure_t *) this->closure();
int w = cairo_image_surface_get_width(this->surface());
int h = cairo_image_surface_get_height(this->surface());
img->loadFromDataBuffer(closure->data,w,h);
mout << "Canvas::loadImageData pdf type, finished loading image" << LogStream::endl;
}
else {
mout << "Canvas::loadImageData data canvas type " << LogStream::endl;
cairo_surface_flush(this->surface());
int w = cairo_image_surface_get_width(this->surface());
int h = cairo_image_surface_get_height(this->surface());
img->loadFromDataBuffer(cairo_image_surface_get_data(this->surface()),w,h);
mout << "Canvas::loadImageData image type, finished loading image" << LogStream::endl;
}
}
and here's what the current method in Image looks like (I removed some commented out logging info)
https://github.com/victusfate/node-canvas/blob/master/src/Image.cc#L240
/*
* load from data buffer width*height*4 bytes
*/
cairo_status_t
Image::loadFromDataBuffer(uint8_t *buf, int width, int height) {
this->clearData();
int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width); // 4*width + ?
this->_surface = cairo_image_surface_create_for_data(buf,CAIRO_FORMAT_ARGB32,width,height,stride);
this->data_mode = DATA_IMAGE;
this->loaded();
cairo_status_t status = cairo_surface_status(_surface);
if (status) return status;
return CAIRO_STATUS_SUCCESS;
}
Any help, pro tips, assistance, or words of encouragement would be appreciated.
Originally from google groups

Got it!
I was working on another library today that uses cairomm and discovered surfaces created from data buffers require those buffers to live on as long as the surface does.
http://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create-for-data
"Creates an image surface for the provided pixel data. The output buffer must be kept around until the cairo_surface_t is destroyed or cairo_surface_finish() is called on the surface. The initial contents of data will be used as the initial image contents; you must explicitly clear the buffer, using, for example, cairo_rectangle() and cairo_fill() if you want it cleared."
I introduced a surface that was created from a temporary buffer.
Easy solution in node-canvas fork:
There's a member variable called _data which I can assign a locally malloced data buffer to, which will live on as long as the cairo surface does.
Solution:
A general way to copy a buffer into a surface is to create a temporary surface from the buffer, then draw from the temporary surface onto an allocated surface and let cairo manage it's own memory.
It would look something like this with the c api to cairo to implement.
cairo_surface_t *pTmp = cairo_image_surface_create_for_data (
data
, CAIRO_FORMAT_ARGB32
, width
, height
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
_surface = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32
, width
, height);
cairo_t *cr = cairo_create (_surface);
cairo_set_source_surface (cr, pTmp, x, y);
cairo_paint (cr);

In addition I'm having trouble calling any of the methods of classes
defined in node-canvas that aren't inline within header files. As a
side question What's the best way to create common native source code
packages that other node modules can rely on?
While I don't have an answer for the memory issue/seg fault I was running into in our staging environment. I do have an answer for constructing reusable libraries with native node modules.
I'm using git submodules for all the independent native node modules, and added a conditional preprocessor definition for each of their wscript or binding.gyp files to specify whether or not to generate a shared object .node module.
update Alternatively unique init function names or namespaces can surround the module initialization call (moved to this setup).
In addition I'll be using this new package to aid in debugging or rewriting code sections (I can't spend too much time debugging utilization of several remote libraries).
in wscript or binding.gyp
flags = ['-D_NAME_NODE_MODULE', '-O3', '-Wall', '-D_FILE_OFFSET_BITS=64', '-D_LARGEFILE_SOURCE', '-msse2']
then in an initialization file
#ifdef _NAME_NODE_MODULE
extern "C" {
static void init(Handle<Object> target) {
HandleScope scope;
NODE_SET_METHOD(target, "someFunction", someFunction);
}
NODE_MODULE(moduleName, init);
}
#endif
This way a node native module is only added to when the flag is set. Otherwise it can be linked to normally (like in another node module).

Related

Problem getting OpenCV multitracking to work

I'm in the early stages (VERY alpha) of writing the backend for an open source home video surveillance system. I'm building it on Gstreamer as a series of plugins. The idea is going to make each step of the process modular based on implementations, so that anyone can write a custom module and use that in the pipeline. For an example, take a look at "odo-lib-opencv-yolo" in the repo linked below. Using that as a template, anyone can write their own detection lib using any library or neural network model as use it within this pipeline. I also plan to expand that out to include a few "default" libraries that are optimized for different systems (Nvidia GPUs, Raspberry Pis, Coral TPUs, etc).
The issue I'm running into is the tracking portion. I wanted to start off with a simple and basic tracking system using the tracking API in OpenCV as a "filler" while I flesh out everything else. But it just doesn't seem to work right.
Here's the link to the repo: https://github.com/CeeBeeEh/Odo-Project
Of most importance is this code block in gst/gstodotrack.cpp at line 270:
if (meta->isInferenceFrame) {
LAST_COUNT = meta->detectionCount;
TRACKER->clear();
TRACKER = cv::legacy::MultiTracker::create();
for (int i=0; i<LAST_COUNT; i++) {
LAST_DETECTION[i].class_id = meta->detections[i].class_id;
LAST_DETECTION[i].confidence = meta->detections[i].confidence;
strcpy(LAST_DETECTION[i].label, meta->detections[i].label);
cv::Rect2i rect = cv::Rect2i(meta->detections[i].box.x, meta->detections[i].box.y,
meta->detections[i].box.width, meta->detections[i].box.height);
TRACKER->add(create_cvtracker(odo), img, rect);
}
GST_DEBUG("Added %zu objects to tracker", LAST_COUNT);
}
else {
meta->detectionCount = LAST_COUNT;
std::vector<cv::Rect2d> tracked;
if (!TRACKER->update(img, tracked)) GST_ERROR("Error tracking objects");
GST_DEBUG("Infer count=%lu, tracked count=%zu", LAST_COUNT, tracked.size());
for (int i=0; i<tracked.size(); i++) {
meta->detections[i].box.x = tracked[i].x;
meta->detections[i].box.y = tracked[i].y;
meta->detections[i].box.width = tracked[i].width;
meta->detections[i].box.height = tracked[i].height;
meta->detections[i].class_id = LAST_DETECTION[i].class_id;
meta->detections[i].confidence = LAST_DETECTION[i].confidence;
strcpy(meta->detections[i].label, LAST_DETECTION[i].label);
}
}
Variables at top of file:
cv::Ptr<cv::legacy::MultiTracker> TRACKER;
ulong LAST_COUNT = 0;
DetectionData LAST_DETECTION[DETECTION_MAX] = {};
I originally had
TRACKER = cv::legacy::MultiTracker::create();
in the start method to create it when the plugin is loaded, but that caused a segfault.
I've also tried creating a fixed size array of cv::Ptr<cv::Tracker> and just use each one for a single object with the same id within each frame, and then recycle each one for each inference frame. But that cause odd behaviour with each object was tracking to the same size and position within the frame.
Right now, in the current configuration, the tracking is dog-slow. It's actually faster to just inference each frame on GPU than inference a single frame and track for x additional frames. It should be the opposite.
It seems as though OpenCV's tracking API has changed as of 4.5.1, but I'm not sure if that's the cause of the issue.
I'm looking for help in sorting out the OpenCV tracking issue. I find all the docs very lacking in explaining the what and why of the multi-tracking API. So those are not of much help.

Writing / Accessing points in a PointCloud2 structure C++ / ROS / PCL Library

I am currently building a perception pipeline and am having trouble reading the points of my point cloud structure. I am reading in a point cloud into a PointCloud2 Structure and want to be able to write out the points in series of the point cloud or at the very least access them so I find a way to write them to a file later. The basic code I am working with is here:
void cloud_cropbox_cb(const sensor_msgs::PointCloud2ConstPtr &cloud_msg)
{
// Container for original & filtered data
pcl::PCLPointCloud2 *cloud = new pcl::PCLPointCloud2;
pcl::PCLPointCloud2ConstPtr cloudPtr(cloud);
pcl::PCLPointCloud2 cloud_filtered;
// convert given message into PCL data type
pcl_conversions::toPCL(*cloud_msg, *cloud);
I want to be able to access the x,y,z elements of each point in the cloud and ideally just write them all out to a textfile. I have tried using pcl::io::savePCDFileASCII() but I don't think that is quite applicable here. An additional note is the base program is constantly running and never exits until manually closed I don't know if that will be relavent to writing out to a file. Any help on what exact functions to use / steps I need to take would be much appreciated.
If you literally want to write out the contents of the message to a file, you can just (in launch file or commandline) use ros topic echo [args] > my_file.txt.
For ways that are compatible in C++, I recommend reading from http://wiki.ros.org/pcl_ros, which describes that:
pcl_ros extends the ROS C++ client library to support message passing with PCL native data types. Simply add the following include to your ROS node source code:
#include <pcl_ros/point_cloud.h>
This header allows you to publish and subscribe pcl::PointCloud objects as ROS messages. These appear to ROS as sensor_msgs/PointCloud2 messages, offering seamless interoperability with non-PCL-using ROS nodes. For example, you may publish a pcl::PointCloud in one of your nodes and visualize it in rviz using a Point Cloud2 display. It works by hooking into the roscpp serialization infrastructure.
This means that you can internally use PCL all you want, and it's all a ROS PointCloud2 message. But you have to declare the PCL type / interface you want to use in the message / publisher / subscriber that you access the data with. So instead of using the sensor_msgs/PointCloud2 type everywhere, use the pcl::Pointcloud<pcl::Point*> type everywhere.
Publishing:
typedef pcl::PointCloud<pcl::PointXYZ> PointCloud;
ros::Publisher pub = nh.advertise<PointCloud> ("points2", 1);
PointCloud::Ptr msg (new PointCloud);
msg->points.push_back (pcl::PointXYZ(1.0, 2.0, 3.0));
pub.publish (msg);
Subscribing:
typedef pcl::PointCloud<pcl::PointXYZ> PointCloud;
ros::Subscriber sub = nh.subscribe<PointCloud>("points2", 1, callback);
void callback(const PointCloud::ConstPtr& msg){
printf ("Cloud: width = %d, height = %d\n", msg->width, msg->height);
BOOST_FOREACH (const pcl::PointXYZ& pt, msg->points)
printf ("\t(%f, %f, %f)\n", pt.x, pt.y, pt.z);
}
More examples (or links to node source code) in http://wiki.ros.org/pcl_ros, tutorials on other pipeline stuff in http://wiki.ros.org/pcl_ros/Tutorials.

Visualization of dynamically created modules

I am building a dynamic multi agent simulation in OMNeT and for this I have to create new modules at runtime. The module creation is working, however, the modules created at runtime are not appearing in the 3D visualization.
module "node" is created sucessfully
Does anyone know how to make the module appear in the visualization? Do I have to update the visualization module?
omnet.ini:
[General]
network = AgentNetwork
*.visualizer.osgVisualizer.typename = "IntegratedOsgVisualizer"
*.visualizer.*.mobilityVisualizer.animationSpeed = 1
*.visualizer.osgVisualizer.sceneVisualizer.typename = "SceneOsgEarthVisualizer"
*.visualizer.osgVisualizer.sceneVisualizer.mapFile = "hamburg.earth"
AgentSpawner:
void AgentSpawner::initialize()
{
cMessage *timer = new cMessage("timer");
scheduleAt(1.0, timer);
}
void AgentSpawner::handleMessage(cMessage *msg)
{
cModuleType *moduleType = cModuleType::get("simulations.Agent");
cModule *module = moduleType->create("node", getParentModule());
// set up parameters and gate sizes before we set up its submodules
module->par("osgModel") = "3d/glider.osgb.(20).scale.0,0,180.rot";
module->getDisplayString().parse("p=200,100;i=misc/aircraft");
module->finalizeParameters();
// create internals, and schedule it
module->buildInside();
module->callInitialize();
module->scheduleStart(simTime()+5.0);
}
The OSG visualization info is maintained totally separately from the actual simulation model module object (that's because the visualization must be ALWAYS optional in the simulation, so make sure your simulation builds fine with OSG totally turned off). This means that an entirely different data structure is built during initialization time from the existing network nodes. As this is done only once during the initialization, dynamically created modules will not have their visualization counterpart data structure.
The code which created the corresponding objects is here.
The solution would be to look up the NetworkNodeOsgVisualizer module in your AgentSpawner code then create and add the corresponding data structures (NetworkNodeOsgVisualization objects). The needed methods (create and add) are there, but sadly they are protected, so you many need to modify the INET code and make them public to be able to call them.

How to Remove Hex Digits Prepended to a Windows Process Name

I have created a 64-bit executable using Visual Studio 2015, intended to be run on Windows 7. The executable is a C++ wrapper that calls the main method in a Java application via JNI. The application runs as expected, but in Windows Task Manager, on the "Process" tab, my application name has been prepended with 16 hex digits. So even though my application compiles to "someapp.exe", it is listed as "80005b29594d4a91someapp.exe" in the process list when I run it. Does anyone know why this is happening, and how to make it show up as just "someapp.exe" in the Task Manager?
EDIT 1:
I should note that the hex string is always the same when it appears in the name. However, there is a small percentage of the time I run my application when it actually has the expected name of "someapp.exe". I have not been able to figure out the pattern of when the hex string is prepended and when it is not, but I estimate the hex string appears 98% of the time it is executed.
EDIT 2:
This appears to be related somehow to the use of JNI. When I remove the JNI calls, this stops occuring altogether. The following represents the entirety of the C++ code making up the "someapp" application:
#include <jni.h>
#include <Windows.h>
#define STRING_CLASS "java/lang/String"
int main(size_t const argc, char const *const argv[]) {
// Modify the DLL search path
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 |
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_USER_DIRS);
SetDllDirectoryA(R"(C:\Program Files\Java\jdk1.8.0_112\jre\bin\server)");
// Create and populate the JVM input arguments
JavaVMInitArgs vm_args;
vm_args.version = JNI_VERSION_1_8;
vm_args.ignoreUnrecognized = JNI_FALSE;
vm_args.nOptions = 2;
vm_args.options = new JavaVMOption[vm_args.nOptions];
// Set command-line options
vm_args.options[0].optionString = "-Dfile.encoding=UTF-8";
vm_args.options[1].optionString = "-Djava.class.path=someapp.jar";
// Create the JVM instance
JavaVM *jvm;
JNIEnv *env;
JNI_CreateJavaVM(&jvm, reinterpret_cast<void**>(&env), &vm_args);
// Get the main entry point of the Java application
jclass mainClass = env->FindClass("myNamespace/MainClass");
jmethodID mainMethod = env->GetStaticMethodID(
mainClass, "main", "([L" STRING_CLASS ";)V");
// Create the arguments passed to the JVM
jclass stringClass = env->FindClass(STRING_CLASS);
jobjectArray mainArgs = env->NewObjectArray(
static_cast<jsize>(argc - 1), stringClass, NULL);
for (size_t i(1); i < argc; ++i) {
env->SetObjectArrayElement(mainArgs,
static_cast<jsize>(i - 1), env->NewStringUTF(argv[i]));
}
env->CallStaticVoidMethod(mainClass, mainMethod, mainArgs);
// Free the JVM, and return
jvm->DestroyJavaVM();
delete[] vm_args.options;
return 0;
}
I have tried to remove the arguments passed to the Java main method, but that had no affect on the outcome.
EDIT 3:
Thanks to the suggestion from 1201ProgramAlarm, I realized that this was actually related to running from a dynamic ClearCase view. The "Image Path Name" column in the Task Manager was one of the following values, which directly correlates with the incorrect "Image Name" symptom that I was observing:
\view\view-name\someapp-path\someapp.exe
\view-server\views\domain\username\view-name.vws.s\00035\8‌​0005b29594d4a91somea‌​pp.exe
I would still like to know why this is happening, but since this only affects our development environment, fixing it has become low priority. For anyone else experiencing this problem, the following represents the relevant software installed in my environment:
Windows 7 Enterprise x64 SP1
Rational ClearCase Explorer 7.1.2.8
Visual Studio 2015 Update 3
Java x64 JDK 8u112
Run your application from a drive that isn't a ClearCase dynamic view.
The Image Name of the running process reference a file in a ClearCase view Storage (\\view\view-name\someapp-path\someapp.exe =>
\\view-server\views\domain\username\view-name.vws\.s\00035\8‌​0005b29594d4a91somea‌​pp.exe), the .vws meaning view storage.
See "About dynamic view storage directories":
Every view has a view storage directory. For dynamic views, this directory is used to keep track of which versions are checked out to your view and to store view-private objects
So a view storage exists both for snapshot and dynamic view.
But for dynamic view, that storage is also used to keep a local copy of the file you want to read/execute (all the other visible files are accessed through the network with MVFS: MultiVersion File System)
That is why you see \\view-server\views\domain\username\view-name.vws\.s\00035\8‌​0005b29594d4a91somea‌​pp.exe when you execute that file: you see the local copy done through MVFS by ClearCase.
Would you have used a snapshot view, you would not have seen such a complex path, since a snapshot view by its very nature does copy all files locally.
It appears as though the path is "correct" when I have not accessed the MVFS mount recently using Windows Explorer
That means the executable executed by Windows is still the correct one, while MVFS will be busy downloading that same executable from the Vob onto the inner folder of the view storage.
But once you re-execute it, that executable is already there (in the view storage), so MVFS will communicate its full path (again, in the view storage) to Windows (as seen in the Process Explorer)

Photoshop plug-in: Access layer information in C++

I am currently writing a plug-in for Photoshop to save layer data into an .xml file, so far I've had no trouble (other than the bleak Adobe SDK) finding information such as layer names, the amount of layers, the document name, the dimensions of the document and more. However, I cannot find out how to access the dimensions and position of a layer, the SDK has been no help and I was wondering if someone had any experience on this?
For reference, here is the code to access the name of the layer:
OSErr PIGetLayerName(const int32 index, string & layerName)
{
Handle h = NULL;
OSErr error = sPSProperty->getPropertyProc(kPhotoshopSignature,
propLayerName,
index, NULL, &h);
if (!error && h != NULL)
{
error = HandleToString(h, layerName);
sPSHandle->Dispose(h);
}
return error;
}
"propLayerName" and "kPhotoshopSignature" are defined in "PIProperties.h" (included in the SDK)
similar definitions of "propTitle" (to get the document name) are also there.
However there are no such definitions for finding the dimensions, bounds, width and height - whatever it may be called - of the layer.
If anyone has any information I would be extremely grateful if you could post it.
Thanks in advance,
Harry