Changes in / [edc783e:66dbbfb] in sasmodels
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/jitter.py
rcff2939 r7d97437 1 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*-3 2 """ 4 3 Jitter Explorer … … 6 5 7 6 Application to explore orientation angle and angular dispersity. 8 9 From the command line::10 11 # Show docs12 python -m sasmodels.jitter --help13 14 # Guyou projection jitter, uniform over 20 degree theta and 10 in phi15 python -m sasmodels.jitter --projection=guyou --dist=uniform --jitter=20,10,016 17 From a jupyter cell::18 19 import ipyvolume as ipv20 from sasmodels import jitter21 import importlib; importlib.reload(jitter)22 jitter.set_plotter("ipv")23 24 size = (10, 40, 100)25 view = (20, 0, 0)26 27 #size = (15, 15, 100)28 #view = (60, 60, 0)29 30 dview = (0, 0, 0)31 #dview = (5, 5, 0)32 #dview = (15, 180, 0)33 #dview = (180, 15, 0)34 35 projection = 'equirectangular'36 #projection = 'azimuthal_equidistance'37 #projection = 'guyou'38 #projection = 'sinusoidal'39 #projection = 'azimuthal_equal_area'40 41 dist = 'uniform'42 #dist = 'gaussian'43 44 jitter.run(size=size, view=view, jitter=dview, dist=dist, projection=projection)45 #filename = projection+('_theta' if dview[0] == 180 else '_phi' if dview[1] == 180 else '')46 #ipv.savefig(filename+'.png')47 7 """ 48 8 from __future__ import division, print_function … … 50 10 import argparse 51 11 12 try: # CRUFT: travis-ci does not support mpl_toolkits.mplot3d 13 import mpl_toolkits.mplot3d # Adds projection='3d' option to subplot 14 except ImportError: 15 pass 16 17 import matplotlib as mpl 18 import matplotlib.pyplot as plt 19 from matplotlib.widgets import Slider 52 20 import numpy as np 53 21 from numpy import pi, cos, sin, sqrt, exp, degrees, radians 54 22 55 def draw_beam(axes, view=(0, 0) , alpha=0.5, steps=25):23 def draw_beam(axes, view=(0, 0)): 56 24 """ 57 25 Draw the beam going from source at (0, 0, 1) to detector at (0, 0, -1) 58 26 """ 59 27 #axes.plot([0,0],[0,0],[1,-1]) 60 #axes.scatter([0]*100,[0]*100,np.linspace(1, -1, 100), alpha=alpha) 61 28 #axes.scatter([0]*100,[0]*100,np.linspace(1, -1, 100), alpha=0.8) 29 30 steps = 25 62 31 u = np.linspace(0, 2 * np.pi, steps) 63 v = np.linspace(-1, 1, 2)32 v = np.linspace(-1, 1, steps) 64 33 65 34 r = 0.02 … … 73 42 points = Rz(phi)*Ry(theta)*points 74 43 x, y, z = [v.reshape(shape) for v in points] 75 axes.plot_surface(x, y, z, color='yellow', alpha=alpha) 76 77 # TODO: draw endcaps on beam 78 ## Drawing tiny balls on the end will work 79 #draw_sphere(axes, radius=0.02, center=(0, 0, 1.3), color='yellow', alpha=alpha) 80 #draw_sphere(axes, radius=0.02, center=(0, 0, -1.3), color='yellow', alpha=alpha) 81 ## The following does not work 82 #triangles = [(0, i+1, i+2) for i in range(steps-2)] 83 #x_cap, y_cap = x[:, 0], y[:, 0] 84 #for z_cap in z[:, 0], z[:, -1]: 85 # axes.plot_trisurf(x_cap, y_cap, z_cap, triangles, 86 # color='yellow', alpha=alpha) 87 44 45 axes.plot_surface(x, y, z, rstride=4, cstride=4, color='y', alpha=0.5) 88 46 89 47 def draw_ellipsoid(axes, size, view, jitter, steps=25, alpha=1): … … 97 55 x, y, z = transform_xyz(view, jitter, x, y, z) 98 56 99 axes.plot_surface(x, y, z, color='w', alpha=alpha)57 axes.plot_surface(x, y, z, rstride=4, cstride=4, color='w', alpha=alpha) 100 58 101 59 draw_labels(axes, view, jitter, [ … … 166 124 return atoms 167 125 168 def draw_box(axes, size, view): 169 a, b, c = size 170 x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) 171 y = b*np.array([+1, +1, -1, -1, +1, +1, -1, -1]) 172 z = c*np.array([+1, +1, +1, +1, -1, -1, -1, -1]) 173 x, y, z = transform_xyz(view, None, x, y, z) 174 def draw(i, j): 175 axes.plot([x[i],x[j]], [y[i], y[j]], [z[i], z[j]], color='black') 176 draw(0, 1) 177 draw(0, 2) 178 draw(0, 3) 179 draw(7, 4) 180 draw(7, 5) 181 draw(7, 6) 182 183 def draw_parallelepiped(axes, size, view, jitter, steps=None, 184 color=(0.6, 1.0, 0.6), alpha=1): 126 def draw_parallelepiped(axes, size, view, jitter, steps=None, alpha=1): 185 127 """Draw a parallelepiped.""" 186 128 a, b, c = size … … 200 142 201 143 x, y, z = transform_xyz(view, jitter, x, y, z) 202 axes.plot_trisurf(x, y, triangles=tri, Z=z, color=color, alpha=alpha, 203 linewidth=0) 204 205 # Colour the c+ face of the box. 144 axes.plot_trisurf(x, y, triangles=tri, Z=z, color='w', alpha=alpha) 145 146 # Draw pink face on box. 206 147 # Since I can't control face color, instead draw a thin box situated just 207 148 # in front of the "c+" face. Use the c face so that rotations about psi 208 149 # rotate that face. 209 if 0: 210 color = (1, 0.6, 0.6) # pink 150 if 1: 211 151 x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) 212 152 y = b*np.array([+1, +1, -1, -1, +1, +1, -1, -1]) 213 153 z = c*np.array([+1, +1, +1, +1, -1, -1, -1, -1]) 214 154 x, y, z = transform_xyz(view, jitter, x, y, abs(z)+0.001) 215 axes.plot_trisurf(x, y, triangles=tri, Z=z, color= color, alpha=alpha)155 axes.plot_trisurf(x, y, triangles=tri, Z=z, color=[1, 0.6, 0.6], alpha=alpha) 216 156 217 157 draw_labels(axes, view, jitter, [ … … 224 164 ]) 225 165 226 def draw_sphere(axes, radius= 0.5, steps=25, center=(0,0,0), color='w', alpha=1.):166 def draw_sphere(axes, radius=10., steps=100): 227 167 """Draw a sphere""" 228 168 u = np.linspace(0, 2 * np.pi, steps) 229 169 v = np.linspace(0, np.pi, steps) 230 170 231 x = radius * np.outer(np.cos(u), np.sin(v)) + center[0] 232 y = radius * np.outer(np.sin(u), np.sin(v)) + center[1] 233 z = radius * np.outer(np.ones(np.size(u)), np.cos(v)) + center[2] 234 axes.plot_surface(x, y, z, color=color, alpha=alpha) 235 #axes.plot_wireframe(x, y, z) 236 237 def draw_axes(axes, origin=(-1, -1, -1), length=(2, 2, 2)): 238 x, y, z = origin 239 dx, dy, dz = length 240 axes.plot([x, x+dx], [y, y], [z, z], color='black') 241 axes.plot([x, x], [y, y+dy], [z, z], color='black') 242 axes.plot([x, x], [y, y], [z, z+dz], color='black') 243 244 def draw_person_on_sphere(axes, view, height=0.5, radius=0.5): 245 limb_offset = height * 0.05 246 head_radius = height * 0.10 247 head_height = height - head_radius 248 neck_length = head_radius * 0.50 249 shoulder_height = height - 2*head_radius - neck_length 250 torso_length = shoulder_height * 0.55 251 torso_radius = torso_length * 0.30 252 leg_length = shoulder_height - torso_length 253 arm_length = torso_length * 0.90 254 255 def _draw_part(x, z): 256 y = np.zeros_like(x) 257 xp, yp, zp = transform_xyz(view, None, x, y, z + radius) 258 axes.plot(xp, yp, zp, color='k') 259 260 # circle for head 261 u = np.linspace(0, 2 * np.pi, 40) 262 x = head_radius * np.cos(u) 263 z = head_radius * np.sin(u) + head_height 264 _draw_part(x, z) 265 266 # rectangle for body 267 x = np.array([-torso_radius, torso_radius, torso_radius, -torso_radius, -torso_radius]) 268 z = np.array([0., 0, torso_length, torso_length, 0]) + leg_length 269 _draw_part(x, z) 270 271 # arms 272 x = np.array([-torso_radius - limb_offset, -torso_radius - limb_offset, -torso_radius]) 273 z = np.array([shoulder_height - arm_length, shoulder_height, shoulder_height]) 274 _draw_part(x, z) 275 _draw_part(-x, z) 276 277 # legs 278 x = np.array([-torso_radius + limb_offset, -torso_radius + limb_offset]) 279 z = np.array([0, leg_length]) 280 _draw_part(x, z) 281 _draw_part(-x, z) 282 283 limits = [-radius-height, radius+height] 284 axes.set_xlim(limits) 285 axes.set_ylim(limits) 286 axes.set_zlim(limits) 287 axes.set_axis_off() 288 289 def draw_jitter(axes, view, jitter, dist='gaussian', 290 size=(0.1, 0.4, 1.0), 291 draw_shape=draw_parallelepiped, 292 projection='equirectangular', 293 alpha=0.8, 294 views=None): 171 x = radius * np.outer(np.cos(u), np.sin(v)) 172 y = radius * np.outer(np.sin(u), np.sin(v)) 173 z = radius * np.outer(np.ones(np.size(u)), np.cos(v)) 174 axes.plot_surface(x, y, z, rstride=4, cstride=4, color='w') 175 176 def draw_jitter(axes, view, jitter, dist='gaussian', size=(0.1, 0.4, 1.0), 177 draw_shape=draw_parallelepiped): 295 178 """ 296 179 Represent jitter as a set of shapes at different orientations. 297 180 """ 298 project, project_weight = get_projection(projection)299 300 181 # set max diagonal to 0.95 301 182 scale = 0.95/sqrt(sum(v**2 for v in size)) 302 183 size = tuple(scale*v for v in size) 303 184 185 #np.random.seed(10) 186 #cloud = np.random.randn(10,3) 187 cloud = [ 188 [-1, -1, -1], 189 [-1, -1, +0], 190 [-1, -1, +1], 191 [-1, +0, -1], 192 [-1, +0, +0], 193 [-1, +0, +1], 194 [-1, +1, -1], 195 [-1, +1, +0], 196 [-1, +1, +1], 197 [+0, -1, -1], 198 [+0, -1, +0], 199 [+0, -1, +1], 200 [+0, +0, -1], 201 [+0, +0, +0], 202 [+0, +0, +1], 203 [+0, +1, -1], 204 [+0, +1, +0], 205 [+0, +1, +1], 206 [+1, -1, -1], 207 [+1, -1, +0], 208 [+1, -1, +1], 209 [+1, +0, -1], 210 [+1, +0, +0], 211 [+1, +0, +1], 212 [+1, +1, -1], 213 [+1, +1, +0], 214 [+1, +1, +1], 215 ] 304 216 dtheta, dphi, dpsi = jitter 305 base = {'gaussian':3, 'rectangle':sqrt(3), 'uniform':1}[dist] 306 def steps(delta): 307 if views is None: 308 n = max(3, min(25, 2*int(base*delta/5))) 309 else: 310 n = views 311 return base*delta*np.linspace(-1, 1, n) if delta > 0 else [0.] 312 for theta in steps(dtheta): 313 for phi in steps(dphi): 314 for psi in steps(dpsi): 315 w = project_weight(theta, phi, 1.0, 1.0) 316 if w > 0: 317 dview = project(theta, phi, psi) 318 draw_shape(axes, size, view, dview, alpha=alpha) 217 if dtheta == 0: 218 cloud = [v for v in cloud if v[0] == 0] 219 if dphi == 0: 220 cloud = [v for v in cloud if v[1] == 0] 221 if dpsi == 0: 222 cloud = [v for v in cloud if v[2] == 0] 223 draw_shape(axes, size, view, [0, 0, 0], steps=100, alpha=0.8) 224 scale = {'gaussian':1, 'rectangle':1/sqrt(3), 'uniform':1/3}[dist] 225 for point in cloud: 226 delta = [scale*dtheta*point[0], scale*dphi*point[1], scale*dpsi*point[2]] 227 draw_shape(axes, size, view, delta, alpha=0.8) 319 228 for v in 'xyz': 320 229 a, b, c = size 321 230 lim = np.sqrt(a**2 + b**2 + c**2) 322 231 getattr(axes, 'set_'+v+'lim')([-lim, lim]) 323 #getattr(axes, v+'axis').label.set_text(v)232 getattr(axes, v+'axis').label.set_text(v) 324 233 325 234 PROJECTIONS = [ … … 329 238 'azimuthal_equal_area', 330 239 ] 331 def get_projection(projection): 332 333 """ 240 def draw_mesh(axes, view, jitter, radius=1.2, n=11, dist='gaussian', 241 projection='equirectangular'): 242 """ 243 Draw the dispersion mesh showing the theta-phi orientations at which 244 the model will be evaluated. 245 334 246 jitter projections 335 247 <https://en.wikipedia.org/wiki/List_of_map_projections> … … 387 299 # TODO: try Kent distribution instead of a gaussian warped by projection 388 300 301 dist_x = np.linspace(-1, 1, n) 302 weights = np.ones_like(dist_x) 303 if dist == 'gaussian': 304 dist_x *= 3 305 weights = exp(-0.5*dist_x**2) 306 elif dist == 'rectangle': 307 # Note: uses sasmodels ridiculous definition of rectangle width 308 dist_x *= sqrt(3) 309 elif dist == 'uniform': 310 pass 311 else: 312 raise ValueError("expected dist to be gaussian, rectangle or uniform") 313 389 314 if projection == 'equirectangular': #define PROJECTION 1 390 def _project(theta_i, phi_j, psi): 391 latitude, longitude = theta_i, phi_j 392 return latitude, longitude, psi 393 #return Rx(phi_j)*Ry(theta_i) 315 def _rotate(theta_i, phi_j): 316 return Rx(phi_j)*Ry(theta_i) 394 317 def _weight(theta_i, phi_j, w_i, w_j): 395 318 return w_i*w_j*abs(cos(radians(theta_i))) 396 319 elif projection == 'sinusoidal': #define PROJECTION 2 397 def _ project(theta_i, phi_j, psi):320 def _rotate(theta_i, phi_j): 398 321 latitude = theta_i 399 322 scale = cos(radians(latitude)) 400 323 longitude = phi_j/scale if abs(phi_j) < abs(scale)*180 else 0 401 324 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 402 return latitude, longitude, psi 403 #return Rx(longitude)*Ry(latitude) 404 def _project(theta_i, phi_j, w_i, w_j): 325 return Rx(longitude)*Ry(latitude) 326 def _weight(theta_i, phi_j, w_i, w_j): 405 327 latitude = theta_i 406 328 scale = cos(radians(latitude)) … … 408 330 return active*w_i*w_j 409 331 elif projection == 'guyou': #define PROJECTION 3 (eventually?) 410 def _ project(theta_i, phi_j, psi):332 def _rotate(theta_i, phi_j): 411 333 from .guyou import guyou_invert 412 334 #latitude, longitude = guyou_invert([theta_i], [phi_j]) 413 335 longitude, latitude = guyou_invert([phi_j], [theta_i]) 414 return latitude, longitude, psi 415 #return Rx(longitude[0])*Ry(latitude[0]) 336 return Rx(longitude[0])*Ry(latitude[0]) 416 337 def _weight(theta_i, phi_j, w_i, w_j): 417 338 return w_i*w_j 418 elif projection == 'azimuthal_equidistance': 419 # Note that calculates angles for Rz Ry rather than Rx Ry 420 def _project(theta_i, phi_j, psi): 339 elif projection == 'azimuthal_equidistance': # Note: Rz Ry, not Rx Ry 340 def _rotate(theta_i, phi_j): 421 341 latitude = sqrt(theta_i**2 + phi_j**2) 422 342 longitude = degrees(np.arctan2(phi_j, theta_i)) 423 343 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 424 return latitude, longitude, psi-longitude, 'zyz' 425 #R = Rz(longitude)*Ry(latitude)*Rz(psi) 426 #return R_to_xyz(R) 427 #return Rz(longitude)*Ry(latitude) 344 return Rz(longitude)*Ry(latitude) 428 345 def _weight(theta_i, phi_j, w_i, w_j): 429 346 # Weighting for each point comes from the integral: … … 459 376 return weight*w_i*w_j if latitude < 180 else 0 460 377 elif projection == 'azimuthal_equal_area': 461 # Note that calculates angles for Rz Ry rather than Rx Ry 462 def _project(theta_i, phi_j, psi): 378 def _rotate(theta_i, phi_j): 463 379 radius = min(1, sqrt(theta_i**2 + phi_j**2)/180) 464 380 latitude = 180-degrees(2*np.arccos(radius)) 465 381 longitude = degrees(np.arctan2(phi_j, theta_i)) 466 382 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 467 return latitude, longitude, psi, "zyz" 468 #R = Rz(longitude)*Ry(latitude)*Rz(psi) 469 #return R_to_xyz(R) 470 #return Rz(longitude)*Ry(latitude) 383 return Rz(longitude)*Ry(latitude) 471 384 def _weight(theta_i, phi_j, w_i, w_j): 472 385 latitude = sqrt(theta_i**2 + phi_j**2) … … 476 389 raise ValueError("unknown projection %r"%projection) 477 390 478 return _project, _weight479 480 def R_to_xyz(R):481 """482 Return phi, theta, psi Tait-Bryan angles corresponding to the given rotation matrix.483 484 Extracting Euler Angles from a Rotation Matrix485 Mike Day, Insomniac Games486 https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf487 Based on: Shoemakeâs "Euler Angle Conversion", Graphics Gems IV, pp. 222-229488 """489 phi = np.arctan2(R[1, 2], R[2, 2])490 theta = np.arctan2(-R[0, 2], np.sqrt(R[0, 0]**2 + R[0, 1]**2))491 psi = np.arctan2(R[0, 1], R[0, 0])492 return np.degrees(phi), np.degrees(theta), np.degrees(psi)493 494 def draw_mesh(axes, view, jitter, radius=1.2, n=11, dist='gaussian',495 projection='equirectangular'):496 """497 Draw the dispersion mesh showing the theta-phi orientations at which498 the model will be evaluated.499 """500 501 _project, _weight = get_projection(projection)502 def _rotate(theta, phi, z):503 dview = _project(theta, phi, 0.)504 if len(dview) == 4: # hack for zyz coords505 return Rz(dview[1])*Ry(dview[0])*z506 else:507 return Rx(dview[1])*Ry(dview[0])*z508 509 510 dist_x = np.linspace(-1, 1, n)511 weights = np.ones_like(dist_x)512 if dist == 'gaussian':513 dist_x *= 3514 weights = exp(-0.5*dist_x**2)515 elif dist == 'rectangle':516 # Note: uses sasmodels ridiculous definition of rectangle width517 dist_x *= sqrt(3)518 elif dist == 'uniform':519 pass520 else:521 raise ValueError("expected dist to be gaussian, rectangle or uniform")522 523 391 # mesh in theta, phi formed by rotating z 524 392 dtheta, dphi, dpsi = jitter 525 393 z = np.matrix([[0], [0], [radius]]) 526 points = np.hstack([_rotate(theta_i, phi_j , z)394 points = np.hstack([_rotate(theta_i, phi_j)*z 527 395 for theta_i in dtheta*dist_x 528 396 for phi_j in dphi*dist_x]) … … 602 470 Points are stored in a 3 x n numpy matrix, not a numpy array or tuple. 603 471 """ 604 if jitter is None: 605 return points 606 # Hack to deal with the fact that azimuthal_equidistance uses euler angles 607 if len(jitter) == 4: 608 dtheta, dphi, dpsi, _ = jitter 609 points = Rz(dphi)*Ry(dtheta)*Rz(dpsi)*points 610 else: 611 dtheta, dphi, dpsi = jitter 612 points = Rx(dphi)*Ry(dtheta)*Rz(dpsi)*points 472 dtheta, dphi, dpsi = jitter 473 points = Rx(dphi)*Ry(dtheta)*Rz(dpsi)*points 613 474 return points 614 475 … … 620 481 """ 621 482 theta, phi, psi = view 622 points = Rz(phi)*Ry(theta)*Rz(psi)*points # viewing angle 623 #points = Rz(psi)*Ry(pi/2-theta)*Rz(phi)*points # 1-D integration angles 624 #points = Rx(phi)*Ry(theta)*Rz(psi)*points # angular dispersion angle 483 points = Rz(phi)*Ry(theta)*Rz(psi)*points 625 484 return points 626 627 def orient_relative_to_beam_quaternion(view, points):628 """629 Apply the view transform to a set of points.630 631 Points are stored in a 3 x n numpy matrix, not a numpy array or tuple.632 633 This variant uses quaternions rather than rotation matrices for the634 computation. It works but it is not used because it doesn't solve635 any problems. The challenge of mapping theta/phi/psi to SO(3) does636 not disappear by calculating the transform differently.637 """638 theta, phi, psi = view639 x, y, z = [1, 0, 0], [0, 1, 0], [0, 0, 1]640 q = Quaternion(1, [0, 0, 0])641 ## Compose a rotation about the three axes by rotating642 ## the unit vectors before applying the rotation.643 #q = Quaternion.from_angle_axis(theta, q.rot(x)) * q644 #q = Quaternion.from_angle_axis(phi, q.rot(y)) * q645 #q = Quaternion.from_angle_axis(psi, q.rot(z)) * q646 ## The above turns out to be equivalent to reversing647 ## the order of application, so ignore it and use below.648 q = q * Quaternion.from_angle_axis(theta, x)649 q = q * Quaternion.from_angle_axis(phi, y)650 q = q * Quaternion.from_angle_axis(psi, z)651 ## Reverse the order by post-multiply rather than pre-multiply652 #q = Quaternion.from_angle_axis(theta, x) * q653 #q = Quaternion.from_angle_axis(phi, y) * q654 #q = Quaternion.from_angle_axis(psi, z) * q655 #print("axes psi", q.rot(np.matrix([x, y, z]).T))656 return q.rot(points)657 #orient_relative_to_beam = orient_relative_to_beam_quaternion658 659 # Simple stand-alone quaternion class660 import numpy as np661 from copy import copy662 class Quaternion(object):663 def __init__(self, w, r):664 self.w = w665 self.r = np.asarray(r, dtype='d')666 @staticmethod667 def from_angle_axis(theta, r):668 theta = np.radians(theta)/2669 r = np.asarray(r)670 w = np.cos(theta)671 r = np.sin(theta)*r/np.dot(r,r)672 return Quaternion(w, r)673 def __mul__(self, other):674 if isinstance(other, Quaternion):675 w = self.w*other.w - np.dot(self.r, other.r)676 r = self.w*other.r + other.w*self.r + np.cross(self.r, other.r)677 return Quaternion(w, r)678 def rot(self, v):679 v = np.asarray(v).T680 use_transpose = (v.shape[-1] != 3)681 if use_transpose: v = v.T682 v = v + np.cross(2*self.r, np.cross(self.r, v) + self.w*v)683 #v = v + 2*self.w*np.cross(self.r, v) + np.cross(2*self.r, np.cross(self.r, v))684 if use_transpose: v = v.T685 return v.T686 def conj(self):687 return Quaternion(self.w, -self.r)688 def inv(self):689 return self.conj()/self.norm()**2690 def norm(self):691 return np.sqrt(self.w**2 + np.sum(self.r**2))692 def __str__(self):693 return "%g%+gi%+gj%+gk"%(self.w, self.r[0], self.r[1], self.r[2])694 def test_qrot():695 # Define rotation of 60 degrees around an axis in y-z that is 60 degrees from y.696 # The rotation axis is determined by rotating the point [0, 1, 0] about x.697 ax = Quaternion.from_angle_axis(60, [1, 0, 0]).rot([0, 1, 0])698 q = Quaternion.from_angle_axis(60, ax)699 # Set the point to be rotated, and its expected rotated position.700 p = [1, -1, 2]701 target = [(10+4*np.sqrt(3))/8, (1+2*np.sqrt(3))/8, (14-3*np.sqrt(3))/8]702 #print(q, q.rot(p) - target)703 assert max(abs(q.rot(p) - target)) < 1e-14704 #test_qrot()705 #import sys; sys.exit()706 485 707 486 # translate between number of dimension of dispersity and the number of … … 777 556 vmin = vmax*10**-7 778 557 #vmin, vmax = clipped_range(Iqxy, portion=portion, mode='top') 779 #vmin, vmax = Iqxy.min(), Iqxy.max()780 558 #print("range",(vmin,vmax)) 781 559 #qx, qy = np.meshgrid(qx, qy) 782 560 if 0: 783 from matplotlib import cm784 561 level = np.asarray(255*(Iqxy - vmin)/(vmax - vmin), 'i') 785 562 level[level < 0] = 0 786 563 colors = plt.get_cmap()(level) 787 #colors = cm.coolwarm(level) 788 #colors = cm.gist_yarg(level) 789 #colors = cm.Wistia(level) 790 colors[level<=0, 3] = 0. # set floor to transparent 791 x, y = np.meshgrid(qx/qx.max(), qy/qy.max()) 792 axes.plot_surface(x, y, -1.1*np.ones_like(x), facecolors=colors) 564 axes.plot_surface(qx, qy, -1.1, rstride=1, cstride=1, facecolors=colors) 793 565 elif 1: 794 566 axes.contourf(qx/qx.max(), qy/qy.max(), Iqxy, zdir='z', offset=-1.1, … … 920 692 } 921 693 922 923 694 def run(model_name='parallelepiped', size=(10, 40, 100), 924 view=(0, 0, 0), jitter=(0, 0, 0),925 695 dist='gaussian', mesh=30, 926 696 projection='equirectangular'): … … 932 702 933 703 *size* gives the dimensions (a, b, c) of the shape. 934 935 *view* gives the initial view (theta, phi, psi) of the shape.936 937 *view* gives the initial jitter (dtheta, dphi, dpsi) of the shape.938 704 939 705 *dist* is the type of dispersition: gaussian, rectangle, or uniform. … … 955 721 calculator, size = select_calculator(model_name, n=150, size=size) 956 722 draw_shape = DRAW_SHAPES.get(model_name, draw_parallelepiped) 957 #draw_shape = draw_fcc958 723 959 724 ## uncomment to set an independent the colour range for every view … … 961 726 calculator.limits = None 962 727 963 PLOT_ENGINE(calculator, draw_shape, size, view, jitter, dist, mesh, projection) 964 965 def mpl_plot(calculator, draw_shape, size, view, jitter, dist, mesh, projection): 966 # Note: travis-ci does not support mpl_toolkits.mplot3d, but this shouldn't be 967 # an issue since we are lazy-loading the package on a path that isn't tested. 968 import mpl_toolkits.mplot3d # Adds projection='3d' option to subplot 969 import matplotlib as mpl 970 import matplotlib.pyplot as plt 971 from matplotlib.widgets import Slider 728 ## initial view 729 #theta, dtheta = 70., 10. 730 #phi, dphi = -45., 3. 731 #psi, dpsi = -45., 3. 732 theta, phi, psi = 0, 0, 0 733 dtheta, dphi, dpsi = 0, 0, 0 972 734 973 735 ## create the plot window … … 990 752 991 753 ## add control widgets to plot 992 axes_theta = plt.axes([0.05, 0.15, 0.50, 0.04], **props) 993 axes_phi = plt.axes([0.05, 0.10, 0.50, 0.04], **props) 994 axes_psi = plt.axes([0.05, 0.05, 0.50, 0.04], **props) 995 stheta = Slider(axes_theta, u'Ξ', -90, 90, valinit=0) 996 sphi = Slider(axes_phi, u'Ï', -180, 180, valinit=0) 997 spsi = Slider(axes_psi, u'Ï', -180, 180, valinit=0) 998 999 axes_dtheta = plt.axes([0.70, 0.15, 0.20, 0.04], **props) 1000 axes_dphi = plt.axes([0.70, 0.1, 0.20, 0.04], **props) 1001 axes_dpsi = plt.axes([0.70, 0.05, 0.20, 0.04], **props) 1002 754 axes_theta = plt.axes([0.1, 0.15, 0.45, 0.04], **props) 755 axes_phi = plt.axes([0.1, 0.1, 0.45, 0.04], **props) 756 axes_psi = plt.axes([0.1, 0.05, 0.45, 0.04], **props) 757 stheta = Slider(axes_theta, 'Theta', -90, 90, valinit=theta) 758 sphi = Slider(axes_phi, 'Phi', -180, 180, valinit=phi) 759 spsi = Slider(axes_psi, 'Psi', -180, 180, valinit=psi) 760 761 axes_dtheta = plt.axes([0.75, 0.15, 0.15, 0.04], **props) 762 axes_dphi = plt.axes([0.75, 0.1, 0.15, 0.04], **props) 763 axes_dpsi = plt.axes([0.75, 0.05, 0.15, 0.04], **props) 1003 764 # Note: using ridiculous definition of rectangle distribution, whose width 1004 765 # in sasmodels is sqrt(3) times the given width. Divide by sqrt(3) to keep 1005 766 # the maximum width to 90. 1006 767 dlimit = DIST_LIMITS[dist] 1007 sdtheta = Slider(axes_dtheta, u'ÎΞ', 0, 2*dlimit, valinit=0) 1008 sdphi = Slider(axes_dphi, u'ÎÏ', 0, 2*dlimit, valinit=0) 1009 sdpsi = Slider(axes_dpsi, u'ÎÏ', 0, 2*dlimit, valinit=0) 1010 1011 ## initial view and jitter 1012 theta, phi, psi = view 1013 stheta.set_val(theta) 1014 sphi.set_val(phi) 1015 spsi.set_val(psi) 1016 dtheta, dphi, dpsi = jitter 1017 sdtheta.set_val(dtheta) 1018 sdphi.set_val(dphi) 1019 sdpsi.set_val(dpsi) 768 sdtheta = Slider(axes_dtheta, 'dTheta', 0, 2*dlimit, valinit=dtheta) 769 sdphi = Slider(axes_dphi, 'dPhi', 0, 2*dlimit, valinit=dphi) 770 sdpsi = Slider(axes_dpsi, 'dPsi', 0, 2*dlimit, valinit=dpsi) 771 1020 772 1021 773 ## callback to draw the new view … … 1025 777 # set small jitter as 0 if multiple pd dims 1026 778 dims = sum(v > 0 for v in jitter) 1027 limit = [0, 0.5, 5 , 5][dims]779 limit = [0, 0.5, 5][dims] 1028 780 jitter = [0 if v < limit else v for v in jitter] 1029 781 axes.cla() 1030 1031 ## Visualize as person on globe 1032 #draw_sphere(axes) 1033 #draw_person_on_sphere(axes, view) 1034 1035 ## Move beam instead of shape 1036 #draw_beam(axes, -view[:2]) 1037 #draw_jitter(axes, (0,0,0), (0,0,0), views=3) 1038 1039 ## Move shape and draw scattering 1040 draw_beam(axes, (0, 0), alpha=1.) 1041 draw_jitter(axes, view, jitter, dist=dist, size=size, alpha=1., 1042 draw_shape=draw_shape, projection=projection, views=3) 782 draw_beam(axes, (0, 0)) 783 draw_jitter(axes, view, jitter, dist=dist, size=size, draw_shape=draw_shape) 784 #draw_jitter(axes, view, (0,0,0)) 1043 785 draw_mesh(axes, view, jitter, dist=dist, n=mesh, projection=projection) 1044 786 draw_scattering(calculator, axes, view, jitter, dist=dist) 1045 1046 787 plt.gcf().canvas.draw() 1047 788 … … 1059 800 ## go interactive 1060 801 plt.show() 1061 1062 1063 def map_colors(z, kw):1064 from matplotlib import cm1065 1066 cmap = kw.pop('cmap', cm.coolwarm)1067 alpha = kw.pop('alpha', None)1068 vmin = kw.pop('vmin', z.min())1069 vmax = kw.pop('vmax', z.max())1070 c = kw.pop('c', None)1071 color = kw.pop('color', c)1072 if color is None:1073 znorm = ((z - vmin) / (vmax - vmin)).clip(0, 1)1074 color = cmap(znorm)1075 elif isinstance(color, np.ndarray) and color.shape == z.shape:1076 color = cmap(color)1077 if alpha is None:1078 if isinstance(color, np.ndarray):1079 color = color[..., :3]1080 else:1081 color[..., 3] = alpha1082 kw['color'] = color1083 1084 def make_vec(*args):1085 #return [np.asarray(v, 'd').flatten() for v in args]1086 return [np.asarray(v, 'd') for v in args]1087 1088 def make_image(z, kw):1089 import PIL.Image1090 from matplotlib import cm1091 1092 cmap = kw.pop('cmap', cm.coolwarm)1093 1094 znorm = (z-z.min())/z.ptp()1095 c = cmap(znorm)1096 c = c[..., :3]1097 rgb = np.asarray(c*255, 'u1')1098 image = PIL.Image.fromarray(rgb, mode='RGB')1099 return image1100 1101 1102 _IPV_MARKERS = {1103 'o': 'sphere',1104 }1105 _IPV_COLORS = {1106 'w': 'white',1107 'k': 'black',1108 'c': 'cyan',1109 'm': 'magenta',1110 'y': 'yellow',1111 'r': 'red',1112 'g': 'green',1113 'b': 'blue',1114 }1115 def ipv_fix_color(kw):1116 alpha = kw.pop('alpha', None)1117 color = kw.get('color', None)1118 if isinstance(color, str):1119 color = _IPV_COLORS.get(color, color)1120 kw['color'] = color1121 if alpha is not None:1122 color = kw['color']1123 #TODO: convert color to [r, g, b, a] if not already1124 if isinstance(color, (tuple, list)):1125 if len(color) == 3:1126 color = (color[0], color[1], color[2], alpha)1127 else:1128 color = (color[0], color[1], color[2], alpha*color[3])1129 color = np.array(color)1130 if isinstance(color, np.ndarray) and color.shape[-1] == 4:1131 color[..., 3] = alpha1132 kw['color'] = color1133 1134 def ipv_set_transparency(kw, obj):1135 color = kw.get('color', None)1136 if (isinstance(color, np.ndarray)1137 and color.shape[-1] == 41138 and (color[..., 3] != 1.0).any()):1139 obj.material.transparent = True1140 obj.material.side = "FrontSide"1141 1142 def ipv_axes():1143 import ipyvolume as ipv1144 1145 class Plotter:1146 # transparency can be achieved by setting the following:1147 # mesh.color = [r, g, b, alpha]1148 # mesh.material.transparent = True1149 # mesh.material.side = "FrontSide"1150 # smooth(ish) rotation can be achieved by setting:1151 # slide.continuous_update = True1152 # figure.animation = 0.1153 # mesh.material.x = x1154 # mesh.material.y = y1155 # mesh.material.z = z1156 # maybe need to synchronize update of x/y/z to avoid shimmy when moving1157 def plot(self, x, y, z, **kw):1158 ipv_fix_color(kw)1159 x, y, z = make_vec(x, y, z)1160 ipv.plot(x, y, z, **kw)1161 def plot_surface(self, x, y, z, **kw):1162 facecolors = kw.pop('facecolors', None)1163 if facecolors is not None:1164 kw['color'] = facecolors1165 ipv_fix_color(kw)1166 x, y, z = make_vec(x, y, z)1167 h = ipv.plot_surface(x, y, z, **kw)1168 ipv_set_transparency(kw, h)1169 #h.material.side = "DoubleSide"1170 return h1171 def plot_trisurf(self, x, y, triangles=None, Z=None, **kw):1172 kw.pop('linewidth', None)1173 ipv_fix_color(kw)1174 x, y, z = make_vec(x, y, Z)1175 if triangles is not None:1176 triangles = np.asarray(triangles)1177 h = ipv.plot_trisurf(x, y, z, triangles=triangles, **kw)1178 ipv_set_transparency(kw, h)1179 return h1180 def scatter(self, x, y, z, **kw):1181 x, y, z = make_vec(x, y, z)1182 map_colors(z, kw)1183 marker = kw.get('marker', None)1184 kw['marker'] = _IPV_MARKERS.get(marker, marker)1185 h = ipv.scatter(x, y, z, **kw)1186 ipv_set_transparency(kw, h)1187 return h1188 def contourf(self, x, y, v, zdir='z', offset=0, levels=None, **kw):1189 # Don't use contour for now (although we might want to later)1190 self.pcolor(x, y, v, zdir='z', offset=offset, **kw)1191 def pcolor(self, x, y, v, zdir='z', offset=0, **kw):1192 x, y, v = make_vec(x, y, v)1193 image = make_image(v, kw)1194 xmin, xmax = x.min(), x.max()1195 ymin, ymax = y.min(), y.max()1196 x = np.array([[xmin, xmax], [xmin, xmax]])1197 y = np.array([[ymin, ymin], [ymax, ymax]])1198 z = x*0 + offset1199 u = np.array([[0., 1], [0, 1]])1200 v = np.array([[0., 0], [1, 1]])1201 h = ipv.plot_mesh(x, y, z, u=u, v=v, texture=image, wireframe=False)1202 ipv_set_transparency(kw, h)1203 h.material.side = "DoubleSide"1204 return h1205 def text(self, *args, **kw):1206 pass1207 def set_xlim(self, limits):1208 ipv.xlim(*limits)1209 def set_ylim(self, limits):1210 ipv.ylim(*limits)1211 def set_zlim(self, limits):1212 ipv.zlim(*limits)1213 def set_axes_on(self):1214 ipv.style.axis_on()1215 def set_axis_off(self):1216 ipv.style.axes_off()1217 return Plotter()1218 1219 def ipv_plot(calculator, draw_shape, size, view, jitter, dist, mesh, projection):1220 import ipywidgets as widgets1221 import ipyvolume as ipv1222 1223 axes = ipv_axes()1224 1225 def draw(view, jitter):1226 camera = ipv.gcf().camera1227 #print(ipv.gcf().__dict__.keys())1228 #print(dir(ipv.gcf()))1229 ipv.figure(animation=0.) # no animation when updating object mesh1230 1231 # set small jitter as 0 if multiple pd dims1232 dims = sum(v > 0 for v in jitter)1233 limit = [0, 0.5, 5, 5][dims]1234 jitter = [0 if v < limit else v for v in jitter]1235 1236 ## Visualize as person on globe1237 #draw_beam(axes, (0, 0))1238 #draw_sphere(axes)1239 #draw_person_on_sphere(axes, view)1240 1241 ## Move beam instead of shape1242 #draw_beam(axes, view=(-view[0], -view[1]))1243 #draw_jitter(axes, view=(0,0,0), jitter=(0,0,0))1244 1245 ## Move shape and draw scattering1246 draw_beam(axes, (0, 0), steps=25)1247 draw_jitter(axes, view, jitter, dist=dist, size=size, alpha=1.0,1248 draw_shape=draw_shape, projection=projection)1249 draw_mesh(axes, view, jitter, dist=dist, n=mesh, radius=0.95,1250 projection=projection)1251 draw_scattering(calculator, axes, view, jitter, dist=dist)1252 1253 draw_axes(axes, origin=(-1, -1, -1.1))1254 ipv.style.box_off()1255 ipv.style.axes_off()1256 ipv.xyzlabel(" ", " ", " ")1257 1258 ipv.gcf().camera = camera1259 ipv.show()1260 1261 1262 trange, prange = (-180., 180., 1.), (-180., 180., 1.)1263 dtrange, dprange = (0., 180., 1.), (0., 180., 1.)1264 1265 ## Super simple interfaca, but uses non-ascii variable namese1266 # Ξ Ï Ï ÎΞ ÎÏ ÎÏ1267 #def update(**kw):1268 # view = kw['Ξ'], kw['Ï'], kw['Ï']1269 # jitter = kw['ÎΞ'], kw['ÎÏ'], kw['ÎÏ']1270 # draw(view, jitter)1271 #widgets.interact(update, Ξ=trange, Ï=prange, Ï=prange, ÎΞ=dtrange, ÎÏ=dprange, ÎÏ=dprange)1272 1273 def update(theta, phi, psi, dtheta, dphi, dpsi):1274 draw(view=(theta, phi, psi), jitter=(dtheta, dphi, dpsi))1275 1276 def slider(name, slice, init=0.):1277 return widgets.FloatSlider(1278 value=init,1279 min=slice[0],1280 max=slice[1],1281 step=slice[2],1282 description=name,1283 disabled=False,1284 #continuous_update=True,1285 continuous_update=False,1286 orientation='horizontal',1287 readout=True,1288 readout_format='.1f',1289 )1290 theta = slider(u'Ξ', trange, view[0])1291 phi = slider(u'Ï', prange, view[1])1292 psi = slider(u'Ï', prange, view[2])1293 dtheta = slider(u'ÎΞ', dtrange, jitter[0])1294 dphi = slider(u'ÎÏ', dprange, jitter[1])1295 dpsi = slider(u'ÎÏ', dprange, jitter[2])1296 fields = {1297 'theta': theta, 'phi': phi, 'psi': psi,1298 'dtheta': dtheta, 'dphi': dphi, 'dpsi': dpsi,1299 }1300 ui = widgets.HBox([1301 widgets.VBox([theta, phi, psi]),1302 widgets.VBox([dtheta, dphi, dpsi])1303 ])1304 1305 out = widgets.interactive_output(update, fields)1306 display(ui, out)1307 1308 1309 _ENGINES = {1310 "matplotlib": mpl_plot,1311 "mpl": mpl_plot,1312 #"plotly": plotly_plot,1313 "ipvolume": ipv_plot,1314 "ipv": ipv_plot,1315 }1316 PLOT_ENGINE = _ENGINES["matplotlib"]1317 def set_plotter(name):1318 global PLOT_ENGINE1319 PLOT_ENGINE = _ENGINES[name]1320 802 1321 803 def main(): … … 1329 811 parser.add_argument('-s', '--size', type=str, default='10,40,100', 1330 812 help='a,b,c lengths') 1331 parser.add_argument('-v', '--view', type=str, default='0,0,0',1332 help='initial view angles')1333 parser.add_argument('-j', '--jitter', type=str, default='0,0,0',1334 help='initial angular dispersion')1335 813 parser.add_argument('-d', '--distribution', choices=DISTRIBUTIONS, 1336 814 default=DISTRIBUTIONS[0], … … 1341 819 help='oriented shape') 1342 820 opts = parser.parse_args() 1343 size = tuple(float(v) for v in opts.size.split(',')) 1344 view = tuple(float(v) for v in opts.view.split(',')) 1345 jitter = tuple(float(v) for v in opts.jitter.split(',')) 1346 run(opts.shape, size=size, view=view, jitter=jitter, 821 size = tuple(int(v) for v in opts.size.split(',')) 822 run(opts.shape, size=size, 1347 823 mesh=opts.mesh, dist=opts.distribution, 1348 824 projection=opts.projection)
Note: See TracChangeset
for help on using the changeset viewer.