Compare commits

...

10 Commits

Author SHA1 Message Date
Lukas Droste
6a7f7a2aff added Medium und High Scan Data 2021-09-16 17:26:06 +02:00
Lukas Droste
23fc1e8abd ScanData 2021-09-05 16:45:49 +02:00
Lukas Droste
5c98948af5 cleanup Code 2021-09-05 11:31:09 +02:00
Tim Wundenberg
c2ac30ac2c disable test data 2021-09-02 15:56:11 +02:00
Tim Wundenberg
816bd51287 fix ScanConverterService 2021-09-02 15:55:53 +02:00
Tim Wundenberg
051ea83a26 read registration output 2021-08-26 19:35:24 +02:00
Tim Wundenberg
5c48abfbd0 run registration in wsl 2021-08-23 09:49:50 +02:00
Tim Wundenberg
acda8b9eac implement registration
try running simple wsl-command fails
add Ply-File export
change build-target to x64 (for wsl)
2021-08-22 18:58:46 +02:00
Tim Wundenberg
4c131c103b add TEASER++ sample 2021-08-22 18:58:46 +02:00
Lukas Droste
90ad989244 Cleanup and ignore null distance 2021-08-21 15:38:57 +02:00
33 changed files with 18445 additions and 38974 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ node_modules/
*.csproj.user
.idea
/temp/
build/

View File

@@ -1,2 +0,0 @@
x,y,z
143, 1307, 2
1 x y z
2 143 1307 2

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
double degreeY = 0;
String rxData = ""; //Empfangen
CheapStepper stepper (8,9,10,11);
void setup() {
@@ -11,7 +10,6 @@ void setup() {
}
void loop() {
stepper.run();
// send data only when you receive data:
if (Serial.available() > 0) {
@@ -37,17 +35,11 @@ void loop() {
}
String moveMotor(double y){
if(y < degreeY){
stepper.newMoveDegrees (true, calculateMove(y)); //true = im Uhrzeigersinn drehen
//motor.step((int)calculateStepps(degreeY - y), BACKWARD, INTERLEAVE); //"interleave" means that it alternates between single and double to get twice the resolution (but of course its half the speed)
//motor.release(); // Strom sparen und Überhitzung des Controllers vorbeugen!
}
else{
stepper.newMoveDegrees (false, calculateMove(y)); //false = gegen Uhrzeigersinn drehen
//motor.step((int)calculateStepps(y - degreeY), FORWARD, INTERLEAVE);
//motor.release();
}
degreeY = y;
return "<move><" + (String)y + ">";

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
y, x, z

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
1,0.109375001,1315.000057
1 1 0.109375001 1315.000057

View File

@@ -0,0 +1,390 @@
from serial import Serial
from time import sleep
from math import atan,pi,floor
from enum import Enum
name = "PyLidar3"
class FrequencyStep(Enum):
oneTenthHertz=1
oneHertz=2
class YdLidarX4:
"""Deals with X4 version of Ydlidar from http://www.ydlidar.com/"""
def __init__(self,port,chunk_size=6000,no_value=0):
"""Initialize the connection and set port and baudrate."""
self._port = port
self._baudrate = 128000
self._is_scanning = False
self._is_connected = False
self.chunk_size=chunk_size
self._no_value = no_value
def Connect(self):
"""Begin serial connection with Lidar by opening serial port.\nReturn success status True/False.\n"""
try:
if(not self._is_connected):
self._s=Serial(self._port, self._baudrate)
self._is_connected = True
sleep(3)
self._s.reset_input_buffer()
if("YdLidarX4" in str(type(self))):
self._Stop_motor()
if(self.GetHealthStatus()):
return True
else:
raise Exception('Device status error.Try reconnecting device')
else:
raise Exception("Already connected")
except Exception as e:
print(e)
return False
def _Start_motor(self):
self._s.setDTR(1)
sleep(0.5)
def _Stop_motor(self):
self._s.setDTR(0)
sleep(0.5)
@classmethod
def _AngleCorr(cls,dist):
if dist==0:
return 0
else:
return (atan(21.8*((155.3-dist)/(155.3*dist)))*(180/pi))
@classmethod
def _HexArrToDec(cls,data):
littleEndianVal = 0
for i in range(0,len(data)):
littleEndianVal = littleEndianVal+(data[i]*(256**i))
return littleEndianVal
@classmethod
def _Calculate(cls,d):
ddict=[]
LSN=d[1]
Angle_fsa = ((YdLidarX4._HexArrToDec((d[2],d[3]))>>1)/64.0)#+YdLidarX4._AngleCorr(YdLidarX4._HexArrToDec((d[8],d[9]))/4)
Angle_lsa = ((YdLidarX4._HexArrToDec((d[4],d[5]))>>1)/64.0)#+YdLidarX4._AngleCorr(YdLidarX4._HexArrToDec((d[LSN+6],d[LSN+7]))/4)
if Angle_fsa<Angle_lsa:
Angle_diff = Angle_lsa-Angle_fsa
else:
Angle_diff = 360+Angle_lsa-Angle_fsa
for i in range(0,2*LSN,2):
# Distance calculation
dist_i = YdLidarX4._HexArrToDec((d[8+i],d[8+i+1]))/4
# Ignore zero values, they result in massive noise when
# computing mean of distances for each angle.
if dist_i == 0:
continue
# Intermediate angle solution
Angle_i_tmp = ((Angle_diff/float(LSN-1))*(i/2))+Angle_fsa
# Angle correction
Angle_i_tmp += YdLidarX4._AngleCorr(dist_i)
if Angle_i_tmp > 360:
Angle_i = Angle_i_tmp-360
elif Angle_i_tmp < 0:
Angle_i = Angle_i_tmp+360
else:
Angle_i = Angle_i_tmp
ddict.append((dist_i,Angle_i))
return ddict
@classmethod
def _CheckSum(cls,data):
try:
ocs = YdLidarX4._HexArrToDec((data[6],data[7]))
LSN = data[1]
cs = 0x55AA^YdLidarX4._HexArrToDec((data[0],data[1]))^YdLidarX4._HexArrToDec((data[2],data[3]))^YdLidarX4._HexArrToDec((data[4],data[5]))
for i in range(0,2*LSN,2):
cs = cs^YdLidarX4._HexArrToDec((data[8+i],data[8+i+1]))
if(cs==ocs):
return True
else:
return False
except Exception as e:
return False
@classmethod
def _Mean(cls,data):
if(len(data)>0):
return int(sum(data)/len(data))
return 0
def StartScanning(self):
"""Begin the lidar and returns a generator which returns a dictionary consisting angle(degrees) and distance(meters).\nReturn Format : {angle(1):distance, angle(2):distance,....................,angle(360):distance}."""
if(self._is_connected):
if(not self._is_scanning):
self._is_scanning = True
self._s.reset_input_buffer()
if("YdLidarX4" in str(type(self))):
self._Start_motor()
self._s.write(b"\xA5\x60")
sleep(0.5)
self._s.read(7)
distdict = {}
countdict = {}
lastChunk = None
while self._is_scanning == True:
for i in range(0,360):
distdict.update({i:[]})
data = self._s.read(self.chunk_size).split(b"\xaa\x55")
if lastChunk is not None:
data[0] = lastChunk + data[0]
lastChunk = data.pop()
for e in data:
try:
if(e[0]==0):
if(YdLidarX4._CheckSum(e)):
d = YdLidarX4._Calculate(e)
for ele in d:
angle = floor(ele[1])
if(angle>=0 and angle<360):
distdict[angle].append(ele[0])
except Exception as e:
pass
for i in distdict.keys():
if len(distdict[i]) > 0:
distdict[i]=self._Mean(distdict[i])
else:
distdict[i]=self._no_value
yield distdict
else:
raise Exception("Device is currently in scanning mode.")
else:
raise Exception("Device is not connected")
def StopScanning(self):
"""Stops scanning but keeps serial connection alive."""
if(self._is_connected):
if(self._is_scanning):
self._is_scanning = False
self._s.write(b"\xA5\x65")
sleep(1)
self._s.reset_input_buffer()
if("YdLidarX4" in str(type(self))):
self._Stop_motor()
else:
raise Exception("Device is not set to scanning mode")
else:
raise Exception("Device is not connected")
def GetHealthStatus(self):
"""Returns Health status of lidar\nTrue: good\nFalse: Not good"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.reset_input_buffer()
sleep(0.5)
self._s.write(b"\xA5\x91")
sleep(0.5)
data = self._s.read(10)
if data[9]==0 and data[8]==0 and (data[7]==0 or data[7]==1):
return True
else:
return False
else:
raise Exception("Device is not connected")
def GetDeviceInfo(self):
"""Return device information of lidar in form of dictonary\n{"model_number":model_number,"firmware_version":firmware_version,"hardware_version":hardware_version,"serial_number":serial_number}"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.reset_input_buffer()
sleep(0.5)
self._s.write(b"\xA5\x90")
sleep(0.5)
data = self._s.read(27)
model_number = str(data[7])
firmware_version = str(data[9])+"."+str(data[8])
hardware_version = str(data[10])
serial_number = ""
for i in range(11,20):
serial_number = serial_number+str(data[i])
return {"model_number":model_number,"firmware_version":firmware_version,"hardware_version":hardware_version,"serial_number":serial_number}
else:
raise Exception("Device is not connected")
def Reset(self):
"""Reboots the Lidar."""
if(self._is_connected):
self._s.write(b"\xA5\x80")
sleep(0.5)
self.Disconnect()
self.Connect()
else:
raise Exception("Device is not connected")
def Disconnect(self):
"""Stop scanning and close serial communication with Lidar."""
if(self._is_connected):
if(self._is_scanning == True):
self.StopScanning()
self._s.close()
self._is_connected=False
else:
raise Exception("Device is not connected")
class YdLidarG4(YdLidarX4):
"""Deals with G4 version of Ydlidar from http://www.ydlidar.com/"""
def __init__(self,port,chunk_size=6000):
"""Initialize the connection and set port and baudrate."""
YdLidarX4.__init__(self,port,chunk_size)
self._baudrate= 230400
def EnableLowPowerMode(self):
"""Enable Low Power Consumption Mode(Turn motor and distance-measuring unit off in StopScanning)\n"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.write(b"\xA5\x01")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def DisableLowPowerMode(self):
"""Disable Low Power Consumption Mode(Turn motor and distance-measuring unit on StopScanning)\n"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.write(b"\xA5\x02")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def GetLowPowerModeStatus(self):
"""Return True if Low Power Consumption Mode is Enable else return False"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.reset_input_buffer()
sleep(0.5)
self._s.write(b"\xA5\x05")
sleep(1)
data = self._s.read(8)
if(data[7]!=1):
return True
return False
else:
raise Exception("Device is not connected")
def IncreaseCurrentFrequency(self,frequencyStep):
"""Increase current frequency by oneTenth or one depends on enum FrequencyStep"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
if(frequencyStep.value==1):
self._s.write(b"\xA5\x09")
elif(frequencyStep.value==2):
self._s.write(b"\xA5\x0B")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def DecreaseCurrentFrequency(self,frequencyStep):
"""Decrease current frequency by oneTenth or one depends on enum FrequencyStep"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
if(frequencyStep.value==1):
self._s.write(b"\xA5\x0A")
elif(frequencyStep.value==2):
self._s.write(b"\xA5\x0C")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def GetCurrentFrequency(self):
"""Returns current frequency in hertz"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.reset_input_buffer()
sleep(0.5)
self._s.write(b"\xA5\x0D")
sleep(0.5)
data = self._s.read(11)
if(data[0]==165):
return (self._HexArrToDec(data[-4:]))/100.0
else:
return (self._HexArrToDec(data[:4]))/100.0
else:
raise Exception("Device is not connected")
def EnableConstantFrequency(self):
"""Enables constant frequency \n default:Enable"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.write(b"\xA5\x0E")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def DisableConstantFrequency(self):
"""Disable constant frequency \n default:Enable"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.write(b"\xA5\x0F")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def SwitchRangingFrequency(self):
"""Switch between ranging frequencies 4khz, 8khz and 9khz.\ndefault:9khz"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.write(b"\xA5\xD0")
while(self._s.inWaiting()==0):
sleep(0.5)
self._s.reset_input_buffer()
else:
raise Exception("Device is not connected")
def GetCurrentRangingFrequency(self):
"""Returns current Ranging Frequency in khz"""
if(self._is_connected):
if self._is_scanning == True:
self.StopScanning()
self._s.reset_input_buffer()
sleep(0.5)
self._s.write(b"\xA5\xD1")
sleep(1)
data = self._s.read(8)
if(data[-1]==0):
return 4
elif(data[-1]==1):
return 8
elif(data[-1]==2):
return 9
else:
raise Exception("Device is not connected")
def Disconnect(self):
"""Stop scanning and close serial communication with Lidar."""
if(self._is_connected):
if(self._is_scanning == True):
self.StopScanning()
if(self.GetLowPowerModeStatus()==False):
self.EnableLowPowerMode()
sleep(2)
self._s.close()
self._is_connected=False
else:
raise Exception("Device is not connected")

View File

@@ -9,11 +9,10 @@ import collections
import requests
import uuid
# f = open("PointCloudWeb.Scanner\datafile.txt","wt")
# f.write("y, x, z\n")
f = open("PointCloudWeb.Scanner\datafile.txt","wt")
arduino_status = False
arduino_port = "COM9"
arduino_port = "COM10"
arduino_baud = 9600
arduino = None
lidar_status = False
@@ -52,59 +51,61 @@ async def init():
else:
ws_message_queue.appendleft("<connection>false")
def arduino_write_read(x):
arduino.write(bytes(x, 'utf-8'))
def setY(y):
arduino.write(bytes("<set><"+str(y)+">", 'utf-8'))
data1 = arduino.readline()
return filterY(str(data1))
def setY(y):
tmp = arduino_write_read("<set><"+str(y)+">")
def filterY(data):
temp = data[data.find("<"):data.find(">")]
return temp + data[data.find("><"):data.find(">", data.find("><")+2)+1]
def senddata(data,posy):
def sendData(data,posy):
global scan_id
temp ="{\"Id\": \""+scan_id+"\",\"ScanPoints\":["
for x,y in data.items():
temp += ("{\"RAY\":" + str(posy) + ",\"RAX\":" + str(x) + ",\"DistanceMM\":" + str(y) + "},")
# f.write("{\"RAY\":" + str(posy) + ",\"RAX\":" + str(x) + ",\"DistanceMM\":" + str(y) + "},")
if y != 0:
temp += ("{\"RAY\":" + str(posy) + ",\"RAX\":" + str(x) + ",\"DistanceMM\":" + str(y) + "},")
#f.write("{\"RAY\":" + str(posy) + ",\"RAX\":" + str(x) + ",\"DistanceMM\":" + str(y) + "},")
l = len(temp)
temp = temp[:l-1] + "]}"
f.write(temp)
r = requests.put(url='http://localhost:35588/scandata', data=temp, headers={'content-type': 'application/json'})
#print(r.status_code)
def startScaner(mode):
global scan_progress, lidar, stop_scan, scan_id
if lidar_status == True:
def startScanner(mode):
global scan_progress, lidar, stop_scan, scan_id, arduino_status
if lidar_status == True and arduino_status == True:
ws_message_queue.appendleft(str(lidar.GetDeviceInfo()))
scan_id = str(uuid.uuid4())
ws_message_queue.appendleft("Scan ID: " + scan_id)
gen = lidar.StartScanning()
#gen = lidar.StartScanning()
if mode == "0":
print("Mode 0")
ws_message_queue.appendleft("<scan>running")
for y in range(1):
for y in range(19):
if(stop_scan == True):
break
print("send data")
senddata(next(gen),y*10)
gen = lidar.StartScanning()
sendData(next(gen),y*10)
lidar.StopScanning()
time.sleep(2)
setY( y*10)
setY(y*10)
time.sleep(2)
scan_progress = round(y/18*100)
ws_message_queue.appendleft("<progress>" + str(scan_progress))
r = requests.put(url='http://localhost:35588/scandata/finished/'+scan_id)
setY(0)
lidar.StopScanning()
#lidar.StopScanning()
elif mode == "1":
ws_message_queue.appendleft("<scan>running")
for y in range(91):
if(stop_scan == True):
break
senddata(next(gen),y*2)
gen = lidar.StartScanning()
sendData(next(gen),y*2)
lidar.StopScanning()
time.sleep(1)
setY(y*2)
time.sleep(1)
@@ -112,13 +113,15 @@ def startScaner(mode):
ws_message_queue.appendleft("<progress>" + str(scan_progress))
r = requests.put(url='http://localhost:35588/scandata/finished/'+scan_id)
setY(0)
lidar.StopScanning()
#lidar.StopScanning()
elif mode == "2":
ws_message_queue.appendleft("<scan>running")
for y in range(361):
if(stop_scan == True):
break
senddata(next(gen),y*0.5)
gen = lidar.StartScanning()
sendData(next(gen),y*0.5)
lidar.StopScanning()
time.sleep(1)
setY(y*0.5)
time.sleep(1)
@@ -126,10 +129,10 @@ def startScaner(mode):
ws_message_queue.appendleft("<progress>" + str(scan_progress))
r = requests.put(url='http://localhost:35588/scandata/finished/'+scan_id)
setY(0)
lidar.StopScanning()
#lidar.StopScanning()
else:
ws_message_queue.appendleft("mode error")
#f.close()
f.close()
if(stop_scan == True):
stop_scan = False
ws_message_queue.appendleft("<scan>canceld")
@@ -150,21 +153,21 @@ async def wsaction(command, value):
if command == "start":
if value == "0":
ws_message_queue.appendleft("start scan on low resolution")
x = threading.Thread(target=startScaner, args=(value))
x = threading.Thread(target=startScanner, args=(value))
x.start()
elif value =="1":
ws_message_queue.appendleft("start scan on medium resolution")
x = threading.Thread(target=startScaner, args=(value))
x = threading.Thread(target=startScanner, args=(value))
x.start()
elif value =="2":
ws_message_queue.appendleft("start scan on high resolution")
x = threading.Thread(target=startScaner, args=(value))
x = threading.Thread(target=startScanner, args=(value))
x.start()
else:
ws_message_queue.appendleft("mode error")
elif command == "connect" and arduino and lidar != None:
ws_message_queue.appendleft("try to connect to Adruino and LIDAR")
await init()
# elif command == "connect" and arduino and lidar != None:
# ws_message_queue.appendleft("try to connect to Adruino and LIDAR")
# await init()
elif command == "status":
ws_message_queue.appendleft("progress: " + scan_progress)
elif command == "stop":

View File

@@ -1,21 +1,23 @@
import PyLidar3
import lidar3
import time # Time module
#Serial port to which lidar connected, Get it from device manager windows
#In linux type in terminal -- ls /dev/tty*
#port = input("Enter port name which lidar is connected:") #windows
#port = "/dev/ttyUSB0" #linux
f = open("PoinCloudWeb.Scanner\datafile.txt","wt")
Obj = PyLidar3.YdLidarX4(port='COM6',chunk_size=20000) #PyLidar3.your_version_of_lidar(port,chunk_size)
#f = open("PoinCloudWeb.Scanner\datafile.txt","wt")
Obj = lidar3.YdLidarX4(port='COM6',chunk_size=20000) #PyLidar3.your_version_of_lidar(port,chunk_size)
if(Obj.Connect()):
# gen = Obj.StartScanning()
# t = time.time() # start time
# data = next(gen)
# #print(data)
# for x,y in data.items():
# f.write(str(x) + " / " + str(y) + "\n")
# f.close()
# Obj.StopScanning()
Obj.Reset
print(Obj.GetDeviceInfo())
gen = Obj.StartScanning()
t = time.time() # start time
data = next(gen)
#print(data)
for x,y in data.items():
f.write(str(x) + " / " + str(y) + "\n")
f.close()
Obj.StopScanning()
print(Obj.GetHealthStatus())
Obj.Disconnect()
else:
print("Error connecting to device")

View File

@@ -5,6 +5,14 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PointCloudWeb.Server\PointCloudWeb.Server.csproj" />
</ItemGroup>

View File

@@ -6,6 +6,14 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
namespace PointCloudWeb.Server
{
@@ -12,12 +13,24 @@ namespace PointCloudWeb.Server
TempPath = basePath + "/temp";
CloudCompareExe = "C:/Program Files/CloudCompare/CloudCompare.exe";
LasToolsTxt2Las = basePath + "/PointCloudWeb.Server/Tools/LAStools/txt2las.exe";
TeaserPp = basePath + "/PointCloudWeb.Server/Tools/TEASERpp/build/teaser_cpp_ply";
}
public static string LasToolsTxt2Las { get; }
public static string LasToolsTxt2Las { get; }
public static string PotreeDataPath { get; }
public static string PotreeConverterExe { get; }
public static string TempPath { get; }
public static string CloudCompareExe { get; }
public static string TeaserPp { get; }
public static string ToWslPath(string path)
{
path = path.Replace("\\", "/");
if (path.StartsWith("C:"))
return path.Replace("C:", "/mnt/c");
throw new NotImplementedException();
}
}
}

View File

@@ -63,12 +63,20 @@ namespace PointCloudWeb.Server.Models
public string ToStringXyz()
{
var stringBuilder = new StringBuilder();
foreach (var point in Points)
// var maxPoints = 5_000;
var takeEvery = 1;//Points.Count / maxPoints;
for (var i = 0; i < Points.Count; i++)
{
if (i % takeEvery != 0)
continue;
var point = Points[i];
stringBuilder.AppendLine(string.Join(',',
(point.X).ToString(CultureInfo.InvariantCulture),
(point.Y).ToString(CultureInfo.InvariantCulture),
(point.Z).ToString(CultureInfo.InvariantCulture)
point.X.ToString(CultureInfo.InvariantCulture),
point.Y.ToString(CultureInfo.InvariantCulture),
point.Z.ToString(CultureInfo.InvariantCulture)
)
);
}
@@ -76,12 +84,57 @@ namespace PointCloudWeb.Server.Models
return stringBuilder.ToString();
}
private string ToStringPly(int maxPoints)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("ply\n");
stringBuilder.Append("format ascii 1.0\n");
stringBuilder.Append("element vertex COUNT_PLACEHOLDER\n");
stringBuilder.Append("property float x\n");
stringBuilder.Append("property float y\n");
stringBuilder.Append("property float z\n");
stringBuilder.Append("end_header\n");
if (maxPoints == 0)
maxPoints = Points.Count;
var takeEvery = Points.Count / maxPoints;
if (takeEvery < 1)
takeEvery = 1;
var count = 0;
for (var i = 0; i < Points.Count; i++)
{
if (i % takeEvery != 0)
continue;
count += 1;
var point = Points[i];
stringBuilder.Append(string.Join(' ',
point.X,
point.Y,
point.Z
) + "\n"
);
}
stringBuilder.Replace("COUNT_PLACEHOLDER", count.ToString());
return stringBuilder.ToString();
}
// ReSharper disable once MemberCanBePrivate.Global
public void WriteToXyz(string fileName)
{
File.WriteAllText(fileName, ToStringXyz());
}
// ReSharper disable once MemberCanBePrivate.Global
public void WriteToPly(string fileName, int maxPoints)
{
File.WriteAllText(fileName, ToStringPly(maxPoints));
}
public void WriteToLasCloudCompare(string fileName)
{
var fileNameXyz = Path.ChangeExtension(fileName, ".xyz");

View File

@@ -4,6 +4,14 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
</ItemGroup>

View File

@@ -3,8 +3,19 @@ using System.Numerics;
namespace PointCloudWeb.Server.Services
{
public interface IPointCloudRegistationService
public class RegistrationResult
{
public Matrix4x4 RegisterPointCloud(PointCloud source, PointCloud target);
public RegistrationResult()
{
Transformation = Vector3.Zero;
Rotation = Vector3.Zero;
}
public Vector3 Transformation { get; set; }
public Vector3 Rotation { get; set; }
}
public interface IPointCloudRegistrationService
{
public RegistrationResult RegisterPointCloud(PointCloud source, PointCloud target);
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using PointCloudWeb.Server.Models;
using PointCloudWeb.Server.Utils;
// ReSharper disable PossibleMultipleEnumeration
namespace PointCloudWeb.Server.Services
{
public class PointCloudRegistrationServiceTeaerPp : IPointCloudRegistrationService
{
public RegistrationResult RegisterPointCloud(PointCloud source, PointCloud target)
{
var sourceFileName = Globals.TempPath + $"/{source.Id}.ply";
var targetFileName = Globals.TempPath + $"/{target.Id}.ply";
var outputFileName = Globals.TempPath + $"/{target.Id}.txt";
var maxPoints = 5_000;
source.WriteToPly(sourceFileName, maxPoints);
target.WriteToPly(targetFileName, maxPoints);
var teaserPp = new Process();
teaserPp.StartInfo.FileName = "wsl";
teaserPp.StartInfo.Arguments =
$"{Globals.ToWslPath(Globals.TeaserPp)} " +
$"\"{Globals.ToWslPath(outputFileName)}\" " +
$"\"{Globals.ToWslPath(sourceFileName)}\" " +
$"\"{Globals.ToWslPath(targetFileName)}\" ";
teaserPp.Start();
teaserPp.WaitForExit();
//Console.WriteLine($"RegistrationExitCode: {teaserPp.ExitCode}");
var result = new RegistrationResult();
// if (teaserPp.ExitCode != 0)
// return result;
var lines = File.ReadAllLines(outputFileName).ToList();
while (lines.Any(s => s.Contains(" ")))
lines = lines.Select(s =>
{
var newString = s.Replace(" ", " ");
if (newString.Length > 0 && newString[0] == ' ')
newString = newString.Remove(0, 1);
return newString;
}).ToList();
var rotationIndex = lines.FindIndex(s => s.Contains("Estimated rotation:"));
var translationIndex = lines.FindIndex(s => s.Contains("Estimated translation:"));
var ci = CultureInfo.InvariantCulture;
var rotationMatrix = Matrix4x4.Identity;
rotationMatrix.M11 = float.Parse(lines[rotationIndex + 1].Split(' ')[0], ci);
rotationMatrix.M12 = float.Parse(lines[rotationIndex + 1].Split(' ')[1], ci);
rotationMatrix.M13 = float.Parse(lines[rotationIndex + 1].Split(' ')[2], ci);
rotationMatrix.M21 = float.Parse(lines[rotationIndex + 2].Split(' ')[0], ci);
rotationMatrix.M22 = float.Parse(lines[rotationIndex + 2].Split(' ')[1], ci);
rotationMatrix.M23 = float.Parse(lines[rotationIndex + 2].Split(' ')[2], ci);
rotationMatrix.M31 = float.Parse(lines[rotationIndex + 3].Split(' ')[0], ci);
rotationMatrix.M32 = float.Parse(lines[rotationIndex + 3].Split(' ')[1], ci);
rotationMatrix.M33 = float.Parse(lines[rotationIndex + 3].Split(' ')[2], ci);
result.Rotation = MatrixUtils.RotationMatrixToAngles(rotationMatrix);
var transformation = Vector3.Zero;
transformation.X = float.Parse(lines[translationIndex + 1], ci);
transformation.Y = float.Parse(lines[translationIndex + 2], ci);
transformation.Z = float.Parse(lines[translationIndex + 3], ci);
result.Transformation = transformation;
return result;
}
}
}

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using PointCloudWeb.Server.Models;
using PointCloudWeb.Server.Utils;
@@ -10,13 +9,13 @@ namespace PointCloudWeb.Server.Services
{
public class PointCloudService
{
//private readonly IPointCloudRegistrationService pointCloudRegistration;
private readonly IPointCloudRegistrationService _pointCloudRegistration;
private readonly PointCloudCollection _pointClouds;
public PointCloudService( /*IPointCloudRegistrationService pointCloudRegistration*/)
public PointCloudService( IPointCloudRegistrationService pointCloudRegistration)
{
_pointClouds = new PointCloudCollection();
//this.pointCloudRegistration = pointCloudRegistration;
_pointCloudRegistration = pointCloudRegistration;
InitSampleData();
}
@@ -78,17 +77,18 @@ namespace PointCloudWeb.Server.Services
{
RaiseIfNotExists(id);
var pc = GetById(id);
var source = GetById(id);
pc.Transformation = Vector3.Zero;
pc.Rotation = Vector3.Zero;
//the first can't be registered
if (_pointClouds.IndexOf(source) == 0)
return;
// //the first can't be registered
// if (_pointClouds.IndexOf(pointCloud) == 0)
// return;
var target = _pointClouds[_pointClouds.IndexOf(source) - 1];
//var transformation = pointCloudRegistration.RegisterPointCloud(pointCloud, pointClouds[0]);
//pointCloud.Transformation = transformation;
var result = _pointCloudRegistration.RegisterPointCloud(source, target);
source.Rotation = result.Rotation;
source.Transformation = result.Transformation;
}
public void RegisterPointClouds()
@@ -104,8 +104,8 @@ namespace PointCloudWeb.Server.Services
public void PointCloudCompleted(Guid id)
{
RegisterPointCloud(id);
GeneratePotreeData(id);
RegisterPointCloud(id);
}
}
}

View File

@@ -7,13 +7,13 @@ namespace PointCloudWeb.Server.Services
{
public static Point Transform(ScanDataPoint scan)
{
var alpha = scan.RAX * Math.PI / 180;
var alpha = (90 - scan.RAX) * Math.PI / 180;
var beta = scan.RAY * Math.PI / 180;
var p = new Point(
y: (int)(scan.DistanceMM * Math.Sin(alpha)),
z: (int)(scan.DistanceMM * Math.Sin(alpha)),
x: (int)(scan.DistanceMM * Math.Cos(beta) * Math.Cos(alpha)),
z: (int)(scan.DistanceMM * Math.Sin(beta) * Math.Cos(alpha))
y: (int)(scan.DistanceMM * Math.Sin(beta) * Math.Cos(alpha))
);
return p;
}

View File

@@ -51,6 +51,7 @@ namespace PointCloudWeb.Server
services.AddSingleton<PointCloudService>();
services.AddTransient<ScanConverterService>();
services.AddTransient<ScanDataService>();
services.AddTransient<IPointCloudRegistrationService, PointCloudRegistrationServiceTeaerPp>();
services.AddControllers();
}
}

View File

@@ -37,13 +37,13 @@ namespace PointCloudWeb.Server.Utils
public static void CreateData(PointCloudService pointCloudService)
{
var pc = pointCloudService.AddPointCloud();
LoadPointCloudFromEthFile(pc, "ETH-Data/Hokuyo_0.csv");
pointCloudService.PointCloudCompleted(pc.Id);
pc = pointCloudService.AddPointCloud();
LoadPointCloudFromEthFile(pc, "ETH-Data/Hokuyo_1.csv");
pointCloudService.PointCloudCompleted(pc.Id);
// var pc = pointCloudService.AddPointCloud(new Guid("c4b9b7fc-0b97-4f52-ad1b-737aeca5ba97"));
// LoadPointCloudFromEthFile(pc, "ETH-Data/Hokuyo_0.csv");
// pointCloudService.PointCloudCompleted(pc.Id);
//
// pc = pointCloudService.AddPointCloud(new Guid("c620b175-ace8-42e5-bf29-55b6c99372bc"));
// LoadPointCloudFromEthFile(pc, "ETH-Data/Hokuyo_1.csv");
// pointCloudService.PointCloudCompleted(pc.Id);
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Numerics;
namespace PointCloudWeb.Server.Utils
{
public class MatrixUtils
{
public static Vector3 RotationMatrixToAngles(Matrix4x4 m)
{
var result = Vector3.Zero;
result.X = (float)Math.Atan2(m.M32, m.M33);
result.Y = (float)Math.Atan2(-m.M31, Math.Sqrt(m.M32 * m.M32 + m.M33 * m.M33));
result.Z = (float)Math.Atan2(m.M21, m.M11);
return result;
}
}
}

View File

@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.10)
project(teaser_cpp_ply)
set(CMAKE_CXX_STANDARD 17)
find_package(Eigen3 REQUIRED)
find_package(teaserpp REQUIRED)
# Change this line to include your own executable file
add_executable(teaser_cpp_ply teaser_cpp_ply.cc)
# Link to teaserpp & Eigen3
target_link_libraries(teaser_cpp_ply Eigen3::Eigen teaserpp::teaser_registration teaserpp::teaser_io)
# Copy the data files to build directory
file(COPY ../example_data/
DESTINATION ./example_data/
FILES_MATCHING
PATTERN *.ply)

View File

@@ -0,0 +1,3 @@
`bun_zipper.ply` is taken from the [Stanford 3D Scanning Repository](https://graphics.stanford.edu/data/3Dscanrep/).
In the `3dmatch_sample/` folder, a pair of scan from the `sun3d-home_at-home_at_scan1_2013_jan_1` scene in the 3DMatch dataset is provided. Descriptors calculated by using the `32_dim` pretrained network from [3DSmoothNet](https://github.com/zgojcic/3DSmoothNet) is also provided, together with the ground truth transformation.

View File

@@ -0,0 +1,101 @@
// An example showing TEASER++ registration with the Stanford bunny model
#include <chrono>
#include <iostream>
#include <fstream>
#include <random>
#include <filesystem>
#include <Eigen/Core>
#include <teaser/ply_io.h>
#include <teaser/registration.h>
// Macro constants for generating noise and outliers
#define NOISE_BOUND 0.05
// #define N_OUTLIERS 1700
// #define OUTLIER_TRANSLATION_LB 5
// #define OUTLIER_TRANSLATION_UB 10
inline double getAngularError(Eigen::Matrix3d R_exp, Eigen::Matrix3d R_est)
{
return std::abs(std::acos(fmin(fmax(((R_exp.transpose() * R_est).trace() - 1) / 2, -1.0), 1.0)));
}
int main(int argc, char **argv)
{
std::ofstream out(argv[1]);
std::streambuf *coutbuf = std::cout.rdbuf(); //save old buf
std::cout.rdbuf(out.rdbuf()); //redirect std::cout to out.txt!
std::cout << "You have entered " << argc
<< " arguments:"
<< "\n";
for (int i = 0; i < argc; ++i)
std::cout << argv[i] << "\n";
auto src_fileName = argv[2];
auto tgt_fileName = argv[3];
// Load the .ply file
teaser::PLYReader reader;
teaser::PointCloud src_cloud;
auto status = reader.read(src_fileName, src_cloud);
int src_cloud_size = src_cloud.size();
// Convert the point cloud to Eigen
Eigen::Matrix<double, 3, Eigen::Dynamic> src(3, src_cloud_size);
for (size_t i = 0; i < src_cloud_size; ++i)
{
src.col(i) << src_cloud[i].x, src_cloud[i].y, src_cloud[i].z;
}
teaser::PointCloud tgt_cloud;
status = reader.read(tgt_fileName, tgt_cloud);
int tgt_cloud_size = tgt_cloud.size();
// Convert the point cloud to Eigen
Eigen::Matrix<double, 3, Eigen::Dynamic> tgt(3, tgt_cloud_size);
for (size_t i = 0; i < tgt_cloud_size; ++i)
{
tgt.col(i) << tgt_cloud[i].x, tgt_cloud[i].y, tgt_cloud[i].z;
}
// Run TEASER++ registration
// Prepare solver parameters
teaser::RobustRegistrationSolver::Params params;
params.noise_bound = NOISE_BOUND;
params.cbar2 = 1;
params.estimate_scaling = false;
params.rotation_max_iterations = 100;
params.rotation_gnc_factor = 1.4;
params.rotation_estimation_algorithm =
teaser::RobustRegistrationSolver::ROTATION_ESTIMATION_ALGORITHM::GNC_TLS;
params.rotation_cost_threshold = 0.005;
// Solve with TEASER++
teaser::RobustRegistrationSolver solver(params);
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
solver.solve(src, tgt);
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
auto solution = solver.getSolution();
// Compare results
std::cout << "=====================================" << std::endl;
std::cout << " TEASER++ Results " << std::endl;
std::cout << "=====================================" << std::endl;
std::cout << "Estimated rotation: " << std::endl;
std::cout << solution.rotation << std::endl;
std::cout << std::endl;
std::cout << "Estimated translation: " << std::endl;
std::cout << solution.translation << std::endl;
std::cout << std::endl;
std::cout << "Time taken (s): "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() /
1000000.0
<< std::endl;
//reset to standard output again, otherwise an error occurs
std::cout.rdbuf(coutbuf);
}

View File

@@ -22,7 +22,6 @@
</div>
</ul>
</div>
<!--<button v-on:click="connection_status = !connection_status" >test</button>-->
<button v-on:click="progress = 0, logs = []" >clear logs</button>
</div>
</template>
@@ -71,7 +70,6 @@ export default {
if(message.search("<") != -1){
that.command = message.substr(message.search("<")+1, message.search(">")-1)
that.value = message.substr(message.search(">")+1)
//console.log("command: " + that.command + " / value: " + that.value)
this.action(that.command, that.value)
}
else{
@@ -143,19 +141,17 @@ li {
.progressbar {
background-color: grey;
border-radius: 7px;
/* (height of inner div) / 2 + padding */
padding: 3px;
margin: auto;
}
.progressbar>div {
/* Adjust with JavaScript */
height: 20px;
border-radius: 4px;
}
.button {
background-color: #4CAF50; /* Green */
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
@@ -173,6 +169,6 @@ li {
.button2 {background-color: #00ba9b;}
.button3 {background-color: #008cff}
.button4 {background-color: #f44336;} /* Red */
.button4 {background-color: #f44336;}
</style>