source: trunk/GSASIIIO.py @ 3228

Last change on this file since 3228 was 3228, checked in by toby, 5 years ago

fix rename of PWDR entries

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 113.9 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2018-01-16 23:11:37 +0000 (Tue, 16 Jan 2018) $
4# $Author: toby $
5# $Revision: 3228 $
6# $URL: trunk/GSASIIIO.py $
7# $Id: GSASIIIO.py 3228 2018-01-16 23:11:37Z toby $
8########### SVN repository information ###################
9'''
10*GSASIIIO: Misc I/O routines*
11=============================
12
13Module with miscellaneous routines for input and output. Many
14are GUI routines to interact with user.
15
16Includes support for image reading.
17
18Also includes base classes for data import routines.
19
20This module needs some work to separate wx from non-wx routines
21'''
22# If this is being used for GSASIIscriptable, don't depend on wx
23from __future__ import division, print_function
24try:
25    import wx
26except ImportError:
27    class Placeholder(object):
28        def __init__(self):
29            self.Dialog = object
30    wx = Placeholder()
31import math
32import numpy as np
33import copy
34import platform
35if '2' in platform.python_version_tuple()[0]:
36    import cPickle
37else:
38    import _pickle as cPickle
39import sys
40import re
41import glob
42import random as ran
43import GSASIIpath
44GSASIIpath.SetVersionNumber("$Revision: 3228 $")
45try:
46    import GSASIIdataGUI as G2gd
47except ImportError:
48    pass
49import GSASIIobj as G2obj
50import GSASIIlattice as G2lat
51import GSASIImath as G2mth
52try:
53    import GSASIIpwdGUI as G2pdG
54    import GSASIIimgGUI as G2imG
55except ImportError:
56    pass
57import GSASIIimage as G2img
58import GSASIIElem as G2el
59import GSASIIstrIO as G2stIO
60import GSASIImapvars as G2mv
61try:
62    import GSASIIctrlGUI as G2G
63except ImportError:
64    pass
65import os
66import os.path as ospath
67
68DEBUG = False       #=True for various prints
69TRANSP = False      #=true to transpose images for testing
70if GSASIIpath.GetConfigValue('Transpose'): TRANSP = True
71npsind = lambda x: np.sin(x*np.pi/180.)
72
73def sfloat(S):
74    'Convert a string to float. An empty field or a unconvertable value is treated as zero'
75    if S.strip():
76        try:
77            return float(S)
78        except ValueError:
79            pass
80    return 0.0
81
82def sint(S):
83    'Convert a string to int. An empty field is treated as zero'
84    if S.strip():
85        return int(S)
86    else:
87        return 0
88
89def trim(val):
90    '''Simplify a string containing leading and trailing spaces
91    as well as newlines, tabs, repeated spaces etc. into a shorter and
92    more simple string, by replacing all ranges of whitespace
93    characters with a single space.
94
95    :param str val: the string to be simplified
96
97    :returns: the (usually) shortened version of the string
98    '''
99    return re.sub('\s+', ' ', val).strip()
100
101def FileDlgFixExt(dlg,file):
102    'this is needed to fix a problem in linux wx.FileDialog'
103    ext = dlg.GetWildcard().split('|')[2*dlg.GetFilterIndex()+1].strip('*')
104    if ext not in file:
105        file += ext
106    return file
107       
108def GetPowderPeaks(fileName):
109    'Read powder peaks from a file'
110    sind = lambda x: math.sin(x*math.pi/180.)
111    asind = lambda x: 180.*math.asin(x)/math.pi
112    wave = 1.54052
113    File = open(fileName,'Ur')
114    Comments = []
115    peaks = []
116    S = File.readline()
117    while S:
118        if S[:1] == '#':
119            Comments.append(S[:-1])
120        else:
121            item = S.split()
122            if len(item) == 1:
123                peaks.append([float(item[0]),1.0])
124            elif len(item) > 1:
125                peaks.append([float(item[0]),float(item[0])])
126        S = File.readline()
127    File.close()
128    if Comments:
129       print ('Comments on file:')
130       for Comment in Comments: 
131            print (Comment)
132            if 'wavelength' in Comment:
133                wave = float(Comment.split('=')[1])
134    Peaks = []
135    if peaks[0][0] > peaks[-1][0]:          # d-spacings - assume CuKa
136        for peak in peaks:
137            dsp = peak[0]
138            sth = wave/(2.0*dsp)
139            if sth < 1.0:
140                tth = 2.0*asind(sth)
141            else:
142                break
143            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
144    else:                                   #2-thetas - assume Cuka (for now)
145        for peak in peaks:
146            tth = peak[0]
147            dsp = wave/(2.0*sind(tth/2.0))
148            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
149    limits = [1000.,0.]
150    for peak in Peaks:
151        limits[0] = min(limits[0],peak[0])
152        limits[1] = max(limits[1],peak[0])
153    limits[0] = max(1.,(int(limits[0]-1.)/5)*5.)
154    limits[1] = min(170.,(int(limits[1]+1.)/5)*5.)
155    return Comments,Peaks,limits,wave
156
157def GetCheckImageFile(G2frame,treeId):
158    '''Try to locate an image file if the project and image have been moved
159    together. If the image file cannot be found, request the location from
160    the user.
161
162    :param wx.Frame G2frame: main GSAS-II Frame and data object
163    :param wx.Id treeId: Id for the main tree item for the image
164    :returns: Npix,imagefile,imagetag with (Npix) number of pixels,
165       imagefile, if it exists, or the name of a file that does exist or False if the user presses Cancel
166       and (imagetag) an optional image number
167
168    '''
169    Npix,Imagefile,imagetag = G2frame.GPXtree.GetImageLoc(treeId)
170    if isinstance(Imagefile,list):
171        imagefile,imagetag = Imagefile
172    else:
173        imagefile = Imagefile
174    if not os.path.exists(imagefile):
175        print ('Image file '+imagefile+' not found')
176        fil = imagefile.replace('\\','/') # windows?!
177        # see if we can find a file with same name or in a similarly named sub-dir
178        pth,fil = os.path.split(fil)
179        prevpth = None
180        while pth and pth != prevpth:
181            prevpth = pth
182            if os.path.exists(os.path.join(G2frame.dirname,fil)):
183                print ('found image file '+os.path.join(G2frame.dirname,fil))
184                imagefile = os.path.join(G2frame.dirname,fil)
185                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
186                return Npix,imagefile,imagetag
187            pth,enddir = os.path.split(pth)
188            fil = os.path.join(enddir,fil)
189        # not found as a subdirectory, drop common parts of path for last saved & image file names
190        #    if image was .../A/B/C/imgs/ima.ge
191        #      & GPX was  .../A/B/C/refs/fil.gpx but is now .../NEW/TEST/TEST1
192        #    will look for .../NEW/TEST/TEST1/imgs/ima.ge, .../NEW/TEST/imgs/ima.ge, .../NEW/imgs/ima.ge and so on
193        Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
194        gpxPath = Controls.get('LastSavedAs','').replace('\\','/').split('/') # blank in older .GPX files
195        imgPath = imagefile.replace('\\','/').split('/')
196        for p1,p2 in zip(gpxPath,imgPath):
197            if p1 == p2:
198                gpxPath.pop(0),imgPath.pop(0)
199            else:
200                break
201        fil = os.path.join(*imgPath) # file with non-common prefix elements
202        prevpth = None
203        pth = os.path.abspath(G2frame.dirname)
204        while pth and pth != prevpth:
205            prevpth = pth
206            if os.path.exists(os.path.join(pth,fil)):
207                print ('found image file '+os.path.join(pth,fil))
208                imagefile = os.path.join(pth,fil)
209                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
210                return Npix,imagefile,imagetag
211            pth,enddir = os.path.split(pth)
212        #GSASIIpath.IPyBreak()
213
214    if not os.path.exists(imagefile):
215        # note that this fails (at least on Mac) to get an image during the GUI initialization
216        prevnam = os.path.split(imagefile)[1]
217        prevext = os.path.splitext(imagefile)[1]
218        wildcard = 'Image format (*'+prevext+')|*'+prevext
219        dlg = wx.FileDialog(G2frame, 'Previous image file ('+prevnam+') not found; open here', '.', prevnam,
220                            wildcard,wx.FD_OPEN)
221        try:
222            dlg.SetFilename(''+ospath.split(imagefile)[1])
223            if dlg.ShowModal() == wx.ID_OK:
224                imagefile = dlg.GetPath()
225                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
226            else:
227                imagefile = False
228        finally:
229            dlg.Destroy()
230    return Npix,imagefile,imagetag
231
232def EditImageParms(parent,Data,Comments,Image,filename):
233    dlg = wx.Dialog(parent, wx.ID_ANY, 'Edit image parameters',
234                    style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
235    def onClose(event):
236        dlg.EndModal(wx.ID_OK)
237    mainsizer = wx.BoxSizer(wx.VERTICAL)
238    h,w = Image.shape[:2]
239    mainsizer.Add(wx.StaticText(dlg,wx.ID_ANY,'File '+str(filename)+'\nImage size: '+str(h)+' x '+str(w)),
240        0,wx.ALIGN_LEFT|wx.ALL, 2)
241   
242    vsizer = wx.BoxSizer(wx.HORIZONTAL)
243    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Wavelength (\xC5) '),
244        0,wx.ALIGN_LEFT|wx.ALL, 2)
245    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'wavelength')
246    vsizer.Add(wdgt)
247    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
248
249    vsizer = wx.BoxSizer(wx.HORIZONTAL)
250    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Pixel size (\xb5m). Width '),
251        0,wx.ALIGN_LEFT|wx.ALL, 2)
252    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],0,
253                                 size=(50,-1))
254    vsizer.Add(wdgt)
255    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Height '),wx.ALIGN_LEFT|wx.ALL, 2)
256    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],1,size=(50,-1))
257    vsizer.Add(wdgt)
258    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
259
260    vsizer = wx.BoxSizer(wx.HORIZONTAL)
261    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Sample to detector (mm) '),
262        0,wx.ALIGN_LEFT|wx.ALL, 2)
263    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'distance')
264    vsizer.Add(wdgt)
265    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
266
267    vsizer = wx.BoxSizer(wx.HORIZONTAL)
268    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Beam center (pixels). X = '),
269        0,wx.ALIGN_LEFT|wx.ALL, 2)
270    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],0,size=(75,-1))
271    vsizer.Add(wdgt)
272    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Y = '),wx.ALIGN_LEFT|wx.ALL, 2)
273    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],1,size=(75,-1))
274    vsizer.Add(wdgt)
275    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
276
277    vsizer = wx.BoxSizer(wx.HORIZONTAL)
278    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Comments '),
279        0,wx.ALIGN_LEFT|wx.ALL, 2)
280    wdgt = G2G.ValidatedTxtCtrl(dlg,Comments,0,size=(250,-1))
281    vsizer.Add(wdgt)
282    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
283
284    btnsizer = wx.StdDialogButtonSizer()
285    OKbtn = wx.Button(dlg, wx.ID_OK, 'Continue')
286    OKbtn.SetDefault()
287    OKbtn.Bind(wx.EVT_BUTTON,onClose)
288    btnsizer.AddButton(OKbtn) # not sure why this is needed
289    btnsizer.Realize()
290    mainsizer.Add(btnsizer, 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 5)
291    dlg.SetSizer(mainsizer)
292    dlg.CenterOnParent()
293    dlg.ShowModal()
294   
295def LoadImage2Tree(imagefile,G2frame,Comments,Data,Npix,Image):
296    '''Load an image into the tree. Saves the location of the image, as well as the
297    ImageTag (where there is more than one image in the file), if defined.
298    '''
299    ImgNames = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
300    TreeLbl = 'IMG '+os.path.basename(imagefile)
301    ImageTag = Data.get('ImageTag')
302    if ImageTag:
303        TreeLbl += ' #'+'%04d'%(ImageTag)
304        imageInfo = (imagefile,ImageTag)
305    else:
306        imageInfo = imagefile
307    TreeName = G2obj.MakeUniqueLabel(TreeLbl,ImgNames)
308    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=TreeName)
309    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)
310    Imax = np.amax(Image)
311    if G2frame.imageDefault:
312        Data = copy.copy(G2frame.imageDefault)
313        Data['showLines'] = True
314        Data['ring'] = []
315        Data['rings'] = []
316        Data['cutoff'] = 10
317        Data['pixLimit'] = 20
318        Data['edgemin'] = 100000000
319        Data['calibdmin'] = 0.5
320        Data['calibskip'] = 0
321        Data['ellipses'] = []
322        Data['calibrant'] = ''
323        Data['GonioAngles'] = [0.,0.,0.]
324        Data['DetDepthRef'] = False
325    else:
326        Data['type'] = 'PWDR'
327        Data['color'] = GSASIIpath.GetConfigValue('Contour_color','Paired')
328        Data['tilt'] = 0.0
329        Data['rotation'] = 0.0
330        Data['showLines'] = False
331        Data['ring'] = []
332        Data['rings'] = []
333        Data['cutoff'] = 10
334        Data['pixLimit'] = 20
335        Data['calibdmin'] = 0.5
336        Data['calibskip'] = 0
337        Data['edgemin'] = 100000000
338        Data['ellipses'] = []
339        Data['GonioAngles'] = [0.,0.,0.]
340        Data['DetDepth'] = 0.
341        Data['DetDepthRef'] = False
342        Data['calibrant'] = ''
343        Data['IOtth'] = [2.0,5.0]
344        Data['LRazimuth'] = [0.,180.]
345        Data['azmthOff'] = 0.0
346        Data['outChannels'] = 2500
347        Data['outAzimuths'] = 1
348        Data['centerAzm'] = False
349        Data['fullIntegrate'] = False
350        Data['setRings'] = False
351        Data['background image'] = ['',-1.0]                           
352        Data['dark image'] = ['',-1.0]
353        Data['Flat Bkg'] = 0.0
354    Data['setDefault'] = False
355    Data['range'] = [(0,Imax),[0,Imax]]
356    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Image Controls'),Data)
357    Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],'Thresholds':[(0,Imax),[0,Imax]]}
358    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Masks'),Masks)
359    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Stress/Strain'),
360        {'Type':'True','d-zero':[],'Sample phi':0.0,'Sample z':0.0,'Sample load':0.0})
361    G2frame.GPXtree.SetItemPyData(Id,[Npix,imageInfo])
362    G2frame.PickId = Id
363    G2frame.PickIdText = G2frame.GetTreeItemsList(G2frame.PickId)
364    G2frame.Image = Id
365
366def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''):
367    '''Read a single image with an image importer. This is called to reread an image
368    after it has already been imported with :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
369    (or :func:`ReadImages` in Auto Integration) so it is not necessary to reload metadata.
370
371    :param wx.Frame G2frame: main GSAS-II Frame and data object.
372    :param str imagefile: name of image file
373    :param bool imageOnly: If True return only the image,
374      otherwise  (default) return more (see below)
375    :param int/str ImageTag: specifies a particular image to be read from a file.
376      First image is read if None (default).
377    :param str formatName: the image reader formatName
378
379    :returns: an image as a numpy array or a list of four items:
380      Comments, Data, Npix and the Image, as selected by imageOnly
381
382    '''
383    # determine which formats are compatible with this file
384    primaryReaders = []
385    secondaryReaders = []
386    for rd in G2frame.ImportImageReaderlist:
387        flag = rd.ExtensionValidator(imagefile)
388        if flag is None: 
389            secondaryReaders.append(rd)
390        elif flag:
391            if not FormatName:
392                primaryReaders.append(rd)
393            elif FormatName == rd.formatName:
394                primaryReaders.append(rd)
395    if len(secondaryReaders) + len(primaryReaders) == 0:
396        print('Error: No matching format for file '+imagefile)
397        raise Exception('No image read')
398    errorReport = ''
399    if not imagefile:
400        return
401    for rd in primaryReaders+secondaryReaders:
402        rd.ReInitialize() # purge anything from a previous read
403        rd.errors = "" # clear out any old errors
404        if not rd.ContentsValidator(imagefile): # rejected on cursory check
405            errorReport += "\n  "+rd.formatName + ' validator error'
406            if rd.errors: 
407                errorReport += ': '+rd.errors
408                continue
409        if imageOnly:
410            ParentFrame = None # prevent GUI access on reread
411        else:
412            ParentFrame = G2frame
413        if GSASIIpath.GetConfigValue('debug'):
414            flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
415        else:
416            flag = False
417            try:
418                flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
419            except rd.ImportException as detail:
420                rd.errors += "\n  Read exception: "+str(detail)
421            except Exception as detail:
422                import traceback
423                rd.errors += "\n  Unhandled read exception: "+str(detail)
424                rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
425        if flag: # this read succeeded
426            if rd.Image is None:
427                raise Exception('No image read. Strange!')
428            if GSASIIpath.GetConfigValue('Transpose'):
429                print ('Transposing Image!')
430                rd.Image = rd.Image.T
431            #rd.readfilename = imagefile
432            if imageOnly:
433                return rd.Image
434            else:
435                return rd.Comments,rd.Data,rd.Npix,rd.Image
436    else:
437        print('Error reading file '+imagefile)
438        print('Error messages(s)\n'+errorReport)
439        raise Exception('No image read')   
440
441def ReadImages(G2frame,imagefile):
442    '''Read one or more images from a file and put them into the Tree
443    using image importers. Called only in :meth:`AutoIntFrame.OnTimerLoop`.
444
445    ToDo: Images are most commonly read in :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
446    which is called from :meth:`GSASIIdataGUI.GSASII.OnImportImage`
447    it would be good if these routines used a common code core so that changes need to
448    be made in only one place.
449
450    :param wx.Frame G2frame: main GSAS-II Frame and data object.
451    :param str imagefile: name of image file
452
453    :returns: a list of the id's of the IMG tree items created
454    '''
455    # determine which formats are compatible with this file
456    primaryReaders = []
457    secondaryReaders = []
458    for rd in G2frame.ImportImageReaderlist:
459        flag = rd.ExtensionValidator(imagefile)
460        if flag is None:
461            secondaryReaders.append(rd)
462        elif flag:
463            primaryReaders.append(rd)
464    if len(secondaryReaders) + len(primaryReaders) == 0:
465        print('Error: No matching format for file '+imagefile)
466        raise Exception('No image read')
467    errorReport = ''
468    rdbuffer = {} # create temporary storage for file reader
469    for rd in primaryReaders+secondaryReaders:
470        rd.ReInitialize() # purge anything from a previous read
471        rd.errors = "" # clear out any old errors
472        if not rd.ContentsValidator(imagefile): # rejected on cursory check
473            errorReport += "\n  "+rd.formatName + ' validator error'
474            if rd.errors: 
475                errorReport += ': '+rd.errors
476                continue
477        ParentFrame = G2frame
478        block = 0
479        repeat = True
480        CreatedIMGitems = []
481        while repeat: # loop if the reader asks for another pass on the file
482            block += 1
483            repeat = False
484            if GSASIIpath.GetConfigValue('debug'):
485                flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
486            else:
487                flag = False
488                try:
489                    flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
490                except rd.ImportException as detail:
491                    rd.errors += "\n  Read exception: "+str(detail)
492                except Exception as detail:
493                    import traceback
494                    rd.errors += "\n  Unhandled read exception: "+str(detail)
495                    rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
496            if flag: # this read succeeded
497                if rd.Image is None:
498                    raise Exception('No image read. Strange!')
499                if GSASIIpath.GetConfigValue('Transpose'):
500                    print ('Transposing Image!')
501                    rd.Image = rd.Image.T
502                rd.Data['ImageTag'] = rd.repeatcount
503                rd.readfilename = imagefile
504                # Load generic metadata, as configured
505                GetColumnMetadata(rd)
506                LoadImage2Tree(imagefile,G2frame,rd.Comments,rd.Data,rd.Npix,rd.Image)
507                repeat = rd.repeat
508            CreatedIMGitems.append(G2frame.Image)
509        if CreatedIMGitems: return CreatedIMGitems
510    else:
511        print('Error reading file '+imagefile)
512        print('Error messages(s)\n'+errorReport)
513        return []
514        #raise Exception('No image read')   
515
516def SaveMultipleImg(G2frame):
517    if not G2frame.GPXtree.GetCount():
518        print ('no images!')
519        return
520    choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
521    if len(choices) == 1:
522        names = choices
523    else:
524        dlg = G2G.G2MultiChoiceDialog(G2frame,'Stress/Strain fitting','Select images to fit:',choices)
525        dlg.SetSelections([])
526        names = []
527        if dlg.ShowModal() == wx.ID_OK:
528            names = [choices[sel] for sel in dlg.GetSelections()]
529        dlg.Destroy()
530    if not names: return
531    for name in names:
532        Id = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name)
533        Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(Id)
534        imroot = os.path.splitext(imagefile)[0]
535        if imagetag:
536            imroot += '_' + str(imagetag)
537        Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Image Controls'))
538        print('Writing '+imroot+'.imctrl')
539        File = open(imroot+'.imctrl','w')
540        keys = ['type','wavelength','calibrant','distance','center',
541                    'tilt','rotation','azmthOff','fullIntegrate','LRazimuth',
542                    'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
543                    'calibskip','pixLimit','cutoff','calibdmin','chisq','Flat Bkg',
544                    'binType','SampleShape','PolaVal','SampleAbs','dark image','background image']
545        for key in keys:
546            if key not in Data: continue    #uncalibrated!
547            File.write(key+':'+str(Data[key])+'\n')
548        File.close()
549        mask = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'))
550        G2imG.CleanupMasks(mask)
551        print('Writing '+imroot+'.immask')
552        File = open(imroot+'.immask','w')
553        for key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
554            File.write(key+':'+str(mask[key])+'\n')
555        File.close()
556       
557def PutG2Image(filename,Comments,Data,Npix,image):
558    'Write an image as a python pickle - might be better as an .edf file?'
559    File = open(filename,'wb')
560    cPickle.dump([Comments,Data,Npix,image],File,2)
561    File.close()
562    return
563   
564def ProjFileOpen(G2frame,showProvenance=True):
565    'Read a GSAS-II project file and load into the G2 data tree'
566    if not os.path.exists(G2frame.GSASprojectfile):
567        print ('\n*** Error attempt to open project file that does not exist:\n   '+
568               str(G2frame.GSASprojectfile))
569        return
570    LastSavedUsing = None
571    file = open(G2frame.GSASprojectfile,'rb')
572    if showProvenance: print ('loading from file: '+G2frame.GSASprojectfile)
573    #G2frame.SetTitle("GSAS-II data tree: "+
574    #                 os.path.split(G2frame.GSASprojectfile)[1])
575    G2frame.SetTitle("GSAS-II data tree: "+
576        os.path.split(G2frame.GSASprojectfile)[1],1)
577    wx.BeginBusyCursor()
578    try:
579        while True:
580            try:
581                if '2' in platform.python_version_tuple()[0]:
582                    data = cPickle.load(file)
583                else:
584                    data = cPickle.load(file,encoding='latin-1')
585            except EOFError:
586                break
587            datum = data[0]
588           
589            Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
590            if datum[0].startswith('PWDR'):               
591                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
592                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
593                G2frame.GPXtree.SetItemPyData(Id,datum[1][:3])  #temp. trim off junk (patch?)
594            elif datum[0].startswith('HKLF'): 
595                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
596                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
597                G2frame.GPXtree.SetItemPyData(Id,datum[1])
598            else:
599                G2frame.GPXtree.SetItemPyData(Id,datum[1])             
600                if datum[0] == 'Controls' and 'LastSavedUsing' in datum[1]:
601                    LastSavedUsing = datum[1]['LastSavedUsing']
602                if datum[0] == 'Controls' and 'PythonVersions' in datum[1] and GSASIIpath.GetConfigValue('debug') and showProvenance:
603                    print('Packages used to create .GPX file:')
604                    if 'dict' in str(type(datum[1]['PythonVersions'])):  #patch
605                        for p in sorted(datum[1]['PythonVersions'],key=lambda s: s.lower()):
606                            print({:<14s}: {:s}".format(p[0],p[1]))
607                    else:
608                        for p in datum[1]['PythonVersions']:
609                            print({:<12s} {:s}".format(p[0]+':',p[1]))
610            oldPDF = False
611            for datus in data[1:]:
612#patch - 1/23/17 PDF cleanup
613                if datus[0][:4] in ['I(Q)','S(Q)','F(Q)','G(R)']:
614                    oldPDF = True
615                    data[1][1][datus[0][:4]] = copy.deepcopy(datus[1][:2])
616                    continue
617#end PDF cleanup
618                sub = G2frame.GPXtree.AppendItem(Id,datus[0])
619#patch
620                if datus[0] == 'Instrument Parameters' and len(datus[1]) == 1:
621                    if datum[0].startswith('PWDR'):
622                        datus[1] = [dict(zip(datus[1][3],zip(datus[1][0],datus[1][1],datus[1][2]))),{}]
623                    else:
624                        datus[1] = [dict(zip(datus[1][2],zip(datus[1][0],datus[1][1]))),{}]
625                    for item in datus[1][0]:               #zip makes tuples - now make lists!
626                        datus[1][0][item] = list(datus[1][0][item])
627#end patch
628                G2frame.GPXtree.SetItemPyData(sub,datus[1])
629            if 'PDF ' in datum[0][:4] and oldPDF:
630                sub = G2frame.GPXtree.AppendItem(Id,'PDF Peaks')
631                G2frame.GPXtree.SetItemPyData(sub,{'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]})
632            if datum[0].startswith('IMG'):                   #retrieve image default flag & data if set
633                Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
634                if Data['setDefault']:
635                    G2frame.imageDefault = Data               
636        file.close()
637        if LastSavedUsing:
638            print('GPX load successful. Last saved with GSAS-II revision '+LastSavedUsing)
639        else:
640            print('project load successful')
641        G2frame.NewPlot = True
642    except:
643        msg = wx.MessageDialog(G2frame,message="Error reading file "+
644            str(G2frame.GSASprojectfile)+". This is not a GSAS-II .gpx file",
645            caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
646        msg.ShowModal()
647    finally:
648        wx.EndBusyCursor()
649        G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0)
650   
651def ProjFileSave(G2frame):
652    'Save a GSAS-II project file'
653    if not G2frame.GPXtree.IsEmpty():
654        file = open(G2frame.GSASprojectfile,'wb')
655        print ('save to file: '+G2frame.GSASprojectfile)
656        # stick the file name into the tree and version info into tree so they are saved.
657        # (Controls should always be created at this point)
658        try:
659            Controls = G2frame.GPXtree.GetItemPyData(
660                G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
661            Controls['LastSavedAs'] = os.path.abspath(G2frame.GSASprojectfile)
662            Controls['LastSavedUsing'] = str(GSASIIpath.GetVersionNumber())
663            Controls['PythonVersions'] = G2frame.PackageVersions
664        except:
665            pass
666        wx.BeginBusyCursor()
667        try:
668            item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
669            while item:
670                data = []
671                name = G2frame.GPXtree.GetItemText(item)
672                data.append([name,G2frame.GPXtree.GetItemPyData(item)])
673                item2, cookie2 = G2frame.GPXtree.GetFirstChild(item)
674                while item2:
675                    name = G2frame.GPXtree.GetItemText(item2)
676                    data.append([name,G2frame.GPXtree.GetItemPyData(item2)])
677                    item2, cookie2 = G2frame.GPXtree.GetNextChild(item, cookie2)                           
678                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)                           
679                cPickle.dump(data,file,2)
680            file.close()
681            pth = os.path.split(os.path.abspath(G2frame.GSASprojectfile))[0]
682            if GSASIIpath.GetConfigValue('Save_paths'): G2G.SaveGPXdirectory(pth)
683            G2frame.LastGPXdir = pth
684        finally:
685            wx.EndBusyCursor()
686        print('project save successful')
687
688def SaveIntegration(G2frame,PickId,data,Overwrite=False):
689    'Save image integration results as powder pattern(s)'
690    azms = G2frame.Integrate[1]
691    X = G2frame.Integrate[2][:-1]
692    N = len(X)
693    Id = G2frame.GPXtree.GetItemParent(PickId)
694    name = G2frame.GPXtree.GetItemText(Id)
695    name = name.replace('IMG ',data['type']+' ')
696    Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Comments'))
697    Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
698    if 'PWDR' in name:
699        names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
700        codes = [0 for i in range(12)]
701    elif 'SASD' in name:
702        names = ['Type','Lam','Zero','Azimuth'] 
703        codes = [0 for i in range(4)]
704        X = 4.*np.pi*npsind(X/2.)/data['wavelength']    #convert to q
705    Xminmax = [X[0],X[-1]]
706    Azms = []
707    dazm = 0.
708    if data['fullIntegrate'] and data['outAzimuths'] == 1:
709        Azms = [45.0,]                              #a poor man's average?
710    else:
711        for i,azm in enumerate(azms[:-1]):
712            if azm > 360. and azms[i+1] > 360.:
713                Azms.append(G2img.meanAzm(azm%360.,azms[i+1]%360.))
714            else:   
715                Azms.append(G2img.meanAzm(azm,azms[i+1]))
716        dazm = np.min(np.abs(np.diff(azms)))/2.
717    G2frame.IntgOutList = []
718    for i,azm in enumerate(azms[:-1]):
719        Aname = name+" Azm= %.2f"%((azm+dazm)%360.)
720        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
721        # if Overwrite delete any duplicate
722        if Overwrite and G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname):
723            print('Replacing '+Aname)
724            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname)
725            G2frame.GPXtree.Delete(item)
726        else:
727            nOcc = 0
728            while item:
729                Name = G2frame.GPXtree.GetItemText(item)
730                if Aname in Name:
731                    nOcc += 1
732                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
733            if nOcc:
734                Aname += '(%d)'%(nOcc)
735        Sample = G2obj.SetDefaultSample()       #set as Debye-Scherrer
736        Sample['Gonio. radius'] = data['distance']
737        Sample['Omega'] = data['GonioAngles'][0]
738        Sample['Chi'] = data['GonioAngles'][1]
739        Sample['Phi'] = data['GonioAngles'][2]
740        Sample['Azimuth'] = (azm+dazm)%360.    #put here as bin center
741        polariz = 0.99    #set default polarization for synchrotron radiation!
742        for item in Comments:
743            if 'polariz' in item:
744                try:
745                    polariz = float(item.split('=')[1])
746                except:
747                    polariz = 0.99
748            for key in ('Temperature','Pressure','Time','FreePrm1','FreePrm2','FreePrm3','Omega',
749                'Chi','Phi'):
750                if key.lower() in item.lower():
751                    try:
752                        Sample[key] = float(item.split('=')[1])
753                    except:
754                        pass
755            if 'label_prm' in item.lower():
756                for num in ('1','2','3'):
757                    if 'label_prm'+num in item.lower():
758                        Controls['FreePrm'+num] = item.split('=')[1].strip()
759        if 'PWDR' in Aname:
760            parms = ['PXC',data['wavelength'],0.0,polariz,1.0,-0.10,0.4,0.30,1.0,0.0,0.0001,Azms[i]]
761        elif 'SASD' in Aname:
762            Sample['Trans'] = data['SampleAbs'][0]
763            parms = ['LXC',data['wavelength'],0.0,Azms[i]]
764        Y = G2frame.Integrate[0][i]
765        Ymin = np.min(Y)
766        Ymax = np.max(Y)
767        W = np.where(Y>0.,1./Y,1.e-6)                    #probably not true
768        Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Aname)
769        G2frame.IntgOutList.append(Id)
770        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)                   
771        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Limits'),copy.deepcopy([tuple(Xminmax),Xminmax]))
772        if 'PWDR' in Aname:
773            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Background'),[['chebyschev',1,3,1.0,0.0,0.0],
774                {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[]}])
775        inst = [dict(zip(names,zip(parms,parms,codes))),{}]
776        for item in inst[0]:
777            inst[0][item] = list(inst[0][item])
778        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),inst)
779        if 'PWDR' in Aname:
780            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
781            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Peak List'),{'sigDict':{},'peaks':[]})
782            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Index Peak List'),[[],[]])
783            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Unit Cells List'),[])
784            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection Lists'),{})
785        elif 'SASD' in Aname:             
786            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Substances'),G2pdG.SetDefaultSubstances())
787            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
788            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Models'),G2pdG.SetDefaultSASDModel())
789        valuesdict = {
790            'wtFactor':1.0,
791            'Dummy':False,
792            'ranId':ran.randint(0,sys.maxsize),
793            'Offset':[0.0,0.0],'delOffset':0.02*Ymax,'refOffset':-0.1*Ymax,'refDelt':0.1*Ymax,
794            'qPlot':False,'dPlot':False,'sqrtPlot':False,'Yminmax':[Ymin,Ymax]
795            }
796        G2frame.GPXtree.SetItemPyData(
797            Id,[valuesdict,
798                [np.array(X),np.array(Y),np.array(W),np.zeros(N),np.zeros(N),np.zeros(N)]])
799    return Id       #last powder pattern generated
800   
801def XYsave(G2frame,XY,labelX='X',labelY='Y',names=None):
802    'Save XY table data'
803    pth = G2G.GetExportPath(G2frame)
804    dlg = wx.FileDialog(
805        G2frame, 'Enter csv filename for XY table', pth, '',
806        'XY table file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
807    try:
808        if dlg.ShowModal() == wx.ID_OK:
809            filename = dlg.GetPath()
810            filename = os.path.splitext(filename)[0]+'.csv'
811            File = open(filename,'w')
812        else:
813            filename = None
814    finally:
815        dlg.Destroy()
816    if not filename:
817        return
818    for i in range(len(XY)):
819        if names != None:
820            header = '%s,%s(%s)\n'%(labelX,labelY,names[i])
821        else:
822            header = '%s,%s(%d)\n'%(labelX,labelY,i)
823        File.write(header)
824        for x,y in XY[i].T:
825            File.write('%.3f,%.3f\n'%(x,y))   
826    File.close()
827    print (' XY data saved to: '+filename)
828           
829def askSaveDirectory(G2frame):
830    '''Ask the user to supply a directory name. Path name is used as the
831    starting point for the next export path search.
832
833    :returns: a directory name (str) or None if Cancel is pressed
834    '''
835    pth = G2G.GetExportPath(G2frame)
836    dlg = wx.DirDialog(
837            G2frame, 'Input directory where file(s) will be written', pth,
838            wx.DD_DEFAULT_STYLE)
839    dlg.CenterOnParent()
840    try:
841        if dlg.ShowModal() == wx.ID_OK:
842            filename = dlg.GetPath()
843            G2frame.LastExportDir = filename
844        else:
845            filename = None
846    finally:
847        dlg.Destroy()
848    return filename
849
850def askSaveFile(G2frame,defnam,extension,longFormatName):
851    '''Ask the user to supply a file name
852
853    :returns: a file name (str) or None if Cancel is pressed
854    '''
855
856    pth = G2G.GetExportPath(G2frame)
857    dlg = wx.FileDialog(
858        G2frame, 'Input name for file to write', pth, defnam,
859        longFormatName+' (*'+extension+')|*'+extension,
860        wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
861    dlg.CenterOnParent()
862    try:
863        if dlg.ShowModal() == wx.ID_OK:
864            filename = dlg.GetPath()
865            G2frame.LastExportDir = os.path.split(filename)[0]
866            filename = os.path.splitext(filename)[0]+extension # make sure extension is correct
867        else:
868            filename = None
869    finally:
870        dlg.Destroy()
871    return filename
872
873def PDFSave(G2frame,exports,PDFsaves):
874    'Save a PDF I(Q), S(Q), F(Q) and G(r)  in column formats'
875    import scipy.interpolate as scintp
876    if len(exports) > 1:
877        dirname = askSaveDirectory(G2frame)
878        if not dirname: return
879    else:
880        defnam = exports[0].replace(' ','_')[5:]
881        filename = askSaveFile(G2frame,defnam,'.gr','G(r) file, etc.')
882        if not filename: return
883        dirname,filename = os.path.split(filename)
884        filename = os.path.splitext(filename)[0]
885    for export in exports:
886        if len(exports) > 1:
887            filename = export.replace(' ','_')[5:]
888        PickId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, export)
889        PDFControls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, PickId,'PDF Controls'))
890        if PDFsaves[0]:     #I(Q)
891            iqfilename = ospath.join(dirname,filename+'.iq')
892            iqdata = PDFControls['I(Q)'][0]
893            iqfxn = scintp.interp1d(iqdata[0],iqdata[1],kind='linear')
894            iqfile = open(iqfilename,'w')
895            iqfile.write('#T I(Q) %s\n'%(export))
896            iqfile.write('#L Q     I(Q)\n')
897            qnew = np.arange(iqdata[0][0],iqdata[0][-1],0.005)
898            iqnew = zip(qnew,iqfxn(qnew))
899            for q,iq in iqnew:
900                iqfile.write("%15.6g %15.6g\n" % (q,iq))
901            iqfile.close()
902            print (' I(Q) saved to: '+iqfilename)
903           
904        if PDFsaves[1]:     #S(Q)
905            sqfilename = ospath.join(dirname,filename+'.sq')
906            sqdata = PDFControls['S(Q)'][1]
907            sqfxn = scintp.interp1d(sqdata[0],sqdata[1],kind='linear')
908            sqfile = open(sqfilename,'w')
909            sqfile.write('#T S(Q) %s\n'%(export))
910            sqfile.write('#L Q     S(Q)\n')
911            qnew = np.arange(sqdata[0][0],sqdata[0][-1],0.005)
912            sqnew = zip(qnew,sqfxn(qnew))
913            for q,sq in sqnew:
914                sqfile.write("%15.6g %15.6g\n" % (q,sq))
915            sqfile.close()
916            print (' S(Q) saved to: '+sqfilename)
917           
918        if PDFsaves[2]:     #F(Q)
919            fqfilename = ospath.join(dirname,filename+'.fq')
920            fqdata = PDFControls['F(Q)'][1]
921            fqfxn = scintp.interp1d(fqdata[0],fqdata[1],kind='linear')
922            fqfile = open(fqfilename,'w')
923            fqfile.write('#T F(Q) %s\n'%(export))
924            fqfile.write('#L Q     F(Q)\n')
925            qnew = np.arange(fqdata[0][0],fqdata[0][-1],0.005)
926            fqnew = zip(qnew,fqfxn(qnew))
927            for q,fq in fqnew:
928                fqfile.write("%15.6g %15.6g\n" % (q,fq))
929            fqfile.close()
930            print (' F(Q) saved to: '+fqfilename)
931           
932        if PDFsaves[3]:     #G(R)
933            grfilename = ospath.join(dirname,filename+'.gr')
934            grdata = PDFControls['G(R)'][1]
935            grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
936            grfile = open(grfilename,'w')
937            grfile.write('#T G(R) %s\n'%(export))
938            grfile.write('#L R     G(R)\n')
939            rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
940            grnew = zip(rnew,grfxn(rnew))
941            for r,gr in grnew:
942                grfile.write("%15.6g %15.6g\n" % (r,gr))
943            grfile.close()
944            print (' G(R) saved to: '+grfilename)
945       
946        if PDFsaves[4]: #pdfGUI file for G(R)
947            pId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, 'PWDR'+export[4:])
948            Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, pId,'Instrument Parameters'))[0]
949            Limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, pId,'Limits'))
950            grfilename = ospath.join(dirname,filename+'.gr')
951            grdata = PDFControls['G(R)'][1]
952            qdata = PDFControls['I(Q)'][1][0]
953            grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
954            grfile = open(grfilename,'w')
955            rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
956            grnew = zip(rnew,grfxn(rnew))
957
958            grfile.write('[DEFAULT]\n')
959            grfile.write('\n')
960            grfile.write('version = GSAS-II-v'+str(GSASIIpath.GetVersionNumber())+'\n')
961            grfile.write('\n')
962            grfile.write('# input and output specifications\n')
963            grfile.write('dataformat = Qnm\n')
964            grfile.write('inputfile = %s\n'%(PDFControls['Sample']['Name']))
965            grfile.write('backgroundfile = %s\n'%(PDFControls['Sample Bkg.']['Name']))
966            grfile.write('outputtype = gr\n')
967            grfile.write('\n')
968            grfile.write('# PDF calculation setup\n')
969            if 'x' in Inst['Type']:
970                grfile.write('mode = %s\n'%('xray'))
971            elif 'N' in Inst['Type']:
972                grfile.write('mode = %s\n'%('neutron'))
973            wave = G2mth.getMeanWave(Inst)
974            grfile.write('wavelength = %.5f\n'%(wave))
975            formula = ''
976            for el in PDFControls['ElList']:
977                formula += el
978                num = PDFControls['ElList'][el]['FormulaNo']
979                if num == round(num):
980                    formula += '%d'%(int(num))
981                else:
982                    formula += '%.2f'%(num)
983            grfile.write('composition = %s\n'%(formula))
984            grfile.write('bgscale = %.3f\n'%(-PDFControls['Sample Bkg.']['Mult']))
985            highQ = 2.*np.pi/G2lat.Pos2dsp(Inst,Limits[1][1])
986            grfile.write('qmaxinst = %.2f\n'%(highQ))
987            grfile.write('qmin = %.5f\n'%(qdata[0]))
988            grfile.write('qmax = %.4f\n'%(qdata[-1]))
989            grfile.write('rmin = %.2f\n'%(PDFControls['Rmin']))
990            grfile.write('rmax = %.2f\n'%(PDFControls['Rmax']))
991            grfile.write('rstep = 0.01\n')
992           
993           
994            grfile.write('\n')
995            grfile.write('# End of config '+63*'-')
996            grfile.write('\n')
997            grfile.write('#### start data\n')
998            grfile.write('#S 1\n')
999            grfile.write('#L r($\AA$)  G($\AA^{-2}$)\n')           
1000            for r,gr in grnew:
1001                grfile.write("%15.2F %15.6F\n" % (r,gr))
1002            grfile.close()
1003            print (' G(R) saved to: '+grfilename)
1004   
1005def PeakListSave(G2frame,file,peaks):
1006    'Save powder peaks to a data file'
1007    print ('save peak list to file: '+G2frame.peaklistfile)
1008    if not peaks:
1009        dlg = wx.MessageDialog(G2frame, 'No peaks!', 'Nothing to save!', wx.OK)
1010        try:
1011            dlg.ShowModal()
1012        finally:
1013            dlg.Destroy()
1014        return
1015    for peak in peaks:
1016        file.write("%10.4f %12.2f %10.3f %10.3f \n" % \
1017            (peak[0],peak[2],peak[4],peak[6]))
1018    print ('peak list saved')
1019             
1020def IndexPeakListSave(G2frame,peaks):
1021    'Save powder peaks from the indexing list'
1022    file = open(G2frame.peaklistfile,'wa')
1023    print ('save index peak list to file: '+G2frame.peaklistfile)
1024    wx.BeginBusyCursor()
1025    try:
1026        if not peaks:
1027            dlg = wx.MessageDialog(G2frame, 'No peaks!', 'Nothing to save!', wx.OK)
1028            try:
1029                dlg.ShowModal()
1030            finally:
1031                dlg.Destroy()
1032            return
1033        for peak in peaks:
1034            file.write("%12.6f\n" % (peak[7]))
1035        file.close()
1036    finally:
1037        wx.EndBusyCursor()
1038    print ('index peak list saved')
1039   
1040class MultipleChoicesDialog(wx.Dialog):
1041    '''A dialog that offers a series of choices, each with a
1042    title and a wx.Choice widget. Intended to be used Modally.
1043    typical input:
1044
1045        *  choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
1046        *  headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
1047       
1048    selections are placed in self.chosen when OK is pressed
1049
1050    Also see GSASIIctrlGUI
1051    '''
1052    def __init__(self,choicelist,headinglist,
1053                 head='Select options',
1054                 title='Please select from options below',
1055                 parent=None):
1056        self.chosen = []
1057        wx.Dialog.__init__(
1058            self,parent,wx.ID_ANY,head, 
1059            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1060        panel = wx.Panel(self)
1061        mainSizer = wx.BoxSizer(wx.VERTICAL)
1062        mainSizer.Add((10,10),1)
1063        topLabl = wx.StaticText(panel,wx.ID_ANY,title)
1064        mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.CENTER,10)
1065        self.ChItems = []
1066        for choice,lbl in zip(choicelist,headinglist):
1067            mainSizer.Add((10,10),1)
1068            self.chosen.append(0)
1069            topLabl = wx.StaticText(panel,wx.ID_ANY,' '+lbl)
1070            mainSizer.Add(topLabl,0,wx.ALIGN_LEFT,10)
1071            self.ChItems.append(wx.Choice(self, wx.ID_ANY, (100, 50), choices = choice))
1072            mainSizer.Add(self.ChItems[-1],0,wx.ALIGN_CENTER,10)
1073
1074        OkBtn = wx.Button(panel,-1,"Ok")
1075        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1076        cancelBtn = wx.Button(panel,-1,"Cancel")
1077        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1078        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1079        btnSizer.Add((20,20),1)
1080        btnSizer.Add(OkBtn)
1081        btnSizer.Add((20,20),1)
1082        btnSizer.Add(cancelBtn)
1083        btnSizer.Add((20,20),1)
1084        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1085        panel.SetSizer(mainSizer)
1086        panel.Fit()
1087        self.Fit()
1088       
1089    def OnOk(self,event):
1090        parent = self.GetParent()
1091        if parent is not None: parent.Raise()
1092        # save the results from the choice widgets
1093        self.chosen = []
1094        for w in self.ChItems:
1095            self.chosen.append(w.GetSelection())
1096        self.EndModal(wx.ID_OK)             
1097           
1098    def OnCancel(self,event):
1099        parent = self.GetParent()
1100        if parent is not None: parent.Raise()
1101        self.chosen = []
1102        self.EndModal(wx.ID_CANCEL)             
1103           
1104def ExtractFileFromZip(filename, selection=None, confirmread=True,
1105                       confirmoverwrite=True, parent=None,
1106                       multipleselect=False):
1107    '''If the filename is a zip file, extract a file from that
1108    archive.
1109
1110    :param list Selection: used to predefine the name of the file
1111      to be extracted. Filename case and zip directory name are
1112      ignored in selection; the first matching file is used.
1113
1114    :param bool confirmread: if True asks the user to confirm before expanding
1115      the only file in a zip
1116
1117    :param bool confirmoverwrite: if True asks the user to confirm
1118      before overwriting if the extracted file already exists
1119
1120    :param bool multipleselect: if True allows more than one zip
1121      file to be extracted, a list of file(s) is returned.
1122      If only one file is present, do not ask which one, otherwise
1123      offer a list of choices (unless selection is used).
1124   
1125    :returns: the name of the file that has been created or a
1126      list of files (see multipleselect)
1127
1128    If the file is not a zipfile, return the name of the input file.
1129    If the zipfile is empty or no file has been selected, return None
1130    '''
1131    import zipfile # do this now, since we can save startup time by doing this only on need
1132    import shutil
1133    zloc = os.path.split(filename)[0]
1134    if not zipfile.is_zipfile(filename):
1135        #print("not zip")
1136        return filename
1137
1138    z = zipfile.ZipFile(filename,'r')
1139    zinfo = z.infolist()
1140
1141    if len(zinfo) == 0:
1142        #print('Zip has no files!')
1143        zlist = [-1]
1144    if selection:
1145        choices = [os.path.split(i.filename)[1].lower() for i in zinfo]
1146        if selection.lower() in choices:
1147            zlist = [choices.index(selection.lower())]
1148        else:
1149            print('debug: file '+str(selection)+' was not found in '+str(filename))
1150            zlist = [-1]
1151    elif len(zinfo) == 1 and confirmread:
1152        result = wx.ID_NO
1153        dlg = wx.MessageDialog(
1154            parent,
1155            'Is file '+str(zinfo[0].filename)+
1156            ' what you want to extract from '+
1157            str(os.path.split(filename)[1])+'?',
1158            'Confirm file', 
1159            wx.YES_NO | wx.ICON_QUESTION)
1160        try:
1161            result = dlg.ShowModal()
1162        finally:
1163            dlg.Destroy()
1164        if result == wx.ID_NO:
1165            zlist = [-1]
1166        else:
1167            zlist = [0]
1168    elif len(zinfo) == 1:
1169        zlist = [0]
1170    elif multipleselect:
1171        # select one or more from a from list
1172        choices = [i.filename for i in zinfo]
1173        dlg = G2G.G2MultiChoiceDialog(parent,'Select file(s) to extract from zip file '+str(filename),
1174            'Choose file(s)',choices)
1175        if dlg.ShowModal() == wx.ID_OK:
1176            zlist = dlg.GetSelections()
1177        else:
1178            zlist = []
1179        dlg.Destroy()
1180    else:
1181        # select one from a from list
1182        choices = [i.filename for i in zinfo]
1183        dlg = wx.SingleChoiceDialog(parent,
1184            'Select file to extract from zip file'+str(filename),'Choose file',
1185            choices,)
1186        if dlg.ShowModal() == wx.ID_OK:
1187            zlist = [dlg.GetSelection()]
1188        else:
1189            zlist = [-1]
1190        dlg.Destroy()
1191       
1192    outlist = []
1193    for zindex in zlist:
1194        if zindex >= 0:
1195            efil = os.path.join(zloc, os.path.split(zinfo[zindex].filename)[1])
1196            if os.path.exists(efil) and confirmoverwrite:
1197                result = wx.ID_NO
1198                dlg = wx.MessageDialog(parent,
1199                    'File '+str(efil)+' already exists. OK to overwrite it?',
1200                    'Confirm overwrite',wx.YES_NO | wx.ICON_QUESTION)
1201                try:
1202                    result = dlg.ShowModal()
1203                finally:
1204                    dlg.Destroy()
1205                if result == wx.ID_NO:
1206                    zindex = -1
1207        if zindex >= 0:
1208            # extract the file to the current directory, regardless of it's original path
1209            #z.extract(zinfo[zindex],zloc)
1210            eloc,efil = os.path.split(zinfo[zindex].filename)
1211            outfile = os.path.join(zloc, efil)
1212            fpin = z.open(zinfo[zindex])
1213            fpout = file(outfile, "wb")
1214            shutil.copyfileobj(fpin, fpout)
1215            fpin.close()
1216            fpout.close()
1217            outlist.append(outfile)
1218    z.close()
1219    if multipleselect and len(outlist) >= 1:
1220        return outlist
1221    elif len(outlist) == 1:
1222        return outlist[0]
1223    else:
1224        return None
1225
1226######################################################################
1227# base classes for reading various types of data files
1228#   not used directly, only by subclassing
1229######################################################################
1230def BlockSelector(ChoiceList, ParentFrame=None,title='Select a block',
1231    size=None, header='Block Selector',useCancel=True):
1232    ''' Provide a wx dialog to select a block if the file contains more
1233    than one set of data and one must be selected
1234    '''
1235    if useCancel:
1236        dlg = wx.SingleChoiceDialog(
1237            ParentFrame,title, header,ChoiceList)
1238    else:
1239        dlg = wx.SingleChoiceDialog(
1240            ParentFrame,title, header,ChoiceList,
1241            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1242    if size: dlg.SetSize(size)
1243    dlg.CenterOnParent()
1244    if dlg.ShowModal() == wx.ID_OK:
1245        sel = dlg.GetSelection()
1246        return sel
1247    else:
1248        return None
1249    dlg.Destroy()
1250
1251def MultipleBlockSelector(ChoiceList, ParentFrame=None,
1252    title='Select a block',size=None, header='Block Selector'):
1253    '''Provide a wx dialog to select a block of data if the
1254    file contains more than one set of data and one must be
1255    selected.
1256
1257    :returns: a list of the selected blocks
1258    '''
1259    dlg = wx.MultiChoiceDialog(ParentFrame,title, header,ChoiceList+['Select all'],
1260        wx.CHOICEDLG_STYLE)
1261    dlg.CenterOnScreen()
1262    if size: dlg.SetSize(size)
1263    if dlg.ShowModal() == wx.ID_OK:
1264        sel = dlg.GetSelections()
1265    else:
1266        return []
1267    dlg.Destroy()
1268    selected = []
1269    if len(ChoiceList) in sel:
1270        return range(len(ChoiceList))
1271    else:
1272        return sel
1273    return selected
1274
1275def MultipleChoicesSelector(choicelist, headinglist, ParentFrame=None, **kwargs):
1276    '''A modal dialog that offers a series of choices, each with a title and a wx.Choice
1277    widget. Typical input:
1278   
1279       * choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
1280       
1281       * headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
1282       
1283    optional keyword parameters are: head (window title) and title
1284    returns a list of selected indicies for each choice (or None)
1285    '''
1286    result = None
1287    dlg = MultipleChoicesDialog(choicelist,headinglist,
1288        parent=ParentFrame, **kwargs)         
1289    dlg.CenterOnParent()
1290    if dlg.ShowModal() == wx.ID_OK:
1291        result = dlg.chosen
1292    dlg.Destroy()
1293    return result
1294
1295def PhaseSelector(ChoiceList, ParentFrame=None,
1296    title='Select a phase', size=None,header='Phase Selector'):
1297    ''' Provide a wx dialog to select a phase if the file contains more
1298    than one phase
1299    '''
1300    return BlockSelector(ChoiceList,ParentFrame,title,
1301        size,header)
1302
1303######################################################################
1304def striphist(var,insChar=''):
1305    'strip a histogram number from a var name'
1306    sv = var.split(':')
1307    if len(sv) <= 1: return var
1308    if sv[1]:
1309        sv[1] = insChar
1310    return ':'.join(sv)
1311class ExportBaseclass(object):
1312    '''Defines a base class for the exporting of GSAS-II results.
1313
1314    This class is subclassed in the various exports/G2export_*.py files. Those files
1315    are imported in :meth:`GSASIIdataGUI.GSASII._init_Exports` which defines the
1316    appropriate menu items for each one and the .Exporter method is called
1317    directly from the menu item.
1318
1319    Routines may also define a .Writer method, which is used to write a single
1320    file without invoking any GUI objects.
1321    '''
1322    # TODO: review exporters producing exceptions where .Writer can't be used where G2frame is None (see CIF)
1323    # TODO: review conflicting uses of .Writer with mode (SeqRef) & elsewhere
1324    # TODO: move this class to G2fil
1325    def __init__(self,G2frame,formatName,extension,longFormatName=None,):
1326        self.G2frame = G2frame
1327        self.formatName = formatName # short string naming file type
1328        self.extension = extension
1329        if longFormatName: # longer string naming file type
1330            self.longFormatName = longFormatName
1331        else:
1332            self.longFormatName = formatName
1333        self.OverallParms = {}
1334        self.Phases = {}
1335        self.Histograms = {}
1336        self.powderDict = {}
1337        self.xtalDict = {}
1338        self.parmDict = {}
1339        self.sigDict = {}
1340        # updated in InitExport:
1341        self.currentExportType = None # type of export that has been requested
1342        # updated in ExportSelect (when used):
1343        self.phasenam = None # a list of selected phases
1344        self.histnam = None # a list of selected histograms
1345        self.filename = None # name of file to be written (single export) or template (multiple files)
1346        self.dirname = '' # name of directory where file(s) will be written
1347        self.fullpath = '' # name of file being written -- full path
1348       
1349        # items that should be defined in a subclass of this class
1350        self.exporttype = []  # defines the type(s) of exports that the class can handle.
1351        # The following types are defined: 'project', "phase", "powder", "single"
1352        self.multiple = False # set as True if the class can export multiple phases or histograms
1353        # self.multiple is ignored for "project" exports
1354
1355    def InitExport(self,event):
1356        '''Determines the type of menu that called the Exporter and
1357        misc initialization.
1358        '''
1359        self.filename = None # name of file to be written (single export)
1360        self.dirname = '' # name of file to be written (multiple export)
1361        if event:
1362            self.currentExportType = self.G2frame.ExportLookup.get(event.Id)
1363
1364    def MakePWDRfilename(self,hist):
1365        '''Make a filename root (no extension) from a PWDR histogram name
1366
1367        :param str hist: the histogram name in data tree (starts with "PWDR ")
1368        '''
1369        file0 = ''
1370        file1 = hist[5:]
1371        # replace repeated blanks
1372        while file1 != file0:
1373            file0 = file1
1374            file1 = file0.replace('  ',' ').strip()
1375        file0 = file1.replace('Azm= ','A')
1376        # if angle has unneeded decimal places on aziumuth, remove them
1377        if file0[-3:] == '.00': file0 = file0[:-3]
1378        file0 = file0.replace('.','_')
1379        file0 = file0.replace(' ','_')
1380        return file0
1381
1382    def ExportSelect(self,AskFile='ask'):
1383        '''Selects histograms or phases when needed. Sets a default file name when
1384        requested into self.filename; always sets a default directory in self.dirname.
1385
1386        :param bool AskFile: Determines how this routine processes getting a
1387          location to store the current export(s).
1388         
1389          * if AskFile is 'ask' (default option), get the name of the file to be written;
1390            self.filename and self.dirname are always set. In the case where
1391            multiple files must be generated, the export routine should do this
1392            based on self.filename as a template.
1393          * if AskFile is 'dir', get the name of the directory to be used;
1394            self.filename is not used, but self.dirname is always set. The export routine
1395            will always generate the file name.
1396          * if AskFile is 'single', get only the name of the directory to be used when
1397            multiple items will be written (as multiple files) are used
1398            *or* a complete file name is requested when a single file
1399            name is selected. self.dirname is always set and self.filename used
1400            only when a single file is selected. 
1401          * if AskFile is 'default', creates a name of the file to be used from
1402            the name of the project (.gpx) file. If the project has not been saved,
1403            then the name of file is requested.
1404            self.filename and self.dirname are always set. In the case where
1405            multiple file names must be generated, the export routine should do this
1406            based on self.filename.
1407          * if AskFile is 'default-dir', sets self.dirname from the project (.gpx)
1408            file. If the project has not been saved, then a directory is requested.
1409            self.filename is not used.
1410
1411        :returns: True in case of an error
1412        '''
1413       
1414        numselected = 1
1415        if self.currentExportType == 'phase':
1416            if len(self.Phases) == 0:
1417                self.G2frame.ErrorDialog(
1418                    'Empty project',
1419                    'Project does not contain any phases.')
1420                return True
1421            elif len(self.Phases) == 1:
1422                self.phasenam = self.Phases.keys()
1423            elif self.multiple: 
1424                choices = sorted(self.Phases.keys())
1425                phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1426                if phasenum is None: return True
1427                self.phasenam = [choices[i] for i in phasenum]
1428                if not self.phasenam: return True
1429                numselected = len(self.phasenam)
1430            else:
1431                choices = sorted(self.Phases.keys())
1432                phasenum = G2G.ItemSelector(choices,self.G2frame)
1433                if phasenum is None: return True
1434                self.phasenam = [choices[phasenum]]
1435                numselected = len(self.phasenam)
1436        elif self.currentExportType == 'single':
1437            if len(self.xtalDict) == 0:
1438                self.G2frame.ErrorDialog(
1439                    'Empty project',
1440                    'Project does not contain any single crystal data.')
1441                return True
1442            elif len(self.xtalDict) == 1:
1443                self.histnam = self.xtalDict.values()
1444            elif self.multiple:
1445                choices = sorted(self.xtalDict.values())
1446                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1447                if not hnum: return True
1448                self.histnam = [choices[i] for i in hnum]
1449                numselected = len(self.histnam)
1450            else:
1451                choices = sorted(self.xtalDict.values())
1452                hnum = G2G.ItemSelector(choices,self.G2frame)
1453                if hnum is None: return True
1454                self.histnam = [choices[hnum]]
1455                numselected = len(self.histnam)
1456        elif self.currentExportType == 'powder':
1457            if len(self.powderDict) == 0:
1458                self.G2frame.ErrorDialog(
1459                    'Empty project',
1460                    'Project does not contain any powder data.')
1461                return True
1462            elif len(self.powderDict) == 1:
1463                self.histnam = self.powderDict.values()
1464            elif self.multiple:
1465                choices = sorted(self.powderDict.values())
1466                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1467                if not hnum: return True
1468                self.histnam = [choices[i] for i in hnum]
1469                numselected = len(self.histnam)
1470            else:
1471                choices = sorted(self.powderDict.values())
1472                hnum = G2G.ItemSelector(choices,self.G2frame)
1473                if hnum is None: return True
1474                self.histnam = [choices[hnum]]
1475                numselected = len(self.histnam)
1476        elif self.currentExportType == 'image':
1477            if len(self.Histograms) == 0:
1478                self.G2frame.ErrorDialog(
1479                    'Empty project',
1480                    'Project does not contain any images.')
1481                return True
1482            elif len(self.Histograms) == 1:
1483                self.histnam = self.Histograms.keys()
1484            else:
1485                choices = sorted(self.Histograms.keys())
1486                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple)
1487                if self.multiple:
1488                    if not hnum: return True
1489                    self.histnam = [choices[i] for i in hnum]
1490                else:
1491                    if hnum is None: return True
1492                    self.histnam = [choices[hnum]]
1493                numselected = len(self.histnam)
1494        if self.currentExportType == 'map':
1495            # search for phases with maps
1496            mapPhases = []
1497            choices = []
1498            for phasenam in sorted(self.Phases):
1499                phasedict = self.Phases[phasenam] # pointer to current phase info           
1500                if len(phasedict['General']['Map'].get('rho',[])):
1501                    mapPhases.append(phasenam)
1502                    if phasedict['General']['Map'].get('Flip'):
1503                        choices.append('Charge flip map: '+str(phasenam))
1504                    elif phasedict['General']['Map'].get('MapType'):
1505                        choices.append(
1506                            str(phasedict['General']['Map'].get('MapType'))
1507                            + ' map: ' + str(phasenam))
1508                    else:
1509                        choices.append('unknown map: '+str(phasenam))
1510            # select a map if needed
1511            if len(mapPhases) == 0:
1512                self.G2frame.ErrorDialog(
1513                    'Empty project',
1514                    'Project does not contain any maps.')
1515                return True
1516            elif len(mapPhases) == 1:
1517                self.phasenam = mapPhases
1518            else: 
1519                phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple)
1520                if self.multiple:
1521                    if not phasenum: return True
1522                    self.phasenam = [mapPhases[i] for i in phasenum]
1523                else:
1524                    if phasenum is None: return True
1525                    self.phasenam = [mapPhases[phasenum]]
1526            numselected = len(self.phasenam)
1527
1528        # items selected, now set self.dirname and usually self.filename
1529        if AskFile == 'ask' or (AskFile == 'single' and numselected == 1) or (
1530            AskFile == 'default' and not self.G2frame.GSASprojectfile
1531            ):
1532            filename = self.askSaveFile()
1533            if not filename: return True
1534            self.dirname,self.filename = os.path.split(filename)
1535        elif AskFile == 'dir' or AskFile == 'single' or (
1536            AskFile == 'default-dir' and not self.G2frame.GSASprojectfile
1537            ):
1538            self.dirname = self.askSaveDirectory()
1539            if not self.dirname: return True
1540        elif AskFile == 'default-dir' or AskFile == 'default':
1541            self.dirname,self.filename = os.path.split(
1542                os.path.splitext(self.G2frame.GSASprojectfile)[0] + self.extension
1543                )
1544        else:
1545            raise Exception('This should not happen!')
1546
1547    def loadParmDict(self):
1548        '''Load the GSAS-II refinable parameters from the tree into a dict (self.parmDict). Update
1549        refined values to those from the last cycle and set the uncertainties for the
1550        refined parameters in another dict (self.sigDict).
1551
1552        Expands the parm & sig dicts to include values derived from constraints.
1553        '''
1554        self.parmDict = {}
1555        self.sigDict = {}
1556        rigidbodyDict = {}
1557        covDict = {}
1558        consDict = {}
1559        Histograms,Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1560        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1561        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1562        while item:
1563            name = self.G2frame.GPXtree.GetItemText(item)
1564            if name == 'Rigid bodies':
1565                 rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(item)
1566            elif name == 'Covariance':
1567                 covDict = self.G2frame.GPXtree.GetItemPyData(item)
1568            elif name == 'Constraints':
1569                 consDict = self.G2frame.GPXtree.GetItemPyData(item)
1570            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1571        rbVary,rbDict =  G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1572        self.parmDict.update(rbDict)
1573        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1574        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave =  G2stIO.GetPhaseData(
1575            Phases,RestraintDict=None,rbIds=rbIds,Print=False)
1576        self.parmDict.update(phaseDict)
1577        hapVary,hapDict,controlDict =  G2stIO.GetHistogramPhaseData(
1578            Phases,Histograms,Print=False,resetRefList=False)
1579        self.parmDict.update(hapDict)
1580        histVary,histDict,controlDict =  G2stIO.GetHistogramData(Histograms,Print=False)
1581        self.parmDict.update(histDict)
1582        self.parmDict.update(zip(
1583            covDict.get('varyList',[]),
1584            covDict.get('variables',[])))
1585        self.sigDict = dict(zip(
1586            covDict.get('varyList',[]),
1587            covDict.get('sig',[])))
1588        # expand to include constraints: first compile a list of constraints
1589        constList = []
1590        for item in consDict:
1591            if item.startswith('_'): continue
1592            constList += consDict[item]
1593        # now process the constraints
1594        G2mv.InitVars()
1595        constDict,fixedList,ignored = G2stIO.ProcessConstraints(constList)
1596        varyList = covDict.get('varyListStart')
1597        if varyList is None and len(constDict) == 0:
1598            # no constraints can use varyList
1599            varyList = covDict.get('varyList')
1600        elif varyList is None:
1601            # old GPX file from before pre-constraint varyList is saved
1602            print (' *** Old refinement: Please use Calculate/Refine to redo  ***')
1603            raise Exception(' *** Export aborted ***')
1604        else:
1605            varyList = list(varyList)
1606        try:
1607            groups,parmlist = G2mv.GroupConstraints(constDict)
1608            G2mv.GenerateConstraints(groups,parmlist,varyList,constDict,fixedList,self.parmDict)
1609        except:
1610            # this really should not happen
1611            print (' *** ERROR - constraints are internally inconsistent ***')
1612            errmsg, warnmsg = G2mv.CheckConstraints(varyList,constDict,fixedList)
1613            print ('Errors'+errmsg)
1614            if warnmsg: print ('Warnings'+warnmsg)
1615            raise Exception(' *** CIF creation aborted ***')
1616        # add the constrained values to the parameter dictionary
1617        G2mv.Dict2Map(self.parmDict,varyList)
1618        # and add their uncertainties into the esd dictionary (sigDict)
1619        if covDict.get('covMatrix') is not None:
1620            self.sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],covDict['varyList'],self.parmDict))
1621
1622    def loadTree(self):
1623        '''Load the contents of the data tree into a set of dicts
1624        (self.OverallParms, self.Phases and self.Histogram as well as self.powderDict
1625        & self.xtalDict)
1626       
1627        * The childrenless data tree items are overall parameters/controls for the
1628          entire project and are placed in self.OverallParms
1629        * Phase items are placed in self.Phases
1630        * Data items are placed in self.Histogram. The key for these data items
1631          begin with a keyword, such as PWDR, IMG, HKLF,... that identifies the data type.
1632        '''
1633        self.OverallParms = {}
1634        self.powderDict = {}
1635        self.xtalDict = {}
1636        self.Phases = {}
1637        self.Histograms = {}
1638        self.SeqRefdata = None
1639        self.SeqRefhist = None
1640        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1641        histType = None       
1642        if self.currentExportType == 'phase':
1643            # if exporting phases load them here
1644            sub = G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Phases')
1645            if not sub:
1646                print ('no phases found')
1647                return True
1648            item, cookie = self.G2frame.GPXtree.GetFirstChild(sub)
1649            while item:
1650                phaseName = self.G2frame.GPXtree.GetItemText(item)
1651                self.Phases[phaseName] =  self.G2frame.GPXtree.GetItemPyData(item)
1652                item, cookie = self.G2frame.GPXtree.GetNextChild(sub, cookie)
1653            return
1654        elif self.currentExportType == 'single':
1655            histType = 'HKLF'
1656        elif self.currentExportType == 'powder':
1657            histType = 'PWDR'
1658        elif self.currentExportType == 'image':
1659            histType = 'IMG'
1660
1661        if histType: # Loading just one kind of tree entry
1662            item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1663            while item:
1664                name = self.G2frame.GPXtree.GetItemText(item)
1665                if name.startswith(histType):
1666                    if self.Histograms.get(name): # there is already an item with this name
1667                        print('Histogram name '+str(name)+' is repeated. Renaming')
1668                        if name[-1] == '9':
1669                            name = name[:-1] + '10'
1670                        elif name[-1] in '012345678':
1671                            name = name[:-1] + str(int(name[-1])+1)
1672                        else:                           
1673                            name += '-1'
1674                    self.Histograms[name] = {}
1675                    # the main info goes into Data, but the 0th
1676                    # element contains refinement results, carry
1677                    # that over too now.
1678                    self.Histograms[name]['Data'] = self.G2frame.GPXtree.GetItemPyData(item)[1]
1679                    self.Histograms[name][0] = self.G2frame.GPXtree.GetItemPyData(item)[0]
1680                    item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1681                    while item2: 
1682                        child = self.G2frame.GPXtree.GetItemText(item2)
1683                        self.Histograms[name][child] = self.G2frame.GPXtree.GetItemPyData(item2)
1684                        item2, cookie2 = self.G2frame.GPXtree.GetNextChild(item, cookie2)
1685                item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1686            # index powder and single crystal histograms by number
1687            for hist in self.Histograms:
1688                if hist.startswith("PWDR"): 
1689                    d = self.powderDict
1690                elif hist.startswith("HKLF"): 
1691                    d = self.xtalDict
1692                else:
1693                    return                   
1694                i = self.Histograms[hist].get('hId')
1695                if i is None and not d.keys():
1696                    i = 0
1697                elif i is None or i in d.keys():
1698                    i = max(d.keys())+1
1699                d[i] = hist
1700            return
1701        # else standard load: using all interlinked phases and histograms
1702        self.Histograms,self.Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1703        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1704        while item:
1705            name = self.G2frame.GPXtree.GetItemText(item)
1706            item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1707            if not item2: 
1708                self.OverallParms[name] = self.G2frame.GPXtree.GetItemPyData(item)
1709            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1710        # index powder and single crystal histograms
1711        for hist in self.Histograms:
1712            i = self.Histograms[hist]['hId']
1713            if hist.startswith("PWDR"): 
1714                self.powderDict[i] = hist
1715            elif hist.startswith("HKLF"): 
1716                self.xtalDict[i] = hist
1717
1718    def dumpTree(self,mode='type'):
1719        '''Print out information on the data tree dicts loaded in loadTree.
1720        Used for testing only.
1721        '''
1722        if self.SeqRefdata and self.SeqRefhist:
1723            print('Note that dumpTree does not show sequential results')
1724        print ('\nOverall')
1725        if mode == 'type':
1726            def Show(arg): return type(arg)
1727        else:
1728            def Show(arg): return arg
1729        for key in self.OverallParms:
1730            print ('  '+key+Show(self.OverallParms[key]))
1731        print ('Phases')
1732        for key1 in self.Phases:
1733            print ('    '+key1+Show(self.Phases[key1]))
1734        print ('Histogram')
1735        for key1 in self.Histograms:
1736            print ('    '+key1+Show(self.Histograms[key1]))
1737            for key2 in self.Histograms[key1]:
1738                print ('      '+key2+Show(self.Histograms[key1][key2]))
1739
1740    def defaultSaveFile(self):
1741        return os.path.abspath(
1742            os.path.splitext(self.G2frame.GSASprojectfile
1743                             )[0]+self.extension)
1744       
1745    def askSaveFile(self):
1746        '''Ask the user to supply a file name
1747
1748        :returns: a file name (str) or None if Cancel is pressed
1749        '''
1750       
1751        pth = G2G.GetExportPath(self.G2frame)
1752        defnam = os.path.splitext(
1753            os.path.split(self.G2frame.GSASprojectfile)[1]
1754            )[0]+self.extension
1755        dlg = wx.FileDialog(
1756            self.G2frame, 'Input name for file to write', pth, defnam,
1757            self.longFormatName+' (*'+self.extension+')|*'+self.extension,
1758            wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
1759        dlg.CenterOnParent()
1760        try:
1761            if dlg.ShowModal() == wx.ID_OK:
1762                filename = dlg.GetPath()
1763                self.G2frame.LastExportDir = os.path.split(filename)[0]
1764                filename = os.path.splitext(filename)[0]+self.extension # make sure extension is correct
1765            else:
1766                filename = None
1767        finally:
1768            dlg.Destroy()
1769        return filename
1770
1771    def askSaveDirectory(self):
1772        '''Ask the user to supply a directory name. Path name is used as the
1773        starting point for the next export path search.
1774
1775        :returns: a directory name (str) or None if Cancel is pressed
1776        '''
1777        pth = G2G.GetExportPath(self.G2frame)
1778        dlg = wx.DirDialog(
1779            self.G2frame, 'Input directory where file(s) will be written', pth,
1780            wx.DD_DEFAULT_STYLE)
1781        dlg.CenterOnParent()
1782        try:
1783            if dlg.ShowModal() == wx.ID_OK:
1784                filename = dlg.GetPath()
1785                self.G2frame.LastExportDir = filename
1786            else:
1787                filename = None
1788        finally:
1789            dlg.Destroy()
1790        return filename
1791
1792    # Tools for file writing.
1793    def OpenFile(self,fil=None,mode='w'):
1794        '''Open the output file
1795
1796        :param str fil: The name of the file to open. If None (default)
1797          the name defaults to self.dirname + self.filename.
1798          If an extension is supplied, it is not overridded,
1799          but if not, the default extension is used.
1800        :returns: the file object opened by the routine which is also
1801          saved as self.fp
1802        '''
1803        if mode == 'd': # debug mode
1804            self.fullpath = '(stdout)'
1805            self.fp = sys.stdout
1806            return
1807        if not fil:
1808            if not os.path.splitext(self.filename)[1]:
1809                self.filename += self.extension
1810            fil = os.path.join(self.dirname,self.filename)
1811        self.fullpath = os.path.abspath(fil)
1812        self.fp = open(self.fullpath,mode)
1813        return self.fp
1814
1815    def Write(self,line):
1816        '''write a line of output, attaching a line-end character
1817
1818        :param str line: the text to be written.
1819        '''
1820        self.fp.write(line+'\n')
1821       
1822    def CloseFile(self,fp=None):
1823        '''Close a file opened in OpenFile
1824
1825        :param file fp: the file object to be closed. If None (default)
1826          file object self.fp is closed.
1827        '''
1828        if self.fp == sys.stdout: return # debug mode
1829        if fp is None:
1830            fp = self.fp
1831            self.fp = None
1832        if fp is not None: fp.close()
1833       
1834    def SetSeqRef(self,data,hist):
1835        '''Set the exporter to retrieve results from a sequential refinement
1836        rather than the main tree
1837        '''
1838        self.SeqRefdata = data
1839        self.SeqRefhist = hist
1840        data_name = data[hist]
1841        for i,val in zip(data_name['varyList'],data_name['sig']):
1842            self.sigDict[i] = val
1843            self.sigDict[striphist(i)] = val
1844        for i in data_name['parmDict']:
1845            self.parmDict[striphist(i)] = data_name['parmDict'][i]
1846            self.parmDict[i] = data_name['parmDict'][i]
1847            # zero out the dA[xyz] terms, they would only bring confusion
1848            key = i.split(':')
1849            if len(key) < 3: continue
1850            if key[2].startswith('dA'):
1851                self.parmDict[i] = 0.0
1852        for i,(val,sig) in data_name.get('depParmDict',{}).iteritems():
1853            self.parmDict[i] = val
1854            self.sigDict[i] = sig
1855        #GSASIIpath.IPyBreak()
1856
1857    def SetFromArray(self,hist,histname):
1858        '''Load a histogram into the exporter in preparation for use of the .Writer
1859        rather than the main tree. This is used in GSASIIscriptable when wx
1860        is not present.
1861        '''
1862        self.Histograms[histname] =  {}
1863        self.Histograms[histname]['Data'] = hist['data'][1]
1864        self.Histograms[histname]['Instrument Parameters'] = hist['Instrument Parameters']
1865        self.Histograms[histname]['Sample Parameters'] = hist['Sample Parameters']
1866
1867    # Tools to pull information out of the data arrays
1868    def GetCell(self,phasenam):
1869        """Gets the unit cell parameters and their s.u.'s for a selected phase
1870
1871        :param str phasenam: the name for the selected phase
1872        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1873          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1874          cell values and `cellSig` has their uncertainties.
1875        """
1876        if self.SeqRefdata and self.SeqRefhist:
1877            return self.GetSeqCell(phasenam,self.SeqRefdata[self.SeqRefhist])
1878        phasedict = self.Phases[phasenam] # pointer to current phase info
1879        try:
1880            pfx = str(phasedict['pId'])+'::'
1881            A,sigA = G2stIO.cellFill(pfx,phasedict['General']['SGData'],self.parmDict,self.sigDict)
1882            cellSig = G2stIO.getCellEsd(pfx,phasedict['General']['SGData'],A,
1883                self.OverallParms['Covariance'])  # returns 7 vals, includes sigVol
1884            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
1885            return cellList,cellSig
1886        except KeyError:
1887            cell = phasedict['General']['Cell'][1:]
1888            return cell,7*[0]
1889           
1890    def GetSeqCell(self,phasenam,data_name):
1891        """Gets the unit cell parameters and their s.u.'s for a selected phase
1892        and histogram in a sequential fit
1893
1894        :param str phasenam: the name for the selected phase
1895        :param dict data_name: the sequential refinement parameters for the selected histogram
1896        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1897          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1898          cell values and `cellSig` has their uncertainties.
1899        """
1900        phasedict = self.Phases[phasenam]
1901        SGdata = phasedict['General']['SGData']
1902        pId = phasedict['pId']
1903        RecpCellTerms = G2lat.cell2A(phasedict['General']['Cell'][1:7])
1904        ESDlookup = {}
1905        Dlookup = {}
1906        varied = [striphist(i) for i in data_name['varyList']]
1907        for item,val in data_name['newCellDict'].iteritems():
1908            if item in varied:
1909                ESDlookup[val[0]] = item
1910                Dlookup[item] = val[0]
1911        A = RecpCellTerms[:]
1912        for i in range(6):
1913            var = str(pId)+'::A'+str(i)
1914            if var in ESDlookup:
1915                A[i] = data_name['newCellDict'][ESDlookup[var]][1] # override with refined value
1916        cellDict = dict(zip([str(pId)+'::A'+str(i) for i in range(6)],A))
1917        zeroDict = {i:0.0 for i in cellDict}
1918        A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata,cellDict,zeroDict)
1919        covData = {
1920            'varyList': [Dlookup.get(striphist(v),v) for v in data_name['varyList']],
1921            'covMatrix': data_name['covMatrix']
1922            }
1923        return list(G2lat.A2cell(A)) + [G2lat.calc_V(A)], G2stIO.getCellEsd(str(pId)+'::',SGdata,A,covData)
1924               
1925    def GetAtoms(self,phasenam):
1926        """Gets the atoms associated with a phase. Can be used with standard
1927        or macromolecular phases
1928
1929        :param str phasenam: the name for the selected phase
1930        :returns: a list of items for eac atom where each item is a list containing:
1931          label, typ, mult, xyz, and td, where
1932
1933          * label and typ are the atom label and the scattering factor type (str)
1934          * mult is the site multiplicity (int)
1935          * xyz is contains a list with four pairs of numbers:
1936            x, y, z and fractional occupancy and
1937            their standard uncertainty (or a negative value)
1938          * td is contains a list with either one or six pairs of numbers:
1939            if one number it is U\ :sub:`iso` and with six numbers it is
1940            U\ :sub:`11`, U\ :sub:`22`, U\ :sub:`33`, U\ :sub:`12`, U\ :sub:`13` & U\ :sub:`23`
1941            paired with their standard uncertainty (or a negative value)
1942        """
1943        phasedict = self.Phases[phasenam] # pointer to current phase info           
1944        cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1945        cfrac = cx+3
1946        fpfx = str(phasedict['pId'])+'::Afrac:'       
1947        atomslist = []
1948        for i,at in enumerate(phasedict['Atoms']):
1949            if phasedict['General']['Type'] == 'macromolecular':
1950                label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
1951            else:
1952                label = at[ct-1]
1953            fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1954            fsig = self.sigDict.get(fpfx+str(i),-0.009)
1955            mult = at[cs+1]
1956            typ = at[ct]
1957            xyz = []
1958            for j,v in enumerate(('x','y','z')):
1959                val = at[cx+j]
1960                pfx = str(phasedict['pId']) + '::A' + v + ':' + str(i)
1961                val = self.parmDict.get(pfx, val)
1962                dpfx = str(phasedict['pId'])+'::dA'+v+':'+str(i)
1963                sig = self.sigDict.get(dpfx,-0.000009)
1964                xyz.append((val,sig))
1965            xyz.append((fval,fsig))
1966            td = []
1967            if at[cia] == 'I':
1968                pfx = str(phasedict['pId'])+'::AUiso:'+str(i)
1969                val = self.parmDict.get(pfx,at[cia+1])
1970                sig = self.sigDict.get(pfx,-0.0009)
1971                td.append((val,sig))
1972            else:
1973                for i,var in enumerate(('AU11','AU22','AU33','AU12','AU13','AU23')):
1974                    pfx = str(phasedict['pId'])+'::'+var+':'+str(i)
1975                    val = self.parmDict.get(pfx,at[cia+2+i])
1976                    sig = self.sigDict.get(pfx,-0.0009)
1977                    td.append((val,sig))
1978            atomslist.append((label,typ,mult,xyz,td))
1979        return atomslist
1980######################################################################
1981def ExportPowderList(G2frame):
1982    '''Returns a list of extensions supported by :func:`GSASIIIO:ExportPowder`
1983    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
1984   
1985    :param wx.Frame G2frame: the GSAS-II main data tree window
1986    '''
1987    extList = []
1988    for obj in G2frame.exporterlist:
1989        if 'powder' in obj.exporttype:
1990            try:
1991                obj.Writer
1992                extList.append(obj.extension)
1993            except AttributeError:
1994                pass
1995    return extList
1996
1997def ExportPowder(G2frame,TreeName,fileroot,extension):
1998    '''Writes a single powder histogram using the Export routines.
1999    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
2000
2001    :param wx.Frame G2frame: the GSAS-II main data tree window
2002    :param str TreeName: the name of the histogram (PWDR ...) in the data tree
2003    :param str fileroot: name for file to be written, extension ignored
2004    :param str extension: extension for file to be written (start with '.'). Must
2005      match a powder export routine that has a Writer object.
2006    '''
2007    filename = os.path.abspath(os.path.splitext(fileroot)[0]+extension)
2008    for obj in G2frame.exporterlist:
2009        if obj.extension == extension and 'powder' in obj.exporttype:
2010            obj.currentExportType = 'powder'
2011            obj.InitExport(None)
2012            obj.loadTree() # load all histograms in tree into dicts
2013            if TreeName not in obj.Histograms:
2014                raise Exception('Histogram not found: '+str(TreeName))
2015            try:
2016                obj.Writer
2017            except AttributeError:
2018                continue
2019            try:
2020                obj.Writer(TreeName,filename)
2021                print('wrote file '+filename)
2022                return
2023            except Exception:
2024                print('Export Routine for '+extension+' failed.')
2025    else:
2026        print('No Export routine supports extension '+extension)
2027
2028def ExportSequential(G2frame,data,obj,exporttype):
2029    '''
2030    Used to export from every phase/dataset in a sequential refinement using
2031    a .Writer method for either projects or phases. Prompts to select histograms
2032    and for phase exports, which phase(s).
2033
2034    :param wx.Frame G2frame: the GSAS-II main data tree window
2035    :param dict data: the sequential refinement data object
2036    :param str exporttype: indicates the type of export ('project' or 'phase')
2037    '''
2038    if len(data['histNames']) == 0:
2039        G2G.G2MessageBox(G2frame,'There are no sequential histograms','Warning')
2040    obj.InitExport(None)
2041    obj.loadTree()
2042    obj.loadParmDict()
2043    if len(data['histNames']) == 1:
2044        histlist = data['histNames']
2045    else:
2046        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select histograms to export from list',
2047                                 'Select histograms',data['histNames'])
2048        if dlg.ShowModal() == wx.ID_OK:
2049            histlist = [data['histNames'][l] for l in dlg.GetSelections()]
2050            dlg.Destroy()
2051        else:
2052            dlg.Destroy()
2053            return
2054    if exporttype == 'Phase':
2055        phaselist = list(obj.Phases.keys())
2056        if len(obj.Phases) == 0:
2057            G2G.G2MessageBox(G2frame,'There are no phases in sequential ref.','Warning')
2058            return
2059        elif len(obj.Phases) > 1:
2060            dlg = G2G.G2MultiChoiceDialog(G2frame,'Select phases to export from list',
2061                                    'Select phases', phaselist)
2062            if dlg.ShowModal() == wx.ID_OK:
2063                phaselist = [phaselist[l] for l in dlg.GetSelections()]
2064                dlg.Destroy()
2065            else:
2066                dlg.Destroy()
2067                return
2068        filename = obj.askSaveFile()
2069        if not filename: return True
2070        obj.dirname,obj.filename = os.path.split(filename)
2071        print('Writing output to file '+str(obj.filename)+"...")
2072        mode = 'w'
2073        for p in phaselist:
2074            for h in histlist:
2075                obj.SetSeqRef(data,h)
2076                #GSASIIpath.IPyBreak()
2077                obj.Writer(h,phasenam=p,mode=mode)
2078                mode = 'a'
2079        print('...done')
2080    elif exporttype == 'Project':  # note that the CIF exporter is not yet ready for this
2081        filename = obj.askSaveFile()
2082        if not filename: return True
2083        obj.dirname,obj.filename = os.path.split(filename)
2084        print('Writing output to file '+str(obj.filename)+"...")
2085        mode = 'w'
2086        for h in histlist:
2087            obj.SetSeqRef(data,h)
2088            obj.Writer(h,mode=mode)
2089            print('\t'+str(h)+' written')
2090            mode = 'a'
2091        print('...done')
2092    elif exporttype == 'Powder':
2093        filename = obj.askSaveFile()
2094        if not filename: return True
2095        obj.dirname,obj.filename = os.path.split(filename)
2096        print('Writing output to file '+str(obj.filename)+"...")
2097        mode = 'w'
2098        for h in histlist:
2099            obj.SetSeqRef(data,h)
2100            obj.Writer(h,mode=mode)
2101            print('\t'+str(h)+' written')
2102            mode = 'a'
2103        print('...done')
2104
2105def ReadDIFFaX(DIFFaXfile):
2106    print ('read '+DIFFaXfile)
2107    Layer = {'Laue':'-1','Cell':[False,1.,1.,1.,90.,90.,90,1.],'Width':[[10.,10.],[False,False]],
2108        'Layers':[],'Stacking':[],'Transitions':[],'Toler':0.01,'AtInfo':{}}
2109    df = open(DIFFaXfile,'r')
2110    lines = df.readlines()
2111    df.close()
2112    struct = False
2113    Struct = []
2114    stack = False
2115    Stack = []
2116    trans = False
2117    Trans = []
2118    for diff in lines:
2119        diff = diff[:-1].lower()
2120        if '!'  in diff:
2121            continue
2122        while '}' in diff: #strip comments
2123            iB = diff.index('{')
2124            iF = diff.index('}')+1
2125            if iB:
2126                diff = diff[:iB]
2127            else:
2128                diff = diff[iF:]
2129        if not diff:
2130            continue
2131        if diff.strip() == 'instrumental':
2132            continue
2133        if diff.strip() == 'structural':
2134            struct = True
2135            continue
2136        elif diff.strip() == 'stacking':
2137            struct = False
2138            stack = True
2139            continue
2140        elif diff.strip() == 'transitions':
2141            stack = False
2142            trans = True
2143            continue
2144        diff = diff.strip()
2145        if struct:
2146            if diff:
2147                Struct.append(diff)
2148        elif stack:
2149            if diff:
2150                Stack.append(diff)
2151        elif trans:
2152            if diff:
2153                Trans.append(diff)
2154   
2155#STRUCTURE records
2156    laueRec = Struct[1].split()
2157    Layer['Laue'] = laueRec[0]
2158    if Layer['Laue'] == 'unknown' and len(laueRec) > 1:
2159        Layer['Toler'] = float(laueRec[1])    #tolerance for 'unknown'?
2160    if Layer['Laue'] == '2/m(1)': Layer['Laue'] = '2/m(c)'
2161    if Layer['Laue'] == '2/m(2)': Layer['Laue'] = '2/m(ab)'
2162    cell = Struct[0].split()
2163    Layer['Cell'] = [False,float(cell[0]),float(cell[1]),float(cell[2]),90.,90.,float(cell[3]),1.0]
2164    nLayers = int(Struct[2])
2165    N = 3
2166    if 'layer' not in Struct[3]:
2167        N = 4
2168        if Struct[3] != 'infinite':
2169            width = Struct[3].split()
2170            Layer['Width'][0] = [float(width[0]),float(width[1])]
2171    for nL in range(nLayers):
2172        if '=' in Struct[N]:
2173            name = Struct[N].split('=')
2174            sameas = int(name[1])-1
2175            Layer['Layers'].append({'Name':name[0],'SameAs':Layer['Layers'][sameas]['Name'],'Symm':'None','Atoms':[]})
2176            N += 1
2177            continue
2178        Symm = 'None'
2179        if 'centro' in Struct[N+1]: Symm = '-1'
2180        Layer['Layers'].append({'Name':Struct[N],'SameAs':'','Symm':Symm,'Atoms':[]})
2181        N += 2
2182        while 'layer' not in Struct[N]:
2183            atom = Struct[N][4:].split()
2184            atomType = G2el.FixValence(Struct[N][:4].replace(' ','').strip().capitalize())
2185            if atomType not in Layer['AtInfo']:
2186                Layer['AtInfo'][atomType] = G2el.GetAtomInfo(atomType)
2187            atomName = '%s(%s)'%(atomType,atom[0])
2188            newVals = []
2189            for val in atom[1:6]:
2190                if '/' in val:
2191                    newVals.append(eval(val+'.'))
2192                else:
2193                    newVals.append(float(val))               
2194            atomRec = [atomName,atomType,newVals[0],newVals[1],newVals[2],newVals[4],newVals[3]/78.9568]
2195            Layer['Layers'][-1]['Atoms'].append(atomRec)
2196            N += 1
2197            if N > len(Struct)-1:
2198                break
2199#TRANSITIONS records
2200    transArray = []
2201    N = 0
2202    for i in range(nLayers):
2203        transArray.append([])
2204        for j in range(nLayers):
2205            vals = Trans[N].split()
2206            newVals = []
2207            for val in vals[:4]:
2208                if '/' in val:
2209                    newVals.append(eval(val+'.'))
2210                else:
2211                    newVals.append(float(val))
2212            transArray[-1].append(newVals+['',False])
2213            N += 1
2214    Layer['Transitions'] = transArray
2215#STACKING records
2216    Layer['Stacking'] = [Stack[0],'']
2217    if Stack[0] == 'recursive':
2218        Layer['Stacking'][1] = Stack[1]
2219    elif Stack[0] == 'explicit':
2220        if Stack[1] == 'random':
2221            Layer['Stacking'][1] = Stack[1]
2222        else:
2223            Layer['Stacking'][1] = 'list'
2224            Layer['Stacking'].append('')
2225            for stack in Stack[2:]:
2226                Layer['Stacking'][2] += ' '+stack
2227    return Layer
2228
2229def readColMetadata(imagefile):
2230    '''Reads image metadata from a column-oriented metadata table
2231    (1-ID style .par file). Called by :func:`GetColumnMetadata`
2232   
2233    The .par file has any number of columns separated by spaces.
2234    The directory for the file must be specified in
2235    Config variable ``Column_Metadata_directory``.
2236    As an index to the .par file a second "label file" must be specified with the
2237    same file root name as the .par file but the extension must be .XXX_lbls (where
2238    .XXX is the extension of the image) or if that is not present extension
2239    .lbls.
2240
2241    :param str imagefile: the full name of the image file (with extension, directory optional)
2242
2243    :returns: a dict with parameter values. Named parameters will have the type based on
2244       the specified Python function, named columns will be character strings
2245   
2246    The contents of the label file will look like this::
2247   
2248        # define keywords
2249        filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34
2250        distance: float | 75
2251        wavelength:lambda keV: 12.398425/float(keV)|9
2252        pixelSize:lambda x: [74.8, 74.8]|0
2253        ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4
2254        Temperature: float|53
2255        FreePrm2: int | 34 | Free Parm2 Label
2256        # define other variables
2257        0:day
2258        1:month
2259        2:date
2260        3:time
2261        4:year
2262        7:I_ring
2263
2264    This file contains three types of lines in any order.
2265     * Named parameters are evaluated with user-supplied Python code (see
2266       subsequent information). Specific named parameters are used
2267       to determine values that are used for image interpretation (see table,
2268       below). Any others are copied to the Comments subsection of the Image
2269       tree item.
2270     * Column labels are defined with a column number (integer) followed by
2271       a colon (:) and a label to be assigned to that column. All labeled
2272       columns are copied to the Image's Comments subsection.
2273     * Comments are any line that does not contain a colon.
2274
2275    Note that columns are numbered starting at zero.
2276
2277    Any named parameter may be defined provided it is not a valid integer,
2278    but the named parameters in the table have special meanings, as descibed.
2279    The parameter name is followed by a colon. After the colon, specify
2280    Python code that defines or specifies a function that will be called to
2281    generate a value for that parameter.
2282
2283    Note that several keywords, if defined in the Comments, will be found and
2284    placed in the appropriate section of the powder histogram(s)'s Sample
2285    Parameters after an integration: ``Temperature``,``Pressure``,``Time``,
2286    ``FreePrm1``,``FreePrm2``,``FreePrm3``,``Omega``,``Chi``, and ``Phi``.
2287
2288    After the Python code, supply a vertical bar (|) and then a list of one
2289    more more columns that will be supplied as arguments to that function.
2290
2291    Note that the labels for the three FreePrm items can be changed by
2292    including that label as a third item with an additional vertical bar. Labels
2293    will be ignored for any other named parameters.
2294   
2295    The examples above are discussed here:
2296
2297    ``filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34``
2298        Here the function to be used is defined with a lambda statement::
2299       
2300          lambda x,y: "{}_{:0>6}".format(x,y)
2301
2302        This function will use the format function to create a file name from the
2303        contents of columns 33 and 34. The first parameter (x, col. 33) is inserted directly into
2304        the file name, followed by a underscore (_), followed by the second parameter (y, col. 34),
2305        which will be left-padded with zeros to six characters (format directive ``:0>6``).
2306
2307        When there will be more than one image generated per line in the .par file, an alternate way to
2308        generate list of file names takes into account the number of images generated::
2309
2310          lambda x,y,z: ["{}_{:0>6}".format(x,int(y)+i) for i in range(int(z))]
2311
2312        Here a third parameter is used to specify the number of images generated, where
2313        the image number is incremented for each image.
2314         
2315    ``distance: float | 75``
2316        Here the contents of column 75 will be converted to a floating point number
2317        by calling float on it. Note that the spaces here are ignored.
2318       
2319    ``wavelength:lambda keV: 12.398425/float(keV)|9``
2320        Here we define an algebraic expression to convert an energy in keV to a
2321        wavelength and pass the contents of column 9 as that input energy
2322       
2323    ``pixelSize:lambda x: [74.8, 74.8]|0``
2324        In this case the pixel size is a constant (a list of two numbers). The first
2325        column is passed as an argument as at least one argument is required, but that
2326        value is not used in the expression.
2327
2328    ``ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4``
2329        This example defines a parameter that takes items in the first five columns
2330        and formats them in a different way. This parameter is not one of the pre-defined
2331        parameter names below. Some external code could be used to change the month string
2332        (argument ``m``) to a integer from 1 to 12.
2333       
2334    ``FreePrm2: int | 34 | Free Parm2 Label``
2335        In this example, the contents of column 34 will be converted to an integer and
2336        placed as the second free-named parameter in the Sample Parameters after an
2337        integration. The label for this parameter will be changed to "Free Parm2 Label".
2338           
2339    **Pre-defined parameter names**
2340   
2341    =============  =========  ========  =====================================================
2342     keyword       required    type      Description
2343    =============  =========  ========  =====================================================
2344       filename    yes         str or   generates the file name prefix for the matching image
2345                               list     file (MyImage001 for file /tmp/MyImage001.tif) or
2346                                        a list of file names.
2347     polarization  no         float     generates the polarization expected based on the
2348                                        monochromator angle, defaults to 0.99.
2349       center      no         list of   generates the approximate beam center on the detector
2350                              2 floats  in mm, such as [204.8, 204.8].
2351       distance    yes        float     generates the distance from the sample to the detector
2352                                        in mm
2353       pixelSize   no         list of   generates the size of the pixels in microns such as
2354                              2 floats  [200.0, 200.0].
2355       wavelength  yes        float     generates the wavelength in Angstroms
2356    =============  =========  ========  =====================================================
2357   
2358    '''
2359    dir,fil = os.path.split(os.path.abspath(imagefile))
2360    imageName,ext = os.path.splitext(fil)
2361    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
2362    parfiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
2363    if len(parfiles) == 0:
2364        print('Sorry, No Column metadata (.par) file found in '+
2365              GSASIIpath.GetConfigValue('Column_Metadata_directory'))
2366        return {}
2367    for parFil in parfiles: # loop over all .par files (hope just 1) in image dir until image is found
2368        parRoot = os.path.splitext(parFil)[0]
2369        for e in (ext+'_lbls','.lbls'):
2370            if os.path.exists(parRoot+e):
2371                lblFil = parRoot+e
2372                break
2373        else:
2374            print('Warning: No labels definitions found for '+parFil)
2375            continue
2376        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFil)
2377        if errors:
2378            print('Errors in labels file '+lblFil)
2379            for i in errors: print('  '+i)
2380            continue
2381        else:
2382            print('Read '+lblFil)
2383        # scan through each line in this .par file, looking for the matching image rootname
2384        fp = open(parFil,'Ur')
2385        for iline,line in enumerate(fp):
2386            items = line.strip().split(' ')
2387            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
2388            if type(nameList) is str:
2389                if nameList != imageName: continue
2390                name = nameList
2391            else:
2392                for name in nameList:
2393                    if name == imageName: break # got a match
2394                else:
2395                    continue
2396            # parse the line and finish
2397            metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp)
2398            metadata['par file'] = parFil
2399            metadata['lbls file'] = lblFil
2400            print("Metadata read from {} line {}".format(parFil,iline+1))
2401            fp.close()
2402            return metadata
2403        else:
2404            print("Image {} not found in {}".format(imageName,parFil))
2405            fp.close()
2406            continue
2407        fp.close()
2408    else:
2409        print("Warning: No .par metadata for image {}".format(imageName))
2410        return {}
2411
2412def readColMetadataLabels(lblFil):
2413    '''Read the .*lbls file and setup for metadata assignments
2414    '''
2415    lbldict = {}
2416    keyExp = {}
2417    keyCols = {}
2418    labels = {}
2419    errors = []
2420    fp = open(lblFil,'Ur')         # read column labels
2421    for iline,line in enumerate(fp): # read label definitions
2422        line = line.strip()
2423        if not line or line[0] == '#': continue # comments
2424        items = line.split(':')
2425        if len(items) < 2: continue # lines with no colon are also comments
2426        # does this line a definition for a named parameter?
2427        key = items[0]
2428        try: 
2429            int(key)
2430        except ValueError: # try as named parameter since not a valid number
2431            items = line.split(':',1)[1].split('|')
2432            try:
2433                f = eval(items[0]) # compile the expression
2434                if not callable(f):
2435                    errors += ['Expression "{}" for key {} is not a function (line {})'.
2436                           format(items[0],key,iline)]
2437                    continue
2438                keyExp[key] = f
2439            except Exception as msg:
2440                errors += ['Expression "{}" for key {} is not valid (line {})'.
2441                           format(items[0],key,iline)]
2442                errors += [str(msg)]
2443                continue
2444            keyCols[key] = [int(i) for i in items[1].strip().split(',')]
2445            if key.lower().startswith('freeprm') and len(items) > 2:
2446                labels[key] = items[2]
2447            continue
2448        if len(items) == 2: # simple column definition
2449            lbldict[int(items[0])] = items[1]
2450    fp.close()
2451    if 'filename' not in keyExp:
2452        errors += ["File {} is invalid. No valid filename expression.".format(lblFil)]
2453    return labels,lbldict,keyCols,keyExp,errors
2454
2455def evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,ShowError=False):
2456    '''Evaluate the metadata for a line in the .par file
2457    '''
2458    metadata = {lbldict[j]:items[j] for j in lbldict}
2459    named = {}
2460    for key in keyExp:
2461        try:
2462            res = keyExp[key](*[items[j] for j in keyCols[key]])
2463        except:
2464            if ShowError:
2465                res = "*** error ***"
2466            else:
2467                continue
2468        named[key] = res
2469    metadata.update(named)
2470    for lbl in labels: # add labels for FreePrm's
2471        metadata['label_'+lbl[4:].lower()] = labels[lbl]
2472    return metadata
2473
2474def GetColumnMetadata(reader):
2475    '''Add metadata to an image from a column-type metadata file
2476    using :func:`readColMetadata`
2477   
2478    :param reader: a reader object from reading an image
2479   
2480    '''
2481    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
2482    parParms = readColMetadata(reader.readfilename)
2483    if not parParms: return # check for read failure
2484    specialKeys = ('filename',"polarization", "center", "distance", "pixelSize", "wavelength",)
2485    reader.Comments = ['Metadata from {} assigned by {}'.format(parParms['par file'],parParms['lbls file'])]
2486    for key in parParms:
2487        if key in specialKeys+('par file','lbls file'): continue
2488        reader.Comments += ["{} = {}".format(key,parParms[key])]
2489    if "polarization" in parParms:
2490        reader.Data['PolaVal'][0] = parParms["polarization"]
2491    else:
2492        reader.Data['PolaVal'][0] = 0.99
2493    if "center" in parParms:
2494        reader.Data['center'] = parParms["center"]
2495    if "pixelSize" in parParms:
2496        reader.Data['pixelSize'] = parParms["pixelSize"]
2497    if "wavelength" in parParms:
2498        reader.Data['wavelength'] = parParms['wavelength']
2499    else:
2500        print('Error: wavelength not defined in {}'.format(parParms['lbls file']))
2501    if "distance" in parParms:
2502        reader.Data['distance'] = parParms['distance']
2503        reader.Data['setdist'] = parParms['distance']
2504    else:
2505        print('Error: distance not defined in {}'.format(parParms['lbls file']))
2506
2507def testColumnMetadata(G2frame):
2508    '''Test the column-oriented metadata parsing, as implemented at 1-ID, by showing results
2509    when using a .par and .lbls pair.
2510   
2511     * Select a .par file, if more than one in selected dir.
2512     * Select the .*lbls file, if more than one matching .par file.
2513     * Parse the .lbls file, showing errors if encountered; loop until errors are fixed.
2514     * Search for an image or a line in the .par file and show the results when interpreted
2515     
2516    See :func:`readColMetadata` for more details.
2517    '''
2518    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'):
2519        G2G.G2MessageBox(G2frame,'The configuration option for I-ID Metadata is not set.\n'+
2520                         'Please use the File/Preferences menu to set Column_Metadata_directory',
2521                         'Warning')
2522        return
2523    parFiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
2524    if not parFiles: 
2525        G2G.G2MessageBox(G2frame,'No .par files found in directory {}. '
2526                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
2527                         '\nThis is set by config variable Column_Metadata_directory '+
2528                         '(Set in File/Preferences menu).',
2529                         'Warning')
2530        return
2531    parList = []
2532    for parFile in parFiles:
2533        lblList = []
2534        parRoot = os.path.splitext(parFile)[0]
2535        for f in glob.glob(parRoot+'.*lbls'):
2536            if os.path.exists(f): lblList.append(f)
2537        if not len(lblList):
2538            continue
2539        parList.append(parFile)
2540    if len(parList) == 0:
2541        G2G.G2MessageBox(G2frame,'No .lbls or .EXT_lbls file found for .par file(s) in directory {}. '
2542                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
2543                         '\nThis is set by config variable Column_Metadata_directory '+
2544                         '(Set in File/Preferences menu).',
2545                         'Warning')
2546        return
2547    elif len(parList) == 1:
2548        parFile = parList[0]
2549    else:
2550        dlg = G2G.G2SingleChoiceDialog(G2frame,
2551                'More than 1 .par file found. (Better if only 1!). Choose the one to test in '+
2552                GSASIIpath.GetConfigValue('Column_Metadata_directory'),
2553                'Choose .par file', [os.path.split(i)[1] for i in parList])
2554        if dlg.ShowModal() == wx.ID_OK:
2555            parFile = parList[dlg.GetSelection()]
2556            dlg.Destroy()
2557        else:
2558            dlg.Destroy()
2559            return
2560    # got .par file; now work on .*lbls file
2561    lblList = []
2562    parRoot = os.path.splitext(parFile)[0]
2563    for f in glob.glob(parRoot+'.*lbls'):
2564        if os.path.exists(f): lblList.append(f)
2565    if not len(lblList):
2566        raise Exception('How did this happen! No .*lbls files for '+parFile)
2567    elif len(lblList) == 1:
2568        lblFile = lblList[0]
2569    else:
2570        dlg = G2G.G2SingleChoiceDialog(G2frame,
2571                'Select label file for .par file '+parFile,
2572                'Choose label file', [os.path.split(i)[1] for i in lblList])
2573        if dlg.ShowModal() == wx.ID_OK:
2574            lblFile = lblList[dlg.GetSelection()]
2575            dlg.Destroy()
2576        else:
2577            dlg.Destroy()
2578            return
2579    # parse the labels file
2580    errors = True
2581    while errors:
2582        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFile)
2583        if errors:
2584            t = "Error reading file "+lblFile
2585            for l in errors:
2586                t += '\n'
2587                t += l
2588            t += "\n\nPlease edit the file and press OK (or Cancel to quit)"
2589            dlg = wx.MessageDialog(G2frame,message=t,
2590                caption="Read Error",style=wx.ICON_ERROR| wx.OK|wx.STAY_ON_TOP|wx.CANCEL)
2591            if dlg.ShowModal() != wx.ID_OK: return           
2592    # request a line number, read that line
2593    dlg = G2G.SingleStringDialog(G2frame,'Read what',
2594                                 'Enter a line number or an image file name (-1=last line)',
2595                                 '-1',size=(400,-1))
2596    if dlg.Show():
2597        fileorline = dlg.GetValue()
2598        dlg.Destroy()
2599    else:
2600        dlg.Destroy()
2601        return
2602    # and report the generated key pairs in metadata dict
2603    linenum = None
2604    try:
2605        linenum = int(fileorline)
2606    except:
2607        imageName = os.path.splitext(os.path.split(fileorline)[1])[0]
2608
2609    fp = open(parFile,'Ur')
2610    for iline,line in enumerate(fp):
2611        if linenum is not None:
2612            if iline == linenum:
2613                items = line.strip().split(' ')
2614                n = "Line {}".format(iline)
2615                break
2616            else:
2617                continue
2618        else:
2619            items = line.strip().split(' ')
2620            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
2621            if type(nameList) is str:
2622                if nameList != imageName: continue
2623                name = nameList
2624                break
2625            else:
2626                for name in nameList:
2627                    print (name,name == imageName)
2628                    if name == imageName:
2629                        n = "Image {} found in line {}".format(imageName,iline)
2630                        break # got a match
2631                else:
2632                    continue
2633                break
2634    else:
2635        if linenum is not None:
2636            n = "Line {}".format(iline)
2637        else:
2638            n = "Image {} not found. Reporting line {}".format(imageName,iline)
2639        items = line.strip().split(' ')
2640    fp.close()
2641    metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,True)
2642    title = "Results: ("+n+")"
2643    t = ['Files: '+parFile,lblFile,' ']
2644    n = ["Named parameters:"]
2645    l = ['',"Labeled columns:"]
2646    for key in sorted(metadata):
2647        if key == "filename" or key.startswith('label_prm'): continue
2648        if key in keyCols:
2649            n += [{} = {}".format(key,metadata[key])]
2650        elif key in lbldict.values():
2651            l += [{} = {}".format(key,metadata[key])]
2652        else:
2653            t += ["** Unexpected:  {}".format(key,metadata[key])]
2654    if type(metadata['filename']) is str:
2655        l += ["","Filename: "+ metadata['filename']]
2656    else:
2657        l += ["","Filename(s): "]
2658        for i,j in enumerate(metadata['filename']):
2659            if i: l[-1] += ', '
2660            l[-1] += j
2661    t += n + l + ['','Unused columns:']
2662    usedCols = lbldict.keys()
2663    for i in keyCols.values(): usedCols += i
2664    for i in range(len(items)):
2665        if i in usedCols: continue
2666        t += [{}: {}".format(i,items[i])]
2667    dlg = G2G.G2SingleChoiceDialog(None,title,'Column metadata parse results',t,
2668                                   monoFont=True,filterBox=False,size=(400,600),
2669                                   style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE|wx.OK)
2670    dlg.ShowModal()
2671
2672if __name__ == '__main__':
2673    import GSASIIdataGUI
2674    application = GSASIIdataGUI.GSASIImain(0)
2675    G2frame = application.main
2676    #app = wx.PySimpleApp()
2677    #G2frame = wx.Frame(None) # create a frame
2678    #frm.Show(True)
2679    #filename = '/tmp/notzip.zip'
2680    #filename = '/tmp/all.zip'
2681    #filename = '/tmp/11bmb_7652.zip'
2682   
2683    #selection=None, confirmoverwrite=True, parent=None
2684    #print ExtractFileFromZip(filename, selection='11bmb_7652.fxye',parent=frm)
2685    #print ExtractFileFromZip(filename,multipleselect=True)
2686    #                         #confirmread=False, confirmoverwrite=False)
2687
2688    # choicelist=[ ('a','b','c'),
2689    #              ('test1','test2'),('no choice',)]
2690    # titles = [ 'a, b or c', 'tests', 'No option here']
2691    # dlg = MultipleChoicesDialog(
2692    #     choicelist,titles,
2693    #     parent=frm)
2694    # if dlg.ShowModal() == wx.ID_OK:
2695    #     print 'Got OK'
2696    imagefile = '/tmp/NDC5_00237_3.ge3'
2697    Comments, Data, Npix, Image = GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None)
2698
2699    print("\n\nResults loaded to Comments, Data, Npix and Image\n\n")
2700
2701    GSASIIpath.IPyBreak_base()
Note: See TracBrowser for help on using the repository browser.