diff --git a/components/operators/CMakeLists.txt b/components/operators/CMakeLists.txt
index d946fae15ff253467a2e4bc56b99bbb7f6045938..54da0eb4be9284b27ac9d5f9c338a7fa98eb6987 100644
--- a/components/operators/CMakeLists.txt
+++ b/components/operators/CMakeLists.txt
@@ -1,5 +1,5 @@
 add_library(ftloperators
-    src/smoothing.cpp
+	src/smoothing.cpp
 	src/smoothing.cu
 	src/mls.cu
 	src/smoothchan.cu
@@ -8,6 +8,7 @@ add_library(ftloperators
 	src/normals.cpp
 	src/filling.cpp
 	src/filling.cu
+	src/nvopticalflow.cpp
 )
 
 # These cause errors in CI build and are being removed from PCL in newer versions
diff --git a/components/operators/include/ftl/operators/operator.hpp b/components/operators/include/ftl/operators/operator.hpp
index fe9b52f2c6b7d6d3364c1d1722f70bd691602170..04f542b3b1b088b53adfdb79cb012d9fb3e89e9c 100644
--- a/components/operators/include/ftl/operators/operator.hpp
+++ b/components/operators/include/ftl/operators/operator.hpp
@@ -23,7 +23,7 @@ namespace operators {
 class Operator {
 	public:
 	explicit Operator(ftl::Configurable *cfg);
-    virtual ~Operator();
+	virtual ~Operator();
 
 	enum class Type {
 		OneToOne,		// Frame to Frame (Filter or generator)
@@ -87,7 +87,7 @@ struct OperatorNode {
 class Graph : public ftl::Configurable {
 	public:
 	explicit Graph(nlohmann::json &config);
-    ~Graph();
+	~Graph();
 
 	template <typename T>
 	ftl::Configurable *append(const std::string &name);
diff --git a/components/operators/include/ftl/operators/opticalflow.hpp b/components/operators/include/ftl/operators/opticalflow.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1cbd58b76a33e4956fc45fc94afc32804f742fb2
--- /dev/null
+++ b/components/operators/include/ftl/operators/opticalflow.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <ftl/operators/operator.hpp>
+#include <opencv2/cudaoptflow.hpp>
+
+namespace ftl {
+namespace operators {
+
+class NVOpticalFlow : public ftl::operators::Operator {
+	public:
+	explicit NVOpticalFlow(ftl::Configurable*);
+	~NVOpticalFlow();
+
+	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
+
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+
+	protected:
+	void init();
+
+	private:
+	cv::Size size_;
+	
+	// TODO: Left to Flow always assumed, could also calculate something else?
+	const ftl::codecs::Channel channel_in_ = ftl::codecs::Channel::Left;
+	const ftl::codecs::Channel channel_out_ = ftl::codecs::Channel::Flow;
+
+	cv::Ptr<cv::cuda::NvidiaOpticalFlow_1_0> nvof_;
+	cv::cuda::GpuMat left_gray_;
+	cv::cuda::GpuMat left_gray_prev_;
+};
+
+}
+}
diff --git a/components/operators/include/ftl/operators/smoothing.hpp b/components/operators/include/ftl/operators/smoothing.hpp
index b5362ea6f1a6c16de81cdb7c55339058476da09d..f6683fcc3e665f67b708a8bf11140bfabf179060 100644
--- a/components/operators/include/ftl/operators/smoothing.hpp
+++ b/components/operators/include/ftl/operators/smoothing.hpp
@@ -13,17 +13,17 @@ namespace operators {
  * first derivative. Only the depth channel is used and modified.
  */
 class HFSmoother : public ftl::operators::Operator {
-    public:
-    explicit HFSmoother(ftl::Configurable*);
-    ~HFSmoother();
+	public:
+	explicit HFSmoother(ftl::Configurable*);
+	~HFSmoother();
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
 
-    private:
-    cv::cuda::GpuMat temp_;
-    ftl::rgbd::Frame frames_[4];
+	private:
+	cv::cuda::GpuMat temp_;
+	ftl::rgbd::Frame frames_[4];
 };
 
 /**
@@ -34,13 +34,13 @@ class HFSmoother : public ftl::operators::Operator {
  * no smoothing.
  */
 class SmoothChannel : public ftl::operators::Operator {
-    public:
-    explicit SmoothChannel(ftl::Configurable*);
-    ~SmoothChannel();
+	public:
+	explicit SmoothChannel(ftl::Configurable*);
+	~SmoothChannel();
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
 
 	private:
 	ftl::rgbd::Frame temp_[6];
@@ -52,15 +52,15 @@ class SmoothChannel : public ftl::operators::Operator {
  * Also outputs Depth + Normals.
  */
 class SimpleMLS : public ftl::operators::Operator {
-    public:
-    explicit SimpleMLS(ftl::Configurable*);
-    ~SimpleMLS();
+	public:
+	explicit SimpleMLS(ftl::Configurable*);
+	~SimpleMLS();
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
 
-    private:
+	private:
 };
 
 /**
@@ -68,15 +68,15 @@ class SimpleMLS : public ftl::operators::Operator {
  * by a simple colour similarity weighting. In practice this is too naive.
  */
 class ColourMLS : public ftl::operators::Operator {
-    public:
-    explicit ColourMLS(ftl::Configurable*);
-    ~ColourMLS();
+	public:
+	explicit ColourMLS(ftl::Configurable*);
+	~ColourMLS();
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
 
-    private:
+	private:
 };
 
 /**
@@ -87,15 +87,15 @@ class ColourMLS : public ftl::operators::Operator {
  * it can be only a few or even no pixels with a zero smoothing factor.
  */
 class AdaptiveMLS : public ftl::operators::Operator {
-    public:
-    explicit AdaptiveMLS(ftl::Configurable*);
-    ~AdaptiveMLS();
+	public:
+	explicit AdaptiveMLS(ftl::Configurable*);
+	~AdaptiveMLS();
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
 
-    private:
+	private:
 };
 
 }
diff --git a/components/operators/src/nvopticalflow.cpp b/components/operators/src/nvopticalflow.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba4fefa6bbe3c635c8bfadf880e31eb9f38d9eaf
--- /dev/null
+++ b/components/operators/src/nvopticalflow.cpp
@@ -0,0 +1,48 @@
+#include <ftl/operators/opticalflow.hpp>
+
+using ftl::rgbd::Frame;
+using ftl::rgbd::Source;
+using ftl::codecs::Channel;
+
+using ftl::operators::NVOpticalFlow;
+
+using cv::Size;
+using cv::cuda::GpuMat;
+
+NVOpticalFlow::NVOpticalFlow(ftl::Configurable* cfg) :
+		ftl::operators::Operator(cfg) {
+	size_ = Size(0, 0);
+}
+
+NVOpticalFlow::~NVOpticalFlow() {
+}
+
+void NVOpticalFlow::init() {
+	nvof_ = cv::cuda::NvidiaOpticalFlow_1_0::create(
+				size_.width, size_.height, 
+				cv::cuda::NvidiaOpticalFlow_1_0::NV_OF_PERF_LEVEL_SLOW,
+				true, false, false, 0);
+	
+	left_gray_.create(size_, CV_8UC1);
+	left_gray_prev_.create(size_, CV_8UC1);
+}
+
+bool NVOpticalFlow::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) {
+	if (!in.hasChannel(channel_in_)) {
+		return true; // false?
+	}
+
+	if (in.get<GpuMat>(channel_in_).size() != size_) {
+		size_ = in.get<GpuMat>(channel_in_).size();
+		init();
+	}
+	
+	auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
+	auto &flow = out.create<GpuMat>(channel_out_);
+
+	cv::cuda::cvtColor(in.get<GpuMat>(channel_in_), left_gray_, cv::COLOR_BGR2GRAY, 0, cvstream);
+
+	nvof_->calc(left_gray_, left_gray_prev_, flow, cvstream);
+
+	return true;
+}
\ No newline at end of file
diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt
index 0dbd5ccf8cca5aecea9b270c6fc28d4b53b3b4ac..7b81e216d9adb11c5898493077bd02dd2121dfbb 100644
--- a/components/rgbd-sources/CMakeLists.txt
+++ b/components/rgbd-sources/CMakeLists.txt
@@ -69,7 +69,7 @@ set_property(TARGET ftlrgbd PROPERTY CUDA_SEPARABLE_COMPILATION OFF)
 endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES} ftlcodecs)
+target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES} ftlcodecs ftloperators)
 
 add_subdirectory(test)
 
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
index f8e08a9f6d1f7670adeb8be9310129e8353daf1b..8f0d6e345e1db51b0fbe9a1a867805d4e449d49a 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
@@ -1,6 +1,9 @@
 #include <loguru.hpp>
 #include "stereovideo.hpp"
+
 #include <ftl/configuration.hpp>
+#include <ftl/operators/opticalflow.hpp>
+
 #include <ftl/threads.hpp>
 #include "calibrate.hpp"
 #include "local.hpp"
@@ -58,16 +61,24 @@ void StereoVideoSource::init(const string &file)
 		lsrc_ = ftl::create<LocalSource>(host_, "feed");
 	}
 
+	// Create the source depth map pipeline
+	pipeline_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity");
+	/*pipeline1->append<ftl::operators::ColourChannels>("colour");  // Convert BGR to BGRA
+	pipeline1->append<ftl::operators::HFSmoother>("hfnoise");  // Remove high-frequency noise
+	pipeline1->append<ftl::operators::Normals>("normals");  // Estimate surface normals
+	pipeline1->append<ftl::operators::SmoothChannel>("smoothing");  // Generate a smoothing channel
+	//pipeline1->append<ftl::operators::ScanFieldFill>("filling");  // Generate a smoothing channel
+	pipeline1->append<ftl::operators::ColourMLS>("mls");  // Perform MLS (using smoothing channel)
+	*/
+
 	cv::Size size = cv::Size(lsrc_->width(), lsrc_->height());
 	frames_ = std::vector<Frame>(2);
 
 #ifdef HAVE_OPTFLOW
+
 	use_optflow_ =  host_->value("use_optflow", false);
 	LOG(INFO) << "Using optical flow: " << (use_optflow_ ? "true" : "false");
-
-	nvof_ = cv::cuda::NvidiaOpticalFlow_1_0::create(size.width, size.height,
-													cv::cuda::NvidiaOpticalFlow_1_0::NV_OF_PERF_LEVEL_SLOW,
-													true, false, false, 0);
+	pipeline_->append<ftl::operators::NVOpticalFlow>("optflow");
 
 #endif
 
@@ -179,10 +190,11 @@ bool StereoVideoSource::retrieve() {
 	auto &left = frame.create<cv::cuda::GpuMat>(Channel::Left);
 	auto &right = frame.create<cv::cuda::GpuMat>(Channel::Right);
 	lsrc_->get(left, right, calib_, stream2_);
+	pipeline_->apply(frame, frame, (ftl::rgbd::Source*) lsrc_, cv::cuda::StreamAccessor::getStream(stream2_));
 
 #ifdef HAVE_OPTFLOW
 	// see comments in https://gitlab.utu.fi/nicolas.pope/ftl/issues/155
-	
+	/*
 	if (use_optflow_)
 	{
 		auto &left_gray = frame.create<cv::cuda::GpuMat>(Channel::LeftGray);
@@ -201,7 +213,7 @@ bool StereoVideoSource::retrieve() {
 			// cv::cuda::resize() does not work wiht 2-channel input
 			// cv::cuda::resize(optflow_, optflow, left.size(), 0.0, 0.0, cv::INTER_NEAREST, stream2_);
 		}
-	}
+	}*/
 #endif
 
 	stream2_.waitForCompletion();
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
index dfea7937f99777c23f753a7ab2b5bad8c68bb4af..eb29186770fd0fe2347be20653ff51ec9b5080b4 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
@@ -3,6 +3,7 @@
 #define _FTL_RGBD_STEREOVIDEO_HPP_
 
 #include <ftl/rgbd/source.hpp>
+#include <ftl/operators/operator.hpp>
 #include <string>
 
 namespace ftl {
@@ -39,7 +40,9 @@ class StereoVideoSource : public detail::Source {
 	LocalSource *lsrc_;
 	Calibrate *calib_;
 	Disparity *disp_;
-	
+
+	ftl::operators::Graph *pipeline_;
+
 	bool ready_;
 	bool use_optflow_;