Added the ability to choose output types and the ability to show what points came from the left laser versus the right

This commit is contained in:
Uriah Liggett
2015-02-22 18:52:40 +00:00
parent 834232c02c
commit 0824e1a7bc
21 changed files with 724 additions and 318 deletions

159
config/freelss Executable file
View File

@@ -0,0 +1,159 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: freelss
# Required-Start: $remote_fs $syslog $named $network $time
# Required-Stop: $remote_fs $syslog $named $network $time
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Example initscript
# Description: This file should be used to construct scripts to be
# placed in /etc/init.d.
### END INIT INFO
# Author: Foo Bar <foobar@baz.org>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
DESC="ATLAS 3D"
NAME=freelss
DAEMON=/usr/local/bin/$NAME
DAEMON_ARGS="--options args"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_ARGS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
return 0
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
#reload|force-reload)
#
# If do_reload() is not implemented then leave this commented out
# and leave 'force-reload' as an alias for 'restart'.
#
#log_daemon_msg "Reloading $DESC" "$NAME"
#do_reload
#log_end_msg $?
#;;
restart|force-reload)
#
# If the "reload" option is implemented then remove the
# 'force-reload' alias
#
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View File

@@ -85,8 +85,7 @@ bool Calibrator::detectLaserX(real * laserX, PixelLocation& topLocation, PixelLo
bool detected = false;
ImageProcessor imageProcessor;
int firstRowLaserCol = 0;
int numSuspectedBadLaserLocations = 0;
int numImageProcessingRetries = 0;
real percentPixelsOverThreshold = 0;
int numLocations = 0;
int maxNumLocations = camera->getImageHeight();
PixelLocation * laserLocations = new PixelLocation[maxNumLocations];
@@ -99,8 +98,7 @@ bool Calibrator::detectLaserX(real * laserX, PixelLocation& topLocation, PixelLo
laserLocations,
maxNumLocations,
firstRowLaserCol,
numSuspectedBadLaserLocations,
numImageProcessingRetries,
percentPixelsOverThreshold,
NULL);
//

View File

@@ -230,6 +230,20 @@ static void SavePreset(RequestInfo * reqInfo)
preset->framesPerRevolution = ToInt(framesPerRevolution.c_str());
}
preset->generatePly = !reqInfo->arguments[WebContent::GENERATE_PLY].empty();
preset->generateStl = !reqInfo->arguments[WebContent::GENERATE_STL].empty();
preset->generateXyz = !reqInfo->arguments[WebContent::GENERATE_XYZ].empty();
if (reqInfo->arguments[WebContent::SEPARATE_LASERS_BY_COLOR].empty())
{
preset->laserMergeAction = Preset::LMA_PREFER_RIGHT_LASER;
}
else
{
preset->laserMergeAction = Preset::LMA_SEPARATE_BY_COLOR;
preset->generatePly = true;
}
/** Save the properties */
SaveProperties();
}

View File

@@ -38,7 +38,6 @@ namespace freelss
const real ImageProcessor::RED_HUE_LOWER_THRESHOLD = 30;
const real ImageProcessor::RED_HUE_UPPER_THRESHOLD = 329;
const int ImageProcessor::NUM_LASER_RANGE_THRESHOLD = 3;
const unsigned ImageProcessor::RANGE_DISTANCE_THRESHOLD = 5;
struct GaussPrivate
@@ -116,22 +115,7 @@ ImageProcessor::~ImageProcessor()
}
int ImageProcessor::process(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations,
int maxNumLocations, int& firstRowLaserCol, int & numSuspectedBadLaserLocations, int & numImageProcessingRetries, const char * debuggingCsvFile)
{
int numBad = 0;
int numLocations = 0;
numLocations = subProcess(before, after, debuggingImage, laserLocations, maxNumLocations, m_laserMagnitudeThreshold, firstRowLaserCol, numBad, debuggingCsvFile);
numSuspectedBadLaserLocations += numBad;
return numLocations;
}
// We want to detect a red, white, to red transition in the image
int ImageProcessor::subProcess(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations,
int maxNumLocations, real laserThreshold, int& firstRowLaserCol, int & numSuspectedBadLaserLocations,
const char * debuggingCsvFile)
int maxNumLocations, int& firstRowLaserCol, real & percentPixelsOverThreshold, const char * debuggingCsvFile)
{
const real MAX_MAGNITUDE_SQ = 255 * 255 * 3; // The maximum pixel magnitude sq we can see
const real INV_MAX_MAGNITUDE_SQ = 1.0f / MAX_MAGNITUDE_SQ;
@@ -153,14 +137,15 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
rowOut.open(debuggingCsvFile, std::ios::out);
}
unsigned width = before.getWidth();
unsigned height = before.getHeight();
const unsigned width = before.getWidth();
const unsigned height = before.getHeight();
unsigned components = before.getNumComponents();
unsigned rowStep = width * components;
int numLocations = 0;
int numMerged = 0;
int numPixelsOverThreshold = 0;
// The location that we last detected a laser line
int prevLaserCol = firstRowLaserCol;
@@ -180,7 +165,7 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
for (unsigned iCol = 0; iCol < rowStep; iCol += components)
{
// Perform image subtraction
#if 1
#if 0
const int r = (int)br[iCol + 0] - (int)ar[iCol + 0];
const int magSq = r * r;
real mag = 255.0f * (magSq * 0.000015379f);
@@ -193,7 +178,7 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
#endif
if (writeDebugImage)
{
if (mag > laserThreshold)
if (mag > m_laserMagnitudeThreshold)
{
dr[iCol + 0] = mag;
dr[iCol + 1] = mag;
@@ -208,8 +193,11 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
}
// Compare it against the threshold
if (mag > laserThreshold)
if (mag > m_laserMagnitudeThreshold)
{
// Flag that this pixel was over the threshold value
numPixelsOverThreshold++;
// The start of pixels with laser in them
if (m_laserRanges[numLaserRanges].startCol == -1)
{
@@ -292,11 +280,6 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
numLocations++;
// Detect if we think this may have been a bad detection
if (numLaserRanges > NUM_LASER_RANGE_THRESHOLD)
{
numSuspectedBadLaserLocations++;
}
}
// Increment to the next row
@@ -319,6 +302,9 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
rowOut.close();
}
// Compute the number of pixels over the threshold
percentPixelsOverThreshold = 100.0f * numPixelsOverThreshold / (width * height);
return numLocations;
}

View File

@@ -38,11 +38,11 @@ public:
* This can be helpful when debugging laser detection issues.
* @param laserLocations - Output variable to store the laser locations.
* @param maxNumLocations - The maximum number of locations to store in @p laserLocations.
* @param thresholdFactor - Scales the laser threshold by this amount. default = 1.0
* @param percentPixelsOverThreshold - The percentage of pixels that were over the threshold amount.
* @return Returns the number of locations written to @p laserLocations.
*/
int process(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations, int maxNumLocations,
int& firstRowLaserCol, int & numSuspectedBadLaserLocations, int & numImageProcessingRetries, const char * debuggingCsvFile);
int& firstRowLaserCol, real & percentPixelsOverThreshold, const char * debuggingCsvFile);
private:
@@ -58,14 +58,10 @@ private:
real detectLaserRangeCenter(const ImageProcessor::LaserRange& range, unsigned char * ar, unsigned char * br);
int subProcess(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations,
int maxNumLocations, real laserThreshold, int& firstRowLaserCol, int & numSuspectedBadLaserLocations, const char * debuggingCsvFile);
/** Converts the RGB color to HSV */
static void toHsv(unsigned char r, unsigned char g, unsigned char b, Hsv * hsv);
static const real RED_HUE_LOWER_THRESHOLD;
static const real RED_HUE_UPPER_THRESHOLD;
static const int NUM_LASER_RANGE_THRESHOLD;
static const unsigned RANGE_DISTANCE_THRESHOLD;
/** The LaserRanges for each column */

View File

@@ -51,8 +51,15 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
std::vector<NeutralFileRecord> & rightLaserResults,
int numFramesPerRevolution,
int numFramesBetweenLaserPlanes,
int maxPointY)
int maxPointY,
Preset::LaserMergeAction mergeAction)
{
// Sanity check
if (mergeAction != Preset::LMA_PREFER_RIGHT_LASER && mergeAction != Preset::LMA_SEPARATE_BY_COLOR)
{
throw Exception("Unsupported Laser Merge Action");
}
// Sanity check
if (leftLaserResults.empty() && rightLaserResults.empty())
{
@@ -92,9 +99,14 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
{
NeutralFileRecord& right = rightLaserResults[iRight];
right.pseudoFrame = right.frame;
//right.point.r = 0;
//right.point.g = 0;
//right.point.b = 0;
// Make the Right laser black
if (mergeAction == Preset::LMA_SEPARATE_BY_COLOR)
{
right.point.r = 0;
right.point.g = 0;
right.point.b = 0;
}
}
for (size_t iLeft = 0; iLeft < leftLaserResults.size(); iLeft++)
@@ -110,9 +122,13 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
left.pseudoFrame -= m_numFramesPerRevolution;
}
//left.point.r = 255;
//left.point.g = 0;
//left.point.b = 0;
// Make the Left laser red
if (mergeAction == Preset::LMA_SEPARATE_BY_COLOR)
{
left.point.r = 255;
left.point.g = 0;
left.point.b = 0;
}
}
int numCulledPoints = 0;
@@ -141,12 +157,15 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
}
else
{
/*
left.point.r = 175;
left.point.g = 175;
left.point.b = 175;
out.push_back(left);
*/
// Make the culled left laser results gray
if (mergeAction == Preset::LMA_SEPARATE_BY_COLOR)
{
left.point.r = 175;
left.point.g = 175;
left.point.b = 175;
out.push_back(left);
}
numCulledPoints++;
}
}

View File

@@ -20,6 +20,8 @@
#pragma once
#include "Preset.h"
namespace freelss
{
@@ -39,7 +41,8 @@ public:
std::vector<NeutralFileRecord> & rightLaserResults,
int numFramesPerRevolution,
int numFramesBetweenLaserPlanes,
int maxPointY);
int maxPointY,
Preset::LaserMergeAction mergeAction);
private:

View File

@@ -28,6 +28,12 @@
#include "PresetManager.h"
#include "PropertyReaderWriter.h"
#include "Setup.h"
#include <algorithm>
static bool SortRecordByRow(const freelss::NeutralFileRecord& a, const freelss::NeutralFileRecord& b)
{
return a.pixel.y < b.pixel.y;
}
void ReleaseSingletons()
{
@@ -53,6 +59,17 @@ int main(int argc, char **argv)
{
int retVal = 0;
pid_t pid = fork();
if (pid < 0)
{
std::cerr << "Error forking process!!!" << std::endl;
return 1;
}
else if (pid != 0)
{
return 0;
}
// Initialize the Raspberry Pi hardware
bcm_host_init();
@@ -74,10 +91,9 @@ int main(int argc, char **argv)
while (true)
{
freelss::Thread::usleep(100000);
freelss::Thread::usleep(500000); // Sleep 500ms
}
ReleaseSingletons();
}
catch (freelss::Exception& ex)
@@ -120,6 +136,124 @@ time_t ScanResult::getScanDate() const
return files.front().creationTime;
}
bool NeutralFileRecord::readNextFrame(std::vector<NeutralFileRecord>& out, const std::vector<NeutralFileRecord>& results, size_t & resultIndex)
{
out.clear();
if (resultIndex >= results.size() || results.empty())
{
return false;
}
int pseudoStep = results[resultIndex].pseudoFrame;
while (pseudoStep == results[resultIndex].pseudoFrame && resultIndex < results.size())
{
out.push_back(results[resultIndex]);
resultIndex++;
}
return true;
}
void NeutralFileRecord::computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out)
{
out = bin.front();
real32 invSize = 1.0f / bin.size();
real32 rotation = 0;
real32 pixelLocationX = 0;
real32 pixelLocationY = 0;
real32 ptX = 0;
real32 ptY = 0;
real32 ptZ = 0;
real32 ptR = 0;
real32 ptG = 0;
real32 ptB = 0;
for (size_t iBin = 0; iBin < bin.size(); iBin++)
{
const NeutralFileRecord& br = bin[iBin];
rotation += invSize * br.rotation;
pixelLocationX += invSize * br.pixel.x;
pixelLocationY += invSize * br.pixel.y;
ptX += invSize * br.point.x;
ptY += invSize * br.point.y;
ptZ += invSize * br.point.z;
ptR += invSize * br.point.r;
ptG += invSize * br.point.g;
ptB += invSize * br.point.b;
}
out.rotation = rotation;
out.pixel.x = pixelLocationX; // TODO: We should probably round these values
out.pixel.y = pixelLocationY;
out.point.x = ptX;
out.point.y = ptY;
out.point.z = ptZ;
out.point.r = ptR;
out.point.g = ptG;
out.point.b = ptB;
}
void NeutralFileRecord::lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins)
{
output.clear();
// Sanity check
if (frame.empty())
{
return;
}
// Sort by image row
std::sort(frame.begin(), frame.end(), SortRecordByRow);
unsigned binSize = maxNumRows / numRowBins;
// Holds the current contents of the bin
std::vector<NeutralFileRecord> bin;
unsigned nextBinY = frame.front().pixel.y + binSize;
// unsigned bin = frame.front().pixel.y / numRowBins;
for (size_t iFr = 0; iFr < frame.size(); iFr++)
{
NeutralFileRecord& record = frame[iFr];
if (record.pixel.y < nextBinY)
{
bin.push_back(record);
}
else
{
// Average the bin results and add it to the output
if (!bin.empty())
{
NeutralFileRecord out;
computeAverage(bin, out);
output.push_back(out);
bin.clear();
}
nextBinY = record.pixel.y + binSize;
bin.push_back(record);
}
}
// Process any results still left in the bin
if (!bin.empty())
{
NeutralFileRecord out;
computeAverage(bin, out);
output.push_back(out);
bin.clear();
}
}
void LoadProperties()
{
PropertyReaderWriter reader;
@@ -196,6 +330,11 @@ std::string ToString(int value)
return sstr.str();
}
std::string ToString(bool value)
{
return value ? "1" : "0";
}
real ToReal(const std::string& str)
{
return atof(str.c_str());
@@ -206,6 +345,11 @@ int ToInt(const std::string& str)
return atoi(str.c_str());
}
bool ToBool(const std::string& str)
{
return str == "1" || str == "true";
}
bool EndsWith(const std::string& str, const std::string& ending)
{
if (str.length() >= ending.length())

View File

@@ -206,6 +206,21 @@ struct Ray
struct NeutralFileRecord
{
/** Populates frameC with the next frame from the results vector starting at index resultIndex */
static bool readNextFrame(std::vector<NeutralFileRecord>& out, const std::vector<NeutralFileRecord>& results, size_t & resultIndex);
/**
* Reduce the number of result rows and filter out some of the noise
* @param maxNumRows - The number of rows in the image the produced the frame.
* @param numRowBins - The total number of row bins in the entire image, not necessarily what is returned by this function.
*/
static void lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins);
/**
* Computes the average of all the records in the bin.
*/
static void computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out);
PixelLocation pixel;
ColoredPoint point;
real rotation;
@@ -232,8 +247,10 @@ double GetTimeInSeconds();
std::string ToString(real value);
std::string ToString(int value);
std::string ToString(bool value);
real ToReal(const std::string& str);
int ToInt(const std::string& str);
bool ToBool(const std::string& str);
bool EndsWith(const std::string& str, const std::string& ending);
void HtmlEncode(std::string& data);
void LoadProperties();

View File

@@ -13,7 +13,8 @@ OBJECTS=WebContent.o PropertyReaderWriter.o Main.o \
NeutralFileWriter.o NeutralFileReader.o RaspicamCamera.o \
Camera.o ScanResultsWriter.o MmalStillCamera.o \
MmalVideoCamera.o PixelLocationWriter.o StlWriter.o TurnTable.o \
Laser.o Preset.o PresetManager.o Setup.o LaserResultsMerger.o
Laser.o Preset.o PresetManager.o Setup.o LaserResultsMerger.o\
XyzWriter.o
all: freelss
@@ -113,10 +114,15 @@ Setup.o: Setup.cpp Setup.h Main.h
LaserResultsMerger.o: LaserResultsMerger.cpp LaserResultsMerger.h Main.h
$(CC) -c $(CFLAGS) LaserResultsMerger.cpp
XyzWriter.o: XyzWriter.cpp XyzWriter.h Main.h
$(CC) -c $(CFLAGS) XyzWriter.cpp
github:
mkdir -p ../../github
mkdir -p ../../github/src
cp -f *.h *.cpp Makefile ../../github/src
mkdir -p ../../github/config
cp ../config/freelss ../../github/config/
cp -f *.h *.cpp Makefile ../../github/src/
run: freelss
sudo ./freelss
@@ -127,5 +133,10 @@ dist: clean
install: freelss
sudo cp -f freelss /usr/local/bin/freelss
startup: install
sudo cp ../config/freelss /etc/init.d/freelss
sudo update-rc.d freelss remove
sudo update-rc.d freelss defaults
clean:
rm -f *.o *.gch *~ freelss

View File

@@ -29,7 +29,7 @@ Preset::Preset() :
name("Preset"),
laserSide(Laser::RIGHT_LASER),
cameraMode(Camera::CM_VIDEO_1P2MP),
laserThreshold(3),
laserThreshold(10),
minLaserWidth(3),
maxLaserWidth(40),
maxObjectSize(215.9), // 8.5"
@@ -38,7 +38,11 @@ Preset::Preset() :
laserDelay(180000),
stabilityDelay(0),
id(-1),
framesPerRevolution(800)
framesPerRevolution(800),
generateXyz (false),
generateStl(true),
generatePly(true),
laserMergeAction (LMA_PREFER_RIGHT_LASER)
{
// Do nothing
}
@@ -57,6 +61,10 @@ void Preset::encodeProperties(std::vector<Property>& properties, bool isActivePr
properties.push_back(Property("presets." + name + ".laserDelay", ToString(laserDelay)));
properties.push_back(Property("presets." + name + ".stabilityDelay", ToString(stabilityDelay)));
properties.push_back(Property("presets." + name + ".framesPerRevolution", ToString(framesPerRevolution)));
properties.push_back(Property("presets." + name + ".generateXyz", ToString(generateXyz)));
properties.push_back(Property("presets." + name + ".generateStl", ToString(generateStl)));
properties.push_back(Property("presets." + name + ".generatePly", ToString(generatePly)));
properties.push_back(Property("presets." + name + ".laserMergeAction", ToString((int)laserMergeAction)));
if (isActivePreset)
{
@@ -124,6 +132,22 @@ void Preset::decodeProperties(const std::vector<Property>& properties, const std
{
framesPerRevolution = ToInt(prop.value);
}
else if (EndsWith(prop.name, name + ".generateXyz"))
{
generateXyz = ToBool(prop.value);
}
else if (EndsWith(prop.name, name + ".generateStl"))
{
generateStl = ToBool(prop.value);
}
else if (EndsWith(prop.name, name + ".generatePly"))
{
generatePly = ToBool(prop.value);
}
else if (EndsWith(prop.name, name + ".laserMergeAction"))
{
laserMergeAction = (LaserMergeAction)ToInt(prop.value);
}
}
}
}

View File

@@ -33,6 +33,10 @@ namespace freelss
class Preset
{
public:
/** The action that should be taken to merge laser results */
enum LaserMergeAction {LMA_PREFER_RIGHT_LASER, LMA_SEPARATE_BY_COLOR };
Preset();
/** Encodes property information to the properties vector */
@@ -59,6 +63,10 @@ public:
int stabilityDelay;
int id;
int framesPerRevolution;
bool generateXyz;
bool generateStl;
bool generatePly;
LaserMergeAction laserMergeAction;
};
}

View File

@@ -29,6 +29,7 @@
#include "PixelLocationWriter.h"
#include "NeutralFileReader.h"
#include "StlWriter.h"
#include "XyzWriter.h"
#include "LaserResultsMerger.h"
namespace freelss
@@ -63,9 +64,8 @@ Scanner::Scanner() :
m_filename(""),
m_progress(0.0),
m_status(),
m_maxNumScanTries(3), // TODO: Place this in Database
m_badLaserLocationThreshold(15), // TODO: Place this in Database
m_numSuspectedBadLaserLocations(0),
m_maxNumFrameRetries(5), // TODO: Place this in Database
m_maxPercentPixelsOverThreshold(3), // TODO: Place this in Database
m_columnPoints(NULL),
m_startTimeSec(0),
m_remainingTime(0),
@@ -202,8 +202,6 @@ void Scanner::run()
m_filename = sstr.str();
m_numSuspectedBadLaserLocations = 0;
m_firstRowRightLaserCol = m_camera->getImageWidth() * 0.5;
m_firstRowLeftLaserCol = m_camera->getImageWidth() * 0.5;
@@ -213,7 +211,7 @@ void Scanner::run()
double time1 = 0;
Setup * setup = Setup::get();
Preset& preset = PresetManager::get()->getActivePreset();
Preset preset = PresetManager::get()->getActivePreset();
// Read the laser selection
m_laserSelection = preset.laserSide;
@@ -315,14 +313,6 @@ void Scanner::run()
numFrames = ceil(rangeRadians / frameRadians);
// Start the output thread
m_scanResultsWriter.setBaseFilePath(m_filename);
std::cout << "Starting output thread..." << std::endl;
time1 = GetTimeInSeconds();
m_scanResultsWriter.execute();
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
std::cout << "Output thread started" << std::endl;
m_numFramesBetweenLaserPlanes = m_radiansBetweenLaserPlanes / frameRadians;
std::cout << "Angle between laser planes: " << RADIANS_TO_DEGREES(m_radiansBetweenLaserPlanes)
@@ -379,7 +369,6 @@ void Scanner::run()
}
m_rangeFout.close();
std::cout << m_numSuspectedBadLaserLocations << " laser locations are suspected as being bad." << std::endl;
m_turnTable->setMotorEnabled(false);
@@ -397,7 +386,7 @@ void Scanner::run()
std::vector<NeutralFileRecord> results;
LaserResultsMerger merger;
merger.merge(results, leftLaserResults, rightLaserResults, maxFramesPerRevolution,
m_numFramesBetweenLaserPlanes, Camera::getInstance()->getImageHeight());
m_numFramesBetweenLaserPlanes, Camera::getInstance()->getImageHeight(), preset.laserMergeAction);
// Sort by pseudo-step and row
std::cout << "Sort 2... " << std::endl;
@@ -407,23 +396,46 @@ void Scanner::run()
std::cout << "Merged " << leftLaserResults.size() << " left laser and " << rightLaserResults.size() << " right laser results into " << results.size() << " results." << std::endl;
std::cout << "Constructing mesh..." << std::endl;
// Finish writing to the output files
std::cout << "Writing PLY file..." << std::endl;
time1 = GetTimeInSeconds();
for (size_t iRec = 0; iRec < results.size(); iRec++)
timingStats.laserMergeTime += GetTimeInSeconds() - time1;
// Write the PLY file
std::cout << "Starting output thread..." << std::endl;
if (preset.generatePly)
{
m_scanResultsWriter.write(results[iRec]);
std::cout << "Writing PLY file..." << std::endl;
time1 = GetTimeInSeconds();
m_scanResultsWriter.setBaseFilePath(m_filename);
m_scanResultsWriter.execute();
for (size_t iRec = 0; iRec < results.size(); iRec++)
{
m_scanResultsWriter.write(results[iRec]);
}
finishWritingToOutput();
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
}
finishWritingToOutput();
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
// Generate the XYZ file
if (preset.generateXyz)
{
std::cout << "Generating XYZ file..." << std::endl;
time1 = GetTimeInSeconds();
XyzWriter xyzWriter;
xyzWriter.write(m_filename, results);
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
}
// Mesh the contents
std::cout << "Generating STL mesh..." << std::endl;
time1 = GetTimeInSeconds();
StlWriter stlWriter;
stlWriter.write(m_filename, results, m_range > 359);
timingStats.meshBuildTime = GetTimeInSeconds() - time1;
// Generate the STL file
if (preset.generateStl)
{
std::cout << "Generating STL mesh..." << std::endl;
time1 = GetTimeInSeconds();
StlWriter stlWriter;
stlWriter.write(m_filename, results, m_range > 359);
timingStats.meshBuildTime = GetTimeInSeconds() - time1;
}
logTimingStats(timingStats);
@@ -468,35 +480,20 @@ void Scanner::generateDebugInfo(Laser::LaserSide laserSide)
std::string debuggingCsv = std::string(DEBUG_OUTPUT_DIR) + "/0.csv";
int firstRowLaserCol = m_camera->getImageWidth() * 0.5;
int numSuspectedBadLaserLocations = 0;
int numImageProcessingRetries = 0;
real percentPixelsOverThreshold = 0;
int numLocations = m_imageProcessor.process(m_image1,
m_image2,
&debuggingImage,
m_laserLocations,
m_maxNumLocations,
firstRowLaserCol,
numSuspectedBadLaserLocations,
numImageProcessingRetries,
percentPixelsOverThreshold,
debuggingCsv.c_str());
std::string baseFilename = std::string(DEBUG_OUTPUT_DIR) + "/";
// Write the laser off image
//Image::writeJpeg(m_image1, baseFilename + "1.jpg");
// Write the laser on image
//Image::writeJpeg(m_image2, baseFilename + "2.jpg");
PixelLocationWriter locWriter;
// Write the difference image
//locWriter.writeImage(debuggingImage, debuggingImage.getWidth(), debuggingImage.getHeight(), baseFilename + "3.png");
// Write the pixel image
//locWriter.writePixels(m_laserLocations, numLocations, m_image1.getWidth(), m_image1.getHeight(), baseFilename + "4.png");
// Overlay the pixels onto the debug image and write that as a new image
PixelLocationWriter locWriter;
Image::overlayPixels(debuggingImage, m_laserLocations, numLocations);
locWriter.writeImage(debuggingImage, debuggingImage.getWidth(), debuggingImage.getHeight(), baseFilename + "5.png");
@@ -534,52 +531,80 @@ void Scanner::singleScan(std::vector<NeutralFileRecord> & leftLaserResults, std:
// Scan with the Right laser
if (m_laserSelection == Laser::RIGHT_LASER || m_laserSelection == Laser::ALL_LASERS)
{
// Turn on the right laser
time1 = GetTimeInSeconds();
m_laser->turnOn(Laser::RIGHT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
bool goodResult = false;
// Take a picture with the right laser on
time1 = GetTimeInSeconds();
acquireImage(&m_image2);
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
for (int iTry = 0; iTry < m_maxNumFrameRetries && ! goodResult; iTry++)
{
// Turn on the right laser
time1 = GetTimeInSeconds();
m_laser->turnOn(Laser::RIGHT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
// Turn off the right laser
time1 = GetTimeInSeconds();
m_laser->turnOff(Laser::RIGHT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
// Take a picture with the right laser on
time1 = GetTimeInSeconds();
acquireImage(&m_image2);
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
// Process the right laser results
processScan(rightLaserResults, frame, rotation, rightLocMapper, Laser::RIGHT_LASER, m_firstRowRightLaserCol, timingStats);
// Turn off the right laser
time1 = GetTimeInSeconds();
m_laser->turnOff(Laser::RIGHT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
// Process the right laser results
goodResult = processScan(rightLaserResults, frame, rotation, rightLocMapper, Laser::RIGHT_LASER, m_firstRowRightLaserCol, timingStats);
// If it didn't succeed, take the first image again
if (!goodResult)
{
// Take a picture with the laser off
time1 = GetTimeInSeconds();
acquireImage(&m_image1);
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
}
}
}
// Scan with the Left laser
if (m_laserSelection == Laser::LEFT_LASER || m_laserSelection == Laser::ALL_LASERS)
{
// Turn on the left laser
time1 = GetTimeInSeconds();
m_laser->turnOn(Laser::LEFT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
bool goodResult = false;
// Take a picture with the left laser on
time1 = GetTimeInSeconds();
acquireImage(&m_image2);
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
for (int iTry = 0; iTry < m_maxNumFrameRetries && ! goodResult; iTry++)
{
// Turn on the left laser
time1 = GetTimeInSeconds();
m_laser->turnOn(Laser::LEFT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
// Turn off the left laser
time1 = GetTimeInSeconds();
m_laser->turnOff(Laser::LEFT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
// Take a picture with the left laser on
time1 = GetTimeInSeconds();
acquireImage(&m_image2);
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
// Process the left laser results
processScan(leftLaserResults, frame, rotation, leftLocMapper, Laser::LEFT_LASER, m_firstRowLeftLaserCol, timingStats);
// Turn off the left laser
time1 = GetTimeInSeconds();
m_laser->turnOff(Laser::LEFT_LASER);
timingStats->laserTime += GetTimeInSeconds() - time1;
// Process the left laser results
goodResult = processScan(leftLaserResults, frame, rotation, leftLocMapper, Laser::LEFT_LASER, m_firstRowLeftLaserCol, timingStats);
// If it didn't succeed, take the first image again
if (!goodResult)
{
// Take a picture with the laser off
time1 = GetTimeInSeconds();
acquireImage(&m_image1);
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
}
}
}
}
void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats)
bool Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats)
{
int numLocationsMapped = 0;
int numSuspectedBadLaserLocations = 0;
real percentPixelsOverThreshold = 0;
// Send the pictures off for processing
double time1 = GetTimeInSeconds();
@@ -589,13 +614,23 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
m_laserLocations,
m_maxNumLocations,
firstRowLaserCol,
numSuspectedBadLaserLocations,
timingStats->numImageProcessingRetries,
percentPixelsOverThreshold,
NULL);
timingStats->imageProcessingTime += GetTimeInSeconds() - time1;
// If we had major problems with this frame, try it again
std::cout << "percentPixelsOverThreshold: " << percentPixelsOverThreshold << std::endl;
if (percentPixelsOverThreshold > m_maxPercentPixelsOverThreshold)
{
std::cout << "!! Many bad laser locations suspected, pctOverThreshold=" << percentPixelsOverThreshold
<< ", maxPct=" << m_maxPercentPixelsOverThreshold << ", rescanning..." << std::endl;
timingStats->numFrameRetries++;
return false;
}
std::cout << "Detected " << numLocations << " laser pixels." << std::endl;
// Lookup the 3D locations for the laser points
@@ -611,11 +646,6 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
{
std::cout << "Discarded " << numLocations - numLocationsMapped << " points." << std::endl;
}
if (numSuspectedBadLaserLocations > m_badLaserLocationThreshold)
{
std::cout << "!! Many bad laser locations suspected, num=" << numSuspectedBadLaserLocations << std::endl;
}
}
else
{
@@ -628,7 +658,6 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
if (numLocationsMapped > 0)
{
time1 = GetTimeInSeconds();
m_numSuspectedBadLaserLocations += numSuspectedBadLaserLocations;
if (m_writeRangeCsvEnabled)
{
@@ -654,6 +683,8 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
}
timingStats->fileWritingTime += GetTimeInSeconds() - time1;
}
return true;
}
void Scanner::rotatePoints(ColoredPoint * points, float theta, int numPoints)
@@ -739,7 +770,7 @@ void Scanner::logTimingStats(const Scanner::TimingStats& stats)
double totalTime = now - stats.startTime;
double accountedTime = stats.meshBuildTime + stats.imageAcquisitionTime + stats.imageProcessingTime + stats.pointMappingTime
+ stats.pointProcessingTime + stats.rotationTime + stats.fileWritingTime + stats.laserTime;
+ stats.pointProcessingTime + stats.rotationTime + stats.fileWritingTime + stats.laserTime + stats.laserMergeTime;
double unaccountedTime = totalTime - accountedTime;
double rate = totalTime / stats.numFrames;
@@ -752,10 +783,10 @@ void Scanner::logTimingStats(const Scanner::TimingStats& stats)
std::cout << "Point Mapping:\t" << (100.0 * stats.pointMappingTime / totalTime) << "%" << std::endl;
std::cout << "Point Rotating:\t" << (100.0 * stats.pointProcessingTime / totalTime) << "%" << std::endl;
std::cout << "Table Rotation:\t" << (100.0 * stats.rotationTime / totalTime) << "%" << std::endl;
std::cout << "Laser Merging:\t" << (100.0 * stats.laserMergeTime / totalTime) << "%" << std::endl;
std::cout << "File Writing:\t" << (100.0 * stats.fileWritingTime / totalTime) << "%" << std::endl;
std::cout << "Mesh Construction:\t" << (100.0 * stats.meshBuildTime / totalTime) << "%" << std::endl;
std::cout << "Num Scan Retries:\t" << stats.numScanRetries << std::endl;
std::cout << "Num Image Processing Retries:\t" << stats.numImageProcessingRetries << std::endl;
std::cout << "Num Frame Retries:\t" << stats.numFrameRetries << std::endl;
std::cout << "Num Frames:\t" << stats.numFrames << std::endl;
std::cout << "Total Time (min):\t" << (totalTime / 60.0) << std::endl << std::endl;
}

View File

@@ -93,8 +93,8 @@ private:
double fileWritingTime;
double meshBuildTime;
double laserTime;
int numScanRetries;
int numImageProcessingRetries;
double laserMergeTime;
int numFrameRetries;
int numFrames;
};
@@ -113,12 +113,13 @@ private:
void finishWritingToOutput();
void processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats);
/**
* Returns true if the scan was processed successfully and false if there was a problem and the frame needs to be again.
*/
bool processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats);
void writeRangePoints(ColoredPoint * points, int numLocationsMapped,Laser::LaserSide laserSide);
private:
/** Unowned objects */
Laser * m_laser;
@@ -162,14 +163,11 @@ private:
/** Protection for the running, progress, and any other status parameters */
CriticalSection m_status;
/** The maximum number of times to try a scan (at a particular rotation) before giving up */
const int m_maxNumScanTries;
/** The maximum number of times to try a frame (at a particular rotation) before giving up */
const int m_maxNumFrameRetries;
/** The threshold for number of suspected bad laser locations before a rescan is required */
const int m_badLaserLocationThreshold;
// Diagnostic info
int m_numSuspectedBadLaserLocations;
/** The threshold for percentage of pixels over the threshold */
const real m_maxPercentPixelsOverThreshold;
/** The image points for every column */
ColoredPoint * m_columnPoints;

View File

@@ -60,7 +60,7 @@ Setup::Setup() :
leftLaserLocation.y = 82.55;
leftLaserLocation.z = 260.35;
rightLaserLocation.x = 114.3;
rightLaserLocation.x = 125.73;
rightLaserLocation.y = 82.55;
rightLaserLocation.z = 260.35;
}

View File

@@ -36,11 +36,6 @@ static real32 DistanceSquared(const ColoredPoint& a, const ColoredPoint&b)
return dx * dx + dy * dy + dz * dz;
}
static bool SortRecordByRow(const NeutralFileRecord& a, const NeutralFileRecord& b)
{
return a.pixel.y < b.pixel.y;
}
StlWriter::StlWriter() :
m_maxEdgeDistMmSq(12.4 * 12.2), // TODO: Make this a setting or autodetect it based off the distance to table and the detail level
@@ -52,122 +47,6 @@ StlWriter::StlWriter() :
m_normal[2] = 0;
}
void StlWriter::computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out)
{
out = bin.front();
real32 invSize = 1.0f / bin.size();
real32 rotation = 0;
real32 pixelLocationX = 0;
real32 pixelLocationY = 0;
real32 ptX = 0;
real32 ptY = 0;
real32 ptZ = 0;
real32 ptR = 0;
real32 ptG = 0;
real32 ptB = 0;
for (size_t iBin = 0; iBin < bin.size(); iBin++)
{
const NeutralFileRecord& br = bin[iBin];
rotation += invSize * br.rotation;
pixelLocationX += invSize * br.pixel.x;
pixelLocationY += invSize * br.pixel.y;
ptX += invSize * br.point.x;
ptY += invSize * br.point.y;
ptZ += invSize * br.point.z;
ptR += invSize * br.point.r;
ptG += invSize * br.point.g;
ptB += invSize * br.point.b;
}
out.rotation = rotation;
out.pixel.x = pixelLocationX; // TODO: We should probably round these values
out.pixel.y = pixelLocationY;
out.point.x = ptX;
out.point.y = ptY;
out.point.z = ptZ;
out.point.r = ptR;
out.point.g = ptG;
out.point.b = ptB;
}
void StlWriter::lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins)
{
output.clear();
// Sanity check
if (frame.empty())
{
return;
}
// Sort by image row
std::sort(frame.begin(), frame.end(), SortRecordByRow);
unsigned binSize = maxNumRows / numRowBins;
// Holds the current contents of the bin
std::vector<NeutralFileRecord> bin;
unsigned nextBinY = frame.front().pixel.y + binSize;
// unsigned bin = frame.front().pixel.y / numRowBins;
for (size_t iFr = 0; iFr < frame.size(); iFr++)
{
NeutralFileRecord& record = frame[iFr];
if (record.pixel.y < nextBinY)
{
bin.push_back(record);
}
else
{
// Average the bin results and add it to the output
if (!bin.empty())
{
NeutralFileRecord out;
computeAverage(bin, out);
output.push_back(out);
bin.clear();
}
nextBinY = record.pixel.y + binSize;
bin.push_back(record);
}
}
// Process any results still left in the bin
if (!bin.empty())
{
NeutralFileRecord out;
computeAverage(bin, out);
output.push_back(out);
bin.clear();
}
}
bool StlWriter::readNextStep(std::vector<NeutralFileRecord>& out, const std::vector<NeutralFileRecord>& results, size_t & resultIndex)
{
out.clear();
if (resultIndex >= results.size() || results.empty())
{
return false;
}
int pseudoStep = results[resultIndex].pseudoFrame;
while (pseudoStep == results[resultIndex].pseudoFrame && resultIndex < results.size())
{
out.push_back(results[resultIndex]);
resultIndex++;
}
return true;
}
void StlWriter::write(const std::string& baseFilename, const std::vector<NeutralFileRecord>& results, bool connectLastFrameToFirst)
{
@@ -178,13 +57,6 @@ void StlWriter::write(const std::string& baseFilename, const std::vector<Neutral
throw Exception("Error opening STL file for writing: " + stlFilename);
}
std::string xyzFilename = baseFilename + ".xyz";
std::ofstream xyz (xyzFilename.c_str());
if (!xyz.is_open())
{
throw Exception("Error opening STL file for writing: " + xyzFilename);
}
uint32 maxNumRows = Camera::getInstance()->getImageHeight(); // TODO: Make this use the image height that generated the result and not the current Camera
m_imageWidth = Camera::getInstance()->getImageWidth();
uint32 numRowBins = 400; // TODO: Autodetect this or have it in Database
@@ -208,27 +80,16 @@ void StlWriter::write(const std::string& baseFilename, const std::vector<Neutral
size_t resultIndex = 0;
size_t totNumPoints = 0;
while (readNextStep(frameC, results, resultIndex))
while (NeutralFileRecord::readNextFrame(frameC, results, resultIndex))
{
// Reduce the number of result rows and filter out some of the noise
lowpassFilter(* currentFrame, frameC, maxNumRows, numRowBins);
NeutralFileRecord::lowpassFilter(* currentFrame, frameC, maxNumRows, numRowBins);
totNumPoints += currentFrame->size();
// Sort the results in decreasing Y
//std::sort(currentFrame->begin(), currentFrame->end(), SortRecordByYValue);
// Write the filtered results to the XYZ file
for (size_t iRec = 0; iRec < currentFrame->size(); iRec++)
{
const NeutralFileRecord& rec = currentFrame->at(iRec);
const ColoredPoint & pt = rec.point;
xyz << pt.x << " " << pt.y << " " << pt.z << " "
<< pt.normal.x << " " << pt.normal.y << " " << pt.normal.z
<< std::endl;
}
if (iStep > 0)
{
writeTrianglesForColumn(* currentFrame, * lastFrame, fout, numTriangles);
@@ -272,12 +133,10 @@ void StlWriter::write(const std::string& baseFilename, const std::vector<Neutral
catch (...)
{
fout.close();
xyz.close();
throw;
}
fout.close();
xyz.close();
}
void StlWriter::writeHeader(std::ofstream& fout)

View File

@@ -23,14 +23,12 @@
namespace freelss
{
class NeutralFileReader;
class StlWriter
{
public:
StlWriter();
void write(const std::string& filename, const std::vector<NeutralFileRecord>& results, bool connectLastFrameToFirst);
private:
@@ -39,18 +37,6 @@ private:
void writeTrianglesForColumn(const std::vector<NeutralFileRecord>& currentFrame, const std::vector<NeutralFileRecord>& lastFrame, std::ofstream& fout, uint32& numTriangles);
void writeTriangle(const ColoredPoint& pt1, const ColoredPoint& pt2, const ColoredPoint& pt3, bool flipNormal, std::ofstream& fout);
/**
* Reduce the number of result rows and filter out some of the noise
* @param maxNumRows - The number of rows in the image the produced the frame.
* @param numRowBins - The total number of row bins in the entire image, not necessarily what is returned by this function.
*/
void lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins);
/**
* Computes the average of all the records in the bin.
*/
void computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out);
bool isValidTriangle(const ColoredPoint& pt1, const ColoredPoint& pt2, const ColoredPoint& pt3);
/**
@@ -60,7 +46,6 @@ private:
*/
bool isInwardFacingFace(const NeutralFileRecord& p1, const NeutralFileRecord& p2, const NeutralFileRecord& p3);
bool readNextStep(std::vector<NeutralFileRecord>& frameC, const std::vector<NeutralFileRecord>& results, size_t & resultIndex);
/** The max triangle edge distance in mm sq */
real32 m_maxEdgeDistMmSq;

View File

@@ -229,6 +229,10 @@ const std::string WebContent::DIRECTION_VALUE = "DIRECTION_VALUE";
const std::string WebContent::RESPONSE_DELAY = "RESPONSE_DELAY";
const std::string WebContent::STEP_DELAY = "STEP_DELAY";
const std::string WebContent::FRAMES_PER_REVOLUTION = "FRAMES_PER_REVOLUTION";
const std::string WebContent::GENERATE_XYZ = "GENERATE_XYZ";
const std::string WebContent::GENERATE_STL = "GENERATE_STL";
const std::string WebContent::GENERATE_PLY = "GENERATE_PLY";
const std::string WebContent::SEPARATE_LASERS_BY_COLOR = "SEPARATE_LASERS_BY_COLOR";
const std::string WebContent::CAMERA_X_DESCR = "X-compoment of camera location. ie: The camera is always at X = 0.";
const std::string WebContent::CAMERA_Y_DESCR = "Y-component of camera location. ie: The distance from camera center to the XZ plane";
@@ -254,6 +258,10 @@ const std::string WebContent::STEP_DELAY_DESCR = "The amount of time between ste
const std::string WebContent::DIRECTION_PIN_DESCR = "The wiringPi pin number for the stepper motor direction or rotation";
const std::string WebContent::RESPONSE_DELAY_DESCR = "The time it takes for the stepper controller to recognize a pin value change in microseconds";
const std::string WebContent::FRAMES_PER_REVOLUTION_DESCR = "The number of frames that should be taken for a scan. Default is 800.";
const std::string WebContent::GENERATE_XYZ_DESCR = "Whether to generate an XYZ point cloud file from the scan.";
const std::string WebContent::GENERATE_STL_DESCR = "Whether to generate an STL mesh from the scan.";
const std::string WebContent::GENERATE_PLY_DESCR = "Whether to generate a PLY point clould from the scan.";
const std::string WebContent::SEPARATE_LASERS_BY_COLOR_DESCR = "Calibration debugging option to separate the results from different lasers by color (requires PLY).";
std::string WebContent::scan(const std::vector<ScanResult>& pastScans)
{
@@ -587,6 +595,10 @@ std::string WebContent::settings(const std::string& message)
sstr << setting(WebContent::STABILITY_DELAY, "Stability Delay", preset.stabilityDelay, STABILITY_DELAY_DESCR, "&mu;s");
sstr << setting(WebContent::MAX_LASER_WIDTH, "Max Laser Width", preset.maxLaserWidth, MAX_LASER_WIDTH_DESCR, "px.");
sstr << setting(WebContent::MIN_LASER_WIDTH, "Min Laser Width", preset.minLaserWidth, MIN_LASER_WIDTH_DESCR, "px.");
sstr << checkbox(WebContent::GENERATE_PLY, "Generate PLY File", preset.generatePly, GENERATE_PLY_DESCR);
sstr << checkbox(WebContent::GENERATE_STL, "Generate STL File", preset.generateStl, GENERATE_STL_DESCR);
sstr << checkbox(WebContent::GENERATE_XYZ, "Generate XYZ File", preset.generateXyz, GENERATE_XYZ_DESCR);
sstr << checkbox(WebContent::SEPARATE_LASERS_BY_COLOR, "Separate the Lasers", preset.laserMergeAction == Preset::LMA_SEPARATE_BY_COLOR, SEPARATE_LASERS_BY_COLOR_DESCR);
sstr << "</form>\
<form action=\"/settings\" method=\"POST\" enctype=\"application/x-www-form-urlencoded\">\
@@ -627,6 +639,28 @@ std::string WebContent::setting(const std::string& name, const std::string& labe
return sstr.str();
}
std::string WebContent::checkbox(const std::string& name, const std::string& label, bool checked, const std::string& description)
{
std::stringstream sstr;
sstr << "<div><div class=\"settingsText\">"
<< label
<< "</div><input class=\"settingsInput\" name=\""
<< name
<< "\" type=\"checkbox\"";
if (checked)
{
sstr << " checked";
}
sstr << "> ";
sstr << "</div><div class=\"settingsDescr\">" << description << "</div>\n";
return sstr.str();
}
std::string WebContent::setting(const std::string& name, const std::string& label,
int value, const std::string& description, const std::string& units, bool readOnly)
{

View File

@@ -70,7 +70,10 @@ public:
static const std::string RESPONSE_DELAY;
static const std::string STEP_DELAY;
static const std::string FRAMES_PER_REVOLUTION;
static const std::string GENERATE_XYZ;
static const std::string GENERATE_STL;
static const std::string GENERATE_PLY;
static const std::string SEPARATE_LASERS_BY_COLOR;
private:
static std::string setting(const std::string& name, const std::string& label,
const std::string& value, const std::string& description, const std::string& units = "", bool readOnly = false);
@@ -81,6 +84,8 @@ private:
static std::string setting(const std::string& name, const std::string& label,
real value, const std::string& description, const std::string& units = "", bool readOnly = false);
static std::string checkbox(const std::string& name, const std::string& label, bool checked, const std::string& description);
static std::string scanResult(size_t index, const ScanResult& result);
static const std::string CSS;
@@ -109,6 +114,10 @@ private:
static const std::string DIRECTION_PIN_DESCR;
static const std::string RESPONSE_DELAY_DESCR;
static const std::string FRAMES_PER_REVOLUTION_DESCR;
static const std::string GENERATE_XYZ_DESCR;
static const std::string GENERATE_STL_DESCR;
static const std::string GENERATE_PLY_DESCR;
static const std::string SEPARATE_LASERS_BY_COLOR_DESCR;
};
}

75
src/XyzWriter.cpp Normal file
View File

@@ -0,0 +1,75 @@
/*
****************************************************************************
* Copyright (c) 2014 Uriah Liggett <hairu526@gmail.com> *
* This file is part of FreeLSS. *
* *
* FreeLSS is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* FreeLSS is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with FreeLSS. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************
*/
#include "Main.h"
#include "XyzWriter.h"
#include "Camera.h"
namespace freelss
{
void XyzWriter::write(const std::string& baseFilename, const std::vector<NeutralFileRecord>& results)
{
std::string xyzFilename = baseFilename + ".xyz";
std::ofstream xyz (xyzFilename.c_str());
if (!xyz.is_open())
{
throw Exception("Error opening STL file for writing: " + xyzFilename);
}
uint32 maxNumRows = Camera::getInstance()->getImageHeight(); // TODO: Make this use the image height that generated the result and not the current Camera
uint32 numRowBins = 400; // TODO: Autodetect this or have it in Database
try
{
int iFrame = 0;
std::vector<NeutralFileRecord> frameA;
std::vector<NeutralFileRecord> currentFrame;
size_t resultIndex = 0;
while (NeutralFileRecord::readNextFrame(frameA, results, resultIndex))
{
// Reduce the number of result rows and filter out some of the noise
NeutralFileRecord::lowpassFilter(currentFrame, frameA, maxNumRows, numRowBins);
// Write the filtered results to the XYZ file
for (size_t iRec = 0; iRec < currentFrame.size(); iRec++)
{
const NeutralFileRecord& rec = currentFrame[iRec];
const ColoredPoint & pt = rec.point;
xyz << pt.x << " " << pt.y << " " << pt.z << " "
<< pt.normal.x << " " << pt.normal.y << " " << pt.normal.z
<< std::endl;
}
}
}
catch (...)
{
xyz.close();
throw;
}
xyz.close();
}
} // ns freelss

36
src/XyzWriter.h Normal file
View File

@@ -0,0 +1,36 @@
/*
****************************************************************************
* Copyright (c) 2015 Uriah Liggett <hairu526@gmail.com> *
* This file is part of FreeLSS. *
* *
* FreeLSS is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* FreeLSS is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with FreeLSS. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************
*/
#pragma once
namespace freelss
{
/**
* Writes the scan results as an XYZ file.
*/
class XyzWriter
{
public:
void write(const std::string& filename, const std::vector<NeutralFileRecord>& results);
};
}