diff --git a/applications/reconstruct/include/ftl/voxel_scene.hpp b/applications/reconstruct/include/ftl/voxel_scene.hpp
index 57634d2f14662c7f298feeb81785a39eee7872e9..6b7a6080e1662af27705e3c422de0a16708910f0 100644
--- a/applications/reconstruct/include/ftl/voxel_scene.hpp
+++ b/applications/reconstruct/include/ftl/voxel_scene.hpp
@@ -14,6 +14,12 @@
 namespace ftl {
 namespace voxhash {
 
+struct Cameras {
+	ftl::rgbd::Source *source;
+	DepthCameraData gpu;
+	DepthCameraParams params;
+};
+
 class SceneRep : public ftl::Configurable {
 	public:
 	SceneRep(nlohmann::json &config);
@@ -24,7 +30,7 @@ class SceneRep : public ftl::Configurable {
 	/**
 	 * Send all camera frames to GPU and allocate required voxels.
 	 */
-	void upload();
+	int upload();
 
 	/**
 	 * Merge all camera frames into the voxel hash datastructure.
@@ -86,7 +92,9 @@ class SceneRep : public ftl::Configurable {
 
 	//CUDAScan		m_cudaScan;
 	unsigned int	m_numIntegratedFrames;	//used for garbage collect
+	unsigned int	m_frameCount;
 	bool do_reset_;
+	std::vector<Cameras> cameras_;
 };
 
 };  // namespace voxhash
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index 1ba82399cd9250b5353e297b5a77c0a78ecc577c..de5cc68756bd3b9bff75fb6f591e665d8a621aa7 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -88,24 +88,17 @@ static void run(ftl::Configurable *root) {
 		return;
 	}
 
-	for (int i=0; i<sources.size(); i++) {
-		Source *in = sources[i];
-		auto &cam = inputs.emplace_back();
-		cam.source = in;
-		cam.params.m_imageWidth = 0;
-	}
-
 	// TODO	move this block in its own method and add automated tests
 	//		for error cases
 	
 	std::optional<json_t> merge = root->get<json_t>("merge");
 	if (!merge) {
 		LOG(WARNING) << "Input merging not configured, using only first input in configuration";
-		inputs = { inputs[0] };
-		inputs[0].source->setPose(Eigen::Matrix4d::Identity());
+		sources = { sources[0] };
+		sources[0]->setPose(Eigen::Matrix4d::Identity());
 	}
 
-	if (inputs.size() > 1) {
+	if (sources.size() > 1) {
 		std::map<std::string, Eigen::Matrix4d> transformations;
 
 		/*if ((*merge)["register"]) {
@@ -138,25 +131,25 @@ static void run(ftl::Configurable *root) {
 			}
 		//}
 
-		for (auto &input : inputs) {
-			string uri = input.source->getURI();
+		for (auto &input : sources) {
+			string uri = input->getURI();
 			auto T = transformations.find(uri);
 			if (T == transformations.end()) {
 				LOG(ERROR) << "Camera pose for " + uri + " not found in transformations";
 				LOG(WARNING) << "Using only first configured source";
 				// TODO: use target source if configured and found
-				inputs = { inputs[0] };
-				inputs[0].source->setPose(Eigen::Matrix4d::Identity());
+				sources = { sources[0] };
+				sources[0]->setPose(Eigen::Matrix4d::Identity());
 				break;
 			}
-			input.source->setPose(T->second);
+			input->setPose(T->second);
 		}
 	}
 
 	// Displays and Inputs configured
 	
-	LOG(INFO) << "Using sources:";
-	for (auto &input : inputs) { LOG(INFO) << "    " + (string) input.source->getURI(); }
+	//LOG(INFO) << "Using sources:";
+	//for (auto &input : inputs) { LOG(INFO) << "    " + (string) input.source->getURI(); }
 	
 	//ftl::rgbd::Display *display = ftl::create<ftl::rgbd::Display>(root, "display");
 	ftl::rgbd::Source *virt = ftl::create<ftl::rgbd::Source>(root, "virtual", net);
@@ -171,8 +164,10 @@ static void run(ftl::Configurable *root) {
 	ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net);
 	stream->add(virt);
 	// Also proxy all inputs
-	for (auto &in : inputs) {
-		stream->add(in.source);
+	for (int i=0; i<sources.size(); i++) {
+		Source *in = sources[i];
+		stream->add(in);
+		scene->addSource(in);
 	}
 
 	unsigned char frameCount = 0;
@@ -183,7 +178,7 @@ static void run(ftl::Configurable *root) {
 	//	if (key == 32) paused = !paused;
 	//});
 
-	int active = inputs.size();
+	int active = sources.size();
 	while (ftl::running) {
 		if (active == 0) {
 			LOG(INFO) << "Waiting for sources...";
@@ -196,63 +191,16 @@ static void run(ftl::Configurable *root) {
 			//net.broadcast("grab");  // To sync cameras
 			scene->nextFrame();
 		
-			// TODO(Nick) Improve sync further...
-			for (size_t i = 0; i < inputs.size(); i++) {
-				inputs[i].source->grab();
-			}
-
+			active = scene->upload();
 			stream->wait();
 
-			for (size_t i = 0; i < inputs.size(); i++) {
-				if (!inputs[i].source->isReady()) {
-					inputs[i].params.m_imageWidth = 0;
-					// TODO(Nick) : Free gpu allocs if was ready before
-					continue;
-				} else {
-					auto &cam = inputs[i];
-					auto in = inputs[i].source;
-					if (cam.params.m_imageWidth == 0) {
-						cam.params.fx = in->parameters().fx;
-						cam.params.fy = in->parameters().fy;
-						cam.params.mx = -in->parameters().cx;
-						cam.params.my = -in->parameters().cy;
-						cam.params.m_imageWidth = in->parameters().width;
-						cam.params.m_imageHeight = in->parameters().height;
-						cam.params.m_sensorDepthWorldMax = in->parameters().maxDepth;
-						cam.params.m_sensorDepthWorldMin = in->parameters().minDepth;
-						cam.gpu.alloc(cam.params);
-					}
-					
-					//LOG(INFO) << in->getURI() << " loaded " << cam.params.fx;
-				}
-
-				// Get the RGB-Depth frame from input
-				Source *input = inputs[i].source;
-				Mat rgb, depth;
-				input->getFrames(rgb,depth);
-				
-				active += 1;
-
-				if (depth.cols == 0) continue;
-
-				// Must be in RGBA for GPU
-				Mat rgba;
-				cv::cvtColor(rgb,rgba, cv::COLOR_BGR2BGRA);
-
-				inputs[i].params.flags = frameCount;
-
-				// Send to GPU and merge view into scene
-				inputs[i].gpu.updateParams(inputs[i].params);
-				inputs[i].gpu.updateData(depth, rgba);
-				// TODO(Nick): Use double pose
-				scene->integrate(inputs[i].source->getPose().cast<float>(), inputs[i].gpu, inputs[i].params, nullptr);
-			}
+			scene->integrate();
+
+			// scene->garbage();
 		} else {
 			active = 1;
 		}
 
-		frameCount++;
-
 		stream->poll();
 		//display->update();
 		//sleep_for(milliseconds(10));
diff --git a/applications/reconstruct/src/voxel_scene.cpp b/applications/reconstruct/src/voxel_scene.cpp
index 971f622eb13d31ebc6f6c99484a6e86e5535c5f6..b47977b081a582a0b75ace042f11987cbf3f7d8f 100644
--- a/applications/reconstruct/src/voxel_scene.cpp
+++ b/applications/reconstruct/src/voxel_scene.cpp
@@ -1,7 +1,9 @@
 #include <ftl/voxel_scene.hpp>
 
 using namespace ftl::voxhash;
+using ftl::rgbd::Source;
 using ftl::Configurable;
+using cv::Mat;
 
 #define 	SAFE_DELETE_ARRAY(a)   { delete [] (a); (a) = NULL; }
 
@@ -18,18 +20,8 @@ extern "C" void starveVoxelsKernelCUDA(ftl::voxhash::HashData& hashData, const f
 extern "C" void garbageCollectIdentifyCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
 extern "C" void garbageCollectFreeCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams);
 
-SceneRep::SceneRep(nlohmann::json &config) : Configurable(config), do_reset_(false) {
-	REQUIRED({
-		{"hashNumBuckets", "Desired hash entries divide bucket size", "number"},
-		{"hashMaxCollisionLinkedListSize", "", "number"},
-		{"hashNumSDFBlocks", "", "number"},
-		{"SDFVoxelSize", "Size in meters of one voxel", "number"},
-		{"SDFMaxIntegrationDistance", "", "number"},
-		{"SDFTruncation", "Base error size", "number"},
-		{"SDFTruncationScale", "Error size scale with depth", "number"},
-		{"SDFIntegrationWeightSample", "", "number"},
-		{"SDFIntegrationWeightMax", "", "number"}
-	});
+SceneRep::SceneRep(nlohmann::json &config) : Configurable(config), do_reset_(false), m_frameCount(0) {
+	// Allocates voxel structure on GPU
 	_create(_parametersFromConfig());
 
 	on("SDFVoxelSize", [this](const ftl::config::Event &e) {
@@ -59,6 +51,94 @@ SceneRep::~SceneRep() {
 	_destroy();
 }
 
+void SceneRep::addSource(ftl::rgbd::Source *src) {
+	auto &cam = cameras_.emplace_back();
+	cam.source = src;
+	cam.params.m_imageWidth = 0;
+}
+
+int SceneRep::upload() {
+	int active = 0;
+
+	for (size_t i=0; i<cameras_.size(); ++i) {
+		cameras_[i].source->grab();
+	}
+
+	for (size_t i=0; i<cameras_.size(); ++i) {
+		auto &cam = cameras_[i];
+
+		if (!cam.source->isReady()) {
+			cam.params.m_imageWidth = 0;
+			// TODO(Nick) : Free gpu allocs if was ready before
+			continue;
+		} else {
+			auto in = cam.source;
+			// Only now do we have camera parameters for allocations...
+			if (cam.params.m_imageWidth == 0) {
+				cam.params.fx = in->parameters().fx;
+				cam.params.fy = in->parameters().fy;
+				cam.params.mx = -in->parameters().cx;
+				cam.params.my = -in->parameters().cy;
+				cam.params.m_imageWidth = in->parameters().width;
+				cam.params.m_imageHeight = in->parameters().height;
+				cam.params.m_sensorDepthWorldMax = in->parameters().maxDepth;
+				cam.params.m_sensorDepthWorldMin = in->parameters().minDepth;
+				cam.gpu.alloc(cam.params);
+			}
+		}
+
+		// Get the RGB-Depth frame from input
+		Source *input = cam.source;
+		Mat rgb, depth;
+
+		// TODO(Nick) Direct GPU upload to save copy
+		input->getFrames(rgb,depth);
+		
+		active += 1;
+
+		if (depth.cols == 0) continue;
+
+		// Must be in RGBA for GPU
+		Mat rgba;
+		cv::cvtColor(rgb,rgba, cv::COLOR_BGR2BGRA);
+
+		cam.params.flags = m_frameCount;
+
+		// Send to GPU and merge view into scene
+		cam.gpu.updateParams(cam.params);
+		cam.gpu.updateData(depth, rgba);
+
+		setLastRigidTransform(input->getPose().cast<float>());
+
+		//make the rigid transform available on the GPU
+		m_hashData.updateParams(m_hashParams);
+
+		//allocate all hash blocks which are corresponding to depth map entries
+		_alloc(cam.gpu, cam.params, nullptr);
+	}
+
+	return active;
+}
+
+void SceneRep::integrate() {
+	for (size_t i=0; i<cameras_.size(); ++i) {
+		auto &cam = cameras_[i];
+
+		setLastRigidTransform(cam.source->getPose().cast<float>());
+		m_hashData.updateParams(m_hashParams);
+
+		//generate a linear hash array with only occupied entries
+		_compactifyHashEntries();
+
+		//volumetrically integrate the depth data into the depth SDFBlocks
+		_integrateDepthMap(cam.gpu, cam.params);
+
+		_garbageCollect(cam.gpu);
+
+		m_numIntegratedFrames++;
+	}
+}
+
 void SceneRep::integrate(const Eigen::Matrix4f& lastRigidTransform, const DepthCameraData& depthCameraData, const DepthCameraParams& depthCameraParams, unsigned int* d_bitMask) {
 		
 	setLastRigidTransform(lastRigidTransform);