PolyDataの記述:線分

VTKにおいて線分情報をPolyDataを用いて記述してみる. データセット要素 PolyData のうち,PointsとLinesを用いて幾何形状を形成し,Schalar Data をのせる. 例として,

\begin{cases}
x &= \cos( 2 \pi t        ) \\
y &= \sin( 2 \pi t        ) \ \ ( \ 0 \le t \le 100 \ ) \\
z &= \sin( 2 \pi t * 0.01 )
\end{cases}

で表される螺旋を描く.

PolyData (Line) の例

PolyDataデータセット要素を用いて3次元空間内で線分を表現できる. PolyDataを用いた記述例を以下に示す.

<?xml version="1.0" encoding="utf-8"?>
<VTKFile type="PolyData">
  <PolyData>
    <Piece NumberOfPoints="21" NumberOfLines="20" NumberOfVerts="0" NumberOfStrips="0" NumberOfPolys="0">
      <PointData Scalars="Line">
        <DataArray Name="Line" type="Float64" NumberOfComponents="1" format="ascii">
	  0.0 5.0 10.0 15.0 20.0 25.0 30.0 35.0 40.0 45.0 50.0 55.0 60.0 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 
	</DataArray>
      </PointData>
      <Points>
        <DataArray Name="points" type="Float64" NumberOfComponents="3" format="ascii">
	  1.0 0.0 0.0
	  1.0 -1.2246467991473533e-15 0.3090169943749474
	  1.0 -2.4492935982947065e-15 0.5877852522924731
	  1.0 -1.0779367755043061e-14 0.8090169943749473
	  1.0 -4.898587196589413e-15 0.9510565162951535
	  1.0 9.82193361864236e-16 1.0
	  1.0 -2.1558735510086122e-14 0.9510565162951536
	  1.0 -1.5677954951632475e-14 0.8090169943749475
	  1.0 -9.797174393178826e-15 0.5877852522924732
	  1.0 -3.9163938347251765e-15 0.3090169943749475
	  1.0 1.964386723728472e-15 1.2246467991473532e-16
	  1.0 7.84516728218212e-15 -0.30901699437494773
	  1.0 -4.3117471020172244e-14 -0.587785252292473
	  1.0 -3.7236690461718594e-14 -0.8090169943749473
	  1.0 -3.135590990326495e-14 -0.9510565162951535
	  1.0 -2.54751293448113e-14 -1.0
	  1.0 -1.9594348786357652e-14 -0.9510565162951536
	  1.0 -1.3713568227904002e-14 -0.8090169943749476
	  1.0 -7.832787669450353e-15 -0.5877852522924732
	  1.0 -1.952007110996705e-15 -0.3090169943749476
	  1.0 3.928773447456944e-15 -2.4492935982947064e-16
	</DataArray>
      </Points>
      <Lines>
        <DataArray Name="connectivity" type="Int64" NumberOfComponents="1" format="ascii">
	  0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 
	</DataArray>
        <DataArray Name="offsets" type="Int64" NumberOfComponents="1" format="ascii">
	  2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 
	</DataArray>
      </Lines>
    </Piece>
  </PolyData>
</VTKFile>

線分データの記述様式

線分をPolyDataを用いて書く場合,<Points> と <Lines> タグを用いて,線分を表現する. <PolyData>タグ内( <PolyData> <Piece> ... </Piece> </PolyData> )の構成要素は,

  • <PointData>... </PointData> ( 線分にのせるスカラー情報 : [nData] )

  • <Points>...</Points> ( 線分を構成する 点群 : [nData,NumberOfComponents=3] )

  • <Lines>...</Lines> ( 線分の 接続情報connectivity / offsets により構成 )

    • <DataArray Name="connectivity" >...</DataArray> ( 各データ間の 連結 (0,1), (1,2), ... )

    • <DataArray Name="offsets" >...</DataArray> ( 各構成要素データの 終点 を示す 2 4 6 8 ... )

Paraviewによる表示例

Paraviewによる 3次元描画例 を示す.

../_images/screenshot_PolyLine_ParaView_sample.png

PolyData(Line) 作成用クラス

PolyDataを用いて,線分を記述するためのクラス, vtk_makePolyData_line を以下に示す.

入出力

vtk_makePolyData_line( vtkFile=None, Data=None, xyz=None, VectorData=False, DataFormat="ascii" )
  • vtkFile = ( default : out.vtp )

  • Data = [ NoPoints, NoLines ]

  • xyz = [ NoPoints, NoCoords(3), NoLines ]

  • VectorData = True or False ( default : False )

  • DataFormat = ( default : "ascii" )

コード

import sys, subprocess
import numpy as np


# ========================================================= #
# ===  vtk_makePolyData_line class                      === #
# ========================================================= #
class vtk_makePolyData_line():
    # ------------------------------------------------- #
    # --- class Initiator                           --- #
    # ------------------------------------------------- #
    def __init__( self, vtkFile=None, Data=None, xyz=None, \
                  VectorData=False, DataFormat="ascii", ):
        # --- [1-1] Arguments                       --- #
        if ( vtkFile is None ): vtkFile = "out.vtp"
        # --- [1-2] Variables Settings              --- #
        self.vtkFile      = vtkFile
        self.vtkContents  = ''
        self.vtkEndTags   = ''
        self.xyz          = xyz
        self.Data         = Data
        self.DataFormat   = DataFormat
        self.VectorData   = VectorData
        self.DataDims     = None
        self.NoLines      = None
        self.NoPoints     = None
        self.NoCoords     = None
        # --- [1-3] Routines                        --- #
        self.inquireLineData( xyz=self.xyz, Data=self.Data, VectorData=self.VectorData )
        self.vtk_add_VTKFileTag( datatype="PolyData" )
        self.vtk_add_PolyDataTag_Line( Data=self.Data, xyz=self.xyz, VectorData=self.VectorData )
        self.vtk_writeFile()

        
    # ------------------------------------------------- #
    # --- vtk_add_VTKFileTag                        --- #
    # ------------------------------------------------- #
    def vtk_add_VTKFileTag( self, datatype=None ):
        # ------------------------------------------------- #
        # --- [1] Add XML Definition & VTKFile Tag      --- #
        # ------------------------------------------------- #
        if ( datatype is None ): datatype = "ImageData"
        self.vtkContents  += '<?xml version="1.0"?>\n'
        self.vtkContents  += '<VTKFile type="{0}">\n'.format( datatype )
        self.vtkEndTags    = '</VTKFile>'     + '\n' + self.vtkEndTags

        
    # ------------------------------------------------- #
    # --- vtk_add_PolyDataTag                       --- #
    # ------------------------------------------------- #
    def vtk_add_PolyDataTag_Line( self, xyz=None, Data=None, VectorData=None, DataName="Line", \
                                  NoPoints=None, NoSegments=None, NoVerts=0, NoStrips=0, NoPolys=0 ):
        # ------------------------------------------------- #
        # --- [1] Arguments                             --- #
        # ------------------------------------------------- #
        if ( xyz        is None ): xyz        = self.xyz
        if ( Data       is None ): Data       = self.Data
        if ( VectorData is None ): VectorData = self.VectorData
        if ( NoPoints   is None ): NoPoints   = self.NoPoints
        if ( NoSegments is None ): NoSegments = self.NoPoints-1
        self.inquireLineData( xyz=xyz, Data=Data, VectorData=VectorData )
        connect, offsets   = self.prepareLineInfo( NoPoints=NoPoints )
        # ------------------------------------------------- #
        # --- [2] Open PolyData Tag                     --- #
        # ------------------------------------------------- #
        self.vtkContents  += '<PolyData>\n'
        self.vtkContents  += '<Piece NumberOfPoints="{0}" NumberOfLines="{1}" ' \
                             'NumberOfVerts="{2}" NumberOfStrips="{3}" NumberOfPolys="{4}">\n'\
                             .format( NoPoints, NoSegments, NoVerts, NoStrips, NoPolys )
        # ------------------------------------------------- #
        # --- [3] add Point & Line Data                 --- #
        # ------------------------------------------------- #
        #  -- [3-1] Data   -- #
        self.vtkContents  += '<PointData {0}="{1}">\n'.format( "Scalars", DataName )
        self.vtkContents  += self.vtk_add_DataArray( Data=Data, DataName=DataName, VectorData=False )
        self.vtkContents  += '</PointData>\n'
        #  -- [3-2] xyz points -- #
        self.vtkContents  += '<Points>\n'
        self.vtkContents  += self.vtk_add_DataArray( Data=xyz, DataName="points", VectorData=True )
        self.vtkContents  += '</Points>\n'
        #  -- [3-3] connectivity -- #
        self.vtkContents  += '<Lines>\n'
        self.vtkContents  += self.vtk_add_DataArray( Data=connect, DataName="connectivity", VectorData=False )
        self.vtkContents  += self.vtk_add_DataArray( Data=offsets, DataName="offsets"     , VectorData=False )
        self.vtkContents  += '</Lines>\n'
        # ------------------------------------------------- #
        # --- [4] Close PolyData Tags                   --- #
        # ------------------------------------------------- #
        self.vtkContents  += '</Piece>\n'
        self.vtkContents  += '</PolyData>\n'


    # ------------------------------------------------- #
    # --- vtk_add_DataArray                         --- #
    # ------------------------------------------------- #
    def vtk_add_DataArray( self, Data=None, DataName=None, DataFormat=None, DataType=None, nComponents=None, nData=None, VectorData=False ):
        if ( Data        is None ): sys.exit( "[vtk_add_DataArray -@makeStructuredGrid-] Data     == ??? " )
        if ( DataName    is None ): sys.exit( "[vtk_add_DataArray -@makeStructuredGrid-] DataName == ??? " )
        if ( DataFormat  is None ): DataFormat  = self.DataFormat
        if ( DataType    is None ): DataType    = self.inquiryData( Data=Data, ret_DataType   =True, VectorData=VectorData )
        if ( nComponents is None ): nComponents = self.inquiryData( Data=Data, ret_nComponents=True, VectorData=VectorData )
        if ( nData       is None ): nData       = self.inquiryData( Data=Data, ret_nData      =True, VectorData=VectorData )
        ret  = ""
        ret += '<DataArray Name="{0}" type="{1}" NumberOfComponents="{2}" format="{3}">\n'\
                                 .format( DataName, DataType, nComponents, DataFormat )
        lines = ""
        if ( nComponents == 1 ):
            for line in np.ravel( Data ):
                lines += "{0} ".format( line )
            lines += "\n"
        else:
            for line in Data:
                lines += ( " ".join( [ str( val ) for val in line ] ) + "\n" )
        ret += lines
        ret += '</DataArray>\n'
        return( ret )

    
    # ------------------------------------------------- #
    # --- vtk_writeFile                             --- #
    # ------------------------------------------------- #
    def vtk_writeFile( self, vtkFile=None ):
        if ( vtkFile is None ): vtkFile = self.vtkFile
        with open( vtkFile, "w" ) as f:
            f.write( self.vtkContents )
            f.write( self.vtkEndTags  )
        subprocess.call( ( "xmllint --format --encode utf-8 {0} -o {0}"\
                           .format( vtkFile ) ).split() )
        print( "[vtk_writeFile-@makePolyData_line-] VTK File output :: {0}".format( vtkFile ) )


    # ------------------------------------------------- #
    # --- inquiryData                               --- #
    # ------------------------------------------------- #
    def inquiryData( self, Data=None, VectorData=False, ret_DataType=False, ret_nComponents=False, ret_nData=False ):
        if ( Data is None ): sys.exit( "[inquiryData-@vtk_makeImageData-] Data  == ??? " )
        # ------------------------------------------------- #
        # --- [1] DataType Check                        --- #
        # ------------------------------------------------- #
        if ( type(Data) is not np.ndarray ):
            sys.exit( "[inquiryData-@vtk_makeImageData-] Data should be np.ndarray [ERROR]" )
        if ( Data.dtype == np.int32   ): DataType = "Int32"
        if ( Data.dtype == np.int64   ): DataType = "Int64"
        if ( Data.dtype == np.float32 ): DataType = "Float32"
        if ( Data.dtype == np.float64 ): DataType = "Float64"
        # ------------------------------------------------- #
        # --- [2] Data Shape Check                      --- #
        # ------------------------------------------------- #
        if ( VectorData is True ):
            nComponents = Data.shape[-1]
            nData       = np.size( Data[-1][:] )
        else:
            nComponents = 1
            nData       = np.size( Data[:]    )
        # ------------------------------------------------- #
        # --- [3] Return                                --- #
        # ------------------------------------------------- #
        if ( ret_DataType    ): return( DataType    )
        if ( ret_nComponents ): return( nComponents )
        if ( ret_nData       ): return( nData       )
        return( { "DataType":DataType, "nComponents":nComponents, "nData":nData } )


    # ------------------------------------------------- #
    # --- vtk_inquireLineData                       --- #
    # ------------------------------------------------- #
    def inquireLineData( self, xyz=None, Data=None, VectorData=False ):
        # ------------------------------------------------- #
        # --- [1] Arguments                             --- #
        # ------------------------------------------------- #
        if ( xyz  is None ): xyz  = self.xyz
        if ( xyz  is None ): return( None )
        if ( Data is None ): Data = self.Data
        if ( type( xyz ) is not np.ndarray ):
            print( "[inquireLineData-@makePolyData_Line-] xyz should be np.ndarray [ERROR]" )
        if ( xyz.ndim >= 5 ):
            sys.exit( "[inquireLineData-@makePolyData_Line-] incorrect xyz size ( ndim >= 5 ) [ERROR]" )
        # ------------------------------------------------- #
        # --- [2] DataDims & LILJLK Check               --- #
        # ------------------------------------------------- #
        if ( VectorData is True ):
            self.NoPoints   = xyz.shape[0]
            self.NoCoords   = xyz.shape[1]
            self.NoLines    = xyz.shape[2]
        else:
            self.NoPoints   = xyz.shape[0]
            self.NoCoords   = xyz.shape[1]
            self.NoLines    = 1
            xyz             =  xyz.reshape(  xyz.shape + (1,) )
        # ------------------------------------------------- #
        # --- [3] prepare Data                          --- #
        # ------------------------------------------------- #
        if ( Data is None ): Data = np.zeros( ( self.NoPoints, self.NoLines ) )
        return( xyz, Data )

    
    # ------------------------------------------------- #
    # --- prepareLineInfo                           --- #
    # ------------------------------------------------- #
    def prepareLineInfo( self, NoPoints=None ):
        if ( NoPoints is None ): NoPoints = self.NoPoints
        connect = np.ravel( np.array( [ [ ik, ik+1 ] for ik in range( NoPoints-1 ) ] ) )
        offsets = np.ravel( np.array( [ 2*(ik+1)     for ik in range( NoPoints-1 ) ] ) )
        return( connect, offsets )

        
# ======================================== #
# ===  実行部                          === #
# ======================================== #
if ( __name__=="__main__" ):
    import myUtils.genArgs as gar
    args    = gar.genArgs()
    tAxis   = np.linspace( 0.0, 100.0, 10001 )
    xAxis   = np.cos( 2.*np.pi*tAxis        )
    yAxis   = np.sin( 2.*np.pi*tAxis        )
    zAxis   = np.sin( 2.*np.pi*tAxis * 0.01 )
    import myConvert.pilearr  as pil
    xyz     = np.transpose( pil.pilearr( (xAxis,yAxis,zAxis), axis=1 ) )
    Data    = np.copy( tAxis )
    vtk     = vtk_makePolyData_line( xyz=xyz, Data=Data )