source: trunk/GSASII.py @ 1030

Last change on this file since 1030 was 1030, checked in by vondreele, 10 years ago

mods to CIF export
add residuals to histogram GUI
save histogram residual info in data[0] dictionary

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 117.4 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#GSASII
4########### SVN repository information ###################
5# $Date: 2013-08-13 19:25:42 +0000 (Tue, 13 Aug 2013) $
6# $Author: vondreele $
7# $Revision: 1030 $
8# $URL: trunk/GSASII.py $
9# $Id: GSASII.py 1030 2013-08-13 19:25:42Z vondreele $
10########### SVN repository information ###################
11'''
12*GSAS-II Main Module*
13=====================
14
15Main routines for the GSAS-II program
16'''
17
18import os
19import sys
20import math
21import copy
22import random as ran
23import time
24import copy
25import glob
26import imp
27import inspect
28import numpy as np
29import scipy as sp
30import wx
31import matplotlib as mpl
32import wx.lib.inspection as wxeye
33try:
34    import OpenGL as ogl
35except ImportError:
36    print('*******************************************************')
37    print('PyOpenGL is missing from your python installation')
38    print('     - we will try to install it')
39    print('*******************************************************')
40    def install_with_easyinstall(package):
41        try: 
42            print "trying a system-wide PyOpenGl install"
43            easy_install.main(['-f',os.path.split(__file__)[0],package])
44            return
45        except:
46            pass
47        try: 
48            print "trying a user level PyOpenGl install"
49            easy_install.main(['-f',os.path.split(__file__)[0],'--user',package])
50            return
51        except:
52            print "Install of '+package+' failed. Please report this information:"
53            import traceback
54            print traceback.format_exc()
55            sys.exit()
56    from setuptools.command import easy_install
57    install_with_easyinstall('PyOpenGl')
58    print('*******************************************************')         
59    print('OpenGL has been installed. Please restart GSAS-II')
60    print('*******************************************************')         
61    sys.exit()
62
63# load the GSAS routines
64import GSASIIpath
65GSASIIpath.SetVersionNumber("$Revision: 1030 $")
66import GSASIIIO as G2IO
67import GSASIIgrid as G2gd
68import GSASIIplot as G2plt
69import GSASIIpwd as G2pwd
70import GSASIIpwdGUI as G2pdG
71import GSASIIspc as G2spc
72import GSASIIstrMain as G2stMn
73import GSASIIstrIO as G2stIO
74import GSASIImapvars as G2mv
75import GSASIIsolve as G2sol
76
77#wx inspector - use as needed
78wxInspector = False
79
80# print versions
81print "Python module versions loaded:"
82print "python:     ",sys.version[:5]
83print "wxpython:   ",wx.__version__
84print "matplotlib: ",mpl.__version__
85print "numpy:      ",np.__version__
86print "scipy:      ",sp.__version__
87print "OpenGL:     ",ogl.__version__
88try:
89    import mkl
90    print "Max threads ",mkl.get_max_threads()
91except:
92    pass
93#    print "MKL module not present"
94__version__ = '0.2.0'
95G2gd.__version__ = __version__
96print "This is GSAS-II version:     ",__version__,' revision '+str(GSASIIpath.GetVersionNumber())
97
98# useful degree trig functions
99sind = lambda x: math.sin(x*math.pi/180.)
100cosd = lambda x: math.cos(x*math.pi/180.)
101tand = lambda x: math.tan(x*math.pi/180.)
102asind = lambda x: 180.*math.asin(x)/math.pi
103acosd = lambda x: 180.*math.acos(x)/math.pi
104atan2d = lambda x,y: 180.*math.atan2(y,x)/math.pi
105
106def create(parent):
107    return GSASII(parent)
108
109class GSASII(wx.Frame):
110    '''Define the main GSAS-II frame and its associated menu items
111    '''
112    def _Add_FileMenuItems(self, parent):
113        item = parent.Append(
114            help='Open a gsasii project file (*.gpx)', id=wx.ID_ANY,
115            kind=wx.ITEM_NORMAL,text='&Open project...')
116        self.Bind(wx.EVT_MENU, self.OnFileOpen, id=item.GetId())
117        item = parent.Append(
118            help='Save project to old file', id=wx.ID_ANY,
119            kind=wx.ITEM_NORMAL,text='&Save project')
120        self.Bind(wx.EVT_MENU, self.OnFileSave, id=item.GetId())
121        item = parent.Append(
122            help='Save project to new file', id=wx.ID_ANY,
123            kind=wx.ITEM_NORMAL,text='Save As...')
124        self.Bind(wx.EVT_MENU, self.OnFileSaveas, id=item.GetId())
125        item = parent.Append(
126            help='Close project, saving is optional', id=wx.ID_ANY,
127            kind=wx.ITEM_NORMAL,text='&Close project')
128        self.Bind(wx.EVT_MENU, self.OnFileClose, id=item.GetId())
129        item = parent.Append(
130            help='Exit from gsasii', id=wx.ID_ANY,
131            kind=wx.ITEM_NORMAL,text='&Exit')
132        self.Bind(wx.EVT_MENU, self.OnFileExit, id=item.GetId())
133       
134    def _Add_DataMenuItems(self,parent):
135        item = parent.Append(
136            help='',id=wx.ID_ANY,
137            kind=wx.ITEM_NORMAL,
138            text='Read image data...')
139        self.Bind(wx.EVT_MENU, self.OnImageRead, id=item.GetId())
140        item = parent.Append(
141            help='',id=wx.ID_ANY,
142            kind=wx.ITEM_NORMAL,
143            text='Read Powder Pattern Peaks...')
144        self.Bind(wx.EVT_MENU, self.OnReadPowderPeaks, id=item.GetId())
145        item = parent.Append(
146            help='',id=wx.ID_ANY,
147            kind=wx.ITEM_NORMAL,
148            text='Sum powder data')
149        self.Bind(wx.EVT_MENU, self.OnPwdrSum, id=item.GetId())
150        item = parent.Append(
151            help='',id=wx.ID_ANY,
152            kind=wx.ITEM_NORMAL,
153            text='Sum image data')
154        self.Bind(wx.EVT_MENU, self.OnImageSum, id=item.GetId())
155        item = parent.Append(
156            help='',id=wx.ID_ANY,
157            kind=wx.ITEM_NORMAL,
158            text='Add phase')
159        self.Bind(wx.EVT_MENU, self.OnAddPhase, id=item.GetId())
160        item = parent.Append(
161            help='',id=wx.ID_ANY,
162            kind=wx.ITEM_NORMAL,
163            text='Delete phase')
164        self.Bind(wx.EVT_MENU, self.OnDeletePhase, id=item.GetId())
165        item = parent.Append(
166            help='',id=wx.ID_ANY,
167            kind=wx.ITEM_NORMAL,
168            text='Rename data') 
169        self.Bind(wx.EVT_MENU, self.OnRenameData, id=item.GetId())
170        item = parent.Append(
171            help='',id=wx.ID_ANY,
172            kind=wx.ITEM_NORMAL,
173            text='Delete data')
174        self.Bind(wx.EVT_MENU, self.OnDataDelete, id=item.GetId())
175               
176    def _Add_CalculateMenuItems(self,parent):
177        item = parent.Append(help='Make new PDFs from selected powder patterns', 
178            id=wx.ID_ANY, kind=wx.ITEM_NORMAL,text='Make new PDFs')
179        self.MakePDF.append(item)
180#        item.Enable(False)
181        self.Bind(wx.EVT_MENU, self.OnMakePDFs, id=item.GetId())
182       
183        item = parent.Append(help='View least squares parameters', 
184            id=wx.ID_ANY, kind=wx.ITEM_NORMAL,text='&View LS parms')
185        self.Bind(wx.EVT_MENU, self.OnViewLSParms, id=item.GetId())
186       
187        item = parent.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
188            text='&Refine')
189        self.Refine.append(item)
190        item.Enable(False)
191        self.Bind(wx.EVT_MENU, self.OnRefine, id=item.GetId())
192       
193        item = parent.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL,
194            text='Sequental refine')
195        self.SeqRefine.append(item)
196        item.Enable(False)
197        self.Bind(wx.EVT_MENU, self.OnSeqRefine, id=item.GetId())
198       
199    def _init_Imports(self):
200        '''import all the G2phase*.py & G2sfact*.py & G2pwd*.py files that
201        are found in the path
202        '''
203
204        self.ImportPhaseReaderlist = []
205        self._init_Import_routines('phase',self.ImportPhaseReaderlist,'Phase')
206        self.ImportSfactReaderlist = []
207        self._init_Import_routines('sfact',self.ImportSfactReaderlist,'Struct_Factor')
208        self.ImportPowderReaderlist = []
209        self._init_Import_routines('pwd',self.ImportPowderReaderlist,'Powder_Data')
210        self.ImportMenuId = {}
211
212    def _init_Import_routines(self,prefix,readerlist,errprefix):
213        '''import all the import readers matching the prefix
214        '''
215        #path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # location of this file
216        #pathlist = sys.path[:]
217        #if path2GSAS2 not in pathlist: pathlist.append(path2GSAS2)
218        #path2GSAS2 = os.path.join(
219        #    os.path.dirname(os.path.realpath(__file__)), # location of this file
220        #    'imports')
221        pathlist = sys.path[:]
222        #if path2GSAS2 not in pathlist: pathlist.append(path2GSAS2)
223
224        filelist = []
225        for path in pathlist:
226            for filename in glob.iglob(os.path.join(
227                path,
228                "G2"+prefix+"*.py")):
229                filelist.append(filename)   
230                #print 'debug: found',filename
231        filelist = sorted(list(set(filelist))) # remove duplicates
232        for filename in filelist:
233            path,rootname = os.path.split(filename)
234            pkg = os.path.splitext(rootname)[0]
235            try:
236                fp = None
237                fp, fppath,desc = imp.find_module(pkg,[path,])
238                pkg = imp.load_module(pkg,fp,fppath,desc)
239                for clss in inspect.getmembers(pkg): # find classes defined in package
240                    if clss[0].startswith('_'): continue
241                    if inspect.isclass(clss[1]):
242                        # check if we have the required methods
243                        for m in 'Reader','ExtensionValidator','ContentsValidator':
244                            if not hasattr(clss[1],m): break
245                            if not callable(getattr(clss[1],m)): break
246                        else:
247                            reader = clss[1]() # create an import instance
248                            readerlist.append(reader)
249            except AttributeError:
250                print 'Import_'+errprefix+': Attribute Error'+str(filename)
251                pass
252            except ImportError:
253                print 'Import_'+errprefix+': Error importing file'+str(filename)
254                pass
255            if fp: fp.close()
256
257    def OnImportGeneric(self,reader,readerlist,label,multiple=False):
258        '''Used to import Phases, powder dataset or single
259        crystal datasets (structure factor tables) using reader objects
260        subclassed from GSASIIIO.ImportPhase, GSASIIIO.ImportStructFactor
261        or GSASIIIO.ImportPowderData. If a reader is specified, only
262        that will be attempted, but if no reader is specified, every one
263        that is potentially compatible (by file extension) will
264        be tried on the selected file(s).
265
266        :param readerobject reader: This will be a reference to
267          a particular object to be used to read a file or None,
268          if every appropriate reader should be used.
269
270        :param list readerlist: a list of reader objects appropriate for
271          the current read attempt. At present, this will be either
272          self.ImportPhaseReaderlist, self.ImportSfactReaderlist or
273          self.ImportPowderReaderlist (defined in _init_Imports from
274          the files found in the path), but in theory this list could
275          be tailored. Used only when reader is None.
276
277        :param str label: string to place on the open file dialog:
278          Open `label` input file
279
280        :param bool multiple: True if multiple files can be selected
281          in the file dialog. False is default. At present True is used
282          only for reading of powder data.
283         
284        :returns: a list of reader objects (rd_list) that were able
285          to read the specified file(s). This list may be empty.
286        '''
287        self.lastimport = ''
288        self.zipfile = None
289        if reader is None:
290            multiple = False
291            #print "use all formats"
292            choices = "any file (*.*)|*.*"
293            choices += "|zip archive (.zip)|*.zip"
294            extdict = {}
295            # compile a list of allowed extensions
296            for rd in readerlist:
297                fmt = rd.formatName
298                for extn in rd.extensionlist:
299                    if not extdict.get(extn): extdict[extn] = []
300                    extdict[extn] += [fmt,]
301            for extn in sorted(extdict.keys(),cmp=lambda x,y: cmp(x.lower(), y.lower())):
302                fmt = ''
303                for f in extdict[extn]:
304                    if fmt != "": fmt += ', '
305                    fmt += f
306                choices += "|" + fmt + " file (*" + extn + ")|*" + extn
307        else:
308            readerlist = [reader,]
309            # compile a list of allowed extensions
310            choices = reader.formatName + " file ("
311            w = ""
312            for extn in reader.extensionlist:
313                if w != "": w += ";"
314                w += "*" + extn
315            choices += w + ")|" + w
316            choices += "|zip archive (.zip)|*.zip"
317            if not reader.strictExtension:
318                choices += "|any file (*.*)|*.*"
319        # get the file(s)
320        if multiple:
321            mode = style=wx.OPEN | wx.CHANGE_DIR | wx.MULTIPLE
322        else:
323            mode = style=wx.OPEN | wx.CHANGE_DIR
324        dlg = wx.FileDialog(self, message="Choose "+label+" input file",
325            defaultFile="",wildcard=choices, style=mode)
326        try:
327            if dlg.ShowModal() == wx.ID_OK:
328                if multiple:
329                    filelist = dlg.GetPaths()
330                    if len(filelist) == 0: return []
331                else:
332                    filename = dlg.GetPath()
333                    filelist = [filename,]
334                os.chdir(dlg.GetDirectory())           # to get Mac/Linux to change directory!
335            else: # cancel was pressed
336                return []
337        finally:
338            dlg.Destroy()
339        rd_list = []
340        filelist1 = []
341        for filename in filelist:
342            # is this a zip file?
343            if os.path.splitext(filename)[1].lower() == '.zip':
344                extractedfiles = G2IO.ExtractFileFromZip(
345                    filename,parent=self,
346                    multipleselect=True)
347                if extractedfiles is None: continue # error or Cancel
348                if extractedfiles != filename:
349                    self.zipfile = filename # save zip name
350                    filelist1 += extractedfiles
351                    continue
352            filelist1.append(filename)
353        filelist = filelist1
354        for filename in filelist:
355            # is this a zip file?
356            if os.path.splitext(filename)[1].lower() == '.zip':
357                extractedfile = G2IO.ExtractFileFromZip(filename,parent=self)
358                if extractedfile is None: continue # error or Cancel
359                if extractedfile != filename:
360                    filename,self.zipfile = extractedfile,filename # now use the file that was created
361            # set what formats are compatible with this file
362            primaryReaders = []
363            secondaryReaders = []
364            for r in readerlist:
365                flag = r.ExtensionValidator(filename)
366                if flag is None: 
367                    secondaryReaders.append(r)
368                elif flag:
369                    primaryReaders.append(r)
370            if len(secondaryReaders) + len(primaryReaders) == 0:
371                self.ErrorDialog('No Format','No matching format for file '+filename)
372                return []
373
374            fp = None
375            msg = ''
376            try:
377                fp = open(filename,'Ur')
378                if len(filelist) == 1:
379                    # confirm we have the right file
380                    rdmsg = 'File '+str(filename)+' begins:\n\n'
381                    for i in range(3):
382                        rdmsg += fp.readline()
383                    rdmsg += '\n\nDo you want to read this file?'
384                    if not all([ord(c) < 128 and ord(c) != 0 for c in rdmsg]): # show only if ASCII
385                        rdmsg = 'File '+str(
386                            filename)+' is a binary file. Do you want to read this file?'
387                    result = wx.ID_NO
388                    # it would be better to use something that
389                    # would resize better, but this will do for now
390                    dlg = wx.MessageDialog(
391                        self, rdmsg,
392                        'Is this the file you want?', 
393                        wx.YES_NO | wx.ICON_QUESTION,
394                        )
395                    dlg.SetSize((700,300)) # does not resize on Mac
396                    try:
397                        result = dlg.ShowModal()
398                    finally:
399                        dlg.Destroy()
400                    if result == wx.ID_NO: return []
401                           
402                self.lastimport = filename
403                # try the file first with Readers that specify the
404                # files extension and later with ones that allow it
405                flag = False
406                for rd in primaryReaders+secondaryReaders:
407                    try:
408                        fp.seek(0)  # rewind
409                        if not rd.ContentsValidator(fp): continue # rejected on cursory check
410                        repeat = True
411                        rdbuffer = {} # create temporary storage for file reader
412                        block = 0
413                        while repeat:
414                            block += 1
415                            repeat = False
416                            fp.seek(0)  # rewind
417                            rd.objname = os.path.basename(filename)
418                            flag = rd.Reader(filename,fp,self,
419                                             buffer=rdbuffer,
420                                             blocknum=block)
421                            if flag:
422                                rd_list.append(copy.deepcopy(rd)) # success
423                                if rd.repeat: repeat = True
424                    except:
425                        import traceback
426                        print traceback.format_exc()
427                        msg += '\nError reading file '+filename+' with format '+ rd.formatName
428                        #self.ErrorDialog('Read Error',
429                        #                 'Error reading file '+filename
430                        #                 +' with format '+ rd.formatName)
431                        continue
432                    if flag:
433                        if rd.warnings:
434                            self.ErrorDialog('Read Warning','The '+ rd.formatName+
435                                             ' reader reported a warning message:\n\n'+
436                                             rd.warnings)
437                        break # success reading
438                else:
439                    if reader:
440                        self.ErrorDialog('Read Error','The '+ rd.formatName+
441                                         ' reader was not able to read file '+filename+msg)
442                    else:
443                        self.ErrorDialog('Read Error','No reader is able to read file '+filename+msg)
444            except:
445                import traceback
446                print traceback.format_exc()
447                self.ErrorDialog('Open Error','Error on open of file '+filename)
448            if fp: fp.close()
449        return rd_list
450
451    def _Add_ImportMenu_Phase(self,parent):
452        '''configure the Import Phase menus accord to the readers found in _init_Imports
453        '''
454        submenu = wx.Menu()
455        item = parent.AppendMenu(wx.ID_ANY, 'Phase',
456            submenu, help='Import phase data')
457        for reader in self.ImportPhaseReaderlist:
458            item = submenu.Append(wx.ID_ANY,help=reader.longFormatName,
459                kind=wx.ITEM_NORMAL,text='from '+reader.formatName+' file')
460            self.ImportMenuId[item.GetId()] = reader
461            self.Bind(wx.EVT_MENU, self.OnImportPhase, id=item.GetId())
462        item = submenu.Append(wx.ID_ANY,
463                              help='Import phase data, use file to try to determine format',
464                              kind=wx.ITEM_NORMAL,
465                              text='guess format from file')
466        self.Bind(wx.EVT_MENU, self.OnImportPhase, id=item.GetId())
467
468    def OnImportPhase(self,event):
469        '''Called in response to an Import/Phase/... menu item
470        to read phase information.
471        dict self.ImportMenuId is used to look up the specific
472        reader item associated with the menu item, which will be
473        None for the last menu item, which is the "guess" option
474        where all appropriate formats will be tried.
475        '''
476        # look up which format was requested
477        reqrdr = self.ImportMenuId.get(event.GetId())
478        rdlist = self.OnImportGeneric(reqrdr,
479                                  self.ImportPhaseReaderlist,
480                                  'phase')
481        if len(rdlist) == 0: return
482        # for now rdlist is only expected to have one element
483        # but this will allow multiple phases to be imported
484        self.CheckNotebook()
485        for rd in rdlist:
486            dlg = wx.TextEntryDialog( # allow editing of phase name
487                self, 'Enter the name for the new phase',
488                'Edit phase name', rd.Phase['General']['Name'],
489                style=wx.OK)
490            dlg.CenterOnParent()
491            if dlg.ShowModal() == wx.ID_OK:
492                rd.Phase['General']['Name'] = dlg.GetValue()
493            dlg.Destroy()
494            PhaseName = rd.Phase['General']['Name']
495            print 'Read phase '+str(PhaseName)+' from file '+str(self.lastimport)
496            if not G2gd.GetPatternTreeItemId(self,self.root,'Phases'):
497                sub = self.PatternTree.AppendItem(parent=self.root,text='Phases')
498            else:
499                sub = G2gd.GetPatternTreeItemId(self,self.root,'Phases')
500            psub = self.PatternTree.AppendItem(parent=sub,text=PhaseName)
501            self.PatternTree.SetItemPyData(psub,rd.Phase)
502            self.PatternTree.Expand(self.root) # make sure phases are seen
503            self.PatternTree.Expand(sub) 
504            self.PatternTree.Expand(psub) 
505        return # success
506       
507    def _Add_ImportMenu_Sfact(self,parent):
508        '''configure the Import Structure Factor menus accord to the readers found in _init_Imports
509        '''
510        submenu = wx.Menu()
511        item = parent.AppendMenu(wx.ID_ANY, 'Structure Factor',
512            submenu, help='Import Structure Factor data')
513        for reader in self.ImportSfactReaderlist:
514            item = submenu.Append(wx.ID_ANY,help=reader.longFormatName,               
515                kind=wx.ITEM_NORMAL,text='from '+reader.formatName+' file')
516            self.ImportMenuId[item.GetId()] = reader
517            self.Bind(wx.EVT_MENU, self.OnImportSfact, id=item.GetId())
518        item = submenu.Append(wx.ID_ANY,
519            help='Import Structure Factor, use file to try to determine format',
520            kind=wx.ITEM_NORMAL,
521            text='guess format from file')
522        self.Bind(wx.EVT_MENU, self.OnImportSfact, id=item.GetId())
523
524    def OnImportSfact(self,event):
525        '''Called in response to an Import/Structure Factor/... menu item
526        to read single crystal datasets.
527        dict self.ImportMenuId is used to look up the specific
528        reader item associated with the menu item, which will be
529        None for the last menu item, which is the "guess" option
530        where all appropriate formats will be tried.
531        '''
532        # look up which format was requested
533        reqrdr = self.ImportMenuId.get(event.GetId())
534        rdlist = self.OnImportGeneric(reqrdr,self.ImportSfactReaderlist,
535            'Structure Factor')
536        if len(rdlist) == 0: return
537        self.CheckNotebook()
538        for rd in rdlist:
539            HistName = rd.objname
540            if len(rdlist) <= 2: 
541                dlg = wx.TextEntryDialog( # allow editing of Structure Factor name
542                    self, 'Enter the name for the new Structure Factor',
543                    'Edit Structure Factor name', HistName,
544                    style=wx.OK)
545                dlg.CenterOnParent()
546                if dlg.ShowModal() == wx.ID_OK:
547                    HistName = dlg.GetValue()
548                dlg.Destroy()
549            print 'Read structure factor table '+str(HistName)+' from file '+str(self.lastimport)
550            Id = self.PatternTree.AppendItem(parent=self.root,
551                                             text='HKLF '+HistName)
552            self.PatternTree.SetItemPyData(Id,[{'wtFactor':1.0},rd.RefList])
553            Sub = self.PatternTree.AppendItem(Id,text='Instrument Parameters')
554            self.PatternTree.SetItemPyData(Sub,rd.Parameters)
555            self.PatternTree.SetItemPyData(
556                self.PatternTree.AppendItem(Id,text='HKL Plot Controls'),
557                rd.Controls)
558            self.PatternTree.SetItemPyData(
559                self.PatternTree.AppendItem(Id,text='Reflection List'),[])  #dummy entry for GUI use
560            self.PatternTree.SelectItem(Id)
561            self.PatternTree.Expand(Id)
562            self.Sngl = Id
563        return # success
564
565    def _Add_ImportMenu_powder(self,parent):
566        '''configure the Powder Data menus accord to the readers found in _init_Imports
567        '''
568        submenu = wx.Menu()
569        item = parent.AppendMenu(wx.ID_ANY, 'Powder Data',
570            submenu, help='Import Powder data')
571        for reader in self.ImportPowderReaderlist:
572            item = submenu.Append(wx.ID_ANY,help=reader.longFormatName,
573                kind=wx.ITEM_NORMAL,text='from '+reader.formatName+' file')
574            self.ImportMenuId[item.GetId()] = reader
575            self.Bind(wx.EVT_MENU, self.OnImportPowder, id=item.GetId())
576        item = submenu.Append(wx.ID_ANY,
577            help='Import powder data, use file to try to determine format',
578            kind=wx.ITEM_NORMAL,text='guess format from file')
579        self.Bind(wx.EVT_MENU, self.OnImportPowder, id=item.GetId())
580           
581    def ReadPowderInstprm(self,instfile):       #fix the write routine for [inst1,inst2] style
582        '''Read a GSAS-II (new) instrument parameter file
583
584        :param str instfile: name of instrument parameter file
585
586        '''
587        if os.path.splitext(instfile)[1].lower() != '.instprm': # invalid file
588            return None           
589        if not os.path.exists(instfile): # no such file
590            return None
591        File = open(instfile,'r')
592        S = File.readline()
593        if not S.startswith('#GSAS-II'): # not a valid file
594            File.close()
595            return None
596        newItems = []
597        newVals = []
598        while S:
599            if S[0] == '#':
600                S = File.readline()
601                continue
602            [item,val] = S[:-1].split(':')
603            newItems.append(item)
604            try:
605                newVals.append(float(val))
606            except ValueError:
607                newVals.append(val)                       
608            S = File.readline()               
609        File.close()
610        # add a second MT dict here. TOF parms? (BHT)
611        return G2IO.makeInstDict(newItems,newVals,len(newVals)*[False,]),{}
612       
613    def ReadPowderIparm(self,instfile,bank,databanks,rd):
614        '''Read a GSAS (old) instrument parameter file
615
616        :param str instfile: name of instrument parameter file
617
618        :param int bank: the bank number read in the raw data file
619
620        :param int databanks: the number of banks in the raw data file.
621          If the number of banks in the data and instrument parameter files
622          agree, then the sets of banks are assumed to match up and bank
623          is used to select the instrument parameter file. If not, the user
624          is asked to make a selection.
625
626        :param obj rd: the raw data (histogram) data object. This
627          sets rd.instbank.
628
629        '''
630        if not os.path.exists(instfile): # no such file
631            return {}
632        fp = 0
633        try:
634            fp = open(instfile,'Ur')
635            Iparm = {}
636            for S in fp:
637                Iparm[S[:12]] = S[12:-1]
638        except IOError:
639            print('Error reading file:'+str(instfile))
640        if fp:       
641            fp.close()
642
643        ibanks = int(Iparm.get('INS   BANK  ','1').strip())
644        hType = Iparm['INS   HTYPE '].strip()
645        if ibanks == 1: # there is only one bank here, return it
646            rd.instbank = 1
647            return Iparm
648        if 'PNT' in hType:
649            rd.instbank = bank
650        elif ibanks != databanks:
651            # number of banks in data and prm file not not agree, need a
652            # choice from a human here
653            choices = []
654            for i in range(1,1+ibanks):
655                choices.append('Bank '+str(i))
656            bank = rd.BlockSelector(
657                choices, self,
658                title='Select an instrument parameter bank for '+
659                os.path.split(rd.powderentry[0])[1]+' BANK '+str(bank)+
660                '\nOr use Cancel to select from the default parameter sets',
661                header='Block Selector')
662        if bank is None: return {}
663        # pull out requested bank # bank from the data, and change the bank to 1
664        IparmS = {}
665        for key in Iparm:
666            if key[4:6] == "  ":
667                IparmS[key] = Iparm[key]
668            elif int(key[4:6].strip()) == bank:
669                IparmS[key[:4]+' 1'+key[6:]] = Iparm[key]
670        rd.instbank = bank
671        return IparmS
672                       
673    def GetPowderIparm(self,rd, prevIparm, lastIparmfile, lastdatafile):
674        '''Open and read an instrument parameter file for a data file
675        Returns the list of parameters used in the data tree
676
677        :param obj rd: the raw data (histogram) data object.
678
679        :param str prevIparm: not used
680
681        :param str lastIparmfile: Name of last instrument parameter
682          file that was read, or a empty string.
683
684        :param str lastdatafile: Name of last data file that was read.
685
686        :returns: a list of two dicts, the first containing instrument parameters
687          and the second used for future TOF datasets (timemaps?)
688
689        '''
690        def SetPowderInstParms(Iparm, rd):
691            '''extracts values from instrument parameters in rd.instdict
692            or in array Iparm.
693            Create and return the contents of the instrument parameter tree entry.
694            '''
695            DataType = Iparm['INS   HTYPE '].strip()[:3]  # take 1st 3 chars
696            # override inst values with values read from data file
697            if rd.instdict.get('type'):
698                DataType = rd.instdict.get('type')
699            data = [DataType,]
700            instname = Iparm.get('INS  1INAME ')
701            if instname:
702                rd.Sample['InstrName'] = instname.strip()
703            if 'C' in DataType:
704                wave1 = None
705                wave2 = 0.0
706                if rd.instdict.get('wave'):
707                    wl = rd.instdict.get('wave')
708                    wave1 = wl[0]
709                    if len(wl) > 1: wave2 = wl[1]
710                s = Iparm['INS  1 ICONS']
711                if not wave1:
712                    wave1 = G2IO.sfloat(s[:10])
713                    wave2 = G2IO.sfloat(s[10:20])
714                v = (wave1,wave2,
715                     G2IO.sfloat(s[20:30]),G2IO.sfloat(s[55:65]),G2IO.sfloat(s[40:50])) #get lam1, lam2, zero, pola & ratio
716                if not v[1]:
717                    names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','SH/L','Azimuth'] 
718                    v = (v[0],v[2],v[4])
719                    codes = [0,0,0,0]
720                else:
721                    names = ['Type','Lam1','Lam2','Zero','I(L2)/I(L1)','Polariz.','U','V','W','X','Y','SH/L','Azimuth']
722                    codes = [0,0,0,0,0,0]
723                data.extend(v)
724                v1 = Iparm['INS  1PRCF1 '].split()                                                 
725                v = Iparm['INS  1PRCF11'].split()
726                data.extend([float(v[0]),float(v[1]),float(v[2])])                  #get GU, GV & GW - always here
727                azm = float(Iparm.get('INS  1DETAZM','0.0'))
728                v = Iparm['INS  1PRCF12'].split()
729                if v1[0] == 3:
730                    data.extend([float(v[0]),float(v[1]),float(v[2])+float(v[3],azm)])  #get LX, LY, S+H/L & azimuth
731                else:
732                    data.extend([0.0,0.0,0.002,azm])                                      #OK defaults if fxn #3 not 1st in iprm file
733                codes.extend([0,0,0,0,0,0,0])
734                return [G2IO.makeInstDict(names,data,codes),{}]
735            elif 'T' in DataType:
736                names = ['Type','2-theta','difC','difA','Zero','alpha','beta-0','beta-1',
737                    'beta-q','sig-0','sig-1','sig-q','X','Y','Azimuth']
738                codes = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
739                azm = 0.
740                if 'INS  1DETAZM' in Iparm:
741                    azm = float(Iparm['INS  1DETAZM'])
742                s = Iparm['INS  1BNKPAR'].split()
743                data.extend([G2IO.sfloat(s[1]),])               #2-theta for bank
744                s = Iparm['INS  1 ICONS'].split()
745                data.extend([G2IO.sfloat(s[0]),G2IO.sfloat(s[1]),G2IO.sfloat(s[2])])    #difC, difA, Zero
746                s = Iparm['INS  1PRCF1 '].split()
747                pfType = int(s[0])
748                s = Iparm['INS  1PRCF11'].split()
749                if abs(pfType) == 1:
750                    data.extend([G2IO.sfloat(s[1]),G2IO.sfloat(s[2]),G2IO.sfloat(s[3])])
751                    s = Iparm['INS  1PRCF12'].split()
752                    data.extend([0.0,0.0,G2IO.sfloat(s[1]),0.0,0.0,0.0,azm])
753                elif abs(pfType) in [3,4,5]:
754                    data.extend([G2IO.sfloat(s[0]),G2IO.sfloat(s[1]),G2IO.sfloat(s[2])])
755                    if abs(pfType) == 4:
756                        data.extend([0.0,0.0,G2IO.sfloat(s[3]),0.0,0.0,0.0,azm])
757                    else:
758                        s = Iparm['INS  1PRCF12'].split()
759                        data.extend([0.0,0.0,G2IO.sfloat(s[0]),0.0,0.0,0.0,azm])                       
760                Inst1 = G2IO.makeInstDict(names,data,codes)
761                Inst2 = {}
762                if pfType < 0:
763                    Ipab = 'INS  1PAB'+str(-pfType)
764                    Npab = int(Iparm[Ipab+'  '].strip())
765                    Inst2['Pdabc'] = []
766                    for i in range(Npab):
767                        k = Ipab+str(i+1).rjust(2)
768                        s = Iparm[k].split()
769                        Inst2['Pdabc'].append([float(t) for t in s])
770                    Inst2['Pdabc'] = np.array(Inst2['Pdabc'])
771                    Inst2['Pdabc'].T[3] += Inst2['Pdabc'].T[0]*Inst1['difC'][0] #turn 3rd col into TOF
772                if 'INS  1I ITYP' in Iparm:
773                    s = Iparm['INS  1I ITYP'].split()
774                    Ityp = int(s[0])
775                    Tminmax = [float(s[1])*1000.,float(s[2])*1000.]
776                    Itypes = ['Exponential','Maxwell/Exponential','','Maxwell/Chebyschev','']
777                    if Ityp in [1,2,4]:
778                        Inst2['Itype'] = Itypes[Ityp-1]
779                        Inst2['Tminmax'] = Tminmax
780                        Icoeff = []
781                        Iesd = []
782                        Icovar = []                   
783                        for i in range(3):
784                            s = Iparm['INS  1ICOFF'+str(i+1)].split()
785                            Icoeff += [float(S) for S in s]
786                            s = Iparm['INS  1IECOF'+str(i+1)].split()
787                            Iesd += [float(S) for S in s]
788                        for i in range(8):
789                            s = Iparm['INS  1IECOR'+str(i+1)].split()
790                            Icovar += [float(S) for S in s]
791                        Inst2['Icoeff'] = Icoeff
792                        Inst2['Iesd'] = Iesd
793                        Inst2['Icovar'] = Icovar
794                return [Inst1,Inst2]
795
796        # stuff we might need from the reader
797        filename = rd.powderentry[0]
798        bank = rd.powderentry[2]
799        numbanks = rd.numbanks
800        # is there an instrument parameter file defined for the current data set?
801        if rd.instparm or (lastdatafile == filename and lastIparmfile):
802            if rd.instparm:
803                instfile = os.path.join(os.path.split(filename)[0],
804                                    rd.instparm)
805            else:
806                # for multiple reads of one data file, reuse the inst parm file
807                instfile = lastIparmfile
808            if os.path.exists(instfile):
809                #print 'debug: try read',instfile
810                instParmList = self.ReadPowderInstprm(instfile)
811                if instParmList is not None:
812                    rd.instfile = instfile
813                    rd.instmsg = 'GSAS-II file '+instfile
814                    return instParmList
815                Iparm = self.ReadPowderIparm(instfile,bank,numbanks,rd)
816                if Iparm:
817                    #print 'debug: success'
818                    rd.instfile = instfile
819                    rd.instmsg = instfile + ' bank ' + str(rd.instbank)
820                    return SetPowderInstParms(Iparm,rd)
821            else:
822                self.ErrorDialog('Open Error','Error opening instrument parameter file '
823                    +str(instfile)+' requested by file '+ filename)
824        # is there an instrument parameter file matching the current file
825        # with extension .inst or .prm? If so read it
826        basename = os.path.splitext(filename)[0]
827        for ext in '.instprm','.prm','.inst','.ins':
828            instfile = basename + ext
829            instParmList = self.ReadPowderInstprm(instfile)
830            if instParmList is not None:
831                rd.instfile = instfile
832                rd.instmsg = 'GSAS-II file '+instfile
833                return instParmList
834            Iparm = self.ReadPowderIparm(instfile,bank,numbanks,rd)
835            if Iparm:
836                #print 'debug: success'
837                rd.instfile = instfile
838                rd.instmsg = instfile + ' bank ' + str(rd.instbank)
839                return SetPowderInstParms(Iparm,rd)
840            else:
841                #print 'debug: open/read failed',instfile
842                pass # fail silently
843
844        # did we read the data file from a zip? If so, look there for a
845        # instrument parameter file
846        if self.zipfile:
847            for ext in '.instprm','.prm','.inst','.ins':
848                instfile = G2IO.ExtractFileFromZip(
849                    self.zipfile,
850                    selection=os.path.split(basename + ext)[1],
851                    parent=self)
852                if instfile is not None and instfile != self.zipfile:
853                    print 'debug:',instfile,'created from ',self.zipfile
854                    instParmList = self.ReadPowderInstprm(instfile)
855                    if instParmList is not None:
856                        rd.instfile = instfile
857                        rd.instmsg = 'GSAS-II file '+instfile
858                        return instParmList
859                    Iparm = self.ReadPowderIparm(instfile,bank,numbanks,rd)
860                    if Iparm:
861                        rd.instfile = instfile
862                        rd.instmsg = instfile + ' bank ' + str(rd.instbank)
863                        return SetPowderInstParms(Iparm,rd)
864                    else:
865                        #print 'debug: open/read for',instfile,'from',self.zipfile,'failed'
866                        pass # fail silently
867
868        while True: # loop until we get a file that works or we get a cancel
869            instfile = ''
870            dlg = wx.FileDialog(
871                self,
872                'Choose inst. param file for "'
873                +rd.idstring
874                +'" (or Cancel for default)',
875                '.', '',
876                'GSAS iparm file (*.prm,*.inst,*.ins)|*.prm;*.inst;*.ins|'
877                'GSAS-II iparm file (*.instprm)|*.instprm|'
878                'All files (*.*)|*.*', 
879                wx.OPEN|wx.CHANGE_DIR)
880            if os.path.exists(lastIparmfile):
881                dlg.SetFilename(lastIparmfile)
882            if dlg.ShowModal() == wx.ID_OK:
883                instfile = dlg.GetPath()
884            dlg.Destroy()
885            if not instfile: break
886            instParmList = self.ReadPowderInstprm(instfile)
887            if instParmList is not None:
888                rd.instfile = instfile
889                rd.instmsg = 'GSAS-II file '+instfile
890                return instParmList
891            Iparm = self.ReadPowderIparm(instfile,bank,numbanks,rd)
892            if Iparm:
893                #print 'debug: success with',instfile
894                rd.instfile = instfile
895                rd.instmsg = instfile + ' bank ' + str(rd.instbank)
896                return SetPowderInstParms(Iparm,rd)
897            else:
898                self.ErrorDialog('Read Error',
899                                 'Error opening/reading file '+str(instfile))
900       
901        # still no success: offer user choice of defaults
902        while True: # loop until we get a choice
903            choices = []
904            head = 'Select from default instrument parameters for '+rd.idstring
905
906            for l in rd.defaultIparm_lbl:
907                choices.append('Defaults for '+l)
908            res = rd.BlockSelector(
909                choices,
910                ParentFrame=self,
911                title=head,
912                header='Select default inst parms',
913                useCancel=False)
914            if res is None: continue
915            rd.instfile = ''
916            rd.instmsg = 'default: '+rd.defaultIparm_lbl[res]
917            return SetPowderInstParms(rd.defaultIparms[res],rd)
918
919    def OnImportPowder(self,event):
920        '''Called in response to an Import/Powder Data/... menu item
921        to read a powder diffraction data set.
922        dict self.ImportMenuId is used to look up the specific
923        reader item associated with the menu item, which will be
924        None for the last menu item, which is the "guess" option
925        where all appropriate formats will be tried.
926
927        Also reads an instrument parameter file for each dataset.
928        '''
929        reqrdr = self.ImportMenuId.get(event.GetId())  # look up which format was requested
930        rdlist = self.OnImportGeneric(
931            reqrdr,self.ImportPowderReaderlist,'Powder Data',multiple=True)
932        if len(rdlist) == 0: return
933        self.CheckNotebook()
934        Iparm = None
935        lastIparmfile = ''
936        lastdatafile = ''
937        for rd in rdlist:
938            # get instrument parameters for each dataset
939            Iparm1,Iparm2 = self.GetPowderIparm(rd, Iparm, lastIparmfile, lastdatafile)
940            if rd.repeat_instparm: 
941                lastIparmfile = rd.instfile
942            lastdatafile = rd.powderentry[0]
943            print 'Read powder data '+str(rd.idstring)+ \
944                ' from file '+str(self.lastimport) + \
945                ' with parameters from '+str(rd.instmsg)
946            # data are read, now store them in the tree
947            Id = self.PatternTree.AppendItem(parent=self.root,
948                text='PWDR '+rd.idstring)
949            if 'T' in Iparm1['Type'][0]:
950                if not rd.clockWd and rd.GSAS:
951                    rd.powderdata[0] *= 100.        #put back the CW centideg correction
952                cw = np.diff(rd.powderdata[0])
953                rd.powderdata[0] = rd.powderdata[0][:-1]+cw/2.
954                rd.powderdata[1] = rd.powderdata[1][:-1]/cw
955                rd.powderdata[2] = rd.powderdata[2][:-1]*cw**2  #1/var=w at this point
956                if 'Itype' in Iparm2:
957                    Ibeg = np.searchsorted(rd.powderdata[0],Iparm2['Tminmax'][0])
958                    Ifin = np.searchsorted(rd.powderdata[0],Iparm2['Tminmax'][1])
959                    rd.powderdata[0] = rd.powderdata[0][Ibeg:Ifin]
960                    YI,WYI = G2pwd.calcIncident(Iparm2,rd.powderdata[0])
961                    rd.powderdata[1] = rd.powderdata[1][Ibeg:Ifin]/YI
962                    var = 1./rd.powderdata[2][Ibeg:Ifin]
963                    var += WYI*rd.powderdata[1]**2
964                    var /= YI**2
965                    rd.powderdata[2] = 1./var
966                rd.powderdata[3] = np.zeros_like(rd.powderdata[0])                                       
967                rd.powderdata[4] = np.zeros_like(rd.powderdata[0])                                       
968                rd.powderdata[5] = np.zeros_like(rd.powderdata[0])                                       
969            Tmin = min(rd.powderdata[0])
970            Tmax = max(rd.powderdata[0])
971            self.PatternTree.SetItemPyData(Id,[{'wtFactor':1.0},rd.powderdata])
972            self.PatternTree.SetItemPyData(
973                self.PatternTree.AppendItem(Id,text='Comments'),
974                rd.comments)
975            self.PatternTree.SetItemPyData(
976                self.PatternTree.AppendItem(Id,text='Limits'),
977                [(Tmin,Tmax),[Tmin,Tmax]])
978            self.PatternId = G2gd.GetPatternTreeItemId(self,Id,'Limits')
979            self.PatternTree.SetItemPyData(
980                self.PatternTree.AppendItem(Id,text='Background'),
981                [['chebyschev',True,3,1.0,0.0,0.0],
982                 {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[]}])
983            self.PatternTree.SetItemPyData(
984                self.PatternTree.AppendItem(Id,text='Instrument Parameters'),
985                [Iparm1,Iparm2])
986            self.PatternTree.SetItemPyData(
987                self.PatternTree.AppendItem(Id,text='Sample Parameters'),
988                rd.Sample)
989            self.PatternTree.SetItemPyData(
990                self.PatternTree.AppendItem(Id,text='Peak List')
991                ,[])
992            self.PatternTree.SetItemPyData(
993                self.PatternTree.AppendItem(Id,text='Index Peak List'),
994                [])
995            self.PatternTree.SetItemPyData(
996                self.PatternTree.AppendItem(Id,text='Unit Cells List'),
997                [])
998            self.PatternTree.SetItemPyData(
999                self.PatternTree.AppendItem(Id,text='Reflection Lists'),
1000                {})
1001            self.PatternTree.Expand(Id)
1002        self.PatternTree.SelectItem(Id)
1003        return # success
1004
1005    def _init_Exports(self,menu):
1006        '''This is a place holder for when exports are handled in a manner similar to imports
1007        '''
1008#        submenu = wx.Menu()
1009#        item = menu.AppendMenu(
1010#            wx.ID_ANY, 'entire project',
1011#            submenu, help='Export entire project')
1012
1013        # for now hard-code CIF testing here
1014        item = menu.Append(
1015            wx.ID_ANY,
1016            help='full CIF file includes powder/reflection data',
1017            kind=wx.ITEM_NORMAL,
1018            text='full CIF file')
1019        self.Bind(wx.EVT_MENU, self.OnTestCIF, id=item.GetId())
1020        item = menu.Append(
1021            wx.ID_ANY,
1022            help='quick CIF file with no data, no distance/angle table',
1023            kind=wx.ITEM_NORMAL,
1024            text='quick CIF file')
1025        self.Bind(wx.EVT_MENU, self.OnTestCIF, id=item.GetId())
1026
1027    def OnTestCIF(self,event):
1028        # hard-code CIF testing here
1029       
1030        # get the menu command on Windows and Linux
1031        menu = self.ExportMenu.FindItemById(event.GetId())
1032        mode = 'full'
1033        if menu:
1034            if 'quick' in menu.GetLabel():
1035                mode = 'simple'
1036        else: # this works on the Mac
1037            try: 
1038                if 'quick' in event.EventObject.GetLabelText(event.Id):
1039                    mode = 'simple'
1040            except:
1041                pass
1042        #path2GSAS2 = os.path.join(
1043        #    os.path.dirname(os.path.realpath(__file__)), # location of this file
1044        #    'exports')
1045        #if path2GSAS2 not in sys.path: sys.path.append(path2GSAS2)
1046        #reload(G2IO)
1047        import G2cif
1048        reload(G2cif)
1049        exp = G2cif.ExportCIF(self)
1050        exp.export(mode)
1051
1052    def _Add_ExportMenuItems(self,parent):
1053        item = parent.Append(
1054            help='Select PWDR item to enable',id=wx.ID_ANY,
1055            kind=wx.ITEM_NORMAL,
1056            text='Export Powder Patterns...')
1057        self.ExportPattern.append(item)
1058        item.Enable(False)
1059        self.Bind(wx.EVT_MENU, self.OnExportPatterns, id=item.GetId())
1060
1061        item = parent.Append(
1062            help='',id=wx.ID_ANY,
1063            kind=wx.ITEM_NORMAL,
1064            text='Export All Peak Lists...')
1065        self.ExportPeakList.append(item)
1066        item.Enable(True)
1067        self.Bind(wx.EVT_MENU, self.OnExportPeakList, id=item.GetId())
1068
1069        item = parent.Append(
1070            help='',id=wx.ID_ANY,
1071            kind=wx.ITEM_NORMAL,
1072            text='Export HKLs...')
1073        self.ExportHKL.append(item)
1074        self.Bind(wx.EVT_MENU, self.OnExportHKL, id=item.GetId())
1075
1076        item = parent.Append(
1077            help='Select PDF item to enable',
1078            id=wx.ID_ANY,
1079            kind=wx.ITEM_NORMAL,
1080            text='Export PDF...')
1081        self.ExportPDF.append(item)
1082        item.Enable(False)
1083        self.Bind(wx.EVT_MENU, self.OnExportPDF, id=item.GetId())
1084
1085        item = parent.Append(
1086            help='',id=wx.ID_ANY,
1087            kind=wx.ITEM_NORMAL,
1088            text='Export Phase...')
1089        self.ExportPhase.append(item)
1090        item.Enable(False)
1091        self.Bind(wx.EVT_MENU, self.OnExportPhase, id=item.GetId())
1092
1093#        item = parent.Append(
1094#            help='',id=wx.ID_ANY,
1095#            kind=wx.ITEM_NORMAL,
1096#            text='Export CIF...')
1097#        self.ExportCIF.append(item)
1098#        item.Enable(False)
1099#        self.Bind(wx.EVT_MENU, self.OnExportCIF, id=item.GetId())
1100               
1101    def FillMainMenu(self,menubar):
1102        '''Define contents of the main GSAS-II menu for the (main) data tree window
1103        in the mac, used also for the data item windows as well.
1104        '''
1105        File = wx.Menu(title='')
1106        menubar.Append(menu=File, title='&File')
1107        self._Add_FileMenuItems(File)
1108        Data = wx.Menu(title='')
1109        menubar.Append(menu=Data, title='Data')
1110        self._Add_DataMenuItems(Data)
1111        Calculate = wx.Menu(title='')       
1112        menubar.Append(menu=Calculate, title='&Calculate')
1113        self._Add_CalculateMenuItems(Calculate)
1114        Import = wx.Menu(title='')       
1115        menubar.Append(menu=Import, title='Import')
1116        self._Add_ImportMenu_Phase(Import)
1117        self._Add_ImportMenu_powder(Import)
1118        self._Add_ImportMenu_Sfact(Import)
1119        self.ExportMenu = wx.Menu(title='')
1120        menubar.Append(menu=self.ExportMenu, title='Export')
1121        self._init_Exports(self.ExportMenu)
1122        self._Add_ExportMenuItems(self.ExportMenu)
1123        HelpMenu=G2gd.MyHelp(self,helpType='Data tree',
1124            morehelpitems=[('&Tutorials','Tutorials')])
1125        menubar.Append(menu=HelpMenu,title='&Help')
1126
1127    def _init_ctrls(self, parent):
1128        wx.Frame.__init__(self, name='GSASII', parent=parent,
1129            size=wx.Size(400, 250),style=wx.DEFAULT_FRAME_STYLE, title='GSAS-II data tree')
1130        clientSize = wx.ClientDisplayRect()
1131        Size = self.GetSize()
1132        xPos = clientSize[2]-Size[0]
1133        self.SetPosition(wx.Point(xPos,clientSize[1]))
1134        self._init_Imports()
1135        #initialize Menu item objects (these contain lists of menu items that are enabled or disabled)
1136        self.MakePDF = []
1137        self.Refine = []
1138        self.SeqRefine = []
1139        self.ExportPattern = []
1140        self.ExportPeakList = []
1141        self.ExportHKL = []
1142        self.ExportPDF = []
1143        self.ExportPhase = []
1144        self.ExportCIF = []
1145        #
1146        self.GSASIIMenu = wx.MenuBar()
1147        self.FillMainMenu(self.GSASIIMenu)
1148        self.SetMenuBar(self.GSASIIMenu)
1149        self.Bind(wx.EVT_SIZE, self.OnSize)
1150        self.CreateStatusBar()
1151        self.mainPanel = wx.Panel(self,-1)
1152       
1153        wxID_PATTERNTREE = wx.NewId()
1154        self.PatternTree = wx.TreeCtrl(id=wxID_PATTERNTREE,
1155            parent=self.mainPanel, pos=wx.Point(0, 0),style=wx.TR_DEFAULT_STYLE )
1156        self.PatternTree.Bind(wx.EVT_TREE_SEL_CHANGED,
1157            self.OnPatternTreeSelChanged, id=wxID_PATTERNTREE)
1158        self.PatternTree.Bind(wx.EVT_TREE_ITEM_COLLAPSED,
1159            self.OnPatternTreeItemCollapsed, id=wxID_PATTERNTREE)
1160        self.PatternTree.Bind(wx.EVT_TREE_ITEM_EXPANDED,
1161            self.OnPatternTreeItemExpanded, id=wxID_PATTERNTREE)
1162        self.PatternTree.Bind(wx.EVT_TREE_DELETE_ITEM,
1163            self.OnPatternTreeItemDelete, id=wxID_PATTERNTREE)
1164        self.PatternTree.Bind(wx.EVT_TREE_KEY_DOWN,
1165            self.OnPatternTreeKeyDown, id=wxID_PATTERNTREE)
1166        self.root = self.PatternTree.AddRoot('Loaded Data: ')
1167       
1168        plotFrame = wx.Frame(None,-1,'GSASII Plots',size=wx.Size(700,600), \
1169            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
1170        self.G2plotNB = G2plt.G2PlotNoteBook(plotFrame)
1171        plotFrame.Show()
1172       
1173        self.dataDisplay = None
1174       
1175    def __init__(self, parent):
1176        self._init_ctrls(parent)
1177        self.Bind(wx.EVT_CLOSE, self.ExitMain)
1178        # various defaults
1179        self.oldFocus = None
1180        self.GSASprojectfile = ''
1181        self.dirname = os.path.expanduser('~')       #start in the users home directory by default; may be meaningless
1182        self.undofile = ''
1183        self.TreeItemDelete = False
1184        self.Offset = [0.0,0.0]
1185        self.delOffset = .02
1186        self.refOffset = -100.0
1187        self.refDelt = .01
1188        self.Weight = False
1189        self.IparmName = ''  # to be removed when SelectPowderData & GetInstrumentFile is
1190        self.IfPlot = False
1191        self.PatternId = 0
1192        self.PickId = 0
1193        self.PeakTable = []
1194        self.LimitsTable = []
1195        self.HKL = []
1196        self.Lines = []
1197        self.itemPicked = None
1198        self.dataFrame = None
1199        self.Interpolate = 'nearest'
1200        self.ContourColor = 'Paired'
1201        self.VcovColor = 'RdYlGn'
1202        self.RamaColor = 'Blues'
1203        self.Projection = 'equal area'
1204        self.logPlot = False
1205        self.qPlot = False
1206        self.Contour = False
1207        self.Legend = False
1208        self.SinglePlot = False
1209        self.SubBack = False
1210        self.plotView = 0
1211        self.Image = 0
1212        self.oldImagefile = ''
1213        self.ImageZ = []
1214        self.Integrate = 0
1215        self.imageDefault = {}
1216        self.Sngl = 0
1217        self.ifGetRing = False
1218        self.setPoly = False
1219        arg = sys.argv
1220        if len(arg) > 1:
1221            self.GSASprojectfile = arg[1]
1222            self.dirname = os.path.dirname(arg[1])
1223            if self.dirname: os.chdir(self.dirname)
1224            try:
1225                G2IO.ProjFileOpen(self)
1226                self.PatternTree.Expand(self.root)
1227                for item in self.Refine: item.Enable(True)
1228                for item in self.SeqRefine: item.Enable(True)
1229            except:
1230                print 'Error opening file',arg[1]
1231
1232    def OnSize(self,event):
1233        'Called when the main window is resized. Not sure why'
1234        w,h = self.GetClientSizeTuple()
1235        self.mainPanel.SetSize(wx.Size(w,h))
1236        self.PatternTree.SetSize(wx.Size(w,h))
1237                       
1238    def OnPatternTreeSelChanged(self, event):
1239        '''Called when a data tree item is selected'''
1240        if self.TreeItemDelete:
1241            self.TreeItemDelete = False
1242        else:
1243            pltNum = self.G2plotNB.nb.GetSelection()
1244            if pltNum >= 0:                         #to avoid the startup with no plot!
1245                pltPage = self.G2plotNB.nb.GetPage(pltNum)
1246                pltPlot = pltPage.figure
1247            item = event.GetItem()
1248            G2gd.MovePatternTreeToGrid(self,item)
1249            if self.oldFocus:
1250                self.oldFocus.SetFocus()
1251       
1252    def OnPatternTreeItemCollapsed(self, event):
1253        'Called when a tree item is collapsed'
1254        event.Skip()
1255
1256    def OnPatternTreeItemExpanded(self, event):
1257        'Called when a tree item is expanded'
1258        event.Skip()
1259       
1260    def OnPatternTreeItemDelete(self, event):
1261        'Called when a tree item is deleted -- not sure what this does'
1262        self.TreeItemDelete = True
1263
1264    def OnPatternTreeItemActivated(self, event):
1265        'Called when a tree item is activated'
1266        event.Skip()
1267       
1268    def OnPatternTreeKeyDown(self,event):
1269        'Not sure what this does'
1270        key = event.GetKeyCode()
1271        item = self.PickId
1272        if type(item) is int: return # is this the toplevel in tree?
1273        if key == wx.WXK_UP:
1274            self.oldFocus = wx.Window.FindFocus()
1275            self.PatternTree.GetPrevSibling(item)
1276        elif key == wx.WXK_DOWN:
1277            self.oldFocus = wx.Window.FindFocus()
1278            self.PatternTree.GetNextSibling(item)
1279               
1280    def OnReadPowderPeaks(self,event):
1281        'Bound to menu Data/Read Powder Peaks -- still needed?'
1282        Cuka = 1.54052
1283        self.CheckNotebook()
1284        dlg = wx.FileDialog(self, 'Choose file with peak list', '.', '', 
1285            'peak files (*.txt)|*.txt|All files (*.*)|*.*',wx.OPEN|wx.CHANGE_DIR)
1286        try:
1287            if dlg.ShowModal() == wx.ID_OK:
1288                self.HKL = []
1289                self.powderfile = dlg.GetPath()
1290                comments,peaks = G2IO.GetPowderPeaks(self.powderfile)
1291                Id = self.PatternTree.AppendItem(parent=self.root,text='PKS '+os.path.basename(self.powderfile))
1292                data = ['PKS',Cuka,0.0]
1293                names = ['Type','Lam','Zero'] 
1294                codes = [0,0,0]
1295                inst = [G2IO.makeInstDict(names,data,codes),{}]
1296                self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Instrument Parameters'),inst)
1297                self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Comments'),comments)
1298                self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Index Peak List'),peaks)
1299                self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Unit Cells List'),[])             
1300                self.PatternTree.Expand(Id)
1301                self.PatternTree.SelectItem(Id)
1302                os.chdir(dlg.GetDirectory())           # to get Mac/Linux to change directory!
1303        finally:
1304            dlg.Destroy()
1305           
1306    def OnImageRead(self,event):
1307        'Called to read in an image in any known format'
1308        self.CheckNotebook()
1309        dlg = wx.FileDialog(
1310            self, 'Choose image files', '.', '',
1311            'Any image file (*.edf;*.tif;*.tiff;*.mar*;*.avg;*.sum;*.img;*.G2img)|'
1312            '*.edf;*.tif;*.tiff;*.mar*;*.avg;*.sum;*.img;*.G2img;*.zip|'
1313            'European detector file (*.edf)|*.edf|'
1314            'Any detector tif (*.tif;*.tiff)|*.tif;*.tiff|'
1315            'MAR file (*.mar*)|*.mar*|'
1316            'GE Image (*.avg;*.sum)|*.avg;*.sum|'
1317            'ADSC Image (*.img)|*.img|'
1318            'GSAS-II Image (*.G2img)|*.G2img|'
1319            'Zip archive (*.zip)|*.zip|'
1320            'All files (*.*)|*.*',
1321            wx.OPEN | wx.MULTIPLE|wx.CHANGE_DIR)
1322        try:
1323            if dlg.ShowModal() == wx.ID_OK:
1324                imagefiles = dlg.GetPaths()
1325                imagefiles.sort()
1326                for imagefile in imagefiles:
1327                    # if a zip file, open and extract
1328                    if os.path.splitext(imagefile)[1].lower() == '.zip':
1329                        extractedfile = G2IO.ExtractFileFromZip(imagefile,parent=self)
1330                        if extractedfile is not None and extractedfile != imagefile:
1331                            imagefile = extractedfile
1332                    Comments,Data,Npix,Image = G2IO.GetImageData(self,imagefile)
1333                    if Comments:
1334                        Id = self.PatternTree.AppendItem(parent=self.root,text='IMG '+os.path.basename(imagefile))
1335                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Comments'),Comments)
1336                        Imax = np.amax(Image)
1337                        Imin = max(0.0,np.amin(Image))          #force positive
1338                        if self.imageDefault:
1339                            Data = copy.copy(self.imageDefault)
1340                            Data['showLines'] = True
1341                            Data['ring'] = []
1342                            Data['rings'] = []
1343                            Data['cutoff'] = 10
1344                            Data['pixLimit'] = 20
1345                            Data['edgemin'] = 100000000
1346                            Data['calibdmin'] = 0.5
1347                            Data['calibskip'] = 0
1348                            Data['ellipses'] = []
1349                            Data['calibrant'] = ''
1350                            Data['GonioAngles'] = [0.,0.,0.]
1351                        else:
1352                            Data['type'] = 'PWDR'
1353                            Data['color'] = 'Paired'
1354                            Data['tilt'] = 0.0
1355                            Data['rotation'] = 0.0
1356                            Data['showLines'] = False
1357                            Data['ring'] = []
1358                            Data['rings'] = []
1359                            Data['cutoff'] = 10
1360                            Data['pixLimit'] = 20
1361                            Data['calibdmin'] = 0.5
1362                            Data['calibskip'] = 0
1363                            Data['edgemin'] = 100000000
1364                            Data['ellipses'] = []
1365                            Data['GonioAngles'] = [0.,0.,0.]
1366                            Data['calibrant'] = ''
1367                            Data['IOtth'] = [2.0,5.0]
1368                            Data['LRazimuth'] = [135,225]
1369                            Data['azmthOff'] = 0.0
1370                            Data['outChannels'] = 2500
1371                            Data['outAzimuths'] = 1
1372                            Data['centerAzm'] = False
1373                            Data['fullIntegrate'] = False
1374                            Data['setRings'] = False
1375                            Data['background image'] = ['',1.0]                           
1376                        Data['setDefault'] = False
1377                        Data['range'] = [(Imin,Imax),[Imin,Imax]]
1378                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Image Controls'),Data)
1379                        Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Thresholds':[(Imin,Imax),[Imin,Imax]]}
1380                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Masks'),Masks)
1381                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Stress/Strain'),
1382                            {'Type':'True','d-zero':[],'Sample phi':0.0,'Sample z':0.0,'strain':np.zeros((3,3))})
1383                        self.PatternTree.SetItemPyData(Id,[Npix,imagefile])
1384                        self.PickId = Id
1385                        self.Image = Id
1386                os.chdir(dlg.GetDirectory())           # to get Mac/Linux to change directory!
1387                self.PatternTree.SelectItem(G2gd.GetPatternTreeItemId(self,Id,'Image Controls'))             #show last one
1388        finally:
1389            path = dlg.GetDirectory()           # to get Mac/Linux to change directory!
1390            os.chdir(path)
1391            dlg.Destroy()
1392
1393    def CheckNotebook(self):
1394        '''Make sure the data tree has the minimally expected controls.
1395        (BHT) correct?
1396        '''
1397        if not G2gd.GetPatternTreeItemId(self,self.root,'Notebook'):
1398            sub = self.PatternTree.AppendItem(parent=self.root,text='Notebook')
1399            self.PatternTree.SetItemPyData(sub,[''])
1400        if not G2gd.GetPatternTreeItemId(self,self.root,'Controls'):
1401            sub = self.PatternTree.AppendItem(parent=self.root,text='Controls')
1402            self.PatternTree.SetItemPyData(sub,{'deriv type':'analytic Hessian',    #default controls
1403                'min dM/M':0.0001,'shift factor':1.,'max cyc':3,'F**2':True,
1404                'minF/sig':0,})
1405        if not G2gd.GetPatternTreeItemId(self,self.root,'Covariance'):
1406            sub = self.PatternTree.AppendItem(parent=self.root,text='Covariance')
1407            self.PatternTree.SetItemPyData(sub,{})
1408        if not G2gd.GetPatternTreeItemId(self,self.root,'Constraints'):
1409            sub = self.PatternTree.AppendItem(parent=self.root,text='Constraints')
1410            self.PatternTree.SetItemPyData(sub,{'Hist':[],'HAP':[],'Phase':[]})
1411        if not G2gd.GetPatternTreeItemId(self,self.root,'Restraints'):
1412            sub = self.PatternTree.AppendItem(parent=self.root,text='Restraints')
1413            self.PatternTree.SetItemPyData(sub,{})
1414        if not G2gd.GetPatternTreeItemId(self,self.root,'Rigid bodies'):
1415            sub = self.PatternTree.AppendItem(parent=self.root,text='Rigid bodies')
1416            self.PatternTree.SetItemPyData(sub,{'Vector':{'AtInfo':{}},
1417                'Residue':{'AtInfo':{}},'RBIds':{'Vector':[],'Residue':[]}})
1418               
1419    class CopyDialog(wx.Dialog):
1420        '''Creates a dialog for copying control settings between
1421        data tree items'''
1422        def __init__(self,parent,title,text,data):
1423            wx.Dialog.__init__(self,parent,-1,title, 
1424                pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1425            self.data = data
1426            panel = wx.Panel(self)
1427            mainSizer = wx.BoxSizer(wx.VERTICAL)
1428            topLabl = wx.StaticText(panel,-1,text)
1429            mainSizer.Add((10,10),1)
1430            mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
1431            mainSizer.Add((10,10),1)
1432            ncols = len(data)/40+1
1433            dataGridSizer = wx.FlexGridSizer(rows=len(data),cols=ncols,hgap=2,vgap=2)
1434            for id,item in enumerate(self.data):
1435                ckbox = wx.CheckBox(panel,id,item[1])
1436                ckbox.Bind(wx.EVT_CHECKBOX,self.OnCopyChange)                   
1437                dataGridSizer.Add(ckbox,0,wx.LEFT,10)
1438            mainSizer.Add(dataGridSizer,0,wx.EXPAND)
1439            OkBtn = wx.Button(panel,-1,"Ok")
1440            OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1441            cancelBtn = wx.Button(panel,-1,"Cancel")
1442            cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1443            btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1444            btnSizer.Add((20,20),1)
1445            btnSizer.Add(OkBtn)
1446            btnSizer.Add((20,20),1)
1447            btnSizer.Add(cancelBtn)
1448            btnSizer.Add((20,20),1)
1449           
1450            mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1451            panel.SetSizer(mainSizer)
1452            panel.Fit()
1453            self.Fit()
1454       
1455        def OnCopyChange(self,event):
1456            id = event.GetId()
1457            self.data[id][0] = self.FindWindowById(id).GetValue()       
1458           
1459        def OnOk(self,event):
1460            parent = self.GetParent()
1461            parent.Raise()
1462            self.EndModal(wx.ID_OK)             
1463           
1464        def OnCancel(self,event):
1465            parent = self.GetParent()
1466            parent.Raise()
1467            self.EndModal(wx.ID_CANCEL)             
1468           
1469        def GetData(self):
1470            return self.data
1471       
1472    class SumDialog(wx.Dialog):
1473        'Allows user to supply scale factor(s) when summing data'
1474        def __init__(self,parent,title,text,dataType,data):
1475            wx.Dialog.__init__(self,parent,-1,title, 
1476                pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1477            self.data = data
1478            panel = wx.Panel(self)
1479            mainSizer = wx.BoxSizer(wx.VERTICAL)
1480            topLabl = wx.StaticText(panel,-1,text)
1481            mainSizer.Add((10,10),1)
1482            mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
1483            mainSizer.Add((10,10),1)
1484            dataGridSizer = wx.FlexGridSizer(rows=len(data),cols=2,hgap=2,vgap=2)
1485            for id,item in enumerate(self.data[:-1]):
1486                name = wx.TextCtrl(panel,-1,item[1],size=wx.Size(200,20))
1487                name.SetEditable(False)
1488                scale = wx.TextCtrl(panel,id,'%.3f'%(item[0]),style=wx.TE_PROCESS_ENTER)
1489                scale.Bind(wx.EVT_TEXT_ENTER,self.OnScaleChange)
1490                scale.Bind(wx.EVT_KILL_FOCUS,self.OnScaleChange)
1491                dataGridSizer.Add(scale,0,wx.LEFT,10)
1492                dataGridSizer.Add(name,0,wx.RIGHT,10)
1493            if dataType:
1494                dataGridSizer.Add(wx.StaticText(panel,-1,'Sum result name: '+dataType),0, \
1495                    wx.LEFT|wx.TOP|wx.ALIGN_CENTER_VERTICAL,10)
1496                self.name = wx.TextCtrl(panel,-1,self.data[-1],size=wx.Size(200,20),style=wx.TE_PROCESS_ENTER)
1497                self.name.Bind(wx.EVT_TEXT_ENTER,self.OnNameChange)
1498                self.name.Bind(wx.EVT_KILL_FOCUS,self.OnNameChange)
1499                dataGridSizer.Add(self.name,0,wx.RIGHT|wx.TOP,10)
1500            mainSizer.Add(dataGridSizer,0,wx.EXPAND)
1501            OkBtn = wx.Button(panel,-1,"Ok")
1502            OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1503            cancelBtn = wx.Button(panel,-1,"Cancel")
1504            cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1505            btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1506            btnSizer.Add((20,20),1)
1507            btnSizer.Add(OkBtn)
1508            btnSizer.Add((20,20),1)
1509            btnSizer.Add(cancelBtn)
1510            btnSizer.Add((20,20),1)
1511           
1512            mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1513            panel.SetSizer(mainSizer)
1514            panel.Fit()
1515            self.Fit()
1516
1517        def OnScaleChange(self,event):
1518            id = event.GetId()
1519            value = self.FindWindowById(id).GetValue()
1520            try:
1521                self.data[id][0] = float(value)
1522                self.FindWindowById(id).SetValue('%.3f'%(self.data[id][0]))
1523            except ValueError:
1524                if value and '-' not in value[0]:
1525                    print 'bad input - numbers only'
1526                    self.FindWindowById(id).SetValue('0.000')
1527           
1528        def OnNameChange(self,event):
1529            self.data[-1] = self.name.GetValue() 
1530           
1531        def OnOk(self,event):
1532            parent = self.GetParent()
1533            parent.Raise()
1534            self.EndModal(wx.ID_OK)             
1535           
1536        def OnCancel(self,event):
1537            parent = self.GetParent()
1538            parent.Raise()
1539            self.EndModal(wx.ID_CANCEL)             
1540           
1541        def GetData(self):
1542            return self.data
1543           
1544    class ConstraintDialog(wx.Dialog):
1545        '''Window to edit Constraint values
1546        '''
1547        def __init__(self,parent,title,text,data,separator='*'):
1548            wx.Dialog.__init__(self,parent,-1,title, 
1549                pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1550            self.data = data
1551            panel = wx.Panel(self)
1552            mainSizer = wx.BoxSizer(wx.VERTICAL)
1553            topLabl = wx.StaticText(panel,-1,text)
1554            mainSizer.Add((10,10),1)
1555            mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
1556            mainSizer.Add((10,10),1)
1557            dataGridSizer = wx.FlexGridSizer(rows=len(data),cols=2,hgap=2,vgap=2)
1558            for id,item in enumerate(self.data[:-1]):
1559                lbl = item[1]
1560                if lbl[-1] != '=': lbl += ' ' + separator + ' '
1561                name = wx.StaticText(panel,-1,lbl,size=wx.Size(200,20),
1562                                     style=wx.ALIGN_RIGHT)
1563                scale = wx.TextCtrl(panel,id,'%.3f'%(item[0]),style=wx.TE_PROCESS_ENTER)
1564                scale.Bind(wx.EVT_TEXT_ENTER,self.OnScaleChange)
1565                scale.Bind(wx.EVT_KILL_FOCUS,self.OnScaleChange)
1566                dataGridSizer.Add(name,0,wx.LEFT,10)
1567                dataGridSizer.Add(scale,0,wx.RIGHT,10)
1568            mainSizer.Add(dataGridSizer,0,wx.EXPAND)
1569            OkBtn = wx.Button(panel,-1,"Ok")
1570            OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1571            cancelBtn = wx.Button(panel,-1,"Cancel")
1572            cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1573            btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1574            btnSizer.Add((20,20),1)
1575            btnSizer.Add(OkBtn)
1576            btnSizer.Add((20,20),1)
1577            btnSizer.Add(cancelBtn)
1578            btnSizer.Add((20,20),1)
1579           
1580            mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1581            panel.SetSizer(mainSizer)
1582            panel.Fit()
1583            self.Fit()
1584            self.CenterOnParent()
1585           
1586        def OnNameChange(self,event):
1587            self.data[-1] = self.name.GetValue() 
1588           
1589        def OnScaleChange(self,event):
1590            id = event.GetId()
1591            value = self.FindWindowById(id).GetValue()
1592            try:
1593                self.data[id][0] = float(value)
1594                self.FindWindowById(id).SetValue('%.3f'%(self.data[id][0]))
1595            except ValueError:
1596                if value and '-' not in value[0]:
1597                    print 'bad input - numbers only'
1598                    self.FindWindowById(id).SetValue('0.000')
1599           
1600        def OnOk(self,event):
1601            parent = self.GetParent()
1602            parent.Raise()
1603            self.EndModal(wx.ID_OK)             
1604           
1605        def OnCancel(self,event):
1606            parent = self.GetParent()
1607            parent.Raise()
1608            self.EndModal(wx.ID_CANCEL)             
1609           
1610        def GetData(self):
1611            return self.data
1612           
1613    def OnPwdrSum(self,event):
1614        'Sum together powder data(?)'
1615        TextList = []
1616        DataList = []
1617        SumList = []
1618        Names = []
1619        Inst = None
1620        SumItemList = []
1621        Comments = ['Sum equals: \n']
1622        if self.PatternTree.GetCount():
1623            item, cookie = self.PatternTree.GetFirstChild(self.root)
1624            while item:
1625                name = self.PatternTree.GetItemText(item)
1626                Names.append(name)
1627                if 'PWDR' in name:
1628                    TextList.append([0.0,name])
1629                    DataList.append(self.PatternTree.GetItemPyData(item)[1])    # (x,y,w,yc,yb,yd)
1630                    if not Inst:
1631                        Inst = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,item, 'Instrument Parameters'))
1632                item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
1633            if len(TextList) < 2:
1634                self.ErrorDialog('Not enough data to sum','There must be more than one "PWDR" pattern')
1635                return
1636            TextList.append('default_sum_name')               
1637            dlg = self.SumDialog(self,'Sum data','Enter scale for each pattern in summation','PWDR',TextList)
1638            try:
1639                if dlg.ShowModal() == wx.ID_OK:
1640                    lenX = 0
1641                    Xminmax = [0,0]
1642                    Xsum = []
1643                    Ysum = []
1644                    Vsum = []
1645                    result = dlg.GetData()
1646                    for i,item in enumerate(result[:-1]):
1647                        scale,name = item
1648                        data = DataList[i]
1649                        if scale:
1650                            Comments.append("%10.3f %s" % (scale,' * '+name))
1651                            x,y,w,yc,yb,yd = data   #numpy arrays!
1652                            v = 1./w
1653                            if lenX:
1654                                if lenX != len(x):
1655                                    self.ErrorDialog('Data length error','Data to be summed must have same number of points'+ \
1656                                        '\nExpected:'+str(lenX)+ \
1657                                        '\nFound:   '+str(len(x))+'\nfor '+name)
1658                                    return
1659                            else:
1660                                lenX = len(x)
1661                            if Xminmax[1]:
1662                                if Xminmax != [x[0],x[-1]]:
1663                                    self.ErrorDialog('Data range error','Data to be summed must span same range'+ \
1664                                        '\nExpected:'+str(Xminmax[0])+' '+str(Xminmax[1])+ \
1665                                        '\nFound:   '+str(x[0])+' '+str(x[-1])+'\nfor '+name)
1666                                    return
1667                                else:
1668                                    for j,yi in enumerate(y):
1669                                         Ysum[j] += scale*yi
1670                                         Vsum[j] += abs(scale)*v[j]
1671                            else:
1672                                Xminmax = [x[0],x[-1]]
1673                                YCsum = YBsum = YDsum = [0.0 for i in range(lenX)]
1674                                for j,yi in enumerate(y):
1675                                    Xsum.append(x[j])
1676                                    Ysum.append(scale*yi)
1677                                    Vsum.append(abs(scale*v[j]))
1678                    Wsum = 1./np.array(Vsum)
1679                    outname = 'PWDR '+result[-1]
1680                    Id = 0
1681                    if outname in Names:
1682                        dlg2 = wx.MessageDialog(self,'Overwrite data?','Duplicate data name',wx.OK|wx.CANCEL)
1683                        try:
1684                            if dlg2.ShowModal() == wx.ID_OK:
1685                                Id = G2gd.GetPatternTreeItemId(self,self.root,name)
1686                                self.PatternTree.Delete(Id)
1687                        finally:
1688                            dlg2.Destroy()
1689                    Id = self.PatternTree.AppendItem(parent=self.root,text=outname)
1690                    if Id:
1691                        Sample = G2pdG.SetDefaultSample()
1692                        self.PatternTree.SetItemPyData(Id,[{'wtFactor':1.0},[np.array(Xsum),np.array(Ysum),np.array(Wsum),
1693                            np.array(YCsum),np.array(YBsum),np.array(YDsum)]])
1694                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Comments'),Comments)                   
1695                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Limits'),[tuple(Xminmax),Xminmax])
1696                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Background'),[['chebyschev',True,3,1.0,0.0,0.0],
1697                            {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[]}])
1698                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Instrument Parameters'),Inst)
1699                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Sample Parameters'),Sample)
1700                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Peak List'),[])
1701                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Index Peak List'),[])
1702                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Unit Cells List'),[])             
1703                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Reflection Lists'),{})             
1704                        self.PatternTree.SelectItem(Id)
1705                        self.PatternTree.Expand(Id)
1706                   
1707            finally:
1708                dlg.Destroy()
1709
1710    def OnImageSum(self,event):
1711        'Sum together image data(?)'
1712        TextList = []
1713        DataList = []
1714        SumList = []
1715        Names = []
1716        Inst = []
1717        SumItemList = []
1718        Comments = ['Sum equals: \n']
1719        if self.PatternTree.GetCount():
1720            item, cookie = self.PatternTree.GetFirstChild(self.root)
1721            while item:
1722                name = self.PatternTree.GetItemText(item)
1723                Names.append(name)
1724                if 'IMG' in name:
1725                    TextList.append([0.0,name])
1726                    DataList.append(self.PatternTree.GetItemPyData(item))        #Size,Image
1727                    Data = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,item,'Image Controls'))
1728                item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
1729            if len(TextList) < 2:
1730                self.ErrorDialog('Not enough data to sum','There must be more than one "IMG" pattern')
1731                return
1732            TextList.append('default_sum_name')               
1733            dlg = self.SumDialog(self,'Sum data','Enter scale for each image in summation','IMG',TextList)
1734            try:
1735                if dlg.ShowModal() == wx.ID_OK:
1736                    imSize = 0
1737                    result = dlg.GetData()
1738                    First = True
1739                    Found = False
1740                    for i,item in enumerate(result[:-1]):
1741                        scale,name = item
1742                        data = DataList[i]
1743                        if scale:
1744                            Found = True                               
1745                            Comments.append("%10.3f %s" % (scale,' * '+name))
1746                            Npix,imagefile = data
1747                            image = G2IO.GetImageData(self,imagefile,imageOnly=True)
1748                            if First:
1749                                newImage = np.zeros_like(image)
1750                                First = False
1751                            if imSize:
1752                                if imSize != Npix:
1753                                    self.ErrorDialog('Image size error','Images to be summed must be same size'+ \
1754                                        '\nExpected:'+str(imSize)+ \
1755                                        '\nFound:   '+str(Npix)+'\nfor '+name)
1756                                    return
1757                                newImage = newImage+scale*image
1758                            else:
1759                                imSize = Npix
1760                                newImage = newImage+scale*image
1761                            del(image)
1762                    if not Found:
1763                        self.ErrorDialog('Image sum error','No nonzero image multipliers found')
1764                        return
1765                       
1766                    newImage = np.asfarray(newImage,dtype=np.float32)                       
1767                    outname = 'IMG '+result[-1]
1768                    Id = 0
1769                    if outname in Names:
1770                        dlg2 = wx.MessageDialog(self,'Overwrite data?','Duplicate data name',wx.OK|wx.CANCEL)
1771                        try:
1772                            if dlg2.ShowModal() == wx.ID_OK:
1773                                Id = G2gd.GetPatternTreeItemId(self,self.root,name)
1774                        finally:
1775                            dlg2.Destroy()
1776                    else:
1777                        Id = self.PatternTree.AppendItem(parent=self.root,text=outname)
1778                    if Id:
1779                        dlg = wx.FileDialog(self, 'Choose sum image filename', '.', '', 
1780                            'G2img files (*.G2img)|*.G2img', 
1781                            wx.SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
1782                        if dlg.ShowModal() == wx.ID_OK:
1783                            newimagefile = dlg.GetPath()
1784                            newimagefile = G2IO.FileDlgFixExt(dlg,newimagefile)
1785                            G2IO.PutG2Image(newimagefile,Comments,Data,Npix,newImage)
1786                            Imax = np.amax(newImage)
1787                            Imin = np.amin(newImage)
1788                            newImage = []
1789                            self.PatternTree.SetItemPyData(Id,[imSize,newimagefile])
1790                            self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Comments'),Comments)
1791                        del(newImage)
1792                        if self.imageDefault:
1793                            Data = copy.copy(self.imageDefault)
1794                        Data['showLines'] = True
1795                        Data['ring'] = []
1796                        Data['rings'] = []
1797                        Data['cutoff'] = 10
1798                        Data['pixLimit'] = 20
1799                        Data['ellipses'] = []
1800                        Data['calibrant'] = ''
1801                        Data['range'] = [(Imin,Imax),[Imin,Imax]]
1802                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Image Controls'),Data)                                           
1803                        Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Thresholds':[(Imin,Imax),[Imin,Imax]]}
1804                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Masks'),Masks)
1805                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='Stress/Strain'),{})
1806                        self.PatternTree.SelectItem(Id)
1807                        self.PatternTree.Expand(Id)
1808                        self.PickId = G2gd.GetPatternTreeItemId(self,self.root,outname)
1809                        self.Image = self.PickId
1810            finally:
1811                dlg.Destroy()
1812                     
1813    def OnAddPhase(self,event):
1814        'Add a new, empty phase to the tree. Called by Data/Add Phase menu'
1815        if not G2gd.GetPatternTreeItemId(self,self.root,'Phases'):
1816            sub = self.PatternTree.AppendItem(parent=self.root,text='Phases')
1817        else:
1818            sub = G2gd.GetPatternTreeItemId(self,self.root,'Phases')
1819        PhaseName = ''
1820        dlg = wx.TextEntryDialog(None,'Enter a name for this phase','Phase Name Entry','New phase',
1821            style=wx.OK)
1822        if dlg.ShowModal() == wx.ID_OK:
1823            PhaseName = dlg.GetValue()
1824        dlg.Destroy()
1825        sub = self.PatternTree.AppendItem(parent=sub,text=PhaseName)
1826        E,SGData = G2spc.SpcGroup('P 1')
1827        self.PatternTree.SetItemPyData(sub,G2IO.SetNewPhase(Name=PhaseName,SGData=SGData))
1828       
1829    def OnDeletePhase(self,event):
1830        'Delete a phase from the tree. Called by Data/Delete Phase menu'
1831        #Hmm, also need to delete this phase from Reflection Lists for each PWDR histogram
1832        if self.dataFrame:
1833            self.dataFrame.Clear() 
1834        TextList = []
1835        DelList = []
1836        DelItemList = []
1837        if G2gd.GetPatternTreeItemId(self,self.root,'Phases'):
1838            sub = G2gd.GetPatternTreeItemId(self,self.root,'Phases')
1839        else:
1840            return
1841        if sub:
1842            item, cookie = self.PatternTree.GetFirstChild(sub)
1843            while item:
1844                TextList.append(self.PatternTree.GetItemText(item))
1845                item, cookie = self.PatternTree.GetNextChild(sub, cookie)               
1846            dlg = wx.MultiChoiceDialog(self, 'Which phase to delete?', 'Delete phase', TextList, wx.CHOICEDLG_STYLE)
1847            try:
1848                if dlg.ShowModal() == wx.ID_OK:
1849                    result = dlg.GetSelections()
1850                    for i in result: DelList.append([i,TextList[i]])
1851                    item, cookie = self.PatternTree.GetFirstChild(sub)
1852                    i = 0
1853                    while item:
1854                        if [i,self.PatternTree.GetItemText(item)] in DelList: DelItemList.append(item)
1855                        item, cookie = self.PatternTree.GetNextChild(sub, cookie)
1856                        i += 1
1857                    for item in DelItemList:
1858                        name = self.PatternTree.GetItemText(item)
1859                        self.PatternTree.Delete(item)
1860                        self.G2plotNB.Delete(name)
1861                    item, cookie = self.PatternTree.GetFirstChild(self.root)
1862                    while item:
1863                        name = self.PatternTree.GetItemText(item)
1864                        if 'PWDR' in name:
1865                            Id = G2gd.GetPatternTreeItemId(self,item, 'Reflection Lists')
1866                            refList = self.PatternTree.GetItemPyData(Id)
1867                            for i,item in DelList:
1868                                del(refList[item])
1869                            self.PatternTree.SetItemPyData(Id,refList)
1870                        item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
1871            finally:
1872                dlg.Destroy()
1873               
1874    def OnRenameData(self,event):
1875        'Renames an existing phase. Called by Data/Rename Phase menu'
1876        name = self.PatternTree.GetItemText(self.PickId)     
1877        if 'PWDR' in name or 'HKLF' in name or 'IMG' in name:
1878            dataType = name[:name.index(' ')+1]                 #includes the ' '
1879            dlg = wx.TextEntryDialog(self,'Data name: '+dataType,'Change data name',
1880                defaultValue=name[name.index(' ')+1:])
1881            try:
1882                if dlg.ShowModal() == wx.ID_OK:
1883                    self.PatternTree.SetItemText(self.PickId,dataType+dlg.GetValue())
1884            finally:
1885                dlg.Destroy()
1886       
1887    def GetFileList(self,fileType,skip=None):        #potentially useful?
1888        'Appears unused. Note routine of same name in GSASIIpwdGUI'
1889        fileList = []
1890        Source = ''
1891        id, cookie = self.PatternTree.GetFirstChild(self.root)
1892        while id:
1893            name = self.PatternTree.GetItemText(id)
1894            if fileType in name:
1895                if id == skip:
1896                    Source = name
1897                else:
1898                    fileList.append([False,name,id])
1899            id, cookie = self.PatternTree.GetNextChild(self.root, cookie)
1900        if skip:
1901            return fileList,Source
1902        else:
1903            return fileList
1904           
1905    def OnDataDelete(self, event):
1906        '''Delete one or more histograms from data tree. Called by the
1907        Data/DeleteData menu
1908        '''
1909        TextList = ['All Data']
1910        DelList = []
1911        DelItemList = []
1912        ifPWDR = False
1913        ifIMG = False
1914        ifHKLF = False
1915        ifPDF = False
1916        if self.PatternTree.GetCount():
1917            item, cookie = self.PatternTree.GetFirstChild(self.root)
1918            while item:
1919                name = self.PatternTree.GetItemText(item)
1920                if name not in ['Notebook','Controls','Covariance','Constraints','Restraints','Phases']:
1921                    if 'PWDR' in name: ifPWDR = True
1922                    if 'IMG' in name: ifIMG = True
1923                    if 'HKLF' in name: ifHKLF = True
1924                    if 'PDF' in name: ifPDF = True
1925                    TextList.append(name)
1926                item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
1927            if ifPWDR: TextList.insert(1,'All PWDR')
1928            if ifIMG: TextList.insert(1,'All IMG')
1929            if ifHKLF: TextList.insert(1,'All HKLF')
1930            if ifPDF: TextList.insert(1,'All PDF')               
1931            dlg = wx.MultiChoiceDialog(self, 'Which data to delete?', 'Delete data', TextList, wx.CHOICEDLG_STYLE)
1932            try:
1933                if dlg.ShowModal() == wx.ID_OK:
1934                    result = dlg.GetSelections()
1935                    for i in result: DelList.append(TextList[i])
1936                    if 'All Data' in DelList:
1937                        DelList = [item for item in TextList if item[:3] != 'All']
1938                    elif 'All PWDR' in DelList:
1939                        DelList = [item for item in TextList if item[:4] == 'PWDR']
1940                    elif 'All IMG' in DelList:
1941                        DelList = [item for item in TextList if item[:3] == 'IMG']
1942                    elif 'All HKLF' in DelList:
1943                        DelList = [item for item in TextList if item[:4] == 'HKLF']
1944                    elif 'All PDF' in DelList:
1945                        DelList = [item for item in TextList if item[:3] == 'PDF']
1946                    item, cookie = self.PatternTree.GetFirstChild(self.root)
1947                    while item:
1948                        if self.PatternTree.GetItemText(item) in DelList: DelItemList.append(item)
1949                        item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
1950                    for item in DelItemList:
1951                        self.PatternTree.Delete(item)
1952                    self.PickId = 0
1953                    wx.CallAfter(G2plt.PlotPatterns,self,True)                        #so plot gets updated
1954            finally:
1955                dlg.Destroy()
1956
1957    def OnFileOpen(self, event, filename=None):
1958        '''Reads in a GSAS-II .gpx project file in response to the
1959        File/Open Project menu button
1960        '''
1961        result = wx.ID_OK
1962        Id = 0
1963        if self.PatternTree.GetChildrenCount(self.root,False):
1964            if self.dataFrame:
1965                self.dataFrame.Clear() 
1966            dlg = wx.MessageDialog(
1967                self,
1968                'Do you want to overwrite the current project? '
1969                'Any unsaved changes will be lost. Press OK to continue.',
1970                'Overwrite?',  wx.OK | wx.CANCEL)
1971            try:
1972                result = dlg.ShowModal()
1973                if result == wx.ID_OK:
1974                    self.PatternTree.DeleteChildren(self.root)
1975                    self.GSASprojectfile = ''
1976                    if self.HKL: self.HKL = []
1977                    if self.G2plotNB.plotList:
1978                        self.G2plotNB.clear()
1979            finally:
1980                dlg.Destroy()
1981        if result != wx.ID_OK: return
1982
1983        if not filename:
1984            if self.dataDisplay: self.dataDisplay.Destroy()
1985            dlg = wx.FileDialog(self, 'Choose GSAS-II project file', '.', '', 
1986                'GSAS-II project file (*.gpx)|*.gpx',wx.OPEN|wx.CHANGE_DIR)
1987            try:
1988                if dlg.ShowModal() != wx.ID_OK: return
1989                self.GSASprojectfile = dlg.GetPath()
1990                self.GSASprojectfile = G2IO.FileDlgFixExt(dlg,self.GSASprojectfile)
1991                self.dirname = dlg.GetDirectory()
1992            finally:
1993                dlg.Destroy()
1994        else:
1995            self.GSASprojectfile = os.path.splitext(filename)[0]+'.gpx'
1996            self.dirname = os.path.split(filename)[0]
1997
1998        G2IO.ProjFileOpen(self)
1999        self.PatternTree.SetItemText(self.root,'Loaded Data: '+self.GSASprojectfile)
2000        self.PatternTree.Expand(self.root)
2001        self.HKL = []
2002        item, cookie = self.PatternTree.GetFirstChild(self.root)
2003        while item and not Id:
2004            name = self.PatternTree.GetItemText(item)
2005            if name[:4] in ['PWDR','HKLF','IMG ','PDF ']:
2006                Id = item
2007            elif name == 'Controls':
2008                data = self.PatternTree.GetItemPyData(item)
2009                if data:
2010                    for item in self.Refine: item.Enable(True)
2011                    for item in self.SeqRefine: item.Enable(True)
2012            item, cookie = self.PatternTree.GetNextChild(self.root, cookie)               
2013        if Id:
2014            self.PatternTree.SelectItem(Id)
2015        self.CheckNotebook()
2016        os.chdir(self.dirname)           # to get Mac/Linux to change directory!
2017
2018    def OnFileClose(self, event):
2019        '''Clears the data tree in response to the
2020        File/Close Project menu button. User is given option to save
2021        the project.
2022        '''
2023        if self.dataFrame:
2024            self.dataFrame.Clear()
2025            self.dataFrame.SetLabel('GSAS-II data display') 
2026        dlg = wx.MessageDialog(self, 'Save current project?', ' ', wx.YES | wx.NO | wx.CANCEL)
2027        try:
2028            result = dlg.ShowModal()
2029            if result == wx.ID_OK:
2030                self.OnFileSaveMenu(event)
2031            if result != wx.ID_CANCEL:
2032                self.GSASprojectfile = ''
2033                self.PatternTree.SetItemText(self.root,'Loaded Data: ')
2034                self.PatternTree.DeleteChildren(self.root)
2035                if self.HKL: self.HKL = []
2036                if self.G2plotNB.plotList:
2037                    self.G2plotNB.clear()
2038        finally:
2039            dlg.Destroy()
2040
2041    def OnFileSave(self, event):
2042        '''Save the current project in response to the
2043        File/Save Project menu button
2044        '''
2045       
2046        if self.GSASprojectfile: 
2047            self.PatternTree.SetItemText(self.root,'Loaded Data: '+self.GSASprojectfile)
2048            G2IO.ProjFileSave(self)
2049        else:
2050            self.OnFileSaveas(event)
2051
2052    def OnFileSaveas(self, event):
2053        '''Save the current project in response to the
2054        File/Save as menu button
2055        '''
2056        dlg = wx.FileDialog(self, 'Choose GSAS-II project file name', '.', '', 
2057            'GSAS-II project file (*.gpx)|*.gpx',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
2058        try:
2059            if dlg.ShowModal() == wx.ID_OK:
2060                self.GSASprojectfile = dlg.GetPath()
2061                self.GSASprojectfile = G2IO.FileDlgFixExt(dlg,self.GSASprojectfile)
2062                self.PatternTree.SetItemText(self.root,'Saving project as'+self.GSASprojectfile)
2063                self.SetTitle("GSAS-II data tree: "+os.path.split(self.GSASprojectfile)[1])
2064                G2IO.ProjFileSave(self)
2065                os.chdir(dlg.GetDirectory())           # to get Mac/Linux to change directory!
2066        finally:
2067            dlg.Destroy()
2068
2069    def ExitMain(self, event):
2070        '''Called if the main window is closed'''
2071        if self.undofile:
2072            os.remove(self.undofile)
2073        sys.exit()
2074       
2075    def OnFileExit(self, event):
2076        '''Called in response to the File/Quit menu button'''
2077        if self.dataFrame:
2078            self.dataFrame.Clear() 
2079            self.dataFrame.Destroy()
2080        self.Close()
2081       
2082    def OnExportPatterns(self,event):
2083        names = ['All']
2084        exports = []
2085        item, cookie = self.PatternTree.GetFirstChild(self.root)
2086        while item:
2087            name = self.PatternTree.GetItemText(item)
2088            if 'PWDR' in name:
2089                names.append(name)
2090            item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
2091        if names:
2092            dlg = wx.MultiChoiceDialog(self,'Select','Powder patterns to export',names)
2093            if dlg.ShowModal() == wx.ID_OK:
2094                sel = dlg.GetSelections()
2095                if sel[0] == 0:
2096                    exports = names[1:]
2097                else:
2098                    for x in sel:
2099                        exports.append(names[x])
2100            dlg.Destroy()
2101        if exports:
2102            dlg = wx.FileDialog(self, 'Choose output powder file name', '.', '', 
2103                'GSAS fxye file (*.fxye)|*.fxye|xye file (*.xye)|*.xye',
2104                wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
2105            try:
2106                if dlg.ShowModal() == wx.ID_OK:
2107                    powderfile = dlg.GetPath()
2108                    powderfile = G2IO.FileDlgFixExt(dlg,powderfile)
2109                    if 'fxye' in powderfile:
2110                        G2IO.powderFxyeSave(self,exports,powderfile)
2111                    else:       #just xye
2112                        G2IO.powderXyeSave(self,exports,powderfile)
2113            finally:
2114                dlg.Destroy()
2115       
2116    def OnExportPeakList(self,event):
2117        dlg = wx.FileDialog(self, 'Choose output peak list file name', '.', '', 
2118            '(*.*)|*.*',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
2119        try:
2120            if dlg.ShowModal() == wx.ID_OK:
2121                self.peaklistfile = dlg.GetPath()
2122                self.peaklistfile = G2IO.FileDlgFixExt(dlg,self.peaklistfile)
2123                file = open(self.peaklistfile,'w')               
2124                item, cookie = self.PatternTree.GetFirstChild(self.root)
2125                while item:
2126                    name = self.PatternTree.GetItemText(item)
2127                    if 'PWDR' in name:
2128                        item2, cookie2 = self.PatternTree.GetFirstChild(item)
2129                        while item2:
2130                            name2 = self.PatternTree.GetItemText(item2)
2131                            if name2 == 'Peak List':
2132                                peaks = self.PatternTree.GetItemPyData(item2)
2133                                file.write("%s \n" % (name+' Peak List'))               
2134                                for peak in peaks:
2135                                    file.write("%10.5f %12.2f %10.3f %10.3f \n" % \
2136                                        (peak[0],peak[2],peak[4],peak[6]))
2137                            item2, cookie2 = self.PatternTree.GetNextChild(item, cookie2)                           
2138                    item, cookie = self.PatternTree.GetNextChild(self.root, cookie)                           
2139                file.close()
2140        finally:
2141            dlg.Destroy()
2142       
2143    def OnExportHKL(self,event):
2144        dlg = wx.FileDialog(self, 'Choose output reflection list file name', '.', '', 
2145            '(*.*)|*.*',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT|wx.CHANGE_DIR)
2146        try:
2147            if dlg.ShowModal() == wx.ID_OK:
2148                self.peaklistfile = dlg.GetPath()
2149                self.peaklistfile = G2IO.FileDlgFixExt(dlg,self.peaklistfile)
2150                file = open(self.peaklistfile,'w')               
2151                item, cookie = self.PatternTree.GetFirstChild(self.root)
2152                while item:
2153                    name = self.PatternTree.GetItemText(item)
2154                    if 'PWDR' in name:
2155                        item2, cookie2 = self.PatternTree.GetFirstChild(item)
2156                        while item2:
2157                            name2 = self.PatternTree.GetItemText(item2)
2158                            if name2 == 'Reflection Lists':
2159                                data = self.PatternTree.GetItemPyData(item2)
2160                                phases = data.keys()
2161                                for phase in phases:
2162                                    peaks = data[phase]
2163                                    file.write("%s %s %s \n" % (name,phase,' Reflection List'))
2164                                    file.write('%s \n'%(' h  k  l  m  d-space 2-theta wid F**2'))               
2165                                    for peak in peaks:
2166                                        FWHM = G2pwd.getgamFW(peak[7],peak[6])/50.      #to get delta-2-theta in deg.
2167                                        file.write(" %3d %3d %3d %3d %10.5f %10.5f %10.5f %10.3f \n" % \
2168                                            (int(peak[0]),int(peak[1]),int(peak[2]),int(peak[3]),peak[4],peak[5],FWHM,peak[8]))
2169                            item2, cookie2 = self.PatternTree.GetNextChild(item, cookie2)                           
2170                    item, cookie = self.PatternTree.GetNextChild(self.root, cookie)                           
2171                file.close()
2172        finally:
2173            dlg.Destroy()
2174       
2175    def OnExportPDF(self,event):
2176        #need S(Q) and G(R) to be saved here - probably best from selection?
2177        names = ['All']
2178        exports = []
2179        item, cookie = self.PatternTree.GetFirstChild(self.root)
2180        while item:
2181            name = self.PatternTree.GetItemText(item)
2182            if 'PDF' in name:
2183                names.append(name)
2184            item, cookie = self.PatternTree.GetNextChild(self.root, cookie)
2185        if names:
2186            dlg = wx.MultiChoiceDialog(self,'Select','PDF patterns to export',names)
2187            if dlg.ShowModal() == wx.ID_OK:
2188                sel = dlg.GetSelections()
2189                if sel[0] == 0:
2190                    exports = names[1:]
2191                else:
2192                    for x in sel:
2193                        exports.append(names[x])
2194            dlg.Destroy()
2195        if exports:
2196            G2IO.PDFSave(self,exports)
2197       
2198    def OnExportPhase(self,event):
2199        event.Skip()
2200       
2201    def OnExportCIF(self,event):
2202        event.Skip()
2203
2204    def OnMakePDFs(self,event):
2205        '''Calculates PDFs
2206        '''
2207        tth2q = lambda t,w:4.0*math.pi*sind(t/2.0)/w
2208        TextList = ['All PWDR']
2209        PDFlist = []
2210        Names = []
2211        if self.PatternTree.GetCount():
2212            id, cookie = self.PatternTree.GetFirstChild(self.root)
2213            while id:
2214                name = self.PatternTree.GetItemText(id)
2215                Names.append(name)
2216                if 'PWDR' in name:
2217                    TextList.append(name)
2218                id, cookie = self.PatternTree.GetNextChild(self.root, cookie)
2219            if len(TextList) == 1:
2220                self.ErrorDialog('Nothing to make PDFs for','There must be at least one "PWDR" pattern')
2221                return
2222            dlg = wx.MultiChoiceDialog(self,'Make PDF controls','Make PDF controls for:',TextList, wx.CHOICEDLG_STYLE)
2223            try:
2224                if dlg.ShowModal() == wx.ID_OK:
2225                    result = dlg.GetSelections()
2226                    for i in result: PDFlist.append(TextList[i])
2227                    if 0 in result:
2228                        PDFlist = [item for item in TextList if item[:4] == 'PWDR']                       
2229                    for item in PDFlist:
2230                        PWDRname = item[4:]
2231                        Id = self.PatternTree.AppendItem(parent=self.root,text='PDF '+PWDRname)
2232                        Data = {
2233                            'Sample':{'Name':item,'Mult':1.0,'Add':0.0},
2234                            'Sample Bkg.':{'Name':'','Mult':-1.0,'Add':0.0},
2235                            'Container':{'Name':'','Mult':-1.0,'Add':0.0},
2236                            'Container Bkg.':{'Name':'','Mult':-1.0,'Add':0.0},'ElList':{},
2237                            'Geometry':'Cylinder','Diam':1.0,'Pack':0.50,'Form Vol':10.0,
2238                            'DetType':'Image plate','ObliqCoeff':0.2,'Ruland':0.025,'QScaleLim':[0,100],
2239                            'Lorch':True,}
2240                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='PDF Controls'),Data)
2241                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='I(Q)'+PWDRname),[])       
2242                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='S(Q)'+PWDRname),[])       
2243                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='F(Q)'+PWDRname),[])       
2244                        self.PatternTree.SetItemPyData(self.PatternTree.AppendItem(Id,text='G(R)'+PWDRname),[])       
2245                for item in self.ExportPDF: item.Enable(True)
2246            finally:
2247                dlg.Destroy()
2248               
2249    def GetPWDRdatafromTree(self,PWDRname):
2250        ''' Returns powder data from GSASII tree
2251
2252        :param str PWDRname: a powder histogram name as obtained from
2253          :meth:`GSASIIstruct.GetHistogramNames`
2254
2255        :returns: PWDRdata = powder data dictionary with
2256          Powder data arrays, Limits, Instrument Parameters,
2257          Sample Parameters           
2258        '''
2259        PWDRdata = {}
2260        try:
2261            PWDRdata.update(self.PatternTree.GetItemPyData(PWDRname)[0])            #wtFactor + ?
2262        except ValueError:
2263            PWDRdata['wtFactor'] = 1.0
2264        PWDRdata['Data'] = self.PatternTree.GetItemPyData(PWDRname)[1]          #powder data arrays
2265        PWDRdata['Limits'] = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,PWDRname,'Limits'))
2266        PWDRdata['Background'] = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,PWDRname,'Background'))
2267        PWDRdata['Instrument Parameters'] = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,PWDRname,'Instrument Parameters'))
2268        PWDRdata['Sample Parameters'] = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,PWDRname,'Sample Parameters'))
2269        PWDRdata['Reflection Lists'] = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,PWDRname,'Reflection Lists'))
2270        if 'ranId' not in PWDRdata['Sample Parameters']:
2271            PWDRdata['Sample Parameters']['ranId'] = ran.randint(0,sys.maxint)
2272        PWDRdata['ranId'] = PWDRdata['Sample Parameters']['ranId']
2273        return PWDRdata
2274
2275    def GetHKLFdatafromTree(self,HKLFname):
2276        ''' Returns single crystal data from GSASII tree
2277
2278        :param str HKLFname: a single crystal histogram name as obtained
2279          from
2280          :meth:`GSASIIstruct.GetHistogramNames`
2281
2282        :returns: HKLFdata = single crystal data list of reflections
2283
2284        '''
2285        HKLFdata = {}
2286        try:
2287            HKLFdata.update(self.PatternTree.GetItemPyData(HKLFname)[0])            #wtFactor + ?
2288        except ValueError:
2289            HKLFdata['wtFactor'] = 1.0
2290        HKLFdata['Data'] = self.PatternTree.GetItemPyData(HKLFname)[1]
2291        HKLFdata['Instrument Parameters'] = self.PatternTree.GetItemPyData(G2gd.GetPatternTreeItemId(self,HKLFname,'Instrument Parameters'))
2292        return HKLFdata
2293       
2294    def GetPhaseData(self):
2295        '''Returns a list of defined phases. Used only in GSASIIgrid
2296        Note routine :meth:`GSASIIstruct.GetPhaseData` also exists.
2297        '''
2298        phaseData = {}
2299        if G2gd.GetPatternTreeItemId(self,self.root,'Phases'):
2300            sub = G2gd.GetPatternTreeItemId(self,self.root,'Phases')
2301        else:
2302            print 'no phases to be refined'
2303            return
2304        if sub:
2305            item, cookie = self.PatternTree.GetFirstChild(sub)
2306            while item:
2307                phaseName = self.PatternTree.GetItemText(item)
2308                phaseData[phaseName] =  self.PatternTree.GetItemPyData(item)
2309                if 'ranId' not in phaseData[phaseName]:
2310                    phaseData[phaseName]['ranId'] = ran.randint(0,sys.maxint)         
2311                item, cookie = self.PatternTree.GetNextChild(sub, cookie)
2312        return phaseData               
2313                   
2314    def GetUsedHistogramsAndPhasesfromTree(self):
2315        ''' Returns all histograms that are found in any phase
2316        and any phase that uses a histogram
2317        :returns: two dicts:
2318
2319            * Histograms = dictionary of histograms as {name:data,...}
2320            * Phases = dictionary of phases that use histograms
2321        '''
2322        phaseData = self.GetPhaseData()
2323        if not phaseData:
2324            return {},{}
2325        Histograms = {}
2326        Phases = {}
2327        pId = 0
2328        hId = 0
2329        for phase in phaseData:
2330            Phase = phaseData[phase]
2331            if Phase['Histograms']:
2332                if phase not in Phases:
2333                    Phase['pId'] = pId
2334                    pId += 1
2335                    Phases[phase] = Phase
2336                for hist in Phase['Histograms']:
2337                    if hist not in Histograms:
2338                        item = G2gd.GetPatternTreeItemId(self,self.root,hist)
2339                        if 'PWDR' in hist[:4]: 
2340                            Histograms[hist] = self.GetPWDRdatafromTree(item)
2341                        elif 'HKLF' in hist[:4]:
2342                            Histograms[hist] = self.GetHKLFdatafromTree(item)
2343                        #future restraint, etc. histograms here           
2344                        Histograms[hist]['hId'] = hId
2345                        hId += 1
2346        return Histograms,Phases
2347       
2348    class ViewParmDialog(wx.Dialog):
2349        '''Window to show all parameters in the refinement.
2350        Called from :meth:`OnViewLSParms`
2351        '''
2352        def __init__(self,parent,title,parmDict):
2353            wx.Dialog.__init__(self,parent,-1,title,size=(300,430),
2354                pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
2355            panel = wx.Panel(self,size=(300,430))
2356            parmNames = parmDict.keys()
2357            parmNames.sort()
2358            parmText = ' p:h:Parameter       refine?              value\n'
2359            for name in parmNames:
2360                parmData = parmDict[name]
2361                try:
2362                    parmText += ' %s \t%12.4g \n'%(name.ljust(19)+'\t'+parmData[1],parmData[0])
2363                except TypeError:
2364                    pass
2365            parmTable = wx.TextCtrl(panel,-1,parmText,
2366                style=wx.TE_MULTILINE|wx.TE_READONLY,size=(290,400))
2367            mainSizer = wx.BoxSizer(wx.VERTICAL)
2368            mainSizer.Add(parmTable)
2369            panel.SetSizer(mainSizer)
2370                           
2371    def OnViewLSParms(self,event):
2372        '''Displays a window showing all parameters in the refinement.
2373        Called from the Calculate/View LS Parms menu.
2374        '''
2375        parmDict = {}
2376        Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree()
2377        rigidbodyDict = self.PatternTree.GetItemPyData(   
2378            G2gd.GetPatternTreeItemId(self,self.root,'Rigid bodies'))
2379        rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
2380        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
2381        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,BLtable = G2stIO.GetPhaseData(Phases,RestraintDict=None,rbIds=rbIds,Print=False)       
2382        hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,Histograms,Print=False)
2383        histVary,histDict,controlDict = G2stIO.GetHistogramData(Histograms,Print=False)
2384        varyList = rbVary+phaseVary+hapVary+histVary
2385        parmDict.update(rbDict)
2386        parmDict.update(phaseDict)
2387        parmDict.update(hapDict)
2388        parmDict.update(histDict)
2389        for parm in parmDict:
2390            if parm.split(':')[-1] in ['Azimuth','Gonio. radius','Lam1','Lam2',
2391                'Omega','Chi','Phi','nDebye','nPeaks']:
2392                parmDict[parm] = [parmDict[parm],' ']
2393            elif parm.split(':')[-2] in ['Ax','Ay','Az','SHmodel','SHord']:
2394                parmDict[parm] = [parmDict[parm],' ']
2395            elif parm in varyList:
2396                parmDict[parm] = [parmDict[parm],'True']
2397            else:
2398                parmDict[parm] = [parmDict[parm],'False']
2399        parmDict[' Num refined'] = [len(varyList),'']
2400        dlg = self.ViewParmDialog(self,'Parameters for least squares',parmDict)
2401        try:
2402            if dlg.ShowModal() == wx.ID_OK:
2403                print 'do something with changes?? - No!'
2404        finally:
2405            dlg.Destroy()
2406       
2407    def OnRefine(self,event):
2408        '''Perform a refinement.
2409        Called from the Calculate/Refine menu.
2410        '''       
2411        self.OnFileSave(event)
2412        # check that constraints are OK here
2413        errmsg, warnmsg = G2stIO.CheckConstraints(self.GSASprojectfile)
2414        if errmsg:
2415            print('Error in constraints:\n'+errmsg+
2416                  '\nRefinement not possible')
2417            self.ErrorDialog('Constraint Error',
2418                             'Error in constraints:\n'+errmsg+
2419                             '\nRefinement not possible')
2420            return
2421        if warnmsg:
2422            print('Conflict between refinment flag settings and constraints:\n'+
2423                  warnmsg+'\nRefinement not possible')
2424            self.ErrorDialog('Refinement Flag Error',
2425                             'Conflict between refinment flag settings and constraints:\n'+
2426                             warnmsg+
2427                             '\nRefinement not possible')
2428            return
2429        #works - but it'd be better if it could restore plots
2430        dlg = wx.ProgressDialog('Residual','All data Rw =',101.0, 
2431            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT)
2432        screenSize = wx.ClientDisplayRect()
2433        Size = dlg.GetSize()
2434        Size = (int(Size[0]*1.2),Size[1]) # increase size a bit along x
2435        dlg.SetPosition(wx.Point(screenSize[2]-Size[0]-305,screenSize[1]+5))
2436        dlg.SetSize(Size)
2437        Rw = 100.00
2438        try:
2439            Rw = G2stMn.Refine(self.GSASprojectfile,dlg)
2440        finally:
2441            dlg.Destroy()
2442        oldId =  self.PatternTree.GetSelection()
2443        oldName = self.PatternTree.GetItemText(oldId)
2444        parentId = self.PatternTree.GetItemParent(oldId)
2445        parentName = ''
2446        if parentId:
2447            parentName = self.PatternTree.GetItemText(parentId)
2448        dlg = wx.MessageDialog(self,'Load new result?','Refinement results, Rw =%.3f'%(Rw),wx.OK|wx.CANCEL)
2449        try:
2450            if dlg.ShowModal() == wx.ID_OK:
2451                Id = 0
2452                self.PatternTree.DeleteChildren(self.root)
2453                if self.HKL: self.HKL = []
2454                if self.G2plotNB.plotList:
2455                    self.G2plotNB.clear()
2456                G2IO.ProjFileOpen(self)
2457                item, cookie = self.PatternTree.GetFirstChild(self.root)
2458                while item and not Id:
2459                    name = self.PatternTree.GetItemText(item)
2460                    if name[:4] in ['PWDR','HKLF']:
2461                        Id = item
2462                    item, cookie = self.PatternTree.GetNextChild(self.root, cookie)               
2463                if parentName:
2464                    parentId = G2gd.GetPatternTreeItemId(self, self.root, parentName)
2465                    if parentId:
2466                        itemId = G2gd.GetPatternTreeItemId(self, parentId, oldName)
2467                    else:
2468                        itemId = G2gd.GetPatternTreeItemId(self, self.root, oldName)
2469                    self.PatternTree.SelectItem(itemId)
2470                elif Id:
2471                    self.PatternTree.SelectItem(Id)
2472        finally:
2473            dlg.Destroy()
2474
2475    def OnSeqRefine(self,event):
2476        '''Perform a sequential refinement.
2477        Called from the Calculate/Sequential refine menu.
2478        '''       
2479        Id = G2gd.GetPatternTreeItemId(self,self.root,'Sequental results')
2480        if not Id:
2481            Id = self.PatternTree.AppendItem(self.root,text='Sequental results')
2482            self.PatternTree.SetItemPyData(Id,{})           
2483        self.OnFileSave(event)
2484        # check that constraints are OK here
2485        errmsg, warnmsg = G2stIO.CheckConstraints(self.GSASprojectfile)
2486        if errmsg:
2487            print('Error in constraints:\n'+errmsg+
2488                  '\nRefinement not possible')
2489            self.ErrorDialog('Constraint Error',
2490                             'Error in constraints:\n'+errmsg+
2491                             '\nRefinement not possible')
2492            return
2493        if warnmsg:
2494            print('Conflict between refinment flag settings and constraints:\n'+
2495                  warnmsg+'\nRefinement not possible')
2496            self.ErrorDialog('Refinement Flag Error',
2497                             'Conflict between refinment flag settings and constraints:\n'+
2498                             warnmsg+'\nRefinement not possible')
2499            return
2500        dlg = wx.ProgressDialog('Residual for histogram 0','Powder profile Rwp =',101.0, 
2501            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT)
2502        screenSize = wx.ClientDisplayRect()
2503        Size = dlg.GetSize()
2504        Size = (int(Size[0]*1.2),Size[1]) # increase size a bit along x
2505        dlg.SetPosition(wx.Point(screenSize[2]-Size[0]-305,screenSize[1]+5))
2506        dlg.SetSize(Size)
2507        try:
2508            G2stMn.SeqRefine(self.GSASprojectfile,dlg)
2509        finally:
2510            dlg.Destroy()       
2511        dlg = wx.MessageDialog(self,'Load new result?','Refinement results',wx.OK|wx.CANCEL)
2512        try:
2513            if dlg.ShowModal() == wx.ID_OK:
2514                Id = 0
2515                self.PatternTree.DeleteChildren(self.root)
2516                if self.HKL: self.HKL = []
2517                if self.G2plotNB.plotList:
2518                    self.G2plotNB.clear()
2519                G2IO.ProjFileOpen(self)
2520                item, cookie = self.PatternTree.GetFirstChild(self.root)
2521                while item and not Id:
2522                    name = self.PatternTree.GetItemText(item)
2523                    if name[:4] in ['PWDR','HKLF']:
2524                        Id = item
2525                    item, cookie = self.PatternTree.GetNextChild(self.root, cookie)               
2526                if Id:
2527                    self.PatternTree.SelectItem(Id)
2528        finally:
2529            dlg.Destroy()
2530       
2531    def ErrorDialog(self,title,message,parent=None, wtype=wx.OK):
2532        'Display an error message'
2533        result = None
2534        if parent is None:
2535            dlg = wx.MessageDialog(self, message, title,  wtype)
2536        else:
2537            dlg = wx.MessageDialog(parent, message, title,  wtype)
2538            dlg.CenterOnParent() # not working on Mac
2539        try:
2540            result = dlg.ShowModal()
2541        finally:
2542            dlg.Destroy()
2543        return result
2544
2545class GSASIImain(wx.App):
2546    '''Defines a wxApp for GSAS-II
2547
2548    Creates a wx frame (self.main) which contains the display of the
2549    data tree.
2550    '''
2551    def OnInit(self):
2552        '''Called automatically when the app is created.'''
2553        self.main = GSASII(None)
2554        self.main.Show()
2555        self.SetTopWindow(self.main)
2556        return True
2557    def MacOpenFile(self, filename):
2558        '''Called on Mac every time a file is dropped on the app when it is running,
2559        treat this like a File/Open project menu action.
2560        Should be ignored on other platforms
2561        '''
2562        self.main.OnFileOpen(None,filename)
2563
2564def main():
2565    '''Start up the GSAS-II application'''
2566    #application = GSASIImain() # don't redirect output, someday we
2567    # may want to do this if we can
2568    application = GSASIImain(0)
2569    if wxInspector: wxeye.InspectionTool().Show()
2570
2571    #application.main.OnRefine(None)
2572    application.MainLoop()
2573   
2574if __name__ == '__main__':
2575    main() # start the GUI
Note: See TracBrowser for help on using the repository browser.