/* This file is part of the Pangolin Project. * http://github.com/stevenlovegrove/Pangolin * * Copyright (c) 2014 Steven Lovegrove * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include namespace pangolin { const size_t ROGUE_ADDR = 0x01; const double MAX_DELTA_TIME = 20000.0; //u_s DepthSenseContext& DepthSenseContext::I() { static DepthSenseContext s; return s; } DepthSense::Context& DepthSenseContext::Context() { return g_context; } void DepthSenseContext::NewDeviceRunning() { running_devices++; if(running_devices == 1) { StartNodes(); } } void DepthSenseContext::DeviceClosing() { running_devices--; if(running_devices == 0) { StopNodes(); // Force destruction of current context g_context = DepthSense::Context(); } } DepthSenseVideo* DepthSenseContext::GetDepthSenseVideo(size_t device_num, DepthSenseSensorType s1, DepthSenseSensorType s2, ImageDim dim1, ImageDim dim2, unsigned int fps1, unsigned int fps2, const Uri& uri) { if(running_devices == 0) { // Initialise SDK g_context = DepthSense::Context::create("localhost"); } // Get the list of currently connected devices std::vector da = g_context.getDevices(); if( da.size() > device_num ) { return new DepthSenseVideo(da[device_num], s1, s2, dim1, dim2, fps1, fps2, uri); } throw VideoException("DepthSense device not connected."); } DepthSenseContext::DepthSenseContext() : is_running(false), running_devices(0) { } DepthSenseContext::~DepthSenseContext() { StopNodes(); } void DepthSenseContext::StartNodes() { if(!is_running) { // Launch EventLoop thread event_thread = std::thread(&DepthSenseContext::EventLoop, this ); } } void DepthSenseContext::StopNodes() { if(is_running && event_thread.joinable()) { g_context.quit(); event_thread.join(); } } void DepthSenseContext::EventLoop() { is_running = true; g_context.startNodes(); g_context.run(); g_context.stopNodes(); is_running = false; } DepthSenseVideo::DepthSenseVideo(DepthSense::Device device, DepthSenseSensorType s1, DepthSenseSensorType s2, ImageDim dim1, ImageDim dim2, unsigned int fps1, unsigned int fps2, const Uri& uri) : device(device), fill_image(0), depthmap_stream(-1), rgb_stream(-1), gotDepth(0), gotColor(0), enableDepth(false), enableColor(false), depthTs(0.0), colorTs(0.0), size_bytes(0) { streams_properties = &frame_properties["streams"]; *streams_properties = picojson::value(picojson::array_type, false); streams_properties->get().resize(2); sensorConfig[0] = {s1, dim1, fps1}; sensorConfig[1] = {s2, dim2, fps2}; ConfigureNodes(uri); DepthSenseContext::I().NewDeviceRunning(); } DepthSenseVideo::~DepthSenseVideo() { if (g_cnode.isSet()) DepthSenseContext::I().Context().unregisterNode(g_cnode); if (g_dnode.isSet()) DepthSenseContext::I().Context().unregisterNode(g_dnode); fill_image = (unsigned char*)ROGUE_ADDR; cond_image_requested.notify_all(); DepthSenseContext::I().DeviceClosing(); } picojson::value Json(DepthSense::IntrinsicParameters& p) { picojson::value js; js["model"] = "polynomial"; js["width"] = p.width; js["height"] = p.height; js["RDF"] = "[1,0,0; 0,1,0; 0,0,1]"; js["fx"] = p.fx; js["fy"] = p.fy; js["u0"] = p.cx; js["v0"] = p.cy; js["k1"] = p.k1; js["k2"] = p.k2; js["k3"] = p.k3; js["p1"] = p.p1; js["p2"] = p.p2; return js; } picojson::value Json(DepthSense::ExtrinsicParameters& p) { picojson::value js; js["rows"] = "3"; js["cols"] = "4"; std::ostringstream oss; oss << std::setprecision(17); oss << "[" << p.r11 << "," << p.r12 << "," << p.r13 << "," << p.t1 << ";"; oss << p.r21 << "," << p.r22 << "," << p.r23 << "," << p.t2 << ";"; oss << p.r31 << "," << p.r32 << "," << p.r33 << "," << p.t3 << "]"; js["data"] = oss.str(); return js; } void DepthSenseVideo::ConfigureNodes(const Uri& uri) { std::vector nodes = device.getNodes(); for (int i = 0; i<2; ++i) { switch (sensorConfig[i].type) { case DepthSenseDepth: { for (int n = 0; n < (int)nodes.size(); n++) { DepthSense::Node node = nodes[n]; if ((node.is()) && (!g_dnode.isSet())) { depthmap_stream = i; g_dnode = node.as(); ConfigureDepthNode(sensorConfig[i], uri); DepthSenseContext::I().Context().registerNode(node); } } break; } case DepthSenseRgb: { for (int n = 0; n < (int)nodes.size(); n++) { DepthSense::Node node = nodes[n]; if ((node.is()) && (!g_cnode.isSet())) { rgb_stream = i; g_cnode = node.as(); ConfigureColorNode(sensorConfig[i], uri); DepthSenseContext::I().Context().registerNode(node); } } break; } default: continue; } } DepthSense::StereoCameraParameters scp = device.getStereoCameraParameters(); //Set json device properties for intrinsics and extrinsics picojson::value& jsintrinsics = device_properties["intrinsics"]; if (jsintrinsics.is()) { jsintrinsics = picojson::value(picojson::array_type, false); jsintrinsics.get().resize(streams.size()); if (depthmap_stream >= 0) jsintrinsics[depthmap_stream] = Json(scp.depthIntrinsics); if (rgb_stream >= 0) jsintrinsics[rgb_stream] = Json(scp.colorIntrinsics); } picojson::value& jsextrinsics = device_properties["extrinsics"]; if(jsextrinsics.is()){ jsextrinsics = Json(scp.extrinsics); } } inline DepthSense::FrameFormat ImageDim2FrameFormat(const ImageDim& dim) { DepthSense::FrameFormat retVal = DepthSense::FRAME_FORMAT_UNKNOWN; if(dim.x == 160 && dim.y == 120) { retVal = DepthSense::FRAME_FORMAT_QQVGA; } else if(dim.x == 176 && dim.y == 144) { retVal = DepthSense::FRAME_FORMAT_QCIF; } else if(dim.x == 240 && dim.y == 160) { retVal = DepthSense::FRAME_FORMAT_HQVGA; } else if(dim.x == 320 && dim.y == 240) { retVal = DepthSense::FRAME_FORMAT_QVGA; } else if(dim.x == 352 && dim.y == 288) { retVal = DepthSense::FRAME_FORMAT_CIF; } else if(dim.x == 480 && dim.y == 320) { retVal = DepthSense::FRAME_FORMAT_HVGA; } else if(dim.x == 640 && dim.y == 480) { retVal = DepthSense::FRAME_FORMAT_VGA; } else if(dim.x == 1280 && dim.y == 720) { retVal = DepthSense::FRAME_FORMAT_WXGA_H; } else if(dim.x == 320 && dim.y == 120) { retVal = DepthSense::FRAME_FORMAT_DS311; } else if(dim.x == 1024 && dim.y == 768) { retVal = DepthSense::FRAME_FORMAT_XGA; } else if(dim.x == 800 && dim.y == 600) { retVal = DepthSense::FRAME_FORMAT_SVGA; } else if(dim.x == 636 && dim.y == 480) { retVal = DepthSense::FRAME_FORMAT_OVVGA; } else if(dim.x == 640 && dim.y == 240) { retVal = DepthSense::FRAME_FORMAT_WHVGA; } else if(dim.x == 640 && dim.y == 360) { retVal = DepthSense::FRAME_FORMAT_NHD; } return retVal; } void DepthSenseVideo::UpdateParameters(const DepthSense::Node& node, const Uri& uri) { DepthSense::Type type = node.getType(); picojson::value& jsnode = device_properties[type.name()]; std::vector properties = type.getProperties(); for(std::vector::const_iterator it = properties.begin(); it != properties.end(); ++it) { const DepthSense::PropertyBase& prop = *it; if (prop.is >()) { DepthSense::Property tprop = prop.as >(); if (uri.Contains(prop.name())) { if (!prop.isReadOnly()) { tprop.setValue(node, uri.Get(prop.name(), tprop.getValue(node))); } else { pango_print_warn("DepthSense property '%s' is read-only\n", prop.name().c_str() ); } } jsnode[prop.name()] = tprop.getValue(node); } else if (prop.is >()) { DepthSense::Property tprop = prop.as >(); if (uri.Contains(prop.name())) { if (!prop.isReadOnly()) { tprop.setValue(node, uri.Get(prop.name(), tprop.getValue(node))); } else { pango_print_warn("DepthSense property '%s' is read-only\n", prop.name().c_str() ); } } jsnode[prop.name()] = tprop.getValue(node); } else if (prop.is >()) { DepthSense::Property tprop = prop.as >(); if (uri.Contains(prop.name())) { if (!prop.isReadOnly()) { tprop.setValue(node, uri.Get(prop.name(), tprop.getValue(node))); } else { pango_print_warn("DepthSense property '%s' is read-only\n", prop.name().c_str() ); } } jsnode[prop.name()] = tprop.getValue(node); } else if (prop.is >()){ DepthSense::Property tprop = prop.as >(); if (uri.Contains(prop.name())) { if (!prop.isReadOnly()) { tprop.setValue(node, uri.Get(prop.name(), tprop.getValue(node)).c_str() ); } else { pango_print_warn("DepthSense property '%s' is read-only\n", prop.name().c_str() ); } } jsnode[prop.name()] = tprop.getValue(node); } } } void DepthSenseVideo::ConfigureDepthNode(const SensorConfig& sensorConfig, const Uri& uri) { g_dnode.newSampleReceivedEvent().connect(this, &DepthSenseVideo::onNewDepthSample); DepthSense::DepthNode::Configuration config = g_dnode.getConfiguration(); config.frameFormat = ImageDim2FrameFormat(sensorConfig.dim); config.framerate = sensorConfig.fps; config.mode = DepthSense::DepthNode::CAMERA_MODE_CLOSE_MODE; config.saturation = true; try { DepthSenseContext::I().Context().requestControl(g_dnode, 0); g_dnode.setConfiguration(config); g_dnode.setEnableDepthMap(true); } catch (DepthSense::Exception& e) { throw pangolin::VideoException("DepthSense exception whilst configuring node", e.what()); } //Set pangolin stream for this channel const int w = sensorConfig.dim.x; const int h = sensorConfig.dim.y; const PixelFormat pfmt = PixelFormatFromString("GRAY16LE"); const StreamInfo stream_info(pfmt, w, h, (w*pfmt.bpp) / 8, (unsigned char*)0); streams.push_back(stream_info); size_bytes += stream_info.SizeBytes(); enableDepth = true; UpdateParameters(g_dnode, uri); } void DepthSenseVideo::ConfigureColorNode(const SensorConfig& sensorConfig, const Uri& uri) { // connect new color sample handler g_cnode.newSampleReceivedEvent().connect(this, &DepthSenseVideo::onNewColorSample); DepthSense::ColorNode::Configuration config = g_cnode.getConfiguration(); config.frameFormat = ImageDim2FrameFormat(sensorConfig.dim); config.compression = DepthSense::COMPRESSION_TYPE_MJPEG; config.powerLineFrequency = DepthSense::POWER_LINE_FREQUENCY_50HZ; config.framerate = sensorConfig.fps; try { DepthSenseContext::I().Context().requestControl(g_cnode,0); g_cnode.setConfiguration(config); g_cnode.setEnableColorMap(true); UpdateParameters(g_cnode, uri); } catch (DepthSense::Exception& e) { throw pangolin::VideoException("DepthSense exception whilst configuring node", e.what()); } //Set pangolin stream for this channel const int w = sensorConfig.dim.x; const int h = sensorConfig.dim.y; const PixelFormat pfmt = PixelFormatFromString("BGR24"); const StreamInfo stream_info(pfmt, w, h, (w*pfmt.bpp) / 8, (unsigned char*)0 + size_bytes); streams.push_back(stream_info); size_bytes += stream_info.SizeBytes(); enableColor = true; } void DepthSenseVideo::onNewColorSample(DepthSense::ColorNode node, DepthSense::ColorNode::NewSampleReceivedData data) { { std::unique_lock lock(update_mutex); // Wait for fill request while (!fill_image) { cond_image_requested.wait(lock); } // Update per-frame parameters //printf("Color delta: %.1f\n", fabs(colorTs - data.timeOfCapture)); colorTs = data.timeOfCapture; picojson::value& jsstream = frame_properties["streams"][rgb_stream]; jsstream["time_us"] = data.timeOfCapture; if (fill_image != (unsigned char*)ROGUE_ADDR) { // Fill with data unsigned char* imagePtr = fill_image; bool copied = false; for (int i = 0; !copied && i < 2; ++i) { switch (sensorConfig[i].type) { case DepthSenseDepth: { imagePtr += streams[i].SizeBytes(); break; } case DepthSenseRgb: { // Leave as BGR std::memcpy(imagePtr, data.colorMap, streams[i].SizeBytes()); copied = true; break; } default: continue; } } gotColor++; } //printf("Got color at: %.1f\n", colorTs); if(gotDepth) { double delta = fabs(GetDeltaTime()); if(delta > MAX_DELTA_TIME) { //printf("**** Waiting for another depth, delta: %.1f ****\n", delta); gotDepth = 0; return; } } } cond_image_filled.notify_one(); } void DepthSenseVideo::onNewDepthSample(DepthSense::DepthNode node, DepthSense::DepthNode::NewSampleReceivedData data) { { std::unique_lock lock(update_mutex); // Wait for fill request while(!fill_image) { cond_image_requested.wait(lock); } // Update per-frame parameters //printf("Depth delta: %.1f\n", fabs(depthTs - data.timeOfCapture)); depthTs = data.timeOfCapture; picojson::value& jsstream = frame_properties["streams"][depthmap_stream]; jsstream["time_us"] = depthTs; if(fill_image != (unsigned char*)ROGUE_ADDR) { // Fill with data unsigned char* imagePtr = fill_image; bool copied = false; for (int i = 0; i < 2; ++i) { switch (sensorConfig[i].type) { case DepthSenseDepth: { memcpy(imagePtr, data.depthMap, streams[i].SizeBytes()); copied = true; break; } case DepthSenseRgb: { imagePtr += streams[i].SizeBytes(); break; } default: continue; } if(copied) { break; } } gotDepth++; } //printf("Got depth at: %.1f\n", depthTs); if(gotColor) { double delta = fabs(GetDeltaTime()); if(delta > MAX_DELTA_TIME) { //printf("**** Waiting for another color, delta: %.1f ****\n", delta); gotColor = 0; return; } } } cond_image_filled.notify_one(); } void DepthSenseVideo::Start() { } void DepthSenseVideo::Stop() { } size_t DepthSenseVideo::SizeBytes() const { return size_bytes; } const std::vector& DepthSenseVideo::Streams() const { return streams; } bool DepthSenseVideo::GrabNext( unsigned char* image, bool /*wait*/ ) { if(fill_image) { throw std::runtime_error("GrabNext Cannot be called concurrently"); } //printf("#### Grab Next ####\n"); // Request that image is filled with data fill_image = image; cond_image_requested.notify_one(); // Wait until it has been filled successfully. { std::unique_lock lock(update_mutex); while ((enableDepth && !gotDepth) || (enableColor && !gotColor)) { cond_image_filled.wait(lock); } if (gotDepth) { gotDepth = 0; } if (gotColor) { gotColor = 0; } fill_image = 0; } //printf("Delta time: %.1f\n", fabs(GetDeltaTime())); return true; } bool DepthSenseVideo::GrabNewest( unsigned char* image, bool wait ) { return GrabNext(image,wait); } double DepthSenseVideo::GetDeltaTime() const { return depthTs - colorTs; } DepthSenseSensorType depthsense_sensor(const std::string& str) { if (!str.compare("rgb")) { return DepthSenseRgb; } else if (!str.compare("depth")) { return DepthSenseDepth; } else if (str.empty()) { return DepthSenseUnassigned; } else{ throw pangolin::VideoException("Unknown DepthSense sensor", str); } } PANGOLIN_REGISTER_FACTORY(DepthSenseVideo) { struct DepthSenseVideoFactory final : public FactoryInterface { std::unique_ptr Open(const Uri& uri) override { DepthSenseSensorType img1 = depthsense_sensor(uri.Get("img1", "depth")); DepthSenseSensorType img2 = depthsense_sensor(uri.Get("img2", "")); const ImageDim dim1 = uri.Get("size1", img1 == DepthSenseDepth ? ImageDim(320, 240) : ImageDim(640, 480) ); const ImageDim dim2 = uri.Get("size2", img2 == DepthSenseDepth ? ImageDim(320, 240) : ImageDim(640, 480) ); const unsigned int fps1 = uri.Get("fps1", 30); const unsigned int fps2 = uri.Get("fps2", 30); return std::unique_ptr( DepthSenseContext::I().GetDepthSenseVideo(0, img1, img2, dim1, dim2, fps1, fps2, uri) ); } }; FactoryRegistry::I().RegisterFactory(std::make_shared(), 10, "depthsense"); } }