Skip to content

Commit

Permalink
RGB PCD Support, v. 1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dmandrioli committed Feb 10, 2020
1 parent c4316cb commit 5bc6b92
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 135 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ It is a [Meteor](http://www.meteor.com) app developed with [React](http://reactj
[Paper.js](http://paperjs.org/) and [three.js](https://threejs.org/).

**Latest changes**
- **Version 1.4:** Support for RGB pointclouds (thanks @Gekk0r)
- **Version 1.3:** Improve pointcloud labeling: bug fixes and performance improvement (labeling a 1M pointcloud is now possible)
- **Version 1.2.2:** Breaking change: exported point cloud coordinates are no longer translated (thanks @hetzge)
- **Version 1.2.0:** Support for binary and binary compressed point clouds (thanks @CecilHarvey)
Expand Down Expand Up @@ -148,12 +149,18 @@ marking)
### Using the point cloud editor

- Mouse left button: Rotate the point cloud around the current focused point (the center of the point cloud by
default), clickon a single point to add it to the current selection
default), click on a single point to add it to the current selection
- Mouse wheel: Zoom in/out
- Mouse middle button (or Ctrl+Click): Change the target of the camera
- Mouse right button: Used to select multiple points at the same time depending on the current Selection Tool and
Selection Mode.

### PCD support

- Supported input PCD format: ASCII, Binary and Binary compressed
- Supported input fields: `x`, `y`, `z`, `label` (optional integer), `rgb` (optional integer)
- Output PCD format is ASCII with fields `x`, `y`, `z`, `label`, `object` and `rgb` (if available)

### API Endpoints

- <code>/api/listing</code>: List all annotated images
Expand Down
4 changes: 2 additions & 2 deletions imports/common/SseToolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ export default class SseToolbar extends React.Component {
}
}

addCommand(name, title, isToggle, shortcut, actionMessage, icon, initialState, legend, icon2) {
const command = {name, title, isToggle, shortcut, actionMessage, icon, legend, icon2};
addCommand(name, title, isToggle, shortcut, actionMessage, icon, initialState, legend) {
const command = {name, title, isToggle, shortcut, actionMessage, icon, legend};
this.commands.set(name, command);
if (isToggle) {
if (typeof isToggle == "boolean")
Expand Down
46 changes: 10 additions & 36 deletions imports/editor/3d/SseCameraToolbar.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';

import Slider, { Range } from 'rc-slider';
import Slider from 'rc-slider';
import {
ArrowCollapseDown, ArrowCollapseLeft, ArrowCollapseRight, ArrowCollapseUp, ArrowExpandDown, Blur, BlurOff,
Brightness6, CubeSend, ImageFilterTiltShift, Rotate3D, Target, Video, Lightbulb, LightbulbOff, RayStartEnd
Brightness6, CubeSend, ImageFilterTiltShift, Rotate3D, Target, Video, Lightbulb, LightbulbOn
} from 'mdi-material-ui';
import SseToolbar from "../../common/SseToolbar";

Expand All @@ -13,8 +13,7 @@ export default class SseCameraToolbar extends SseToolbar {
this.state = {
colorBoostVisible: "none",
pointSizeVisible: "none",
intensityRangeVisible: "none",
intensityRange : [1000,10000]
showRgbToggle: false
};
this.state.data = {
colorBoost: 0
Expand All @@ -34,11 +33,14 @@ export default class SseCameraToolbar extends SseToolbar {
this.addCommand("orientationCommand", "Pointcloud Orientation", false, "-", "orientation-change", Rotate3D, undefined, " ");
this.addCommand("colorBoostCommand", "Color Intensity", 1, "-", "color-boost-toggle", Brightness6, undefined, " ");
this.addCommand("pointSizeCommand", "Point Size", 1, "-", "point-size-toggle", ImageFilterTiltShift, undefined, " ");
this.addCommand("distanceAttenuationCommand", "Distance Attenuation", Blur, "", "distance-attenuation", BlurOff, undefined, undefined, BlurOff);
this.addCommand("intensityCommand", "Display Intensity", LightbulbOff, "", "intensity-toggle", Lightbulb, undefined, undefined, LightbulbOff);
this.addCommand("intensityRangeCommand", "Set Intensity range", 1, "-", "intensity-range-toggle", RayStartEnd, undefined, " ");
this.addCommand("distanceAttenuationCommand", "Distance Attenuation", Blur, "", "distance-attenuation", BlurOff, undefined, undefined);
this.addCommand("rgbCommand", "Toggle RGB", LightbulbOn, "+", "rgb-toggle", Lightbulb, undefined, undefined);

this.setState({ready: true});

this.onMsg("show-rgb-toggle", ()=>{
this.setState({showRgbToggle: true})
});
this.onMsg("color-boost-toggle", () => {
if (this.state.colorBoostVisible == "none") {
this.onMsg("mouse-down", () => {
Expand All @@ -47,7 +49,6 @@ export default class SseCameraToolbar extends SseToolbar {
})
}
this.setState({pointSizeVisible: "none"});
this.setState({intensityRangeVisible: "none"});
this.setState({colorBoostVisible: this.state.colorBoostVisible == "none" ? "" : "none"});
});
this.onMsg("point-size-toggle", () => {
Expand All @@ -58,21 +59,8 @@ export default class SseCameraToolbar extends SseToolbar {
})
}
this.setState({colorBoostVisible: "none"});
this.setState({intensityRangeVisible: "none"});
this.setState({pointSizeVisible: this.state.pointSizeVisible == "none" ? "" : "none"});
});

this.onMsg("intensity-range-toggle", () => {
if (this.state.intensityRangeVisible == "none") {
this.onMsg("mouse-down", () => {
this.setState({intensityRangeVisible: "none"});
this.forgetMsg("mouse-down");
})
}
this.setState({colorBoostVisible: "none"});
this.setState({pointSizeVisible: "none"});
this.setState({intensityRangeVisible: this.state.intensityRangeVisible == "none" ? "" : "none"});
});
}

dataChange(filterName, dataMsg) {
Expand All @@ -99,9 +87,6 @@ export default class SseCameraToolbar extends SseToolbar {
};
const pointSizeSliderStyle =
{display: this.state.pointSizeVisible, height: "150px", position: "absolute", left: "-30px", top: "-110px"};

const intensityRangeSliderStyle =
{display: this.state.intensityRangeVisible, height: "150px", position: "absolute", left: "-30px", top: "-110px"};

return (
<div className="vflex flex-justify-content-space-around sse-toolbar no-shrink">
Expand All @@ -115,19 +100,8 @@ export default class SseCameraToolbar extends SseToolbar {
{this.renderCommand("viewTopCommand")}
</div>
<div className="grow v group flex-justify-content-end">
{this.state.showRgbToggle ? this.renderCommand("rgbCommand") : null}
{this.renderCommand("distanceAttenuationCommand")}
{this.renderCommand("intensityCommand")}
<div className="relative">
{this.renderCommand("intensityRangeCommand")}
<Range
style={intensityRangeSliderStyle}
vertical={true}
min={0}
max={500}
value={this.state.data.intensityRange}
onChange={this.dataChange('intensityRange', "intensity-range").bind(this)}
/>
</div>
<div className="relative">
{this.renderCommand("pointSizeCommand")
}
Expand Down
102 changes: 27 additions & 75 deletions imports/editor/3d/SseEditor3d.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ export default class SseEditor3d extends React.Component {
this.pixelProjection = new Map();
this.highlightedIndex = undefined;
this.dataManager = new SseDataManager();

this.MinIntensity = 0;
this.MaxIntensity = 100000;

this.tweenDuration = 500;

Expand Down Expand Up @@ -496,9 +493,7 @@ export default class SseEditor3d extends React.Component {
this.invalidateColor();
}));

// Intensity-Flag
this.onMsg("intensity-toggle", () => this.toggleIntensity());
this.onMsg("intensity-range", (arg) => this.intensityRange(arg.value));
this.onMsg("rgb-toggle", () => this.toggleRgbDisplay());
}

componentWillUnmount(){
Expand Down Expand Up @@ -907,42 +902,27 @@ export default class SseEditor3d extends React.Component {

paintScene() {
if (this.cloudData) {
if(this.DisplayIntensity && this.compression === 'ascii'){
if (this.displayRgb && this.rgbArray.length > 0) {
this.cloudData.forEach((pt, idx) => {
//TODO
var _v = (this.intensityArray[idx]-this.MinIntensity)/(this.MaxIntensity-this.MinIntensity)
_v = _v < 0 ? 0 : _v
_v = _v > 1 ? 1 : _v
var rgb = this.Map2Color(_v)
this.setColor(idx, {red: rgb[0], green: rgb[1], blue: rgb[2]});
var rgb = this.rgbArray[idx];
this.setColor(idx, {red: rgb[0] / 255, green: rgb[1] / 255, blue: rgb[2] / 255});
});
this.colorIsDirty = false;
this.cloudObject.geometry.attributes.color.needsUpdate = true;
}
else if(this.DisplayIntensity && this.compression === 'binary'){
} else {
this.cloudData.forEach((pt, idx) => {
//TODO
var rgb = this.rgbArray[idx];
this.setColor(idx, {red: rgb[0]/255, green: rgb[1]/255, blue: rgb[2]/255});
if (this.selection.has(idx)) {
this.setColor(idx, {red: 1, green: 0});
} else if (this.grayIndices.has(idx)) {
this.setColor(idx, {red: .5, green: 0.5, blue: 0.5});
} else {
this.setColor(idx,
this.classesDescriptors.byIndex[pt.classIndex]);
}
});
this.colorIsDirty = false;
this.cloudObject.geometry.attributes.color.needsUpdate = true;
}
else{
this.cloudData.forEach((pt, idx) => {
if (this.selection.has(idx)) {
this.setColor(idx, {red: 1, green: 0});
} else if (this.grayIndices.has(idx)) {
this.setColor(idx, {red: .5, green: 0.5, blue: 0.5});
}
else {
this.setColor(idx,
this.classesDescriptors.byIndex[pt.classIndex]);
}
});
this.colorIsDirty = false;
this.cloudObject.geometry.attributes.color.needsUpdate = true;
}
}
}

Expand Down Expand Up @@ -1297,14 +1277,14 @@ export default class SseEditor3d extends React.Component {
this.meta.rotationY = ry || 0;
this.meta.rotationZ = rz || 0;
this.cloudGeometry.rotateX(this.meta.rotationX).rotateY(this.meta.rotationY).rotateZ(this.meta.rotationZ);
this.display(this.objects, this.positionArray, this.labelArray, this.intensityArray, this.compression, this.rgbArray);
this.display(this.objects, this.positionArray, this.labelArray, this.rgbArray);
this.saveMeta();
}

resetRotation() {
const {rotationX, rotationY, rotationZ} = this.meta;
this.cloudGeometry.rotateZ(-rotationZ || 0).rotateY(-rotationY || 0).rotateX(-rotationX || 0);
this.display(undefined, this.positionArray, this.labelArray, this.intensityArray, this.compression, this.rgbArray);
this.display(undefined, this.positionArray, this.labelArray, this.rgbArray);
this.meta.rotationX = this.meta.rotationY = this.meta.rotationZ = 0;
this.updateGlobalBox();
this.invalidatePosition();
Expand Down Expand Up @@ -1917,34 +1897,18 @@ export default class SseEditor3d extends React.Component {
getPixel(o) {
return this.pixelProjection.get(o);
}

Map2Color(value){
var r = (1-value)*1.0 + value*1.0
var g = (1-value)*0.0 + value*1.0
var b = (1-value)*0.0 + value*0.0
// console.log(r, g, b);
return [r,g,b]
}

toggleIntensity(){
toggleRgbDisplay(){
// Adapt Our Color
this.invalidateColor()
if(this.DisplayIntensity){
this.DisplayIntensity = false;
if(this.displayRgb){
this.displayRgb = false;
}else{
this.DisplayIntensity = true;
this.displayRgb = true;
}
}

intensityRange(range){
this.MinIntensity = range[0]
this.MaxIntensity = range[1]
if(this.DisplayIntensity){
this.invalidateColor()
}
}

display(objectArray, positionArray, labelArray, intensityArray, compression, rgbArray) {
display(objectArray, positionArray, labelArray, rgbArray) {
return new Promise( (res, rej)=> {
this.scene.remove(this.cloudObject);
const geometry = this.geometry = new THREE.BufferGeometry();
Expand All @@ -1954,8 +1918,7 @@ export default class SseEditor3d extends React.Component {
this.objects = new Set(objectArray);
this.buildPointToObjectMap();
this.labelArray = labelArray;
this.intensityArray = intensityArray;
this.compression = compression;

this.rgbArray = rgbArray;

positionArray.forEach((v, i) => {
Expand All @@ -1973,20 +1936,7 @@ export default class SseEditor3d extends React.Component {
}
});
const colorArray = [];
if (this.DisplayIntensity && this.compression === 'ascii'){
// Here we can insert our color-scheme
if (intensityArray) {
intensityArray.forEach((v, i) => {
//this.cloudData[i].classIndex = v;
var _v = (v - this.MinIntensity)/(this.MaxIntensity-this.MinIntensity)
_v = _v < 0 ? 0 : _v
_v = _v > 1 ? 1 : _v
const rgb = this.Map2Color(_v)
colorArray.push(rgb[0], rgb[1], rgb[2]);
});
}
}
else if(this.DisplayIntensity && this.compression === 'binary'){
if(this.displayRgb){
if (rgbArray) {
rgbArray.forEach((v, i) => {
//this.cloudData[i].classIndex = v;
Expand Down Expand Up @@ -2053,7 +2003,7 @@ export default class SseEditor3d extends React.Component {
return new Promise((res) => {
loader.load(fileUrl, (arg) => {

this.display(arg.object, arg.position, arg.label, arg.intensity, arg.header.data, arg.rgb);
this.display(arg.object, arg.position, arg.label, arg.rgb);
Object.assign(this.meta, {header: arg.header});
res();
});
Expand All @@ -2080,14 +2030,16 @@ export default class SseEditor3d extends React.Component {
}

initDone(){

this.setupTools();
this.setupLight();
this.animate();
this.orbiter.activate();
setTimeout(this.resizeCanvas.bind(this), 100); // Global layout can take some time...

$("#waiting").addClass("display-none");
if (this.rgbArray.length > 0){
this.sendMsg("show-rgb-toggle");
}
}

start() {
Expand Down Expand Up @@ -2123,7 +2075,7 @@ export default class SseEditor3d extends React.Component {
this.dataManager.loadBinaryFile(this.props.imageUrl + ".objects").then(result => {
if (!result.forEach)
result = undefined;
this.display(result, this.positionArray, this.labelArray, this.intensityArray, this.compression, this.rgbArray).then( ()=>{
this.display(result, this.positionArray, this.labelArray, this.rgbArray).then( ()=>{
this.initDone();
});
}, () => {
Expand Down
13 changes: 1 addition & 12 deletions imports/editor/3d/SsePCDLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,10 @@ export default class SsePCDLoader {
var position = [];
var color = [];
var label = [];
var intensity = [];
var payload = [];
var rgb = [];

if (PCDheader.data === 'ascii') {
console.log("ASCII PCD");
const meta = PCDheader;

let camPosition = new THREE.Vector3(parseFloat(meta.viewpoint.tx), parseFloat(meta.viewpoint.ty),
Expand Down Expand Up @@ -196,10 +194,6 @@ export default class SsePCDLoader {
color.push(0);
color.push(0);

// Push Intensity
var _intensity = parseFloat(line[offset.intensity]) || 0;
//item.intensity = _intensity
intensity.push(_intensity);
}
}

Expand All @@ -208,7 +202,6 @@ export default class SsePCDLoader {
// binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB
// that requires a totally different parsing approach compared to non-compressed data
if ( PCDheader.data === 'binary_compressed' ) {
console.log("BINARY PCD COMPRESSED");
var dataview = new DataView( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
var compressedSize = dataview.getUint32( 0, true );
var decompressedSize = dataview.getUint32( 4, true );
Expand Down Expand Up @@ -307,8 +300,6 @@ export default class SsePCDLoader {
}

// Initialize colors
// rgb.push(dataview.getUint8( row + offset.rgb, true));
// rgb.push(dataview.getUint8( row + offset.rgb + 1, true));
var colorRGB = dataview.getUint32( row + offset.rgb, true);
var r = (colorRGB >> 16) & 0x0000ff;
var g = (colorRGB >> 8) & 0x0000ff;
Expand Down Expand Up @@ -345,9 +336,7 @@ export default class SsePCDLoader {
name = /([^\/]*)/.exec(name);
name = name[1].split('').reverse().join('');
mesh.name = url;

return {position, label, intensity, header: PCDheader, rgb};

return {position, label, header: PCDheader, rgb};
}

};
Expand Down
Loading

0 comments on commit 5bc6b92

Please sign in to comment.