source: trunk/GSASIIIntPDFtool.py @ 4364

Last change on this file since 4364 was 4364, checked in by toby, 21 months ago

minor docs changes; remove remaining .xor. reference

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 45.7 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#GSAS-II autointegration routines
4########### SVN repository information ###################
5# $Date: 2020-03-12 20:07:32 +0000 (Thu, 12 Mar 2020) $
6# $Author: toby $
7# $Revision: 4364 $
8# $URL: trunk/GSASIIIntPDFtool.py $
9# $Id: GSASIIIntPDFtool.py 4364 2020-03-12 20:07:32Z toby $
10########### SVN repository information ###################
11'''
12.. _GSASIIIntPDFtool:
13
14*GSASIIIntPDFtool: autointegration routines*
15---------------------------------------------
16
17Independent-running GSAS-II based auto-integration program with minimal
18GUI, no visualization but intended to implement significant levels of
19parallelization.
20'''
21# Autointegration from
22# $Id: GSASIIIntPDFtool.py 4364 2020-03-12 20:07:32Z toby $
23# hacked for stand-alone use
24#
25# idea: select image file type & set filter from that
26#
27from __future__ import division, print_function
28import os
29import copy
30import glob
31import time
32import re
33import math
34import sys
35import wx
36import wx.lib.mixins.listctrl  as  listmix
37import wx.grid as wg
38import numpy as np
39import GSASIIpath
40GSASIIpath.SetBinaryPath(True)
41GSASIIpath.SetVersionNumber("$Revision: 4364 $")
42import GSASIIIO as G2IO
43import GSASIIctrlGUI as G2G
44import GSASIIobj as G2obj
45import GSASIIpy3 as G2py3
46import GSASIIimgGUI as G2imG
47import GSASIIfiles as G2fil
48import GSASIIscriptable as G2sc
49import multiprocessing as mp
50
51try: # fails during doc build
52    wxMainFrameStyle = wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX
53except:
54    wxMainFrameStyle = None
55
56class AutoIntFrame(wx.Frame):
57    '''Creates a wx.Frame window for the Image AutoIntegration.
58    The intent is that this will be used as a non-modal dialog window.
59   
60    Implements a Start button that morphs into a pause and resume button.
61    This button starts a processing loop that is repeated every
62    :meth:`PollTime` seconds.
63
64    :param wx.Frame G2frame: main GSAS-II frame
65    :param float PollTime: frequency in seconds to repeat calling the
66      processing loop. (Default is 30.0 seconds.)
67    '''
68    def __init__(self,G2frame,PollTime=30.0):
69        def OnStart(event):
70            '''Called when the start button is pressed. Changes button label
71            to Pause. When Pause is pressed the label changes to Resume.
72            When either Start or Resume is pressed, the processing loop
73            is started. When Pause is pressed, the loop is stopped.
74            '''
75            self.Pause = False
76            # change button label
77            if self.btnstart.GetLabel() != 'Pause':
78                self.btnstart.SetLabel('Pause')
79                self.Status.SetStatusText('Press Pause to delay integration or Reset to prepare to reintegrate all images')
80                if self.timer.IsRunning(): self.timer.Stop()
81                self.PreventTimerReEntry = False
82                if self.StartLoop():
83                    G2G.G2MessageBox(self,'Error in setting up integration. See console')
84                    return
85                # delete pool of slaved interpreters in case input has changed
86                if self.MPpool:
87                    self.MPpool.terminate()
88                    self.MPpool = None
89#                if self.ncores >= 1: # debug
90                if self.ncores > 1:
91#                    print('Creating pool of ',self.ncores,'cores') # debug
92                    self.MPpool = mp.Pool(self.ncores)
93                self.OnTimerLoop(None) # run once immediately
94                if not self.Pause:
95                    # no pause, so start timer to check for new files
96                    self.timer.Start(int(1000*PollTime),oneShot=False)
97                    return
98            # we will get to this point if Paused
99            self.OnPause()
100           
101        def OnReset(event):
102            '''Called when Reset button is pressed. This stops the
103            processing loop and resets the list of integrated files so
104            all images can be reintegrated.
105            '''
106            self.btnstart.SetLabel('Restart')
107            self.Status.SetStatusText('Press Restart to reload and re-integrate images matching filter')
108            if self.timer.IsRunning(): self.timer.Stop()
109            self.Reset = True
110            self.Pause = True
111            self.ProcessedList = []
112            self.ShowMatchingFiles(None)
113           
114        def OnQuit(event):
115            '''Stop the processing loop and close the Frame
116            '''
117            if self.timer.IsRunning(): self.timer.Stop() # make sure we stop first
118            wx.CallAfter(self.Destroy)
119           
120        def OnBrowse(event):
121            '''Responds when the Browse button is pressed to load a file.
122            The routine determines which button was pressed and gets the
123            appropriate file type and loads it into the appropriate place
124            in the dict.
125            '''
126            if btn3 == event.GetEventObject():
127                dlg = wx.DirDialog(
128                    self, 'Select directory for output files',
129                    self.params['outdir'],wx.DD_DEFAULT_STYLE)
130                dlg.CenterOnParent()
131                try:
132                    if dlg.ShowModal() == wx.ID_OK:
133                        self.params['outdir'] = dlg.GetPath()
134                        fInp3.SetValue(self.params['outdir'])
135                finally:
136                    dlg.Destroy()
137                return
138                               
139        def showPDFctrls(event):
140            '''Called to show or hide AutoPDF widgets. Note that TextCtrl's
141            must be included in the sizer layout with .Show(True) before
142            .Show(False) will work properly.
143            '''
144            TestInput()
145            if len(self.pdfList) == 0 or self.params['TableMode']:
146                lbl4b.SetValue(False)
147                self.params['ComputePDF'] = False
148                lbl4b.Enable(False)
149            else:
150                lbl4b.Enable(True)
151
152            for w in [self.pbkg[i][4] for i in (0,1,2)]:
153                w.Enable(self.params['ComputePDF'])
154                w.Show(self.params['ComputePDF'])
155            #for o in pdfwidgets+[self.pbkg[i][2] for i in (0,1,2)]:
156            for o in pdfwidgets+[self.pdfSel]+[self.pbkg[i][1] for i in range(3)]:
157                o.Enable(self.params['ComputePDF'])
158            if self.params['ComputePDF']:
159                c = "black"
160            else:
161                c = "gray"
162            for l in ([lbl4,lbl4a,lbl5,lbl5a,lbl5b]
163                          +[self.pbkg[i][0] for i in (0,1,2)]
164                          +[self.pbkg[i][5] for i in (0,1,2)]):
165                l.SetForegroundColour(c)
166            checkPDFselection()
167            self.SendSizeEvent()
168                                   
169        def GetGPXInputFile(event):
170            'Get and read input from input GPX file'
171            pth = self.params['readdir']
172            lbl = 'Select a project input file'
173            filtyp = 'project file (.gpx)|*.gpx'
174            if event is not None:
175                dlg = wx.FileDialog(self,lbl, pth,
176                        style=wx.FD_OPEN,wildcard=filtyp)
177                dlg.CenterOnParent()
178                if dlg.ShowModal() == wx.ID_OK:
179                    self.gpxin[3] = dlg.GetPath()
180                dlg.Destroy()
181            if not os.path.exists(self.gpxin[3]):
182                G2G.G2MessageBox(self,'Error: file {} not found.'.format(self.gpxin[3]))
183                return
184            SetGPXInputFile()
185        def SetGPXInputFile():
186            gpx = G2sc.G2Project(self.gpxin[3])
187            self.imgList = gpx.images()
188            self.histList = gpx.histograms()
189            self.pdfList = gpx.pdfs()
190            if not self.imgList:
191                G2G.G2MessageBox(self,'Error: no images in {}.'.format(self.gpxin[3]))
192                return
193            self.gpxin[1].SetValue(self.gpxin[3])           
194            self.imprm[1].Clear()
195            self.imprm[1].AppendItems([i.name for i in self.imgList])
196            if len(self.imgList) == 1:
197                self.imprm[1].SetSelection(0)
198            self.maskfl[1].Clear()
199            self.maskfl[1].AppendItems(['']+[i.name for i in self.imgList])
200            for i in range(3):
201                self.pbkg[i][1].Clear()
202                self.pbkg[i][1].AppendItems(['']+[i.name for i in self.histList])
203            self.pdfSel.Clear()
204            self.pdfSel.AppendItems([i.name for i in self.pdfList])
205            showPDFctrls(None)
206
207        def TestInput(*args,**kwargs):
208            '''Determine if the start button should be enabled and
209            ask for the exporter type with ambiguous extensions
210            '''
211            for dfmt,obj in self.fmtlist.items():
212                fmt = dfmt[1:]
213                if not self.params['outsel'][fmt]: continue
214                if fmt in self.multipleFmtChoices: continue
215                if type(obj) is not list: continue # only one exporter for this
216                choices = []
217                for e in obj: 
218                    choices.append(e.formatName)
219                if len(choices) == 0:
220                    print('Error: why 0 choices in TestInput?')
221                    continue
222                if len(choices) == 1:
223                    print('Error: why 1 choice in TestInput?')
224                    self.multipleFmtChoices[fmt] = obj[0].formatName
225                    continue
226                # select the format here
227                dlg = G2G.G2SingleChoiceDialog(self,
228                        'There is more than one format with a '+
229                        '.{} output. Choose the one to use'.format(fmt),
230                        'Choose output format',choices)
231                dlg.CenterOnParent()
232                dlg.clb.SetSelection(0)  # force a selection
233                if dlg.ShowModal() == wx.ID_OK and dlg.GetSelection() >= 0:
234                    self.multipleFmtChoices[fmt] = choices[dlg.GetSelection()]
235                dlg.Destroy()
236               
237            writingSomething = False
238            writingPDF = False
239            # is at least one output file selected?
240            for dfmt in self.fmtlist:
241                fmt = dfmt[1:]
242                if self.params['outsel'][fmt]:
243                    writingSomething = True
244                    break
245            if self.params['ComputePDF']:
246                for fmt in self.PDFformats:
247                    if self.params['outsel'][fmt]:
248                        writingPDF = writingSomething = True
249                        break
250            if not writingSomething: 
251                self.EnableIntButtons(False)
252                return
253            # do we have integration input?
254            if not self.params['TableMode']:
255                if not self.gpxin[3]:
256                    self.EnableIntButtons(False)
257                    return
258                elif not os.path.exists(self.gpxin[3]):
259                    self.EnableIntButtons(False)
260                    return
261                elif len(self.imgList) == 0:
262                    self.EnableIntButtons(False)
263                    return
264                else:
265                    self.EnableIntButtons(True)
266            else:
267                if self.ImgTblParms:
268                    self.EnableIntButtons(True)
269                else:
270                    self.EnableIntButtons(False)
271                    return
272            # do we have PDF input, if requested
273            if self.params['ComputePDF']:
274                if len(self.pdfList) == 0 or not writingPDF:
275                    self.EnableIntButtons(False)
276                elif 'Error' in self.formula:
277                    self.EnableIntButtons(False)
278               
279        def checkPDFselection():
280            'Read PDF entry from input GPX file & show in GUI'
281            pdfEntry = self.pdfSel.GetStringSelection()
282            if not self.pdfList:
283                self.params['ComputePDF'] = False
284                lbl4b.Enable(False)
285                return
286            else:
287                lbl4b.Enable(True)
288            if pdfEntry not in [i.name for i in self.pdfList]:
289                # strange something -- not selected
290                self.pdfSel.SetSelection(0)
291                pdfEntry = self.pdfSel.GetStringSelection()
292            if self.gpxInp is None or self.gpxInp.filename != self.gpxin[3]:
293                self.gpxInp = G2sc.G2Project(self.gpxin[3])
294            try: 
295                PDFobj = self.gpxInp.pdf(pdfEntry)
296            except KeyError:
297                print("PDF entry not found: {}".format(pdfEntry))
298                return
299            histNames = [i.name for i in self.histList]
300            for i,lbl in enumerate(('Sample Bkg.','Container',
301                                    'Container Bkg.')):
302                self.pbkg[i][4].SetValue(str(PDFobj.data['PDF Controls'][lbl]['Mult']))
303                self.pbkg[i][6] = PDFobj.data['PDF Controls'][lbl]['Mult']
304                try:
305                    i = 1 + histNames.index(PDFobj.data['PDF Controls'][lbl]['Name'])
306                    self.pbkg[i][1].SetSelection(i)
307                except ValueError:
308                    i = 0
309                    self.pbkg[i][1].SetSelection(0)
310                    if PDFobj.data['PDF Controls'][lbl]['Name']:
311                        print('PDF {} hist entry {} not found'.format(
312                            lbl,PDFobj.data['PDF Controls'][lbl]['Name']))
313                        PDFobj.data['PDF Controls'][lbl]['Name'] = ''
314            self.formula = ''
315            for el in PDFobj.data['PDF Controls']['ElList']:
316                i = PDFobj.data['PDF Controls']['ElList'][el]['FormulaNo']
317                if i <= 0:
318                    continue
319                elif i == 1:
320                    if self.formula: self.formula += ' '
321                    self.formula += '{}'.format(el)
322                else:
323                    if self.formula: self.formula += ' '
324                    self.formula += '{}({:.1f})'.format(el,i)
325            if not self.formula:
326                self.formula = 'Error: no chemical formula'
327            lbl5b.SetLabel(self.formula)
328            TestInput()
329                   
330        def ShowbyMode():
331            'create table or non-table integration section of GUI'
332            intPrmSizer.Clear(True)
333            if not self.params['TableMode']:
334                sizer = wx.BoxSizer(wx.HORIZONTAL)
335                self.gpxin[0] = wx.StaticText(mnpnl, wx.ID_ANY,'Project (gpx) file:')
336                sizer.Add(self.gpxin[0])
337                self.gpxin[1] = G2G.ValidatedTxtCtrl(mnpnl,self.gpxin,3,
338                                OKcontrol=TestInput,OnLeave=TestInput)
339                sizer.Add(self.gpxin[1],1,wx.EXPAND,1)
340                self.gpxin[2] = wx.Button(mnpnl, wx.ID_ANY, "Browse")
341                self.gpxin[2].Bind(wx.EVT_BUTTON, GetGPXInputFile)
342                sizer.Add(self.gpxin[2],0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
343                intPrmSizer.Add(sizer,0,wx.EXPAND,0)
344
345                sizer = wx.BoxSizer(wx.HORIZONTAL)
346                self.imprm[0] = wx.StaticText(mnpnl, wx.ID_ANY,'Image parms from:')
347                sizer.Add(self.imprm[0])
348                self.imprm[3] = 0
349                self.imprm[1] = G2G.G2ChoiceButton(mnpnl,[''],self.imprm,3,onChoice=TestInput)
350                sizer.Add(self.imprm[1],1,wx.EXPAND,1)
351                intPrmSizer.Add(sizer,0,wx.EXPAND,0)
352
353                sizer = wx.BoxSizer(wx.HORIZONTAL)
354                self.maskfl[0] = wx.StaticText(mnpnl, wx.ID_ANY,'Mask parms from:')
355                sizer.Add(self.maskfl[0])
356                self.maskfl[3] = 0
357                self.maskfl[1] = G2G.G2ChoiceButton(mnpnl,[''],self.maskfl,3,onChoice=TestInput)
358                sizer.Add(self.maskfl[1],1,wx.EXPAND,1)
359                intPrmSizer.Add(sizer,0,wx.EXPAND,0)
360            else:
361                sizer = wx.BoxSizer(wx.HORIZONTAL)
362                self.table = [None,None,None,None]
363                self.table[0] = wx.Button(mnpnl,  wx.ID_ANY, "Create table")
364                sizer.Add(self.table[0],0,wx.ALIGN_LEFT|wx.ALL,5)
365                self.table[0].Bind(wx.EVT_BUTTON, OnTableButton)
366                self.table[1] = wx.Button(mnpnl,  wx.ID_ANY, "Read table")
367                sizer.Add(self.table[1],0,wx.ALIGN_LEFT|wx.ALL,5)
368                self.table[1].Bind(wx.EVT_BUTTON, OnTableButton)
369                self.table[2] = wx.Button(mnpnl,  wx.ID_ANY, "Edit table")
370                sizer.Add(self.table[2],0,wx.ALIGN_LEFT|wx.ALL,5)
371                self.table[2].Bind(wx.EVT_BUTTON, OnTableButton)
372                #self.table[3] = wx.Button(mnpnl,  wx.ID_ANY, "Save table")
373                #sizer.Add(self.table[3],0,wx.ALIGN_LEFT|wx.ALL,5)
374                #self.table[3].Bind(wx.EVT_BUTTON, OnTableButton)
375                intPrmSizer.Add(sizer,0,wx.EXPAND,0)
376            # enable/disable based on status of files/table
377            TestInput()
378            mnsizer.Fit(self)
379           
380        def OnTableButton(event):
381            '''Called to edit/create the distance-dependent parameter look-up table.
382            '''
383            pth = self.params['readdir']
384            readFileList = []
385            parms,fileList = [], []
386            if event.GetEventObject() == self.table[0]:
387                dlg = wx.FileDialog(self, 'Build new table by selecting image control files', pth,
388                    style=wx.FD_OPEN| wx.FD_MULTIPLE,
389                    wildcard='Image control files (.imctrl)|*.imctrl')
390                dlg.CenterOnParent()
391                if dlg.ShowModal() == wx.ID_OK:
392                    readFileList = dlg.GetPaths()
393                dlg.Destroy()
394                if len(readFileList) <= 0: return
395            elif event.GetEventObject() == self.table[1]:
396                dlg = wx.FileDialog(self, 'Reload table by selecting saved file', pth,
397                    style=wx.FD_OPEN,
398                    wildcard='Integration table (*.imtbl)|*.imtbl')
399                dlg.CenterOnParent()
400                if dlg.ShowModal() == wx.ID_OK:
401                    readFileList = [dlg.GetPath()]
402                dlg.Destroy()
403                if len(readFileList) <= 0: return
404            elif event.GetEventObject() == self.table[2]:
405                parms = copy.deepcopy(self.ImgTblParms)
406                fileList = copy.copy(self.IMfileList)
407                if not parms:
408                    G2G.G2MessageBox(self,'Create or Read table first')
409                    return
410            dlg = None
411            try:
412                dlg = G2imG.IntegParmTable(self,parms,fileList,readFileList)
413                dlg.CenterOnParent()
414                if dlg.ShowModal() == wx.ID_OK:
415                    self.params['InterVals'] = SetupInterpolation(dlg)
416                    self.ImgTblParms = dlg.ReadImageParmTable()
417                    self.IMfileList = dlg.IMfileList
418                    self.params['TableMode'] = True
419                    self.params['ControlsTable'] = {}
420                    self.params['MaskTable'] = {}
421                    for f,m in zip(self.IMfileList,self.ImgTblParms[-1]):
422                        n = os.path.split(f)[1]
423                        if n in self.params['ControlsTable']:
424                            print('Warning overwriting entry {}'.format(n))
425                        self.params['ControlsTable'][n] = G2imG.ReadControls(f)
426                        if m and os.path.exists(m):
427                            self.params['MaskTable'][n] = G2imG.ReadMask(m)
428                        elif m != "(none)":
429                            print("Error: Mask file {} not found".format(m))
430                else:
431                    self.params['TableMode'] = False
432                    self.params['ControlsTable'] = {}
433                    self.params['MaskTable'] = {}
434                    self.imageBase = G2frame.Image
435            finally:
436                if dlg: dlg.Destroy()
437            TestInput()
438           
439        ##################################################
440        # beginning of __init__ processing
441        ##################################################
442        self.G2frame = G2frame
443        self.ImgTblParms = None
444        self.IMfileList = None
445        self.params = {}
446        self.Reset = False
447        self.Pause = False
448        self.PreventReEntryShowMatch = False
449        self.PreventTimerReEntry = False
450        self.params['ControlsTable'] = {}
451        self.params['MaskTable'] = {}
452        G2sc.LoadG2fil()
453        self.fmtlist = G2sc.exportersByExtension.get('powder',{})
454        self.fmtlist['.gpx'] = None
455        self.timer = wx.Timer()
456        self.timer.Bind(wx.EVT_TIMER,self.OnTimerLoop)
457        self.imageBase = G2frame.Image
458        self.params['ComputePDF'] = False
459        self.params['pdfDmax'] = 0.0
460        self.params['pdfprm'] = ''
461        self.params['optPDF'] = True
462        self.params['TableMode'] = False
463        self.params['outsel'] = {}
464        self.formula = 'Error'
465        self.pdfControls = {}
466        self.imgList = []
467        self.histList = []
468        self.pdfList = []
469        self.Scale = [1.0,]
470        self.ProcessedList = [] # files that have been integrated
471        self.currImageList = [] # files that will be integrated
472        self.gpxInp = None
473        self.gpxin = [None,None,None,'']
474        self.imprm = [None,None,None,'']
475        self.maskfl = [None,None,None,'']
476        self.ncores = mp.cpu_count()//2
477        self.MPpool = None
478        self.params['readdir'] = os.getcwd()
479        self.params['filter'] = '*.tif'
480        self.params['outdir'] = os.getcwd()
481        self.PDFformats = ('I(Q)', 'S(Q)', 'F(Q)', 'G(r)', 'PDFgui')
482        self.multipleFmtChoices = {}
483        #GSASIIpath.IPyBreak_base()
484       
485        wx.Frame.__init__(self, None, title='Automatic Integration',
486                          style=wxMainFrameStyle)
487        self.Status = self.CreateStatusBar()
488        self.Status.SetStatusText('Press Start to load and integrate images matching filter')
489        mnpnl = wx.Panel(self)
490        mnsizer = wx.BoxSizer(wx.VERTICAL)
491        # box for integration controls & masks input
492        intSizer = wx.BoxSizer(wx.VERTICAL)
493        sizer = wx.BoxSizer(wx.HORIZONTAL)
494        sizer.Add((-1,-1),1,wx.EXPAND,1)
495        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Calibration/Integration parameters'))
496        sizer.Add((-1,-1),1,wx.EXPAND,1)
497        intSizer.Add(sizer,1,wx.EXPAND,1)
498        def ontblModeBtn(event):
499            if tblModeBtn.GetValue():
500                self.params['TableMode'] = True
501            else:
502                self.params['TableMode'] = False
503            ShowbyMode()
504        tblModeBtn = wx.CheckBox(mnpnl,label='Use distance lookup table')
505        tblModeBtn.SetValue(False)
506        tblModeBtn.Bind(wx.EVT_CHECKBOX, ontblModeBtn)
507        intSizer.Add(tblModeBtn)
508        mnsizer.Add(intSizer,0,wx.EXPAND,0)
509        intPrmSizer = wx.BoxSizer(wx.VERTICAL)
510       
511        mnsizer.Add(intPrmSizer,0,wx.EXPAND,0)
512        # file filter stuff
513        mnsizer.Add((-1,15))
514        sizer = wx.BoxSizer(wx.HORIZONTAL)
515        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Read images from '))
516        self.readDir = G2G.ValidatedTxtCtrl(mnpnl,self.params,'readdir',
517                            OnLeave=self.ShowMatchingFiles,size=(200,-1))
518        sizer.Add(self.readDir,1,wx.EXPAND,1)
519        btn3 = wx.Button(mnpnl, wx.ID_ANY, "Browse")
520        btn3.Bind(wx.EVT_BUTTON, self.SetSourceDir)
521        sizer.Add(btn3,0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
522        mnsizer.Add(sizer,0,wx.EXPAND,0)
523        sizer = wx.BoxSizer(wx.HORIZONTAL)
524        sizer.Add((-1,-1),1,wx.EXPAND,1)
525        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'  Image filter'))
526        flterInp = G2G.ValidatedTxtCtrl(mnpnl,self.params,'filter',
527                                        OnLeave=self.ShowMatchingFiles)
528        sizer.Add(flterInp)
529        mnsizer.Add(sizer,0,wx.EXPAND,0)
530       
531        self.ListBox = wx.ListBox(mnpnl,size=(-1,100))
532        mnsizer.Add(self.ListBox,1,wx.EXPAND,1)
533        self.ShowMatchingFiles(None)
534
535        # box for output selections
536        mnsizer.Add((-1,15))
537        lbl = wx.StaticBox(mnpnl, wx.ID_ANY, "Integration output")
538        lblsizr = wx.StaticBoxSizer(lbl, wx.VERTICAL)
539        sizer = wx.BoxSizer(wx.HORIZONTAL)
540        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Write to: '),0,wx.ALIGN_CENTER_VERTICAL)
541        fInp3 = G2G.ValidatedTxtCtrl(mnpnl,self.params,'outdir',notBlank=False,size=(300,-1))
542        sizer.Add(fInp3,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
543        btn3 = wx.Button(mnpnl,  wx.ID_ANY, "Browse")
544        btn3.Bind(wx.EVT_BUTTON, OnBrowse)
545        sizer.Add(btn3,0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
546        lblsizr.Add(sizer,0,wx.EXPAND)
547        sizer = wx.BoxSizer(wx.HORIZONTAL)
548        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Select format(s):'))
549        for dfmt in sorted(self.fmtlist):
550            sizer.Add((6,2)) # add a bit of extra space
551            fmt = dfmt[1:]
552            if fmt not in self.params['outsel']: self.params['outsel'][fmt] = False
553            btn = G2G.G2CheckBox(mnpnl,dfmt,self.params['outsel'],fmt,
554                                     OnChange=TestInput)
555            sizer.Add(btn)
556        lblsizr.Add(sizer)
557        sizer = wx.BoxSizer(wx.HORIZONTAL)
558        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Separate dir for each format: '))
559        self.params['SeparateDir'] = False
560        sizer.Add(G2G.G2CheckBox(mnpnl,'',self.params,'SeparateDir'))
561        lblsizr.Add(sizer)
562        mnsizer.Add(lblsizr,0,wx.ALIGN_CENTER|wx.EXPAND,1)
563       
564        mnsizer.Add((-1,15))
565        lbl = wx.StaticBox(mnpnl, wx.ID_ANY, "PDF settings")
566        pdfwidgets = []
567        lblsizr = wx.StaticBoxSizer(lbl, wx.VERTICAL)
568        sizer = wx.BoxSizer(wx.HORIZONTAL)
569        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Autocompute PDF:'),0,wx.ALIGN_CENTER_VERTICAL)
570        lbl4b = G2G.G2CheckBox(mnpnl,'',self.params,'ComputePDF',
571                                     OnChange=showPDFctrls)
572        sizer.Add(lbl4b)
573        lbl4a = wx.StaticText(mnpnl, wx.ID_ANY,'Max detector distance: ')
574        sizer.Add(lbl4a,0,wx.ALIGN_CENTER_VERTICAL)
575        fInp4a = G2G.ValidatedTxtCtrl(mnpnl,self.params,'pdfDmax',min=0.0)
576        pdfwidgets.append(fInp4a)
577        sizer.Add(fInp4a,0,wx.ALIGN_CENTER_VERTICAL)
578        cOpt = G2G.G2CheckBox(mnpnl,'Optimize',self.params,'optPDF')
579        pdfwidgets.append(cOpt)
580        sizer.Add(cOpt)
581        lblsizr.Add(sizer,0)
582       
583        sizer = wx.BoxSizer(wx.HORIZONTAL)
584        lbl4 = wx.StaticText(mnpnl, wx.ID_ANY,'PDF control: ')
585        sizer.Add(lbl4,0,wx.ALIGN_CENTER_VERTICAL)
586        self.pdfSel = G2G.G2ChoiceButton(mnpnl,[''],self.params,'pdfprm',
587                                       onChoice=checkPDFselection)
588        sizer.Add(self.pdfSel,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND,1)
589        lblsizr.Add(sizer,0,wx.EXPAND)
590        sizer = wx.BoxSizer(wx.HORIZONTAL)
591        lbl5a = wx.StaticText(mnpnl, wx.ID_ANY,'Chemical formula: ')
592        sizer.Add(lbl5a)
593        lbl5b = wx.StaticText(mnpnl, wx.ID_ANY,'(formula)')
594        sizer.Add(lbl5b)
595        lblsizr.Add(sizer,0,wx.EXPAND)
596
597        sizer = wx.BoxSizer(wx.HORIZONTAL)
598        lbl5 = wx.StaticText(mnpnl, wx.ID_ANY,'Select format(s):')
599        sizer.Add(lbl5)
600        for fmt in self.PDFformats:
601            sizer.Add((6,2)) # add a bit of extra space
602            if fmt not in self.params['outsel']: self.params['outsel'][fmt] = False
603            btn = G2G.G2CheckBox(mnpnl,fmt,self.params['outsel'],fmt,
604                                     OnChange=TestInput)
605            sizer.Add(btn)
606            pdfwidgets.append(btn)
607        lblsizr.Add(sizer,0,wx.EXPAND)
608
609        self.pbkg = 3*[None]
610        for i,lbl in enumerate((' Sample bkg:',' Container:',
611                                   'Container bkg:')):
612            self.pbkg[i] = [None,None,None,'',None,None,-1.0]
613            sizer = wx.BoxSizer(wx.HORIZONTAL)
614            self.pbkg[i][0] = wx.StaticText(mnpnl, wx.ID_ANY,lbl)
615            sizer.Add(self.pbkg[i][0])
616            self.pbkg[i][1] = G2G.G2ChoiceButton(mnpnl,[''],self.pbkg[i],3)
617            sizer.Add(self.pbkg[i][1],1,wx.EXPAND,1)
618            self.pbkg[i][5] = wx.StaticText(mnpnl, wx.ID_ANY,' mult:')
619            sizer.Add(self.pbkg[i][5])
620            self.pbkg[i][4] = G2G.ValidatedTxtCtrl(mnpnl,self.pbkg[i],6,
621                            (6,3),typeHint=float,size=(50,-1),
622                            OnLeave=TestInput,notBlank=False)
623            sizer.Add(self.pbkg[i][4])
624            lblsizr.Add(sizer,0,wx.EXPAND,0)
625       
626        mnsizer.Add(lblsizr,0,wx.ALIGN_CENTER|wx.EXPAND,1)
627
628        # buttons on bottom
629        sizer = wx.BoxSizer(wx.HORIZONTAL)
630        sizer.Add((20,-1))
631        self.btnstart = wx.Button(mnpnl,  wx.ID_ANY, "Start")
632        self.btnstart.Bind(wx.EVT_BUTTON, OnStart)
633        sizer.Add(self.btnstart)
634        self.btnreset = wx.Button(mnpnl,  wx.ID_ANY, "Reset")
635        self.btnreset.Bind(wx.EVT_BUTTON, OnReset)
636        sizer.Add(self.btnreset)
637        sizer.Add((20,-1),wx.EXPAND,1)
638       
639        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,' cores:'))
640        spin = wx.SpinCtrl(mnpnl, wx.ID_ANY,size=(50,-1))
641        spin.SetRange(1, mp.cpu_count())
642#        spin.SetRange(0, mp.cpu_count()) # debug
643        spin.SetValue(self.ncores)
644        def _onSpin(event):
645            if event: event.Skip()
646            self.ncores = spin.GetValue()
647        spin.Bind(wx.EVT_SPINCTRL, _onSpin)
648        spin.Bind(wx.EVT_KILL_FOCUS, _onSpin)
649        sizer.Add(spin)
650
651        sizer.Add((20,-1),wx.EXPAND,1)
652        self.btnclose = wx.Button(mnpnl,  wx.ID_ANY, "Exit")
653        self.btnclose.Bind(wx.EVT_BUTTON, OnQuit)
654        self.EnableIntButtons(False)
655        sizer.Add(self.btnclose)
656        sizer.Add((20,-1))
657        mnsizer.Add(sizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP,5)
658        # finish up window
659        mnpnl.SetSizer(mnsizer)
660        #OnRadioSelect(None) # disable widgets
661        mnsizer.Fit(self)
662        ShowbyMode()
663        if len(sys.argv) > 1:
664            fil = os.path.splitext(sys.argv[1])[0]+'.gpx'
665            if os.path.exists(fil):
666                self.gpxin[3] = fil
667                SetGPXInputFile()
668        showPDFctrls(None)
669   
670    def SetSourceDir(self,event):
671        '''Use a dialog to get a directory for image files
672        '''
673        dlg = wx.DirDialog(self, 'Select directory for image files',
674                        self.params['readdir'],wx.DD_DEFAULT_STYLE)
675        dlg.CenterOnParent()
676        try:
677            if dlg.ShowModal() == wx.ID_OK:
678                self.params['readdir'] = dlg.GetPath()
679            self.readDir.SetValue(self.params['readdir'])
680            self.ShowMatchingFiles(None)
681        finally:
682            dlg.Destroy()
683        return
684       
685    def ShowMatchingFiles(self,value,invalid=False,**kwargs):
686        '''Find and image files matching the image
687        file directory (self.params['readdir']) and the image file filter
688        (self.params['filter']) and add this information to the GUI list box
689        '''
690        if invalid: return
691        if self.PreventReEntryShowMatch: return
692        self.PreventReEntryShowMatch = True
693        filmsg = ""
694        self.currImageList = []
695        if os.path.exists(self.params['readdir']): 
696            imageList = sorted(
697                glob.glob(os.path.join(self.params['readdir'],self.params['filter'])))
698            if not imageList:
699                msg = 'Warning: No files match search string '+os.path.join(
700                        self.params['readdir'],self.params['filter'])
701            else:
702                for fil in imageList:
703                    if fil not in self.ProcessedList:
704                        filmsg += '\n  '+fil
705                        self.currImageList.append(fil)
706                if filmsg:
707                    msg = 'Files to integrate from '+os.path.join(
708                            self.params['readdir'],self.params['filter'])+filmsg
709                else:
710                    msg = 'No files found to process in '+self.params['readdir']
711        else:
712            msg = 'Warning, does not exist: '+self.params['readdir']
713        if self.ProcessedList:
714            msg += '\nIntegrated files:'
715            for fil in self.ProcessedList:
716                msg += '\n  '+fil
717        self.ListBox.Clear()
718        self.ListBox.AppendItems(msg.split('\n'))
719        self.PreventReEntryShowMatch = False
720        return
721       
722    def OnPause(self):
723        '''Respond to Pause, changes text on button/Status line, if needed
724        Stops timer
725        self.Pause should already be True
726        '''
727        if self.timer.IsRunning(): self.timer.Stop()
728        if self.btnstart.GetLabel() == 'Restart':
729            return
730        if self.btnstart.GetLabel() != 'Resume':
731            print('\nPausing autointegration\n')
732            self.btnstart.SetLabel('Resume')
733            self.Status.SetStatusText(
734                    'Press Resume to continue integration or Reset to prepare to reintegrate all images')
735        self.Pause = True
736           
737    def EnableIntButtons(self,flag):
738        for item in (self.btnstart,self.btnreset): item.Enable(flag)
739           
740    def StartLoop(self):
741        '''Prepare to start autointegration timer loop.
742        Save current Image params for use in future integrations
743        also label the window so users understand what is being used
744        '''
745        print('\nStarting new autointegration\n')
746        # make sure all output directories exist
747        if self.params['SeparateDir']:
748            for dfmt in self.fmtlist:
749                if not self.params['outsel'][dfmt[1:]]: continue
750                dir = os.path.join(self.params['outdir'],dfmt[1:])
751                if not os.path.exists(dir): os.makedirs(dir)
752        else:
753            if not os.path.exists(self.params['outdir']):
754                os.makedirs(self.params['outdir'])
755        if self.Reset: # special things to do after Reset has been pressed
756            self.G2frame.IntegratedList = []
757            wx.Yield()
758            self.Reset = False
759        if self.params['ComputePDF'] and self.params['SeparateDir']:
760            for fmt in self.PDFformats:
761                if not self.params['outsel'][fmt]: continue
762                dir = os.path.join(self.params['outdir'],
763                                   fmt.replace("(","_").replace(")",""))
764                if not os.path.exists(dir): os.makedirs(dir)
765        return False
766               
767    def ArgGen(self,PDFobj,imgprms,mskprms,xydata):
768        '''generator for arguments for integration/PDF calc
769        '''
770        for newImage in self.currImageList:
771            self.Pause |= self.G2frame.PauseIntegration
772            if self.Pause:
773                self.OnPause()
774                self.PreventTimerReEntry = False
775                self.Raise()
776                return
777            TableMode = self.params['TableMode']
778            ComputePDF = self.params['ComputePDF']
779            SeparateDir = self.params['SeparateDir']
780            optPDF = self.params['optPDF']
781            outdir = self.params['outdir']
782            calcModes = (TableMode,ComputePDF,SeparateDir,optPDF)
783            InterpVals = self.params.get('InterVals')
784            outputSelect = self.params['outsel']
785            PDFformats = self.PDFformats
786            outputModes = (outputSelect,PDFformats,self.fmtlist,outdir,
787                               self.multipleFmtChoices)
788            if PDFobj:
789                PDFdict = PDFobj.data
790            else:
791                PDFdict = None
792            yield (newImage,imgprms,mskprms,xydata,PDFdict,InterpVals,calcModes,outputModes)
793    def OnTimerLoop(self,event):
794        '''A method that is called every :meth:`PollTime` seconds that is
795        used to check for new files and process them. Integrates new images.
796        Also optionally sets up and computes PDF.
797        This is called only after the "Start" button is pressed (then its label reads "Pause").
798        '''
799           
800        if GSASIIpath.GetConfigValue('debug'):
801            import datetime
802            print ("DBG_Timer tick at {:%d %b %Y %H:%M:%S}\n".format(datetime.datetime.now()))
803        if self.PreventTimerReEntry: return
804        self.PreventTimerReEntry = True
805        self.ShowMatchingFiles(None)
806        if not self.currImageList:
807            self.PreventTimerReEntry = False
808            return
809        updateList = False
810
811        # get input for integration
812        imgprms = mskprms = None
813        if not self.params['TableMode']:
814            # read in image controls/masks, used below in loop. In Table mode
815            # we will get this image-by image.
816            gpxinp = G2sc.G2Project(self.gpxin[3])
817            print('reading template project',gpxinp.filename)
818            img = gpxinp.image(self.imprm[1].GetStringSelection())
819            imgprms = img.getControls(True)
820            if self.maskfl[1].GetStringSelection().strip():
821                img = gpxinp.image(self.maskfl[1].GetStringSelection())
822                mskprms = img.getMasks()
823        # setup shared input for PDF computation (for now will not be table mode)
824        xydata = {}
825        if self.params['ComputePDF']:
826            pdfEntry = self.pdfSel.GetStringSelection()
827            try: 
828                PDFobj = gpxinp.pdf(pdfEntry)
829            except KeyError:
830                print("PDF entry not found: {}".format(pdfEntry))
831            # update with GUI input
832            for i,lbl in enumerate(('Sample Bkg.','Container',
833                                    'Container Bkg.')):
834                name = self.pbkg[i][1].GetStringSelection()
835                try:
836                    xydata[lbl] = gpxinp.histogram(name).data['data']
837                except AttributeError:
838                    pass
839                PDFobj.data['PDF Controls'][lbl]['Mult'] = self.pbkg[i][6]
840                PDFobj.data['PDF Controls'][lbl]['Name'] = name
841        else:
842            PDFobj = None
843        if self.MPpool:
844            self.MPpool.imap_unordered(ProcessImageMP,
845                            self.ArgGen(PDFobj,imgprms,mskprms,xydata))
846        else:
847            for intArgs in self.ArgGen(PDFobj,imgprms,mskprms,xydata):
848                newImage = intArgs[0]
849                print('processing ',newImage)
850                ProcessImage(*intArgs)
851                updateList = True
852        for newImage in self.currImageList:
853            self.ProcessedList.append(newImage)
854        if updateList: self.ShowMatchingFiles(None)
855        self.PreventTimerReEntry = False
856        self.Raise()
857       
858def ProcessImageMP(onearg):
859    newImage = onearg[0]
860    print('processing ',newImage)
861    t0 = time.time() # debug
862    ProcessImage(*onearg)
863    print('ProcessImageMP time',time.time()-t0)
864
865MapCache = {'maskMap':{}, 'ThetaAzimMap':{}, 'distanceList':[]}
866'caches for TA and Mask maps'
867
868def ProcessImage(newImage,imgprms,mskprms,xydata,PDFdict,InterpVals,calcModes,outputModes):
869    '''Process one image that is read from file newImage and is integrated into
870    one or more diffraction patterns and optionally each diffraction pattern can
871    be transformed into a pair distribution function.
872
873    :param str newImage: file name (full path) for input image
874    :param dict imgprms: dict with some nested lists & dicts describing the image
875      settings and integration parameters
876    :param dict mskprms: dict with areas of image to be masked
877    :param dict xydata: contains histogram information with about background
878      contributions, used for PDF computation (used if ComputePDF is True)
879    :param PDFdict: contains PDF parameters (used if ComputePDF is True)
880    :param InterpVals: contains interpolation table (used if TableMode is True)
881    :param tuple calcModes: set of values for which computations are
882      performed and how
883    :param tuple outputModes: determines which files are written and where
884    '''
885    (TableMode,ComputePDF,SeparateDir,optPDF) = calcModes
886    (outputSelect,PDFformats,fmtlist,outdir,multipleFmtChoices) = outputModes
887    if SeparateDir:
888        savedir = os.path.join(outdir,'gpx')
889        if not os.path.exists(savedir): os.makedirs(savedir)
890    else:
891        savedir = outdir
892    outgpx = os.path.join(savedir,os.path.split(os.path.splitext(newImage)[0]+'.gpx')[1])
893    gpxout = G2sc.G2Project(filename=outgpx)
894    print('creating',gpxout.filename)
895    # looped because a file can contain multiple images
896    if TableMode: # look up parameter values from table
897        imgprms,mskprms = LookupFromTable(im.data['Image Controls'].get('setdist'),
898                                                  InterpVals)   
899    for im in gpxout.add_image(newImage):
900        # apply image parameters
901        im.setControls(imgprms)
902        setdist = '{:.2f}'.format(im.getControls()['setdist']) # ignore differences in position less than 0.01 mm
903        if setdist not in MapCache['distanceList']:
904            if mskprms:
905                im.setMasks(mskprms)
906            else:
907                im.initMasks()
908            MapCache['distanceList'].append(setdist)
909            MapCache['maskMap'][setdist] = G2sc.calcMaskMap(im.getControls(),
910                                                            im.getMasks())
911            MapCache['ThetaAzimMap'][setdist] = G2sc.calcThetaAzimMap(im.getControls())
912#        else: # debug
913#            print('*** reusing',setdist)
914        #if mskprms:
915        #    im.setMasks(mskprms)
916        #else:
917        #    im.initMasks()
918        hists = im.Integrate(MaskMap=MapCache['maskMap'][setdist],
919                             ThetaAzimMap=MapCache['ThetaAzimMap'][setdist])
920        # write requested files
921        for dfmt in fmtlist:
922            fmt = dfmt[1:]
923            if not outputSelect[fmt]: continue
924            if fmtlist[dfmt] is None: continue
925            hint = ''
926            if fmt in multipleFmtChoices:
927                hint = multipleFmtChoices[fmt]
928            if SeparateDir:
929                savedir = os.path.join(outdir,fmt)
930            else:
931                savedir = outdir
932            if not os.path.exists(savedir): os.makedirs(savedir)
933            # loop over created histgrams (multiple if caked), writing them as requested
934            for i,h in enumerate(hists):
935                fname = h.name[5:].replace(' ','_')
936                #if len(hists) > 1:
937                #GSASIIpath.IPyBreak_base()
938                try:
939                    fil = os.path.join(savedir,fname)
940                    print('Wrote',h.Export(fil,dfmt,hint))
941                except Exception as msg:
942                    print('Failed to write {} as {}. Error msg\n{}'
943                              .format(fname,dfmt,msg))
944        if ComputePDF:  # compute PDF
945            for h in hists:
946                pdf = gpxout.copy_PDF(PDFdict,h)
947                pdf.data['PDF Controls']['Sample']['Name'] = h.name
948                xydata['Sample'] = h.data['data']
949                fname = h.name[5:].replace(' ','_')
950                limits = h.data['Limits'][1]
951                inst = h.data['Instrument Parameters'][0]
952                pdf.calculate(copy.deepcopy(xydata),limits,inst)
953                if optPDF:
954                    for i in range(5):
955                        if pdf.optimize(True,5,copy.deepcopy(xydata),limits,inst):
956                            break
957                    pdf.calculate(copy.deepcopy(xydata),limits,inst)
958                for fmt in PDFformats:
959                    if not outputSelect[fmt]: continue
960                    if SeparateDir:
961                        savedir = os.path.join(outdir,fmt.replace("(","_").replace(")",""))
962                    else:
963                        savedir = outdir
964                    pdf.export(os.path.join(savedir,fname),fmt)
965    if outputSelect.get('gpx'):
966        gpxout.save()
967    else:
968        del gpxout
969# Autointegration end
970
971def SetupInterpolation(dlg):
972    '''Creates an object for interpolating image parameters at a given distance value
973    '''
974    parms = dlg.ReadImageParmTable()
975    IMfileList = dlg.IMfileList
976    cols = dlg.list.GetColumnCount()
977    ParmList = dlg.ParmList
978    nonInterpVars = dlg.nonInterpVars
979    ControlsTable = {}
980    MaskTable = {}
981    for f,m in zip(IMfileList,parms[-1]):
982        n = os.path.split(f)[1]
983        if n in ControlsTable:
984            print('Warning overwriting entry {}'.format(n))
985        ControlsTable[n] = G2imG.ReadControls(f)
986        if m and os.path.exists(m):
987            MaskTable[n] = G2imG.ReadMask(m)
988        elif m != "(none)":
989            print("Error: Mask file {} not found".format(m))
990    return copy.deepcopy([cols, parms, IMfileList, ParmList, nonInterpVars,ControlsTable,MaskTable])
991
992def LookupFromTable(dist,parmList):
993    '''Interpolate image parameters for a supplied distance value
994
995    :param float dist: distance to use for interpolation
996    :returns: a list with 2 items:
997      * a dict with interpolated parameter values,
998      * the closest imctrl
999    '''
1000    cols, parms, IMfileList, ParmList, nonInterpVars,ControlsTable,MaskTable = parmList
1001    x = np.array([float(i) for i in parms[0]])
1002    closest = abs(x-dist).argmin()
1003    D = {'setdist':dist}
1004    imctfile = IMfileList[closest]
1005    for c in range(1,cols-1):
1006        lbl = ParmList[c]
1007        if lbl in nonInterpVars:
1008            if lbl in ['outChannels',]:
1009                D[lbl] = int(float(parms[c][closest]))
1010            else:
1011                D[lbl] = float(parms[c][closest])
1012        else:
1013            y = np.array([float(i) for i in parms[c]])
1014            D[lbl] = np.interp(dist,x,y)
1015    # full integration when angular range is 0
1016    D['fullIntegrate'] = (D['LRazimuth_min'] == D['LRazimuth_max'])
1017    # conversion for paired values
1018    for a,b in ('center_x','center_y'),('LRazimuth_min','LRazimuth_max'),('IOtth_min','IOtth_max'):
1019        r = a.split('_')[0]
1020        D[r] = [D[a],D[b]]
1021        if r in ['LRazimuth',]:
1022            D[r] = [int(D[a]),int(D[b])]
1023        del D[a]
1024        del D[b]
1025    interpDict,imgctrl = D,imctfile
1026    if GSASIIpath.GetConfigValue('debug'):
1027        print ('DBG_interpolated values: ',interpDict)
1028    f = os.path.split(imgctrl)[1]
1029    ImageControls = ControlsTable[f]
1030    ImageControls.update(interpDict)
1031    ImageControls['showLines'] = True
1032    ImageControls['ring'] = []
1033    ImageControls['rings'] = []
1034    ImageControls['ellipses'] = []
1035    ImageControls['setDefault'] = False
1036    for i in 'range','size','GonioAngles':
1037        if i in ImageControls: del ImageControls[i]
1038    ImageMasks = MaskTable.get(f)
1039    return ImageControls,ImageMasks
1040   
1041###########################################################################
1042if __name__ == "__main__":
1043    GSASIIpath.InvokeDebugOpts()
1044    App = wx.App()
1045    class dummyClass(object):
1046        '''An empty class where a few values needed from parent are placed
1047        '''
1048        def __init__(self): 
1049            self.Image = None
1050            self.PauseIntegration = False
1051            self.TutorialImportDir = None
1052            self.GSASprojectfile = ''
1053            self.LastExportDir = ''
1054            self.LastGPXdir = ''
1055           
1056    G2frame = dummyClass()
1057    frm = AutoIntFrame(G2frame,5)
1058    App.GetTopWindow().Show(True)
1059    App.MainLoop()
Note: See TracBrowser for help on using the repository browser.