[a5b8477] | 1 | from __future__ import print_function |
---|
| 2 | |
---|
[70bbb74] | 3 | import sys, os, math, re |
---|
| 4 | import numpy as np |
---|
[b866abf] | 5 | import matplotlib |
---|
| 6 | matplotlib.use('Agg') |
---|
[70bbb74] | 7 | import matplotlib.pyplot as plt |
---|
[91c5fdc] | 8 | sys.path.insert(0, os.path.abspath('..')) |
---|
| 9 | from sasmodels import generate, core |
---|
[745b7bb] | 10 | from sasmodels.direct_model import DirectModel, call_profile |
---|
[70bbb74] | 11 | from sasmodels.data import empty_data1D, empty_data2D |
---|
| 12 | |
---|
[a5b8477] | 13 | try: |
---|
| 14 | from typing import Dict, Any |
---|
| 15 | except ImportError: |
---|
| 16 | pass |
---|
| 17 | else: |
---|
| 18 | from matplotlib.axes import Axes |
---|
| 19 | from sasmodels.kernel import KernelModel |
---|
| 20 | from sasmodels.modelinfo import ModelInfo |
---|
[c094758] | 21 | |
---|
[745b7bb] | 22 | |
---|
[c094758] | 23 | def plot_1d(model, opts, ax): |
---|
[a5b8477] | 24 | # type: (KernelModel, Dict[str, Any], Axes) -> None |
---|
| 25 | """ |
---|
| 26 | Create a 1-D image. |
---|
| 27 | """ |
---|
[c094758] | 28 | q_min, q_max, nq = opts['q_min'], opts['q_max'], opts['nq'] |
---|
| 29 | q_min = math.log10(q_min) |
---|
| 30 | q_max = math.log10(q_max) |
---|
| 31 | q = np.logspace(q_min, q_max, nq) |
---|
| 32 | data = empty_data1D(q) |
---|
| 33 | calculator = DirectModel(data, model) |
---|
| 34 | Iq1D = calculator() |
---|
| 35 | |
---|
[a5b8477] | 36 | ax.plot(q, Iq1D, color='blue', lw=2, label=model.info.name) |
---|
[c094758] | 37 | ax.set_xlabel(r'$Q \/(\AA^{-1})$') |
---|
| 38 | ax.set_ylabel(r'$I(Q) \/(\mathrm{cm}^{-1})$') |
---|
| 39 | ax.set_xscale(opts['xscale']) |
---|
| 40 | ax.set_yscale(opts['yscale']) |
---|
| 41 | #ax.legend(loc='best') |
---|
| 42 | |
---|
| 43 | def plot_2d(model, opts, ax): |
---|
[a5b8477] | 44 | # type: (KernelModel, Dict[str, Any], Axes) -> None |
---|
| 45 | """ |
---|
| 46 | Create a 2-D image. |
---|
| 47 | """ |
---|
[c094758] | 48 | qx_max, nq2d = opts['qx_max'], opts['nq2d'] |
---|
[a5b8477] | 49 | q = np.linspace(-qx_max, qx_max, nq2d) # type: np.ndarray |
---|
[c094758] | 50 | data2d = empty_data2D(q, resolution=0.0) |
---|
| 51 | calculator = DirectModel(data2d, model) |
---|
| 52 | Iq2D = calculator() #background=0) |
---|
| 53 | Iq2D = Iq2D.reshape(nq2d, nq2d) |
---|
| 54 | if opts['zscale'] == 'log': |
---|
| 55 | Iq2D = np.log(np.clip(Iq2D, opts['vmin'], np.inf)) |
---|
[044a9c1] | 56 | ax.imshow(Iq2D, interpolation='nearest', aspect=1, origin='lower', |
---|
[a5b8477] | 57 | extent=[-qx_max, qx_max, -qx_max, qx_max], cmap=opts['colormap']) |
---|
[c094758] | 58 | ax.set_xlabel(r'$Q_x \/(\AA^{-1})$') |
---|
| 59 | ax.set_ylabel(r'$Q_y \/(\AA^{-1})$') |
---|
| 60 | |
---|
[745b7bb] | 61 | def plot_profile_inset(model_info, ax): |
---|
| 62 | p = ax.get_position() |
---|
| 63 | width, height = 0.4*(p.x1-p.x0), 0.4*(p.y1-p.y0) |
---|
| 64 | left, bottom = p.x1-width, p.y1-height |
---|
| 65 | inset = plt.gcf().add_axes([left, bottom, width, height]) |
---|
| 66 | x, y, labels = call_profile(model_info) |
---|
| 67 | inset.plot(x, y, '-') |
---|
| 68 | inset.locator_params(nbins=4) |
---|
| 69 | #inset.set_xlabel(labels[0]) |
---|
| 70 | #inset.set_ylabel(labels[1]) |
---|
| 71 | inset.text(0.99, 0.99, "profile", |
---|
| 72 | horizontalalignment="right", |
---|
| 73 | verticalalignment="top", |
---|
| 74 | transform=inset.transAxes) |
---|
| 75 | |
---|
[a5b8477] | 76 | def figfile(model_info): |
---|
| 77 | # type: (ModelInfo) -> str |
---|
| 78 | return model_info.id + '_autogenfig.png' |
---|
| 79 | |
---|
| 80 | def make_figure(model_info, opts): |
---|
| 81 | # type: (ModelInfo, Dict[str, Any]) -> None |
---|
| 82 | """ |
---|
| 83 | Generate the figure file to include in the docs. |
---|
| 84 | """ |
---|
| 85 | model = core.build_model(model_info) |
---|
| 86 | |
---|
| 87 | fig_height = 3.0 # in |
---|
| 88 | fig_left = 0.6 # in |
---|
| 89 | fig_right = 0.5 # in |
---|
| 90 | fig_top = 0.6*0.25 # in |
---|
| 91 | fig_bottom = 0.6*0.75 |
---|
| 92 | if model_info.parameters.has_2d: |
---|
| 93 | plot_height = fig_height - (fig_top+fig_bottom) |
---|
| 94 | plot_width = plot_height |
---|
| 95 | fig_width = 2*(plot_width + fig_left + fig_right) |
---|
| 96 | aspect = (fig_width, fig_height) |
---|
| 97 | ratio = aspect[0]/aspect[1] |
---|
| 98 | ax_left = fig_left/fig_width |
---|
| 99 | ax_bottom = fig_bottom/fig_height |
---|
| 100 | ax_height = plot_height/fig_height |
---|
| 101 | ax_width = ax_height/ratio # square axes |
---|
| 102 | fig = plt.figure(figsize=aspect) |
---|
| 103 | ax2d = fig.add_axes([0.5+ax_left, ax_bottom, ax_width, ax_height]) |
---|
| 104 | plot_2d(model, opts, ax2d) |
---|
| 105 | ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height]) |
---|
| 106 | plot_1d(model, opts, ax1d) |
---|
| 107 | #ax.set_aspect('square') |
---|
| 108 | else: |
---|
| 109 | plot_height = fig_height - (fig_top+fig_bottom) |
---|
| 110 | plot_width = (1+np.sqrt(5))/2*fig_height |
---|
| 111 | fig_width = plot_width + fig_left + fig_right |
---|
| 112 | ax_left = fig_left/fig_width |
---|
| 113 | ax_bottom = fig_bottom/fig_height |
---|
| 114 | ax_width = plot_width/fig_width |
---|
| 115 | ax_height = plot_height/fig_height |
---|
| 116 | aspect = (fig_width, fig_height) |
---|
| 117 | fig = plt.figure(figsize=aspect) |
---|
| 118 | ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height]) |
---|
| 119 | plot_1d(model, opts, ax1d) |
---|
| 120 | |
---|
[745b7bb] | 121 | if model_info.profile: |
---|
| 122 | plot_profile_inset(model_info, ax1d) |
---|
| 123 | |
---|
[a5b8477] | 124 | # Save image in model/img |
---|
| 125 | path = os.path.join('model', 'img', figfile(model_info)) |
---|
| 126 | plt.savefig(path, bbox_inches='tight') |
---|
| 127 | #print("figure saved in",path) |
---|
| 128 | |
---|
| 129 | def gen_docs(model_info): |
---|
| 130 | # type: (ModelInfo) -> None |
---|
| 131 | """ |
---|
| 132 | Generate the doc string with the figure inserted before the references. |
---|
| 133 | """ |
---|
| 134 | |
---|
| 135 | # Load the doc string from the module definition file and store it in rst |
---|
| 136 | docstr = generate.make_doc(model_info) |
---|
| 137 | |
---|
| 138 | # Auto caption for figure |
---|
| 139 | captionstr = '\n' |
---|
| 140 | captionstr += '.. figure:: img/' + figfile(model_info) + '\n' |
---|
| 141 | captionstr += '\n' |
---|
| 142 | if model_info.parameters.has_2d: |
---|
| 143 | captionstr += ' 1D and 2D plots corresponding to the default parameters of the model.\n' |
---|
| 144 | else: |
---|
| 145 | captionstr += ' 1D plot corresponding to the default parameters of the model.\n' |
---|
| 146 | captionstr += '\n' |
---|
| 147 | |
---|
| 148 | # Add figure reference and caption to documentation (at end, before References) |
---|
| 149 | pattern = '\*\*REFERENCE' |
---|
| 150 | match = re.search(pattern, docstr.upper()) |
---|
| 151 | |
---|
| 152 | if match: |
---|
| 153 | docstr1 = docstr[:match.start()] |
---|
| 154 | docstr2 = docstr[match.start():] |
---|
| 155 | docstr = docstr1 + captionstr + docstr2 |
---|
| 156 | else: |
---|
| 157 | print('------------------------------------------------------------------') |
---|
| 158 | print('References NOT FOUND for model: ', model_info.id) |
---|
| 159 | print('------------------------------------------------------------------') |
---|
| 160 | docstr += captionstr |
---|
| 161 | |
---|
| 162 | open(sys.argv[2],'w').write(docstr) |
---|
| 163 | |
---|
| 164 | def process_model(path): |
---|
| 165 | # type: (str) -> None |
---|
| 166 | """ |
---|
| 167 | Generate doc file and image file for the given model definition file. |
---|
| 168 | """ |
---|
| 169 | |
---|
| 170 | # Load the model file |
---|
| 171 | model_name = os.path.basename(path)[:-3] |
---|
| 172 | model_info = core.load_model_info(model_name) |
---|
| 173 | |
---|
| 174 | # Plotting ranges and options |
---|
| 175 | opts = { |
---|
| 176 | 'xscale' : 'log', |
---|
| 177 | 'yscale' : 'log' if not model_info.structure_factor else 'linear', |
---|
| 178 | 'zscale' : 'log' if not model_info.structure_factor else 'linear', |
---|
| 179 | 'q_min' : 0.001, |
---|
| 180 | 'q_max' : 1.0, |
---|
| 181 | 'nq' : 1000, |
---|
| 182 | 'nq2d' : 1000, |
---|
| 183 | 'vmin' : 1e-3, # floor for the 2D data results |
---|
| 184 | 'qx_max' : 0.5, |
---|
| 185 | #'colormap' : 'gist_ncar', |
---|
| 186 | 'colormap' : 'nipy_spectral', |
---|
| 187 | #'colormap' : 'jet', |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | # Generate the RST file and the figure. Order doesn't matter. |
---|
| 191 | gen_docs(model_info) |
---|
| 192 | make_figure(model_info, opts) |
---|
| 193 | |
---|
| 194 | if __name__ == "__main__": |
---|
| 195 | process_model(sys.argv[1]) |
---|