Skip to content

Commit 28c40e7

Browse files
committed
Added conversions to SHW reader and writer that convert the depth to a standard Z ( distance from image plane ), instead of the weird 3delight "spherical distance from the near clip".
This will change the results we get from reading and converting SHWs, but it should be much more consistent now, and will fix the misalignment we've been seeing in Houdini.
1 parent 7535ab7 commit 28c40e7

File tree

10 files changed

+116
-41
lines changed

10 files changed

+116
-41
lines changed

include/IECore/DeepPixel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ namespace IECore
4646
IE_CORE_FORWARDDECLARE( DeepPixel )
4747

4848
/// A DeepPixel represents arbitrary channel data stored at varying depths in space.
49+
/// By convention, depth is measured as distance from the eye plane
4950
/// \ingroup deepCompositingGroup
5051
class DeepPixel : public RefCounted
5152
{

include/IECoreRI/SHWDeepImageReader.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class SHWDeepImageReader : public IECore::DeepImageReader
8989
Imath::Box2i m_dataWindow;
9090
Imath::M44f m_worldToCamera;
9191
Imath::M44f m_worldToNDC;
92+
Imath::M44f m_NDCToCamera;
9293
std::string m_inputFileName;
9394
std::string m_channelNames;
9495

include/IECoreRI/SHWDeepImageWriter.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class SHWDeepImageWriter : public IECore::DeepImageWriter
7979
DtexCache *m_dtexCache;
8080
DtexImage *m_dtexImage;
8181
DtexPixel *m_dtexPixel;
82-
82+
83+
Imath::M44f m_NDCToCamera;
8384
std::string m_outputFileName;
8485
int m_alphaOffset;
8586

src/IECoreRI/SHWDeepImageReader.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,22 @@ DeepPixelPtr SHWDeepImageReader::doReadPixel( int x, int y )
153153
{
154154
previous[j] = 0.0;
155155
}
156-
156+
157+
float nearClip = m_NDCToCamera[3][2] / m_NDCToCamera[3][3];
158+
float correction = 1;
159+
if( m_NDCToCamera[3][2] != 0 && m_NDCToCamera[2][3] != 0 )
160+
{
161+
// Compute a correction factor that converts from spherical distance to perpendicular distance,
162+
// by comparing the closest distance to the near clip with the distance to the near clip at the current pixel position
163+
correction = nearClip / ( Imath::V3f(((x+0.5f)/(m_dataWindow.max.x+1) * 2 - 1), -((y+0.5)/(m_dataWindow.max.y+1) * 2 - 1),0) * m_NDCToCamera ).length();
164+
}
165+
157166
for ( int i=0; i < numSamples; ++i )
158167
{
159168
DtexPixelGetPoint( m_dtexPixel, i, &depth, channelData );
169+
170+
// Convert from "3delight distance" ( spherical distance from near clip ) to Z ( distance from eye plane )
171+
depth = depth * correction + nearClip;
160172

161173
for ( unsigned j=0; j < numRealChannels; ++j )
162174
{
@@ -190,6 +202,7 @@ bool SHWDeepImageReader::open( bool throwOnFailure )
190202
m_dataWindow.max.y = 0;
191203
m_worldToCamera = Imath::M44f();
192204
m_worldToNDC = Imath::M44f();
205+
m_NDCToCamera = Imath::M44f();
193206

194207
clean();
195208

@@ -214,6 +227,7 @@ bool SHWDeepImageReader::open( bool throwOnFailure )
214227

215228
DtexNl( m_dtexImage, m_worldToCamera.getValue() );
216229
DtexNP( m_dtexImage, m_worldToNDC.getValue() );
230+
m_NDCToCamera = m_worldToNDC.inverse() * m_worldToCamera;
217231
}
218232
else
219233
{

src/IECoreRI/SHWDeepImageWriter.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,17 @@ void SHWDeepImageWriter::doWritePixel( int x, int y, const DeepPixel *pixel )
108108

109109
float previous = 0.0;
110110
unsigned numSamples = pixel->numSamples();
111+
112+
const Imath::V2i &resolution = m_resolutionParameter->getTypedValue();
113+
float nearClip = m_NDCToCamera[3][2] / m_NDCToCamera[3][3];
114+
float correction = 1;
115+
if( m_NDCToCamera[3][2] != 0 && m_NDCToCamera[2][3] != 0 )
116+
{
117+
// Compute a correction factor that converts from perpendicular distance to spherical distance,
118+
// by comparing the closest distance to the near clip with the distance to the near clip at the current pixel position
119+
correction = ( Imath::V3f(((x+0.5f)/resolution.x * 2 - 1), -((y+0.5)/resolution.y * 2 - 1),0) * m_NDCToCamera ).length() / nearClip;
120+
}
121+
111122
for ( unsigned i=0; i < numSamples; ++i )
112123
{
113124
// SHW files require composited values, accumulated over depth, but we have uncomposited values
@@ -124,8 +135,13 @@ void SHWDeepImageWriter::doWritePixel( int x, int y, const DeepPixel *pixel )
124135
{
125136
adjustedData[c] = value;
126137
}
127-
128-
DtexAppendPixel( m_dtexPixel, pixel->getDepth( i ), numChannels, adjustedData, 0 );
138+
139+
float depth = pixel->getDepth( i );
140+
141+
// Convert from Z ( distance from eye plane ) to "3delight distance" ( spherical distance from near clip )
142+
depth = ( depth - nearClip ) * correction;
143+
144+
DtexAppendPixel( m_dtexPixel, depth, numChannels, adjustedData, 0 );
129145
}
130146

131147
DtexFinishPixel( m_dtexPixel );
@@ -183,13 +199,16 @@ void SHWDeepImageWriter::open()
183199

184200
float *NL = worldToCameraParameter()->getTypedValue().getValue();
185201
float *NP = worldToNDCParameter()->getTypedValue().getValue();
202+
203+
m_NDCToCamera = worldToNDCParameter()->getTypedValue().inverse() * worldToCameraParameter()->getTypedValue();
186204

187205
/// \todo: does image name mean anything for this format?
188206
int status = DtexAddImage(
189207
m_outputFile, "", numChannels,
190208
resolution.x, resolution.y, tileSize.x, tileSize.y,
191209
NP, NL, DTEX_COMPRESSION_NONE, DTEX_TYPE_FLOAT, &m_dtexImage
192210
);
211+
193212

194213
if ( status != DTEX_NOERR )
195214
{
@@ -198,7 +217,7 @@ void SHWDeepImageWriter::open()
198217
clean();
199218
throw IOException( std::string( "Failed to create the main sub-image in \"" ) + fileName() + "\" for writing." );
200219
}
201-
220+
202221
m_dtexPixel = DtexMakePixel( numChannels );
203222
}
204223

test/IECoreRI/SHWDeepImageReaderTest.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
class TestSHWDeepImageReader( unittest.TestCase ) :
4141

4242
__shw = "test/IECoreRI/data/shw/translucentBoxes.shw"
43+
__shwConstantPlane = "test/IECoreRI/data/shw/constantPlane.shw"
44+
__shwConstantPlane2 = "test/IECoreRI/data/shw/constantPlane2.shw"
45+
__shwOrtho = "test/IECoreRI/data/shw/constantPlaneOrtho.shw"
46+
4347
__exr = "test/IECoreRI/data/shw/groundPlane.exr"
4448

4549
def testConstructor( self ) :
@@ -113,16 +117,16 @@ def testReadPixel( self ) :
113117
p = reader.readPixel( 100, 100 )
114118
self.assertEqual( p.channelNames(), ( "A", ) )
115119
self.assertEqual( p.numSamples(), 1 )
116-
self.assertAlmostEqual( p.getDepth( 0 ), 107.5978927, 6 )
120+
self.assertAlmostEqual( p.getDepth( 0 ), 102.17636108, 6 )
117121
self.assertAlmostEqual( p[0][0], 1.0, 6 )
118122

119123
# hits one box then ground plane
120124
p2 = reader.readPixel( 256, 256 )
121125
self.assertEqual( p2.channelNames(), tuple(reader.channelNames()) )
122126
self.assertEqual( p2.numSamples(), 3 )
123-
self.assertAlmostEqual( p2.getDepth( 0 ), 71.7940826, 6 )
124-
self.assertAlmostEqual( p2.getDepth( 1 ), 76.9240646, 6 )
125-
self.assertAlmostEqual( p2.getDepth( 2 ), 84.8475646, 6 )
127+
self.assertAlmostEqual( p2.getDepth( 0 ), 72.6087493, 6 )
128+
self.assertAlmostEqual( p2.getDepth( 1 ), 77.7387313, 6 )
129+
self.assertAlmostEqual( p2.getDepth( 2 ), 85.6622314, 6 )
126130

127131
expected = ( 0.5, 0.5, 1.0 )
128132
for i in range( 0, len(expected) ) :
@@ -132,11 +136,11 @@ def testReadPixel( self ) :
132136
p3 = reader.readPixel( 195, 225 )
133137
self.assertEqual( p3.channelNames(), tuple(reader.channelNames()) )
134138
self.assertEqual( p3.numSamples(), 5 )
135-
self.assertAlmostEqual( p3.getDepth( 0 ), 68.2118148, 6 )
136-
self.assertAlmostEqual( p3.getDepth( 1 ), 74.9367370, 6 )
137-
self.assertAlmostEqual( p3.getDepth( 2 ), 77.0554046, 6 )
138-
self.assertAlmostEqual( p3.getDepth( 3 ), 79.7311859, 6 )
139-
self.assertAlmostEqual( p3.getDepth( 4 ), 88.5616073, 6 )
139+
self.assertAlmostEqual( p3.getDepth( 0 ), 68.6177368, 6 )
140+
self.assertAlmostEqual( p3.getDepth( 1 ), 75.3023605, 6 )
141+
self.assertAlmostEqual( p3.getDepth( 2 ), 77.4083328, 6 )
142+
self.assertAlmostEqual( p3.getDepth( 3 ), 80.0680771, 6 )
143+
self.assertAlmostEqual( p3.getDepth( 4 ), 88.8455811, 6 )
140144

141145
expected = ( 0.5, 0.75, 0.5, 0.5, 1.0 )
142146
for i in range( 0, len(expected) ) :
@@ -170,5 +174,31 @@ def testComposite( self ) :
170174
for i in range( imageData.size() ) :
171175
self.assertAlmostEqual( imageData[i], realImageData[i], 6 )
172176

177+
def __testDepthConversionWithFile( self, filename, tolerance ) :
178+
179+
reader = IECore.DeepImageReader.create( filename )
180+
dataWindow = reader.dataWindow()
181+
182+
for y in range( dataWindow.min.y, dataWindow.max.y + 1 ) :
183+
for x in range( dataWindow.min.x, dataWindow.max.x + 1 ) :
184+
p = reader.readPixel( x, y )
185+
s = p.numSamples()
186+
avgDepth = 0
187+
for i in range( s ):
188+
avgDepth += p.getDepth( i ) / s
189+
self.assertAlmostEqual( avgDepth, 10, tolerance )
190+
191+
def testDepthConversion( self ) :
192+
193+
# The first constant plane test is rendered with a single sample, and no jittering, so the depth should match to 6 significant digits
194+
self.__testDepthConversionWithFile( TestSHWDeepImageReader.__shwConstantPlane, 4 )
195+
196+
# The second constant plane was rendered with a small number of jittered samples, and because of the low resolution, there is quite a bit
197+
# of variation in the correction factor across the pixel, so it won't be a perfect match, but it should still be close
198+
self.__testDepthConversionWithFile( TestSHWDeepImageReader.__shwConstantPlane2, 2 )
199+
200+
# Test out an ortho render as well
201+
self.__testDepthConversionWithFile( TestSHWDeepImageReader.__shwOrtho, 2 )
202+
173203
if __name__ == "__main__":
174204
unittest.main()

test/IECoreRI/SHWDeepImageWriterTest.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@
4141
class TestSHWDeepImageWriter( unittest.TestCase ) :
4242

4343
__shw = "test/IECoreRI/data/shw/translucentBoxes.shw"
44+
__shwOrtho = "test/IECoreRI/data/shw/constantPlaneOrtho.shw"
4445
__exr = "test/IECoreRI/data/dtex/groundPlane.exr"
4546
__output = "test/IECoreRI/data/shw/written.shw"
46-
47+
4748
def testConstructor( self ) :
4849

4950
self.failUnless( "shw" in IECore.DeepImageWriter.supportedExtensions() )
@@ -156,16 +157,16 @@ def testReadWritePixel( self ) :
156157
p = reader.readPixel( 100, 100 )
157158
self.assertEqual( p.channelNames(), ( "A", ) )
158159
self.assertEqual( p.numSamples(), 1 )
159-
self.assertAlmostEqual( p.getDepth( 0 ), 107.5978927, 6 )
160+
self.assertAlmostEqual( p.getDepth( 0 ), 102.17636108, 6 )
160161
self.assertAlmostEqual( p[0][0], 1.0, 6 )
161162

162163
# hits one box then ground plane
163164
p2 = reader.readPixel( 256, 256 )
164165
self.assertEqual( p2.channelNames(), tuple(reader.channelNames()) )
165166
self.assertEqual( p2.numSamples(), 3 )
166-
self.assertAlmostEqual( p2.getDepth( 0 ), 71.7940826, 6 )
167-
self.assertAlmostEqual( p2.getDepth( 1 ), 76.9240646, 6 )
168-
self.assertAlmostEqual( p2.getDepth( 2 ), 84.8475646, 6 )
167+
self.assertAlmostEqual( p2.getDepth( 0 ), 72.6087493, 6 )
168+
self.assertAlmostEqual( p2.getDepth( 1 ), 77.7387313, 6 )
169+
self.assertAlmostEqual( p2.getDepth( 2 ), 85.6622314, 6 )
169170

170171
expected = ( 0.5, 0.5, 1.0 )
171172
for i in range( 0, len(expected) ) :
@@ -175,11 +176,11 @@ def testReadWritePixel( self ) :
175176
p3 = reader.readPixel( 195, 225 )
176177
self.assertEqual( p3.channelNames(), tuple(reader.channelNames()) )
177178
self.assertEqual( p3.numSamples(), 5 )
178-
self.assertAlmostEqual( p3.getDepth( 0 ), 68.2118148, 6 )
179-
self.assertAlmostEqual( p3.getDepth( 1 ), 74.9367370, 6 )
180-
self.assertAlmostEqual( p3.getDepth( 2 ), 77.0554046, 6 )
181-
self.assertAlmostEqual( p3.getDepth( 3 ), 79.7311859, 6 )
182-
self.assertAlmostEqual( p3.getDepth( 4 ), 88.5616073, 6 )
179+
self.assertAlmostEqual( p3.getDepth( 0 ), 68.6177368, 6 )
180+
self.assertAlmostEqual( p3.getDepth( 1 ), 75.3023605, 6 )
181+
self.assertAlmostEqual( p3.getDepth( 2 ), 77.4083328, 6 )
182+
self.assertAlmostEqual( p3.getDepth( 3 ), 80.0680771, 6 )
183+
self.assertAlmostEqual( p3.getDepth( 4 ), 88.8455811, 6 )
183184

184185
expected = ( 0.5, 0.75, 0.5, 0.5, 1.0 )
185186
for i in range( 0, len(expected) ) :
@@ -200,16 +201,16 @@ def testReadWritePixel( self ) :
200201
rp = reader.readPixel( 0, 0 )
201202
self.assertEqual( rp.channelNames(), ( "A", ) )
202203
self.assertEqual( rp.numSamples(), 1 )
203-
self.assertAlmostEqual( rp.getDepth( 0 ), 107.5978927, 6 )
204+
self.assertAlmostEqual( rp.getDepth( 0 ), 102.17636108, 4 )
204205
self.assertAlmostEqual( rp[0][0], 1.0, 6 )
205206

206207
# hits one box then ground plane
207208
rp2 = reader.readPixel( 0, 1 )
208209
self.assertEqual( rp2.channelNames(), tuple(reader.channelNames()) )
209210
self.assertEqual( rp2.numSamples(), 3 )
210-
self.assertAlmostEqual( rp2.getDepth( 0 ), 71.7940826, 6 )
211-
self.assertAlmostEqual( rp2.getDepth( 1 ), 76.9240646, 6 )
212-
self.assertAlmostEqual( rp2.getDepth( 2 ), 84.8475646, 6 )
211+
self.assertAlmostEqual( rp2.getDepth( 0 ), 72.6087493, 4 )
212+
self.assertAlmostEqual( rp2.getDepth( 1 ), 77.7387313, 4 )
213+
self.assertAlmostEqual( rp2.getDepth( 2 ), 85.6622314, 4 )
213214

214215
expected = ( 0.5, 0.5, 1.0 )
215216
for i in range( 0, len(expected) ) :
@@ -219,11 +220,11 @@ def testReadWritePixel( self ) :
219220
rp3 = reader.readPixel( 1, 1 )
220221
self.assertEqual( rp3.channelNames(), tuple(reader.channelNames()) )
221222
self.assertEqual( rp3.numSamples(), 5 )
222-
self.assertAlmostEqual( rp3.getDepth( 0 ), 68.2118148, 6 )
223-
self.assertAlmostEqual( rp3.getDepth( 1 ), 74.9367370, 6 )
224-
self.assertAlmostEqual( rp3.getDepth( 2 ), 77.0554046, 6 )
225-
self.assertAlmostEqual( rp3.getDepth( 3 ), 79.7311859, 6 )
226-
self.assertAlmostEqual( rp3.getDepth( 4 ), 88.5616073, 6 )
223+
self.assertAlmostEqual( rp3.getDepth( 0 ), 68.6177368, 6 )
224+
self.assertAlmostEqual( rp3.getDepth( 1 ), 75.3023605, 6 )
225+
self.assertAlmostEqual( rp3.getDepth( 2 ), 77.4083328, 6 )
226+
self.assertAlmostEqual( rp3.getDepth( 3 ), 80.0680771, 6 )
227+
self.assertAlmostEqual( rp3.getDepth( 4 ), 88.8455811, 6 )
227228

228229
expected = ( 0.5, 0.75, 0.5, 0.5, 1.0 )
229230
for i in range( 0, len(expected) ) :
@@ -299,17 +300,21 @@ def testEmptyPixel( self ) :
299300
self.failUnless( isinstance( rp, IECore.DeepPixel ) )
300301
self.assertEqual( rp[0], ( 0.5, ) )
301302

302-
def testFileConversion( self ) :
303+
def __testFileConversionWithFile( self, filename ) :
303304

304-
reader = IECore.DeepImageReader.create( TestSHWDeepImageWriter.__shw )
305+
reader = IECore.DeepImageReader.create( filename )
305306
dataWindow = reader.dataWindow()
306307

307308
writer = IECore.DeepImageWriter.create( TestSHWDeepImageWriter.__output )
308309
writer.parameters()['channelNames'].setValue( reader.channelNames() )
309310
writer.parameters()['resolution'].setTypedValue( dataWindow.size() + IECore.V2i( 1 ) )
311+
writer.parameters()['worldToCameraMatrix'] = reader.worldToCameraMatrix()
312+
writer.parameters()['worldToNDCMatrix'] = reader.worldToNDCMatrix()
313+
writer.parameters()['tileSize'] = IECore.V2i( 16, 16 )
314+
310315

311-
for y in range( dataWindow.min.y, dataWindow.max.y ) :
312-
for x in range( dataWindow.min.x, dataWindow.max.x ) :
316+
for y in range( dataWindow.min.y, dataWindow.max.y + 1 ) :
317+
for x in range( dataWindow.min.x, dataWindow.max.x + 1 ) :
313318
writer.writePixel( x, y, reader.readPixel( x, y ) )
314319

315320
del writer
@@ -319,8 +324,8 @@ def testFileConversion( self ) :
319324
self.assertEqual( reader2.dataWindow(), reader.dataWindow() )
320325
self.assertEqual( reader2.read(), reader.read() )
321326

322-
for y in range( dataWindow.min.y, dataWindow.max.y ) :
323-
for x in range( dataWindow.min.x, dataWindow.max.x ) :
327+
for y in range( dataWindow.min.y, dataWindow.max.y + 1 ) :
328+
for x in range( dataWindow.min.x, dataWindow.max.x + 1 ) :
324329
p = reader.readPixel( x, y )
325330
p2 = reader2.readPixel( x, y )
326331
if not p2 and not p :
@@ -329,9 +334,13 @@ def testFileConversion( self ) :
329334
self.assertEqual( p2.channelNames(), p.channelNames() )
330335
self.assertEqual( p2.numSamples(), p.numSamples() )
331336
for i in range( 0, p.numSamples() ) :
332-
self.assertEqual( p2.getDepth( i ), p.getDepth( i ) )
337+
self.assertAlmostEqual( p2.getDepth( i ), p.getDepth( i ), 4 )
333338
self.assertEqual( p2[i], p[i] )
334-
339+
340+
def testFileConversion( self ) :
341+
self.__testFileConversionWithFile( TestSHWDeepImageWriter.__shw )
342+
self.__testFileConversionWithFile( TestSHWDeepImageWriter.__shwOrtho )
343+
335344
def testStrangeOrder( self ) :
336345

337346
writer = IECoreRI.SHWDeepImageWriter( TestSHWDeepImageWriter.__output )
2.03 KB
Binary file not shown.
7.02 KB
Binary file not shown.
702 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)