-
-
Save n1ckfg/395a2c8ac8ed83a7bc5a8c0dacbc5b26 to your computer and use it in GitHub Desktop.
Sensing Machines FBOs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
int main() | |
{ | |
ofGLFWWindowSettings settings; | |
settings.setSize(1280, 720); | |
settings.setPosition(glm::vec2(0, 0)); | |
settings.resizable = true; | |
settings.decorated = true; | |
settings.title = "Drumbotica"; | |
auto mainApp = std::make_shared<ofApp>(); | |
// Control window. | |
auto controlWindow = ofCreateWindow(settings); | |
controlWindow->setWindowPosition(ofGetScreenWidth() / 2 - settings.getWidth() / 2, ofGetScreenHeight() / 2 - settings.getHeight() / 2); | |
// Main window. | |
settings.setSize(1920, 1080); | |
settings.setPosition(glm::vec2(1920, 0)); | |
settings.resizable = false; | |
settings.decorated = false; | |
settings.shareContextWith = controlWindow; | |
auto projWindow = ofCreateWindow(settings); | |
projWindow->setVerticalSync(false); | |
ofAddListener(projWindow->events().draw, mainApp.get(), &ofApp::drawProjection); | |
ofRunApp(controlWindow, mainApp); | |
ofRunMainLoop(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
// Texture coordinates from RealSense are normalized (between 0-1). | |
// This call normalizes all OF texture coordinates so that they match. | |
ofDisableArbTex(); | |
// Start the RealSense context. | |
// Devices are added in the deviceAdded() callback function. | |
ofAddListener(rsContext.deviceAddedEvent, this, &ofApp::deviceAdded); | |
rsContext.setup(false); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
visionFbo.allocate(settings); | |
colorFbo.allocate(settings); | |
pointsFbo.allocate(settings); | |
// Setup the parameters. | |
nearThreshold.set("Near Threshold", 0.01f, 0.0f, 0.1f); | |
farThreshold.set("Far Threshold", 0.02f, 0.0f, 0.1f); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
// Setup the gui. | |
guiPanel.setup("Depth Threshold", "settings.json"); | |
guiPanel.add(nearThreshold); | |
guiPanel.add(farThreshold); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
} | |
void ofApp::deviceAdded(std::string& serialNumber) | |
{ | |
ofLogNotice(__FUNCTION__) << "Starting device " << serialNumber; | |
auto device = rsContext.getDevice(serialNumber); | |
device->enableDepth(); | |
device->enableColor(); | |
device->enablePoints(); | |
device->startPipeline(); | |
device->alignMode = ofxRealSense2::Device::Align::Color; | |
// Uncomment this to add the device specific settings to the GUI. | |
//guiPanel.add(device->params); | |
} | |
void ofApp::update() | |
{ | |
rsContext.update(); | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice && rsDevice->isFrameNew()) | |
{ | |
// Threshold the depth. | |
ofFloatPixels rawDepthPix = rsDevice->getRawDepthPix(); | |
ofFloatPixels thresholdNear, thresholdFar, thresholdResult; | |
ofxCv::threshold(rawDepthPix, thresholdNear, nearThreshold); | |
ofxCv::threshold(rawDepthPix, thresholdFar, farThreshold, true); | |
ofxCv::bitwise_and(thresholdNear, thresholdFar, thresholdResult); | |
thresholdImg.setFromPixels(thresholdResult); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(thresholdImg); | |
// Draw CV operations. | |
visionFbo.begin(); | |
{ | |
// Draw the threshold background. | |
ofSetColor(255); | |
thresholdImg.draw(0, 0); | |
// Draw the contours over it. | |
ofSetColor(0, 255, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
// Draw masked color. | |
colorFbo.begin(); | |
{ | |
// Draw the color background. | |
rsDevice->getColorTex().draw(0, 0); | |
// Set the blend mode to multiply to clip out any black pixels. | |
ofEnableBlendMode(OF_BLENDMODE_MULTIPLY); | |
// Draw the threshold image on top. | |
// The result will be a colored threshold image. | |
thresholdImg.draw(0, 0); | |
} | |
colorFbo.end(); | |
// Draw 3D world. | |
pointsFbo.begin(); | |
{ | |
ofBackground(0); | |
camera.begin(); | |
ofPushMatrix(); | |
// Adjust points to match OF 3D space. | |
ofScale(1000); | |
ofRotateXDeg(180); | |
// Set the blend mode to screen to let any pixels with color through. | |
ofEnableBlendMode(OF_BLENDMODE_SCREEN); | |
// Draw the point cloud using the masked color texture. | |
colorFbo.getTexture().bind(); | |
rsDevice->getPointsMesh().draw(); | |
colorFbo.getTexture().unbind(); | |
ofPopMatrix(); | |
camera.end(); | |
} | |
pointsFbo.end(); | |
} | |
} | |
void ofApp::draw() | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
// 2x2 window. | |
int frameWidth = ofGetWidth() / 2; | |
int frameHeight = ofGetHeight() / 2; | |
rsDevice->getDepthTex().draw(0, 0, frameWidth, frameHeight); | |
colorFbo.draw(0, frameHeight, frameWidth, frameHeight); | |
visionFbo.draw(frameWidth, 0, frameWidth, frameHeight); | |
ofRectangle camDrawRect = ofRectangle(frameWidth, frameHeight, frameWidth, frameHeight); | |
pointsFbo.draw(camDrawRect); | |
camera.setControlArea(camDrawRect); | |
} | |
// Draw the gui. | |
guiPanel.draw(); | |
} | |
void ofApp::drawProjection(ofEventArgs& args) | |
{ | |
// Draw fullscreen points. | |
pointsFbo.draw(ofGetCurrentViewport()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
#include "ofxRealSense2.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void drawProjection(ofEventArgs& args); | |
void deviceAdded(std::string& serialNumber); | |
ofxRealSense2::Context rsContext; | |
ofImage thresholdImg; | |
ofxCv::ContourFinder contourFinder; | |
ofEasyCam camera; | |
ofFbo visionFbo; | |
ofFbo colorFbo; | |
ofFbo pointsFbo; | |
ofParameter<float> nearThreshold; | |
ofParameter<float> farThreshold; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
// Texture coordinates from RealSense are normalized (between 0-1). | |
// This call normalizes all OF texture coordinates so that they match. | |
ofDisableArbTex(); | |
ofSetWindowShape(1280, 720); | |
// Start the RealSense context. | |
// Devices are added in the deviceAdded() callback function. | |
ofAddListener(rsContext.deviceAddedEvent, this, &ofApp::deviceAdded); | |
rsContext.setup(false); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
visionFbo.allocate(settings); | |
pointsFbo.allocate(settings); | |
// Setup the parameters. | |
nearThreshold.set("Near Threshold", 0.01f, 0.0f, 0.1f); | |
farThreshold.set("Far Threshold", 0.02f, 0.0f, 0.1f); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
drawMode.set("Draw Mode", 0, 0, 4); | |
// Setup the gui. | |
guiPanel.setup("Depth Threshold", "settings.json"); | |
guiPanel.add(nearThreshold); | |
guiPanel.add(farThreshold); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
guiPanel.add(drawMode); | |
} | |
void ofApp::deviceAdded(std::string& serialNumber) | |
{ | |
ofLogNotice(__FUNCTION__) << "Starting device " << serialNumber; | |
auto device = rsContext.getDevice(serialNumber); | |
device->enableDepth(); | |
device->enableColor(); | |
device->enablePoints(); | |
device->startPipeline(); | |
// Uncomment this to add the device specific settings to the GUI. | |
//guiPanel.add(device->params); | |
} | |
void ofApp::update() | |
{ | |
rsContext.update(); | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice && rsDevice->isFrameNew()) | |
{ | |
// Threshold the depth. | |
ofFloatPixels rawDepthPix = rsDevice->getRawDepthPix(); | |
ofFloatPixels thresholdNear, thresholdFar, thresholdResult; | |
ofxCv::threshold(rawDepthPix, thresholdNear, nearThreshold); | |
ofxCv::threshold(rawDepthPix, thresholdFar, farThreshold, true); | |
ofxCv::bitwise_and(thresholdNear, thresholdFar, thresholdResult); | |
thresholdImg.setFromPixels(thresholdResult); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(thresholdImg); | |
// Draw CV operations. | |
visionFbo.begin(); | |
{ | |
// Draw the threshold background. | |
ofSetColor(255); | |
thresholdImg.draw(0, 0); | |
// Draw the contours over it. | |
ofSetColor(0, 255, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
// Draw 3D world. | |
pointsFbo.begin(); | |
{ | |
ofBackground(0); | |
camera.begin(); | |
ofEnableDepthTest(); | |
ofPushMatrix(); | |
// Adjust points to match OF 3D space. | |
ofScale(1000); | |
ofRotateXDeg(180); | |
rsDevice->getColorTex().bind(); | |
rsDevice->getPointsMesh().draw(); | |
rsDevice->getColorTex().unbind(); | |
ofPopMatrix(); | |
ofDisableDepthTest(); | |
camera.end(); | |
} | |
pointsFbo.end(); | |
} | |
} | |
void ofApp::draw() | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
ofRectangle camDrawRect; | |
if (drawMode == 0) | |
{ | |
// 2x2 window. | |
int frameWidth = ofGetWidth() / 2; | |
int frameHeight = ofGetHeight() / 2; | |
rsDevice->getDepthTex().draw(0, 0, frameWidth, frameHeight); | |
rsDevice->getColorTex().draw(0, frameHeight, frameWidth, frameHeight); | |
visionFbo.draw(frameWidth, 0, frameWidth, frameHeight); | |
camDrawRect = ofRectangle(frameWidth, frameHeight, frameWidth, frameHeight); | |
pointsFbo.draw(camDrawRect); | |
} | |
else if(drawMode == 1) | |
{ | |
// Draw fullscreen depth. | |
rsDevice->getDepthTex().draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 2) | |
{ | |
// Draw fullscreen color. | |
rsDevice->getColorTex().draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 3) | |
{ | |
// Draw fullscreen vision. | |
visionFbo.draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 4) | |
{ | |
// Draw fullscreen points. | |
camDrawRect = ofGetCurrentViewport(); | |
pointsFbo.draw(camDrawRect); | |
} | |
camera.setControlArea(camDrawRect); | |
} | |
// Draw the gui. | |
guiPanel.draw(); | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == OF_KEY_TAB) | |
{ | |
ofToggleFullscreen(); | |
} | |
else if (key == ' ') | |
{ | |
drawMode = (drawMode + 1) % 5; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
// Texture coordinates from RealSense are normalized (between 0-1). | |
// This call normalizes all OF texture coordinates so that they match. | |
ofDisableArbTex(); | |
ofSetWindowShape(1280, 720); | |
// Start the RealSense context. | |
// Devices are added in the deviceAdded() callback function. | |
ofAddListener(rsContext.deviceAddedEvent, this, &ofApp::deviceAdded); | |
rsContext.setup(false); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
visionFbo.allocate(settings); | |
pointsFbo.allocate(settings); | |
// Setup the parameters. | |
nearThreshold.set("Near Threshold", 0.01f, 0.0f, 0.1f); | |
farThreshold.set("Far Threshold", 0.02f, 0.0f, 0.1f); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
drawMode.set("Draw Mode", 0, 0, 4); | |
// Setup the gui. | |
guiPanel.setup("Depth Threshold", "settings.json"); | |
guiPanel.add(nearThreshold); | |
guiPanel.add(farThreshold); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
guiPanel.add(drawMode); | |
} | |
void ofApp::deviceAdded(std::string& serialNumber) | |
{ | |
ofLogNotice(__FUNCTION__) << "Starting device " << serialNumber; | |
auto device = rsContext.getDevice(serialNumber); | |
device->enableDepth(); | |
device->enableColor(); | |
device->enablePoints(); | |
device->startPipeline(); | |
// Uncomment this to add the device specific settings to the GUI. | |
//guiPanel.add(device->params); | |
} | |
void ofApp::update() | |
{ | |
rsContext.update(); | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice && rsDevice->isFrameNew()) | |
{ | |
// Threshold the depth. | |
ofFloatPixels rawDepthPix = rsDevice->getRawDepthPix(); | |
ofFloatPixels thresholdNear, thresholdFar, thresholdResult; | |
ofxCv::threshold(rawDepthPix, thresholdNear, nearThreshold); | |
ofxCv::threshold(rawDepthPix, thresholdFar, farThreshold, true); | |
ofxCv::bitwise_and(thresholdNear, thresholdFar, thresholdResult); | |
thresholdImg.setFromPixels(thresholdResult); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(thresholdImg); | |
// Draw CV operations. | |
visionFbo.begin(); | |
{ | |
// Draw the threshold background. | |
ofSetColor(255); | |
thresholdImg.draw(0, 0); | |
// Draw the contours over it. | |
ofSetColor(0, 255, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
// Draw 3D world. | |
pointsFbo.begin(); | |
{ | |
ofBackground(0); | |
camera.begin(); | |
ofEnableDepthTest(); | |
ofPushMatrix(); | |
// Adjust points to match OF 3D space. | |
ofScale(1000); | |
ofRotateXDeg(180); | |
rsDevice->getColorTex().bind(); | |
rsDevice->getPointsMesh().draw(); | |
rsDevice->getColorTex().unbind(); | |
ofPopMatrix(); | |
ofDisableDepthTest(); | |
camera.end(); | |
} | |
pointsFbo.end(); | |
} | |
} | |
void ofApp::draw() | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
if (drawMode == 0) | |
{ | |
// 2x2 window. | |
int frameWidth = ofGetWidth() / 2; | |
int frameHeight = ofGetHeight() / 2; | |
rsDevice->getDepthTex().draw(0, 0, frameWidth, frameHeight); | |
rsDevice->getColorTex().draw(0, frameHeight, frameWidth, frameHeight); | |
visionFbo.draw(frameWidth, 0, frameWidth, frameHeight); | |
pointsFbo.draw(frameWidth, frameHeight, frameWidth, frameHeight); | |
} | |
else if(drawMode == 1) | |
{ | |
// Draw fullscreen depth. | |
rsDevice->getDepthTex().draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 2) | |
{ | |
// Draw fullscreen color. | |
rsDevice->getColorTex().draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 3) | |
{ | |
// Draw fullscreen vision. | |
visionFbo.draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 4) | |
{ | |
// Draw fullscreen points. | |
pointsFbo.draw(ofGetCurrentViewport()); | |
} | |
} | |
// Draw the gui. | |
guiPanel.draw(); | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == OF_KEY_TAB) | |
{ | |
ofToggleFullscreen(); | |
} | |
else if (key == ' ') | |
{ | |
drawMode = (drawMode + 1) % 5; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
#include "ofxRealSense2.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void keyPressed(int key); | |
void deviceAdded(std::string& serialNumber); | |
ofxRealSense2::Context rsContext; | |
ofImage thresholdImg; | |
ofxCv::ContourFinder contourFinder; | |
ofEasyCam camera; | |
ofFbo visionFbo; | |
ofFbo pointsFbo; | |
ofParameter<float> nearThreshold; | |
ofParameter<float> farThreshold; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofParameter<int> drawMode; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
// Texture coordinates from RealSense are normalized (between 0-1). | |
// This call normalizes all OF texture coordinates so that they match. | |
ofDisableArbTex(); | |
ofSetWindowShape(1280, 720); | |
// Start the RealSense context. | |
// Devices are added in the deviceAdded() callback function. | |
ofAddListener(rsContext.deviceAddedEvent, this, &ofApp::deviceAdded); | |
rsContext.setup(false); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
visionFbo.allocate(settings); | |
// Setup the parameters. | |
nearThreshold.set("Near Threshold", 0.01f, 0.0f, 0.1f); | |
farThreshold.set("Far Threshold", 0.02f, 0.0f, 0.1f); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
// Setup the gui. | |
guiPanel.setup("Depth Threshold", "settings.json"); | |
guiPanel.add(nearThreshold); | |
guiPanel.add(farThreshold); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
} | |
void ofApp::deviceAdded(std::string& serialNumber) | |
{ | |
ofLogNotice(__FUNCTION__) << "Starting device " << serialNumber; | |
auto device = rsContext.getDevice(serialNumber); | |
device->enableDepth(); | |
device->enableColor(); | |
device->enablePoints(); | |
device->startPipeline(); | |
// Uncomment this to add the device specific settings to the GUI. | |
//guiPanel.add(device->params); | |
} | |
void ofApp::update() | |
{ | |
rsContext.update(); | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice && rsDevice->isFrameNew()) | |
{ | |
// Threshold the depth. | |
ofFloatPixels rawDepthPix = rsDevice->getRawDepthPix(); | |
ofFloatPixels thresholdNear, thresholdFar, thresholdResult; | |
ofxCv::threshold(rawDepthPix, thresholdNear, nearThreshold); | |
ofxCv::threshold(rawDepthPix, thresholdFar, farThreshold, true); | |
ofxCv::bitwise_and(thresholdNear, thresholdFar, thresholdResult); | |
thresholdImg.setFromPixels(thresholdResult); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(thresholdImg); | |
// Draw CV operations. | |
visionFbo.begin(); | |
{ | |
// Draw the threshold background. | |
ofSetColor(255); | |
thresholdImg.draw(0, 0); | |
// Draw the contours over it. | |
ofSetColor(0, 255, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
} | |
} | |
void ofApp::draw() | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
// 2x2 window. | |
int frameWidth = ofGetWidth() / 2; | |
int frameHeight = ofGetHeight() / 2; | |
rsDevice->getDepthTex().draw(0, 0, frameWidth, frameHeight); | |
rsDevice->getColorTex().draw(0, frameHeight, frameWidth, frameHeight); | |
visionFbo.draw(frameWidth, 0, frameWidth, frameHeight); | |
camera.begin(ofRectangle(frameWidth, frameHeight, frameWidth, frameHeight)); | |
ofEnableDepthTest(); | |
ofPushMatrix(); | |
// Adjust points to match OF 3D space. | |
ofScale(100); | |
ofRotateXDeg(180); | |
rsDevice->getColorTex().bind(); | |
rsDevice->getPointsMesh().draw(); | |
rsDevice->getColorTex().unbind(); | |
ofPopMatrix(); | |
ofDisableDepthTest(); | |
camera.end(); | |
} | |
// Draw the gui. | |
guiPanel.draw(); | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == OF_KEY_TAB) | |
{ | |
ofToggleFullscreen(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
#include "ofxRealSense2.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void keyPressed(int key); | |
void deviceAdded(std::string& serialNumber); | |
ofxRealSense2::Context rsContext; | |
ofImage thresholdImg; | |
ofxCv::ContourFinder contourFinder; | |
ofEasyCam camera; | |
ofFbo visionFbo; | |
ofParameter<float> nearThreshold; | |
ofParameter<float> farThreshold; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
ofSetWindowShape(640, 720); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
canvasFbo.allocate(settings); | |
visionFbo.allocate(settings); | |
// Setup the parameters. | |
tintColor.set("Tint Color", ofColor(0, 255, 0)); | |
clearFbo.set("Clear Background", false); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
// Setup the gui. | |
guiPanel.setup("Fbo Draw", "settings.json"); | |
guiPanel.add(tintColor); | |
guiPanel.add(clearFbo); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
} | |
void ofApp::update() | |
{ | |
canvasFbo.begin(); | |
{ | |
if (clearFbo) | |
{ | |
// Clear the background. | |
ofBackground(0); | |
clearFbo = false; | |
} | |
if (ofGetMousePressed() && !guiPanel.getShape().inside(ofGetMouseX(), ofGetMouseY())) | |
{ | |
// Draw a circle if the mouse is pressed and not over the GUI. | |
ofSetColor(255); | |
ofDrawCircle(ofGetMouseX(), ofGetMouseY(), 20); | |
} | |
} | |
canvasFbo.end(); | |
// Download the FBO data as pixels. | |
canvasFbo.readToPixels(canvasPixels); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(canvasPixels); | |
// Draw the result offscreen. | |
visionFbo.begin(); | |
{ | |
ofBackground(127); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
} | |
void ofApp::draw() | |
{ | |
// Draw the canvas above with no tint. | |
ofSetColor(255); | |
canvasFbo.draw(0, 0); | |
// Draw the contours below with a tint color. | |
ofSetColor(tintColor); | |
visionFbo.draw(0, 360); | |
guiPanel.draw(); | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == ' ') | |
{ | |
clearFbo = true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void keyPressed(int key); | |
ofxCv::ContourFinder contourFinder; | |
ofFbo canvasFbo; | |
ofFbo visionFbo; | |
ofPixels canvasPixels; | |
ofParameter<ofColor> tintColor; | |
ofParameter<bool> clearFbo; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
ofSetWindowShape(640, 720); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
canvasFbo.allocate(settings); | |
visionFbo.allocate(settings); | |
// Setup the parameters. | |
fullscreen.set("Fullscreen", false); | |
tintColor.set("Tint Color", ofColor(0, 255, 0)); | |
clearFbo.set("Clear Background", false); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
// Setup the gui. | |
guiPanel.setup("Fbo Draw", "settings.json"); | |
guiPanel.add(fullscreen); | |
guiPanel.add(tintColor); | |
guiPanel.add(clearFbo); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
} | |
void ofApp::update() | |
{ | |
canvasFbo.begin(); | |
{ | |
if (clearFbo) | |
{ | |
// Clear the background. | |
ofBackground(0); | |
clearFbo = false; | |
} | |
if (ofGetMousePressed() && !guiPanel.getShape().inside(ofGetMouseX(), ofGetMouseY())) | |
{ | |
// Draw a circle if the mouse is pressed and not over the GUI. | |
ofSetColor(255); | |
ofDrawCircle(ofGetMouseX(), ofGetMouseY(), 20); | |
} | |
} | |
canvasFbo.end(); | |
// Download the FBO data as pixels. | |
canvasFbo.readToPixels(canvasPixels); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(canvasPixels); | |
// Draw the result offscreen. | |
visionFbo.begin(); | |
{ | |
ofBackground(127); | |
canvasFbo.draw(0, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
} | |
void ofApp::draw() | |
{ | |
if (fullscreen) | |
{ | |
// Draw the contours fullscreen with no tint. | |
ofRectangle drawRect = ofRectangle(0, 0, visionFbo.getWidth(), visionFbo.getHeight()); | |
drawRect.scaleTo(ofGetCurrentViewport()); | |
ofSetColor(255); | |
visionFbo.draw(drawRect); | |
} | |
else | |
{ | |
// Draw the canvas above with no tint. | |
ofSetColor(255); | |
canvasFbo.draw(0, 0); | |
// Draw the contours below with a tint color. | |
ofSetColor(tintColor); | |
visionFbo.draw(0, 360); | |
} | |
guiPanel.draw(); | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == ' ') | |
{ | |
clearFbo = true; | |
} | |
else if (key == OF_KEY_TAB) | |
{ | |
fullscreen = !fullscreen; | |
ofSetFullscreen(fullscreen); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void keyPressed(int key); | |
ofxCv::ContourFinder contourFinder; | |
ofFbo canvasFbo; | |
ofFbo visionFbo; | |
ofPixels canvasPixels; | |
ofParameter<bool> fullscreen; | |
ofParameter<ofColor> tintColor; | |
ofParameter<bool> clearFbo; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
ofSetWindowShape(640, 720); | |
// Allocate the frame buffer. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
canvasFbo.allocate(settings); | |
// Setup the parameters. | |
tintColor.set("Tint Color", ofColor(0, 255, 0)); | |
clearFbo.set("Clear Background", false); | |
// Setup the gui. | |
guiPanel.setup("Fbo Draw", "settings.json"); | |
guiPanel.add(tintColor); | |
guiPanel.add(clearFbo); | |
} | |
void ofApp::update() | |
{ | |
canvasFbo.begin(); | |
{ | |
if (clearFbo) | |
{ | |
// Clear the background. | |
ofBackground(0); | |
clearFbo = false; | |
} | |
if (ofGetMousePressed() && !guiPanel.getShape().inside(ofGetMouseX(), ofGetMouseY())) | |
{ | |
// Draw a circle if the mouse is pressed and not over the GUI. | |
ofSetColor(255); | |
ofDrawCircle(ofGetMouseX(), ofGetMouseY(), 20); | |
} | |
} | |
canvasFbo.end(); | |
} | |
void ofApp::draw() | |
{ | |
// Draw the canvas above with no tint. | |
ofSetColor(255); | |
canvasFbo.draw(0, 0); | |
// Draw the canvas below with a tint color. | |
ofSetColor(tintColor); | |
canvasFbo.draw(0, 360); | |
guiPanel.draw(); | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == ' ') | |
{ | |
clearFbo = true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxGui.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void keyPressed(int key); | |
ofFbo canvasFbo; | |
ofParameter<ofColor> tintColor; | |
ofParameter<bool> clearFbo; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
// Texture coordinates from RealSense are normalized (between 0-1). | |
// This call normalizes all OF texture coordinates so that they match. | |
ofDisableArbTex(); | |
// Start the RealSense context. | |
// Devices are added in the deviceAdded() callback function. | |
ofAddListener(rsContext.deviceAddedEvent, this, &ofApp::deviceAdded); | |
rsContext.setup(false); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
visionFbo.allocate(settings); | |
colorFbo.allocate(settings); | |
pointsFbo.allocate(settings); | |
// Setup the parameters. | |
nearThreshold.set("Near Threshold", 0.01f, 0.0f, 0.1f); | |
farThreshold.set("Far Threshold", 0.02f, 0.0f, 0.1f); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
fadeAlpha.set("Fade Alpha", 16, 0, 255); | |
drawAlpha.set("Draw Alpha", 127, 0, 255); | |
// Setup the gui. | |
guiPanel.setup("Depth Threshold", "settings.json"); | |
guiPanel.add(nearThreshold); | |
guiPanel.add(farThreshold); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
guiPanel.add(fadeAlpha); | |
guiPanel.add(drawAlpha); | |
} | |
void ofApp::deviceAdded(std::string& serialNumber) | |
{ | |
ofLogNotice(__FUNCTION__) << "Starting device " << serialNumber; | |
auto device = rsContext.getDevice(serialNumber); | |
device->enableDepth(); | |
device->enableColor(); | |
device->enablePoints(); | |
device->startPipeline(); | |
device->alignMode = ofxRealSense2::Device::Align::Color; | |
// Uncomment this to add the device specific settings to the GUI. | |
//guiPanel.add(device->params); | |
} | |
void ofApp::update() | |
{ | |
rsContext.update(); | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice && rsDevice->isFrameNew()) | |
{ | |
// Threshold the depth. | |
ofFloatPixels rawDepthPix = rsDevice->getRawDepthPix(); | |
ofFloatPixels thresholdNear, thresholdFar, thresholdResult; | |
ofxCv::threshold(rawDepthPix, thresholdNear, nearThreshold); | |
ofxCv::threshold(rawDepthPix, thresholdFar, farThreshold, true); | |
ofxCv::bitwise_and(thresholdNear, thresholdFar, thresholdResult); | |
thresholdImg.setFromPixels(thresholdResult); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(thresholdImg); | |
// Draw CV operations. | |
visionFbo.begin(); | |
{ | |
// Draw the threshold background. | |
ofSetColor(255); | |
thresholdImg.draw(0, 0); | |
// Draw the contours over it. | |
ofSetColor(0, 255, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
// Draw masked color. | |
colorFbo.begin(); | |
{ | |
// Draw the color background. | |
rsDevice->getColorTex().draw(0, 0); | |
// Set the blend mode to multiply to clip out any black pixels. | |
ofEnableBlendMode(OF_BLENDMODE_MULTIPLY); | |
// Draw the threshold image on top. | |
// The result will be a colored threshold image. | |
thresholdImg.draw(0, 0); | |
} | |
colorFbo.end(); | |
// Draw 3D world. | |
pointsFbo.begin(); | |
{ | |
ofClear(0, 0); | |
camera.begin(); | |
ofPushMatrix(); | |
// Adjust points to match OF 3D space. | |
ofScale(1000); | |
ofRotateXDeg(180); | |
// Set the blend mode to screen to let any pixels with color through. | |
ofEnableBlendMode(OF_BLENDMODE_SCREEN); | |
// Draw the point cloud using the masked color texture. | |
colorFbo.getTexture().bind(); | |
rsDevice->getPointsMesh().draw(); | |
colorFbo.getTexture().unbind(); | |
ofPopMatrix(); | |
camera.end(); | |
} | |
pointsFbo.end(); | |
} | |
} | |
void ofApp::draw() | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
// 2x2 window. | |
int frameWidth = ofGetWidth() / 2; | |
int frameHeight = ofGetHeight() / 2; | |
rsDevice->getDepthTex().draw(0, 0, frameWidth, frameHeight); | |
colorFbo.draw(0, frameHeight, frameWidth, frameHeight); | |
visionFbo.draw(frameWidth, 0, frameWidth, frameHeight); | |
ofRectangle camDrawRect = ofRectangle(frameWidth, frameHeight, frameWidth, frameHeight); | |
pointsFbo.draw(camDrawRect); | |
camera.setControlArea(camDrawRect); | |
} | |
// Draw the gui. | |
guiPanel.draw(); | |
} | |
void ofApp::drawProjection(ofEventArgs& args) | |
{ | |
// Disable clearing the background automatically. | |
ofSetBackgroundAuto(false); | |
// Draw a background color with optional transparency. | |
ofSetColor(0, fadeAlpha); | |
ofDrawRectangle(ofGetCurrentViewport()); | |
// Draw fullscreen points. | |
ofSetColor(255, drawAlpha); | |
pointsFbo.draw(ofGetCurrentViewport()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
#include "ofxRealSense2.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void drawProjection(ofEventArgs& args); | |
void deviceAdded(std::string& serialNumber); | |
ofxRealSense2::Context rsContext; | |
ofImage thresholdImg; | |
ofxCv::ContourFinder contourFinder; | |
ofEasyCam camera; | |
ofFbo visionFbo; | |
ofFbo colorFbo; | |
ofFbo pointsFbo; | |
ofParameter<float> nearThreshold; | |
ofParameter<float> farThreshold; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofParameter<int> fadeAlpha; | |
ofParameter<int> drawAlpha; | |
ofxPanel guiPanel; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofApp.h" | |
void ofApp::setup() | |
{ | |
// Texture coordinates from RealSense are normalized (between 0-1). | |
// This call normalizes all OF texture coordinates so that they match. | |
ofDisableArbTex(); | |
// Start the RealSense context. | |
// Devices are added in the deviceAdded() callback function. | |
ofAddListener(rsContext.deviceAddedEvent, this, &ofApp::deviceAdded); | |
rsContext.setup(false); | |
// Allocate the frame buffers. | |
ofFboSettings settings; | |
settings.width = 640; | |
settings.height = 360; | |
visionFbo.allocate(settings); | |
pointsFbo.allocate(settings); | |
// Setup the parameters. | |
nearThreshold.set("Near Threshold", 0.01f, 0.0f, 0.1f); | |
farThreshold.set("Far Threshold", 0.02f, 0.0f, 0.1f); | |
minArea.set("Min Area", 0.01f, 0, 0.5f); | |
maxArea.set("Max Area", 0.05f, 0, 0.5f); | |
drawMode.set("Draw Mode", 0, 0, 3); | |
// Setup the gui. | |
guiPanel.setup("Depth Threshold", "settings.json"); | |
guiPanel.add(nearThreshold); | |
guiPanel.add(farThreshold); | |
guiPanel.add(minArea); | |
guiPanel.add(maxArea); | |
guiPanel.add(drawMode); | |
} | |
void ofApp::deviceAdded(std::string& serialNumber) | |
{ | |
ofLogNotice(__FUNCTION__) << "Starting device " << serialNumber; | |
auto device = rsContext.getDevice(serialNumber); | |
device->enableDepth(); | |
device->enableColor(); | |
device->enablePoints(); | |
device->startPipeline(); | |
// Uncomment this to add the device specific settings to the GUI. | |
//guiPanel.add(device->params); | |
} | |
void ofApp::update() | |
{ | |
rsContext.update(); | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice && rsDevice->isFrameNew()) | |
{ | |
// Threshold the depth. | |
ofFloatPixels rawDepthPix = rsDevice->getRawDepthPix(); | |
ofFloatPixels thresholdNear, thresholdFar, thresholdResult; | |
ofxCv::threshold(rawDepthPix, thresholdNear, nearThreshold); | |
ofxCv::threshold(rawDepthPix, thresholdFar, farThreshold, true); | |
ofxCv::bitwise_and(thresholdNear, thresholdFar, thresholdResult); | |
thresholdImg.setFromPixels(thresholdResult); | |
// Find contours. | |
contourFinder.setMinAreaNorm(minArea); | |
contourFinder.setMaxAreaNorm(maxArea); | |
contourFinder.findContours(thresholdImg); | |
// Draw CV operations. | |
visionFbo.begin(); | |
{ | |
// Draw the threshold background. | |
ofSetColor(255); | |
thresholdImg.draw(0, 0); | |
// Draw the contours over it. | |
ofSetColor(0, 255, 0); | |
contourFinder.draw(); | |
} | |
visionFbo.end(); | |
// Draw 3D world. | |
pointsFbo.begin(); | |
{ | |
ofBackground(0); | |
camera.begin(); | |
ofEnableDepthTest(); | |
ofPushMatrix(); | |
// Adjust points to match OF 3D space. | |
ofScale(1000); | |
ofRotateXDeg(180); | |
rsDevice->getColorTex().bind(); | |
rsDevice->getPointsMesh().draw(); | |
rsDevice->getColorTex().unbind(); | |
ofPopMatrix(); | |
ofDisableDepthTest(); | |
camera.end(); | |
} | |
pointsFbo.end(); | |
} | |
} | |
void ofApp::draw() | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
// 2x2 window. | |
int frameWidth = ofGetWidth() / 2; | |
int frameHeight = ofGetHeight() / 2; | |
rsDevice->getDepthTex().draw(0, 0, frameWidth, frameHeight); | |
rsDevice->getColorTex().draw(0, frameHeight, frameWidth, frameHeight); | |
visionFbo.draw(frameWidth, 0, frameWidth, frameHeight); | |
ofRectangle camDrawRect = ofRectangle(frameWidth, frameHeight, frameWidth, frameHeight); | |
pointsFbo.draw(camDrawRect); | |
camera.setControlArea(camDrawRect); | |
} | |
// Draw the gui. | |
guiPanel.draw(); | |
} | |
void ofApp::drawProjection(ofEventArgs& args) | |
{ | |
std::shared_ptr<ofxRealSense2::Device> rsDevice = rsContext.getDevice(0); | |
if (rsDevice) | |
{ | |
if (drawMode == 0) | |
{ | |
// Draw fullscreen depth. | |
rsDevice->getDepthTex().draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 1) | |
{ | |
// Draw fullscreen color. | |
rsDevice->getColorTex().draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 2) | |
{ | |
// Draw fullscreen vision. | |
visionFbo.draw(ofGetCurrentViewport()); | |
} | |
else if (drawMode == 3) | |
{ | |
// Draw fullscreen points. | |
pointsFbo.draw(ofGetCurrentViewport()); | |
} | |
} | |
} | |
void ofApp::keyPressed(int key) | |
{ | |
if (key == ' ') | |
{ | |
drawMode = (drawMode + 1) % 4; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "ofMain.h" | |
#include "ofxCv.h" | |
#include "ofxGui.h" | |
#include "ofxRealSense2.h" | |
class ofApp : public ofBaseApp | |
{ | |
public: | |
void setup(); | |
void update(); | |
void draw(); | |
void drawProjection(ofEventArgs& args); | |
void keyPressed(int key); | |
void deviceAdded(std::string& serialNumber); | |
ofxRealSense2::Context rsContext; | |
ofImage thresholdImg; | |
ofxCv::ContourFinder contourFinder; | |
ofEasyCam camera; | |
ofFbo visionFbo; | |
ofFbo pointsFbo; | |
ofParameter<float> nearThreshold; | |
ofParameter<float> farThreshold; | |
ofParameter<float> minArea; | |
ofParameter<float> maxArea; | |
ofParameter<int> drawMode; | |
ofxPanel guiPanel; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment