diff --git a/SDK/CPP/private/pipeline_impl.cpp b/SDK/CPP/private/pipeline_impl.cpp
index 114f73ad4b19a0bf8db3053bdc25b6260e63351c..cdc52fce3eb188cabf35a2e2e80353398e4a23ef 100644
--- a/SDK/CPP/private/pipeline_impl.cpp
+++ b/SDK/CPP/private/pipeline_impl.cpp
@@ -6,6 +6,8 @@
 #include <ftl/operators/fusion.hpp>
 #include <ftl/operators/gt_analysis.hpp>
 
+#include <loguru.hpp>
+
 using voltu::internal::PipelineImpl;
 
 PipelineImpl::PipelineImpl(ftl::Configurable *root)
@@ -28,7 +30,7 @@ void PipelineImpl::submit(const voltu::FramePtr &frame)
 
 	const auto &sets = fimp->getInternalFrameSets();
 
-	if (sets.size() > 1) throw voltu::exceptions::IncompatibleOperation();
+	if (sets.size() > 1 || sets.size() == 0) throw voltu::exceptions::IncompatibleOperation();
 
 	for (const auto &fs : sets)
 	{
diff --git a/SDK/CPP/private/room_impl.cpp b/SDK/CPP/private/room_impl.cpp
index 76ab1b92bad65d098c8a0f2a34685edc9219e458..764b5740341202140bfaade2bbb0207ea46ef8b8 100644
--- a/SDK/CPP/private/room_impl.cpp
+++ b/SDK/CPP/private/room_impl.cpp
@@ -20,7 +20,7 @@ bool RoomImpl::waitNextFrame(int64_t timeout)
 {
 	if (!filter_)
 	{
-		filter_ = feed_->filter(fsids_, {ftl::codecs::Channel::Colour, ftl::codecs::Channel::Depth});
+		filter_ = feed_->filter(fsids_, {ftl::codecs::Channel::Colour, ftl::codecs::Channel::Depth, ftl::codecs::Channel::GroundTruth});
 		filter_->on([this](const std::shared_ptr<ftl::data::FrameSet> &fs)
 		{
 			UNIQUE_LOCK(mutex_, lk);
@@ -36,7 +36,7 @@ bool RoomImpl::waitNextFrame(int64_t timeout)
 	{
 		if (timeout > 0)
 		{
-			cv_.wait_for(lk, std::chrono::seconds(timeout), [this] {
+			cv_.wait_for(lk, std::chrono::milliseconds(timeout), [this] {
 				return last_read_ < last_seen_;
 			});
 
diff --git a/SDK/CPP/public/CMakeLists.txt b/SDK/CPP/public/CMakeLists.txt
index a0eb7eaf58262395b4232cba3a0421cb144ccfca..b3a4d7dc20ec1df5fc529d4a65b805855ec09c11 100644
--- a/SDK/CPP/public/CMakeLists.txt
+++ b/SDK/CPP/public/CMakeLists.txt
@@ -64,6 +64,7 @@ target_link_libraries(voltu_basic_virtual_cam voltu_sdk)
 
 add_executable(voltu_fusion_evaluator
 	samples/fusion_evaluator/main.cpp
+	samples/common/cmd_args.cpp
 )
 target_link_libraries(voltu_fusion_evaluator voltu_sdk)
 
diff --git a/SDK/CPP/public/samples/common/cmd_args.cpp b/SDK/CPP/public/samples/common/cmd_args.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7219fb2158134b7bd9975cb49db6f97d2adcac3b
--- /dev/null
+++ b/SDK/CPP/public/samples/common/cmd_args.cpp
@@ -0,0 +1,35 @@
+#include "cmd_args.hpp"
+
+std::map<std::string, std::string> read_options(char ***argv, int *argc)
+{
+	std::map<std::string, std::string> opts;
+
+	(*argc)--;  // Remove application path
+	(*argv)++;
+
+	while (*argc > 0) {
+		std::string cmd((*argv)[0]);
+
+		size_t p;
+		if (cmd[0] != '-' || (p = cmd.find("=")) == std::string::npos) {
+			opts[cmd.substr(0)] = "true";
+		} else {
+			auto val = cmd.substr(p+1);
+#ifdef WIN32
+			if ((val[0] >= 48 && val[0] <= 57) || val == "true" || val == "false" || val == "null") {
+#else
+			if (std::isdigit(val[0]) || val == "true" || val == "false" || val == "null") {
+#endif
+				opts[cmd.substr(0, p-2)] = val;
+			} else {
+				if (val[0] == '\\') opts[cmd.substr(2, p-2)] = val;
+				else opts[cmd.substr(0, p-2)] = "\""+val+"\"";
+			}
+		}
+
+		(*argc)--;
+		(*argv)++;
+	}
+
+	return opts;
+}
diff --git a/SDK/CPP/public/samples/common/cmd_args.hpp b/SDK/CPP/public/samples/common/cmd_args.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a42486b421cd4a59e7727926ba25dbdd7a1d87e6
--- /dev/null
+++ b/SDK/CPP/public/samples/common/cmd_args.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <map>
+#include <string>
+
+std::map<std::string, std::string> read_options(char ***argv, int *argc);
diff --git a/SDK/CPP/public/samples/fusion_evaluator/main.cpp b/SDK/CPP/public/samples/fusion_evaluator/main.cpp
index 908d0925ed80bf5d4d125d6f184e6dc0d7a9f906..7eebe3461965ff15804aec51cef0592e9717caa5 100644
--- a/SDK/CPP/public/samples/fusion_evaluator/main.cpp
+++ b/SDK/CPP/public/samples/fusion_evaluator/main.cpp
@@ -4,6 +4,8 @@
 #include <thread>
 #include <chrono>
 
+#include "../common/cmd_args.hpp"
+
 #include <opencv2/highgui.hpp>
 
 using std::cout;
@@ -12,19 +14,51 @@ using std::string;
 
 int main(int argc, char **argv)
 {
-	if (argc != 2) return -1;
+	bool do_fusion = true;
+	bool do_eval = true;
+	voltu::Channel display_channel = voltu::Channel::kColour;
+	std::list<std::string> paths;
+
+	auto opts = read_options(&argv, &argc);
+
+	for (const auto &s : opts)
+	{
+		if (s.first == "--no-fusion")
+		{
+			do_fusion = false;
+		}
+		else if (s.first == "--display")
+		{
+			if (s.second == "normals")
+			{
+				display_channel = voltu::Channel::kNormals;
+			}
+		}
+		else if (s.first == "--no-eval")
+		{
+			do_eval = false;
+		}
+		else if (s.first[0] != '-')
+		{
+			paths.push_back(s.first);
+		}
+	}
 
 	auto vtu = voltu::instance();
 
-	if (!vtu->open(argv[1]))
+	for (const auto &p : paths)
 	{
-		cout << "Could not open source" << endl;
-		return -1;
+		if (!vtu->open(p))
+		{
+			cout << "Could not open source" << endl;
+			return -1;
+		}
 	}
 
 	while (vtu->listRooms().size() == 0)
 	{
 		std::this_thread::sleep_for(std::chrono::milliseconds(100));
+		cout << "Wait room..." << endl;
 	}
 
 	auto room = vtu->getRoom(vtu->listRooms().front());
@@ -34,18 +68,31 @@ int main(int argc, char **argv)
 		return -1;
 	}
 
+	//room->waitNextFrame(5000);
 	auto frame = room->getFrame();
 
 	auto pipe = vtu->createPipeline();
 	auto op1 = pipe->appendOperator(voltu::OperatorId::kFusion);
 	auto op2 = pipe->appendOperator(voltu::OperatorId::kGTEvaluator);
 
+	op1->property("enabled")->setBool(do_fusion);
+	op2->property("enabled")->setBool(do_eval);
 	op2->property("show_colour")->setBool(true);
 
 	pipe->submit(frame);
-	pipe->waitCompletion(1000);
+	if (!pipe->waitCompletion(3000))
+	{
+		cout << "Pipeline timeout" << endl;
+		return -1;
+	}
+
+	auto imgset = frame->getImageSet(display_channel);
 
-	auto imgset = frame->getImageSet(voltu::Channel::kColour);
+	if (imgset.size() == 0)
+	{
+		cout << "No images!" << endl;
+		return -1;
+	}
 
 	for (auto img : imgset)
 	{
diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp
index 6e094a0e5b1fe35af04eb651b327cf3c79d5bc3e..9133d394478cc8f9f709020833b0f6d74bf1f539 100644
--- a/components/codecs/src/opencv_decoder.cpp
+++ b/components/codecs/src/opencv_decoder.cpp
@@ -36,6 +36,8 @@ bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 
 	if (tmp2_.type() == CV_8UC3) {
 		cv::cvtColor(tmp2_, tmp_, cv::COLOR_RGB2BGRA);
+	} else if (tmp2_.type() == CV_8U) {
+		tmp_ = tmp2_;
 	} else {
 		if (pkt.flags & ftl::codecs::kFlagFlipRGB) {
 			cv::cvtColor(tmp2_, tmp_, cv::COLOR_RGBA2BGRA);
diff --git a/components/operators/include/ftl/operators/fusion.hpp b/components/operators/include/ftl/operators/fusion.hpp
index 0062f1b648f37c6fbd21a9068e8f6f6da35ab017..fb0dec87e712636c4c98fdb6690ad247dfbbdc8a 100644
--- a/components/operators/include/ftl/operators/fusion.hpp
+++ b/components/operators/include/ftl/operators/fusion.hpp
@@ -17,7 +17,7 @@ class Fusion : public ftl::operators::Operator {
 
 	bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream) override;
 
-	static void configuration(ftl::Configurable*) {}
+	static void configuration(ftl::Configurable*);
 
 	private:
 	ftl::cuda::MLSMultiIntensity mls_;
diff --git a/components/operators/src/analysis/evaluation/gt_analysis.cpp b/components/operators/src/analysis/evaluation/gt_analysis.cpp
index c9559084eb53da55e13c8c4b22ccb221fcd9cb3f..b80d2beba2fdab6ffb93d887259eb297f786f1d3 100644
--- a/components/operators/src/analysis/evaluation/gt_analysis.cpp
+++ b/components/operators/src/analysis/evaluation/gt_analysis.cpp
@@ -1,6 +1,8 @@
 #include <ftl/operators/gt_analysis.hpp>
 #include <ftl/operators/cuda/gt.hpp>
 
+#include <opencv2/core/cuda_stream_accessor.hpp>
+
 using ftl::operators::GTAnalysis;
 using ftl::codecs::Channel;
 using std::string;
@@ -10,6 +12,7 @@ GTAnalysis::GTAnalysis(ftl::operators::Graph *g, ftl::Configurable *cfg) : ftl::
 }
 
 void GTAnalysis::configuration(ftl::Configurable *cfg) {
+	cfg->value("enabled", true);
 	cfg->value("use_disparity", true);
 	cfg->value("show_colour", false);
 }
@@ -72,6 +75,14 @@ bool GTAnalysis::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t
 	const float npixels = dmat.rows * dmat.cols;
 	ftl::cuda::GTAnalysisData err;
 
+	if (!in.hasChannel(Channel::Mask)) {
+		cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
+
+		auto &m = in.create<cv::cuda::GpuMat>(Channel::Mask);
+		m.create(dmat.size(), CV_8UC1);
+		m.setTo(cv::Scalar(0), cvstream);
+	}
+
 	for (const auto &o : (use_disp ? options_disparity : options_depth)) {
 		if (config()->value("show_colour", false)) {
 			ftl::cuda::gt_analysis(
diff --git a/components/operators/src/fusion/carving/carver.cu b/components/operators/src/fusion/carving/carver.cu
index d1dd480ef0cbbf2ea4a75595fd6af4fee712297d..1fc2013e0a7cdc862da4b243bfcfcf27582c7ec3 100644
--- a/components/operators/src/fusion/carving/carver.cu
+++ b/components/operators/src/fusion/carving/carver.cu
@@ -95,6 +95,7 @@ __global__ void reverse_check_kernel(
 			// TODO: Threshold comes from depth error characteristics
 			// If the value is significantly further then carve. Depth error
 			// is not always easy to calculate, depends on source.
+			// FIXME: Use length between 3D points, not depth
 			if (!(d2 < ointrin.maxDepth && d2 - campos.z > d2*d2*err_coef)) {
 				match = fabsf(campos.z - d2) < d2*d2*err_coef; break;
 			}
diff --git a/components/operators/src/fusion/fusion.cpp b/components/operators/src/fusion/fusion.cpp
index 5e0a624b4029eb7ad8d1e00bbda9dc9a60ca5e9f..606df89d69a166f32441ef2fdf600b458d50e1ee 100644
--- a/components/operators/src/fusion/fusion.cpp
+++ b/components/operators/src/fusion/fusion.cpp
@@ -12,6 +12,13 @@ using ftl::operators::Fusion;
 using ftl::codecs::Channel;
 using cv::cuda::GpuMat;
 
+void Fusion::configuration(ftl::Configurable *cfg) {
+	cfg->value("enabled", true);
+	cfg->value("mls_smoothing", 2.0f);
+	cfg->value("mls_iterations", 2);
+	cfg->value("visibility_carving", true);
+}
+
 Fusion::Fusion(ftl::operators::Graph *g, ftl::Configurable *cfg) : ftl::operators::Operator(g, cfg), mls_(3) {
 
 }
@@ -35,7 +42,11 @@ bool Fusion::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream
 		const GpuMat &d = in.frames[i].get<GpuMat>(Channel::Depth);
 
 		cv::cuda::cvtColor(col, temp_, cv::COLOR_BGRA2GRAY, 0, cvstream);
-		cv::cuda::resize(temp_, temp2_, d.size(), 0, 0, cv::INTER_LINEAR, cvstream);
+		if (temp_.size() != d.size()) {
+			cv::cuda::resize(temp_, temp2_, d.size(), 0, 0, cv::INTER_LINEAR, cvstream);
+		} else {
+			temp2_ = temp_;
+		}
 
 		// TODO: Not the best since the mean is entirely lost here.
 		// Perhaps check mean also with greater smoothing value
diff --git a/components/operators/src/fusion/smoothing/mls_multi_weighted.cu b/components/operators/src/fusion/smoothing/mls_multi_weighted.cu
index b9fbec08e15959d265809c928ae5092b6bc50bdd..11248e93bdbf49df3e551391327908cb57382ec6 100644
--- a/components/operators/src/fusion/smoothing/mls_multi_weighted.cu
+++ b/components/operators/src/fusion/smoothing/mls_multi_weighted.cu
@@ -372,7 +372,7 @@ __global__ void mean_subtract_kernel(
 		mean /= float((2*RADIUS+1)*(2*RADIUS+1));
 
 		float diff = float(intensity[x+y*pitch]) - mean;
-		contrast[x+y*pitch] = make_uchar2(max(0, min(254, int(diff)+127)), int(mean));
+		contrast[x+y*cpitch] = make_uchar2(max(0, min(254, int(diff)+127)), int(mean));
 	}
 }
 
diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp
index 496c48807a693e44409b4e59f4de1bdc5ca7c80f..255d438c089a5a7dc0cf27db21c9f6dba96bf408 100644
--- a/components/rgbd-sources/src/frame.cpp
+++ b/components/rgbd-sources/src/frame.cpp
@@ -71,7 +71,7 @@ cv::cuda::GpuMat &VideoFrame::createGPU(const ftl::rgbd::FormatBase &f) {
 }
 
 const cv::Mat &VideoFrame::getCPU() const {
-	if (!validhost) {
+	if (!validhost && !gpu.empty()) {
 		// TODO: Use stream and page locked mem.
 		gpu.download(host);
 		validhost = true;
diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp
index 2cd767af94ae51af325226340214815aecd5b41f..b51cc2ba9620fa5dadcf150a70000d4eada9a8b8 100644
--- a/components/streams/src/feed.cpp
+++ b/components/streams/src/feed.cpp
@@ -534,7 +534,7 @@ void Feed::_createPipeline(uint32_t fsid) {
 		p->append<ftl::operators::BorderMask>("border_mask");
 		p->append<ftl::operators::CullDiscontinuity>("remove_discontinuity");
 		p->append<ftl::operators::MultiViewMLS>("mvmls")->value("enabled", false);
-		p->append<ftl::operators::Fusion>("fusion")->value("enabled", false);
+		p->append<ftl::operators::Fusion>("fusion")->set("enabled", false);
 		p->append<ftl::operators::DisplayMask>("display_mask")->value("enabled", false);
 		p->append<ftl::operators::Poser>("poser")->value("enabled", true);
 		p->append<ftl::operators::GTAnalysis>("gtanalyse");
@@ -1115,8 +1115,12 @@ std::string Feed::getSourceURI(ftl::data::FrameID id) {
 
 std::vector<unsigned int> Feed::listFrameSets() {
 	SHARED_LOCK(mtx_, lk);
+
+	cudaDeviceSynchronize();
+	cudaSafeCall( cudaGetLastError() );
+
 	std::vector<unsigned int> result;
-	result.reserve(fsid_lookup_.size());
+	result.reserve(latest_.size());
 	for (const auto [k, fs] : latest_) {
 		if (fs) {
 			result.push_back(k);
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index 97c58c7dcf8f727a8b8dd58321caf15e33170b88..be3d01d853498d3068c75805018a972c6e9c55fd 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -343,7 +343,7 @@ void Receiver::_processVideo(const StreamPacket &spkt, const Packet &pkt) {
 	int cvtype = ftl::codecs::type(spkt.channel);
 
 	if (surface.type() != cvtype) {
-		LOG(ERROR) << "Invalid video format received";
+		LOG(ERROR) << "Invalid video format received: " << cvtype << " for " << (int)spkt.channel;
 		_terminateVideoPacket(spkt, pkt);
 		return;
 	}