-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRFI.py
159 lines (120 loc) · 6.46 KB
/
RFI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'''
Created on Sep 2, 2011
This module calculates relief index (3D surface area/2D area of surface
projected on XY plane) for a provided 3D mesh using the MeshRFI class.
@author: Julia M. Winchester
'''
import matplotlib
matplotlib.use('AGG')
import warnings
import matplotlib.pyplot as plt
from StringIO import StringIO
from numpy import sqrt, square, amin, amax, array, array_equal
from numpy.linalg import det
try:
import Image
except ImportError:
from PIL import Image
class MeshRFI(object):
"""Class for calculating and storing relief index values for polygonal mesh data.
When instanced, this class calculates relief index and associated variables
and stores them. All attributes below are populated on instantiation.
Args:
TopoMesh (TopoMesh object): Triangulated polygon mesh data.
Attributes:
Mesh (TopoMesh object): Triangulated polygon mesh data.
RFI (float): Mesh surface relief index (surfarea/projarea).
surfarea (float): 3D mesh surface area.
projarea (float): 2D mesh surface area projected on XY plane.
linelin (float): Reference line for building pixel/area unit ratio.
bluepixie (float): Number of blue pixels (mesh) on projected area render.
redpixie (float): Number of red pixels (reference line) on projected area render.
pixelratio (float): Pixel/area unit ratio, used for converting number of
blue pixels to area units.
imgbuffer (StringIO object): 2D plot of surface mesh with reference line for
determining projected XY-plane surface area.
"""
def __init__(self, TopoMesh):
self.Mesh = TopoMesh
self.RFI = None
self.surfarea = None
self.projarea = None
self.linelen = None
self.bluepixie = None
self.redpixie = None
self.pixelratio = None
self.imgbuffer = None
self._check_mesh_consistency()
self.calcrfi()
def calcrfi(self):
"""Calls methods for calculating surface and projected areas, then derives relief index value."""
self.surfarea = round(sum(self._triangle_area(face) for face in self.Mesh.triverts),3)
self._get_projection_area()
self.RFI = round(self.surfarea/self.projarea, 3)
def _get_projection_area(self):
"""Creates 2D plot of surface mesh and derives projection area from this plot."""
self._plot_surface()
self._get_2d_area()
def _plot_surface(self): # Returns pixel length of scalebar and image plot as StringIO file-like object
"""Plots 3D polygonal mesh as 2D raster shape on the XY plane with reference line for area units."""
xarray = self.Mesh.vertices[:,0]
yarray = self.Mesh.vertices[:,1]
xaxismin = amin(xarray) - 0.5
xaxismax = amax(xarray) + 0.5
yaxismin = amin(yarray) - 0.5
yaxismax = amax(yarray) + 0.5
self.linelen = amax(yarray) - amin(yarray) + 1.0
if self.linelen == 1.0:
raise ValueError("Polygon mesh has a zero area projected in the XY plane.")
fig = plt.figure()
ax = fig.add_subplot(111)
linesquare = matplotlib.patches.Polygon([[xaxismin,yaxismin],[xaxismin,yaxismax]], ec='r',fc='r')
plt.axis([xaxismin,xaxismax,yaxismin,yaxismax])
ax.add_patch(linesquare)
ax.set_xscale('linear')
ax.set_yscale('linear')
ax.set_aspect(1)
ax.axis('off')
vert = array([face[:,[0,1]] for face in self.Mesh.triverts]) # makes a copy of self.Mesh.triverts including only XY coordinate points for vertices comprising faces
polygons = matplotlib.collections.PolyCollection(vert,facecolor='b',edgecolor='b')
ax.add_collection(polygons)
self.imgbuffer = StringIO()
plt.savefig(self.imgbuffer,format='png')
def _get_2d_area(self): # Receives image plot from StringIO object and returns absolute area covered by mesh as projected on XY plane
"""Derives 2D surface area of polygonal mesh projected on XY plane given a 2D raster plot and area-unit reference line."""
if isinstance(self.imgbuffer, StringIO) is not True:
raise TypeError("Non-StringIO object provided for imgbuffer.")
self.imgbuffer.seek(0) # Rewind image buffer back to beginning to allow Image.open() to identify it
img = Image.open(self.imgbuffer).getdata()
self.redpixie = self._count_pixels(img, (255,0,0,255), (255,127,127,255))
self.bluepixie = len(list(img)) - self._count_pixels(img, (255, 0, 0, 255), (255, 255, 255, 255), (255, 155, 155, 255), (255, 188, 188, 255), (255, 230, 230, 255), (255, 205, 205, 255))
rope = float(self.linelen)
redballoon = float(self.redpixie)
self.pixelratio = redballoon/rope
self.projarea = round(float(self.bluepixie)*(square(rope)/square(redballoon)), 3)
def _count_pixels(self, image, *args): # Returns the number of pixels in a list of RGB+transparency values that match the colors (RGB+transparency) given in colorlist
"""Returns the number of pixels in an image that match colors given as *args.
Args:
image (StringIO object): Image string buffer object from which pixels are counted.
*args: Series of lists or tuples of RGB+transparency value color data. Pixels in
image that match these colors will be counted.
"""
return sum([list(image).count(color) for color in set(args)])
def _triangle_area(self, verts):
"""Returns the area of a triangle defined by vertices.
Args:
verts(ndarray): A set of three XYZ point triplets forming a triangle.
"""
fx = verts[:,0]
fy = verts[:,1]
fz = verts[:,2]
fc = [1,1,1]
a = [fx, fy, fc]
b = [fy, fz, fc]
c = [fz, fx, fc]
return 0.5*sqrt(square(det(a))+square(det(b))+square(det(c)))
def _check_mesh_consistency(self):
"""Checks mesh vertex and face-vertex arrays to ensure identical vertices throughout."""
for i, trivert in enumerate(self.Mesh.triverts):
if (trivert != self.Mesh.vertices[self.Mesh.faces[i]]).any():
raise ValueError("Mesh vertex and face arrays do not contain identical vertices.")