Changes in / [31d5187:e589e9a] in sasmodels


Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/jitter.py

    rcff2939 r7d97437  
    11#!/usr/bin/env python 
    2 # -*- coding: utf-8 -*- 
    32""" 
    43Jitter Explorer 
     
    65 
    76Application 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') 
    477""" 
    488from __future__ import division, print_function 
     
    5010import argparse 
    5111 
     12try: # CRUFT: travis-ci does not support mpl_toolkits.mplot3d 
     13    import mpl_toolkits.mplot3d  # Adds projection='3d' option to subplot 
     14except ImportError: 
     15    pass 
     16 
     17import matplotlib as mpl 
     18import matplotlib.pyplot as plt 
     19from matplotlib.widgets import Slider 
    5220import numpy as np 
    5321from numpy import pi, cos, sin, sqrt, exp, degrees, radians 
    5422 
    55 def draw_beam(axes, view=(0, 0), alpha=0.5, steps=25): 
     23def draw_beam(axes, view=(0, 0)): 
    5624    """ 
    5725    Draw the beam going from source at (0, 0, 1) to detector at (0, 0, -1) 
    5826    """ 
    5927    #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 
    6231    u = np.linspace(0, 2 * np.pi, steps) 
    63     v = np.linspace(-1, 1, 2) 
     32    v = np.linspace(-1, 1, steps) 
    6433 
    6534    r = 0.02 
     
    7342    points = Rz(phi)*Ry(theta)*points 
    7443    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) 
    8846 
    8947def draw_ellipsoid(axes, size, view, jitter, steps=25, alpha=1): 
     
    9755    x, y, z = transform_xyz(view, jitter, x, y, z) 
    9856 
    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) 
    10058 
    10159    draw_labels(axes, view, jitter, [ 
     
    166124    return atoms 
    167125 
    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): 
     126def draw_parallelepiped(axes, size, view, jitter, steps=None, alpha=1): 
    185127    """Draw a parallelepiped.""" 
    186128    a, b, c = size 
     
    200142 
    201143    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. 
    206147    # Since I can't control face color, instead draw a thin box situated just 
    207148    # in front of the "c+" face.  Use the c face so that rotations about psi 
    208149    # rotate that face. 
    209     if 0: 
    210         color = (1, 0.6, 0.6)  # pink 
     150    if 1: 
    211151        x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) 
    212152        y = b*np.array([+1, +1, -1, -1, +1, +1, -1, -1]) 
    213153        z = c*np.array([+1, +1, +1, +1, -1, -1, -1, -1]) 
    214154        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) 
    216156 
    217157    draw_labels(axes, view, jitter, [ 
     
    224164    ]) 
    225165 
    226 def draw_sphere(axes, radius=0.5, steps=25, center=(0,0,0), color='w', alpha=1.): 
     166def draw_sphere(axes, radius=10., steps=100): 
    227167    """Draw a sphere""" 
    228168    u = np.linspace(0, 2 * np.pi, steps) 
    229169    v = np.linspace(0, np.pi, steps) 
    230170 
    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 
     176def draw_jitter(axes, view, jitter, dist='gaussian', size=(0.1, 0.4, 1.0), 
     177                draw_shape=draw_parallelepiped): 
    295178    """ 
    296179    Represent jitter as a set of shapes at different orientations. 
    297180    """ 
    298     project, project_weight = get_projection(projection) 
    299  
    300181    # set max diagonal to 0.95 
    301182    scale = 0.95/sqrt(sum(v**2 for v in size)) 
    302183    size = tuple(scale*v for v in size) 
    303184 
     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    ] 
    304216    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) 
    319228    for v in 'xyz': 
    320229        a, b, c = size 
    321230        lim = np.sqrt(a**2 + b**2 + c**2) 
    322231        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) 
    324233 
    325234PROJECTIONS = [ 
     
    329238    'azimuthal_equal_area', 
    330239] 
    331 def get_projection(projection): 
    332  
    333     """ 
     240def 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 
    334246    jitter projections 
    335247    <https://en.wikipedia.org/wiki/List_of_map_projections> 
     
    387299    # TODO: try Kent distribution instead of a gaussian warped by projection 
    388300 
     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 
    389314    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) 
    394317        def _weight(theta_i, phi_j, w_i, w_j): 
    395318            return w_i*w_j*abs(cos(radians(theta_i))) 
    396319    elif projection == 'sinusoidal':  #define PROJECTION 2 
    397         def _project(theta_i, phi_j, psi): 
     320        def _rotate(theta_i, phi_j): 
    398321            latitude = theta_i 
    399322            scale = cos(radians(latitude)) 
    400323            longitude = phi_j/scale if abs(phi_j) < abs(scale)*180 else 0 
    401324            #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): 
    405327            latitude = theta_i 
    406328            scale = cos(radians(latitude)) 
     
    408330            return active*w_i*w_j 
    409331    elif projection == 'guyou':  #define PROJECTION 3  (eventually?) 
    410         def _project(theta_i, phi_j, psi): 
     332        def _rotate(theta_i, phi_j): 
    411333            from .guyou import guyou_invert 
    412334            #latitude, longitude = guyou_invert([theta_i], [phi_j]) 
    413335            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]) 
    416337        def _weight(theta_i, phi_j, w_i, w_j): 
    417338            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): 
    421341            latitude = sqrt(theta_i**2 + phi_j**2) 
    422342            longitude = degrees(np.arctan2(phi_j, theta_i)) 
    423343            #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) 
    428345        def _weight(theta_i, phi_j, w_i, w_j): 
    429346            # Weighting for each point comes from the integral: 
     
    459376            return weight*w_i*w_j if latitude < 180 else 0 
    460377    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): 
    463379            radius = min(1, sqrt(theta_i**2 + phi_j**2)/180) 
    464380            latitude = 180-degrees(2*np.arccos(radius)) 
    465381            longitude = degrees(np.arctan2(phi_j, theta_i)) 
    466382            #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) 
    471384        def _weight(theta_i, phi_j, w_i, w_j): 
    472385            latitude = sqrt(theta_i**2 + phi_j**2) 
     
    476389        raise ValueError("unknown projection %r"%projection) 
    477390 
    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  
    523391    # mesh in theta, phi formed by rotating z 
    524392    dtheta, dphi, dpsi = jitter 
    525393    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 
    527395                        for theta_i in dtheta*dist_x 
    528396                        for phi_j in dphi*dist_x]) 
     
    602470    Points are stored in a 3 x n numpy matrix, not a numpy array or tuple. 
    603471    """ 
    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 
    613474    return points 
    614475 
     
    620481    """ 
    621482    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 
    625484    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() 
    706485 
    707486# translate between number of dimension of dispersity and the number of 
     
    777556        vmin = vmax*10**-7 
    778557        #vmin, vmax = clipped_range(Iqxy, portion=portion, mode='top') 
    779     #vmin, vmax = Iqxy.min(), Iqxy.max() 
    780558    #print("range",(vmin,vmax)) 
    781559    #qx, qy = np.meshgrid(qx, qy) 
    782560    if 0: 
    783         from matplotlib import cm 
    784561        level = np.asarray(255*(Iqxy - vmin)/(vmax - vmin), 'i') 
    785562        level[level < 0] = 0 
    786563        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) 
    793565    elif 1: 
    794566        axes.contourf(qx/qx.max(), qy/qy.max(), Iqxy, zdir='z', offset=-1.1, 
     
    920692} 
    921693 
    922  
    923694def run(model_name='parallelepiped', size=(10, 40, 100), 
    924         view=(0, 0, 0), jitter=(0, 0, 0), 
    925695        dist='gaussian', mesh=30, 
    926696        projection='equirectangular'): 
     
    932702 
    933703    *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. 
    938704 
    939705    *dist* is the type of dispersition: gaussian, rectangle, or uniform. 
     
    955721    calculator, size = select_calculator(model_name, n=150, size=size) 
    956722    draw_shape = DRAW_SHAPES.get(model_name, draw_parallelepiped) 
    957     #draw_shape = draw_fcc 
    958723 
    959724    ## uncomment to set an independent the colour range for every view 
     
    961726    calculator.limits = None 
    962727 
    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 
    972734 
    973735    ## create the plot window 
     
    990752 
    991753    ## 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) 
    1003764    # Note: using ridiculous definition of rectangle distribution, whose width 
    1004765    # in sasmodels is sqrt(3) times the given width.  Divide by sqrt(3) to keep 
    1005766    # the maximum width to 90. 
    1006767    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 
    1020772 
    1021773    ## callback to draw the new view 
     
    1025777        # set small jitter as 0 if multiple pd dims 
    1026778        dims = sum(v > 0 for v in jitter) 
    1027         limit = [0, 0.5, 5, 5][dims] 
     779        limit = [0, 0.5, 5][dims] 
    1028780        jitter = [0 if v < limit else v for v in jitter] 
    1029781        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)) 
    1043785        draw_mesh(axes, view, jitter, dist=dist, n=mesh, projection=projection) 
    1044786        draw_scattering(calculator, axes, view, jitter, dist=dist) 
    1045  
    1046787        plt.gcf().canvas.draw() 
    1047788 
     
    1059800    ## go interactive 
    1060801    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] 
    1320802 
    1321803def main(): 
     
    1329811    parser.add_argument('-s', '--size', type=str, default='10,40,100', 
    1330812                        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') 
    1335813    parser.add_argument('-d', '--distribution', choices=DISTRIBUTIONS, 
    1336814                        default=DISTRIBUTIONS[0], 
     
    1341819                        help='oriented shape') 
    1342820    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, 
    1347823        mesh=opts.mesh, dist=opts.distribution, 
    1348824        projection=opts.projection) 
Note: See TracChangeset for help on using the changeset viewer.