mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-28 02:58:18 +08:00
Add support for constructive solid geometry datatypes ply and vtk.
This commit is contained in:
@@ -418,6 +418,11 @@
|
||||
<datatype extension="oxlist" type="galaxy.datatypes.binary:OxliStopTags" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<datatype extension="oxliss" type="galaxy.datatypes.binary:OxliSubset" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<datatype extension="oxligl" type="galaxy.datatypes.binary:OxliGraphLabels" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<!-- Constructive solid geometry datatypes -->
|
||||
<datatype extension="plyascii" type="galaxy.datatypes.constructive_solid_geometry:PlyAscii" display_in_upload="true" />
|
||||
<datatype extension="plybinary" type="galaxy.datatypes.constructive_solid_geometry:PlyBinary" display_in_upload="true" />
|
||||
<datatype extension="vtkascii" type="galaxy.datatypes.constructive_solid_geometry:VtkAscii" display_in_upload="true" />
|
||||
<datatype extension="vtkbinary" type="galaxy.datatypes.constructive_solid_geometry:VtkBinary" display_in_upload="true" />
|
||||
</registration>
|
||||
<sniffers>
|
||||
<!--
|
||||
@@ -427,6 +432,10 @@
|
||||
defined format first, followed by next-most rigidly defined,
|
||||
and so on.
|
||||
-->
|
||||
<sniffer type="galaxy.datatypes.constructive_solid_geometry:PlyAscii"/>
|
||||
<sniffer type="galaxy.datatypes.constructive_solid_geometry:PlyBinary"/>
|
||||
<sniffer type="galaxy.datatypes.constructive_solid_geometry:VtkAscii"/>
|
||||
<sniffer type="galaxy.datatypes.constructive_solid_geometry:VtkBinary"/>
|
||||
<sniffer type="galaxy.datatypes.tabular:Vcf"/>
|
||||
<sniffer type="galaxy.datatypes.binary:TwoBit"/>
|
||||
<sniffer type="galaxy.datatypes.binary:GeminiSQLite"/>
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
"""
|
||||
Constructive Solid Geometry file formats.
|
||||
"""
|
||||
from galaxy.datatypes import data
|
||||
from galaxy.datatypes.binary import Binary
|
||||
from galaxy.datatypes.data import get_file_peek
|
||||
from galaxy.datatypes.data import nice_size
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
from galaxy import util
|
||||
|
||||
MAX_HEADER_LINES = 500
|
||||
MAX_LINE_LEN = 2000
|
||||
COLOR_OPTS = ['COLOR_SCALARS', 'red', 'green', 'blue']
|
||||
|
||||
|
||||
class Ply(object):
|
||||
"""
|
||||
The PLY format describes an object as a collection of vertices,
|
||||
faces and other elements, along with properties such as color and
|
||||
normal direction that can be attached to these elements. A PLY
|
||||
file contains the description of exactly one object.
|
||||
"""
|
||||
# Add metadata elements.
|
||||
MetadataElement(name="file_format", default=None, desc="File format",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="vertex", default=None, desc="Vertex",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="face", default=None, desc="Face",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="other_elements", default=[], desc="Other elements",
|
||||
readonly=True, optional=True, visible=True, no_value=[])
|
||||
|
||||
def sniff(self, filename, subtype):
|
||||
"""
|
||||
The structure of a typical PLY file:
|
||||
Header, Vertex List, Face List, (lists of other elements)
|
||||
"""
|
||||
with open(filename, "r") as fh:
|
||||
if not self._is_ply_header(fh, subtype):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_ply_header(self, fh, subtype):
|
||||
"""
|
||||
The header is a series of carriage-return terminated lines of
|
||||
text that describe the remainder of the file.
|
||||
"""
|
||||
valid_header_items = ['comment', 'obj_info', 'element', 'property']
|
||||
# Line 1: ply
|
||||
line = get_next_line(fh)
|
||||
if line != 'ply':
|
||||
return False
|
||||
# Line 2: format ascii 1.0
|
||||
line = get_next_line(fh)
|
||||
if line.find(subtype) < 0:
|
||||
return False
|
||||
stop_index = 0
|
||||
while True:
|
||||
line = get_next_line(fh)
|
||||
stop_index += 1
|
||||
if line == 'end_header':
|
||||
return True
|
||||
items = line.split()
|
||||
if items[0] not in valid_header_items:
|
||||
return False
|
||||
if stop_index > MAX_HEADER_LINES:
|
||||
# If this is a PLY file, there must be an unusually
|
||||
# large number of comments.
|
||||
break
|
||||
return False
|
||||
|
||||
def set_meta(self, dataset, **kwd):
|
||||
if dataset.has_data():
|
||||
with open(dataset.file_name) as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith('format'):
|
||||
items = line.split()
|
||||
dataset.metadata.file_format = items[1]
|
||||
elif line == 'end_header':
|
||||
# Metadata is complete.
|
||||
break
|
||||
elif line.startswith('element'):
|
||||
items = line.split()
|
||||
if items[1] == 'face':
|
||||
dataset.metadata.face = int(items[2])
|
||||
elif items[1] == 'vertex':
|
||||
dataset.metadata.vertex = int(items[2])
|
||||
else:
|
||||
element_tuple = (items[1], int(items[2]))
|
||||
dataset.metadata.other_elements.append(element_tuple)
|
||||
|
||||
def set_peek(self, dataset, is_multi_byte=False):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = get_file_peek(dataset.file_name, is_multi_byte=is_multi_byte)
|
||||
dataset.blurb = "Faces: %s, Vertices: %s" % (str(dataset.metadata.face), str(dataset.metadata.vertex))
|
||||
else:
|
||||
dataset.peek = 'File does not exist'
|
||||
dataset.blurb = 'File purged from disc'
|
||||
|
||||
def display_peek(self, dataset):
|
||||
try:
|
||||
return dataset.peek
|
||||
except:
|
||||
return "Ply file (%s)" % (nice_size(dataset.get_size()))
|
||||
|
||||
|
||||
class PlyAscii(Ply, data.Text):
|
||||
|
||||
file_ext = "plyascii"
|
||||
|
||||
def __init__(self, **kwd):
|
||||
data.Text.__init__(self, **kwd)
|
||||
|
||||
def sniff(self, filename):
|
||||
return super(PlyAscii, self).sniff(filename, subtype='ascii')
|
||||
|
||||
|
||||
class PlyBinary(Ply, Binary):
|
||||
|
||||
file_ext = "plybinary"
|
||||
|
||||
def __init__(self, **kwd):
|
||||
Binary.__init__(self, **kwd)
|
||||
|
||||
def sniff(self, filename):
|
||||
return super(PlyBinary, self).sniff(filename, subtype='binary')
|
||||
|
||||
Binary.register_sniffable_binary_format("plybinary", "plybinary", PlyBinary)
|
||||
|
||||
|
||||
class Vtk(object):
|
||||
"""
|
||||
The Visualization Toolkit provides a number of source and writer objects to
|
||||
read and write popular data file formats. The Visualization Toolkit also
|
||||
provides some of its own file formats.
|
||||
|
||||
There are two different styles of file formats available in VTK. The simplest
|
||||
are the legacy, serial formats that are easy to read and write either by hand
|
||||
or programmatically. However, these formats are less flexible than the XML
|
||||
based file formats which support random access, parallel I/O, and portable
|
||||
data compression and are preferred to the serial VTK file formats whenever
|
||||
possible.
|
||||
|
||||
All keyword phrases are written in ASCII form whether the file is binary or
|
||||
ASCII. The binary section of the file (if in binary form) is the data proper;
|
||||
i.e., the numbers that define points coordinates, scalars, cell indices, and
|
||||
so forth.
|
||||
|
||||
Binary data must be placed into the file immediately after the newline (\n)
|
||||
character from the previous ASCII keyword and parameter sequence.
|
||||
|
||||
TODO: only legacy formats are currently supported and support for XML formats
|
||||
should be added.
|
||||
"""
|
||||
# Add metadata elements.
|
||||
MetadataElement(name="vtk_version", default=None, desc="Vtk version",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="file_format", default=None, desc="File format",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="dataset_type", default=None, desc="Dataset type",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
|
||||
# STRUCTURED_GRID data_type.
|
||||
MetadataElement(name="dimensions", default=[], desc="Dimensions",
|
||||
readonly=True, optional=True, visible=True, no_value=[])
|
||||
MetadataElement(name="origin", default=[], desc="Origin",
|
||||
readonly=True, optional=True, visible=True, no_value=[])
|
||||
MetadataElement(name="spacing", default=[], desc="Spacing",
|
||||
readonly=True, optional=True, visible=True, no_value=[])
|
||||
|
||||
# POLYDATA data_type (Points element is also a component of UNSTRUCTURED_GRID..
|
||||
MetadataElement(name="points", default=None, desc="Points",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="vertices", default=None, desc="Vertices",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="lines", default=None, desc="Lines",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="polygons", default=None, desc="Polygons",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
MetadataElement(name="triangle_strips", default=None, desc="Triangle strips",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
|
||||
# UNSTRUCTURED_GRID data_type.
|
||||
MetadataElement(name="cells", default=None, desc="Cells",
|
||||
readonly=True, optional=True, visible=True, no_value=None)
|
||||
|
||||
# Additional elements not categorized by data_type.
|
||||
MetadataElement(name="field_names", default=[], desc="Field names",
|
||||
readonly=True, optional=True, visible=True, no_value=[])
|
||||
# The keys in the field_components map to the list of field_names in the above element
|
||||
# which ensures order for select list options that are built from it.
|
||||
MetadataElement(name="field_components", default={}, desc="Field names and components",
|
||||
readonly=True, optional=True, visible=True, no_value={})
|
||||
MetadataElement(name="has_color", default=False, desc="Surface field has color",
|
||||
readonly=True, optional=True, visible=True, no_value=False)
|
||||
|
||||
def sniff(self, filename, subtype):
|
||||
"""
|
||||
VTK files can be either ASCII or binary, with two different
|
||||
styles of file formats: legacy or XML. We'll assume if the
|
||||
file contains a valid VTK header, then it is a valid VTK file.
|
||||
"""
|
||||
with open(filename, "r") as fh:
|
||||
if self._is_vtk_header(fh, subtype):
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
|
||||
def _is_vtk_header(self, fh, subtype):
|
||||
"""
|
||||
The Header section consists of at least 4, but possibly
|
||||
5 lines. This is tricky because sometimes the 4th line
|
||||
is blank (in which case the 5th line consists of the
|
||||
data_kind) or the 4th line consists of the data_kind (in
|
||||
which case the 5th line is blank).
|
||||
"""
|
||||
|
||||
data_kinds = ['STRUCTURED_GRID', 'POLYDATA', 'UNSTRUCTURED_GRID']
|
||||
|
||||
def check_data_kind(line):
|
||||
for data_kind in data_kinds:
|
||||
if line.find(data_kind) >= 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Line 1: vtk DataFile Version 3.0
|
||||
line = get_next_line(fh)
|
||||
if line.find('vtk') < 0:
|
||||
return False
|
||||
# Line 2: can be anything - skip it
|
||||
line = get_next_line(fh)
|
||||
# Line 3: ASCII or BINARY
|
||||
line = get_next_line(fh)
|
||||
if line.find(subtype) < 0:
|
||||
return False
|
||||
# Line 4:
|
||||
line = get_next_line(fh)
|
||||
if line:
|
||||
return check_data_kind(line)
|
||||
# line 5:
|
||||
line = get_next_line(fh)
|
||||
if line:
|
||||
return check_data_kind(line)
|
||||
return False
|
||||
|
||||
def set_meta(self, dataset, **kwd):
|
||||
if dataset.has_data():
|
||||
dataset.metadata.field_names = []
|
||||
dataset.metadata.field_components = {}
|
||||
dataset_type = None
|
||||
field_components = {}
|
||||
dataset_structure_complete = False
|
||||
with open(dataset.file_name) as fh:
|
||||
for i, line in enumerate(fh):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if i < 3:
|
||||
dataset = self.set_initial_metadata(i, line, dataset)
|
||||
elif dataset.metadata.file_format == 'ASCII' or not util.is_binary(line):
|
||||
if dataset_structure_complete:
|
||||
dataset, field_components = self.set_dataset_attributes_metadata(line,
|
||||
dataset,
|
||||
field_components)
|
||||
elif line.startswith('POINT_DATA') or line.startswith('CELL_DATA'):
|
||||
dataset_structure_complete = True
|
||||
dataset, field_components = self.set_dataset_attributes_metadata(line,
|
||||
dataset,
|
||||
field_components)
|
||||
else:
|
||||
dataset, dataset_type = self.set_dataset_structure_metadata(line,
|
||||
dataset,
|
||||
dataset_type)
|
||||
if len(field_components) > 0:
|
||||
dataset.metadata.field_components = field_components
|
||||
|
||||
def set_dataset_attributes_metadata(self, line, dataset, field_components):
|
||||
"""
|
||||
The final part of legacy VTK files describes the dataset attributes.
|
||||
This part begins with the keywords POINT_DATA or CELL_DATA, followed
|
||||
by an integer number specifying the number of points or cells,
|
||||
respectively. Other keyword/ data combinations then define the actual
|
||||
dataset attribute values (i. e., scalars, vectors, tensors, normals,
|
||||
texture coordinates, or field data). Dataset attributes are supported
|
||||
for both points and cells.
|
||||
|
||||
Each type of attribute data has a dataName associated with it. This is
|
||||
a character string (without embedded whitespace) used to identify a
|
||||
particular data. The dataName is used by the VTK readers to extract
|
||||
data. As a result, more than one attribute data of the same type can be
|
||||
included in a file. For example, two different scalar fields defined
|
||||
on the dataset points, pressure and temperature, can be contained in
|
||||
the same file. If the appropriate dataName is not specified in the VTK
|
||||
reader, then the first data of that type is extracted from the file.
|
||||
"""
|
||||
if line.startswith('SCALARS'):
|
||||
# Example: SCALARS surface_field double 2
|
||||
# Scalar definition includes specification of a lookup table. The
|
||||
# definition of a lookup table is optional. If not specified, the
|
||||
# default VTK table will be used, and tableName should be
|
||||
# "default". Also note that the numComp variable is optional. By
|
||||
# default the number of components is equal to one. The parameter
|
||||
# numComp must range between (1,4) inclusive; in versions of VTK
|
||||
# prior to vtk2.3 this parameter was not supported.
|
||||
items = line.split()
|
||||
# Set the field_names and field_components metadata elements.
|
||||
field_name = items[1]
|
||||
dataset.metadata.field_names.append(field_name)
|
||||
try:
|
||||
num_components = int(items[-1])
|
||||
except:
|
||||
num_components = 1
|
||||
field_component_indexes = [str(i) for i in range(num_components)]
|
||||
field_components[field_name] = field_component_indexes
|
||||
else:
|
||||
for opt in COLOR_OPTS:
|
||||
if line.startswith(opt):
|
||||
# TODO: find out if / how color is associated with a specific field name,
|
||||
dataset.metadata.has_color = True
|
||||
return dataset, field_components
|
||||
|
||||
def set_initial_metadata(self, i, line, dataset):
|
||||
if i == 0:
|
||||
# The first part of legacy VTK files is the file version and
|
||||
# identifier. This part contains the single line:
|
||||
# # vtk DataFile Version X.Y
|
||||
dataset.metadata.vtk_version = line.lower().split('version')[1]
|
||||
# The second part of legacy VTK files is the header. The header
|
||||
# consists of a character string terminated by end-of-line
|
||||
# character \n. The header is 256 characters maximum. The header
|
||||
# can be used to describe the data and include any other pertinent
|
||||
# information. We skip the header line...
|
||||
elif i == 2:
|
||||
# The third part of legacy VTK files is the file format. The file
|
||||
# format describes the type of file, either ASCII or binary. On
|
||||
# this line the single word ASCII or BINARY must appear.
|
||||
dataset.metadata.file_format = line
|
||||
return dataset
|
||||
|
||||
def set_dataset_structure_metadata(self, line, dataset, dataset_type):
|
||||
"""
|
||||
The fourth part of legacy VTK files is the dataset structure. The
|
||||
geometry part describes the geometry and topology of the dataset.
|
||||
This part begins with a line containing the keyword DATASET followed
|
||||
by a keyword describing the type of dataset. Then, depending upon
|
||||
the type of dataset, other keyword/ data combinations define the
|
||||
actual data.
|
||||
"""
|
||||
if dataset_type is None and line.startswith('DATASET'):
|
||||
dataset_type = line.split()[1]
|
||||
dataset.metadata.dataset_type = dataset_type
|
||||
if dataset_type == 'STRUCTURED_GRID':
|
||||
# The STRUCTURED_GRID format supports 1D, 2D, and 3D structured
|
||||
# grid datasets. The dimensions nx, ny, nz must be greater
|
||||
# than or equal to 1. The point coordinates are defined by the
|
||||
# data in the POINTS section. This consists of x-y-z data values
|
||||
# for each point.
|
||||
if line.startswith('DIMENSIONS'):
|
||||
# DIMENSIONS 10 5 1
|
||||
dataset.metadata.dimensions = [line.split()[1:]]
|
||||
elif line.startswith('ORIGIN'):
|
||||
# ORIGIN 0 0 0
|
||||
dataset.metadata.origin = [line.split()[1:]]
|
||||
elif line.startswith('SPACING'):
|
||||
# SPACING 1 1 1
|
||||
dataset.metadata.spacing = [line.split()[1:]]
|
||||
elif dataset_type == 'POLYDATA':
|
||||
# The polygonal dataset consists of arbitrary combinations
|
||||
# of surface graphics primitives vertices, lines, polygons
|
||||
# and triangle strips. Polygonal data is defined by the POINTS,
|
||||
# VERTICES, LINES, POLYGONS, or TRIANGLE_STRIPS sections.
|
||||
if line.startswith('POINTS'):
|
||||
# POINTS 18 float
|
||||
dataset.metadata.points = int(line.split()[1])
|
||||
elif line.startswith('VERTICES'):
|
||||
dataset.metadata.vertices = int(line.split()[1])
|
||||
elif line.startswith('LINES'):
|
||||
# LINES 5 17
|
||||
dataset.metadata.lines = int(line.split()[1])
|
||||
elif line.startswith('POLYGONS'):
|
||||
# POLYGONS 6 30
|
||||
dataset.metadata.polygons = int(line.split()[1])
|
||||
elif line.startswith('TRIANGLE_STRIPS'):
|
||||
# TRIANGLE_STRIPS 2212 16158
|
||||
dataset.metadata.triangle_strips = int(line.split()[1])
|
||||
elif dataset_type == 'UNSTRUCTURED_GRID':
|
||||
# The unstructured grid dataset consists of arbitrary combinations
|
||||
# of any possible cell type. Unstructured grids are defined by points,
|
||||
# cells, and cell types.
|
||||
if line.startswith('POINTS'):
|
||||
# POINTS 18 float
|
||||
dataset.metadata.points = int(line.split()[1])
|
||||
if line.startswith('CELLS'):
|
||||
# CELLS 756 3024
|
||||
dataset.metadata.cells = int(line.split()[1])
|
||||
return dataset, dataset_type
|
||||
|
||||
def get_blurb(self, dataset):
|
||||
blurb = ""
|
||||
if dataset.metadata.vtk_version is not None:
|
||||
blurb += 'VTK Version %s' % str(dataset.metadata.vtk_version)
|
||||
if dataset.metadata.dataset_type is not None:
|
||||
if blurb:
|
||||
blurb += ' '
|
||||
blurb += str(dataset.metadata.dataset_type)
|
||||
return blurb or 'VTK data'
|
||||
|
||||
def set_peek(self, dataset, is_multi_byte=False):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = get_file_peek(dataset.file_name, is_multi_byte=is_multi_byte)
|
||||
dataset.blurb = self.get_blurb(dataset)
|
||||
else:
|
||||
dataset.peek = 'File does not exist'
|
||||
dataset.blurb = 'File purged from disc'
|
||||
|
||||
def display_peek(self, dataset):
|
||||
try:
|
||||
return dataset.peek
|
||||
except:
|
||||
return "Vtk file (%s)" % (nice_size(dataset.get_size()))
|
||||
|
||||
|
||||
class VtkAscii(Vtk, data.Text):
|
||||
|
||||
file_ext = "vtkascii"
|
||||
|
||||
def __init__(self, **kwd):
|
||||
data.Text.__init__(self, **kwd)
|
||||
|
||||
def sniff(self, filename):
|
||||
return super(VtkAscii, self).sniff(filename, subtype='ASCII')
|
||||
|
||||
|
||||
class VtkBinary(Vtk, Binary):
|
||||
|
||||
file_ext = "vtkbinary"
|
||||
|
||||
def __init__(self, **kwd):
|
||||
Binary.__init__(self, **kwd)
|
||||
|
||||
def sniff(self, filename):
|
||||
return super(VtkBinary, self).sniff(filename, subtype='BINARY')
|
||||
|
||||
Binary.register_sniffable_binary_format("vtkbinary", "vtkbinary", VtkBinary)
|
||||
|
||||
|
||||
# Utility functions
|
||||
def get_next_line(fh):
|
||||
line = fh.readline(MAX_LINE_LEN)
|
||||
return line.strip()
|
||||
Reference in New Issue
Block a user