source: trunk/GSASIIscriptable.py @ 3818

Last change on this file since 3818 was 3815, checked in by toby, 6 years ago

Add recalibrate & more image load/set routines

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 153.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2019-02-11 17:26:52 +0000 (Mon, 11 Feb 2019) $
5# $Author: vondreele $
6# $Revision: 3815 $
7# $URL: trunk/GSASIIscriptable.py $
8# $Id: GSASIIscriptable.py 3815 2019-02-11 17:26:52Z vondreele $
9########### SVN repository information ###################
10#
11# TODO: integrate 2d data based on a model
12#
13"""
14*GSASIIscriptable: Scripting Interface*
15=======================================
16
17Routines for reading, writing, modifying and creating GSAS-II project (.gpx) files.
18This file specifies several wrapper classes around GSAS-II data representations.
19They all inherit from :class:`G2ObjectWrapper`. The chief class is :class:`G2Project`,
20which represents an entire GSAS-II project and provides several methods to access
21phases, powder histograms, and execute Rietveld refinements. These routines can be
22accessed in two ways:
23the :ref:`CommandlineInterface` provides access a number of features without writing
24Python scripts of from the :ref:`API`, which allows much more versatile access from
25within Python.
26
27
28=====================
29Overview
30=====================
31The most commonly used routines are:
32
33:class:`G2Project`
34   Always needed: used to create a new project (.gpx) file or read in an existing one.
35
36:meth:`G2Project.add_powder_histogram`
37   Used to read in powder diffraction data to a project file.
38
39:meth:`G2Project.add_simulated_powder_histogram`
40   Defines a "dummy" powder diffraction data that will be simulated after a refinement step.
41
42:meth:`G2Project.save`
43   Writes the current project to disk.
44
45:meth:`G2Project.do_refinements`
46   This is passed a list of dictionaries, where each dict defines a refinement step.
47   Passing a list with a single empty dict initiates a refinement with the current
48   parameters and flags.
49   
50:meth:`G2Project.set_refinement`
51   This is passed a single dict which is used to set parameters and flags.
52   These actions can be performed also in :meth:`G2Project.do_refinements`.
53
54Refinement dicts
55   A refinement dict sets up a single refinement step
56   (as used in :meth:`G2Project.do_refinements` and described in
57   :ref:`Project_dicts`).
58   It specifies parameter & refinement flag changes, which are usually followed by a refinement and
59   optionally by calls to locally-defined Python functions. The keys used in these dicts are
60   defined in the :ref:`Refinement_recipe` table, below.
61
62There are several ways to set parameters project using different level objects, as is
63are described in sections below, but the simplest way to access parameters and flags
64in a project is to use the above routines.
65
66=====================
67Refinement parameters
68=====================
69The most complex part of scripting GSAS-II comes in setting up the input to control refinements, which is
70described here.
71
72.. _Project_dicts:
73
74-----------------------------
75Project-level Parameter Dict
76-----------------------------
77
78As noted below (:ref:`Refinement_parameters_kinds`), there are three types of refinement parameters,
79which can be accessed individually by the objects that encapsulate individual phases and histograms
80but it will be usually simplest to create a composite dictionary
81that is used at the project-level. A dict is created with keys
82"set" and "clear" that can be supplied to :meth:`G2Project.set_refinement`
83(or :meth:`G2Project.do_refinements`, see :ref:`Refinement_recipe` below) that will
84determine parameter values and will determine which parameters will be refined.
85
86The specific keys and subkeys that can be used are defined in tables
87:ref:`Histogram_parameters_table`, :ref:`Phase_parameters_table` and :ref:`HAP_parameters_table`.
88
89Note that optionally a list of histograms and/or phases can be supplied in the call to
90:meth:`G2Project.set_refinement`, but if not specified, the default is to use all defined
91phases and histograms.
92
93As an example:
94
95.. code-block::  python
96
97    pardict = {'set': { 'Limits': [0.8, 12.0],
98                       'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
99                       'Background': {'type': 'chebyschev', 'refine': True}},
100              'clear': {'Instrument Parameters': ['U', 'V', 'W']}}
101    my_project.set_refinement(pardict)
102   
103.. _Refinement_recipe:
104   
105------------------------
106Refinement recipe
107------------------------
108Building on the :ref:`Project_dicts`,
109it is possible to specify a sequence of refinement actions as a list of
110these dicts and supplying this list
111as an argument to :meth:`G2Project.do_refinements`.
112
113As an example, this code performs the same actions as in the example in the section above:
114
115.. code-block::  python
116   
117    pardict = {'set': { 'Limits': [0.8, 12.0],
118                       'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
119                       'Background': {'type': 'chebyschev', 'refine': True}},
120              'clear': {'Instrument Parameters': ['U', 'V', 'W']}}
121    my_project.do_refinements([pardict])
122
123However, in addition to setting a number of parameters, this example will perform a refinement as well.
124If more than one dict is specified in the list, as is done in this example,
125two refinement steps will be performed:
126
127.. code-block::  python
128
129    my_project.do_refinements([pardict,pardict1])
130
131
132The keys defined in the following table
133may be used in a dict supplied to :meth:`G2Project.do_refinements`. Note that now keys ``histograms``
134and ``phases`` are used to limit actions to specific sets of parameters within the project.
135
136========== ============================================================================
137key         explanation
138========== ============================================================================
139set                    Specifies a dict with keys and subkeys as described in the
140                       :ref:`Refinement_parameters_fmt` section. Items listed here
141                       will be set to be refined.
142clear                  Specifies a dict, as above for set, except that parameters are
143                       cleared and thus will not be refined.
144once                   Specifies a dict as above for set, except that parameters are
145                       set for the next cycle of refinement and are cleared once the
146                       refinement step is completed.
147skip                   Normally, once parameters are processed with a set/clear/once
148                       action(s), a refinement is started. If skip is defined as True
149                       (or any other value) the refinement step is not performed.
150output                 If a file name is specified for output is will be used to save
151                       the current refinement.
152histograms             Should contain a list of histogram(s) to be used for the
153                       set/clear/once action(s) on :ref:`Histogram_parameters_table` or
154                       :ref:`HAP_parameters_table`. Note that this will be
155                       ignored for :ref:`Phase_parameters_table`. Histograms may be
156                       specified as a list of strings [('PWDR ...'),...], indices
157                       [0,1,2] or as list of objects [hist1, hist2].
158phases                 Should contain a list of phase(s) to be used for the
159                       set/clear/once action(s) on :ref:`Phase_parameters_table` or
160                       :ref:`HAP_parameters_table`. Note that this will be
161                       ignored for :ref:`Histogram_parameters_table`.
162                       Phases may be specified as a list of strings
163                       [('Phase name'),...], indices [0,1,2] or as list of objects
164                       [phase0, phase2].
165call                   Specifies a function to call after a refinement is completed.
166                       The value supplied can be the object (typically a function)
167                       that will be called or a string that will evaluate (in the
168                       namespace inside :meth:`G2Project.iter_refinements` where
169                       ``self`` references the project.)
170                       Nothing is called if this is not specified.
171callargs               Provides a list of arguments that will be passed to the function
172                       in call (if any). If call is defined and callargs is not, the
173                       current <tt>G2Project</tt> is passed as a single argument.
174========== ============================================================================
175
176An example that performs a series of refinement steps follows:
177
178.. code-block::  python
179
180    reflist = [
181            {"set": { "Limits": { "low": 0.7 },
182                      "Background": { "no. coeffs": 3,
183                                      "refine": True }}},
184            {"set": { "LeBail": True,
185                      "Cell": True }},
186            {"set": { "Sample Parameters": ["DisplaceX"]}},
187            {"set": { "Instrument Parameters": ["U", "V", "W", "X", "Y"]}},
188            {"set": { "Mustrain": { "type": "uniaxial",
189                                    "refine": "equatorial",
190                                    "direction": [0, 0, 1]}}},
191            {"set": { "Mustrain": { "type": "uniaxial",
192                                    "refine": "axial"}}},
193            {"clear": { "LeBail": True},
194             "set": { "Atoms": { "Mn": "X" }}},
195            {"set": { "Atoms": { "O1": "X", "O2": "X" }}},]
196    my_project.do_refinements(reflist)
197   
198
199In this example, a separate refinement step will be performed for each dict in the list (since
200"skip" is not included).
201Note that in the second from last refinement step, parameters are both set and cleared.
202   
203.. _Refinement_parameters_kinds:
204
205----------------------------
206Refinement parameter types
207----------------------------
208
209Note that parameters and refinement flags used in GSAS-II fall into three classes:
210
211    * **Histogram**: There will be a set of these for each dataset loaded into a
212      project file. The parameters available depend on the type of histogram
213      (Bragg-Brentano, Single-Crystal, TOF,...). Typical Histogram parameters
214      include the overall scale factor, background, instrument and sample parameters;
215      see the :ref:`Histogram_parameters_table` table for a list of the histogram
216      parameters where access has been provided.
217     
218    * **Phase**: There will be a set of these for each phase loaded into a
219      project file. While some parameters are found in all types of phases,
220      others are only found in certain types (modulated, magnetic, protein...).
221      Typical phase parameters include unit cell lengths and atomic positions; see the
222      :ref:`Phase_parameters_table` table for a list of the phase     
223      parameters where access has been provided.
224     
225    * **Histogram-and-phase** (HAP): There is a set of these for every histogram
226      that is associated with each phase, so that if there are ``N`` phases and ``M``
227      histograms, there can be ``N*M`` total sets of "HAP" parameters sets (fewer if all
228      histograms are not linked to all phases.) Typical HAP parameters include the
229      phase fractions, sample microstrain and crystallite size broadening terms,
230      hydrostatic strain perturbations of the unit cell and preferred orientation
231      values.
232      See the :ref:`HAP_parameters_table` table for the HAP parameters where access has
233      been provided.
234
235.. _Refinement_parameters_fmt:
236
237=================================
238Specifying Refinement Parameters
239=================================
240
241Refinement parameter values and flags to turn refinement on and off are specified within dictionaries,
242where the details of these dicts are organized depends on the
243type of parameter (see :ref:`Refinement_parameters_kinds`), with a different set
244of keys (as described below) for each of the three types of parameters.
245
246.. _Histogram_parameters_table:
247
248--------------------
249Histogram parameters
250--------------------
251
252This table describes the dictionaries supplied to :func:`G2PwdrData.set_refinements`
253and :func:`G2PwdrData.clear_refinements`. As an example,
254
255.. code-block::  python
256
257   hist.set_refinements({"Background": {"no.coeffs": 3, "refine": True},
258                         "Sample Parameters": ["Scale"],
259                         "Limits": [10000, 40000]})
260
261With :meth:`G2Project.do_refinements`, these parameters should be placed inside a dict with a key
262``set``, ``clear``, or ``once``. Values will be set for all histograms, unless the ``histograms``
263key is used to define specific histograms. As an example:
264
265.. code-block::  python
266
267  gsas_proj.do_refinements([
268      {'set': {
269          'Background': {'no.coeffs': 3, 'refine': True},
270          'Sample Parameters': ['Scale'],
271          'Limits': [10000, 40000]},
272      'histograms': [1,2]}
273                            ])
274
275Note that below in the Instrument Parameters section,
276related profile parameters (such as U and V) are grouped together but
277separated by commas to save space in the table.
278
279.. tabularcolumns:: |l|l|p{3.5in}|
280
281===================== ====================  =================================================
282key                   subkey                explanation
283===================== ====================  =================================================
284Limits                                      The range of 2-theta (degrees) or TOF (in
285                                            microsec) range of values to use. Can
286                                            be either a dictionary of 'low' and/or 'high',
287                                            or a list of 2 items [low, high]
288\                     low                   Sets the low limit
289\                     high                  Sets the high limit
290
291Sample Parameters                           Should be provided as a **list** of subkeys
292                                            to set or clear, e.g. ['DisplaceX', 'Scale']
293\                     Absorption
294\                     Contrast
295\                     DisplaceX             Sample displacement along the X direction
296\                     DisplaceY             Sample displacement along the Y direction
297\                     Scale                 Histogram Scale factor
298
299Background                                  Sample background. If value is a boolean,
300                                            the background's 'refine' parameter is set
301                                            to the given boolean. Usually should be a
302                                            dictionary with any of the following keys:
303\                     type                  The background model, e.g. 'chebyschev'
304\                     refine                The value of the refine flag, boolean
305\                     no. coeffs            Number of coefficients to use, integer
306\                     coeffs                List of floats, literal values for background
307\                     FixedPoints           List of (2-theta, intensity) values for fixed points
308\                     fit fixed points      If True, triggers a fit to the fixed points to
309                                            be calculated. It is calculated when this key is
310                                            detected, regardless of calls to refine.
311
312Instrument Parameters                       As in Sample Paramters, provide as a **list** of
313                                            subkeys to
314                                            set or clear, e.g. ['X', 'Y', 'Zero', 'SH/L']
315\                     U, V, W               Gaussian peak profile terms
316\                     X, Y, Z               Lorentzian peak profile terms
317\                     alpha, beta-0,        TOF profile terms
318                      beta-1, beta-q,
319\                     sig-0, sig-1,         TOF profile terms
320                      sig-2, sig-q
321\                     difA, difB, difC      TOF Calibration constants
322\                     Zero                  Zero shift
323\                     SH/L                  Finger-Cox-Jephcoat low-angle peak asymmetry
324\                     Polariz.              Polarization parameter
325\                     Lam                   Lambda, the incident wavelength
326===================== ====================  =================================================
327
328.. _Phase_parameters_table:
329
330----------------
331Phase parameters
332----------------
333
334This table describes the dictionaries supplied to :func:`G2Phase.set_refinements`
335and :func:`G2Phase.clear_refinements`. With :meth:`G2Project.do_refinements`,
336these parameters should be placed inside a dict with a key
337``set``, ``clear``, or ``once``. Values will be set for all phases, unless the ``phases``
338key is used to define specific phase(s).
339
340
341.. tabularcolumns:: |l|p{4.5in}|
342
343======= ==========================================================
344key                   explanation
345======= ==========================================================
346Cell                  Whether or not to refine the unit cell.
347Atoms                 Dictionary of atoms and refinement flags.
348                      Each key should be an atom label, e.g.
349                      'O3', 'Mn5', and each value should be
350                      a string defining what values to refine.
351                      Values can be any combination of 'F'
352                      for fractional occupancy, 'X' for position,
353                      and 'U' for Debye-Waller factor
354LeBail                Enables LeBail intensity extraction.
355======= ==========================================================
356
357
358.. _HAP_parameters_table:
359
360
361Histogram-and-phase parameters
362------------------------------
363
364This table describes the dictionaries supplied to :func:`G2Phase.set_HAP_refinements`
365and :func:`G2Phase.clear_HAP_refinements`. When supplied to
366:meth:`G2Project.do_refinements`, these parameters should be placed inside a dict with a key
367``set``, ``clear``, or ``once``. Values will be set for all histograms used in each phase,
368unless the ``histograms`` and ``phases`` keys are used to define specific phases and histograms.
369
370.. tabularcolumns:: |l|l|p{3.5in}|
371
372=============  ==========  ============================================================
373key             subkey                 explanation
374=============  ==========  ============================================================
375Babinet                                Should be a **list** of the following
376                                       subkeys. If not, assumes both
377                                       BabA and BabU
378\               BabA
379\               BabU
380Extinction                             Boolean, True to refine.
381HStrain                                Boolean, True to refine all appropriate
382                                       $D_ij$ terms.
383Mustrain
384\               type                   Mustrain model. One of 'isotropic',
385                                       'uniaxial', or 'generalized'. Should always
386                                       be specified.
387\              direction               For uniaxial only. A list of three
388                                       integers,
389                                       the [hkl] direction of the axis.
390\               refine                 Usually boolean, set to True to refine.
391                                       or False to clear.
392                                       For uniaxial model, can specify a value
393                                       of 'axial' or 'equatorial' to set that flag
394                                       to True or a single
395                                       boolean sets both axial and equatorial.
396Size                                   
397\               type                   Size broadening model. One of 'isotropic',
398                                       'uniaxial', or 'ellipsoid'. Should always
399                                       be specified.
400\              direction               For uniaxial only. A list of three
401                                       integers,
402                                       the [hkl] direction of the axis.
403\               refine                 Boolean, True to refine.
404\               value                  float, size value in microns
405Pref.Ori.                              Boolean, True to refine
406Show                                   Boolean, True to refine
407Use                                    Boolean, True to refine
408Scale                                  Phase fraction; Boolean, True to refine
409=============  ==========  ============================================================
410
411------------------------
412Histogram/Phase objects
413------------------------
414Each phase and powder histogram in a :class:`G2Project` object has an associated
415object. Parameters within each individual object can be turned on and off by calling
416:meth:`G2PwdrData.set_refinements` or :meth:`G2PwdrData.clear_refinements`
417for histogram parameters;
418:meth:`G2Phase.set_refinements` or :meth:`G2Phase.clear_refinements`
419for phase parameters; and :meth:`G2Phase.set_HAP_refinements` or
420:meth:`G2Phase.clear_HAP_refinements`. As an example, if some_histogram is a histogram object (of type :class:`G2PwdrData`), use this to set parameters in that histogram:
421
422.. code-block::  python
423
424    params = { 'Limits': [0.8, 12.0],
425               'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
426               'Background': {'type': 'chebyschev', 'refine': True}}
427    some_histogram.set_refinements(params)
428
429Likewise to turn refinement flags on, use code such as this:
430
431.. code-block::  python
432
433    params = { 'Instrument Parameters': ['U', 'V', 'W']}
434    some_histogram.set_refinements(params)
435
436and to turn these refinement flags, off use this (Note that the
437``.clear_refinements()`` methods will usually will turn off refinement even
438if a refinement parameter is set in the dict to True.):
439
440.. code-block::  python
441
442    params = { 'Instrument Parameters': ['U', 'V', 'W']}
443    some_histogram.clear_refinements(params)
444
445For phase parameters, use code such as this:
446   
447.. code-block::  python
448
449    params = { 'LeBail': True, 'Cell': True,
450               'Atoms': { 'Mn1': 'X',
451                          'O3': 'XU',
452                          'V4': 'FXU'}}
453    some_histogram.set_refinements(params)
454
455
456and here is an example for HAP parameters:
457
458.. code-block::  python
459
460    params = { 'Babinet': 'BabA',
461               'Extinction': True,
462               'Mustrain': { 'type': 'uniaxial',
463                             'direction': [0, 0, 1],
464                             'refine': True}}
465    some_phase.set_HAP_refinements(params)
466
467Note that the parameters must match the object type and method (phase vs. histogram vs. HAP).
468
469.. _CommandlineInterface:
470
471=======================================
472GSASIIscriptable Command-line Interface
473=======================================
474
475The routines described above are intended to be called from a Python script, but an
476alternate way to access some of the same functionality is to
477invoke the ``GSASIIscriptable.py`` script from
478the command line usually from within a shell script or batch file. This
479will usually be done with a command such as::
480
481       python <path/>GSASIIscriptable.py <subcommand> <file.gpx> <options>
482
483    The following subcommands are defined:
484
485        * create, see :func:`create`
486        * add, see :func:`add`
487        * dump, see :func:`dump`
488        * refine, see :func:`refine`
489        * seqrefine, see :func:`seqrefine`
490        * export, :func:`export`
491        * browse, see :func:`IPyBrowse`
492       
493Run::
494
495   python GSASIIscriptable.py --help
496
497to show the available subcommands, and inspect each subcommand with
498`python GSASIIscriptable.py <subcommand> --help` or see the documentation for each of the above routines.
499
500.. _JsonFormat:
501
502-------------------------
503Parameters in JSON files
504-------------------------
505
506The refine command requires two inputs: an existing GSAS-II project (.gpx) file and
507a JSON format file
508(see `Introducing JSON <http://json.org/>`_) that contains a single dict.
509This dict may have two keys:
510
511refinements:
512  This defines the a set of refinement steps in a JSON representation of a
513  :ref:`Refinement_recipe` list.
514
515code:
516  This optionally defines Python code that will be executed after the project is loaded,
517  but before the refinement is started. This can be used to execute Python code to change
518  parameters that are not accessible via a :ref:`Refinement_recipe` dict (note that the
519  project object is accessed with variable ``proj``) or to define code that will be called
520  later (see key ``call`` in the :ref:`Refinement_recipe` section.)
521   
522JSON website: `Introducing JSON <http://json.org/>`_.
523
524.. _API:
525
526============================================================
527GSASIIscriptable Application Layer (API)
528============================================================
529
530The large number of classes and modules in this module are described below.
531Most commonly a script will create a G2Project object using :class:`G2Project` and then
532perform actions such as
533adding a histogram (method :meth:`G2Project.add_powder_histogram`),
534adding a phase (method :meth:`G2Project.add_phase`),
535or setting parameters and performing a refinement
536(method :meth:`G2Project.do_refinements`).
537
538In some cases, it may be easier to use
539methods inside :class:`G2PwdrData` or :class:`G2Phase` or these objects
540may offer more options.
541
542---------------------------------------------------------------
543Complete Documentation: All classes and functions
544---------------------------------------------------------------
545"""
546from __future__ import division, print_function
547import argparse
548import os.path as ospath
549import datetime as dt
550import sys
551import platform
552if '2' in platform.python_version_tuple()[0]:
553    import cPickle
554    strtypes = (str,unicode)
555else:
556    import _pickle as cPickle
557    strtypes = (str,bytes)
558import imp
559import copy
560import os
561import random as ran
562
563import numpy.ma as ma
564import scipy.interpolate as si
565import numpy as np
566import scipy as sp
567
568import GSASIIpath
569GSASIIpath.SetBinaryPath(True)  # for now, this is needed before some of these modules can be imported
570import GSASIIobj as G2obj
571import GSASIIpwd as G2pwd
572import GSASIIstrMain as G2strMain
573#import GSASIIIO as G2IO
574import GSASIIstrIO as G2strIO
575import GSASIIspc as G2spc
576import GSASIIElem as G2elem
577import GSASIIfiles as G2fil
578import GSASIIimage as G2img
579
580# Delay imports to not slow down small scripts that don't need them
581Readers = {'Pwdr':[], 'Phase':[], 'Image':[]}
582'''Readers by reader type'''
583exportersByExtension = {}
584'''Specifies the list of extensions that are supported for Powder data export'''
585
586def LoadG2fil():
587    """Setup GSAS-II importers. Delay importing this module, it is slow"""
588    if len(Readers['Pwdr']) > 0: return
589    # initialize imports
590    Readers['Pwdr'] = G2fil.LoadImportRoutines("pwd", "Powder_Data")
591    Readers['Phase'] = G2fil.LoadImportRoutines("phase", "Phase")
592    Readers['Image'] = G2fil.LoadImportRoutines("img", "Image")
593
594    # initialize exports
595    for obj in exportersByExtension:
596        try:
597            obj.Writer
598        except AttributeError:
599            continue
600        for typ in obj.exporttype:
601            if typ not in exportersByExtension:
602                exportersByExtension[typ] = {obj.extension:obj}
603            else:
604                exportersByExtension[typ][obj.extension] = obj
605
606def LoadDictFromProjFile(ProjFile):
607    '''Read a GSAS-II project file and load items to dictionary
608   
609    :param str ProjFile: GSAS-II project (name.gpx) full file name
610    :returns: Project,nameList, where
611
612      * Project (dict) is a representation of gpx file following the GSAS-II tree structure
613        for each item: key = tree name (e.g. 'Controls','Restraints',etc.), data is dict
614        data dict = {'data':item data whch may be list, dict or None,'subitems':subdata (if any)}
615      * nameList (list) has names of main tree entries & subentries used to reconstruct project file
616
617    Example for fap.gpx::
618
619      Project = {                 #NB:dict order is not tree order
620        'Phases':{'data':None,'fap':{phase dict}},
621        'PWDR FAP.XRA Bank 1':{'data':[histogram data list],'Comments':comments,'Limits':limits, etc},
622        'Rigid bodies':{'data': {rigid body dict}},
623        'Covariance':{'data':{covariance data dict}},
624        'Controls':{'data':{controls data dict}},
625        'Notebook':{'data':[notebook list]},
626        'Restraints':{'data':{restraint data dict}},
627        'Constraints':{'data':{constraint data dict}}]
628        }
629      nameList = [                #NB: reproduces tree order
630        ['Notebook',],
631        ['Controls',],
632        ['Covariance',],
633        ['Constraints',],
634        ['Restraints',],
635        ['Rigid bodies',],
636        ['PWDR FAP.XRA Bank 1',
637             'Comments',
638             'Limits',
639             'Background',
640             'Instrument Parameters',
641             'Sample Parameters',
642             'Peak List',
643             'Index Peak List',
644             'Unit Cells List',
645             'Reflection Lists'],
646        ['Phases', 'fap']
647        ]
648    '''
649    # Let IOError be thrown if file does not exist
650    if not ospath.exists(ProjFile):
651        print ('\n*** Error attempt to open project file that does not exist: \n    {}'.
652                   format(ProjFile))
653        raise IOError('GPX file {} does not exist'.format(ProjFile))
654    try:
655        Project, nameList = G2strIO.GetFullGPX(ProjFile)
656    except Exception as msg:
657        raise IOError(msg)
658    return Project,nameList
659
660def SaveDictToProjFile(Project,nameList,ProjFile):
661    '''Save a GSAS-II project file from dictionary/nameList created by LoadDictFromProjFile
662
663    :param dict Project: representation of gpx file following the GSAS-II
664        tree structure as described for LoadDictFromProjFile
665    :param list nameList: names of main tree entries & subentries used to reconstruct project file
666    :param str ProjFile: full file name for output project.gpx file (including extension)
667    '''
668    file = open(ProjFile,'wb')
669    try:
670        for name in nameList:
671            data = []
672            item = Project[name[0]]
673            data.append([name[0],item['data']])
674            for item2 in name[1:]:
675                data.append([item2,item[item2]])
676            cPickle.dump(data,file,1)
677    finally:
678        file.close()
679
680# def ImportPowder(reader,filename):
681#     '''Use a reader to import a powder diffraction data file
682
683#     :param str reader: a scriptable reader
684#     :param str filename: full name of powder data file; can be "multi-Bank" data
685
686#     :returns: list rdlist: list of reader objects containing powder data, one for each
687#         "Bank" of data encountered in file. Items in reader object of interest are:
688
689#           * rd.comments: list of str: comments found on powder file
690#           * rd.dnames: list of str: data nammes suitable for use in GSASII data tree NB: duplicated in all rd entries in rdlist
691#           * rd.powderdata: list of numpy arrays: pos,int,wt,zeros,zeros,zeros as needed for a PWDR entry in  GSASII data tree.
692#     '''
693#     rdfile,rdpath,descr = imp.find_module(reader)
694#     rdclass = imp.load_module(reader,rdfile,rdpath,descr)
695#     rd = rdclass.GSAS_ReaderClass()
696#     if not rd.scriptable:
697#         print(u'**** ERROR: '+reader+u' is not a scriptable reader')
698#         return None
699#     rdlist = []
700#     if rd.ContentsValidator(filename):
701#         repeat = True
702#         rdbuffer = {} # create temporary storage for file reader
703#         block = 0
704#         while repeat: # loop if the reader asks for another pass on the file
705#             block += 1
706#             repeat = False
707#             rd.objname = ospath.basename(filename)
708#             flag = rd.Reader(filename,None,buffer=rdbuffer,blocknum=block,)
709#             if flag:
710#                 rdlist.append(copy.deepcopy(rd)) # save the result before it is written over
711#                 if rd.repeat:
712#                     repeat = True
713#         return rdlist
714#     print(rd.errors)
715#     return None
716
717def SetDefaultDData(dType,histoName,NShkl=0,NDij=0):
718    '''Create an initial Histogram dictionary
719
720    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
721    '''
722    if dType in ['SXC','SNC']:
723        return {'Histogram':histoName,'Show':False,'Scale':[1.0,True],
724            'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},
725            'Extinction':['Lorentzian','None', {'Tbar':0.1,'Cos2TM':0.955,
726            'Eg':[1.e-10,False],'Es':[1.e-10,False],'Ep':[1.e-10,False]}],
727            'Flack':[0.0,False]}
728    elif dType == 'SNT':
729        return {'Histogram':histoName,'Show':False,'Scale':[1.0,True],
730            'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},
731            'Extinction':['Lorentzian','None', {
732            'Eg':[1.e-10,False],'Es':[1.e-10,False],'Ep':[1.e-10,False]}]}
733    elif 'P' in dType:
734        return {'Histogram':histoName,'Show':False,'Scale':[1.0,False],
735            'Pref.Ori.':['MD',1.0,False,[0,0,1],0,{},[],0.1],
736            'Size':['isotropic',[1.,1.,1.],[False,False,False],[0,0,1],
737                [1.,1.,1.,0.,0.,0.],6*[False,]],
738            'Mustrain':['isotropic',[1000.0,1000.0,1.0],[False,False,False],[0,0,1],
739                NShkl*[0.01,],NShkl*[False,]],
740            'HStrain':[NDij*[0.0,],NDij*[False,]],
741            'Extinction':[0.0,False],'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]}}
742
743
744def PreSetup(data):
745    '''Create part of an initial (empty) phase dictionary
746
747    from GSASIIphsGUI.py, near end of UpdatePhaseData
748
749    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
750    '''
751    if 'RBModels' not in data:
752        data['RBModels'] = {}
753    if 'MCSA' not in data:
754        data['MCSA'] = {'Models':[{'Type':'MD','Coef':[1.0,False,[.8,1.2],],'axis':[0,0,1]}],'Results':[],'AtInfo':{}}
755    if 'dict' in str(type(data['MCSA']['Results'])):
756        data['MCSA']['Results'] = []
757    if 'Modulated' not in data['General']:
758        data['General']['Modulated'] = False
759#    if 'modulated' in data['General']['Type']:
760#        data['General']['Modulated'] = True
761#        data['General']['Type'] = 'nuclear'
762
763
764def SetupGeneral(data, dirname):
765    """Helps initialize phase data.
766
767    From GSASIIphsGui.py, function of the same name. Minor changes for imports etc.
768
769    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
770    """
771    mapDefault = {'MapType':'','RefList':'','Resolution':0.5,'Show bonds':True,
772                'rho':[],'rhoMax':0.,'mapSize':10.0,'cutOff':50.,'Flip':False}
773    generalData = data['General']
774    atomData = data['Atoms']
775    generalData['AtomTypes'] = []
776    generalData['Isotopes'] = {}
777
778    if 'Isotope' not in generalData:
779        generalData['Isotope'] = {}
780    if 'Data plot type' not in generalData:
781        generalData['Data plot type'] = 'Mustrain'
782    if 'POhkl' not in generalData:
783        generalData['POhkl'] = [0,0,1]
784    if 'Map' not in generalData:
785        generalData['Map'] = mapDefault.copy()
786    if 'Flip' not in generalData:
787        generalData['Flip'] = {'RefList':'','Resolution':0.5,'Norm element':'None',
788            'k-factor':0.1,'k-Max':20.,}
789    if 'testHKL' not in generalData['Flip']:
790        generalData['Flip']['testHKL'] = [[0,0,2],[2,0,0],[1,1,1],[0,2,0],[1,2,3]]
791    if 'doPawley' not in generalData:
792        generalData['doPawley'] = False     #ToDo: change to ''
793    if 'Pawley dmin' not in generalData:
794        generalData['Pawley dmin'] = 1.0
795    if 'Pawley neg wt' not in generalData:
796        generalData['Pawley neg wt'] = 0.0
797    if 'Algolrithm' in generalData.get('MCSA controls',{}) or \
798        'MCSA controls' not in generalData:
799        generalData['MCSA controls'] = {'Data source':'','Annealing':[50.,0.001,50],
800        'dmin':2.0,'Algorithm':'log','Jump coeff':[0.95,0.5],'boltzmann':1.0,
801        'fast parms':[1.0,1.0,1.0],'log slope':0.9,'Cycles':1,'Results':[],'newDmin':True}
802    if 'AtomPtrs' not in generalData:
803        generalData['AtomPtrs'] = [3,1,7,9]
804        if generalData['Type'] == 'macromolecular':
805            generalData['AtomPtrs'] = [6,4,10,12]
806        elif generalData['Type'] == 'magnetic':
807            generalData['AtomPtrs'] = [3,1,10,12]
808    if generalData['Modulated']:
809        generalData['Type'] = 'nuclear'
810        if 'Super' not in generalData:
811            generalData['Super'] = 1
812            generalData['SuperVec'] = [[0,0,.1],False,4]
813            generalData['SSGData'] = {}
814        if '4DmapData' not in generalData:
815            generalData['4DmapData'] = mapDefault.copy()
816            generalData['4DmapData'].update({'MapType':'Fobs'})
817    if 'Modulated' not in generalData:
818        generalData['Modulated'] = False
819    if 'HydIds' not in generalData:
820        generalData['HydIds'] = {}
821    cx,ct,cs,cia = generalData['AtomPtrs']
822    generalData['NoAtoms'] = {}
823    generalData['BondRadii'] = []
824    generalData['AngleRadii'] = []
825    generalData['vdWRadii'] = []
826    generalData['AtomMass'] = []
827    generalData['Color'] = []
828    if generalData['Type'] == 'magnetic':
829        generalData['MagDmin'] = generalData.get('MagDmin',1.0)
830        landeg = generalData.get('Lande g',[])
831    generalData['Mydir'] = dirname
832    badList = {}
833    for iat,atom in enumerate(atomData):
834        atom[ct] = atom[ct].lower().capitalize()              #force to standard form
835        if generalData['AtomTypes'].count(atom[ct]):
836            generalData['NoAtoms'][atom[ct]] += atom[cx+3]*float(atom[cs+1])
837        elif atom[ct] != 'UNK':
838            Info = G2elem.GetAtomInfo(atom[ct])
839            if not Info:
840                if atom[ct] not in badList:
841                    badList[atom[ct]] = 0
842                badList[atom[ct]] += 1
843                atom[ct] = 'UNK'
844                continue
845            atom[ct] = Info['Symbol'] # N.B. symbol might be changed by GetAtomInfo
846            generalData['AtomTypes'].append(atom[ct])
847            generalData['Z'] = Info['Z']
848            generalData['Isotopes'][atom[ct]] = Info['Isotopes']
849            generalData['BondRadii'].append(Info['Drad'])
850            generalData['AngleRadii'].append(Info['Arad'])
851            generalData['vdWRadii'].append(Info['Vdrad'])
852            if atom[ct] in generalData['Isotope']:
853                if generalData['Isotope'][atom[ct]] not in generalData['Isotopes'][atom[ct]]:
854                    isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
855                    generalData['Isotope'][atom[ct]] = isotope
856                generalData['AtomMass'].append(Info['Isotopes'][generalData['Isotope'][atom[ct]]]['Mass'])
857            else:
858                generalData['Isotope'][atom[ct]] = 'Nat. Abund.'
859                if 'Nat. Abund.' not in generalData['Isotopes'][atom[ct]]:
860                    isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
861                    generalData['Isotope'][atom[ct]] = isotope
862                generalData['AtomMass'].append(Info['Mass'])
863            generalData['NoAtoms'][atom[ct]] = atom[cx+3]*float(atom[cs+1])
864            generalData['Color'].append(Info['Color'])
865            if generalData['Type'] == 'magnetic':
866                if len(landeg) < len(generalData['AtomTypes']):
867                    landeg.append(2.0)
868    if generalData['Type'] == 'magnetic':
869        generalData['Lande g'] = landeg[:len(generalData['AtomTypes'])]
870
871    if badList:
872        msg = 'Warning: element symbol(s) not found:'
873        for key in badList:
874            msg += '\n\t' + key
875            if badList[key] > 1:
876                msg += ' (' + str(badList[key]) + ' times)'
877        raise G2ScriptException("Phase error:\n" + msg)
878        # wx.MessageBox(msg,caption='Element symbol error')
879    F000X = 0.
880    F000N = 0.
881    for i,elem in enumerate(generalData['AtomTypes']):
882        F000X += generalData['NoAtoms'][elem]*generalData['Z']
883        isotope = generalData['Isotope'][elem]
884        F000N += generalData['NoAtoms'][elem]*generalData['Isotopes'][elem][isotope]['SL'][0]
885    generalData['F000X'] = F000X
886    generalData['F000N'] = F000N
887    import GSASIImath as G2mth
888    generalData['Mass'] = G2mth.getMass(generalData)
889
890
891def make_empty_project(author=None, filename=None):
892    """Creates an dictionary in the style of GSASIIscriptable, for an empty
893    project.
894
895    If no author name or filename is supplied, 'no name' and
896    <current dir>/test_output.gpx are used , respectively.
897
898    Returns: project dictionary, name list
899
900    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
901    """
902    if not filename:
903        filename = 'test_output.gpx'
904    filename = os.path.abspath(filename)
905    gsasii_version = str(GSASIIpath.GetVersionNumber())
906    LoadG2fil()
907    import matplotlib as mpl
908    python_library_versions = G2fil.get_python_versions([mpl, np, sp])
909
910    controls_data = dict(G2obj.DefaultControls)
911    controls_data['LastSavedAs'] = filename
912    controls_data['LastSavedUsing'] = gsasii_version
913    controls_data['PythonVersions'] = python_library_versions
914    if author:
915        controls_data['Author'] = author
916
917    output = {'Constraints': {'data': {'HAP': [], 'Hist': [], 'Phase': [],
918                                       'Global': []}},
919              'Controls': {'data': controls_data},
920              u'Covariance': {'data': {}},
921              u'Notebook': {'data': ['']},
922              u'Restraints': {'data': {}},
923              u'Rigid bodies': {'data': {'RBIds': {'Residue': [], 'Vector': []},
924                                'Residue': {'AtInfo': {}},
925                                'Vector':  {'AtInfo': {}}}}}
926
927    names = [[u'Notebook'], [u'Controls'], [u'Covariance'],
928             [u'Constraints'], [u'Restraints'], [u'Rigid bodies']]
929
930    return output, names
931
932
933class G2ImportException(Exception):
934    pass
935
936class G2ScriptException(Exception):
937    pass
938
939def import_generic(filename, readerlist, fmthint=None, bank=None):
940    """Attempt to import a filename, using a list of reader objects.
941
942    Returns the first reader object which worked."""
943    # Translated from OnImportGeneric method in GSASII.py
944    primaryReaders, secondaryReaders = [], []
945    for reader in readerlist:
946        if fmthint is not None and fmthint not in reader.formatName: continue
947        flag = reader.ExtensionValidator(filename)
948        if flag is None:
949            secondaryReaders.append(reader)
950        elif flag:
951            primaryReaders.append(reader)
952    if not secondaryReaders and not primaryReaders:
953        raise G2ImportException("Could not read file: ", filename)
954
955    with open(filename, 'Ur') as fp:
956        rd_list = []
957
958        for rd in primaryReaders + secondaryReaders:
959            # Initialize reader
960            rd.selections = []
961            if bank is None:
962                rd.selections = []
963            else:
964                rd.selections = [bank-1]
965            rd.dnames = []
966            rd.ReInitialize()
967            # Rewind file
968            rd.errors = ""
969            if not rd.ContentsValidator(filename):
970                # Report error
971                print("Warning: File {} has a validation error, continuing".format(filename))
972            if len(rd.selections) > 1:
973                raise G2ImportException("File {} has {} banks. Specify which bank to read with databank param."
974                                .format(filename,len(rd.selections)))
975
976            block = 0
977            rdbuffer = {}
978            repeat = True
979            while repeat:
980                repeat = False
981                block += 1
982                rd.objname = os.path.basename(filename)
983                try:
984                    flag = rd.Reader(filename,buffer=rdbuffer, blocknum=block)
985                except:
986                    flag = False
987                if flag:
988                    # Omitting image loading special cases
989                    rd.readfilename = filename
990                    rd_list.append(copy.deepcopy(rd))
991                    repeat = rd.repeat
992                else:
993                    if GSASIIpath.GetConfigValue('debug'): print("DBG_{} Reader failed to read {}".format(rd.formatName,filename))
994            if rd_list:
995                if rd.warnings:
996                    print("Read warning by", rd.formatName, "reader:",
997                          rd.warnings, file=sys.stderr)
998                elif bank is None:
999                    print("{} read by Reader {}"
1000                              .format(filename,rd.formatName))
1001                else:
1002                    print("{} block # {} read by Reader {}"
1003                              .format(filename,bank,rd.formatName))
1004                return rd_list
1005    raise G2ImportException("No reader could read file: " + filename)
1006
1007
1008def load_iprms(instfile, reader, bank=None):
1009    """Loads instrument parameters from a file, and edits the
1010    given reader.
1011
1012    Returns a 2-tuple of (Iparm1, Iparm2) parameters
1013    """
1014    LoadG2fil()
1015    ext = os.path.splitext(instfile)[1]
1016
1017    if ext.lower() == '.instprm':
1018        # New GSAS File, load appropriate bank
1019        with open(instfile) as f:
1020            lines = f.readlines()
1021        if bank is None: 
1022            bank = reader.powderentry[2] 
1023        numbanks = reader.numbanks
1024        iparms = G2fil.ReadPowderInstprm(lines, bank, numbanks, reader)
1025        reader.instfile = instfile
1026        reader.instmsg = '{} (G2 fmt) bank {}'.format(instfile,bank)
1027        return iparms
1028    elif ext.lower() not in ('.prm', '.inst', '.ins'):
1029        raise ValueError('Expected .prm file, found: ', instfile)
1030
1031    # It's an old GSAS file, load appropriately
1032    Iparm = {}
1033    with open(instfile, 'Ur') as fp:
1034        for line in fp:
1035            if '#' in line:
1036                continue
1037            Iparm[line[:12]] = line[12:-1]
1038    ibanks = int(Iparm.get('INS   BANK  ', '1').strip())
1039    if bank is not None:
1040        # pull out requested bank # bank from the data, and change the bank to 1
1041        Iparm,IparmC = {},Iparm
1042        for key in IparmC:
1043            if 'INS' not in key[:3]: continue   #skip around rubbish lines in some old iparm
1044            if key[4:6] == "  ":
1045                Iparm[key] = IparmC[key]
1046            elif int(key[4:6].strip()) == bank:
1047                Iparm[key[:4]+' 1'+key[6:]] = IparmC[key]           
1048        reader.instbank = bank
1049    elif ibanks == 1:
1050        reader.instbank = 1
1051    else: 
1052        raise G2ImportException("Instrument parameter file has {} banks, select one with instbank param."
1053                                    .format(ibanks))
1054    reader.powderentry[2] = 1
1055    reader.instfile = instfile
1056    reader.instmsg = '{} bank {}'.format(instfile,reader.instbank)
1057    return G2fil.SetPowderInstParms(Iparm, reader)
1058
1059def load_pwd_from_reader(reader, instprm, existingnames=[],bank=None):
1060    """Loads powder data from a reader object, and assembles it into a GSASII data tree.
1061
1062    :returns: (name, tree) - 2-tuple of the histogram name (str), and data
1063
1064    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1065    """
1066    HistName = 'PWDR ' + G2obj.StripUnicode(reader.idstring, '_')
1067    HistName = G2obj.MakeUniqueLabel(HistName, existingnames)
1068
1069    try:
1070        Iparm1, Iparm2 = instprm
1071    except ValueError:
1072        Iparm1, Iparm2 = load_iprms(instprm, reader, bank=bank)
1073        print('Instrument parameters read:',reader.instmsg)
1074    Ymin = np.min(reader.powderdata[1])
1075    Ymax = np.max(reader.powderdata[1])
1076    valuesdict = {'wtFactor': 1.0,
1077                  'Dummy': False,
1078                  'ranId': ran.randint(0, sys.maxsize),
1079                  'Offset': [0.0, 0.0], 'delOffset': 0.02*Ymax,
1080                  'refOffset': -0.1*Ymax, 'refDelt': 0.1*Ymax,
1081                  'Yminmax': [Ymin, Ymax]}
1082    reader.Sample['ranId'] = valuesdict['ranId']
1083
1084    # Ending keys:
1085    # [u'Reflection Lists',
1086    #  u'Limits',
1087    #  'data',
1088    #  u'Index Peak List',
1089    #  u'Comments',
1090    #  u'Unit Cells List',
1091    #  u'Sample Parameters',
1092    #  u'Peak List',
1093    #  u'Background',
1094    #  u'Instrument Parameters']
1095    Tmin = np.min(reader.powderdata[0])
1096    Tmax = np.max(reader.powderdata[0])
1097
1098    default_background = [['chebyschev', False, 3, 1.0, 0.0, 0.0],
1099                          {'nDebye': 0, 'debyeTerms': [], 'nPeaks': 0, 'peaksList': []}]
1100
1101    output_dict = {u'Reflection Lists': {},
1102                   u'Limits': reader.pwdparms.get('Limits', [(Tmin, Tmax), [Tmin, Tmax]]),
1103                   u'data': [valuesdict, reader.powderdata, HistName],
1104                   u'Index Peak List': [[], []],
1105                   u'Comments': reader.comments,
1106                   u'Unit Cells List': [],
1107                   u'Sample Parameters': reader.Sample,
1108                   u'Peak List': {'peaks': [], 'sigDict': {}},
1109                   u'Background': reader.pwdparms.get('Background', default_background),
1110                   u'Instrument Parameters': [Iparm1, Iparm2],
1111                   }
1112
1113    names = [u'Comments',
1114             u'Limits',
1115             u'Background',
1116             u'Instrument Parameters',
1117             u'Sample Parameters',
1118             u'Peak List',
1119             u'Index Peak List',
1120             u'Unit Cells List',
1121             u'Reflection Lists']
1122
1123    # TODO controls?? GSASII.py:1664-7
1124
1125    return HistName, [HistName] + names, output_dict
1126
1127
1128def _deep_copy_into(from_, into):
1129    """Helper function for reloading .gpx file. See G2Project.reload()
1130
1131    :author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1132    """
1133    if isinstance(from_, dict) and isinstance(into, dict):
1134        combined_keys = set(from_.keys()).union(into.keys())
1135        for key in combined_keys:
1136            if key in from_ and key in into:
1137                both_dicts = (isinstance(from_[key], dict)
1138                              and isinstance(into[key], dict))
1139                both_lists = (isinstance(from_[key], list)
1140                              and isinstance(into[key], list))
1141                if both_dicts or both_lists:
1142                    _deep_copy_into(from_[key], into[key])
1143                else:
1144                    into[key] = from_[key]
1145            elif key in from_:
1146                into[key] = from_[key]
1147            else:  # key in into
1148                del into[key]
1149    elif isinstance(from_, list) and isinstance(into, list):
1150        if len(from_) == len(into):
1151            for i in range(len(from_)):
1152                both_dicts = (isinstance(from_[i], dict)
1153                              and isinstance(into[i], dict))
1154                both_lists = (isinstance(from_[i], list)
1155                              and isinstance(into[i], list))
1156                if both_dicts or both_lists:
1157                    _deep_copy_into(from_[i], into[i])
1158                else:
1159                    into[i] = from_[i]
1160        else:
1161            into[:] = from_
1162
1163def GetCorrImage(ImageReaderlist,proj,imageRef):
1164    '''Gets image & applies dark, background & flat background corrections.
1165    based on :func:`GSASIIimgGUI.GetImageZ`
1166
1167    :param list ImageReaderlist: list of Reader objects for images
1168    :param object ImageReaderlist: list of Reader objects for images
1169    :param imageRef: A reference to the desired image. Either the Image
1170      tree name (str), the image's index (int) or
1171      a image object (:class:`G2Image`)
1172
1173    :return: array sumImg: corrected image for background/dark/flat back
1174    '''
1175    ImgObj = proj.image(imageRef)
1176    Controls = ImgObj.data['Image Controls']
1177    formatName = Controls.get('formatName','')
1178    imagefile = ImgObj.data['data'][1]
1179    ImageTag = None # fix this for multiimage files
1180    sumImg = G2fil.RereadImageData(ImageReaderlist,imagefile,ImageTag=ImageTag,FormatName=formatName)
1181    if sumImg is None:
1182        return []
1183    darkImg = False
1184    if 'dark image' in Controls:
1185        darkImg,darkScale = Controls['dark image']
1186        if darkImg:
1187            dImgObj = proj.image(darkImg)
1188            formatName = dImgObj.data['Image Controls'].get('formatName','')
1189            imagefile = dImgObj.data['data'][1]
1190            ImageTag = None # fix this for multiimage files
1191            darkImg = G2fil.RereadImageData(ImageReaderlist,imagefile,ImageTag=ImageTag,FormatName=formatName)
1192            if darkImg is None:
1193                raise Exception('Error reading dark image {}'.format(imagefile))
1194            sumImg += np.array(darkImage*darkScale,dtype='int32')
1195    if 'background image' in Controls:
1196        backImg,backScale = Controls['background image']           
1197        if backImg:     #ignores any transmission effect in the background image
1198            bImgObj = proj.image(backImg)
1199            formatName = bImgObj.data['Image Controls'].get('formatName','')
1200            imagefile = bImgObj.data['data'][1]
1201            ImageTag = None # fix this for multiimage files
1202            backImg = G2fil.RereadImageData(ImageReaderlist,imagefile,ImageTag=ImageTag,FormatName=formatName)
1203            if backImage is None:
1204                raise Exception('Error reading background image {}'.format(imagefile))
1205            if darkImg:
1206                backImage += np.array(darkImage*darkScale/backScale,dtype='int32')
1207            else:
1208                sumImg += np.array(backImage*backScale,dtype='int32')
1209    if 'Gain map' in Controls:
1210        gainMap = Controls['Gain map']
1211        if gainMap:
1212            gImgObj = proj.image(gainMap)
1213            formatName = gImgObj.data['Image Controls'].get('formatName','')
1214            imagefile = gImgObj.data['data'][1]
1215            ImageTag = None # fix this for multiimage files
1216            GMimage = G2fil.RereadImageData(ImageReaderlist,imagefile,ImageTag=ImageTag,FormatName=formatName)
1217            if GMimage is None:
1218                raise Exception('Error reading Gain map image {}'.format(imagefile))
1219            sumImg = sumImg*GMimage/1000
1220    sumImg -= int(Controls.get('Flat Bkg',0))
1221    Imax = np.max(sumImg)
1222    Controls['range'] = [(0,Imax),[0,Imax]]
1223    return np.asarray(sumImg,dtype='int32')
1224
1225class G2ObjectWrapper(object):
1226    """Base class for all GSAS-II object wrappers.
1227
1228    The underlying GSAS-II format can be accessed as `wrapper.data`. A number
1229    of overrides are implemented so that the wrapper behaves like a dictionary.
1230
1231    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1232    """
1233    def __init__(self, datadict):
1234        self.data = datadict
1235
1236    def __getitem__(self, key):
1237        return self.data[key]
1238
1239    def __setitem__(self, key, value):
1240        self.data[key] = value
1241
1242    def __contains__(self, key):
1243        return key in self.data
1244
1245    def get(self, k, d=None):
1246        return self.data.get(k, d)
1247
1248    def keys(self):
1249        return self.data.keys()
1250
1251    def values(self):
1252        return self.data.values()
1253
1254    def items(self):
1255        return self.data.items()
1256
1257
1258class G2Project(G2ObjectWrapper):   
1259    """Represents an entire GSAS-II project.
1260
1261    :param str gpxfile: Existing .gpx file to be loaded. If nonexistent,
1262            creates an empty project.
1263    :param str author: Author's name (not yet implemented)
1264    :param str newgpx: The filename the project should be saved to in
1265            the future. If both newgpx and gpxfile are present, the project is
1266            loaded from the gpxfile, then when saved will be written to newgpx.
1267    :param str filename: Name to be used to save the project. Has same function as
1268            parameter newgpx (do not use both gpxfile and filename). Use of newgpx
1269            is preferred over filename.
1270
1271    There are two ways to initialize this object:
1272
1273    >>> # Load an existing project file
1274    >>> proj = G2Project('filename.gpx')
1275   
1276    >>> # Create a new project
1277    >>> proj = G2Project(newgpx='new_file.gpx')
1278   
1279    Histograms can be accessed easily.
1280
1281    >>> # By name
1282    >>> hist = proj.histogram('PWDR my-histogram-name')
1283   
1284    >>> # Or by index
1285    >>> hist = proj.histogram(0)
1286    >>> assert hist.id == 0
1287   
1288    >>> # Or by random id
1289    >>> assert hist == proj.histogram(hist.ranId)
1290
1291    Phases can be accessed the same way.
1292
1293    >>> phase = proj.phase('name of phase')
1294
1295    New data can also be loaded via :meth:`~G2Project.add_phase` and
1296    :meth:`~G2Project.add_powder_histogram`.
1297
1298    >>> hist = proj.add_powder_histogram('some_data_file.chi',
1299                                         'instrument_parameters.prm')
1300    >>> phase = proj.add_phase('my_phase.cif', histograms=[hist])
1301
1302    Parameters for Rietveld refinement can be turned on and off as well.
1303    See :meth:`~G2Project.set_refinement`, :meth:`~G2Project.clear_refinements`,
1304    :meth:`~G2Project.iter_refinements`, :meth:`~G2Project.do_refinements`.
1305    """
1306    def __init__(self, gpxfile=None, author=None, filename=None, newgpx=None):
1307        if filename is not None and newgpx is not None:
1308            raise G2ScriptException('Do not use filename and newgpx together')
1309        elif newgpx is not None:
1310            filename = newgpx
1311        if gpxfile is None:
1312            filename = os.path.abspath(os.path.expanduser(filename))
1313            self.filename = filename
1314            self.data, self.names = make_empty_project(author=author, filename=filename)
1315        elif isinstance(gpxfile, str): # TODO: create replacement for isinstance that checks if path exists
1316                                       # filename is valid, etc.
1317            if isinstance(filename, str): 
1318                self.filename = os.path.abspath(os.path.expanduser(filename)) # both are defined
1319            else: 
1320                self.filename = os.path.abspath(os.path.expanduser(gpxfile))
1321            # TODO set author
1322            self.data, self.names = LoadDictFromProjFile(gpxfile)
1323            self.update_ids()
1324        else:
1325            raise ValueError("Not sure what to do with gpxfile")
1326
1327    @classmethod
1328    def from_dict_and_names(cls, gpxdict, names, filename=None):
1329        """Creates a :class:`G2Project` directly from
1330        a dictionary and a list of names. If in doubt, do not use this.
1331
1332        :returns: a :class:`G2Project`
1333        """
1334        out = cls()
1335        if filename:
1336            filename = os.path.abspath(os.path.expanduser(filename))
1337            out.filename = filename
1338            gpxdict['Controls']['data']['LastSavedAs'] = filename
1339        else:
1340            try:
1341                out.filename = gpxdict['Controls']['data']['LastSavedAs']
1342            except KeyError:
1343                out.filename = None
1344        out.data = gpxdict
1345        out.names = names
1346
1347    def save(self, filename=None):
1348        """Saves the project, either to the current filename, or to a new file.
1349
1350        Updates self.filename if a new filename provided"""
1351        # TODO update LastSavedUsing ?
1352        if filename:
1353            filename = os.path.abspath(os.path.expanduser(filename))
1354            self.data['Controls']['data']['LastSavedAs'] = filename
1355            self.filename = filename
1356        elif not self.filename:
1357            raise AttributeError("No file name to save to")
1358        SaveDictToProjFile(self.data, self.names, self.filename)
1359
1360    def add_powder_histogram(self, datafile, iparams, phases=[], fmthint=None,
1361                                 databank=None, instbank=None):
1362        """Loads a powder data histogram into the project.
1363
1364        Automatically checks for an instrument parameter file, or one can be
1365        provided. Note that in unix fashion, "~" can be used to indicate the
1366        home directory (e.g. ~/G2data/data.fxye).
1367
1368        :param str datafile: The powder data file to read, a filename.
1369        :param str iparams: The instrument parameters file, a filename.
1370        :param list phases: Phases to link to the new histogram
1371        :param str fmthint: If specified, only importers where the format name
1372          (reader.formatName, as shown in Import menu) contains the
1373          supplied string will be tried as importers. If not specified, all
1374          importers consistent with the file extension will be tried
1375          (equivalent to "guess format" in menu).
1376        :param int databank: Specifies a dataset number to read, if file contains
1377          more than set of data. This should be 1 to read the first bank in
1378          the file (etc.) regardless of the number on the Bank line, etc.
1379          Default is None which means there should only be one dataset in the
1380          file.
1381        :param int instbank: Specifies an instrument parameter set to read, if
1382          the instrument parameter file contains more than set of parameters.
1383          This will match the INS # in an GSAS type file so it will typically
1384          be 1 to read the first parameter set in the file (etc.)
1385          Default is None which means there should only be one parameter set
1386          in the file.
1387
1388        :returns: A :class:`G2PwdrData` object representing
1389            the histogram
1390        """
1391        LoadG2fil()
1392        datafile = os.path.abspath(os.path.expanduser(datafile))
1393        iparams = os.path.abspath(os.path.expanduser(iparams))
1394        pwdrreaders = import_generic(datafile, Readers['Pwdr'],fmthint=fmthint,bank=databank)
1395        histname, new_names, pwdrdata = load_pwd_from_reader(
1396                                          pwdrreaders[0], iparams,
1397                                          [h.name for h in self.histograms()],bank=instbank)
1398        if histname in self.data:
1399            print("Warning - redefining histogram", histname)
1400        elif self.names[-1][0] == 'Phases':
1401            self.names.insert(-1, new_names)
1402        else:
1403            self.names.append(new_names)
1404        self.data[histname] = pwdrdata
1405        self.update_ids()
1406
1407        for phase in phases:
1408            phase = self.phase(phase)
1409            self.link_histogram_phase(histname, phase)
1410
1411        return self.histogram(histname)
1412
1413    def add_simulated_powder_histogram(self, histname, iparams, Tmin, Tmax, Tstep,
1414                                       wavelength=None, scale=None, phases=[]):
1415        """Loads a powder data histogram into the project.
1416
1417        Requires an instrument parameter file.
1418        Note that in unix fashion, "~" can be used to indicate the
1419        home directory (e.g. ~/G2data/data.prm). The instrument parameter file
1420        will determine if the histogram is x-ray, CW neutron, TOF, etc. as well
1421        as the instrument type.
1422
1423        :param str histname: A name for the histogram to be created.
1424        :param str iparams: The instrument parameters file, a filename.
1425        :param float Tmin: Minimum 2theta or TOF (ms) for dataset to be simulated
1426        :param float Tmax: Maximum 2theta or TOF (ms) for dataset to be simulated
1427        :param float Tstep: Step size in 2theta or TOF (ms) for dataset to be simulated       
1428        :param float wavelength: Wavelength for CW instruments, overriding the value
1429           in the instrument parameters file if specified.
1430        :param float scale: Histogram scale factor which multiplies the pattern. Note that
1431           simulated noise is added to the pattern, so that if the maximum intensity is
1432           small, the noise will mask the computed pattern. The scale
1433           needs to be a large number for CW neutrons.
1434           The default, None, provides a scale of 1 for x-rays and TOF; 10,000 for CW neutrons.
1435        :param list phases: Phases to link to the new histogram. Use proj.phases() to link to
1436           all defined phases.
1437
1438        :returns: A :class:`G2PwdrData` object representing the histogram
1439        """
1440        LoadG2fil()
1441        iparams = os.path.abspath(os.path.expanduser(iparams))
1442        if not os.path.exists(iparams):
1443            raise G2ScriptException("File does not exist:"+iparams)
1444        rd = G2obj.ImportPowderData( # Initialize a base class reader
1445            extensionlist=tuple(),
1446            strictExtension=False,
1447            formatName = 'Simulate dataset',
1448            longFormatName = 'Compute a simulated pattern')
1449        rd.powderentry[0] = '' # no filename
1450        rd.powderentry[2] = 1 # only one bank
1451        rd.comments.append('This is a dummy dataset for powder pattern simulation')
1452        #Iparm1, Iparm2 = load_iprms(iparams, rd)
1453        if Tmax < Tmin:
1454            Tmin,Tmax = Tmax,Tmin
1455        Tstep = abs(Tstep)
1456        if 'TOF' in rd.idstring:
1457                N = (np.log(Tmax)-np.log(Tmin))/Tstep
1458                x = np.exp((np.arange(0,N))*Tstep+np.log(Tmin*1000.))
1459                N = len(x)
1460        else:           
1461                N = int((Tmax-Tmin)/Tstep)+1
1462                x = np.linspace(Tmin,Tmax,N,True)
1463                N = len(x)
1464        if N < 3:
1465            raise G2ScriptException("Error: Range is too small or step is too large, <3 points")
1466        rd.powderdata = [
1467            np.array(x), # x-axis values
1468            np.zeros_like(x), # powder pattern intensities
1469            np.ones_like(x), # 1/sig(intensity)^2 values (weights)
1470            np.zeros_like(x), # calc. intensities (zero)
1471            np.zeros_like(x), # calc. background (zero)
1472            np.zeros_like(x), # obs-calc profiles
1473            ]
1474        Tmin = rd.powderdata[0][0]
1475        Tmax = rd.powderdata[0][-1]
1476        rd.idstring = histname
1477        histname, new_names, pwdrdata = load_pwd_from_reader(rd, iparams,
1478                                                            [h.name for h in self.histograms()])
1479        if histname in self.data:
1480            print("Warning - redefining histogram", histname)
1481        elif self.names[-1][0] == 'Phases':
1482            self.names.insert(-1, new_names)
1483        else:
1484            self.names.append(new_names)
1485        if scale is not None:
1486            pwdrdata['Sample Parameters']['Scale'][0] = scale
1487        elif pwdrdata['Instrument Parameters'][0]['Type'][0].startswith('PNC'):
1488            pwdrdata['Sample Parameters']['Scale'][0] = 10000.
1489        self.data[histname] = pwdrdata
1490        self.update_ids()
1491
1492        for phase in phases:
1493            phase = self.phase(phase)
1494            self.link_histogram_phase(histname, phase)
1495
1496        return self.histogram(histname)
1497   
1498    def add_phase(self, phasefile, phasename=None, histograms=[], fmthint=None):
1499        """Loads a phase into the project from a .cif file
1500
1501        :param str phasefile: The CIF file from which to import the phase.
1502        :param str phasename: The name of the new phase, or None for the default
1503        :param list histograms: The names of the histograms to associate with
1504            this phase. Use proj.histograms() to add to all histograms.
1505        :param str fmthint: If specified, only importers where the format name
1506          (reader.formatName, as shown in Import menu) contains the
1507          supplied string will be tried as importers. If not specified, all
1508          importers consistent with the file extension will be tried
1509          (equivalent to "guess format" in menu).
1510
1511        :returns: A :class:`G2Phase` object representing the
1512            new phase.
1513        """
1514        LoadG2fil()
1515        histograms = [self.histogram(h).name for h in histograms]
1516        phasefile = os.path.abspath(os.path.expanduser(phasefile))
1517
1518        # TODO handle multiple phases in a file
1519        phasereaders = import_generic(phasefile, Readers['Phase'], fmthint=fmthint)
1520        phasereader = phasereaders[0]
1521       
1522        phasename = phasename or phasereader.Phase['General']['Name']
1523        phaseNameList = [p.name for p in self.phases()]
1524        phasename = G2obj.MakeUniqueLabel(phasename, phaseNameList)
1525        phasereader.Phase['General']['Name'] = phasename
1526
1527        if 'Phases' not in self.data:
1528            self.data[u'Phases'] = { 'data': None }
1529        assert phasename not in self.data['Phases'], "phase names should be unique"
1530        self.data['Phases'][phasename] = phasereader.Phase
1531
1532        if phasereader.Constraints:
1533            Constraints = self.data['Constraints']
1534            for i in phasereader.Constraints:
1535                if isinstance(i, dict):
1536                    if '_Explain' not in Constraints:
1537                        Constraints['_Explain'] = {}
1538                    Constraints['_Explain'].update(i)
1539                else:
1540                    Constraints['Phase'].append(i)
1541
1542        data = self.data['Phases'][phasename]
1543        generalData = data['General']
1544        SGData = generalData['SGData']
1545        NShkl = len(G2spc.MustrainNames(SGData))
1546        NDij = len(G2spc.HStrainNames(SGData))
1547        Super = generalData.get('Super', 0)
1548        if Super:
1549            SuperVec = np.array(generalData['SuperVec'][0])
1550        else:
1551            SuperVec = []
1552        UseList = data['Histograms']
1553
1554        for hist in histograms:
1555            self.link_histogram_phase(hist, phasename)
1556
1557        for obj in self.names:
1558            if obj[0] == 'Phases':
1559                phasenames = obj
1560                break
1561        else:
1562            phasenames = [u'Phases']
1563            self.names.append(phasenames)
1564        phasenames.append(phasename)
1565
1566        # TODO should it be self.filename, not phasefile?
1567        SetupGeneral(data, os.path.dirname(phasefile))
1568        self.index_ids()
1569
1570        self.update_ids()
1571        return self.phase(phasename)
1572
1573    def link_histogram_phase(self, histogram, phase):
1574        """Associates a given histogram and phase.
1575
1576        .. seealso::
1577
1578            :meth:`G2Project.histogram`
1579            :meth:`G2Project.phase`"""
1580        hist = self.histogram(histogram)
1581        phase = self.phase(phase)
1582
1583        generalData = phase['General']
1584
1585        if hist.name.startswith('HKLF '):
1586            raise NotImplementedError("HKLF not yet supported")
1587        elif hist.name.startswith('PWDR '):
1588            hist['Reflection Lists'][generalData['Name']] = {}
1589            UseList = phase['Histograms']
1590            SGData = generalData['SGData']
1591            NShkl = len(G2spc.MustrainNames(SGData))
1592            NDij = len(G2spc.HStrainNames(SGData))
1593            UseList[hist.name] = SetDefaultDData('PWDR', hist.name, NShkl=NShkl, NDij=NDij)
1594            UseList[hist.name]['hId'] = hist.id
1595            for key, val in [('Use', True), ('LeBail', False),
1596                             ('newLeBail', True),
1597                             ('Babinet', {'BabA': [0.0, False],
1598                                          'BabU': [0.0, False]})]:
1599                if key not in UseList[hist.name]:
1600                    UseList[hist.name][key] = val
1601        else:
1602            raise RuntimeError("Unexpected histogram" + hist.name)
1603
1604
1605    def reload(self):
1606        """Reload self from self.filename"""
1607        data, names = LoadDictFromProjFile(self.filename)
1608        self.names = names
1609        # Need to deep copy the new data file data into the current tree,
1610        # so that any existing G2Phase, or G2PwdrData objects will still be
1611        # valid
1612        _deep_copy_into(from_=data, into=self.data)
1613
1614    def refine(self, newfile=None, printFile=None, makeBack=False):
1615        # TODO migrate to RefineCore
1616        # G2strMain.RefineCore(Controls,Histograms,Phases,restraintDict,rigidbodyDict,parmDict,varyList,
1617        #      calcControls,pawleyLookup,ifPrint,printFile,dlg)
1618        # index_ids will automatically save the project
1619        self.index_ids()
1620        # TODO G2strMain does not properly use printFile
1621        G2strMain.Refine(self.filename, makeBack=makeBack)
1622        # Reload yourself
1623        self.reload()
1624
1625    def histogram(self, histname):
1626        """Returns the histogram named histname, or None if it does not exist.
1627
1628        :param histname: The name of the histogram (str), or ranId or index.
1629        :returns: A :class:`G2PwdrData` object, or None if
1630            the histogram does not exist
1631
1632        .. seealso::
1633            :meth:`G2Project.histograms`
1634            :meth:`G2Project.phase`
1635            :meth:`G2Project.phases`
1636        """
1637        if isinstance(histname, G2PwdrData):
1638            if histname.proj == self:
1639                return histname
1640        if histname in self.data:
1641            return G2PwdrData(self.data[histname], self)
1642        try:
1643            # see if histname is an id or ranId
1644            histname = int(histname)
1645        except ValueError:
1646            return
1647
1648        for histogram in self.histograms():
1649            if histogram.id == histname or histogram.ranId == histname:
1650                return histogram
1651
1652    def histograms(self, typ=None):
1653        """Return a list of all histograms, as :class:`G2PwdrData` objects
1654
1655        For now this only finds Powder/Single Xtal histograms, since that is all that is
1656        currently implemented in this module.
1657
1658        :param ste typ: The prefix (type) the histogram such as 'PWDR '. If None
1659          (the default) all known histograms types are found.
1660        :returns: a list of objects
1661
1662        .. seealso::
1663            :meth:`G2Project.histogram`
1664            :meth:`G2Project.phase`
1665            :meth:`G2Project.phases`
1666        """
1667        output = []
1668        # loop through each tree entry. If it is more than one level (more than one item in the
1669        # list of names). then it must be a histogram, unless labeled Phases or Restraints
1670        if typ is None:
1671            for obj in self.names:
1672                if obj[0].startswith('PWDR ') or obj[0].startswith('HKLF '): 
1673                    output.append(self.histogram(obj[0]))
1674        else:
1675            for obj in self.names:
1676                if len(obj) > 1 and obj[0].startswith(typ): 
1677                    output.append(self.histogram(obj[0]))
1678        return output
1679
1680    def phase(self, phasename):
1681        """
1682        Gives an object representing the specified phase in this project.
1683
1684        :param str phasename: A reference to the desired phase. Either the phase
1685            name (str), the phase's ranId, the phase's index (both int) or
1686            a phase object (:class:`G2Phase`)
1687        :returns: A :class:`G2Phase` object
1688        :raises: KeyError
1689
1690        .. seealso::
1691            :meth:`G2Project.histograms`
1692            :meth:`G2Project.phase`
1693            :meth:`G2Project.phases`
1694            """
1695        if isinstance(phasename, G2Phase):
1696            if phasename.proj == self:
1697                return phasename
1698        phases = self.data['Phases']
1699        if phasename in phases:
1700            return G2Phase(phases[phasename], phasename, self)
1701
1702        try:
1703            # phasename should be phase index or ranId
1704            phasename = int(phasename)
1705        except ValueError:
1706            return
1707
1708        for phase in self.phases():
1709            if phase.id == phasename or phase.ranId == phasename:
1710                return phase
1711
1712    def phases(self):
1713        """
1714        Returns a list of all the phases in the project.
1715
1716        :returns: A list of :class:`G2Phase` objects
1717
1718        .. seealso::
1719            :meth:`G2Project.histogram`
1720            :meth:`G2Project.histograms`
1721            :meth:`G2Project.phase`
1722            """
1723        for obj in self.names:
1724            if obj[0] == 'Phases':
1725                return [self.phase(p) for p in obj[1:]]
1726        return []
1727
1728    def _images(self):
1729        """Returns a list of all the phases in the project.
1730        """
1731        return [i[0] for i in self.names if i[0].startswith('IMG ')]
1732   
1733    def image(self, imageRef):
1734        """
1735        Gives an object representing the specified image in this project.
1736
1737        :param str imageRef: A reference to the desired image. Either the Image
1738          tree name (str), the image's index (int) or
1739          a image object (:class:`G2Image`)
1740        :returns: A :class:`G2Image` object
1741        :raises: KeyError
1742
1743        .. seealso::
1744            :meth:`G2Project.images`
1745            """
1746        if isinstance(imageRef, G2Image):
1747            if imageRef.proj == self:
1748                return imageRef
1749            else:
1750                raise Exception("Image {} not in current selected project".format(imageRef.name))
1751        if imageRef in self._images():
1752            return G2Image(self.data[imageRef], imageRef, self)
1753
1754        try:
1755            # imageRef should be an index
1756            num = int(imageRef)
1757            imageRef = self._images()[num] 
1758            return G2Image(self.data[imageRef], imageRef, self)
1759        except ValueError:
1760            raise Exception("imageRef {} not an object, name or image index in current selected project"
1761                                .format(imageRef))
1762        except IndexError:
1763            raise Exception("imageRef {} out of range (max={}) in current selected project"
1764                                .format(imageRef,len(self._images())-1))
1765           
1766    def images(self):
1767        """
1768        Returns a list of all the images in the project.
1769
1770        :returns: A list of :class:`G2Image` objects
1771        """
1772        return [G2Image(self.data[i],i,self) for i in self._images()]
1773   
1774    def update_ids(self):
1775        """Makes sure all phases and histograms have proper hId and pId"""
1776        # Translated from GetUsedHistogramsAndPhasesfromTree,
1777        #   GSASIIdataGUI.py:4107
1778        for i, h in enumerate(self.histograms()):
1779            h.id = i
1780        for i, p in enumerate(self.phases()):
1781            p.id = i
1782
1783    def do_refinements(self, refinements, histogram='all', phase='all',
1784                       outputnames=None, makeBack=False):
1785        """Conducts one or a series of refinements according to the
1786           input provided in parameter refinements. This is a wrapper
1787           around :meth:`iter_refinements`
1788
1789        :param list refinements: A list of dictionaries specifiying changes to be made to
1790            parameters before refinements are conducted.
1791            See the :ref:`Refinement_recipe` section for how this is defined.
1792        :param str histogram: Name of histogram for refinements to be applied
1793            to, or 'all'; note that this can be overridden for each refinement
1794            step via a "histograms" entry in the dict.
1795        :param str phase: Name of phase for refinements to be applied to, or
1796            'all'; note that this can be overridden for each refinement
1797            step via a "phases" entry in the dict.
1798        :param list outputnames: Provides a list of project (.gpx) file names
1799            to use for each refinement step (specifying None skips the save step).
1800            See :meth:`save`.
1801            Note that this can be overridden using an "output" entry in the dict.
1802        :param bool makeBack: determines if a backup ).bckX.gpx) file is made
1803            before a refinement is performed. The default is False.
1804           
1805        To perform a single refinement without changing any parameters, use this
1806        call:
1807
1808        .. code-block::  python
1809       
1810            my_project.do_refinements([])
1811        """
1812       
1813        for proj in self.iter_refinements(refinements, histogram, phase,
1814                                          outputnames, makeBack):
1815            pass
1816        return self
1817
1818    def iter_refinements(self, refinements, histogram='all', phase='all',
1819                         outputnames=None, makeBack=False):
1820        """Conducts a series of refinements, iteratively. Stops after every
1821        refinement and yields this project, to allow error checking or
1822        logging of intermediate results. Parameter use is the same as for
1823        :meth:`do_refinements` (which calls this method).
1824
1825        >>> def checked_refinements(proj):
1826        ...     for p in proj.iter_refinements(refs):
1827        ...         # Track intermediate results
1828        ...         log(p.histogram('0').residuals)
1829        ...         log(p.phase('0').get_cell())
1830        ...         # Check if parameter diverged, nonsense answer, or whatever
1831        ...         if is_something_wrong(p):
1832        ...             raise Exception("I need a human!")
1833
1834           
1835        """
1836        if outputnames:
1837            if len(refinements) != len(outputnames):
1838                raise ValueError("Should have same number of outputs to"
1839                                 "refinements")
1840        else:
1841            outputnames = [None for r in refinements]
1842
1843        for output, refinedict in zip(outputnames, refinements):
1844            if 'histograms' in refinedict:
1845                hist = refinedict['histograms']
1846            else:
1847                hist = histogram
1848            if 'phases' in refinedict:
1849                ph = refinedict['phases']
1850            else:
1851                ph = phase
1852            if 'output' in refinedict:
1853                output = refinedict['output']
1854            self.set_refinement(refinedict, hist, ph)
1855            # Handle 'once' args - refinements that are disabled after this
1856            # refinement
1857            if 'once' in refinedict:
1858                temp = {'set': refinedict['once']}
1859                self.set_refinement(temp, hist, ph)
1860
1861            if output:
1862                self.save(output)
1863
1864            if 'skip' not in refinedict:
1865                self.refine(makeBack=makeBack)
1866            yield self
1867
1868            # Handle 'once' args - refinements that are disabled after this
1869            # refinement
1870            if 'once' in refinedict:
1871                temp = {'clear': refinedict['once']}
1872                self.set_refinement(temp, hist, ph)
1873            if 'call' in refinedict:
1874                fxn = refinedict['call']
1875                if callable(fxn):
1876                    fxn(*refinedict.get('callargs',[self]))
1877                elif callable(eval(fxn)):
1878                    eval(fxn)(*refinedict.get('callargs',[self]))
1879                else:
1880                    raise G2ScriptException("Error: call value {} is not callable".format(fxn))
1881
1882    def set_refinement(self, refinement, histogram='all', phase='all'):
1883        """Apply specified refinements to a given histogram(s) or phase(s).
1884
1885        :param dict refinement: The refinements to be conducted
1886        :param histogram: Specifies either 'all' (default), a single histogram or
1887          a list of histograms. Histograms may be specified as histogram objects
1888          (see :class:`G2PwdrData`), the histogram name (str) or the index number (int)
1889          of the histogram in the project, numbered starting from 0.
1890          Omitting the parameter or the string 'all' indicates that parameters in
1891          all histograms should be set.
1892        :param phase: Specifies either 'all' (default), a single phase or
1893          a list of phases. Phases may be specified as phase objects
1894          (see :class:`G2Phase`), the phase name (str) or the index number (int)
1895          of the phase in the project, numbered starting from 0.
1896          Omitting the parameter or the string 'all' indicates that parameters in
1897          all phases should be set.
1898
1899        Note that refinement parameters are categorized as one of three types:
1900
1901        1. Histogram parameters
1902        2. Phase parameters
1903        3. Histogram-and-Phase (HAP) parameters
1904       
1905        .. seealso::
1906            :meth:`G2PwdrData.set_refinements`
1907            :meth:`G2PwdrData.clear_refinements`
1908            :meth:`G2Phase.set_refinements`
1909            :meth:`G2Phase.clear_refinements`
1910            :meth:`G2Phase.set_HAP_refinements`
1911            :meth:`G2Phase.clear_HAP_refinements`"""
1912
1913        if histogram == 'all':
1914            hists = self.histograms()
1915        elif isinstance(histogram, list) or isinstance(histogram, tuple):
1916            hists = []
1917            for h in histogram:
1918                if isinstance(h, str) or isinstance(h, int):
1919                    hists.append(self.histogram(h))
1920                else:
1921                    hists.append(h)
1922        elif isinstance(histogram, str) or isinstance(histogram, int):
1923            hists = [self.histogram(histogram)]
1924        else:
1925            hists = [histogram]
1926
1927        if phase == 'all':
1928            phases = self.phases()
1929        elif isinstance(phase, list) or isinstance(phase, tuple):
1930            phases = []
1931            for ph in phase:
1932                if isinstance(ph, str) or isinstance(ph, int):
1933                    phases.append(self.phase(ph))
1934                else:
1935                    phases.append(ph)
1936        elif isinstance(phase, str) or isinstance(phase, int):
1937            phases = [self.phase(phase)]
1938        else:
1939            phases = [phase]
1940
1941        # TODO: HAP parameters:
1942        #   Babinet
1943        #   Extinction
1944        #   HStrain
1945        #   Mustrain
1946        #   Pref. Ori
1947        #   Size
1948
1949        pwdr_set = {}
1950        phase_set = {}
1951        hap_set = {}
1952        for key, val in refinement.get('set', {}).items():
1953            # Apply refinement options
1954            if G2PwdrData.is_valid_refinement_key(key):
1955                pwdr_set[key] = val
1956            elif G2Phase.is_valid_refinement_key(key):
1957                phase_set[key] = val
1958            elif G2Phase.is_valid_HAP_refinement_key(key):
1959                hap_set[key] = val
1960            else:
1961                raise ValueError("Unknown refinement key", key)
1962
1963        for hist in hists:
1964            hist.set_refinements(pwdr_set)
1965        for phase in phases:
1966            phase.set_refinements(phase_set)
1967        for phase in phases:
1968            phase.set_HAP_refinements(hap_set, hists)
1969
1970        pwdr_clear = {}
1971        phase_clear = {}
1972        hap_clear = {}
1973        for key, val in refinement.get('clear', {}).items():
1974            # Clear refinement options
1975            if G2PwdrData.is_valid_refinement_key(key):
1976                pwdr_clear[key] = val
1977            elif G2Phase.is_valid_refinement_key(key):
1978                phase_clear[key] = val
1979            elif G2Phase.is_valid_HAP_refinement_key(key):
1980                hap_set[key] = val
1981            else:
1982                raise ValueError("Unknown refinement key", key)
1983
1984        for hist in hists:
1985            hist.clear_refinements(pwdr_clear)
1986        for phase in phases:
1987            phase.clear_refinements(phase_clear)
1988        for phase in phases:
1989            phase.clear_HAP_refinements(hap_clear, hists)
1990
1991    def index_ids(self):
1992        import GSASIIstrIO as G2strIO
1993        self.save()
1994        return G2strIO.GetUsedHistogramsAndPhases(self.filename)
1995
1996    def add_constraint_raw(self, cons_scope, constr):
1997        """Adds a constraint of type consType to the project.
1998        cons_scope should be one of "Hist", "Phase", "HAP", or "Global".
1999
2000        WARNING it does not check the constraint is well-constructed"""
2001        constrs = self.data['Constraints']['data']
2002        if 'Global' not in constrs:
2003            constrs['Global'] = []
2004        constrs[cons_scope].append(constr)
2005
2006    def hold_many(self, vars, type):
2007        """Apply holds for all the variables in vars, for constraint of a given type.
2008
2009        type is passed directly to add_constraint_raw as consType
2010
2011        :param list vars: A list of variables to hold. Either :class:`GSASIIobj.G2VarObj` objects,
2012            string variable specifiers, or arguments for :meth:`make_var_obj`
2013        :param str type: A string constraint type specifier. See
2014            :class:`G2Project.add_constraint_raw`
2015
2016        """
2017        for var in vars:
2018            if isinstance(var, str):
2019                var = self.make_var_obj(var)
2020            elif not isinstance(var, G2obj.G2VarObj):
2021                var = self.make_var_obj(*var)
2022            self.add_constraint_raw(type, [[1.0, var], None, None, 'h'])
2023
2024    def make_var_obj(self, phase=None, hist=None, varname=None, atomId=None,
2025                     reloadIdx=True):
2026        """Wrapper to create a G2VarObj. Takes either a string representaiton ("p:h:name:a")
2027        or individual names of phase, histogram, varname, and atomId.
2028
2029        Automatically converts string phase, hist, or atom names into the ID required
2030        by G2VarObj."""
2031
2032        if reloadIdx:
2033            self.index_ids()
2034
2035        # If string representation, short circuit
2036        if hist is None and varname is None and atomId is None:
2037            if isinstance(phase, str) and ':' in phase:
2038                return G2obj.G2VarObj(phase)
2039
2040        # Get phase index
2041        phaseObj = None
2042        if isinstance(phase, G2Phase):
2043            phaseObj = phase
2044            phase = G2obj.PhaseRanIdLookup[phase.ranId]
2045        elif phase in self.data['Phases']:
2046            phaseObj = self.phase(phase)
2047            phaseRanId = phaseObj.ranId
2048            phase = G2obj.PhaseRanIdLookup[phaseRanId]
2049
2050        # Get histogram index
2051        if isinstance(hist, G2PwdrData):
2052            hist = G2obj.HistRanIdLookup[hist.ranId]
2053        elif hist in self.data:
2054            histRanId = self.histogram(hist).ranId
2055            hist = G2obj.HistRanIdLookup[histRanId]
2056
2057        # Get atom index (if any)
2058        if isinstance(atomId, G2AtomRecord):
2059            atomId = G2obj.AtomRanIdLookup[phase][atomId.ranId]
2060        elif phaseObj:
2061            atomObj = phaseObj.atom(atomId)
2062            if atomObj:
2063                atomRanId = atomObj.ranId
2064                atomId = G2obj.AtomRanIdLookup[phase][atomRanId]
2065
2066        return G2obj.G2VarObj(phase, hist, varname, atomId)
2067
2068    def add_image(self, imagefile, fmthint=None, defaultImage=None):
2069        """Load an image into a project
2070
2071        :param str imagefile: The image file to read, a filename.
2072        :param str fmthint: If specified, only importers where the format name
2073          (reader.formatName, as shown in Import menu) contains the
2074          supplied string will be tried as importers. If not specified, all
2075          importers consistent with the file extension will be tried
2076          (equivalent to "guess format" in menu).
2077        :param str defaultImage: The name of an image to use as a default for
2078          setting parameters for the image file to read.
2079
2080        :returns: a list of G2Image object for the added image(s) [this routine
2081          has not yet been tested with files with multiple images...]
2082        """
2083        LoadG2fil()
2084        imagefile = os.path.abspath(os.path.expanduser(imagefile))
2085        readers = import_generic(imagefile, Readers['Image'], fmthint=fmthint)
2086        objlist = []
2087        for rd in readers:
2088            if rd.SciPy:        #was default read by scipy; needs 1 time fixes
2089                print('TODO: Image {} read by SciPy. Parameters likely need editing'.format(imagefile))
2090                #see this: G2IO.EditImageParms(self,rd.Data,rd.Comments,rd.Image,imagefile)
2091                rd.SciPy = False
2092            rd.readfilename = imagefile
2093            if rd.repeatcount == 1 and not rd.repeat: # skip image number if only one in set
2094                rd.Data['ImageTag'] = None
2095            else:
2096                rd.Data['ImageTag'] = rd.repeatcount
2097            rd.Data['formatName'] = rd.formatName
2098            if rd.sumfile:
2099                rd.readfilename = rd.sumfile
2100            # Load generic metadata, as configured
2101            G2fil.GetColumnMetadata(rd)
2102            # Code from G2IO.LoadImage2Tree(rd.readfilename,self,rd.Comments,rd.Data,rd.Npix,rd.Image)
2103            Imax = np.amax(rd.Image)
2104            ImgNames = [i[0] for i in self.names if i[0].startswith('IMG ')]
2105            TreeLbl = 'IMG '+os.path.basename(imagefile)
2106            ImageTag = rd.Data.get('ImageTag')
2107            if ImageTag:
2108                TreeLbl += ' #'+'%04d'%(ImageTag)
2109                imageInfo = (imagefile,ImageTag)
2110            else:
2111                imageInfo = imagefile
2112            TreeName = G2obj.MakeUniqueLabel(TreeLbl,ImgNames)
2113            # MT dict to contain image info
2114            ImgDict = {}
2115            ImgDict['data'] = [rd.Npix,imageInfo]
2116            ImgDict['Comments'] = rd.Comments
2117            if defaultImage:
2118                if isinstance(defaultImage, G2Image):
2119                    if defaultImage.proj == self:
2120                        defaultImage = self.data[defaultImage.name]['data']
2121                    else:
2122                        raise Exception("Image {} not in current selected project".format(defaultImage.name))
2123                elif defaultImage in self._images():
2124                    defaultImage = self.data[defaultImage]['data']
2125                else:
2126                    defaultImage = None
2127            Data = rd.Data
2128            if defaultImage:
2129                Data = copy.copy(defaultImage)
2130                Data['showLines'] = True
2131                Data['ring'] = []
2132                Data['rings'] = []
2133                Data['cutoff'] = 10.
2134                Data['pixLimit'] = 20
2135                Data['edgemin'] = 100000000
2136                Data['calibdmin'] = 0.5
2137                Data['calibskip'] = 0
2138                Data['ellipses'] = []
2139                Data['calibrant'] = ''
2140                Data['GonioAngles'] = [0.,0.,0.]
2141                Data['DetDepthRef'] = False
2142            else:
2143                Data['type'] = 'PWDR'
2144                Data['color'] = GSASIIpath.GetConfigValue('Contour_color','Paired')
2145                if 'tilt' not in Data:          #defaults if not preset in e.g. Bruker importer
2146                    Data['tilt'] = 0.0
2147                    Data['rotation'] = 0.0
2148                    Data['pixLimit'] = 20
2149                    Data['calibdmin'] = 0.5
2150                    Data['cutoff'] = 10.
2151                Data['showLines'] = False
2152                Data['calibskip'] = 0
2153                Data['ring'] = []
2154                Data['rings'] = []
2155                Data['edgemin'] = 100000000
2156                Data['ellipses'] = []
2157                Data['GonioAngles'] = [0.,0.,0.]
2158                Data['DetDepth'] = 0.
2159                Data['DetDepthRef'] = False
2160                Data['calibrant'] = ''
2161                Data['IOtth'] = [5.0,50.0]
2162                Data['LRazimuth'] = [0.,180.]
2163                Data['azmthOff'] = 0.0
2164                Data['outChannels'] = 2250
2165                Data['outAzimuths'] = 1
2166                Data['centerAzm'] = False
2167                Data['fullIntegrate'] = GSASIIpath.GetConfigValue('fullIntegrate',True)
2168                Data['setRings'] = False
2169                Data['background image'] = ['',-1.0]                           
2170                Data['dark image'] = ['',-1.0]
2171                Data['Flat Bkg'] = 0.0
2172                Data['Oblique'] = [0.5,False]
2173            Data['setDefault'] = False
2174            Data['range'] = [(0,Imax),[0,Imax]]
2175            ImgDict['Image Controls'] = Data
2176            ImgDict['Masks'] = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],
2177                                'Frames':[],'Thresholds':[(0,Imax),[0,Imax]]}
2178            ImgDict['Stress/Strain']  = {'Type':'True','d-zero':[],'Sample phi':0.0,
2179                                             'Sample z':0.0,'Sample load':0.0}
2180            self.names.append([TreeName]+['Comments','Image Controls','Masks','Stress/Strain'])
2181            self.data[TreeName] = ImgDict
2182            del rd.Image
2183            objlist.append(G2Image(self.data[TreeName], TreeName, self))
2184        return objlist
2185
2186class G2AtomRecord(G2ObjectWrapper):
2187    """Wrapper for an atom record. Has convenient accessors via @property.
2188
2189
2190    Available accessors: label, type, refinement_flags, coordinates,
2191        occupancy, ranId, id, adp_flag, uiso
2192
2193    Example:
2194
2195    >>> atom = some_phase.atom("O3")
2196    >>> # We can access the underlying data format
2197    >>> atom.data
2198    ['O3', 'O-2', '', ... ]
2199    >>> # We can also use wrapper accessors
2200    >>> atom.coordinates
2201    (0.33, 0.15, 0.5)
2202    >>> atom.refinement_flags
2203    u'FX'
2204    >>> atom.ranId
2205    4615973324315876477
2206    >>> atom.occupancy
2207    1.0
2208    """
2209    def __init__(self, data, indices, proj):
2210        self.data = data
2211        self.cx, self.ct, self.cs, self.cia = indices
2212        self.proj = proj
2213
2214    @property
2215    def label(self):
2216        return self.data[self.ct-1]
2217
2218    @property
2219    def type(self):
2220        return self.data[self.ct]
2221
2222    @property
2223    def refinement_flags(self):
2224        return self.data[self.ct+1]
2225
2226    @refinement_flags.setter
2227    def refinement_flags(self, other):
2228        # Automatically check it is a valid refinement
2229        for c in other:
2230            if c not in ' FXU':
2231                raise ValueError("Invalid atom refinement: ", other)
2232        self.data[self.ct+1] = other
2233
2234    @property
2235    def coordinates(self):
2236        return tuple(self.data[self.cx:self.cx+3])
2237
2238    @property
2239    def occupancy(self):
2240        return self.data[self.cx+3]
2241
2242    @occupancy.setter
2243    def occupancy(self, val):
2244        self.data[self.cx+3] = float(val)
2245
2246    @property
2247    def ranId(self):
2248        return self.data[self.cia+8]
2249
2250    @property
2251    def adp_flag(self):
2252        # Either 'I' or 'A'
2253        return self.data[self.cia]
2254
2255    @property
2256    def uiso(self):
2257        if self.adp_flag == 'I':
2258            return self.data[self.cia+1]
2259        else:
2260            return self.data[self.cia+2:self.cia+8]
2261
2262    @uiso.setter
2263    def uiso(self, value):
2264        if self.adp_flag == 'I':
2265            self.data[self.cia+1] = float(value)
2266        else:
2267            assert len(value) == 6
2268            self.data[self.cia+2:self.cia+8] = [float(v) for v in value]
2269
2270
2271class G2PwdrData(G2ObjectWrapper):
2272    """Wraps a Powder Data Histogram."""
2273    def __init__(self, data, proj):
2274        self.data = data
2275        self.proj = proj
2276
2277    @staticmethod
2278    def is_valid_refinement_key(key):
2279        valid_keys = ['Limits', 'Sample Parameters', 'Background',
2280                      'Instrument Parameters']
2281        return key in valid_keys
2282
2283    @property
2284    def name(self):
2285        return self.data['data'][-1]
2286
2287    @property
2288    def ranId(self):
2289        return self.data['data'][0]['ranId']
2290
2291    @property
2292    def residuals(self):
2293        '''Provides a dictionary with with the R-factors for this histogram.
2294        Includes the weighted and unweighted profile terms (R, Rb, wR, wRb, wRmin)
2295        as well as the Bragg R-values for each phase (ph:H:Rf and ph:H:Rf^2).
2296        '''
2297        data = self.data['data'][0]
2298        return {key: data[key] for key in data
2299                if key in ['R', 'Rb', 'wR', 'wRb', 'wRmin']
2300                   or ':' in key}
2301
2302    @property
2303    def InstrumentParameters(self):
2304        '''Provides a dictionary with with the Instrument Parameters
2305        for this histogram.
2306        '''
2307        return self.data['Instrument Parameters'][0]
2308
2309    @property
2310    def SampleParameters(self):
2311        '''Provides a dictionary with with the Sample Parameters
2312        for this histogram.
2313        '''
2314        return self.data['Sample Parameters']
2315   
2316    @property
2317    def id(self):
2318        self.proj.update_ids()
2319        return self.data['data'][0]['hId']
2320
2321    @id.setter
2322    def id(self, val):
2323        self.data['data'][0]['hId'] = val
2324
2325    def fit_fixed_points(self):
2326        """Attempts to apply a background fit to the fixed points currently specified."""
2327        def SetInstParms(Inst):
2328            dataType = Inst['Type'][0]
2329            insVary = []
2330            insNames = []
2331            insVals = []
2332            for parm in Inst:
2333                insNames.append(parm)
2334                insVals.append(Inst[parm][1])
2335                if parm in ['U','V','W','X','Y','Z','SH/L','I(L2)/I(L1)','alpha',
2336                    'beta-0','beta-1','beta-q','sig-0','sig-1','sig-2','sig-q',] and Inst[parm][2]:
2337                        Inst[parm][2] = False
2338            instDict = dict(zip(insNames, insVals))
2339            instDict['X'] = max(instDict['X'], 0.01)
2340            instDict['Y'] = max(instDict['Y'], 0.01)
2341            if 'SH/L' in instDict:
2342                instDict['SH/L'] = max(instDict['SH/L'], 0.002)
2343            return dataType, instDict, insVary
2344
2345        bgrnd = self.data['Background']
2346
2347        # Need our fixed points in order
2348        bgrnd[1]['FixedPoints'].sort(key=lambda pair: pair[0])
2349        X = [x for x, y in bgrnd[1]['FixedPoints']]
2350        Y = [y for x, y in bgrnd[1]['FixedPoints']]
2351
2352        limits = self.data['Limits'][1]
2353        if X[0] > limits[0]:
2354            X = [limits[0]] + X
2355            Y = [Y[0]] + Y
2356        if X[-1] < limits[1]:
2357            X += [limits[1]]
2358            Y += [Y[-1]]
2359
2360        # Some simple lookups
2361        controls = self.proj['Controls']['data']
2362        inst, inst2 = self.data['Instrument Parameters']
2363        pwddata = self.data['data'][1]
2364
2365        # Construct the data for background fitting
2366        xBeg = np.searchsorted(pwddata[0], limits[0])
2367        xFin = np.searchsorted(pwddata[0], limits[1])
2368        xdata = pwddata[0][xBeg:xFin]
2369        ydata = si.interp1d(X,Y)(ma.getdata(xdata))
2370
2371        W = [1]*len(xdata)
2372        Z = [0]*len(xdata)
2373
2374        dataType, insDict, insVary = SetInstParms(inst)
2375        bakType, bakDict, bakVary = G2pwd.SetBackgroundParms(bgrnd)
2376
2377        # Do the fit
2378        data = np.array([xdata, ydata, W, Z, Z, Z])
2379        G2pwd.DoPeakFit('LSQ', [], bgrnd, limits, inst, inst2, data,
2380                        prevVaryList=bakVary, controls=controls)
2381
2382        # Post-fit
2383        parmDict = {}
2384        bakType, bakDict, bakVary = G2pwd.SetBackgroundParms(bgrnd)
2385        parmDict.update(bakDict)
2386        parmDict.update(insDict)
2387        pwddata[3][xBeg:xFin] *= 0
2388        pwddata[5][xBeg:xFin] *= 0
2389        pwddata[4][xBeg:xFin] = G2pwd.getBackground('', parmDict, bakType, dataType, xdata)[0]
2390
2391        # TODO adjust pwddata? GSASIIpwdGUI.py:1041
2392        # TODO update background
2393        self.proj.save()
2394
2395    def getdata(self,datatype):
2396        '''Provides access to the histogram data of the selected data type
2397
2398        :param str datatype: must be one of the following values (case is ignored)
2399       
2400           * 'X': the 2theta or TOF values for the pattern
2401           * 'Yobs': the observed intensity values
2402           * 'Yweight': the weights for each data point (1/sigma**2)
2403           * 'Ycalc': the computed intensity values
2404           * 'Background': the computed background values
2405           * 'Residual': the difference between Yobs and Ycalc (obs-calc)
2406
2407        :returns: an numpy MaskedArray with data values of the requested type
2408       
2409        '''
2410        enums = ['x', 'yobs', 'yweight', 'ycalc', 'background', 'residual']
2411        if datatype.lower() not in enums:
2412            raise G2ScriptException("Invalid datatype = "+datatype+" must be one of "+str(enums))
2413        return self.data['data'][1][enums.index(datatype.lower())]
2414       
2415    def y_calc(self):
2416        return self.data['data'][1][3]
2417
2418    def Export(self,fileroot,extension):
2419        '''Write the histogram into a file. The path is specified by fileroot and
2420        extension.
2421       
2422        :param str fileroot: name of the file, optionally with a path (extension is
2423           ignored)
2424        :param str extension: includes '.', must match an extension in global
2425           exportersByExtension['powder'] or a Exception is raised.
2426        :returns: name of file that was written
2427        '''
2428        if extension not in exportersByExtension.get('powder',[]):
2429            raise G2ScriptException('No Writer for file type = "'+extension+'"')
2430        fil = os.path.abspath(os.path.splitext(fileroot)[0]+extension)
2431        obj = exportersByExtension['powder'][extension]
2432        obj.SetFromArray(hist=self.data,histname=self.name)
2433        obj.Writer(self.name,fil)
2434           
2435    def plot(self, Yobs=True, Ycalc=True, Background=True, Residual=True):
2436        try:
2437            import matplotlib.pyplot as plt
2438            data = self.data['data'][1]
2439            if Yobs:
2440                plt.plot(data[0], data[1], label='Yobs')
2441            if Ycalc:
2442                plt.plot(data[0], data[3], label='Ycalc')
2443            if Background:
2444                plt.plot(data[0], data[4], label='Background')
2445            if Residual:
2446                plt.plot(data[0], data[5], label="Residual")
2447        except ImportError:
2448            pass
2449
2450    def get_wR(self):
2451        """returns the overall weighted profile R factor for a histogram
2452       
2453        :returns: a wR value as a percentage or None if not defined
2454        """
2455        return self['data'][0].get('wR')
2456
2457    def set_refinements(self, refs):
2458        """Sets the refinement parameter 'key' to the specification 'value'
2459
2460        :param dict refs: A dictionary of the parameters to be set. See
2461                          :ref:`Histogram_parameters_table` for a description of
2462                          what these dictionaries should be.
2463
2464        :returns: None
2465
2466        """
2467        do_fit_fixed_points = False
2468        for key, value in refs.items():
2469            if key == 'Limits':
2470                old_limits = self.data['Limits'][1]
2471                new_limits = value
2472                if isinstance(new_limits, dict):
2473                    if 'low' in new_limits:
2474                        old_limits[0] = new_limits['low']
2475                    if 'high' in new_limits:
2476                        old_limits[1] = new_limits['high']
2477                else:
2478                    old_limits[0], old_limits[1] = new_limits
2479            elif key == 'Sample Parameters':
2480                sample = self.data['Sample Parameters']
2481                for sparam in value:
2482                    if sparam not in sample:
2483                        raise ValueError("Unknown refinement parameter, "
2484                                         + str(sparam))
2485                    sample[sparam][1] = True
2486            elif key == 'Background':
2487                bkg, peaks = self.data['Background']
2488
2489                # If True or False, just set the refine parameter
2490                if value in (True, False):
2491                    bkg[1] = value
2492                    return
2493
2494                if 'type' in value:
2495                    bkg[0] = value['type']
2496                if 'refine' in value:
2497                    bkg[1] = value['refine']
2498                if 'no. coeffs' in value:
2499                    cur_coeffs = bkg[2]
2500                    n_coeffs = value['no. coeffs']
2501                    if n_coeffs > cur_coeffs:
2502                        for x in range(n_coeffs - cur_coeffs):
2503                            bkg.append(0.0)
2504                    else:
2505                        for _ in range(cur_coeffs - n_coeffs):
2506                            bkg.pop()
2507                    bkg[2] = n_coeffs
2508                if 'coeffs' in value:
2509                    bkg[3:] = value['coeffs']
2510                if 'FixedPoints' in value:
2511                    peaks['FixedPoints'] = [(float(a), float(b))
2512                                            for a, b in value['FixedPoints']]
2513                if value.get('fit fixed points', False):
2514                    do_fit_fixed_points = True
2515
2516            elif key == 'Instrument Parameters':
2517                instrument, secondary = self.data['Instrument Parameters']
2518                for iparam in value:
2519                    try:
2520                        instrument[iparam][2] = True
2521                    except IndexError:
2522                        raise ValueError("Invalid key:", iparam)
2523            else:
2524                raise ValueError("Unknown key:", key)
2525        # Fit fixed points after the fact - ensure they are after fixed points
2526        # are added
2527        if do_fit_fixed_points:
2528            # Background won't be fit if refinement flag not set
2529            orig = self.data['Background'][0][1]
2530            self.data['Background'][0][1] = True
2531            self.fit_fixed_points()
2532            # Restore the previous value
2533            self.data['Background'][0][1] = orig
2534
2535    def clear_refinements(self, refs):
2536        """Clears the refinement parameter 'key' and its associated value.
2537
2538        :param dict refs: A dictionary of parameters to clear."""
2539        for key, value in refs.items():
2540            if key == 'Limits':
2541                old_limits, cur_limits = self.data['Limits']
2542                cur_limits[0], cur_limits[1] = old_limits
2543            elif key == 'Sample Parameters':
2544                sample = self.data['Sample Parameters']
2545                for sparam in value:
2546                    sample[sparam][1] = False
2547            elif key == 'Background':
2548                bkg, peaks = self.data['Background']
2549
2550                # If True or False, just set the refine parameter
2551                if value in (True, False):
2552                    bkg[1] = False
2553                    return
2554
2555                bkg[1] = False
2556                if 'FixedPoints' in value:
2557                    if 'FixedPoints' in peaks:
2558                        del peaks['FixedPoints']
2559            elif key == 'Instrument Parameters':
2560                instrument, secondary = self.data['Instrument Parameters']
2561                for iparam in value:
2562                    instrument[iparam][2] = False
2563            else:
2564                raise ValueError("Unknown key:", key)
2565
2566    def add_peak(self,area,dspace=None,Q=None,ttheta=None):
2567        '''Adds a single peak to the peak list
2568        :param float area: peak area
2569        :param float dspace: peak position as d-space (A)
2570        :param float Q: peak position as Q (A-1)
2571        :param float ttheta: peak position as 2Theta (deg)
2572
2573        Note: only one of the parameters dspace, Q or ttheta may be specified
2574        '''
2575        import GSASIIlattice as G2lat
2576        import GSASIImath as G2mth
2577        if (not dspace) + (not Q) + (not ttheta) != 2:
2578            print('add_peak error: too many or no peak position(s) specified')
2579            return
2580        pos = ttheta
2581        Parms,Parms2 = self.data['Instrument Parameters']
2582        if Q:
2583            pos = G2lat.Dsp2pos(Parms,2.*np.pi/Q)
2584        elif dspace:
2585            pos = G2lat.Dsp2pos(Parms,dspace)
2586        peaks = self.data['Peak List']
2587        peaks['sigDict'] = {}        #no longer valid
2588        peaks['peaks'].append(G2mth.setPeakparms(Parms,Parms2,pos,area))
2589
2590    def set_peakFlags(self,peaklist=None,area=None,pos=None,sig=None,gam=None):
2591        '''Set refinement flags for peaks
2592       
2593        :param list peaklist: a list of peaks to change flags. If None (default), changes
2594          are made to all peaks.
2595        :param bool area: Sets or clears the refinement flag for the peak area value.
2596          If None (the default), no change is made.
2597        :param bool pos: Sets or clears the refinement flag for the peak position value.
2598          If None (the default), no change is made.
2599        :param bool sig: Sets or clears the refinement flag for the peak sig (Gaussian width) value.
2600          If None (the default), no change is made.
2601        :param bool gam: Sets or clears the refinement flag for the peak sig (Lorentzian width) value.
2602          If None (the default), no change is made.
2603         
2604        Note that when peaks are first created the area flag is on and the other flags are
2605        initially off.
2606
2607        Example::
2608       
2609           set_peakFlags(sig=False,gam=True)
2610
2611        causes the sig refinement flag to be cleared and the gam flag to be set, in both cases for
2612        all peaks. The position and area flags are not changed from their previous values.
2613        '''
2614        peaks = self.data['Peak List']
2615        if peaklist is None:
2616            peaklist = range(len(peaks['peaks']))
2617        for i in peaklist:
2618            for var,j in [(area,3),(pos,1),(sig,5),(gam,7)]:
2619                if var is not None:
2620                    peaks['peaks'][i][j] = var
2621           
2622    def refine_peaks(self):
2623        '''Causes a refinement of peak position, background and instrument parameters
2624        '''
2625        import GSASIIpwd as G2pwd
2626        controls = self.proj.data.get('Controls',{})
2627        controls = controls.get('data',
2628                            {'deriv type':'analytic','min dM/M':0.001,}     #fill in defaults if needed
2629                            )
2630        peaks = self.data['Peak List']
2631        Parms,Parms2 = self.data['Instrument Parameters']
2632        background = self.data['Background']
2633        limits = self.data['Limits'][1]
2634        bxye = np.zeros(len(self.data['data'][1][1]))
2635        peaks['sigDict'] = G2pwd.DoPeakFit('LSQ',peaks['peaks'],background,limits,
2636                                           Parms,Parms2,self.data['data'][1],bxye,[],
2637                                           False,controls,None)[0]
2638
2639    def SaveProfile(self,filename):
2640        '''Writes a GSAS-II (new style) .instprm file
2641        '''
2642        data,Parms2 = self.data['Instrument Parameters']
2643        filename = os.path.splitext(filename)[0]+'.instprm'         # make sure extension is .instprm
2644        File = open(filename,'w')
2645        File.write("#GSAS-II instrument parameter file; do not add/delete items!\n")
2646        for item in data:
2647            File.write(item+':'+str(data[item][1])+'\n')
2648        File.close()
2649        print ('Instrument parameters saved to: '+filename)
2650
2651    def LoadProfile(self,filename):
2652        '''Reads a GSAS-II (new style) .instprm file and overwrites the current parameters
2653        '''
2654        filename = os.path.splitext(filename)[0]+'.instprm'         # make sure extension is .instprm
2655        File = open(filename,'r')
2656        S = File.readline()
2657        newItems = []
2658        newVals = []
2659        Found = False
2660        while S:
2661            if S[0] == '#':
2662                if Found:
2663                    break
2664                if 'Bank' in S:
2665                    if bank == int(S.split(':')[0].split()[1]):
2666                        S = File.readline()
2667                        continue
2668                    else:
2669                        S = File.readline()
2670                        while S and '#Bank' not in S:
2671                            S = File.readline()
2672                        continue
2673                else:   #a non #Bank file
2674                    S = File.readline()
2675                    continue
2676            Found = True
2677            [item,val] = S[:-1].split(':')
2678            newItems.append(item)
2679            try:
2680                newVals.append(float(val))
2681            except ValueError:
2682                newVals.append(val)                       
2683            S = File.readline()               
2684        File.close()
2685        LoadG2fil()
2686        self.data['Instrument Parameters'][0] = G2fil.makeInstDict(newItems,newVals,len(newVals)*[False,])
2687
2688       
2689class G2Phase(G2ObjectWrapper):
2690    """A wrapper object around a given phase.
2691
2692    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
2693    """
2694    def __init__(self, data, name, proj):
2695        self.data = data
2696        self.name = name
2697        self.proj = proj
2698
2699    @staticmethod
2700    def is_valid_refinement_key(key):
2701        valid_keys = ["Cell", "Atoms", "LeBail"]
2702        return key in valid_keys
2703
2704    @staticmethod
2705    def is_valid_HAP_refinement_key(key):
2706        valid_keys = ["Babinet", "Extinction", "HStrain", "Mustrain",
2707                      "Pref.Ori.", "Show", "Size", "Use", "Scale"]
2708        return key in valid_keys
2709
2710    def atom(self, atomlabel):
2711        """Returns the atom specified by atomlabel, or None if it does not
2712        exist.
2713
2714        :param str atomlabel: The name of the atom (e.g. "O2")
2715        :returns: A :class:`G2AtomRecord` object
2716            representing the atom.
2717        """
2718        # Consult GSASIIobj.py for the meaning of this
2719        cx, ct, cs, cia = self.data['General']['AtomPtrs']
2720        ptrs = [cx, ct, cs, cia]
2721        atoms = self.data['Atoms']
2722        for atom in atoms:
2723            if atom[ct-1] == atomlabel:
2724                return G2AtomRecord(atom, ptrs, self.proj)
2725
2726    def atoms(self):
2727        """Returns a list of atoms present in the phase.
2728
2729        :returns: A list of :class:`G2AtomRecord` objects.
2730
2731        .. seealso::
2732            :meth:`G2Phase.atom`
2733            :class:`G2AtomRecord`
2734        """
2735        ptrs = self.data['General']['AtomPtrs']
2736        output = []
2737        atoms = self.data['Atoms']
2738        for atom in atoms:
2739            output.append(G2AtomRecord(atom, ptrs, self.proj))
2740        return output
2741
2742    def histograms(self):
2743        output = []
2744        for hname in self.data.get('Histograms', {}).keys():
2745            output.append(self.proj.histogram(hname))
2746        return output
2747
2748    @property
2749    def ranId(self):
2750        return self.data['ranId']
2751
2752    @property
2753    def id(self):
2754        return self.data['pId']
2755
2756    @id.setter
2757    def id(self, val):
2758        self.data['pId'] = val
2759
2760    def get_cell(self):
2761        """Returns a dictionary of the cell parameters, with keys:
2762            'length_a', 'length_b', 'length_c', 'angle_alpha', 'angle_beta', 'angle_gamma', 'volume'
2763
2764        :returns: a dict
2765
2766        .. seealso::
2767           :meth:`G2Phase.get_cell_and_esd`
2768
2769        """
2770        cell = self.data['General']['Cell']
2771        return {'length_a': cell[1], 'length_b': cell[2], 'length_c': cell[3],
2772                'angle_alpha': cell[4], 'angle_beta': cell[5], 'angle_gamma': cell[6],
2773                'volume': cell[7]}
2774
2775    def get_cell_and_esd(self):
2776        """
2777        Returns a pair of dictionaries, the first representing the unit cell, the second
2778        representing the estimated standard deviations of the unit cell.
2779
2780        :returns: a tuple of two dictionaries
2781
2782        .. seealso::
2783           :meth:`G2Phase.get_cell`
2784
2785        """
2786        # translated from GSASIIstrIO.ExportBaseclass.GetCell
2787        import GSASIIstrIO as G2stIO
2788        import GSASIIlattice as G2lat
2789        import GSASIImapvars as G2mv
2790        try:
2791            pfx = str(self.id) + '::'
2792            sgdata = self['General']['SGData']
2793            covDict = self.proj['Covariance']['data']
2794
2795            parmDict = dict(zip(covDict.get('varyList',[]),
2796                                covDict.get('variables',[])))
2797            sigDict = dict(zip(covDict.get('varyList',[]),
2798                               covDict.get('sig',[])))
2799
2800            if covDict.get('covMatrix') is not None:
2801                sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],
2802                                                  covDict['varyList'],
2803                                                  parmDict))
2804
2805            A, sigA = G2stIO.cellFill(pfx, sgdata, parmDict, sigDict)
2806            cellSig = G2stIO.getCellEsd(pfx, sgdata, A, self.proj['Covariance']['data'])
2807            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
2808            cellDict, cellSigDict = {}, {}
2809            for i, key in enumerate(['length_a', 'length_b', 'length_c',
2810                                     'angle_alpha', 'angle_beta', 'angle_gamma',
2811                                     'volume']):
2812                cellDict[key] = cellList[i]
2813                cellSigDict[key] = cellSig[i]
2814            return cellDict, cellSigDict
2815        except KeyError:
2816            cell = self.get_cell()
2817            return cell, {key: 0.0 for key in cell}
2818
2819    def export_CIF(self, outputname, quickmode=True):
2820        """Write this phase to a .cif file named outputname
2821
2822        :param str outputname: The name of the .cif file to write to
2823        :param bool quickmode: Currently ignored. Carryover from exports.G2export_CIF"""
2824        # This code is all taken from exports/G2export_CIF.py
2825        # Functions copied have the same names
2826        import GSASIImath as G2mth
2827        import GSASIImapvars as G2mv
2828        from exports import G2export_CIF as cif
2829
2830        CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2831        CIFname = os.path.splitext(self.proj.filename)[0]
2832        CIFname = os.path.split(CIFname)[1]
2833        CIFname = ''.join([c if ord(c) < 128 else ''
2834                           for c in CIFname.replace(' ', '_')])
2835        try:
2836            author = self.proj['Controls']['data'].get('Author','').strip()
2837        except KeyError:
2838            pass
2839        oneblock = True
2840
2841        covDict = self.proj['Covariance']['data']
2842        parmDict = dict(zip(covDict.get('varyList',[]),
2843                            covDict.get('variables',[])))
2844        sigDict = dict(zip(covDict.get('varyList',[]),
2845                           covDict.get('sig',[])))
2846
2847        if covDict.get('covMatrix') is not None:
2848            sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],
2849                                              covDict['varyList'],
2850                                              parmDict))
2851
2852        with open(outputname, 'w') as fp:
2853            fp.write(' \n' + 70*'#' + '\n')
2854            cif.WriteCIFitem(fp, 'data_' + CIFname)
2855            # from exports.G2export_CIF.WritePhaseInfo
2856            cif.WriteCIFitem(fp, '\n# phase info for '+str(self.name) + ' follows')
2857            cif.WriteCIFitem(fp, '_pd_phase_name', self.name)
2858            # TODO get esds
2859            cellDict = self.get_cell()
2860            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2861            names = ['length_a','length_b','length_c',
2862                     'angle_alpha','angle_beta ','angle_gamma',
2863                     'volume']
2864            for key, val in cellDict.items():
2865                cif.WriteCIFitem(fp, '_cell_' + key, G2mth.ValEsd(val))
2866
2867            cif.WriteCIFitem(fp, '_symmetry_cell_setting',
2868                         self.data['General']['SGData']['SGSys'])
2869
2870            spacegroup = self.data['General']['SGData']['SpGrp'].strip()
2871            # regularize capitalization and remove trailing H/R
2872            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2873            cif.WriteCIFitem(fp, '_symmetry_space_group_name_H-M', spacegroup)
2874
2875            # generate symmetry operations including centering and center of symmetry
2876            SymOpList, offsetList, symOpList, G2oprList, G2opcodes = G2spc.AllOps(
2877                self.data['General']['SGData'])
2878            cif.WriteCIFitem(fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
2879            for i, op in enumerate(SymOpList,start=1):
2880                cif.WriteCIFitem(fp, '   {:3d}  {:}'.format(i,op.lower()))
2881
2882            # TODO skipped histograms, exports/G2export_CIF.py:880
2883
2884            # report atom params
2885            if self.data['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2886                cif.WriteAtomsNuclear(fp, self.data, self.name, parmDict, sigDict, [])
2887                # self._WriteAtomsNuclear(fp, parmDict, sigDict)
2888            else:
2889                raise G2ScriptException("no export for "+str(self.data['General']['Type'])+" coordinates implemented")
2890            # report cell contents
2891            cif.WriteComposition(fp, self.data, self.name, parmDict)
2892            if not quickmode and self.data['General']['Type'] == 'nuclear':      # report distances and angles
2893                # WriteDistances(fp,self.name,SymOpList,offsetList,symOpList,G2oprList)
2894                raise NotImplementedError("only quickmode currently supported")
2895            if 'Map' in self.data['General'] and 'minmax' in self.data['General']['Map']:
2896                cif.WriteCIFitem(fp,'\n# Difference density results')
2897                MinMax = self.data['General']['Map']['minmax']
2898                cif.WriteCIFitem(fp,'_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2899                cif.WriteCIFitem(fp,'_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2900
2901
2902    def set_refinements(self, refs):
2903        """Sets the refinement parameter 'key' to the specification 'value'
2904
2905        :param dict refs: A dictionary of the parameters to be set. See
2906                          :ref:`Phase_parameters_table` for a description of
2907                          this dictionary.
2908
2909        :returns: None"""
2910        for key, value in refs.items():
2911            if key == "Cell":
2912                self.data['General']['Cell'][0] = value
2913
2914            elif key == "Atoms":
2915                for atomlabel, atomrefinement in value.items():
2916                    if atomlabel == 'all':
2917                        for atom in self.atoms():
2918                            atom.refinement_flags = atomrefinement
2919                    else:
2920                        atom = self.atom(atomlabel)
2921                        if atom is None:
2922                            raise ValueError("No such atom: " + atomlabel)
2923                        atom.refinement_flags = atomrefinement
2924
2925            elif key == "LeBail":
2926                hists = self.data['Histograms']
2927                for hname, hoptions in hists.items():
2928                    if 'LeBail' not in hoptions:
2929                        hoptions['newLeBail'] = bool(True)
2930                    hoptions['LeBail'] = bool(value)
2931            else:
2932                raise ValueError("Unknown key:", key)
2933
2934    def clear_refinements(self, refs):
2935        """Clears a given set of parameters.
2936
2937        :param dict refs: The parameters to clear"""
2938        for key, value in refs.items():
2939            if key == "Cell":
2940                self.data['General']['Cell'][0] = False
2941            elif key == "Atoms":
2942                cx, ct, cs, cia = self.data['General']['AtomPtrs']
2943
2944                for atomlabel in value:
2945                    atom = self.atom(atomlabel)
2946                    # Set refinement to none
2947                    atom.refinement_flags = ' '
2948            elif key == "LeBail":
2949                hists = self.data['Histograms']
2950                for hname, hoptions in hists.items():
2951                    if 'LeBail' not in hoptions:
2952                        hoptions['newLeBail'] = True
2953                    hoptions['LeBail'] = False
2954            else:
2955                raise ValueError("Unknown key:", key)
2956
2957    def set_HAP_refinements(self, refs, histograms='all'):
2958        """Sets the given HAP refinement parameters between this phase and
2959        the given histograms
2960
2961        :param dict refs: A dictionary of the parameters to be set. See
2962                          :ref:`HAP_parameters_table` for a description of this
2963                          dictionary.
2964        :param histograms: Either 'all' (default) or a list of the histograms
2965            whose HAP parameters will be set with this phase. Histogram and phase
2966            must already be associated
2967
2968        :returns: None
2969        """
2970        if histograms == 'all':
2971            histograms = self.data['Histograms'].values()
2972        else:
2973            histograms = [self.data['Histograms'][h.name] for h in histograms]
2974
2975        for key, val in refs.items():
2976            for h in histograms:
2977                if key == 'Babinet':
2978                    try:
2979                        sets = list(val)
2980                    except ValueError:
2981                        sets = ['BabA', 'BabU']
2982                    for param in sets:
2983                        if param not in ['BabA', 'BabU']:
2984                            raise ValueError("Not sure what to do with" + param)
2985                        for hist in histograms:
2986                            hist['Babinet'][param][1] = True
2987                elif key == 'Extinction':
2988                    for h in histograms:
2989                        h['Extinction'][1] = bool(val)
2990                elif key == 'HStrain':
2991                    for h in histograms:
2992                        h['HStrain'][1] = [bool(val) for p in h['HStrain'][1]]
2993                elif key == 'Mustrain':
2994                    for h in histograms:
2995                        mustrain = h['Mustrain']
2996                        newType = None
2997                        direction = None
2998                        if isinstance(val, strtypes):
2999                            if val in ['isotropic', 'uniaxial', 'generalized']:
3000                                newType = val
3001                            else:
3002                                raise ValueError("Not a Mustrain type: " + val)
3003                        elif isinstance(val, dict):
3004                            newType = val.get('type', None)
3005                            direction = val.get('direction', None)
3006
3007                        if newType:
3008                            mustrain[0] = newType
3009                            if newType == 'isotropic':
3010                                mustrain[2][0] = True == val.get('refine',False)
3011                                mustrain[5] = [False for p in mustrain[4]]
3012                            elif newType == 'uniaxial':
3013                                if 'refine' in val:
3014                                    mustrain[2][0] = False
3015                                    types = val['refine']
3016                                    if isinstance(types, strtypes):
3017                                        types = [types]
3018                                    elif isinstance(types, bool):
3019                                        mustrain[2][1] = types
3020                                        mustrain[2][2] = types
3021                                        types = []
3022                                    else:
3023                                        raise ValueError("Not sure what to do with: "
3024                                                         + str(types))
3025                                else:
3026                                    types = []
3027
3028                                for unitype in types:
3029                                    if unitype == 'equatorial':
3030                                        mustrain[2][0] = True
3031                                    elif unitype == 'axial':
3032                                        mustrain[2][1] = True
3033                                    else:
3034                                        msg = 'Invalid uniaxial mustrain type'
3035                                        raise ValueError(msg + ': ' + unitype)
3036                            else:  # newtype == 'generalized'
3037                                mustrain[2] = [False for p in mustrain[1]]
3038                                if 'refine' in val:
3039                                    mustrain[5] = [True == val['refine']]*len(mustrain[5])
3040
3041                        if direction:
3042                            if len(direction) != 3:
3043                                raise ValueError("Expected hkl, found", direction)
3044                            direction = [int(n) for n in direction]
3045                            mustrain[3] = direction
3046                elif key == 'Size':
3047                    newSize = None
3048                    if 'value' in val:
3049                        newSize = float(val['value'])
3050                    for h in histograms:
3051                        size = h['Size']
3052                        newType = None
3053                        direction = None
3054                        if isinstance(val, strtypes):
3055                            if val in ['isotropic', 'uniaxial', 'ellipsoidal']:
3056                                newType = val
3057                            else:
3058                                raise ValueError("Not a valid Size type: " + val)
3059                        elif isinstance(val, dict):
3060                            newType = val.get('type', None)
3061                            direction = val.get('direction', None)
3062
3063                        if newType:
3064                            size[0] = newType
3065                            refine = True == val.get('refine')
3066                            if newType == 'isotropic' and refine is not None:
3067                                size[2][0] = bool(refine)
3068                                if newSize: size[1][0] = newSize
3069                            elif newType == 'uniaxial' and refine is not None:
3070                                size[2][1] = bool(refine)
3071                                size[2][2] = bool(refine)
3072                                if newSize: size[1][1] = size[1][2] =newSize
3073                            elif newType == 'ellipsoidal' and refine is not None:
3074                                size[5] = [bool(refine) for p in size[5]]
3075                                if newSize: size[4] = [newSize for p in size[4]]
3076
3077                        if direction:
3078                            if len(direction) != 3:
3079                                raise ValueError("Expected hkl, found", direction)
3080                            direction = [int(n) for n in direction]
3081                            size[3] = direction
3082                elif key == 'Pref.Ori.':
3083                    for h in histograms:
3084                        h['Pref.Ori.'][2] = bool(val)
3085                elif key == 'Show':
3086                    for h in histograms:
3087                        h['Show'] = bool(val)
3088                elif key == 'Use':
3089                    for h in histograms:
3090                        h['Use'] = bool(val)
3091                elif key == 'Scale':
3092                    for h in histograms:
3093                        h['Scale'][1] = bool(val)
3094                else:
3095                    print(u'Unknown HAP key: '+key)
3096
3097    def getHAPvalues(self, histname):
3098        """Returns a dict with HAP values for the selected histogram
3099
3100        :param histogram: is a histogram object (:class:`G2PwdrData`) or
3101            a histogram name or the index number of the histogram
3102
3103        :returns: HAP value dict
3104        """
3105        if isinstance(histname, G2PwdrData):
3106            histname = histname.name
3107        elif histname in self.data['Histograms']:
3108            pass
3109        elif type(histname) is int:
3110            histname = self.proj.histograms()[histname].name
3111        else:
3112            raise G2ScriptException("Invalid histogram reference: "+str(histname))
3113        return self.data['Histograms'][histname]
3114                   
3115    def clear_HAP_refinements(self, refs, histograms='all'):
3116        """Clears the given HAP refinement parameters between this phase and
3117        the given histograms
3118
3119        :param dict refs: A dictionary of the parameters to be cleared.
3120        :param histograms: Either 'all' (default) or a list of the histograms
3121            whose HAP parameters will be cleared with this phase. Histogram and
3122            phase must already be associated
3123
3124        :returns: None
3125        """
3126        if histograms == 'all':
3127            histograms = self.data['Histograms'].values()
3128        else:
3129            histograms = [self.data['Histograms'][h.name] for h in histograms]
3130
3131        for key, val in refs.items():
3132            for h in histograms:
3133                if key == 'Babinet':
3134                    try:
3135                        sets = list(val)
3136                    except ValueError:
3137                        sets = ['BabA', 'BabU']
3138                    for param in sets:
3139                        if param not in ['BabA', 'BabU']:
3140                            raise ValueError("Not sure what to do with" + param)
3141                        for hist in histograms:
3142                            hist['Babinet'][param][1] = False
3143                elif key == 'Extinction':
3144                    for h in histograms:
3145                        h['Extinction'][1] = False
3146                elif key == 'HStrain':
3147                    for h in histograms:
3148                        h['HStrain'][1] = [False for p in h['HStrain'][1]]
3149                elif key == 'Mustrain':
3150                    for h in histograms:
3151                        mustrain = h['Mustrain']
3152                        mustrain[2] = [False for p in mustrain[2]]
3153                        mustrain[5] = [False for p in mustrain[4]]
3154                elif key == 'Pref.Ori.':
3155                    for h in histograms:
3156                        h['Pref.Ori.'][2] = False
3157                elif key == 'Show':
3158                    for h in histograms:
3159                        h['Show'] = False
3160                elif key == 'Size':
3161                    for h in histograms:
3162                        size = h['Size']
3163                        size[2] = [False for p in size[2]]
3164                        size[5] = [False for p in size[5]]
3165                elif key == 'Use':
3166                    for h in histograms:
3167                        h['Use'] = False
3168                elif key == 'Scale':
3169                    for h in histograms:
3170                        h['Scale'][1] = False
3171                else:
3172                    print(u'Unknown HAP key: '+key)
3173
3174class G2Image(G2ObjectWrapper):
3175    """Wrapper for an IMG tree entry, containing an image and various metadata.
3176    """
3177    # parameters in
3178    ControlList = {
3179        'int': ['calibskip', 'pixLimit', 'edgemin', 'outChannels',
3180                    'outAzimuths'],
3181        'float': ['cutoff', 'setdist', 'wavelength', 'Flat Bkg',
3182                      'azmthOff', 'tilt', 'calibdmin', 'rotation',
3183                      'distance', 'DetDepth'],
3184        'bool': ['setRings', 'setDefault', 'centerAzm', 'fullIntegrate',
3185                     'DetDepthRef', 'showLines'],
3186        'str': ['SampleShape', 'binType', 'formatName', 'color',
3187                    'type', ],
3188        'list': ['GonioAngles', 'IOtth', 'LRazimuth', 'Oblique', 'PolaVal',
3189                   'SampleAbs', 'center', 'ellipses', 'linescan',
3190                    'pixelSize', 'range', 'ring', 'rings', 'size', ],
3191        'dict': ['varylist'],
3192        }
3193    '''Defines the items known to exist in the Image Controls tree section
3194    and their data types.
3195    '''
3196    # special handling: 'background image', 'dark image', 'Gain map',
3197    #   'calibrant'
3198
3199    def __init__(self, data, name, proj):
3200        self.data = data
3201        self.name = name
3202        self.proj = proj
3203
3204    def setControl(self,arg,value):
3205        '''Set an Image Controls parameter in the current image.
3206        If the parameter is not found an exception is raised.
3207
3208        :param str arg: the name of a parameter (dict entry) in the
3209          image. The parameter must be found in :data:`ControlList`
3210          or an exception is raised.
3211        :param value: the value to set the parameter. The value is
3212          cast as the appropriate type from :data:`ControlList`.
3213        '''
3214        for typ in self.ControlList:
3215            if arg in self.ControlList[typ]: break
3216        else:
3217            raise Exception('arg {} not defined in G2Image.setControl'
3218                                .format(arg))
3219        try:
3220            if typ == 'int':
3221                self.data['Image Controls'][arg] = int(value)
3222            elif typ == 'float':
3223                self.data['Image Controls'][arg] = float(value)
3224            elif typ == 'bool':
3225                self.data['Image Controls'][arg] = bool(value)
3226            elif typ == 'str':
3227                self.data['Image Controls'][arg] = str(value)
3228            elif typ == 'list':
3229                self.data['Image Controls'][arg] = list(value)
3230            elif typ == 'dict':
3231                self.data['Image Controls'][arg] = dict(value)
3232            else:
3233                raise Exception('Unknown type {} for arg {} in  G2Image.setControl'
3234                                    .format(typ,arg))
3235        except:
3236            raise Exception('Error formatting value {} as type {} for arg {} in  G2Image.setControl'
3237                                    .format(value,typ,arg))
3238
3239    def getControl(self,arg):
3240        '''Return an Image Controls parameter in the current image.
3241        If the parameter is not found an exception is raised.
3242
3243        :param str arg: the name of a parameter (dict entry) in the
3244          image.
3245        :returns: the value as a int, float, list,...
3246        '''
3247        if arg in self.data['Image Controls']:
3248            return self.data['Image Controls'][arg]
3249        raise Exception('arg {} not defined in G2Image.getControl'.format(arg))
3250
3251    def findControl(self,arg):
3252        '''Finds the Image Controls parameter(s) in the current image
3253        that match the string in arg.
3254
3255          Example: findControl('calib') will return
3256          [['calibskip', 'int'], ['calibdmin', 'float'], ['calibrant', 'str']]
3257
3258        :param str arg: a string containing part of the name of a
3259          parameter (dict entry) in the image's Image Controls.
3260        :returns: a list of matching entries in form
3261          [['item','type'], ['item','type'],...] where each 'item' string
3262          contains the sting in arg.
3263        '''
3264        matchList = []
3265        for typ in self.ControlList:
3266            for item in self.ControlList[typ]:
3267                if arg in item:
3268                    matchList.append([item,typ])
3269        return matchList
3270
3271    def setCalibrant(self,calib):
3272        '''Set a calibrant for the current image
3273
3274        :param str calib: specifies a calibrant name which must be one of
3275          the entries in file ImageCalibrants.py. This is validated.
3276        '''
3277        import ImageCalibrants as calFile
3278        if calib in calFile.Calibrants.keys():
3279            self.data['Image Controls']['calibrant'] = calib
3280            return
3281        print('Calibrant {} is not valid. Valid calibrants'.format(calib))
3282        for i in calFile.Calibrants.keys():
3283            if i: print('\t"{}"'.format(i))
3284       
3285    def setControlFile(self,typ,imageRef,mult=None):
3286        '''Set a image to be used as a background/dark/gain map image
3287
3288        :param str typ: specifies image type, which must be one of:
3289           'background image', 'dark image', 'gain map'; N.B. only the first
3290           four characters must be specified and case is ignored.
3291        :param imageRef: A reference to the desired image. Either the Image
3292          tree name (str), the image's index (int) or
3293          a image object (:class:`G2Image`)
3294        :param float mult: a multiplier to be applied to the image (not used
3295          for 'Gain map'; required for 'background image', 'dark image'
3296        '''
3297        if 'back' in typ.lower():
3298            key = 'background image'
3299            mult = float(mult)
3300        elif 'dark' in typ.lower():
3301            key = 'dark image'
3302            mult = float(mult)
3303        elif 'gain' in typ.lower():
3304            #key = 'Gain map'
3305            if mult is not None:
3306                print('Ignoring multiplier for Gain map')
3307            mult = None
3308        else:
3309            raise Exception("Invalid typ {} for setControlFile".format(typ))
3310        imgNam = self.proj.image(imageRef).name
3311        if mult is None:
3312            self.data['Image Controls']['Gain map'] = imgNam
3313        else:
3314            self.data['Image Controls'][key] = [imgNam,mult]
3315
3316    def loadControls(self,filename):
3317        '''load controls from a .imctrl file
3318
3319        :param str filename: specifies a file to be read, which should end
3320          with .imctrl
3321        '''
3322        File = open(filename,'r')
3323        Slines = File.readlines()
3324        File.close()
3325        G2fil.LoadControls(Slines,self.data['Image Controls'])
3326        print('file {} read into {}'.format(filename,self.name))
3327
3328    def saveControls(self,filename):
3329        '''write current controls values to a .imctrl file
3330
3331        :param str filename: specifies a file to write, which should end
3332          with .imctrl
3333        '''
3334        G2fil.WriteControls(filename,self.data['Image Controls'])
3335        print('file {} written from {}'.format(filename,self.name))
3336
3337    def loadMasks(self,filename,ignoreThreshold=False):
3338        '''load masks from a .immask file
3339
3340        :param str filename: specifies a file to be read, which should end
3341          with .immask
3342        :param bool ignoreThreshold: If True, masks are loaded with
3343          threshold masks. Default is False which means any Thresholds
3344          in the file are ignored.
3345        '''
3346        G2fil.readMasks(filename,self.data['Masks'],ignoreThreshold)
3347        print('file {} read into {}'.format(filename,self.name))
3348       
3349    def Recalibrate(self):
3350        ImageZ = GetCorrImage(Readers['Image'],self.proj,self)
3351        G2img.ImageRecalibrate(None,ImageZ,self.data['Image Controls'],self.data['Masks'])
3352       
3353##########################
3354# Command Line Interface #
3355##########################
3356# Each of these takes an argparse.Namespace object as their argument,
3357# representing the parsed command-line arguments for the relevant subcommand.
3358# The argument specification for each is in the subcommands dictionary (see
3359# below)
3360
3361commandhelp={}
3362commandhelp["create"] = "creates a GSAS-II project, optionally adding histograms and/or phases"
3363def create(args):
3364    """Implements the create command-line subcommand. This creates a GSAS-II project, optionally adding histograms and/or phases::
3365
3366  usage: GSASIIscriptable.py create [-h] [-d HISTOGRAMS [HISTOGRAMS ...]]
3367                                  [-i IPARAMS [IPARAMS ...]]
3368                                  [-p PHASES [PHASES ...]]
3369                                  filename
3370                                 
3371positional arguments::
3372
3373  filename              the project file to create. should end in .gpx
3374
3375optional arguments::
3376
3377  -h, --help            show this help message and exit
3378  -d HISTOGRAMS [HISTOGRAMS ...], --histograms HISTOGRAMS [HISTOGRAMS ...]
3379                        list of datafiles to add as histograms
3380  -i IPARAMS [IPARAMS ...], --iparams IPARAMS [IPARAMS ...]
3381                        instrument parameter file, must be one for every
3382                        histogram
3383  -p PHASES [PHASES ...], --phases PHASES [PHASES ...]
3384                        list of phases to add. phases are automatically
3385                        associated with all histograms given.
3386
3387    """
3388    proj = G2Project(gpxname=args.filename)
3389
3390    hist_objs = []
3391    if args.histograms:
3392        for h,i in zip(args.histograms,args.iparams):
3393            print("Adding histogram from",h,"with instparm ",i)
3394            hist_objs.append(proj.add_powder_histogram(h, i))
3395
3396    if args.phases: 
3397        for p in args.phases:
3398            print("Adding phase from",p)
3399            proj.add_phase(p, histograms=hist_objs)
3400        print('Linking phase(s) to histogram(s):')
3401        for h in hist_objs:
3402            print ('   '+h.name)
3403
3404    proj.save()
3405
3406commandhelp["add"] = "adds histograms and/or phases to GSAS-II project"
3407def add(args):
3408    """Implements the add command-line subcommand. This adds histograms and/or phases to GSAS-II project::
3409
3410  usage: GSASIIscriptable.py add [-h] [-d HISTOGRAMS [HISTOGRAMS ...]]
3411                               [-i IPARAMS [IPARAMS ...]]
3412                               [-hf HISTOGRAMFORMAT] [-p PHASES [PHASES ...]]
3413                               [-pf PHASEFORMAT] [-l HISTLIST [HISTLIST ...]]
3414                               filename
3415
3416
3417positional arguments::
3418
3419  filename              the project file to open. Should end in .gpx
3420
3421optional arguments::
3422
3423  -h, --help            show this help message and exit
3424  -d HISTOGRAMS [HISTOGRAMS ...], --histograms HISTOGRAMS [HISTOGRAMS ...]
3425                        list of datafiles to add as histograms
3426  -i IPARAMS [IPARAMS ...], --iparams IPARAMS [IPARAMS ...]
3427                        instrument parameter file, must be one for every
3428                        histogram
3429  -hf HISTOGRAMFORMAT, --histogramformat HISTOGRAMFORMAT
3430                        format hint for histogram import. Applies to all
3431                        histograms
3432  -p PHASES [PHASES ...], --phases PHASES [PHASES ...]
3433                        list of phases to add. phases are automatically
3434                        associated with all histograms given.
3435  -pf PHASEFORMAT, --phaseformat PHASEFORMAT
3436                        format hint for phase import. Applies to all phases.
3437                        Example: -pf CIF
3438  -l HISTLIST [HISTLIST ...], --histlist HISTLIST [HISTLIST ...]
3439                        list of histgram indices to associate with added
3440                        phases. If not specified, phases are associated with
3441                        all previously loaded histograms. Example: -l 2 3 4
3442   
3443    """
3444    proj = G2Project(args.filename)
3445
3446    if args.histograms:
3447        for h,i in zip(args.histograms,args.iparams):
3448            print("Adding histogram from",h,"with instparm ",i)
3449            proj.add_powder_histogram(h, i, fmthint=args.histogramformat)
3450
3451    if args.phases: 
3452        if not args.histlist:
3453            histlist = proj.histograms()
3454        else:
3455            histlist = [proj.histogram(i) for i in args.histlist]
3456
3457        for p in args.phases:
3458            print("Adding phase from",p)
3459            proj.add_phase(p, histograms=histlist, fmthint=args.phaseformat)
3460           
3461        if not args.histlist:
3462            print('Linking phase(s) to all histogram(s)')
3463        else:
3464            print('Linking phase(s) to histogram(s):')
3465            for h in histlist:
3466                print ('   '+h.name)
3467
3468    proj.save()
3469
3470
3471commandhelp["dump"] = "Shows the contents of a GSAS-II project"
3472def dump(args):
3473    """Implements the dump command-line subcommand, which shows the contents of a GSAS-II project::
3474
3475       usage: GSASIIscriptable.py dump [-h] [-d] [-p] [-r] files [files ...]
3476
3477positional arguments::
3478
3479  files
3480
3481optional arguments::
3482
3483  -h, --help        show this help message and exit
3484  -d, --histograms  list histograms in files, overrides --raw
3485  -p, --phases      list phases in files, overrides --raw
3486  -r, --raw         dump raw file contents, default
3487 
3488    """
3489    if not args.histograms and not args.phases:
3490        args.raw = True
3491    if args.raw:
3492        import IPython.lib.pretty as pretty
3493
3494    for fname in args.files:
3495        if args.raw:
3496            proj, nameList = LoadDictFromProjFile(fname)
3497            print("file:", fname)
3498            print("names:", nameList)
3499            for key, val in proj.items():
3500                print(key, ":")
3501                pretty.pprint(val)
3502        else:
3503            proj = G2Project(fname)
3504            if args.histograms:
3505                hists = proj.histograms()
3506                for h in hists:
3507                    print(fname, "hist", h.id, h.name)
3508            if args.phases:
3509                phase_list = proj.phases()
3510                for p in phase_list:
3511                    print(fname, "phase", p.id, p.name)
3512
3513
3514commandhelp["browse"] = "Load a GSAS-II project and then open a IPython shell to browse it"
3515def IPyBrowse(args):
3516    """Load a .gpx file and then open a IPython shell to browse it::
3517
3518  usage: GSASIIscriptable.py browse [-h] files [files ...]
3519
3520positional arguments::
3521
3522  files       list of files to browse
3523
3524optional arguments::
3525
3526  -h, --help  show this help message and exit
3527
3528    """
3529    for fname in args.files:
3530        proj, nameList = LoadDictFromProjFile(fname)
3531        msg = "\nfname {} loaded into proj (dict) with names in nameList".format(fname)
3532        GSASIIpath.IPyBreak_base(msg)
3533        break
3534
3535
3536commandhelp["refine"] = '''
3537Conducts refinements on GSAS-II projects according to a list of refinement
3538steps in a JSON dict
3539'''
3540def refine(args):
3541    """Implements the refine command-line subcommand:
3542    conducts refinements on GSAS-II projects according to a JSON refinement dict::
3543
3544        usage: GSASIIscriptable.py refine [-h] gpxfile [refinements]
3545
3546positional arguments::
3547
3548  gpxfile      the project file to refine
3549  refinements  json file of refinements to apply. if not present refines file
3550               as-is
3551
3552optional arguments::
3553
3554  -h, --help   show this help message and exit
3555 
3556    """
3557    proj = G2Project(args.gpxfile)
3558    if args.refinements is None:
3559        proj.refine()
3560    else:
3561        import json
3562        with open(args.refinements) as refs:
3563            refs = json.load(refs)
3564        if type(refs) is not dict:
3565            raise G2ScriptException("Error: JSON object must be a dict.")
3566        if "code" in refs:
3567            print("executing code:\n|  ",'\n|  '.join(refs['code']))
3568            exec('\n'.join(refs['code']))
3569        proj.do_refinements(refs['refinements'])
3570
3571
3572commandhelp["seqrefine"] = "Not implemented. Placeholder for eventual sequential refinement implementation"
3573def seqrefine(args):
3574    """Future implementation for the seqrefine command-line subcommand """
3575    raise NotImplementedError("seqrefine is not yet implemented")
3576
3577
3578commandhelp["export"] = "Export phase as CIF"
3579def export(args):
3580    """Implements the export command-line subcommand: Exports phase as CIF::
3581
3582      usage: GSASIIscriptable.py export [-h] gpxfile phase exportfile
3583
3584positional arguments::
3585
3586  gpxfile     the project file from which to export
3587  phase       identifier of phase to export
3588  exportfile  the .cif file to export to
3589
3590optional arguments::
3591
3592  -h, --help  show this help message and exit
3593
3594    """
3595    proj = G2Project(args.gpxfile)
3596    phase = proj.phase(args.phase)
3597    phase.export_CIF(args.exportfile)
3598
3599
3600def _args_kwargs(*args, **kwargs):
3601    return args, kwargs
3602
3603# A dictionary of the name of each subcommand, and a tuple
3604# of its associated function and a list of its arguments
3605# The arguments are passed directly to the add_argument() method
3606# of an argparse.ArgumentParser
3607
3608subcommands = {"create":
3609               (create, [_args_kwargs('filename',
3610                                      help='the project file to create. should end in .gpx'),
3611
3612                         _args_kwargs('-d', '--histograms',
3613                                      nargs='+',
3614                                      help='list of datafiles to add as histograms'),
3615                                     
3616                         _args_kwargs('-i', '--iparams',
3617                                      nargs='+',
3618                                      help='instrument parameter file, must be one'
3619                                           ' for every histogram'
3620                                      ),
3621
3622                         _args_kwargs('-p', '--phases',
3623                                      nargs='+',
3624                                      help='list of phases to add. phases are '
3625                                           'automatically associated with all '
3626                                           'histograms given.')]),
3627               "add": (add, [_args_kwargs('filename',
3628                                      help='the project file to open. Should end in .gpx'),
3629
3630                         _args_kwargs('-d', '--histograms',
3631                                      nargs='+',
3632                                      help='list of datafiles to add as histograms'),
3633                                     
3634                         _args_kwargs('-i', '--iparams',
3635                                      nargs='+',
3636                                      help='instrument parameter file, must be one'
3637                                           ' for every histogram'
3638                                      ),
3639                                     
3640                         _args_kwargs('-hf', '--histogramformat',
3641                                      help='format hint for histogram import. Applies to all'
3642                                           ' histograms'
3643                                      ),
3644
3645                         _args_kwargs('-p', '--phases',
3646                                      nargs='+',
3647                                      help='list of phases to add. phases are '
3648                                           'automatically associated with all '
3649                                           'histograms given.'),
3650
3651                         _args_kwargs('-pf', '--phaseformat',
3652                                      help='format hint for phase import. Applies to all'
3653                                           ' phases. Example: -pf CIF'
3654                                      ),
3655                                     
3656                         _args_kwargs('-l', '--histlist',
3657                                      nargs='+',
3658                                      help='list of histgram indices to associate with added'
3659                                           ' phases. If not specified, phases are'
3660                                           ' associated with all previously loaded'
3661                                           ' histograms. Example: -l 2 3 4')]),
3662                                           
3663               "dump": (dump, [_args_kwargs('-d', '--histograms',
3664                                     action='store_true',
3665                                     help='list histograms in files, overrides --raw'),
3666
3667                               _args_kwargs('-p', '--phases',
3668                                            action='store_true',
3669                                            help='list phases in files, overrides --raw'),
3670
3671                               _args_kwargs('-r', '--raw',
3672                                      action='store_true', help='dump raw file contents, default'),
3673
3674                               _args_kwargs('files', nargs='+')]),
3675
3676               "refine":
3677               (refine, [_args_kwargs('gpxfile', help='the project file to refine'),
3678                         _args_kwargs('refinements',
3679                                      help='JSON file of refinements to apply. if not present'
3680                                           ' refines file as-is',
3681                                      default=None,
3682                                      nargs='?')]),
3683
3684               "seqrefine": (seqrefine, []),
3685               "export": (export, [_args_kwargs('gpxfile',
3686                                                help='the project file from which to export'),
3687                                   _args_kwargs('phase', help='identifier of phase to export'),
3688                                   _args_kwargs('exportfile', help='the .cif file to export to')]),
3689               "browse": (IPyBrowse, [_args_kwargs('files', nargs='+',
3690                                                   help='list of files to browse')])}
3691
3692
3693def main():
3694    '''The command-line interface for calling GSASIIscriptable as a shell command,
3695    where it is expected to be called as::
3696
3697       python GSASIIscriptable.py <subcommand> <file.gpx> <options>
3698
3699    The following subcommands are defined:
3700
3701        * create, see :func:`create`
3702        * add, see :func:`add`
3703        * dump, see :func:`dump`
3704        * refine, see :func:`refine`
3705        * seqrefine, see :func:`seqrefine`
3706        * export, :func:`export`
3707        * browse, see :func:`IPyBrowse`
3708
3709    .. seealso::
3710        :func:`create`
3711        :func:`add`
3712        :func:`dump`
3713        :func:`refine`
3714        :func:`seqrefine`
3715        :func:`export`
3716        :func:`IPyBrowse`
3717    '''
3718    parser = argparse.ArgumentParser(description=
3719        "Use of "+os.path.split(__file__)[1]+" Allows GSAS-II actions from command line."
3720        )
3721    subs = parser.add_subparsers()
3722
3723    # Create all of the specified subparsers
3724    for name, (func, args) in subcommands.items():
3725        new_parser = subs.add_parser(name,help=commandhelp.get(name),
3726                                     description='Command "'+name+'" '+commandhelp.get(name))
3727        for listargs, kwds in args:
3728            new_parser.add_argument(*listargs, **kwds)
3729        new_parser.set_defaults(func=func)
3730
3731    # Parse and trigger subcommand
3732    result = parser.parse_args()
3733    result.func(result)
3734
3735if __name__ == '__main__':
3736    #fname='/tmp/corundum-template.gpx'
3737    #prj = G2Project(fname)
3738    main()
Note: See TracBrowser for help on using the repository browser.