aboutsummaryrefslogtreecommitdiff
path: root/src/automobile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/automobile.cpp')
-rw-r--r--src/automobile.cpp403
1 files changed, 403 insertions, 0 deletions
diff --git a/src/automobile.cpp b/src/automobile.cpp
new file mode 100644
index 0000000..365c96b
--- /dev/null
+++ b/src/automobile.cpp
@@ -0,0 +1,403 @@
+/* automobile.cpp -- recording data about the car
+ * Copyright (C) 2013 Galois, Inc.
+ *
+ * This library 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.
+ *
+ * This library 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
+ * this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * To contact Galois, complete the Web form at <http://corp.galois.com/contact/>
+ * or write to Galois, Inc., 421 Southwest 6th Avenue, Suite 300, Portland,
+ * Oregon, 97204-1622. */
+
+/* This module assumes only one car is recording data at once. Thus, it follows
+ * a singleton object pattern. To keep everything a bit simpler, the state
+ * variables live in an anonymous namespace, and the functions simply mutate
+ * them as appropriate. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <cassert>
+#include <cstdarg>
+
+#include <array>
+#include <fstream>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
+#include <v_repLib.h>
+
+#include "automobile.h"
+#include "noise.h"
+#include "vrepFfi.h"
+
+#ifndef HAVE_CXX11_INITIALIZER_LISTS
+# include <cassert>
+# include <list>
+#endif
+
+
+namespace {
+
+ // Paths to the output files
+ namespace path {
+
+ std::string dataDir = "/tmp";
+
+ const std::string groundDir = "/ground";
+ const std::string noisyDir = "/noisy";
+
+ const std::string properties = "/properties.csv";
+ const std::string sensor = "/slam_sensor.csv";
+ const std::string pose = "/slam_gps.csv";
+ const std::string control = "/slam_control.csv";
+ const std::string laser = "/slam_laser.csv";
+
+ }
+
+ // Run specifications
+ struct Properties : public csv::Datum {
+ inline Properties(float L, float h, float a, float b, float theta0)
+ : L(L), h(h), a(a), b(b), theta0(theta0) {
+ }
+ inline virtual std::string csvHeader() const;
+ virtual std::string csv() const;
+ inline virtual unsigned int nCols() const;
+ // Distance between front and rear axles
+ float L;
+ // Distance between center of rear axle and encoder on left rear wheel
+ float h;
+ // Lengthwise distance between the rear axle and the lidar
+ float a;
+ // Widthwise distance between the rear axle and the lidar
+ float b;
+ // Initial angle of the car
+ float theta0;
+ };
+
+ // Individual sample records
+ struct Sample : public csv::Datum {
+ inline explicit Sample(const Pose &);
+ inline explicit Sample(const ControlSignals &);
+ inline explicit Sample(const LidarDatum &);
+ inline virtual std::string csvHeader() const;
+ inline virtual std::string csv() const;
+ inline virtual unsigned int nCols() const;
+ float time;
+ unsigned short sensorId;
+ };
+
+
+ // Lidar specifications //
+ namespace laser {
+
+ float MAX_DISTANCE = 10.;
+ float MAX_INTENSITY = 32768.;
+
+ }
+
+
+ // Noise //
+ namespace noise {
+
+ bool requested = false;
+
+ // Dummy noise source that provides no noise
+ GaussianNoiseSource<float> none(0., 0.);
+
+ // Noise sources for the various measurements (can be set via Lua)
+ boost::optional<GaussianNoiseSource<float>> position;
+ boost::optional<GaussianNoiseSource<float>> angle;
+ boost::optional<GaussianNoiseSource<float>> speed;
+ boost::optional<GaussianNoiseSource<float>> steeringAngle;
+ boost::optional<GaussianNoiseSource<float>> distance;
+ boost::optional<GaussianNoiseSource<float>> intensity;
+
+ }
+
+
+ // Prototypes //
+
+ // Lua callbacks
+ typedef simVoid LuaFunc(SLuaCallBack *);
+ LuaFunc init;
+ LuaFunc setNoiseParameters;
+ LuaFunc savePose;
+ LuaFunc saveControls;
+ LuaFunc saveLaser;
+
+ inline void savePropertiesFile(const Properties &);
+
+ template<typename CsvDatum>
+ inline void saveDatum(const std::string &subdir,
+ const std::string &filename, const CsvDatum &);
+
+ void outputDatum(const std::string &filePath, const csv::Datum &);
+
+}
+
+
+// Registering //
+
+void registerLuaFunctions() {
+ vrep::exposeFunction<
+ std::string,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float>(
+ "simExtAutomobileInit",
+ "simExtAutomobileInit(string directoryName, number L, number h, number a, number b, number theta0, number max_distance, number max_intensity)",
+ init);
+ vrep::exposeFunction<
+ std::vector<float>, // x and y
+ std::vector<float>, // angle
+ std::vector<float>, // speed
+ std::vector<float>, // steering angle
+ std::vector<float>, // distance
+ std::vector<float>>( // intensity
+ "simExtAutomobileRequestNoise",
+ "simExtAutomobileRequestNoise(table2 xy, table2 angle, table2 speed, table2 steeringAngle, table2 distance, table2 intensity)",
+ setNoiseParameters);
+ vrep::exposeFunction<float, float, float, float>(
+ "simExtAutomobileSavePose",
+ "simExtAutomobileSavePose(number simulationTime, number x, number y, number theta)",
+ savePose);
+ vrep::exposeFunction<float, float, float>(
+ "simExtAutomobileSaveControls",
+ "simExtAutomobileSaveControls(number simulationTime, number speed, number steeringAngle)",
+ saveControls);
+ /* V-REP's depth sensor part has a maximum field of view narrower than 180
+ * degrees. To compensate, we instead use two 90-degree depth sensors and
+ * combine the results here. */
+ vrep::exposeFunction<
+ float,
+ std::vector<float>, std::vector<float>, // depths
+ std::vector<float>, std::vector<float>>( // grayscale images
+ "simExtAutomobileSaveLaserPair",
+ "simExtAutomobileSaveLaserPair(number simulationTime, table leftDepthBuffer, table rightDepthBuffer, table leftImage, table rightImage)",
+ saveLaser);
+}
+
+
+// Callbacks //
+namespace {
+
+ std::string Properties::csvHeader() const {
+# ifdef HAVE_CXX11_INITIALIZER_LISTS
+ return csv::fromContainer(
+ std::vector<std::string> {"L", "h", "a", "b", "InitialAngle"});
+# else
+ return csv::fromContainer(
+ makeList<const char *>(nCols(), "L", "h", "a", "b",
+ "InitialAngle"));
+# endif
+ }
+
+ std::string Properties::csv() const {
+# ifdef HAVE_CXX11_INITIALIZER_LISTS
+ return csv::fromContainer(
+ std::vector<float> {L, h, a, b, theta0});
+# else
+ return csv::fromContainer(
+ makeList<float>(nCols(), L, h, a, b, theta0));
+# endif
+ }
+
+ unsigned int Properties::nCols() const {
+ return 5;
+ }
+
+ Sample::Sample(const Pose &pose)
+ : time(pose.time), sensorId(1) {
+ }
+
+ Sample::Sample(const ControlSignals &signals)
+ : time(signals.time), sensorId(2) {
+ }
+
+ Sample::Sample(const LidarDatum &datum)
+ : time(datum.time), sensorId(3) {
+ }
+
+ std::string Sample::csvHeader() const {
+ return "Time,Sensor";
+ }
+
+ std::string Sample::csv() const {
+ return std::to_string(time) + "," + std::to_string(sensorId);
+ }
+
+ unsigned int Sample::nCols() const {
+ return 2;
+ }
+
+ simVoid init(SLuaCallBack *const simCall) {
+ // Clean data from previous runs.
+ boost::filesystem::remove_all(path::dataDir + path::groundDir);
+ boost::filesystem::remove_all(path::dataDir + path::noisyDir);
+ boost::filesystem::remove(path::dataDir + path::properties);
+ // Save the passed directory as the base directory for the output.
+ vrep::LuaCall call(simCall);
+ path::dataDir = call.expectAtom<std::string>();
+ // Save the passed properties in the properties file.
+ const float L = call.expectAtom<float>();
+ const float h = call.expectAtom<float>();
+ const float a = call.expectAtom<float>();
+ const float b = call.expectAtom<float>();
+ const float theta0 = call.expectAtom<float>();
+ savePropertiesFile(Properties(L, h, a, b, theta0));
+ // Save the maximum distance and intensity settings.
+ laser::MAX_DISTANCE = call.expectAtom<float>();
+ laser::MAX_INTENSITY = call.expectAtom<float>();
+ // Reset the noise settings.
+ noise::requested = false;
+ const std::array<boost::optional<GaussianNoiseSource<float>> *const, 6> sources =
+ {{&noise::position, &noise::angle, &noise::speed,
+ &noise::steeringAngle, &noise::intensity, &noise::distance}};
+ for (boost::optional<GaussianNoiseSource<float>> *const source : sources) {
+ *source = boost::none;
+ }
+ }
+
+ simVoid setNoiseParameters(SLuaCallBack *const simCall) {
+ vrep::LuaCall call(simCall);
+ const std::array<boost::optional<GaussianNoiseSource<float>> *const, 6> sources =
+ {{&noise::position, &noise::angle, &noise::speed,
+ &noise::steeringAngle, &noise::intensity, &noise::distance}};
+ for (boost::optional<GaussianNoiseSource<float>> *const source : sources) {
+ *source = gaussian(call.expectTable<float>());
+ }
+ noise::requested = true;
+ }
+
+ simVoid savePose(SLuaCallBack *const simCall) {
+ // Build the pose.
+ vrep::LuaCall call(simCall);
+ const float time = call.expectAtom<float>();
+ const float x = call.expectAtom<float>();
+ const float y = call.expectAtom<float>();
+ const float theta = call.expectAtom<float>();
+ const Pose pose(time, x, y, theta);
+ // Record it.
+ saveDatum(path::groundDir, path::pose, pose);
+ if (noise::requested) {
+ saveDatum(path::noisyDir, path::pose,
+ addNoise(pose, noise::position.get_value_or(noise::none),
+ noise::angle.get_value_or(noise::none)));
+ }
+ }
+
+ simVoid saveControls(SLuaCallBack *const simCall) {
+ // Build the control systems object.
+ vrep::LuaCall call(simCall);
+ const float time = call.expectAtom<float>();
+ const float speed = call.expectAtom<float>();
+ const float angle = call.expectAtom<float>();
+ const ControlSignals signals(time, speed, angle);
+ // Record it.
+ saveDatum(path::groundDir, path::control, signals);
+ if (noise::requested) {
+ saveDatum(path::noisyDir, path::control,
+ addNoise(signals, noise::speed.get_value_or(noise::none),
+ noise::steeringAngle.get_value_or(noise::none)));
+ }
+ }
+
+ simVoid saveLaser(SLuaCallBack *const simCall) {
+ // Get the arguments and reconstruct the full lidar measurements.
+ vrep::LuaCall call(simCall);
+ const float time = call.expectAtom<float>();
+ std::vector<float> distance = call.expectTable<float>();
+ const std::vector<float> distanceRight = call.expectTable<float>();
+ distance.insert(distance.end(),
+ distanceRight.begin(), distanceRight.end());
+ std::vector<float> image = call.expectTable<float>();
+ const std::vector<float> imageRight = call.expectTable<float>();
+ image.insert(image.end(), imageRight.begin(), imageRight.end());
+ // Process the lidar measurements.
+# ifdef HAVE_CXX11_CLOSURES
+ std::for_each(distance.begin(), distance.end(),
+ [](float &d) { d *= laser::MAX_DISTANCE; });
+ std::for_each(image.begin(), image.end(),
+ [](float &i) { i *= laser::MAX_INTENSITY; });
+# else
+ for (float &d : distance) {
+ d *= laser::MAX_DISTANCE;
+ }
+ for (float &i : image) {
+ i *= laser::MAX_INTENSITY;
+ }
+# endif
+ // Build the lidar datum and record it.
+ const LidarDatum datum(time, distance, image);
+ saveDatum(path::groundDir, path::laser, datum);
+ if (noise::requested) {
+ saveDatum(path::noisyDir, path::laser,
+ addNoise(datum, noise::distance.get_value_or(noise::none),
+ noise::intensity.get_value_or(noise::none)));
+ }
+ }
+
+ void savePropertiesFile(const Properties &properties) {
+ boost::filesystem::create_directories(path::dataDir);
+ outputDatum(path::dataDir + path::properties, properties);
+ }
+
+ template<typename CsvDatum>
+ void saveDatum(const std::string &subdir, const std::string &filename,
+ const CsvDatum &datum) {
+ const std::string logDir = path::dataDir + subdir;
+ boost::filesystem::create_directories(logDir);
+ outputDatum(logDir + filename, datum);
+ outputDatum(logDir + path::sensor, Sample(datum));
+ }
+
+ void outputDatum(const std::string &filePath, const csv::Datum &datum) {
+ const std::string header = datum.csvHeader();
+ std::fstream file(filePath,
+ std::ios::in | std::ios::out | std::ios::app);
+ // Ensure we're writing correctly-formatted data.
+ std::string firstLine;
+ std::getline(file, firstLine);
+ if (file.eof() && firstLine.empty()) {
+ // The file was empty, so write in the header.
+ file.clear();
+ file << header << std::endl;
+ } else if (firstLine != header) {
+ /* The file was nonempty, but the first line did not match the
+ * expected header. This might occur if an external program
+ * modifies the file in between our writes, but more likely, we've
+ * got a bug somewhere that is causing us to write different data
+ * sets to the same file. */
+ throw std::logic_error(
+ std::string("CSV header mismatch: expected `")
+ + header
+ + "', but got `"
+ + firstLine
+ + "'");
+ } else {
+ /* The file was nonempty, and the headers matched what we expected.
+ * We're good to go. */
+ }
+ // Write the datum.
+ file << datum.csv() << std::endl;
+ }
+
+}