Commit 3fbfb861 authored by Utkarsh Ayachit's avatar Utkarsh Ayachit Committed by Kitware Robot
Browse files

Merge topic 'stereo-animations'

70ac37ab SaveAnimation: extend test to check stereo images
b9acd9e1 vtkSMSaveAnimationProxy: support stereo movies
2dc4d59a

 vtkSMSaveScreenshotProxy: support stereo both eyes
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Acked-by: Cory Quammen's avatarCory Quammen <cory.quammen@kitware.com>
Merge-request: !3976
parents 8b51fdb5 70ac37ab
Pipeline #164226 failed with stages
in 0 seconds
## Save left and right eye images and animations in one go
**Save Animation Options** and **Save Screenshot Options** now support a
stereo mode called **Both Eyes**. When chosen, ParaView will save images (or
videos) for the left and right eye. The left-eye output files are suffixed with
`_left` while the ones for the right-eye are suffixed with `_right`.
......@@ -26,6 +26,7 @@
#include "vtkPVRenderingCapabilitiesInformation.h"
#include "vtkPVServerInformation.h"
#include "vtkPVXMLElement.h"
#include "vtkRenderWindow.h"
#include "vtkSMAnimationScene.h"
#include "vtkSMAnimationSceneWriter.h"
#include "vtkSMParaViewPipelineController.h"
......@@ -44,19 +45,26 @@
namespace vtkSMSaveAnimationProxyNS
{
class SceneGrabber
class Friendship
{
public:
static vtkSmartPointer<vtkImageData> Grab(vtkSMSaveAnimationProxy* proxy)
static std::pair<vtkSmartPointer<vtkImageData>, vtkSmartPointer<vtkImageData> > Grab(
vtkSMSaveAnimationProxy* proxy)
{
return proxy ? proxy->CapturePreppedImage() : vtkSmartPointer<vtkImageData>();
return proxy ? proxy->CapturePreppedImages()
: std::pair<vtkSmartPointer<vtkImageData>, vtkSmartPointer<vtkImageData> >();
}
static std::string GetStereoFileName(
vtkSMSaveAnimationProxy* proxy, const std::string& filename, bool left)
{
return proxy ? proxy->GetStereoFileName(filename, left) : filename;
}
};
template <class T>
class SceneImageWriter : public vtkSMAnimationSceneWriter
{
vtkSmartPointer<T> Writer;
vtkWeakPointer<vtkSMSaveAnimationProxy> Helper;
public:
......@@ -66,12 +74,6 @@ public:
*/
void SetHelper(vtkSMSaveAnimationProxy* helper) { this->Helper = helper; }
/**
* Set the writer to use.
*/
void SetWriter(T* writer) { this->Writer = writer; }
T* GetWriter() { return this->Writer; }
protected:
SceneImageWriter() {}
~SceneImageWriter() {}
......@@ -86,20 +88,20 @@ protected:
bool SaveFrame(double time) override
{
vtkSmartPointer<vtkImageData> image = SceneGrabber::Grab(this->Helper);
auto image_pair = Friendship::Grab(this->Helper);
// Now, in symmetric batch mode, while this method will get called on all
// ranks, we really only to save the image on root node.
// Note, the call to CapturePreppedImage() still needs to happen on all
// ranks, since otherwise we may get mismatched renders.
vtkMultiProcessController* controller = vtkMultiProcessController::GetGlobalController();
if (image == nullptr || (controller && controller->GetLocalProcessId() != 0))
if (image_pair.first == nullptr || (controller && controller->GetLocalProcessId() != 0))
{
// don't actually save anything on this rank.
return true;
}
return this->WriteFrameImage(time, image);
return this->WriteFrameImage(time, image_pair.first, image_pair.second);
}
bool SaveFinalize() override
......@@ -108,7 +110,12 @@ protected:
return true;
}
virtual bool WriteFrameImage(double time, vtkImageData* data) = 0;
virtual bool WriteFrameImage(double time, vtkImageData* dataLeft, vtkImageData* dataRight) = 0;
std::string GetStereoFileName(const std::string& filename, bool left)
{
return Friendship::GetStereoFileName(this->Helper, filename, left);
}
private:
SceneImageWriter(const SceneImageWriter&) = delete;
......@@ -117,10 +124,17 @@ private:
class SceneImageWriterMovie : public SceneImageWriter<vtkGenericMovieWriter>
{
vtkGenericMovieWriter* Writers[2] = { nullptr, nullptr };
public:
static SceneImageWriterMovie* New();
vtkTypeMacro(SceneImageWriterMovie, SceneImageWriter<vtkGenericMovieWriter>);
/**
* Set the writer to use.
*/
void SetWriter(int index, vtkGenericMovieWriter* writer) { this->Writers[index] = writer; }
protected:
SceneImageWriterMovie()
: Started(false)
......@@ -130,35 +144,54 @@ protected:
bool SaveInitialize(int startCount) override
{
if (auto* writer = this->GetWriter())
std::string fname = this->GetFileName();
if (auto rWriter = this->Writers[1])
{
writer->SetFileName(this->GetFileName());
return this->Superclass::SaveInitialize(startCount);
rWriter->SetFileName(this->GetStereoFileName(fname, /*left=*/false).c_str());
fname = this->GetStereoFileName(fname, true);
}
this->Started = false;
return false;
auto* writer = this->Writers[0];
assert(writer != nullptr);
writer->SetFileName(fname.c_str());
return this->Superclass::SaveInitialize(startCount);
}
bool WriteFrameImage(double vtkNotUsed(time), vtkImageData* data) override
bool WriteFrameImage(
double vtkNotUsed(time), vtkImageData* dataLeft, vtkImageData* dataRight) override
{
assert(data);
auto* writer = this->GetWriter();
writer->SetInputData(data);
if (!this->Started)
vtkImageData* data[] = { dataLeft, dataRight };
bool status = true;
for (int cc = 0; cc < 2; ++cc)
{
this->Started = true;
writer->Start(); // start needs input data, hence we do it here.
if (auto* writer = this->Writers[cc])
{
assert(data[cc] != nullptr);
writer->SetInputData(data[cc]);
if (!this->Started)
{
writer->Start(); // start needs input data, hence we do it here.
}
writer->Write();
writer->SetInputData(nullptr);
status &= (writer->GetError() == 0 && writer->GetError() == vtkErrorCode::NoError);
}
}
writer->Write();
writer->SetInputData(nullptr);
return (writer->GetError() == 0 && writer->GetError() == vtkErrorCode::NoError);
this->Started = true;
return status;
}
bool SaveFinalize() override
{
if (this->Started)
{
this->GetWriter()->End();
for (int cc = 0; cc < 2; ++cc)
{
if (auto writer = this->Writers[cc])
{
writer->End();
}
}
}
this->Started = false;
return this->Superclass::SaveFinalize();
......@@ -173,6 +206,8 @@ vtkStandardNewMacro(SceneImageWriterMovie);
class SceneImageWriterImageSeries : public SceneImageWriter<vtkImageWriter>
{
vtkImageWriter* Writer;
public:
static SceneImageWriterImageSeries* New();
vtkTypeMacro(SceneImageWriterImageSeries, SceneImageWriter<vtkImageWriter>);
......@@ -183,6 +218,11 @@ public:
vtkSetStringMacro(SuffixFormat);
vtkGetStringMacro(SuffixFormat);
/**
* Set the writer to use.
*/
void SetWriter(vtkImageWriter* writer) { this->Writer = writer; }
protected:
SceneImageWriterImageSeries()
: Counter(0)
......@@ -201,10 +241,13 @@ protected:
return this->Superclass::SaveInitialize(startCount);
}
bool WriteFrameImage(double vtkNotUsed(time), vtkImageData* data) override
bool WriteFrameImage(
double vtkNotUsed(time), vtkImageData* dataLeft, vtkImageData* dataRight) override
{
auto writer = this->GetWriter();
assert(data);
bool success = true;
auto writer = this->Writer;
assert(dataLeft);
assert(this->SuffixFormat);
assert(writer);
......@@ -213,12 +256,24 @@ protected:
std::ostringstream str;
str << this->Prefix << buffer << this->Extension;
writer->SetInputData(data);
writer->SetFileName(str.str().c_str());
std::string fname = str.str();
if (dataRight)
{
writer->SetInputData(dataRight);
writer->SetFileName(this->GetStereoFileName(fname, /*left*/ false).c_str());
writer->Write();
success &= (writer->GetErrorCode() == vtkErrorCode::NoError);
// update fname for left image.
fname = this->GetStereoFileName(fname, /*left=*/true);
}
writer->SetFileName(fname.c_str());
writer->SetInputData(dataLeft);
writer->Write();
writer->SetInputData(nullptr);
const bool success = writer->GetErrorCode() == vtkErrorCode::NoError;
success &= writer->GetErrorCode() == vtkErrorCode::NoError;
this->Counter += success ? 1 : 0;
return success;
}
......@@ -331,21 +386,37 @@ bool vtkSMSaveAnimationProxy::WriteAnimationLocally(const char* filename)
.Set(vtkSMPropertyHelper(this, "FrameRate").GetAsInt());
formatProxy->UpdateVTKObjects();
// check if we're writing 2-stereo video streams at the same time.
vtkSmartPointer<vtkSMProxy> otherFormatProxy;
// based on the format, we create an appropriate SceneImageWriter.
auto formatObj = formatProxy->GetClientSideObject();
if (auto imgWriter = vtkImageWriter::SafeDownCast(formatObj))
{
vtkNew<vtkSMSaveAnimationProxyNS::SceneImageWriterImageSeries> realWriter;
realWriter->SetWriter(imgWriter);
realWriter->SetSuffixFormat(vtkSMPropertyHelper(formatProxy, "SuffixFormat").GetAsString());
realWriter->SetHelper(this);
realWriter->SetWriter(imgWriter);
writer = realWriter;
}
else if (auto movieWriter = vtkGenericMovieWriter::SafeDownCast(formatObj))
{
vtkNew<vtkSMSaveAnimationProxyNS::SceneImageWriterMovie> realWriter;
realWriter->SetWriter(movieWriter);
realWriter->SetHelper(this);
realWriter->SetWriter(0, movieWriter);
// we need two movie writers when writing stereo videos
if (vtkSMPropertyHelper(this, "StereoMode").GetAsInt() == VTK_STEREO_EMULATE)
{
auto pxm = this->GetSessionProxyManager();
otherFormatProxy.TakeReference(
pxm->NewProxy(formatProxy->GetXMLGroup(), formatProxy->GetXMLName()));
otherFormatProxy->SetLocation(formatProxy->GetLocation());
otherFormatProxy->Copy(formatProxy);
otherFormatProxy->UpdateVTKObjects();
realWriter->SetWriter(
1, vtkGenericMovieWriter::SafeDownCast(otherFormatProxy->GetClientSideObject()));
}
writer = realWriter;
}
else
......
......@@ -30,7 +30,7 @@
#include "vtkSMSaveScreenshotProxy.h"
namespace vtkSMSaveAnimationProxyNS
{
class SceneGrabber;
class Friendship;
}
class vtkPVXMLElement;
......@@ -87,7 +87,7 @@ private:
vtkSMSaveAnimationProxy(const vtkSMSaveAnimationProxy&) = delete;
void operator=(const vtkSMSaveAnimationProxy&) = delete;
friend class vtkSMSaveAnimationProxyNS::SceneGrabber;
friend class vtkSMSaveAnimationProxyNS::Friendship;
vtkSmartPointer<vtkPVXMLElement> SceneState;
};
......
286d6ecf1e2f77d931c564acee8d923893305af7dbd70af6012b2e15885e81676ca0bc10187b992054c9da30da3c3daf3ff1e92e161eddb6ea7d67efe2274fc7
......@@ -71,6 +71,7 @@ set(ParaView::RemotingApplication_ARGS)
# Add tests for pvbatch.
vtk_module_test_data(
"${CMAKE_CURRENT_SOURCE_DIR}/../Data/Baseline/SaveAnimation_right.png"
"${CMAKE_CURRENT_SOURCE_DIR}/../Data/Baseline/MultiView_chart_view.png"
"${CMAKE_CURRENT_SOURCE_DIR}/../Data/Baseline/MultiView_render_view.png")
......
......@@ -3,14 +3,17 @@ from paraview.simple import *
from paraview import smtesting
smtesting.ProcessCommandLineArguments()
tempdir = smtesting.GetUniqueTempDirectory("SaveAnimation-")
print("Generating output files in `%s`" % tempdir)
def RegressionTest(imageName, baselineName):
from paraview.vtk.vtkTestingRendering import vtkTesting
testing = vtkTesting()
testing.AddArgument("-T")
testing.AddArgument(smtesting.TempDir)
testing.AddArgument(tempdir)
testing.AddArgument("-V")
testing.AddArgument(smtesting.DataDir + "/Remoting/Application/Testing/Data/Baseline/" + baselineName)
return testing.RegressionTest(smtesting.TempDir + "/" + imageName, 10) == vtkTesting.PASSED
return testing.RegressionTest(tempdir + "/" + imageName, 10) == vtkTesting.PASSED
# Create a new 'Render View'
......@@ -36,9 +39,30 @@ animationScene1.UpdateAnimationUsingDataTimeSteps()
# show data from reader
canex2Display = Show(reader, renderView1)
SaveAnimation(smtesting.TempDir + "/SaveAnimation.png", ImageResolution=[600, 600], ImageQuality=40)
# Save animation images
SaveAnimation(tempdir + "/SaveAnimation.png", ImageResolution=[600, 600], ImageQuality=40)
# Lets save stereo animation images (two eyes at the same time)
SaveAnimation(tempdir + "/SaveAnimationStereo.png",
ImageResolution=[600, 600], ImageQuality=40,
StereoMode="Both Eyes")
# Lets save stere video
SaveAnimation(tempdir + "/SaveAnimationStereo.ogv",
ImageResolution=[600, 600], ImageQuality=40,
StereoMode="Both Eyes")
pm = servermanager.vtkProcessModule.GetProcessModule()
if pm.GetPartitionId() == 0:
if not RegressionTest("SaveAnimation.0002.png", "SaveAnimation.png"):
raise RuntimeError("Test failed")
raise RuntimeError("Test failed (non-stereo)")
if not RegressionTest("SaveAnimationStereo.0002_left.png", "SaveAnimation.png"):
raise RuntimeError("Test failed (stereo: left-eye)")
if not RegressionTest("SaveAnimationStereo.0002_right.png", "SaveAnimation_right.png"):
raise RuntimeError("Test failed (stereo: right-eye)")
import os.path
if not os.path.exists(os.path.join(tempdir, "SaveAnimationStereo_right.ogv")):
raise RuntimeError("Missing video file (stereo: right-eye)")
if not os.path.exists(os.path.join(tempdir, "SaveAnimationStereo_left.ogv")):
raise RuntimeError("Missing video file (stereo: left-eye)")
......@@ -2448,6 +2448,7 @@
<Entry text="Interlaced" value="3" />
<Entry text="Left Eye Only" value="4" />
<Entry text="Right Eye Only" value="5" />
<Entry text="Both Eyes" value="11" />
<Entry text="Dresden" value="6" />
<Entry text="Anaglyph" value="7" />
<Entry text="Checkerboard" value="8" />
......
......@@ -21,6 +21,7 @@
#include "vtkObjectFactory.h"
#include "vtkPVXMLElement.h"
#include "vtkProcessModule.h"
#include "vtkRenderWindow.h"
#include "vtkSMParaViewPipelineControllerWithRendering.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyHelper.h"
......@@ -113,6 +114,7 @@ private:
protected:
vtkVector2i Magnification;
vtkVector2i OriginalSize;
bool BothEyes;
public:
vtkState(vtkSMSessionProxyManager* pxm)
......@@ -120,6 +122,7 @@ public:
, TransparentBackground(false)
, Magnification(1, 1)
, OriginalSize(0, 0)
, BothEyes(false)
{
this->TransparentBackground = vtkSMViewProxy::GetTransparentBackground();
}
......@@ -196,38 +199,72 @@ public:
}
}
void SetStereoMode(int mode)
{
if (mode == VTK_STEREO_EMULATE)
{
this->BothEyes = true;
this->UpdateStereoMode(VTK_STEREO_LEFT, /*restoreable=*/true);
}
else
{
this->BothEyes = false;
this->UpdateStereoMode(mode, /*restoreable=*/true);
}
}
void SetTransparentBackground(bool val) { vtkSMViewProxy::SetTransparentBackground(val); }
std::pair<vtkSmartPointer<vtkImageData>, vtkSmartPointer<vtkImageData> > CaptureImages()
{
std::pair<vtkSmartPointer<vtkImageData>, vtkSmartPointer<vtkImageData> > result;
if (this->BothEyes)
{
this->UpdateStereoMode(VTK_STEREO_LEFT, /*restoreable=*/false);
result.first = this->CaptureImage();
this->UpdateStereoMode(VTK_STEREO_RIGHT, /*restoreable=*/false);
result.second = this->CaptureImage();
}
else
{
result.first = this->CaptureImage();
}
return result;
}
virtual vtkSmartPointer<vtkImageData> CaptureImage() = 0;
virtual void SetStereoMode(int mode) = 0;
virtual void SetFontScaling(int mode) = 0;
virtual void SetSeparatorWidth(int) {}
virtual void SetSeparatorColor(double[3]) {}
protected:
virtual void Resize(const vtkVector2i& size) = 0;
virtual void UpdateStereoMode(int mode, bool restoreable) = 0;
void SetViewStereoMode(vtkSMViewProxy* view, int stereoMode)
void SetViewStereoMode(vtkSMViewProxy* view, int stereoMode, bool restoreable = true)
{
assert(view);
if (stereoMode <= -1)
{
return;
}
const int stereo_render = stereoMode == 0 ? 0 : 1;
vtkSMPropertyHelper srender(view, "StereoRender", true);
vtkSMPropertyHelper stype(view, "StereoType", true);
if (stype.GetAsInt() != stereoMode)
// Save it so we can restore at the end.
if (restoreable)
{
// if mode changed, then we save it so we can restore at the end.
ViewStereoState svalue;
svalue.View = view;
svalue.StereoType = stype.GetAsInt();
svalue.StereoRender = srender.GetAsInt();
this->SavedStereoValues.push_back(svalue);
stype.Set(stereoMode);
srender.Set(stereoMode == 0 ? 0 : 1);
view->UpdateVTKObjects();
}
stype.Set(stereoMode);
srender.Set(stereo_render);
view->UpdateVTKObjects();
}
void SetViewFontScaling(vtkSMViewProxy* view, int mode)
......@@ -290,10 +327,13 @@ public:
return img;
}
void SetStereoMode(int mode) override { this->SetViewStereoMode(this->View, mode); }
void SetFontScaling(int mode) override { this->SetViewFontScaling(this->View, mode); }
protected:
void UpdateStereoMode(int mode, bool restoreable) override
{
this->SetViewStereoMode(this->View, mode, restoreable);
}
void Resize(const vtkVector2i& size) override
{
vtkSMPropertyHelper(this->View, "ViewSize").Set(size.GetData(), 2);
......@@ -347,15 +387,6 @@ public:
return img;
}
void SetStereoMode(int mode) override
{
const std::vector<vtkSMViewProxy*> views = this->Layout->GetViews();
for (auto iter = views.begin(); iter != views.end(); ++iter)
{
this->SetViewStereoMode(*iter, mode);
}
}
void SetFontScaling(int mode) override
{
const std::vector<vtkSMViewProxy*> views = this->Layout->GetViews();
......@@ -379,6 +410,14 @@ public:
protected:
void Resize(const vtkVector2i& size) override { this->Layout->SetSize(size.GetData()); }
void UpdateStereoMode(int mode, bool restoreable) override
{
const std::vector<vtkSMViewProxy*> views = this->Layout->GetViews();
for (auto iter = views.begin(); iter != views.end(); ++iter)
{
this->SetViewStereoMode(*iter, mode, restoreable);
}
}
};
//============================================================================
......@@ -398,8 +437,15 @@ vtkSMSaveScreenshotProxy::~vtkSMSaveScreenshotProxy()
}
//----------------------------------------------------------------------------
bool vtkSMSaveScreenshotProxy::WriteImage(const char* filename)
bool vtkSMSaveScreenshotProxy::WriteImage(const char* fname)
{
if (fname == nullptr)
{
return false;
}
const std::string filename(fname);
vtkSMViewLayoutProxy* layout = this->GetLayout();
vtkSMViewProxy* view = this->GetView();
......@@ -414,7 +460,7 @@ bool vtkSMSaveScreenshotProxy::WriteImage(const char* filename)
auto format = this->GetFormatProxy(filename);
if (!format)
{
vtkErrorMacro("Failed to determine format for '" << filename << "'");
vtkErrorMacro("Failed to determine format for '" << filename.c_str() << "'");
return false;
}
......@@ -423,55 +469,85 @@ bool vtkSMSaveScreenshotProxy::WriteImage(const char* filename)
SM_SCOPED_TRACE(SaveScreenshotOrAnimation)
.arg("helper", this)
.arg("filename", filename)
.arg("filename", filename.c_str())
.arg("view", view)
.arg("layout", layout)
.arg("mode_screenshot", 1);
vtkSmartPointer<vtkImageData> img = this->CaptureImage();
if (img && vtkProcessModule::GetProcessModule()->GetPartitionId() == 0)
if (!this->Prepare())
{
auto writer = vtkImageWriter::SafeDownCast(format->GetClientSideObject());
if (writer)
{
vtkTimerLog::MarkStartEvent("Write image to disk");
writer->SetFileName(filename);
writer->SetInputData(img);
writer->Write();
writer->SetInputData(nullptr);
vtkTimerLog::MarkEndEvent("Write image to disk");
return writer->GetErrorCode() == vtkErrorCode::NoError;
}
else
{
vtkErrorMacro("Format writer not a 'vtkImageWriter'. "
" Failed to write '"
<< filename << "'.");
}
vtkErrorMacro("Failed to prepare to capture image.");
return false;
}
return false;
auto image_pair = this->CapturePreppedImages();
this->Cleanup();
if (image_pair.first == nullptr || vtkProcessModule::GetProcessModule()->GetPartitionId() != 0)
{
return false;
}
auto writer = vtkImageWriter::SafeDownCast(format->GetClientSideObject());