sensorable is a prototype tool to aid users of Ubisense sensor systems with determining good locations to place sensors in a factory.
sensorable takes a pointcloud file of the factory and visualizes it in the browser. The user can then inspect the factory and place sensor in its virtual representation. sensorable then simulates the range of the sensors and colorizes the environment according to its reachability by sensors.
-
Clone this repository.
-
Install all of the dependencies listed below.
- In a terminal, navigate to this repository.
- Run the following commands:
cd extrusion
mkdir build && cd build
cmake ..
make
Once you have completed these steps, you can proceed to Usage.
Make sure you have completed the steps in Installation.
- Name and copy the following three files into this repository:
model.pcd
This is the file with the pointcloud of your factory. If your pointcloud is in a different format, e.g..laz
, you can convert it to.pcd
with CloudCompare. Make sure that thepcd
file uses the pcd ascii format. To convert a binary pcd file to an ascii pcd file, you can use thepcl_convert_pcd_ascii_binary
tool from the Point Cloud Library.trajectory.txt
This is the trajectory file generated by GeoSLAM.path.MP4
This is the video for the trajectory generated by GeoSLAM.
-
- In a terminal, navigate to this repository.
- Run
python run.py
.
-
Run
python3.8 -m http.server
to start a webserver. -
Open the application in the browser on
0.0.0.0:8000/index.html
The last command opens the application in your browser. It should look similar to this:
-
The factory is visualized in the left view. You can rotate it and movie it around with your left and right mouse buttons.
-
You can add sensors in two ways: Either, use the "Add Sensor" button to add a sensor that you can move around freely. Or, enable the "Place a sensor" feature and click on a wall to add a sensor on that wall.
-
You can delete a sensor by pressing its "x" button in the sensor list.
-
You can visualize the sensor coverage for a slice of the factory by moving around the inspection plane with the slider on the right of the view. The blacker the plane, the better the coverage.
sensorable consists of several components, each with its own tests.
To test the kd-tree and its integration with Pointcloud files, navigate to
kd_tree
and run ./bin/test_kd_tree
. Make sure to copy your model.pcd
into that directory first for the integration tests to work.
To test the floor plan, import test/FloorPlanTest.js and run the floorTest function. Make sure to copy the convex hull file to test/model_fastPLY_chull.json and include a floor plan Image as test/floor_plan.jpeg.
To run an intergration test of the wall extrusion tool, go to its directory at extrusion/
, then go to the integration_tests/
directory and run the shell script run.sh
which will automatically recompile the tool binary and run it on a sample model file with 10 planes and compare the results with the correct extractions.
SensorControls(camera, domElement, orbit, gui)
- camera: THREE.Camera
- domElement: DOMElement
- orbit: THREE.OrbitControl
- gui: GUI from dat.gui
.createSensor(position, hAxis, vAxis, height, width, free) : Object3D
- position : THREE.Vector3 - the position of the new sensor in world coordinates.
- hAxis : THREE.Vector3 - horizontal translation axis for the sensor, must be perpendicular to vAxis, azimuth will be calculated off from this axis.
- vAxis : THREE.Vector3 - vertical translation axis for the sensor, must be perpendicular to hAxis.
- height : Number - how much the sensor can be translated in the vertical direction.
- width : Number - how much the sensor can be translated in the horizontal direction.
- free : Boolean - whether the sensor can be freely movable.
The method returns the reference to the created geometry.
.deleteSensor(sensor)
- sensor : THREE.Object3d - sensor reference
.getSensors() : THREE.Object3D array
return all sensors in the scene.
.getPositionData(sensor, point)
- sensor : THREE.Object3D
- point : THREE.Vector3
.selectSensor(s)
- s : THREE.Object3D - sensor to select.
.updateGizmoSize()
Makes sure the gizmos and the selected sensor are proportional. Should be called in animate().
This module creates a sphere around the sensor and colors it based on azimuth reading.
SensorTest(_sensor, _mode, _radius, _segments, _sensorControls)
- _sensor : THREE.Object3D - sensor you want to test.
- _mode : must be "azimuth".
- _radius : Number - the radius of the test sphere.
- _sengment : Number - the sphere's width and height segments.
- _sensorControls : SensorControls
.setMode(mode)
- mode : must be "azimuth"
.updateColors()
Update the coloring of the sphere when sensor orientation has changed.
Add this module to the scene if you want to visualise what the sensors can "see".
InspectPlane(_plane_size, _plane_fragments, _altitude, _camera, _renderer, _orbit)
- _plane_size : Number - initial plane size in world unit.
- _plane_fragments : Whole Number - the initial plane will have 2*_plane_fragments^2 triangles this ratio will be preserved as the plane is scaled.
- _altitude : Number - initial z coordinate of the plane
- _camere : THREE.Camera
- _renderer : THREE.Renderer
- _orbit : THREE.OrbitControls
.updateColors(getColor)
Call this whenever the plane or sensor configuration has changed.
- getColor : function (point : THREE.Vector3) => THREE.Color - figures out the color of each vertex on the plane.
.setModeRotate()
Set TransformControls on the plane to rotation.
.setModeTranslate()
Set TransformControls on the plane to translation.
.setModeScale()
Set TransformControls on the plane to scale.
.removeGizmos()
Detaches TransformControls from the plane.
.addGizmos()
Attaches TransforControls to the plane.
Module that interpolates vertex colors for the inspection plane based on sensor data.
Colorer(_sensorControls, _plane)
- _sensorControls : SensorControls
- _plane : InspectPlane
.getColor
: function (THREE.Vector3) => THREE.Color - intended to be passed to InspectPlane updateColor method.
PlaneModel(_modelJSON, _sensorControls)
- _modelJSON : path - file from which to read extrapolated wall data.
- _sensorControls : SensorControls
.createSensor(mouse, camera) : Object3D
Intersects a ray with a model and places a sensor if mouse is pointing at a wall. Intended to be used on mouse click. Returns the newly created sensor.
- mouse : THREE.Vector2D - mouse position on the screen
- camera : THREE.Camera
.intersect(mouse, camera) : THREE.Object3D
Selects a plane that the mouse is currently pointing at.
- mouse : THREE.Vector2D - mouse position on the screen
- camera : THREE.Camera
.deleteSelected()
Deletes plane selected via the previous method.
This feature works only in nodejs http server.
TrajectoryTracking(_trajectoryFile, gui, onChange)
- _trajectoryFile : path - file from which to read trajectory data
- gui : dat.GUI - a place to attach trajectory slider
- onChange : function(seconds) - callback when a particular place on the trajectory is selected.
.updateBallSize(_camera)
Makes the current position marker scale with the zoom. Intended to be called in animate() method.
- _camera : THREE.Camera
kd_element_t* make_tree(point_t *t, int len, int i)
: creates the kd-tree from a pointcloudvoid initialize()
: loadsmodel.pcd
into memory and creates a kd-tree from itvoid get_first_intersection(kd_element_t *root, const point_t ray_orig, const point_t ray_dir, point_t **first_intersection)
: determines the first intersection of the rayray_orig, ray_dir
with the kd-tree atroot
and stores it infirst_intersection
.FLOAT_UNIT get_distance_to_first_intersection(FLOAT_UNIT ray_orig_x, FLOAT_UNIT ray_orig_y, FLOAT_UNIT ray_orig_z, FLOAT_UNIT ray_dir_x, FLOAT_UNIT ray_dir_y, FLOAT_UNIT ray_dir_z);
: wrapper aroundget_first_intersection
that is suitable for a WebAssembly interface.
Embedded default values into code, but can be read from a config.json
file in the execution directory as well.
rectangleDistThreshold
: The minimum squared distance between two rectangles in a plane. Used when the plane is split into rectangles.angleModelThreshold
: The standard deviation of the angle discrepancy of the plane from the desired model fit (horizontal/vertical).modelDistThreshold
: The standard deviation of the distance of the inliers, points within a plane, to the matched plane.rectangleSizeFraction
: The minimum number of points to be included into a rectangle from those in the plane in order it to be displayed.pointsModelThreshold
: The minimum points to be within a plane from the overall filtered point cloud size in order the plane to be considered valid. Determines the stopping criteria.filterRadius
: The radius within which the filtering is donemodelIterations
: The number of iterations for which the plane fitting algorithm runs when fitting a single planefilterNeighbours
: The number of neighbours a point should have within the given radius
Model
: A superclass for all models that are about to be exported from the extraction toolprotected coefficients
: a storage of the model coefficients if it is a PCL modelpublic Json::Value toJSON()
: a method that transforms the model to a Json::Value which is written in the output file
Rectangle
: a child class ofModel
used for the rectangle representationprivate vector<pcl::PointXYZ> corners
: containing the 4 corners of the rectanglepublic Json::Value toJSON()
: writing the 4 corners and adding a flagrectangle
distXY(pcl::PointXYZ a, pcl::PointXYZ b)
: finds the distance of the projections of points A and B into the X-Y planeorientation(pcl::PointXYZ a, pcl::PointXYZ b, pcl::PointXYZ c, Eigen::Vector3f normal)
: finds the oriented area of the triangle given by the points A, B, and C assuming that the direction of thenormal
is the positive oneextract_plane(pcl::PointCloud<pcl::PointXYZ>::Ptr &cloud, pcl::SACSegmentation<pcl::PointXYZ> seg, std::string model_id, int type, int cloud_size)
: performs the plane fitting, rectangularization of plane, and extracting it from the cloud through the following steps:- Feeds the point cloud to the RANSAC segmentation instance
- Runs the plane fitting
- Projects all of the inliers onto the matched plane
- If the plane is of type
0
it processes it as a floor plane and extracts its convex hull - Otherwise, it is a vertical plane, so
- We find its normal
- Order the points by orientation, so they can be parsed by a sweep line algorithm
- Use a sweep line algorithm to detect horizontal rectangles and then partition every horizontal rectangle into vertical ones
- Push back to the clouds the points that were not located into any rectangles
- Remove all of the matched points from the cloud, so that new plane detection can take place
showHelp(char *filename)
: Prints a help message to the user with the basic usage of the toolparseCommandLine(int argc, char *argv[], pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
: Parses the command-line arguments for the tool, opening and reading the point cloud files and importing the configuration optionsint main(int argc, char *argv[])
: The main function performing the following tasks- Initializing the point clouds
- Calling
parseCommandLine()
- Doing the filtering
- Setting up the RANSAC plane fitting algorithm
- Extracting the floor
- Extracting the walls
- Writing the extracted data to the JSON model files
floorPlane(string image_file, THREE.Renderer renderer, number[] convex )
: Uses the convex hull as dictated by x,y coordinates in the convex array where every two values indicate a point and takes in a image file name, returning a Three.Plane object with the image_file overlayed on both sides of the plane and placed at a best-fit rectangle over the convex hull.