牛骨文教育服务平台(让学习变的简单)
博文笔记

PLY文件格式及其MATLAB读写操作

创建时间:2016-07-13 投稿人: 浏览次数:2327
     PLY是一种电脑档案格式,全名为多边形档案(Polygon File Format)或 斯坦福三角形档案(Stanford Triangle Format)。 



    史丹佛大学的 The Digital Michelangelo Project计划采用PLY格式储存极高分辨率之米开朗基罗的作品"大卫"雕塑。



   该格式主要用以储存立体扫描结果的三维数值,透过多边形片面的集合描述三维物体,与其他格式相较之下这是较为简单的方法。它可以储存的资讯包含颜色、透明度、表面法向量、材质座标与资料可信度,并能对多边形的正反两面设定不同的属性。



   在档案内容的储存上PLY有两种版本,分别是纯文字(ASCII)版本与二元码(binary)版本,其差异在储存时是否以ASCII编码表示元素资讯。

档案格式

(本文并未提供完整的格式描述,以下仅介绍PLY的基本概念与格式)

每个PLY档都包含档头(header),用以设定网格模型的“元素”与“属性”,以及在档头下方接着一连串的元素“数值资料”。一般而言,网格模型的“元素”就是顶点(vertices)、面(faces),另外还可能包含有边(edges)、深度图样本(samples of range maps)与三角带(triangle strips)等元素。无论是纯文字与二元码的PLY档,档头资讯都是以ASCII编码编写,接续其后的数值资料才有编码之分。PLY档案以此行:

?

ply

开头作为PLY格式的识别。接着第二行是版本资讯,目前有三种写法:

?

format ascii 1.0

format binary_little_endian 1.0

format binary_big_endian 1.0

其中ascii, binary_little_endian, binary_big_endian是档案储存的编码方式,而1.0是遵循的标准版本(现阶段仅有PLY 1.0版)。在档头中可使用’comment’作为一行的开头以编写注解,例如:

?

comment Thisisa comment!

描述元素及属性,必须使用’element’及’property’的关键字,一般的格式为element下方接着属性列表,例如:

?

element

property

property

property

‘property’不仅定义了资料的型态,其出现顺序亦定义了资料的顺序。内定的资料形态有两种写法:一种是char uchar short ushort int uint float double,另外一种是具有位元长度的int8 uint8 int16 uint16 int32 uint32 float32 float64。 例如,描述一个包含12个顶点的物体,每个顶点使用3个单精度浮点数 (x,y,z)代表点的座标,使用3个unsigned char代表顶点颜色,颜色顺序为 (B, G, R),则档头的写法为:

?

element vertex 12

propertyfloatx

propertyfloaty

propertyfloatz

property uchar blue

property uchar green

property uchar red

其中vertex是内定的元素类型,接续的6行property描述构成vertex元素的数值字段顺序代表的意义,及其资料形态。

另一个常使用的元素是面。由于一个面是由3个以上的顶点所组成,因此使用一个“顶点列表”即可描述一个面, PLY格式使用一个特殊关键字’property list’定义之。 例如,一个具有10个面的物体,其PLY档头可能包含:

?

element face 10

property list ucharintvertex_indices

‘property list’表示该元素face的特性是由一行的顶点列表来描述。列表开头以uchar型态的数值表示列表的项目数,后面接着资料型态为int的顶点索引值(vertex_indices),顶点索引值从0开始。

最后,标头必须以此行结尾:

?

end_header

档头后接着的是元素资料(端点座标、拓朴连结等)。在ASCII格式中各个端点与面的资讯都是以独立的一行描述,而二元编码格式则连续储存这些资料,加载时须以’element’定义的元素数目以及’property’中设定的资料形态计算各笔字段的长度。

范例

一个典型的PLY档案结构分成三部分:

?

档头 (从ply开始到end_header)

顶点元素列表

面元素列表

其中的顶点元素列表一般以x y z方式排列,形态如档头所定义;而面元素列表是以下列格式表示。

?

<組成面的端點數N> <端點#1的索引> <端點#2的索引> … <端點#N的索引>

例如画出一个有4个顶点,4个面的四面体,档案内容为:

?

ply

format ascii 1.0

comment這是一個正四面體

element vertex 4

propertyfloatx

propertyfloaty

propertyfloatz

element face 4

property list ucharintvertex_index

end_header

0 3 0

2.449 -1.0 -1.414

0 -1 2.828

-2.449 -1.0 -1.414

3 0 1 3

3 0 2 1

3 0 3 2

3 1 2 3

其中1~10行是档头, 11~14行是顶点元素列表, 15~18行则是面元素列表。

其中: 0 3 0是顶点

历史

PLY格式发展于90年代中期,在史丹佛大学图学实验室的Marc Levoy教授指导下,由Greg Turk及其他成员开发出来。PLY格式受Wavefront .obj格式的启发,但改进了Obj格式所缺少的对任意属性及群组的扩充性。因此PLY格式发明了”property”及”element”这两个关键词,来概括“顶点、面、相关资讯、群组”的概念。

注意

ply文件不支持中文格式的文件名字,所以在使用过程中避免使用中文来命名。

使用MATLAB对PLY文件进行读操作

function [ Elements, varargout ] = PLY_READ ( Path, Str )

%*****************************************************************************80
%
%% PLY_READ reads a PLY 3D data file.
%
%   [DATA,COMMENTS] = PLY_READ(FILENAME) reads a version 1.0 PLY file
%   FILENAME and returns a structure DATA.  The fields in this structure
%   are defined by the PLY header; each element type is a field and each
%   element property is a subfield.  If the file contains any comments,
%   they are returned in a cell string array COMMENTS.
%
%   [TRI,PTS] = PLY_READ(FILENAME,"tri") or
%   [TRI,PTS,DATA,COMMENTS] = PLY_READ(FILENAME,"tri") converts vertex
%   and face data into triangular connectivity and vertex arrays.  The
%   mesh can then be displayed using the TRISURF command.
%
%   Note: This function is slow for large mesh files (+50K faces),
%   especially when reading data with list type properties.
%
%   Example:
%   [Tri,Pts] = PLY_READ("cow.ply","tri");
%   [Tri,Pts] = PLY_READ("bunny.ply","tri");
%   trisurf(Tri,Pts(:,1),Pts(:,2),Pts(:,3));
%   colormap(gray); axis equal;
%
%  Discussion:
%
%    The original version of this program had a mistake that meant it
%    did not properly triangulate files whose faces were not already triangular.
%    This has been corrected (JVB, 25 February 2007).
%
%    Glenn Ramsey pointed out and corrected a problem that occurred
%    with an uninitialized value of Type2, 27 August 2012.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    27 August 2012
%
%  Author:
%
%    Pascal Getreuer 2004
%
%  Parameters:
%
%  Local Parameters:
%
%    COMMENTS, any comments from the file.
%
%    ELEMENTCOUNT, the number of each type of element in file.
%
%    ELEMENTS, the element data.
%
%    PROPERTYTYPES, the element property types.
%
%    SIZEOF, size in bytes of each type.
%

%
%  Open the input file in "read text" mode.
%
  [ fid, Msg ] = fopen ( Path, "rt" );

  if ( fid == -1 )
    error ( Msg );
  end

  Buf = fscanf ( fid, "%s", 1 );

  if ( ~strcmp ( Buf, "ply" ) )
    fclose ( fid );
    error("Not a PLY file.");
  end
%
%  Read the header.
%
  Position = ftell(fid);
  Format = "";
  NumComments = 0;
  Comments = {};
  NumElements = 0;
  NumProperties = 0;
  Elements = [];
  ElementCount = [];
  PropertyTypes = [];
  ElementNames = {};  % list of element names in the order they are stored in the file
  PropertyNames = [];  % structure of lists of property names

  while ( 1 )
%
%  Read a line from the file.
%
    Buf = fgetl ( fid );
    BufRem = Buf;
    Token = {};
    Count = 0;
%
%  Split the line into tokens.
%
    while ( ~isempty(BufRem) )

      [ tmp, BufRem ] = strtok(BufRem);
%
%  Count the tokens.
%
      if ( ~isempty ( tmp ) )
        Count = Count + 1;
        Token{Count} = tmp;
      end

    end
%
%  Parse the line.
%
    if ( Count )

      switch lower ( Token{1} )
%
%  Read the data format.
%
      case "format"

        if ( 2 <= Count )

          Format = lower ( Token{2} );

          if ( Count == 3 & ~strcmp ( Token{3}, "1.0" ) )
            fclose ( fid );
            error("Only PLY format version 1.0 supported.");
          end
        end
%
%  Read a comment.
%
      case "comment"

        NumComments = NumComments + 1;
        Comments{NumComments} = "";
        for i = 2 : Count
          Comments{NumComments} = [Comments{NumComments},Token{i}," "];
        end
%
%  Read an element name.
%
      case "element"

        if ( 3 <= Count )

          if ( isfield(Elements,Token{2}) )
            fclose ( fid );
            error(["Duplicate element name, """,Token{2},"""."]);
          end

          NumElements = NumElements + 1;
          NumProperties = 0;
          Elements = setfield(Elements,Token{2},[]);
          PropertyTypes = setfield(PropertyTypes,Token{2},[]);
          ElementNames{NumElements} = Token{2};
          PropertyNames = setfield(PropertyNames,Token{2},{});
          CurElement = Token{2};
          ElementCount(NumElements) = str2double(Token{3});

          if ( isnan(ElementCount(NumElements)) )
            fclose ( fid );
            error(["Bad element definition: ",Buf]);
          end

        else

          error(["Bad element definition: ",Buf]);

        end
%
%  Read an element property.
%
      case "property"

        if ( ~isempty(CurElement) & Count >= 3 )

          NumProperties = NumProperties + 1;
          eval(["tmp=isfield(Elements.",CurElement,",Token{Count});"],...
            "fclose(fid);error([""Error reading property: "",Buf])");

          if ( tmp )
            error(["Duplicate property name, """,CurElement,".",Token{2},"""."]);
          end
%
%  Add property subfield to Elements.
%
          eval(["Elements.",CurElement,".",Token{Count},"=[];"], ...
            "fclose(fid);error([""Error reading property: "",Buf])");
%
%  Add property subfield to PropertyTypes and save type.
%
          eval(["PropertyTypes.",CurElement,".",Token{Count},"={Token{2:Count-1}};"], ...
            "fclose(fid);error([""Error reading property: "",Buf])");
%
%  Record property name order.
%
          eval(["PropertyNames.",CurElement,"{NumProperties}=Token{Count};"], ...
            "fclose(fid);error([""Error reading property: "",Buf])");

         else

           fclose ( fid );

           if ( isempty(CurElement) )
             error(["Property definition without element definition: ",Buf]);
           else
             error(["Bad property definition: ",Buf]);
           end

         end
%
%  End of header.
%
        case "end_header"
          break;

      end
    end
  end
%
%  Set reading for specified data format.
%
  if ( isempty ( Format ) )
    warning("Data format unspecified, assuming ASCII.");
    Format = "ascii";
  end

  switch Format

    case "ascii"
      Format = 0;
    case "binary_little_endian"
      Format = 1;
    case "binary_big_endian"
      Format = 2;
    otherwise
      fclose ( fid );
      error(["Data format """,Format,""" not supported."]);

  end
%
%  Read the rest of the file as ASCII data...
%
  if ( ~Format )
    Buf = fscanf ( fid, "%f" );
    BufOff = 1;
  else
%
%  ...or, close the file, and reopen in "read binary" mode.
%
    fclose ( fid );
%
%  Reopen the binary file as LITTLE_ENDIAN or BIG_ENDIAN.
%
    if ( Format == 1 )
      fid = fopen ( Path, "r", "ieee-le.l64" );
    else
      fid = fopen ( Path, "r", "ieee-be.l64" );
    end
%
%  Find the end of the header again.
%  Using ftell on the old handle doesn"t give the correct position.
%
    BufSize = 8192;
    Buf = [ blanks(10), char(fread(fid,BufSize,"uchar")") ];
    i = [];
    tmp = -11;

    while ( isempty(i) )

      i = findstr(Buf,["end_header",13,10]);   % look for end_header + CR/LF
      i = [i,findstr(Buf,["end_header",10])];  % look for end_header + LF

      if ( isempty(i) )
        tmp = tmp + BufSize;
        Buf = [Buf(BufSize+1:BufSize+10),char(fread(fid,BufSize,"uchar")")];
      end

    end
%
%  seek to just after the line feed
%
    fseek ( fid, i + tmp + 11 + (Buf(i + 10) == 13), -1 );

  end
%
%  Read element data.
%
%  PLY and MATLAB data types (for fread)
%
  PlyTypeNames = {"char","uchar","short","ushort","int","uint","float","double", ...
    "char8","uchar8","short16","ushort16","int32","uint32","float32","double64"};

  MatlabTypeNames = {"schar","uchar","int16","uint16","int32","uint32","single","double"};

  SizeOf = [1,1,2,2,4,4,4,8];

  for i = 1 : NumElements
%
%  get current element property information
%
    eval(["CurPropertyNames=PropertyNames.",ElementNames{i},";"]);
    eval(["CurPropertyTypes=PropertyTypes.",ElementNames{i},";"]);
    NumProperties = size(CurPropertyNames,2);

%   fprintf("Reading %s...
",ElementNames{i});
%
%  Read ASCII data.
%
    if ( ~Format )

      for j = 1 : NumProperties

        Token = getfield(CurPropertyTypes,CurPropertyNames{j});

        if ( strcmpi(Token{1},"list") )
          Type(j) = 1;
        else
          Type(j) = 0;
        end
%
%  Glenn Ramsey 20120827 
%  Initialise Type2{} to prevent uninitialised value error.
%
        Type2{j} = "";

      end
%
%  Parse the buffer.
%
      if ( ~any(Type) )

         % no list types

        Data = reshape ( ...
          Buf(BufOff:BufOff+ElementCount(i)*NumProperties-1), ...
          NumProperties, ElementCount(i) )";

        BufOff = BufOff + ElementCount(i) * NumProperties;

      else

        ListData = cell(NumProperties,1);

        for k = 1 : NumProperties
          ListData{k} = cell(ElementCount(i),1);
        end
%
% list type
%
        for j = 1 : ElementCount(i)
          for k = 1 : NumProperties

            if ( ~Type(k) )
              Data(j,k) = Buf(BufOff);
              BufOff = BufOff + 1;
            else
              tmp = Buf(BufOff);
              ListData{k}{j} = Buf(BufOff+(1:tmp))";
              BufOff = BufOff + tmp + 1;
            end

          end
        end

      end
%
%  Read binary data.
%
    else
% translate PLY data type names to MATLAB data type names
      ListFlag = 0;  % = 1 if there is a list type
      SameFlag = 1;     % = 1 if all types are the same

      for j = 1 : NumProperties

        Token = getfield(CurPropertyTypes,CurPropertyNames{j});
%
%  Non-list type.
%
        if ( ~strcmp(Token{1},"list" ) )

          tmp = rem(strmatch(Token{1},PlyTypeNames,"exact")-1,8)+1;

          if ( ~isempty(tmp) )

            TypeSize(j) = SizeOf(tmp);
            Type{j} = MatlabTypeNames{tmp};
            TypeSize2(j) = 0;
            Type2{j} = "";

            SameFlag = SameFlag & strcmp(Type{1},Type{j});

          else

            fclose(fid);
            error(["Unknown property data type, """,Token{1},""", in ", ...
              ElementNames{i},".",CurPropertyNames{j},"."]);

          end

        else           % list type

          if ( length(Token) == 3 )

            ListFlag = 1;
            SameFlag = 0;
            tmp = rem(strmatch(Token{2},PlyTypeNames,"exact")-1,8)+1;
            tmp2 = rem(strmatch(Token{3},PlyTypeNames,"exact")-1,8)+1;

            if ( ~isempty(tmp) & ~isempty(tmp2) )
              TypeSize(j) = SizeOf(tmp);
              Type{j} = MatlabTypeNames{tmp};
              TypeSize2(j) = SizeOf(tmp2);
              Type2{j} = MatlabTypeNames{tmp2};
            else
              fclose(fid);
              error(["Unknown property data type, ""list ",Token{2}," ",Token{3},""", in ", ...
                ElementNames{i},".",CurPropertyNames{j},"."]);
            end

          else

            fclose(fid);
            error(["Invalid list syntax in ",ElementNames{i},".",CurPropertyNames{j},"."]);

          end

        end

      end

% read file

      if ( ~ListFlag )
%
%  No list types, all the same type (fast)
%
        if ( SameFlag )

          Data = fread(fid,[NumProperties,ElementCount(i)],Type{1})";
%
%  No list types, mixed type.
%
        else

          Data = zeros(ElementCount(i),NumProperties);

          for j = 1 : ElementCount(i)
            for k = 1 : NumProperties
              Data(j,k) = fread(fid,1,Type{k});
            end
          end

        end

      else

        ListData = cell(NumProperties,1);

        for k = 1 : NumProperties
          ListData{k} = cell(ElementCount(i),1);
        end

        if ( NumProperties == 1 )

          BufSize = 512;
          SkipNum = 4;
          j = 0;
%
%  List type, one property (fast if lists are usually the same length)
%
          while ( j < ElementCount(i) )

            BufSize = min(ElementCount(i)-j,BufSize);
            Position = ftell(fid);
%
%  Read in BufSize count values, assuming all counts = SkipNum
%
            [Buf,BufSize] = fread(fid,BufSize,Type{1},SkipNum*TypeSize2(1));
            Miss = find(Buf ~= SkipNum);     % find first count that is not SkipNum
            fseek(fid,Position + TypeSize(1),-1);   % seek back to after first count

            if ( isempty(Miss) )
% all counts are SkipNum
              Buf = fread(fid,[SkipNum,BufSize],[int2str(SkipNum),"*",Type2{1}],TypeSize(1))";
              fseek(fid,-TypeSize(1),0);     % undo last skip

              for k = 1:BufSize
                ListData{1}{j+k} = Buf(k,:);
              end

              j = j + BufSize;
              BufSize = floor(1.5*BufSize);

            else
%
%  Some counts are SkipNum.
%
              if ( 1 < Miss(1) )

                Buf2 = fread(fid,[SkipNum,Miss(1)-1],[int2str(SkipNum),"*",Type2{1}],TypeSize(1))";

                for k = 1:Miss(1)-1
                  ListData{1}{j+k} = Buf2(k,:);
                end

                j = j + k;

              end
%
%  Read in the list with the missed count.
%
              SkipNum = Buf(Miss(1));
              j = j + 1;
              ListData{1}{j} = fread(fid,[1,SkipNum],Type2{1});
              BufSize = ceil(0.6*BufSize);

            end
          end

        else
%
%  List type(s), multiple properties (slow)
%
          Data = zeros(ElementCount(i),NumProperties);

          for j = 1:ElementCount(i)
            for k = 1:NumProperties

              if ( isempty(Type2{k}) )
                Data(j,k) = fread(fid,1,Type{k});
              else
                tmp = fread(fid,1,Type{k});
                ListData{k}{j} = fread(fid,[1,tmp],Type2{k});
              end

            end
          end
        end
      end
    end
%
%  Put data into Elements structure
%
    for k = 1 : NumProperties

      if ( ( ~Format & ~Type(k) ) || (Format & isempty(Type2{k})) )
        eval(["Elements.",ElementNames{i},".",CurPropertyNames{k},"=Data(:,k);"]);
      else
        eval(["Elements.",ElementNames{i},".",CurPropertyNames{k},"=ListData{k};"]);
      end

    end

  end

  clear Data
  clear ListData;

  fclose ( fid );
%
%  Output the data as a triangular mesh pair.
%
  if ( ( nargin > 1 & strcmpi(Str,"Tri") ) || nargout > 2 )
%
%  Find vertex element field
%
    Name = {"vertex","Vertex","point","Point","pts","Pts"};
    Names = [];

    for i = 1 : length(Name)

      if ( any ( strcmp ( ElementNames, Name{i} ) ) )
        Names = getfield(PropertyNames,Name{i});
        Name = Name{i};
        break;
      end

    end

    if ( any(strcmp(Names,"x")) & any(strcmp(Names,"y")) & any(strcmp(Names,"z")) )
      eval(["varargout{1}=[Elements.",Name,".x,Elements.",Name,".y,Elements.",Name,".z];"]);
    else
      varargout{1} = zeros(1,3);
    end
    varargout{1} = varargout{1}";

    varargout{2} = Elements;
    varargout{3} = Comments;
    Elements = [];
%
% Find face element field
%
    Name = {"face","Face","poly","Poly","tri","Tri"};
    Names = [];

    for i = 1 : length(Name)
      if ( any(strcmp(ElementNames,Name{i})) )
        Names = getfield(PropertyNames,Name{i});
        Name = Name{i};
        break;
      end
    end

    if ( ~isempty(Names) )
      % find vertex indices property subfield
      PropertyName = {"vertex_indices","vertex_indexes","vertex_index","indices","indexes"};

      for i = 1 : length(PropertyName)
        if ( any(strcmp(Names,PropertyName{i})) )
          PropertyName = PropertyName{i};
          break;
        end
      end
%
%  Convert face index list to triangular connectivity.
%
      if ( ~iscell(PropertyName) )

        eval(["FaceIndices=varargout{2}.",Name,".",PropertyName,";"]);
        N = length(FaceIndices);
        Elements = zeros(3,N*2);
        Extra = 0;

        for k = 1 : N

          Elements(1:3,k) = FaceIndices{k}(1:3)";
%
%  The original code had an error in the following loop.
%
          for j = 4 : length(FaceIndices{k})
            Extra = Extra + 1;
            Elements(1,N + Extra) = FaceIndices{k}(1);
            Elements(2,N + Extra) = FaceIndices{k}(j-1);
            Elements(3,N + Extra) = FaceIndices{k}(j);
          end

        end
%
%  Add 1 to each vertex value; PLY vertices are zero based.
%
        Elements = Elements(:,1:N+Extra) + 1;

      end
    end

  else

    varargout{1} = Comments;

  end

  return
end

**使用MATLAB对PLY文件进行写操作**
实现将矩阵的内容转换到封装的结构体中

function [Data1,Data2]=change(A,B)
Data1.vertex.x = A(:,1);
Data1.vertex.y = A(:,2);
Data1.vertex.z = A(:,3);
Data2.vertex.x = B(:,1);
Data2.vertex.y = B(:,2);
Data2.vertex.z = B(:,3);

function ply_write ( Elements, Path, Format, Str )

%*****************************************************************************80
%
%% PLY_WRITE writes 3D data as a PLY file.
%
%  Discussion:
%
%    PLY_WRITE(DATA,FILENAME) writes the structure DATA as a binary
%    PLY file.  Every field of DATA is interpreted as an element
%    and every subfield as an element property.  Each subfield of
%    property data must either be an array or a cell array of
%    arrays.  All property data in an element must have the same
%    length.
%
%    A common PLY data structure has the following fields:
%      DATA.vertex.x = x coordinates, [Nx1] real array
%      DATA.vertex.y = y coordinates, [Nx1] real array
%      DATA.vertex.z = z coordinates, [Nx1] real array
%
%      DATA.face.vertex_indices = vertex index lists,
%         an {Mx1} cell array where each cell holds a one-
%         dimesional array (of any length) of vertex indices.
%
%    Some other common data fields:
%
%      DATA.vertex.nx = x coordinate of normal, [Nx1] real array
%      DATA.vertex.ny = y coordinate of normal, [Nx1] real array
%      DATA.vertex.nz = z coordinate of normal, [Nx1] real array
%
%      DATA.edge.vertex1 = index to a vertex, [Px1] integer array
%      DATA.edge.vertex2 = second vertex index, [Px1] integer array
%
%    Many other fields and properties can be added.  The PLY format
%    is not limited to the naming in the examples above -- they are
%    only the conventional naming.
%
%    PLY_WRITE(DATA,FILENAME,FORMAT) write the PLY with a specified
%    data format, where FORMAT is
%      "ascii"                  ASCII text data
%      "binary_little_endian"   binary data, little endian
%      "binary_big_endian"      binary data, big endian (default)
%
%    PLY_WRITE(DATA,FILENAME,FORMAT,"double") or
%
%    PLY_WRITE(DATA,FILENAME,"double") write floating-point data as
%    double precision rather than in the default single precision.
%
%  Example:
%
%    % make a cube
%    clear Data;
%    Data.vertex.x = [0;0;0;0;1;1;1;1];
%    Data.vertex.y = [0;0;1;1;0;0;1;1];
%    Data.vertex.z = [0;1;1;0;0;1;1;0];
%    Data.face.vertex_indices = {[0,1,2,3],[7,6,5,4], ...
%         [0,4,5,1],[1,5,6,2],[2,6,7,3],[3,7,4,0]};
%    ply_write(Data,"cube.ply","ascii");
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    01 July 2016    Seth Billings   bug fix: "ushort" / "uint16" data type was
%                                    not included in the min/max value arrays; now
%                                    excluding "ushort" from the integer data types 
%                                    (rather than fixing the min/max arrays), since
%                                    some programs such as MeshLab do not handle the 
%                                    "ushort" data type properly anyway
%
%    01 July 2016    Seth Billings   bug fixes for max data value of "short" / "int16" 
%                                    data type and for selection of output data type
%    01 March 2007
%
%  Author:
%
%    Pascal Getreuer 2004
%    Seth Billings 2016
%
  if ( nargin < 4 )

   Str = "";

    if ( nargin < 3 )
      Format = "binary_big_endian";
    elseif ( strcmpi ( Format, "double" ) )
      Str = "double";
      Format = "binary_big_endian";
    end

  end

  [ fid, Msg ] = fopen ( Path, "wt" );

  if ( fid == -1 )
    error(Msg);
  end
%
%  Bug Fix: Seth Billings
%  - correcting max integer value for "short" / "int16" from 2^16-1 to 2^15-1
%  - excluding "ushort" from integer types since MeshLab does not
%    read "ushort" data type properly.
%
  PlyTypeNames = {"char","uchar","short","int","uint","float","double", ...
    "char8","uchar8","short16","int32","uint32","float32","double64"};
  FWriteTypeNames = {"schar","uchar","int16","int32","uint32","single","double"};
  MatlabTypeNames = {"int8","uint8","int16","int32","uint32","single","double"};
  PrintfTypeChar = {"%d","%u","%d","%d","%u","%-.6f","%-.14e"};
  IntegerDataMin =  [-128,   0, -2^15,  -2^31,   0];
  IntegerDataMax =  [ 127, 255,  2^15-1, 2^31-1, 2^32-1];
  numTypes = length(MatlabTypeNames);

  %PlyTypeNames = {"char","uchar","short","ushort","int","uint","float","double", ...
  %  "char8","uchar8","short16","ushort16","int32","uint32","float32","double64"};
  %FWriteTypeNames = {"schar","uchar","int16","uint16","int32","uint32","single","double"};
  %MatlabTypeNames = {"int8","uint8","int16","uint16","int32","uint32","single","double"};
  %PrintfTypeChar = {"%d","%u","%d","%u","%d","%u","%-.6f","%-.14e"};  
  %IntegerDataMin = [-128, 0, -2^15, -2^31, 0];
  %IntegerDataMax = [127, 255, 2^16-1, 2^31-1, 2^32-1];
%
%  Write the PLY header.
%

  fprintf(fid,"ply
format %s 1.0
comment created by MATLAB ply_write
",Format);
  ElementNames = fieldnames(Elements);
  NumElements = length(ElementNames);
  Data = cell(NumElements,1);

  for i = 1 : NumElements

    eval(["tmp=isa(Elements.",ElementNames{i},",""struct"");"]);

    if ( tmp )
      eval(["PropertyNames{i}=fieldnames(Elements.",ElementNames{i},");"]);
    else
      PropertyNames{i} = [];
    end

    if ( ~isempty(PropertyNames{i}) )
      eval(["Data{i}{1}=Elements.",ElementNames{i},".",PropertyNames{i}{1},";"]);
      ElementCount(i) = prod(size(Data{i}{1}));
      Type{i} = zeros(length(PropertyNames{i}),1);
    else
      ElementCount(i) = 0;
    end

    fprintf(fid,"element %s %u
",ElementNames{i},ElementCount(i));

    for j = 1 : length(PropertyNames{i})

      eval(["Data{i}{j}=Elements.",ElementNames{i},".",PropertyNames{i}{j},";"]);

      if ( ElementCount(i) ~= prod(size(Data{i}{j})) )
        fclose(fid);
        error("All property data in an element must have the same length.");
      end

      if ( iscell(Data{i}{j}) )
        Type{i}(j) = numTypes + 1;  % Seth Billings
        %Type{i}(j) = 9;
        Data{i}{j} = Data{i}{j}{1};
      end

      for k = 1 : length(MatlabTypeNames)
        if ( isa(Data{i}{j},MatlabTypeNames{k}) )
          Type{i}(j) = Type{i}(j) + k;
          break;
        end
      end

      if ( ~rem(Type{i}(j),numTypes+1) ) % Seth Billings
      %if ( ~rem(Type{i}(j),9) )
        fclose(fid);
        error("Unsupported data structure.");
      end
%
%  Try to convert float data to integer data
%
%  Array data.
%
      if ( Type{i}(j) <= numTypes)  % Seth Billings
      %if ( Type{i}(j) <= 8 )
        if any(strcmp({"single","double"},MatlabTypeNames{Type{i}(j)}))
          if ~any(floor(Data{i}{j}) ~= Data{i}{j})  % data is integer
            MinValue = min(min(Data{i}{j}));
            MaxValue = max(max(Data{i}{j}));

            % choose smallest possible integer data format            
            % Bug Fix: Seth Billings
            tmp = min(find(MinValue >= IntegerDataMin & MaxValue <= IntegerDataMax));
            %tmp = max(min(find(MinValue >= IntegerDataMin)),min(find(MaxValue <= IntegerDataMax)));

            if ~isempty(tmp)
              Type{i}(j) = tmp;
            end
          end
        end
      else        % cell array data
        eval(["Data{i}{j}=Elements.",ElementNames{i},".",PropertyNames{i}{j},";"]);
        tmp = 1;

        for k = 1:prod(size(Data{i}{j}))
          tmp = tmp & all(floor(Data{i}{j}{k}) == Data{i}{j}{k});
        end

        if tmp  % data is integer
          MinValue = inf;
          MaxValue = -inf;

          for k = 1:prod(size(Data{i}{j}))
            MinValue = min(MinValue,min(Data{i}{j}{k}));
            MaxValue = max(MaxValue,max(Data{i}{j}{k}));
          end
%
%  choose smallest possible integer data format
%  Bug Fix: Seth Billings
%
          tmp = min(find(MinValue >= IntegerDataMin & MaxValue <= IntegerDataMax));
          %tmp = max(min(find(MinValue >= IntegerDataMin)),min(find(MaxValue <= IntegerDataMax)));

          if ( ~ isempty ( tmp ) )
            Type{i}(j) = tmp + numTypes + 1;
            %Type{i}(j) = tmp + 9;
          end

        end
      end

      % convert double to single if specified
      if rem(Type{i}(j),numTypes+1) == numTypes & ~strcmpi(Str,"double") % Seth Billings
      %if rem(Type{i}(j),9) == 8 & ~strcmpi(Str,"double")
        Type{i}(j) = Type{i}(j) - 1;
      end

      if Type{i}(j) <= numTypes  % Seth Billings
      %if Type{i}(j) <= 8
        fprintf(fid,"property %s %s
",PlyTypeNames{Type{i}(j)},PropertyNames{i}{j});
      else
        fprintf(fid,"property list uchar %s %s
",PlyTypeNames{Type{i}(j)-(numTypes+1)},PropertyNames{i}{j}); % Seth Billings
        %fprintf(fid,"property list uchar %s %s
",PlyTypeNames{Type{i}(j)-9},PropertyNames{i}{j});
      end
    end
  end

  fprintf(fid,"end_header
");

  switch Format
    case "ascii"
      Format = 0;
    case "binary_little_endian"
      fclose(fid);
      fid = fopen(Path,"a","ieee-le");
      Format = 1;
    case "binary_big_endian"
      fclose(fid);
      fid = fopen(Path,"a","ieee-be");
      Format = 2;
  end

  for i = 1 : NumElements
    if ~isempty(PropertyNames{i})
      if ~Format          % write ASCII data
        for k = 1:ElementCount(i)
          for j = 1:length(PropertyNames{i})
            if Type{i}(j) <= numTypes  % Seth Billings
            %if Type{i}(j) <= 8
              fprintf(fid,[PrintfTypeChar{Type{i}(j)}," "],Data{i}{j}(k));
            else
              fprintf(fid,"%u%s ",length(Data{i}{j}{k}),sprintf([" ",PrintfTypeChar{Type{i}(j)-(numTypes+1)}],Data{i}{j}{k})); % Seth Billings
              %fprintf(fid,"%u%s ",length(Data{i}{j}{k}),sprintf([" ",PrintfTypeChar{Type{i}(j)-9}],Data{i}{j}{k}));
            end
          end

          fprintf(fid,"
");
        end
      else            % write binary data
        if all(Type{i} <= numTypes) & all(Type{i} == Type{i}(1))  % Seth Billings
        %if all(Type{i} <= 8) & all(Type{i} == Type{i}(1))
          % property data without list types (fast)
          tmp = zeros(length(PropertyNames{i}),ElementCount(i));

          for j = 1:length(PropertyNames{i})
            tmp(j,:) = Data{i}{j}(:)";
          end

          fwrite(fid,tmp,FWriteTypeNames{Type{i}(j)});
        elseif all(Type{i} > numTypes) % Seth Billings
        %elseif all(Type{i} > 8)
        % only list types
          Type{i} = Type{i} - (numTypes+1); % Seth Billings
          %Type{i} = Type{i} - 9;

          if length(PropertyNames{i}) == 1
           % only one list property
            tmp = FWriteTypeNames{Type{i}(1)};

            for k = 1:ElementCount(i)
              fwrite(fid,length(Data{i}{1}{k}),"uchar");
              fwrite(fid,Data{i}{1}{k},tmp);
            end
          else
           % multiple list properties
            for k = 1:ElementCount(i)
              for j = 1:length(PropertyNames{i})
                fwrite(fid,length(Data{i}{j}{k}),"uchar");
                fwrite(fid,Data{i}{j}{k},FWriteTypeNames{Type{i}(j)});
              end
            end
          end
        else
        % mixed type
          for k = 1:ElementCount(i)
            for j = 1:length(PropertyNames{i})
              if Type{i}(j) <= numTypes % Seth Billings
              %if Type{i}(j) <= 8
                fwrite(fid,Data{i}{j}(k),FWriteTypeNames{Type{i}(j)});
              else
                fwrite(fid,length(Data{i}{j}{k}),"uchar");
                fwrite(fid,Data{i}{j}{k},FWriteTypeNames{Type{i}(j)-(numTypes+1)}); % Seth Billings
                %fwrite(fid,Data{i}{j}{k},FWriteTypeNames{Type{i}(j)-9});
              end
            end
          end
        end
      end
    end
  end

  fclose ( fid );

  return
end


声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。