Skip to content

Commit

Permalink
Merge pull request #6224 from lucienfostier/tensorToMeshPR
Browse files Browse the repository at this point in the history
TensorToMesh: Node to convert a position and vertexIds tensors into a mesh.
  • Loading branch information
johnhaddon authored Jan 27, 2025
2 parents aed2e50 + 1794a3a commit 77a4d8d
Show file tree
Hide file tree
Showing 12 changed files with 505 additions and 3 deletions.
4 changes: 2 additions & 2 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -1119,12 +1119,12 @@ libraries = {
"envAppends" : {
"CPPPATH" : [ "$ONNX_ROOT/include" ],
"LIBPATH" : [ "$ONNX_ROOT/lib" ],
"LIBS" : [ "Gaffer", "GafferImage", "onnxruntime" ],
"LIBS" : [ "Gaffer", "GafferImage", "onnxruntime", "GafferScene", "IECoreScene$CORTEX_LIB_SUFFIX" ],
},
"pythonEnvAppends" : {
"CPPPATH" : [ "$ONNX_ROOT/include" ],
"LIBPATH" : [ "$ONNX_ROOT/lib" ],
"LIBS" : [ "GafferBindings", "GafferImage", "GafferML", "onnxruntime" ],
"LIBS" : [ "GafferBindings", "GafferImage", "GafferML", "onnxruntime", "GafferScene", "IECoreScene$CORTEX_LIB_SUFFIX" ],
},
"requiredOptions" : [ "ONNX_ROOT" ],
},
Expand Down
80 changes: 80 additions & 0 deletions include/GafferML/TensorToMesh.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2024, Lucien Fostier. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the following
// disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided with
// the distribution.
//
// * Neither the name of John Haddon nor the names of
// any other contributors to this software may be used to endorse or
// promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include "GafferML/Export.h"
#include "GafferML/TensorPlug.h"

#include "GafferScene/ObjectSource.h"
#include "Gaffer/TransformPlug.h"

namespace GafferML
{

class GAFFERML_API TensorToMesh : public GafferScene::ObjectSource
{

public :

explicit TensorToMesh( const std::string &name=defaultName<TensorToMesh>() );
~TensorToMesh() override;

GAFFER_NODE_DECLARE_TYPE( GafferML::TensorToMesh, TensorToMeshTypeId, GafferScene::ObjectSource );

TensorPlug *positionTensorPlug();
const TensorPlug *positionTensorPlug() const;

TensorPlug *vertexIdsTensorPlug();
const TensorPlug *vertexIdsTensorPlug() const;

void affects( const Gaffer::Plug *input, Gaffer::DependencyNode::AffectedPlugsContainer &outputs ) const override;

protected :

void hashSource( const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
IECore::ConstObjectPtr computeSource( const Gaffer::Context *context ) const override;

private :


static size_t g_firstPlugIndex;

};

IE_CORE_DECLAREPTR( TensorToMesh )

} // namespace GafferML
1 change: 1 addition & 0 deletions include/GafferML/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ enum TypeId
InferenceTypeId = 110455,
TensorReaderTypeId = 110456,
DataToTensorTypeId = 110457,
TensorToMeshTypeId = 110458,

LastTypeId = 110500
};
Expand Down
1 change: 1 addition & 0 deletions python/GafferML/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

__import__( "Gaffer" )
__import__( "GafferImage" )
__import__( "GafferScene" )

if hasattr( os, "add_dll_directory" ) :
os.add_dll_directory( ( pathlib.Path( os.environ["ONNX_ROOT"] ) / "lib" ).resolve() )
Expand Down
147 changes: 147 additions & 0 deletions python/GafferMLTest/TensorToMeshTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
##########################################################################
#
# Copyright (c) 2024, Lucien Fostier. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with
# the distribution.
#
# * Neither the name of John Haddon nor the names of
# any other contributors to this software may be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import unittest

import imath

import IECore

import Gaffer
import GafferTest
import GafferScene
import GafferML

class TensorToMeshTest( GafferTest.TestCase ) :
def testNoInput( self ) :

node = GafferML.TensorToMesh()
with self.assertRaisesRegex( Gaffer.ProcessException, "Empty Position tensor" ) :
node["out"].object("/tensorMesh"),

def test( self ) :

points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ]
position = IECore.FloatVectorData( points )
positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] )
vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] )
vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["position"].setValue( positionTensor )
tensorToMesh["vertexIds"].setValue( vertexIdsTensor )

self.assertEqual( tensorToMesh["out"].object( "/" ), IECore.NullObject() )
self.assertEqual( tensorToMesh["out"].transform( "/" ), imath.M44f() )
self.assertEqual( tensorToMesh["out"].bound( "/" ), imath.Box3f( imath.V3f( 0 ), imath.V3f( 1, 1, 2 ) ) )
self.assertEqual( tensorToMesh["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "tensorMesh" ] ) )

self.assertEqual( tensorToMesh["out"].transform( "/tensorMesh" ), imath.M44f() )
self.assertEqual( tensorToMesh["out"].bound( "/tensorMesh" ), imath.Box3f( imath.V3f( 0 ), imath.V3f( 1, 1, 2 ) ) )
self.assertEqual( tensorToMesh["out"].childNames( "/tensorMesh" ), IECore.InternedStringVectorData() )

mesh = tensorToMesh["out"].object( "/tensorMesh" )
expectedValue = [component for vec in mesh["P"].data for component in (vec.x, vec.y, vec.z)]

self.assertEqual( expectedValue, points )
self.assertEqual( mesh.numFaces(), 1 )
self.assertEqual( mesh.verticesPerFace, IECore.IntVectorData( [ 3 ] ) )

def testWrongVertexTensorDimension( self ) :

points = [ 0.0, 0.0, 0.0 , 1.0, 1.0, 0.0, 1.0, 1.0, 2.0 ]
position = IECore.FloatVectorData( points )
positionTensor = GafferML.Tensor( position, [ 3, 3 ] )
vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] )
vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["position"].setValue( positionTensor )
tensorToMesh["vertexIds"].setValue( vertexIdsTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid position tensor number of dimensions" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

def testWrongVertexDimension( self ) :

points = [ 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 ]
position = IECore.FloatVectorData( points )
positionTensor = GafferML.Tensor( position, [ 1, 3, 2 ] )
vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] )
vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["position"].setValue( positionTensor )
tensorToMesh["vertexIds"].setValue( vertexIdsTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid position dimensions" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

def testEmptyFaces( self ):
points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ]
position = IECore.FloatVectorData( points )
positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["position"].setValue( positionTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Empty VertexIds tensor" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

def testWrongDataTypeVertices( self ):
points = [ 0, 0, 0, 1, 1, 1, 1, 0, 2 ]
position = IECore.IntVectorData( points )
positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] )
vertexIds = IECore.Int64VectorData( [ 0, 1, 2 ] )
vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["position"].setValue( positionTensor )
tensorToMesh["vertexIds"].setValue( vertexIdsTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for position tensor" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

def testWrongDataTypeFaces( self ):
points = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 ]
position = IECore.FloatVectorData( points )
positionTensor = GafferML.Tensor( position, [ 1, 3, 3 ] )
vertexIds = IECore.IntVectorData( [ 0, 1, 2 ] )
vertexIdsTensor = GafferML.Tensor( vertexIds, [ 1, 3 ] )
tensorToMesh = GafferML.TensorToMesh()
tensorToMesh["position"].setValue( positionTensor )
tensorToMesh["vertexIds"].setValue( vertexIdsTensor )

with self.assertRaisesRegex( Gaffer.ProcessException, "Invalid data type input for vertexIds tensor" ):
mesh = tensorToMesh["out"].object( "/tensorMesh" )

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/GafferMLTest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from .InferenceTest import InferenceTest
from .ImageToTensorTest import ImageToTensorTest
from .TensorToImageTest import TensorToImageTest
from .TensorToMeshTest import TensorToMeshTest

if __name__ == "__main__":
import unittest
Expand Down
90 changes: 90 additions & 0 deletions python/GafferMLUI/TensorToMeshUI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
##########################################################################
#
# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with
# the distribution.
#
# * Neither the name of John Haddon nor the names of
# any other contributors to this software may be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import Gaffer
import GafferML

import GafferSceneUI

Gaffer.Metadata.registerNode(

GafferML.TensorToMesh,

"description",
"""
Turns input tensors into a mesh.
""",


plugs = {

"position" : [

"description",
"""
The input tensor containing the point positions for the mesh to create. Typically this would be connected
to the output of an Inference node that is doing mesh generation/processing.
""",

"plugValueWidget:type", "",
"nodule:type", "GafferUI::StandardNodule",

],
"vertexIds" : [

"description",
"""
The input tensor containing the vertex ids for the mesh to create. Typically this would be connected
to the output of an Inference node that is doing mesh generation/processing.
""",

"plugValueWidget:type", "",
"nodule:type", "GafferUI::StandardNodule",

],

"out" : [

"description",
"""
The output mesh.
""",

],


}
)
1 change: 1 addition & 0 deletions python/GafferMLUI/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@
from . import InferenceUI
from . import ImageToTensorUI
from . import TensorToImageUI
from . import TensorToMeshUI

__import__( "IECore" ).loadConfig( "GAFFER_STARTUP_PATHS", subdirectory = "GafferMLUI" )
3 changes: 2 additions & 1 deletion python/GafferMLUITest/DocumentationTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import GafferUITest

import GafferImage
import GafferScene
import GafferML
import GafferMLUI

Expand All @@ -47,7 +48,7 @@ def test( self ) :
self.maxDiff = None
self.assertNodesAreDocumented(
GafferML,
additionalTerminalPlugTypes = ( GafferImage.ImagePlug, )
additionalTerminalPlugTypes = ( GafferImage.ImagePlug, GafferScene.ScenePlug )
)

if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 77a4d8d

Please sign in to comment.