Scripts: capmask.py

File capmask.py, 5.7 KB (added by goddard, 16 years ago)
Line 
1# -----------------------------------------------------------------------------
2# Try to automatically mask cylindrical capillary from x-ray tomogram.
3# Operates on highlighted volume in volume dialog when this script is opened.
4# Creates a new volume data set.
5#
6# Tested with Chimera 1.5, July 19, 2010 daily build.
7#
8# Method: Look at two xy planes at 10% and 90% z values. Sum along y axis
9# to get a 1d funtion. Take first maxima on each end that are above
10# (mean + 2 standard deviations) as ellipse cross-section boundaries.
11# Likewise along x axis. Connect two circles to find cylinder axis and radius.
12#
13def fit_and_mask_cylinder(zpos = (0.10, 0.90), num_sd = 2.0,
14 scale_radius = 0.95, scale_height = 1.05,
15 color = (0,0,.7,1), subdivisions = 100,
16 mask = True):
17
18 from VolumeViewer import active_volume
19 v = active_volume() # Highlighted in dialog.
20 center, axis, radius, height = fit_cylinder(v.data, zpos, num_sd)
21 radius *= scale_radius # Scale cylinder radius.
22 height *= scale_height # Scale cylinder height.
23 s = show_cylinder(center, axis, radius, height, subdivisions, color, v)
24 if mask:
25 from chimera import runCommand
26 runCommand('mask #%d #%d' % (v.id, s.id)) # Mask volume.
27
28# -----------------------------------------------------------------------------
29# Returns center, axis, radius, height in volume data index units.
30#
31def fit_cylinder(data, zpos, num_sd):
32
33 # Get data planes.
34 xsize, ysize, zsize = data.size
35 z1, z2 = [int(round(zp*zsize)) for zp in zpos]
36 m1, m2 = data_z_plane(data,z1), data_z_plane(data,z2)
37
38 # Set values below threshold to 0.
39 m1.put(m1 < m1.mean() + num_sd * m1.std(), 0)
40 m2.put(m2 < m2.mean() + num_sd * m2.std(), 0)
41
42 # Find box bounding cylinder using 2 z-planes.
43 from math import sqrt
44 sdx = num_sd / sqrt(ysize)
45 sdy = num_sd / sqrt(xsize)
46 cx1, rx1 = local_maxima_bounds(m1.sum(axis=0), sdx)
47 cy1, ry1 = local_maxima_bounds(m1.sum(axis=1), sdy)
48 cx2, rx2 = local_maxima_bounds(m2.sum(axis=0), sdx)
49 cy2, ry2 = local_maxima_bounds(m2.sum(axis=1), sdy)
50 if cx1 is None or cy1 is None or cx2 is None or cy2 is None:
51 print 'Projection with no local maxima above threshold'
52 return
53
54 # Correct radius for axis tilted away from z.
55 dx,dy,dz = (cx2-cx1, cy2-cy1, z2-z1)
56 from math import sqrt
57 cosx = dz/sqrt(dx*dx+dz*dz)
58 cosy = dz/sqrt(dy*dy+dz*dz)
59 # Four radius values from x and y widths on z1 and z2 planes.
60 radii = (rx1*cosx, rx2*cosx, ry1*cosy, ry2*cosy)
61 radius = sum(radii)/len(radii) # Use average radius.
62
63 # Calculate cylinder center, axis and height.
64 center = (0.5*(cx1+cx2), 0.5*(cy1+cy2), 0.5*(z1+z2))
65 l = sqrt(dx*dx+dy*dy+dz*dz)
66 axis = (dx/l,dy/l,dz/l)
67 height = l * float(zsize)/(z2-z1)
68
69 return center, axis, radius, height
70
71# -----------------------------------------------------------------------------
72#
73def local_maxima_bounds(data, num_sd):
74
75 cutoff = data.mean() + num_sd * data.std()
76 local_max = ((data[1:-1] >= cutoff) &
77 (data[2:] <= data[1:-1]) &
78 (data[:-2] <= data[1:-1]))
79 mi = local_max.nonzero()[0] # indices of local maxima above mean
80 if len(mi) == 0:
81 return None, None
82
83 i0 = mi[0]+1
84 i1 = mi[-1]+1
85 c = 0.5 * (i0 + i1)
86 r = 0.5 * (i1 - i0)
87 return c, r
88
89# -----------------------------------------------------------------------------
90#
91def data_z_plane(data, z):
92
93 xsize, ysize, zsize = data.size
94 m3d = data.matrix((0,0,z), (xsize,ysize,1))
95 m = m3d[0,:,:].copy()
96 return m
97
98# -----------------------------------------------------------------------------
99#
100def show_cylinder(center, axis, radius, height, divisions, color, volume):
101
102 transform = volume.data.ijk_to_xyz_transform
103 varray, tarray = cylinder_geometry(center, axis, radius, height,
104 divisions, transform)
105 name = volume.name + ' cyl'
106 align = volume.openState.xform
107 s = create_cylinder_model(varray, tarray, color, align, name)
108
109 return s
110
111# -----------------------------------------------------------------------------
112# Mesh for cylinder.
113#
114def cylinder_geometry(center, axis, radius, height, divisions, transform):
115
116 from math import pi
117 circumf = 2*pi*radius
118 if circumf > height:
119 nc = divisions
120 nz = max(1, int(divisions * height / circumf))
121 else:
122 nc = max(6, int(divisions * circumf / height))
123 nz = divisions
124
125 # Surface for cylinder with axis along z.
126 from Shape.shapecmd import cylinder_geometry
127 varray, tarray = cylinder_geometry(radius, height, nz, nc, caps = True)
128
129 # Tilt cylinder axis
130 import Matrix
131 xa,ya,za = Matrix.orthonormal_frame(axis)
132 cx,cy,cz = center
133 tf = ((xa[0], ya[0], za[0], cx),
134 (xa[1], ya[1], za[1], cy),
135 (xa[2], ya[2], za[2], cz))
136 from _contour import affine_transform_vertices
137 affine_transform_vertices(varray, tf)
138 affine_transform_vertices(varray, transform)
139
140 return varray, tarray
141
142# -----------------------------------------------------------------------------
143#
144def create_cylinder_model(vertices, triangles, color, align, name):
145
146 # Create surface model
147 import _surface
148 s = _surface.SurfaceModel()
149 s.name = name
150 p = s.addPiece(vertices, triangles, color)
151 p.displayStyle = p.Mesh
152
153 # Show model
154 from chimera import openModels
155 openModels.add([s])
156 if align:
157 s.openState.xform = align
158 return s
159
160# -----------------------------------------------------------------------------
161#
162fit_and_mask_cylinder()