diff --git a/config/freelss b/config/freelss new file mode 100755 index 0000000..82db688 --- /dev/null +++ b/config/freelss @@ -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 +# +# 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 + +: diff --git a/src/Calibrator.cpp b/src/Calibrator.cpp index 53695b8..1709643 100644 --- a/src/Calibrator.cpp +++ b/src/Calibrator.cpp @@ -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); // diff --git a/src/HttpServer.cpp b/src/HttpServer.cpp index 9da40a3..196d44c 100644 --- a/src/HttpServer.cpp +++ b/src/HttpServer.cpp @@ -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(); } diff --git a/src/ImageProcessor.cpp b/src/ImageProcessor.cpp index 92b2a87..d13d292 100644 --- a/src/ImageProcessor.cpp +++ b/src/ImageProcessor.cpp @@ -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; } diff --git a/src/ImageProcessor.h b/src/ImageProcessor.h index 8d6cf99..9237945 100644 --- a/src/ImageProcessor.h +++ b/src/ImageProcessor.h @@ -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 */ diff --git a/src/LaserResultsMerger.cpp b/src/LaserResultsMerger.cpp index ddb2a71..3774618 100644 --- a/src/LaserResultsMerger.cpp +++ b/src/LaserResultsMerger.cpp @@ -51,8 +51,15 @@ void LaserResultsMerger::merge(std::vector & out, std::vector & 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 & 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 & 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 & 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++; } } diff --git a/src/LaserResultsMerger.h b/src/LaserResultsMerger.h index 577e835..ca5ce9d 100644 --- a/src/LaserResultsMerger.h +++ b/src/LaserResultsMerger.h @@ -20,6 +20,8 @@ #pragma once +#include "Preset.h" + namespace freelss { @@ -39,7 +41,8 @@ public: std::vector & rightLaserResults, int numFramesPerRevolution, int numFramesBetweenLaserPlanes, - int maxPointY); + int maxPointY, + Preset::LaserMergeAction mergeAction); private: diff --git a/src/Main.cpp b/src/Main.cpp index 5e6928d..9c3c4b6 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -28,6 +28,12 @@ #include "PresetManager.h" #include "PropertyReaderWriter.h" #include "Setup.h" +#include + +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& out, const std::vector& 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& 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& output, std::vector& 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 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()) diff --git a/src/Main.h b/src/Main.h index 4ee8b71..533e4b7 100644 --- a/src/Main.h +++ b/src/Main.h @@ -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& out, const std::vector& 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& output, std::vector& frame, unsigned maxNumRows, unsigned numRowBins); + + /** + * Computes the average of all the records in the bin. + */ + static void computeAverage(const std::vector& 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(); diff --git a/src/Makefile b/src/Makefile index 3192eb0..574e76b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/Preset.cpp b/src/Preset.cpp index 92c8491..d0a9b56 100644 --- a/src/Preset.cpp +++ b/src/Preset.cpp @@ -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& 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& 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); + } } } } diff --git a/src/Preset.h b/src/Preset.h index 472f53d..d22ff53 100644 --- a/src/Preset.h +++ b/src/Preset.h @@ -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; }; } diff --git a/src/Scanner.cpp b/src/Scanner.cpp index b668350..763696f 100644 --- a/src/Scanner.cpp +++ b/src/Scanner.cpp @@ -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 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 & 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 & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats) +bool Scanner::processScan(std::vector & 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 & 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 & 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 & results, int frame, f if (numLocationsMapped > 0) { time1 = GetTimeInSeconds(); - m_numSuspectedBadLaserLocations += numSuspectedBadLaserLocations; if (m_writeRangeCsvEnabled) { @@ -654,6 +683,8 @@ void Scanner::processScan(std::vector & 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; } diff --git a/src/Scanner.h b/src/Scanner.h index fc30e4b..e4d3268 100644 --- a/src/Scanner.h +++ b/src/Scanner.h @@ -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 & 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 & 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; diff --git a/src/Setup.cpp b/src/Setup.cpp index 9f5ad3a..fb0cd42 100644 --- a/src/Setup.cpp +++ b/src/Setup.cpp @@ -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; } diff --git a/src/StlWriter.cpp b/src/StlWriter.cpp index 65687e1..348892b 100644 --- a/src/StlWriter.cpp +++ b/src/StlWriter.cpp @@ -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& 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& output, std::vector& 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 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& out, const std::vector& 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& results, bool connectLastFrameToFirst) { @@ -178,13 +57,6 @@ void StlWriter::write(const std::string& baseFilename, const std::vectorgetImageHeight(); // 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::vectorsize(); // 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& results, bool connectLastFrameToFirst); private: @@ -39,18 +37,6 @@ private: void writeTrianglesForColumn(const std::vector& currentFrame, const std::vector& 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& output, std::vector& frame, unsigned maxNumRows, unsigned numRowBins); - - /** - * Computes the average of all the records in the bin. - */ - void computeAverage(const std::vector& 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& frameC, const std::vector& results, size_t & resultIndex); /** The max triangle edge distance in mm sq */ real32 m_maxEdgeDistMmSq; diff --git a/src/WebContent.cpp b/src/WebContent.cpp index ea57e55..e2bbf73 100644 --- a/src/WebContent.cpp +++ b/src/WebContent.cpp @@ -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& 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, "μ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 << "\
\ @@ -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 << "
" + << label + << "
"; + + sstr << "
" << description << "
\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) { diff --git a/src/WebContent.h b/src/WebContent.h index 6337b7f..ab1b765 100644 --- a/src/WebContent.h +++ b/src/WebContent.h @@ -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; }; } diff --git a/src/XyzWriter.cpp b/src/XyzWriter.cpp new file mode 100644 index 0000000..3280efd --- /dev/null +++ b/src/XyzWriter.cpp @@ -0,0 +1,75 @@ +/* + **************************************************************************** + * Copyright (c) 2014 Uriah Liggett * + * 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 . * + **************************************************************************** +*/ + +#include "Main.h" +#include "XyzWriter.h" +#include "Camera.h" + +namespace freelss +{ + +void XyzWriter::write(const std::string& baseFilename, const std::vector& 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 frameA; + std::vector 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 diff --git a/src/XyzWriter.h b/src/XyzWriter.h new file mode 100644 index 0000000..0c90a21 --- /dev/null +++ b/src/XyzWriter.h @@ -0,0 +1,36 @@ +/* + **************************************************************************** + * Copyright (c) 2015 Uriah Liggett * + * 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 . * + **************************************************************************** +*/ + +#pragma once + +namespace freelss +{ + +/** + * Writes the scan results as an XYZ file. + */ +class XyzWriter +{ +public: + + void write(const std::string& filename, const std::vector& results); +}; + +}