Changes in / [9150036:674186e] in sasmodels
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/jitter.py
r7d97437 rcff2939 1 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 2 3 """ 3 4 Jitter Explorer … … 5 6 6 7 Application to explore orientation angle and angular dispersity. 8 9 From the command line:: 10 11 # Show docs 12 python -m sasmodels.jitter --help 13 14 # Guyou projection jitter, uniform over 20 degree theta and 10 in phi 15 python -m sasmodels.jitter --projection=guyou --dist=uniform --jitter=20,10,0 16 17 From a jupyter cell:: 18 19 import ipyvolume as ipv 20 from sasmodels import jitter 21 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') 7 47 """ 8 48 from __future__ import division, print_function … … 10 50 import argparse 11 51 12 try: # CRUFT: travis-ci does not support mpl_toolkits.mplot3d13 import mpl_toolkits.mplot3d # Adds projection='3d' option to subplot14 except ImportError:15 pass16 17 import matplotlib as mpl18 import matplotlib.pyplot as plt19 from matplotlib.widgets import Slider20 52 import numpy as np 21 53 from numpy import pi, cos, sin, sqrt, exp, degrees, radians 22 54 23 def draw_beam(axes, view=(0, 0) ):55 def draw_beam(axes, view=(0, 0), alpha=0.5, steps=25): 24 56 """ 25 57 Draw the beam going from source at (0, 0, 1) to detector at (0, 0, -1) 26 58 """ 27 59 #axes.plot([0,0],[0,0],[1,-1]) 28 #axes.scatter([0]*100,[0]*100,np.linspace(1, -1, 100), alpha=0.8) 29 30 steps = 25 60 #axes.scatter([0]*100,[0]*100,np.linspace(1, -1, 100), alpha=alpha) 61 31 62 u = np.linspace(0, 2 * np.pi, steps) 32 v = np.linspace(-1, 1, steps)63 v = np.linspace(-1, 1, 2) 33 64 34 65 r = 0.02 … … 42 73 points = Rz(phi)*Ry(theta)*points 43 74 x, y, z = [v.reshape(shape) for v in points] 44 45 axes.plot_surface(x, y, z, rstride=4, cstride=4, color='y', alpha=0.5) 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 46 88 47 89 def draw_ellipsoid(axes, size, view, jitter, steps=25, alpha=1): … … 55 97 x, y, z = transform_xyz(view, jitter, x, y, z) 56 98 57 axes.plot_surface(x, y, z, rstride=4, cstride=4,color='w', alpha=alpha)99 axes.plot_surface(x, y, z, color='w', alpha=alpha) 58 100 59 101 draw_labels(axes, view, jitter, [ … … 124 166 return atoms 125 167 126 def draw_parallelepiped(axes, size, view, jitter, steps=None, alpha=1): 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): 127 185 """Draw a parallelepiped.""" 128 186 a, b, c = size … … 142 200 143 201 x, y, z = transform_xyz(view, jitter, x, y, z) 144 axes.plot_trisurf(x, y, triangles=tri, Z=z, color='w', alpha=alpha) 145 146 # Draw pink face on box. 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. 147 206 # Since I can't control face color, instead draw a thin box situated just 148 207 # in front of the "c+" face. Use the c face so that rotations about psi 149 208 # rotate that face. 150 if 1: 209 if 0: 210 color = (1, 0.6, 0.6) # pink 151 211 x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) 152 212 y = b*np.array([+1, +1, -1, -1, +1, +1, -1, -1]) 153 213 z = c*np.array([+1, +1, +1, +1, -1, -1, -1, -1]) 154 214 x, y, z = transform_xyz(view, jitter, x, y, abs(z)+0.001) 155 axes.plot_trisurf(x, y, triangles=tri, Z=z, color= [1, 0.6, 0.6], alpha=alpha)215 axes.plot_trisurf(x, y, triangles=tri, Z=z, color=color, alpha=alpha) 156 216 157 217 draw_labels(axes, view, jitter, [ … … 164 224 ]) 165 225 166 def draw_sphere(axes, radius= 10., steps=100):226 def draw_sphere(axes, radius=0.5, steps=25, center=(0,0,0), color='w', alpha=1.): 167 227 """Draw a sphere""" 168 228 u = np.linspace(0, 2 * np.pi, steps) 169 229 v = np.linspace(0, np.pi, steps) 170 230 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): 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): 178 295 """ 179 296 Represent jitter as a set of shapes at different orientations. 180 297 """ 298 project, project_weight = get_projection(projection) 299 181 300 # set max diagonal to 0.95 182 301 scale = 0.95/sqrt(sum(v**2 for v in size)) 183 302 size = tuple(scale*v for v in size) 184 303 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 ]216 304 dtheta, dphi, dpsi = jitter 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) 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) 228 319 for v in 'xyz': 229 320 a, b, c = size 230 321 lim = np.sqrt(a**2 + b**2 + c**2) 231 322 getattr(axes, 'set_'+v+'lim')([-lim, lim]) 232 getattr(axes, v+'axis').label.set_text(v)323 #getattr(axes, v+'axis').label.set_text(v) 233 324 234 325 PROJECTIONS = [ … … 238 329 'azimuthal_equal_area', 239 330 ] 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 331 def get_projection(projection): 332 333 """ 246 334 jitter projections 247 335 <https://en.wikipedia.org/wiki/List_of_map_projections> … … 299 387 # TODO: try Kent distribution instead of a gaussian warped by projection 300 388 301 dist_x = np.linspace(-1, 1, n)302 weights = np.ones_like(dist_x)303 if dist == 'gaussian':304 dist_x *= 3305 weights = exp(-0.5*dist_x**2)306 elif dist == 'rectangle':307 # Note: uses sasmodels ridiculous definition of rectangle width308 dist_x *= sqrt(3)309 elif dist == 'uniform':310 pass311 else:312 raise ValueError("expected dist to be gaussian, rectangle or uniform")313 314 389 if projection == 'equirectangular': #define PROJECTION 1 315 def _rotate(theta_i, phi_j): 316 return Rx(phi_j)*Ry(theta_i) 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) 317 394 def _weight(theta_i, phi_j, w_i, w_j): 318 395 return w_i*w_j*abs(cos(radians(theta_i))) 319 396 elif projection == 'sinusoidal': #define PROJECTION 2 320 def _ rotate(theta_i, phi_j):397 def _project(theta_i, phi_j, psi): 321 398 latitude = theta_i 322 399 scale = cos(radians(latitude)) 323 400 longitude = phi_j/scale if abs(phi_j) < abs(scale)*180 else 0 324 401 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 325 return Rx(longitude)*Ry(latitude) 326 def _weight(theta_i, phi_j, w_i, w_j): 402 return latitude, longitude, psi 403 #return Rx(longitude)*Ry(latitude) 404 def _project(theta_i, phi_j, w_i, w_j): 327 405 latitude = theta_i 328 406 scale = cos(radians(latitude)) … … 330 408 return active*w_i*w_j 331 409 elif projection == 'guyou': #define PROJECTION 3 (eventually?) 332 def _ rotate(theta_i, phi_j):410 def _project(theta_i, phi_j, psi): 333 411 from .guyou import guyou_invert 334 412 #latitude, longitude = guyou_invert([theta_i], [phi_j]) 335 413 longitude, latitude = guyou_invert([phi_j], [theta_i]) 336 return Rx(longitude[0])*Ry(latitude[0]) 414 return latitude, longitude, psi 415 #return Rx(longitude[0])*Ry(latitude[0]) 337 416 def _weight(theta_i, phi_j, w_i, w_j): 338 417 return w_i*w_j 339 elif projection == 'azimuthal_equidistance': # Note: Rz Ry, not Rx Ry 340 def _rotate(theta_i, phi_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): 341 421 latitude = sqrt(theta_i**2 + phi_j**2) 342 422 longitude = degrees(np.arctan2(phi_j, theta_i)) 343 423 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 344 return Rz(longitude)*Ry(latitude) 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) 345 428 def _weight(theta_i, phi_j, w_i, w_j): 346 429 # Weighting for each point comes from the integral: … … 376 459 return weight*w_i*w_j if latitude < 180 else 0 377 460 elif projection == 'azimuthal_equal_area': 378 def _rotate(theta_i, phi_j): 461 # Note that calculates angles for Rz Ry rather than Rx Ry 462 def _project(theta_i, phi_j, psi): 379 463 radius = min(1, sqrt(theta_i**2 + phi_j**2)/180) 380 464 latitude = 180-degrees(2*np.arccos(radius)) 381 465 longitude = degrees(np.arctan2(phi_j, theta_i)) 382 466 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 383 return Rz(longitude)*Ry(latitude) 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) 384 471 def _weight(theta_i, phi_j, w_i, w_j): 385 472 latitude = sqrt(theta_i**2 + phi_j**2) … … 389 476 raise ValueError("unknown projection %r"%projection) 390 477 478 return _project, _weight 479 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 Matrix 485 Mike Day, Insomniac Games 486 https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf 487 Based on: Shoemakeâs "Euler Angle Conversion", Graphics Gems IV, pp. 222-229 488 """ 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 which 498 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 coords 505 return Rz(dview[1])*Ry(dview[0])*z 506 else: 507 return Rx(dview[1])*Ry(dview[0])*z 508 509 510 dist_x = np.linspace(-1, 1, n) 511 weights = np.ones_like(dist_x) 512 if dist == 'gaussian': 513 dist_x *= 3 514 weights = exp(-0.5*dist_x**2) 515 elif dist == 'rectangle': 516 # Note: uses sasmodels ridiculous definition of rectangle width 517 dist_x *= sqrt(3) 518 elif dist == 'uniform': 519 pass 520 else: 521 raise ValueError("expected dist to be gaussian, rectangle or uniform") 522 391 523 # mesh in theta, phi formed by rotating z 392 524 dtheta, dphi, dpsi = jitter 393 525 z = np.matrix([[0], [0], [radius]]) 394 points = np.hstack([_rotate(theta_i, phi_j )*z526 points = np.hstack([_rotate(theta_i, phi_j, z) 395 527 for theta_i in dtheta*dist_x 396 528 for phi_j in dphi*dist_x]) … … 470 602 Points are stored in a 3 x n numpy matrix, not a numpy array or tuple. 471 603 """ 472 dtheta, dphi, dpsi = jitter 473 points = Rx(dphi)*Ry(dtheta)*Rz(dpsi)*points 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 474 613 return points 475 614 … … 481 620 """ 482 621 theta, phi, psi = view 483 points = Rz(phi)*Ry(theta)*Rz(psi)*points 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 484 625 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 the 634 computation. It works but it is not used because it doesn't solve 635 any problems. The challenge of mapping theta/phi/psi to SO(3) does 636 not disappear by calculating the transform differently. 637 """ 638 theta, phi, psi = view 639 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 rotating 642 ## the unit vectors before applying the rotation. 643 #q = Quaternion.from_angle_axis(theta, q.rot(x)) * q 644 #q = Quaternion.from_angle_axis(phi, q.rot(y)) * q 645 #q = Quaternion.from_angle_axis(psi, q.rot(z)) * q 646 ## The above turns out to be equivalent to reversing 647 ## 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-multiply 652 #q = Quaternion.from_angle_axis(theta, x) * q 653 #q = Quaternion.from_angle_axis(phi, y) * q 654 #q = Quaternion.from_angle_axis(psi, z) * q 655 #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_quaternion 658 659 # Simple stand-alone quaternion class 660 import numpy as np 661 from copy import copy 662 class Quaternion(object): 663 def __init__(self, w, r): 664 self.w = w 665 self.r = np.asarray(r, dtype='d') 666 @staticmethod 667 def from_angle_axis(theta, r): 668 theta = np.radians(theta)/2 669 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).T 680 use_transpose = (v.shape[-1] != 3) 681 if use_transpose: v = v.T 682 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.T 685 return v.T 686 def conj(self): 687 return Quaternion(self.w, -self.r) 688 def inv(self): 689 return self.conj()/self.norm()**2 690 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-14 704 #test_qrot() 705 #import sys; sys.exit() 485 706 486 707 # translate between number of dimension of dispersity and the number of … … 556 777 vmin = vmax*10**-7 557 778 #vmin, vmax = clipped_range(Iqxy, portion=portion, mode='top') 779 #vmin, vmax = Iqxy.min(), Iqxy.max() 558 780 #print("range",(vmin,vmax)) 559 781 #qx, qy = np.meshgrid(qx, qy) 560 782 if 0: 783 from matplotlib import cm 561 784 level = np.asarray(255*(Iqxy - vmin)/(vmax - vmin), 'i') 562 785 level[level < 0] = 0 563 786 colors = plt.get_cmap()(level) 564 axes.plot_surface(qx, qy, -1.1, rstride=1, cstride=1, facecolors=colors) 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) 565 793 elif 1: 566 794 axes.contourf(qx/qx.max(), qy/qy.max(), Iqxy, zdir='z', offset=-1.1, … … 692 920 } 693 921 922 694 923 def run(model_name='parallelepiped', size=(10, 40, 100), 924 view=(0, 0, 0), jitter=(0, 0, 0), 695 925 dist='gaussian', mesh=30, 696 926 projection='equirectangular'): … … 702 932 703 933 *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. 704 938 705 939 *dist* is the type of dispersition: gaussian, rectangle, or uniform. … … 721 955 calculator, size = select_calculator(model_name, n=150, size=size) 722 956 draw_shape = DRAW_SHAPES.get(model_name, draw_parallelepiped) 957 #draw_shape = draw_fcc 723 958 724 959 ## uncomment to set an independent the colour range for every view … … 726 961 calculator.limits = None 727 962 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 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 734 972 735 973 ## create the plot window … … 752 990 753 991 ## add control widgets to plot 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) 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 764 1003 # Note: using ridiculous definition of rectangle distribution, whose width 765 1004 # in sasmodels is sqrt(3) times the given width. Divide by sqrt(3) to keep 766 1005 # the maximum width to 90. 767 1006 dlimit = DIST_LIMITS[dist] 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 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) 772 1020 773 1021 ## callback to draw the new view … … 777 1025 # set small jitter as 0 if multiple pd dims 778 1026 dims = sum(v > 0 for v in jitter) 779 limit = [0, 0.5, 5 ][dims]1027 limit = [0, 0.5, 5, 5][dims] 780 1028 jitter = [0 if v < limit else v for v in jitter] 781 1029 axes.cla() 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)) 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) 785 1043 draw_mesh(axes, view, jitter, dist=dist, n=mesh, projection=projection) 786 1044 draw_scattering(calculator, axes, view, jitter, dist=dist) 1045 787 1046 plt.gcf().canvas.draw() 788 1047 … … 800 1059 ## go interactive 801 1060 plt.show() 1061 1062 1063 def map_colors(z, kw): 1064 from matplotlib import cm 1065 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] = alpha 1082 kw['color'] = color 1083 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.Image 1090 from matplotlib import cm 1091 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 image 1100 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'] = color 1121 if alpha is not None: 1122 color = kw['color'] 1123 #TODO: convert color to [r, g, b, a] if not already 1124 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] = alpha 1132 kw['color'] = color 1133 1134 def ipv_set_transparency(kw, obj): 1135 color = kw.get('color', None) 1136 if (isinstance(color, np.ndarray) 1137 and color.shape[-1] == 4 1138 and (color[..., 3] != 1.0).any()): 1139 obj.material.transparent = True 1140 obj.material.side = "FrontSide" 1141 1142 def ipv_axes(): 1143 import ipyvolume as ipv 1144 1145 class Plotter: 1146 # transparency can be achieved by setting the following: 1147 # mesh.color = [r, g, b, alpha] 1148 # mesh.material.transparent = True 1149 # mesh.material.side = "FrontSide" 1150 # smooth(ish) rotation can be achieved by setting: 1151 # slide.continuous_update = True 1152 # figure.animation = 0. 1153 # mesh.material.x = x 1154 # mesh.material.y = y 1155 # mesh.material.z = z 1156 # maybe need to synchronize update of x/y/z to avoid shimmy when moving 1157 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'] = facecolors 1165 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 h 1171 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 h 1180 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 h 1188 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 + offset 1199 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 h 1205 def text(self, *args, **kw): 1206 pass 1207 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 widgets 1221 import ipyvolume as ipv 1222 1223 axes = ipv_axes() 1224 1225 def draw(view, jitter): 1226 camera = ipv.gcf().camera 1227 #print(ipv.gcf().__dict__.keys()) 1228 #print(dir(ipv.gcf())) 1229 ipv.figure(animation=0.) # no animation when updating object mesh 1230 1231 # set small jitter as 0 if multiple pd dims 1232 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 globe 1237 #draw_beam(axes, (0, 0)) 1238 #draw_sphere(axes) 1239 #draw_person_on_sphere(axes, view) 1240 1241 ## Move beam instead of shape 1242 #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 scattering 1246 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 = camera 1259 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 namese 1266 # Ξ Ï Ï ÎΞ ÎÏ ÎÏ 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_ENGINE 1319 PLOT_ENGINE = _ENGINES[name] 802 1320 803 1321 def main(): … … 811 1329 parser.add_argument('-s', '--size', type=str, default='10,40,100', 812 1330 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') 813 1335 parser.add_argument('-d', '--distribution', choices=DISTRIBUTIONS, 814 1336 default=DISTRIBUTIONS[0], … … 819 1341 help='oriented shape') 820 1342 opts = parser.parse_args() 821 size = tuple(int(v) for v in opts.size.split(',')) 822 run(opts.shape, size=size, 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, 823 1347 mesh=opts.mesh, dist=opts.distribution, 824 1348 projection=opts.projection)
Note: See TracChangeset
for help on using the changeset viewer.