Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make particle emitter invisible in thermal camera image #240

Merged
merged 16 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ namespace ignition
public: virtual void SetColorRangeImage(const std::string &_image)
override;

/// \brief Particle system visibility flags
public: static const uint32_t kParticleVisibilityFlags;

// Documentation inherited.
protected: virtual void Init() override;

Expand Down
4 changes: 4 additions & 0 deletions ogre2/src/Ogre2ParticleEmitter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
using namespace ignition;
using namespace rendering;

const uint32_t Ogre2ParticleEmitter::kParticleVisibilityFlags = 0x00100000;

class ignition::rendering::Ogre2ParticleEmitterPrivate
{
/// \brief Internal material name.
Expand Down Expand Up @@ -481,6 +483,8 @@ void Ogre2ParticleEmitter::CreateParticleSystem()
this->dataPtr->ps->setParticleQuota(500);
this->dataPtr->ps->setSortingEnabled(true);

this->dataPtr->ps->setVisibilityFlags(kParticleVisibilityFlags);

IGN_ASSERT(kOgreEmitterTypes.size() == EmitterType::EM_NUM_EMITTERS,
"The nummer of supported emitters does not match the number of "
"Ogre emitter types.");
Expand Down
8 changes: 7 additions & 1 deletion ogre2/src/Ogre2ThermalCamera.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "ignition/rendering/ogre2/Ogre2Conversions.hh"
#include "ignition/rendering/ogre2/Ogre2Includes.hh"
#include "ignition/rendering/ogre2/Ogre2Material.hh"
#include "ignition/rendering/ogre2/Ogre2ParticleEmitter.hh"
#include "ignition/rendering/ogre2/Ogre2RenderEngine.hh"
#include "ignition/rendering/ogre2/Ogre2RenderTarget.hh"
#include "ignition/rendering/ogre2/Ogre2RenderTypes.hh"
Expand Down Expand Up @@ -737,7 +738,12 @@ void Ogre2ThermalCamera::CreateThermalTexture()
colorTargetDef->addPass(Ogre::PASS_CLEAR));
passClear->mColourValue = Ogre::ColourValue(0, 0, 0);
// scene pass
colorTargetDef->addPass(Ogre::PASS_SCENE);
Ogre::CompositorPassSceneDef *passScene =
static_cast<Ogre::CompositorPassSceneDef *>(
colorTargetDef->addPass(Ogre::PASS_SCENE));
// thermal camera should not see particles
passScene->mVisibilityMask = IGN_VISIBILITY_ALL &
~Ogre2ParticleEmitter::kParticleVisibilityFlags;
}

// rt_input target - converts to thermal
Expand Down
181 changes: 181 additions & 0 deletions test/integration/thermal_camera.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
#include <ignition/common/Filesystem.hh>
#include <ignition/common/Event.hh>

#include <ignition/math/Color.hh>

#include "test_config.h" // NOLINT(build/include)

#include "ignition/rendering/ParticleEmitter.hh"
#include "ignition/rendering/PixelFormat.hh"
#include "ignition/rendering/RenderEngine.hh"
#include "ignition/rendering/RenderingIface.hh"
Expand Down Expand Up @@ -63,6 +66,9 @@ class ThermalCameraTest: public testing::Test,
// Test 8 bit thermal camera output
public: void ThermalCameraBoxes8Bit(const std::string &_renderEngine);

// Test that particles do not appear in thermal camera image
public: void ThermalCameraParticles(const std::string &_renderEngine);

// Path to test textures
public: const std::string TEST_MEDIA_PATH =
ignition::common::joinPaths(std::string(PROJECT_SOURCE_PATH),
Expand Down Expand Up @@ -448,6 +454,176 @@ void ThermalCameraTest::ThermalCameraBoxes8Bit(
ignition::rendering::unloadEngine(engine->Name());
}

//////////////////////////////////////////////////
void ThermalCameraTest::ThermalCameraParticles(
const std::string &_renderEngine)
{
int imgWidth = 50;
int imgHeight = 50;
double aspectRatio = imgWidth / imgHeight;

double unitBoxSize = 1.0;
ignition::math::Vector3d boxPosition(1.8, 0.0, 0.0);

// Only ogre2 supports 8 bit image format
if (_renderEngine.compare("ogre2") != 0)
{
igndbg << "Engine '" << _renderEngine
<< "' doesn't support 8 bit thermal cameras" << std::endl;
return;
}

// Setup ign-rendering with an empty scene
auto *engine = ignition::rendering::engine(_renderEngine);
if (!engine)
{
igndbg << "Engine '" << _renderEngine
<< "' is not supported" << std::endl;
return;
}

ignition::rendering::ScenePtr scene = engine->CreateScene("scene");

// red background
scene->SetBackgroundColor(1.0, 0.0, 0.0);

// Create an scene with a box in it
scene->SetAmbientLight(1.0, 1.0, 1.0);
ignition::rendering::VisualPtr root = scene->RootVisual();

// create box visual
ignition::rendering::VisualPtr box = scene->CreateVisual();
box->AddGeometry(scene->CreateBox());
box->SetOrigin(0.0, 0.0, 0.0);
box->SetLocalPosition(boxPosition);
box->SetLocalRotation(0, 0, 0);
box->SetLocalScale(unitBoxSize, unitBoxSize, unitBoxSize);

// set box temperature
float boxTemp = 310.0;
box->SetUserData("temperature", boxTemp);

root->AddChild(box);

// create particle emitter between camera and box
ignition::rendering::ParticleEmitterPtr emitter =
scene->CreateParticleEmitter();
emitter->SetLocalPosition({0.5, 0, 0});
emitter->SetRate(10);
emitter->SetParticleSize({1, 1, 1});
adlarkin marked this conversation as resolved.
Show resolved Hide resolved
emitter->SetLifetime(2);
emitter->SetVelocityRange(0.1, 0.5);
emitter->SetColorRange(ignition::math::Color::Red,
ignition::math::Color::Black);
emitter->SetScaleRate(1);
emitter->SetEmitting(true);

root->AddChild(emitter);

{
double farDist = 10.0;
double nearDist = 0.15;
double hfov = 1.05;
// set min max values based on thermal camera spec
// using the Vividia HTi HT-301 camera as example:
// https://hti-instrument.com/products/ht-301-mobile-phone-thermal-imager
// The range is ~= -20 to 400 degree Celsius
double minTemp = 253.0;
double maxTemp = 673.0;
// Create thermal camera
auto thermalCamera = scene->CreateThermalCamera("ThermalCamera");
ASSERT_NE(thermalCamera, nullptr);

ignition::math::Pose3d testPose(ignition::math::Vector3d(0, 0, 0),
ignition::math::Quaterniond::Identity);
thermalCamera->SetLocalPose(testPose);

// Configure thermal camera
thermalCamera->SetImageWidth(imgWidth);
EXPECT_EQ(thermalCamera->ImageWidth(),
static_cast<unsigned int>(imgWidth));
thermalCamera->SetImageHeight(imgHeight);
EXPECT_EQ(thermalCamera->ImageHeight(),
static_cast<unsigned int>(imgHeight));
thermalCamera->SetFarClipPlane(farDist);
EXPECT_DOUBLE_EQ(thermalCamera->FarClipPlane(), farDist);
thermalCamera->SetNearClipPlane(nearDist);
EXPECT_DOUBLE_EQ(thermalCamera->NearClipPlane(), nearDist);
thermalCamera->SetAspectRatio(aspectRatio);
EXPECT_DOUBLE_EQ(thermalCamera->AspectRatio(), aspectRatio);
thermalCamera->SetHFOV(hfov);
EXPECT_DOUBLE_EQ(thermalCamera->HFOV().Radian(), hfov);

// set bit depth
thermalCamera->SetImageFormat(ignition::rendering::PF_L8);
EXPECT_EQ(ignition::rendering::PF_L8, thermalCamera->ImageFormat());

// set min max temp
thermalCamera->SetMinTemperature(minTemp);
EXPECT_DOUBLE_EQ(minTemp, thermalCamera->MinTemperature());
thermalCamera->SetMaxTemperature(maxTemp);
EXPECT_DOUBLE_EQ(maxTemp, thermalCamera->MaxTemperature());

// thermal-specific params
// set room temperature: 294 ~ 298 Kelvin
float ambientTemp = 296.0f;
float ambientTempRange = 4.0f;

// 8 bit format so higher number here (lower resolution)
// +- 3 degrees
float linearResolution = 3.0f;
thermalCamera->SetAmbientTemperature(ambientTemp);
EXPECT_FLOAT_EQ(ambientTemp, thermalCamera->AmbientTemperature());
thermalCamera->SetAmbientTemperatureRange(ambientTempRange);
EXPECT_FLOAT_EQ(ambientTempRange, thermalCamera->AmbientTemperatureRange());
thermalCamera->SetLinearResolution(linearResolution);
EXPECT_FLOAT_EQ(linearResolution, thermalCamera->LinearResolution());
scene->RootVisual()->AddChild(thermalCamera);

// Set a callback on the camera sensor to get a thermal camera frame
// todo(anyone) change this to uint8_t when thermal cameras supports a
// ConnectNewThermalFrame event that provides this format
uint16_t *thermalData = new uint16_t[imgHeight * imgWidth];
ignition::common::ConnectionPtr connection =
thermalCamera->ConnectNewThermalFrame(
std::bind(&::OnNewThermalFrame, thermalData,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5));
EXPECT_NE(nullptr, connection);

// thermal image indices
int midWidth = static_cast<int>(thermalCamera->ImageWidth() * 0.5);
int midHeight = static_cast<int>(thermalCamera->ImageHeight() * 0.5);
int mid = midHeight * thermalCamera->ImageWidth() + midWidth -1;
int left = midHeight * thermalCamera->ImageWidth();
int right = (midHeight+1) * thermalCamera->ImageWidth() - 1;

// Update a few times to make sure the flow of particles do not affect
// the readings
for (unsigned int i = 0; i < 100u; ++i)
{
thermalCamera->Update();

// verify temperature
// Box should be in the middle of image and return box temp
// Left and right side of the image frame should be ambient temp
EXPECT_NEAR(ambientTemp, thermalData[left] * linearResolution,
ambientTempRange);
EXPECT_NEAR(ambientTemp, thermalData[right] * linearResolution,
ambientTempRange);
EXPECT_FLOAT_EQ(thermalData[right], thermalData[left]);
EXPECT_NEAR(boxTemp, thermalData[mid] * linearResolution,
linearResolution);
}
adlarkin marked this conversation as resolved.
Show resolved Hide resolved

// Clean up
connection.reset();
delete [] thermalData;
}

engine->DestroyScene(scene);
ignition::rendering::unloadEngine(engine->Name());
}

TEST_P(ThermalCameraTest, ThermalCameraBoxesUniformTemp)
{
Expand All @@ -464,6 +640,11 @@ TEST_P(ThermalCameraTest, ThermalCameraBoxesUniformTemp8Bit)
ThermalCameraBoxes8Bit(GetParam());
}

TEST_P(ThermalCameraTest, ThermalCameraParticles)
{
ThermalCameraParticles(GetParam());
}

INSTANTIATE_TEST_CASE_P(ThermalCamera, ThermalCameraTest,
RENDER_ENGINE_VALUES, ignition::rendering::PrintToStringParam());

Expand Down