source: trunk/GSASIIIO.py @ 3806

Last change on this file since 3806 was 3806, checked in by toby, 4 years ago

redo parameter lookup to provide numerical deriv step size (see G2obj.getVarStep)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 114.9 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2019-01-30 23:15:41 +0000 (Wed, 30 Jan 2019) $
4# $Author: toby $
5# $Revision: 3806 $
6# $URL: trunk/GSASIIIO.py $
7# $Id: GSASIIIO.py 3806 2019-01-30 23:15:41Z 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 numpy.ma as ma
34
35import copy
36import platform
37if '2' in platform.python_version_tuple()[0]:
38    import cPickle
39else:
40    import _pickle as cPickle
41import sys
42import re
43import glob
44import random as ran
45import GSASIIpath
46GSASIIpath.SetVersionNumber("$Revision: 3806 $")
47try:
48    import GSASIIdataGUI as G2gd
49except ImportError:
50    pass
51import GSASIIobj as G2obj
52import GSASIIlattice as G2lat
53import GSASIImath as G2mth
54try:
55    import GSASIIpwdGUI as G2pdG
56    import GSASIIimgGUI as G2imG
57except ImportError:
58    pass
59import GSASIIimage as G2img
60import GSASIIElem as G2el
61import GSASIIstrIO as G2stIO
62import GSASIImapvars as G2mv
63try:
64    import GSASIIctrlGUI as G2G
65except ImportError:
66    pass
67import os
68import os.path as ospath
69
70DEBUG = False       #=True for various prints
71TRANSP = False      #=true to transpose images for testing
72if GSASIIpath.GetConfigValue('Transpose'): TRANSP = True
73npsind = lambda x: np.sin(x*np.pi/180.)
74
75def sfloat(S):
76    'Convert a string to float. An empty field or a unconvertable value is treated as zero'
77    if S.strip():
78        try:
79            return float(S)
80        except ValueError:
81            pass
82    return 0.0
83
84def sint(S):
85    'Convert a string to int. An empty field is treated as zero'
86    if S.strip():
87        return int(S)
88    else:
89        return 0
90
91def trim(val):
92    '''Simplify a string containing leading and trailing spaces
93    as well as newlines, tabs, repeated spaces etc. into a shorter and
94    more simple string, by replacing all ranges of whitespace
95    characters with a single space.
96
97    :param str val: the string to be simplified
98
99    :returns: the (usually) shortened version of the string
100    '''
101    return re.sub('\s+', ' ', val).strip()
102
103def FileDlgFixExt(dlg,file):
104    'this is needed to fix a problem in linux wx.FileDialog'
105    ext = dlg.GetWildcard().split('|')[2*dlg.GetFilterIndex()+1].strip('*')
106    if ext not in file:
107        file += ext
108    return file
109       
110def GetPowderPeaks(fileName):
111    'Read powder peaks from a file'
112    sind = lambda x: math.sin(x*math.pi/180.)
113    asind = lambda x: 180.*math.asin(x)/math.pi
114    wave = 1.54052
115    File = open(fileName,'Ur')
116    Comments = []
117    peaks = []
118    S = File.readline()
119    while S:
120        if S[:1] == '#':
121            Comments.append(S[:-1])
122        else:
123            item = S.split()
124            if len(item) == 1:
125                peaks.append([float(item[0]),1.0])
126            elif len(item) > 1:
127                peaks.append([float(item[0]),float(item[0])])
128        S = File.readline()
129    File.close()
130    if Comments:
131       print ('Comments on file:')
132       for Comment in Comments: 
133            print (Comment)
134            if 'wavelength' in Comment:
135                wave = float(Comment.split('=')[1])
136    Peaks = []
137    if peaks[0][0] > peaks[-1][0]:          # d-spacings - assume CuKa
138        for peak in peaks:
139            dsp = peak[0]
140            sth = wave/(2.0*dsp)
141            if sth < 1.0:
142                tth = 2.0*asind(sth)
143            else:
144                break
145            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
146    else:                                   #2-thetas - assume Cuka (for now)
147        for peak in peaks:
148            tth = peak[0]
149            dsp = wave/(2.0*sind(tth/2.0))
150            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
151    limits = [1000.,0.]
152    for peak in Peaks:
153        limits[0] = min(limits[0],peak[0])
154        limits[1] = max(limits[1],peak[0])
155    limits[0] = max(1.,(int(limits[0]-1.)/5)*5.)
156    limits[1] = min(170.,(int(limits[1]+1.)/5)*5.)
157    return Comments,Peaks,limits,wave
158
159def GetCheckImageFile(G2frame,treeId):
160    '''Try to locate an image file if the project and image have been moved
161    together. If the image file cannot be found, request the location from
162    the user.
163
164    :param wx.Frame G2frame: main GSAS-II Frame and data object
165    :param wx.Id treeId: Id for the main tree item for the image
166    :returns: Npix,imagefile,imagetag with (Npix) number of pixels,
167       imagefile, if it exists, or the name of a file that does exist or False if the user presses Cancel
168       and (imagetag) an optional image number
169
170    '''
171    Npix,Imagefile,imagetag = G2frame.GPXtree.GetImageLoc(treeId)
172    if isinstance(Imagefile,list):
173        imagefile,imagetag = Imagefile
174    else:
175        imagefile = Imagefile
176    if not os.path.exists(imagefile):
177        print ('Image file '+imagefile+' not found')
178        fil = imagefile.replace('\\','/') # windows?!
179        # see if we can find a file with same name or in a similarly named sub-dir
180        pth,fil = os.path.split(fil)
181        prevpth = None
182        while pth and pth != prevpth:
183            prevpth = pth
184            if os.path.exists(os.path.join(G2frame.dirname,fil)):
185                print ('found image file '+os.path.join(G2frame.dirname,fil))
186                imagefile = os.path.join(G2frame.dirname,fil)
187                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
188                return Npix,imagefile,imagetag
189            pth,enddir = os.path.split(pth)
190            fil = os.path.join(enddir,fil)
191        # not found as a subdirectory, drop common parts of path for last saved & image file names
192        #    if image was .../A/B/C/imgs/ima.ge
193        #      & GPX was  .../A/B/C/refs/fil.gpx but is now .../NEW/TEST/TEST1
194        #    will look for .../NEW/TEST/TEST1/imgs/ima.ge, .../NEW/TEST/imgs/ima.ge, .../NEW/imgs/ima.ge and so on
195        Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
196        gpxPath = Controls.get('LastSavedAs','').replace('\\','/').split('/') # blank in older .GPX files
197        imgPath = imagefile.replace('\\','/').split('/')
198        for p1,p2 in zip(gpxPath,imgPath):
199            if p1 == p2:
200                gpxPath.pop(0),imgPath.pop(0)
201            else:
202                break
203        fil = os.path.join(*imgPath) # file with non-common prefix elements
204        prevpth = None
205        pth = os.path.abspath(G2frame.dirname)
206        while pth and pth != prevpth:
207            prevpth = pth
208            if os.path.exists(os.path.join(pth,fil)):
209                print ('found image file '+os.path.join(pth,fil))
210                imagefile = os.path.join(pth,fil)
211                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
212                return Npix,imagefile,imagetag
213            pth,enddir = os.path.split(pth)
214        #GSASIIpath.IPyBreak()
215
216    if not os.path.exists(imagefile):
217        # note that this fails (at least on Mac) to get an image during the GUI initialization
218        prevnam = os.path.split(imagefile)[1]
219        prevext = os.path.splitext(imagefile)[1]
220        wildcard = 'Image format (*'+prevext+')|*'+prevext
221        dlg = wx.FileDialog(G2frame, 'Previous image file ('+prevnam+') not found; open here', '.', prevnam,
222                            wildcard,wx.FD_OPEN)
223        try:
224            dlg.SetFilename(''+ospath.split(imagefile)[1])
225            if dlg.ShowModal() == wx.ID_OK:
226                imagefile = dlg.GetPath()
227                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
228            else:
229                imagefile = False
230        finally:
231            dlg.Destroy()
232    return Npix,imagefile,imagetag
233
234def EditImageParms(parent,Data,Comments,Image,filename):
235    dlg = wx.Dialog(parent, wx.ID_ANY, 'Edit image parameters',
236                    style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
237    def onClose(event):
238        dlg.EndModal(wx.ID_OK)
239    mainsizer = wx.BoxSizer(wx.VERTICAL)
240    h,w = Image.shape[:2]
241    mainsizer.Add(wx.StaticText(dlg,wx.ID_ANY,'File '+str(filename)+'\nImage size: '+str(h)+' x '+str(w)),
242        0,wx.ALIGN_LEFT|wx.ALL, 2)
243   
244    vsizer = wx.BoxSizer(wx.HORIZONTAL)
245    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Wavelength (\xC5) '),
246        0,wx.ALIGN_LEFT|wx.ALL, 2)
247    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'wavelength')
248    vsizer.Add(wdgt)
249    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
250
251    vsizer = wx.BoxSizer(wx.HORIZONTAL)
252    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Pixel size (\xb5m). Width '),
253        0,wx.ALIGN_LEFT|wx.ALL, 2)
254    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],0,
255                                 size=(50,-1))
256    vsizer.Add(wdgt)
257    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Height '),wx.ALIGN_LEFT|wx.ALL, 2)
258    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],1,size=(50,-1))
259    vsizer.Add(wdgt)
260    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
261
262    vsizer = wx.BoxSizer(wx.HORIZONTAL)
263    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Sample to detector (mm) '),
264        0,wx.ALIGN_LEFT|wx.ALL, 2)
265    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'distance')
266    vsizer.Add(wdgt)
267    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
268
269    vsizer = wx.BoxSizer(wx.HORIZONTAL)
270    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Beam center (pixels). X = '),
271        0,wx.ALIGN_LEFT|wx.ALL, 2)
272    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],0,size=(75,-1))
273    vsizer.Add(wdgt)
274    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Y = '),wx.ALIGN_LEFT|wx.ALL, 2)
275    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],1,size=(75,-1))
276    vsizer.Add(wdgt)
277    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
278
279    vsizer = wx.BoxSizer(wx.HORIZONTAL)
280    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Comments '),
281        0,wx.ALIGN_LEFT|wx.ALL, 2)
282    wdgt = G2G.ValidatedTxtCtrl(dlg,Comments,0,size=(250,-1))
283    vsizer.Add(wdgt)
284    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
285
286    btnsizer = wx.StdDialogButtonSizer()
287    OKbtn = wx.Button(dlg, wx.ID_OK, 'Continue')
288    OKbtn.SetDefault()
289    OKbtn.Bind(wx.EVT_BUTTON,onClose)
290    btnsizer.AddButton(OKbtn) # not sure why this is needed
291    btnsizer.Realize()
292    mainsizer.Add(btnsizer, 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 5)
293    dlg.SetSizer(mainsizer)
294    dlg.CenterOnParent()
295    dlg.ShowModal()
296   
297def LoadImage2Tree(imagefile,G2frame,Comments,Data,Npix,Image):
298    '''Load an image into the tree. Saves the location of the image, as well as the
299    ImageTag (where there is more than one image in the file), if defined.
300    '''
301    ImgNames = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
302    TreeLbl = 'IMG '+os.path.basename(imagefile)
303    ImageTag = Data.get('ImageTag')
304    if ImageTag:
305        TreeLbl += ' #'+'%04d'%(ImageTag)
306        imageInfo = (imagefile,ImageTag)
307    else:
308        imageInfo = imagefile
309    TreeName = G2obj.MakeUniqueLabel(TreeLbl,ImgNames)
310    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=TreeName)
311    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)
312    Imax = np.amax(Image)
313    if G2frame.imageDefault:
314        Data = copy.copy(G2frame.imageDefault)
315        Data['showLines'] = True
316        Data['ring'] = []
317        Data['rings'] = []
318        Data['cutoff'] = 10.
319        Data['pixLimit'] = 20
320        Data['edgemin'] = 100000000
321        Data['calibdmin'] = 0.5
322        Data['calibskip'] = 0
323        Data['ellipses'] = []
324        Data['calibrant'] = ''
325        Data['GonioAngles'] = [0.,0.,0.]
326        Data['DetDepthRef'] = False
327    else:
328        Data['type'] = 'PWDR'
329        Data['color'] = GSASIIpath.GetConfigValue('Contour_color','Paired')
330        if 'tilt' not in Data:          #defaults if not preset in e.g. Bruker importer
331            Data['tilt'] = 0.0
332            Data['rotation'] = 0.0
333            Data['pixLimit'] = 20
334            Data['calibdmin'] = 0.5
335            Data['cutoff'] = 10.
336        Data['showLines'] = False
337        Data['calibskip'] = 0
338        Data['ring'] = []
339        Data['rings'] = []
340        Data['edgemin'] = 100000000
341        Data['ellipses'] = []
342        Data['GonioAngles'] = [0.,0.,0.]
343        Data['DetDepth'] = 0.
344        Data['DetDepthRef'] = False
345        Data['calibrant'] = ''
346        Data['IOtth'] = [5.0,50.0]
347        Data['LRazimuth'] = [0.,180.]
348        Data['azmthOff'] = 0.0
349        Data['outChannels'] = 2250
350        Data['outAzimuths'] = 1
351        Data['centerAzm'] = False
352        Data['fullIntegrate'] = GSASIIpath.GetConfigValue('fullIntegrate',True)
353        Data['setRings'] = False
354        Data['background image'] = ['',-1.0]                           
355        Data['dark image'] = ['',-1.0]
356        Data['Flat Bkg'] = 0.0
357        Data['Oblique'] = [0.5,False]
358    Data['setDefault'] = False
359    Data['range'] = [(0,Imax),[0,Imax]]
360    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Image Controls'),Data)
361    Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],'Thresholds':[(0,Imax),[0,Imax]]}
362    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Masks'),Masks)
363    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Stress/Strain'),
364        {'Type':'True','d-zero':[],'Sample phi':0.0,'Sample z':0.0,'Sample load':0.0})
365    G2frame.GPXtree.SetItemPyData(Id,[Npix,imageInfo])
366    G2frame.PickId = Id
367    G2frame.PickIdText = G2frame.GetTreeItemsList(G2frame.PickId)
368    G2frame.Image = Id
369
370def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''):
371    '''Read a single image with an image importer. This is called to reread an image
372    after it has already been imported with :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
373    (or :func:`ReadImages` in Auto Integration) so it is not necessary to reload metadata.
374
375    :param wx.Frame G2frame: main GSAS-II Frame and data object.
376    :param str imagefile: name of image file
377    :param bool imageOnly: If True return only the image,
378      otherwise  (default) return more (see below)
379    :param int/str ImageTag: specifies a particular image to be read from a file.
380      First image is read if None (default).
381    :param str formatName: the image reader formatName
382
383    :returns: an image as a numpy array or a list of four items:
384      Comments, Data, Npix and the Image, as selected by imageOnly
385
386    '''
387    # determine which formats are compatible with this file
388    primaryReaders = []
389    secondaryReaders = []
390    for rd in G2frame.ImportImageReaderlist:
391        flag = rd.ExtensionValidator(imagefile)
392        if flag is None: 
393            secondaryReaders.append(rd)
394        elif flag:
395            if not FormatName:
396                primaryReaders.append(rd)
397            elif FormatName == rd.formatName:
398                primaryReaders.append(rd)
399    if len(secondaryReaders) + len(primaryReaders) == 0:
400        print('Error: No matching format for file '+imagefile)
401        raise Exception('No image read')
402    errorReport = ''
403    if not imagefile:
404        return
405    for rd in primaryReaders+secondaryReaders:
406        rd.ReInitialize() # purge anything from a previous read
407        rd.errors = "" # clear out any old errors
408        if not rd.ContentsValidator(imagefile): # rejected on cursory check
409            errorReport += "\n  "+rd.formatName + ' validator error'
410            if rd.errors: 
411                errorReport += ': '+rd.errors
412                continue
413        if imageOnly:
414            ParentFrame = None # prevent GUI access on reread
415        else:
416            ParentFrame = G2frame
417        if GSASIIpath.GetConfigValue('debug'):
418            flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
419        else:
420            flag = False
421            try:
422                flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
423            except rd.ImportException as detail:
424                rd.errors += "\n  Read exception: "+str(detail)
425            except Exception as detail:
426                import traceback
427                rd.errors += "\n  Unhandled read exception: "+str(detail)
428                rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
429        if flag: # this read succeeded
430            if rd.Image is None:
431                raise Exception('No image read. Strange!')
432            if GSASIIpath.GetConfigValue('Transpose'):
433                print ('Transposing Image!')
434                rd.Image = rd.Image.T
435            #rd.readfilename = imagefile
436            if imageOnly:
437                return rd.Image
438            else:
439                return rd.Comments,rd.Data,rd.Npix,rd.Image
440    else:
441        print('Error reading file '+imagefile)
442        print('Error messages(s)\n'+errorReport)
443        raise Exception('No image read')   
444
445def ReadImages(G2frame,imagefile):
446    '''Read one or more images from a file and put them into the Tree
447    using image importers. Called only in :meth:`AutoIntFrame.OnTimerLoop`.
448
449    ToDo: Images are most commonly read in :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
450    which is called from :meth:`GSASIIdataGUI.GSASII.OnImportImage`
451    it would be good if these routines used a common code core so that changes need to
452    be made in only one place.
453
454    :param wx.Frame G2frame: main GSAS-II Frame and data object.
455    :param str imagefile: name of image file
456
457    :returns: a list of the id's of the IMG tree items created
458    '''
459    # determine which formats are compatible with this file
460    primaryReaders = []
461    secondaryReaders = []
462    for rd in G2frame.ImportImageReaderlist:
463        flag = rd.ExtensionValidator(imagefile)
464        if flag is None:
465            secondaryReaders.append(rd)
466        elif flag:
467            primaryReaders.append(rd)
468    if len(secondaryReaders) + len(primaryReaders) == 0:
469        print('Error: No matching format for file '+imagefile)
470        raise Exception('No image read')
471    errorReport = ''
472    rdbuffer = {} # create temporary storage for file reader
473    for rd in primaryReaders+secondaryReaders:
474        rd.ReInitialize() # purge anything from a previous read
475        rd.errors = "" # clear out any old errors
476        if not rd.ContentsValidator(imagefile): # rejected on cursory check
477            errorReport += "\n  "+rd.formatName + ' validator error'
478            if rd.errors: 
479                errorReport += ': '+rd.errors
480                continue
481        ParentFrame = G2frame
482        block = 0
483        repeat = True
484        CreatedIMGitems = []
485        while repeat: # loop if the reader asks for another pass on the file
486            block += 1
487            repeat = False
488            if GSASIIpath.GetConfigValue('debug'):
489                flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
490            else:
491                flag = False
492                try:
493                    flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
494                except rd.ImportException as detail:
495                    rd.errors += "\n  Read exception: "+str(detail)
496                except Exception as detail:
497                    import traceback
498                    rd.errors += "\n  Unhandled read exception: "+str(detail)
499                    rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
500            if flag: # this read succeeded
501                if rd.Image is None:
502                    raise Exception('No image read. Strange!')
503                if GSASIIpath.GetConfigValue('Transpose'):
504                    print ('Transposing Image!')
505                    rd.Image = rd.Image.T
506                rd.Data['ImageTag'] = rd.repeatcount
507                rd.readfilename = imagefile
508                # Load generic metadata, as configured
509                GetColumnMetadata(rd)
510                LoadImage2Tree(imagefile,G2frame,rd.Comments,rd.Data,rd.Npix,rd.Image)
511                repeat = rd.repeat
512            CreatedIMGitems.append(G2frame.Image)
513        if CreatedIMGitems: return CreatedIMGitems
514    else:
515        print('Error reading file '+imagefile)
516        print('Error messages(s)\n'+errorReport)
517        return []
518        #raise Exception('No image read')   
519
520def SaveMultipleImg(G2frame):
521    if not G2frame.GPXtree.GetCount():
522        print ('no images!')
523        return
524    choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
525    if len(choices) == 1:
526        names = choices
527    else:
528        dlg = G2G.G2MultiChoiceDialog(G2frame,'Stress/Strain fitting','Select images to fit:',choices)
529        dlg.SetSelections([])
530        names = []
531        if dlg.ShowModal() == wx.ID_OK:
532            names = [choices[sel] for sel in dlg.GetSelections()]
533        dlg.Destroy()
534    if not names: return
535    for name in names:
536        Id = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name)
537        Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(Id)
538        imroot = os.path.splitext(imagefile)[0]
539        if imagetag:
540            imroot += '_' + str(imagetag)
541        Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Image Controls'))
542        print('Writing '+imroot+'.imctrl')
543        File = open(imroot+'.imctrl','w')
544        keys = ['type','wavelength','calibrant','distance','center',
545                    'tilt','rotation','azmthOff','fullIntegrate','LRazimuth',
546                    'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
547                    'calibskip','pixLimit','cutoff','calibdmin','chisq','Flat Bkg',
548                    'binType','SampleShape','PolaVal','SampleAbs','dark image','background image']
549        for key in keys:
550            if key not in Data: continue    #uncalibrated!
551            File.write(key+':'+str(Data[key])+'\n')
552        File.close()
553        mask = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'))
554        G2imG.CleanupMasks(mask)
555        print('Writing '+imroot+'.immask')
556        File = open(imroot+'.immask','w')
557        for key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
558            File.write(key+':'+str(mask[key])+'\n')
559        File.close()
560       
561def PutG2Image(filename,Comments,Data,Npix,image):
562    'Write an image as a python pickle - might be better as an .edf file?'
563    File = open(filename,'wb')
564    cPickle.dump([Comments,Data,Npix,image],File,2)
565    File.close()
566    return
567
568objectScanIgnore = [int,bool,float,str,np.float64,np.int32,np.int64,np.ndarray,G2obj.G2VarObj,G2obj.ExpressionObj]
569try:
570    objectScanIgnore += [ma.MaskedArray] # fails in doc builds
571except AttributeError:
572    pass
573   
574if '2' in platform.python_version_tuple()[0]:
575    objectScanIgnore += [unicode,long,]
576   
577def objectScan(data,tag,indexStack=[]):
578    '''Recursively scan an object looking for unexpected data types.
579    This is used in debug mode to scan .gpx files for objects we did not
580    intend to be there.
581    '''
582    if type(data) is list or type(data) is tuple:
583        for i in range(len(data)):
584            objectScan(data[i],tag,indexStack+[i])
585    elif type(data) is dict:
586        for key in data:
587            objectScan(data[key],tag,indexStack+[key])
588    elif data is None:
589        return
590    elif type(data) in objectScanIgnore:
591        return
592    else:
593        s = 'unexpected object in '+tag
594        for i in indexStack:
595            s += "[{}]".format(i)
596        #print(s,data.__class__.__name__) # loses full name of class
597        print(s,type(data))
598   
599def ProjFileOpen(G2frame,showProvenance=True):
600    'Read a GSAS-II project file and load into the G2 data tree'
601    if not os.path.exists(G2frame.GSASprojectfile):
602        print ('\n*** Error attempt to open project file that does not exist:\n   '+
603               str(G2frame.GSASprojectfile))
604        return
605    LastSavedUsing = None
606    file = open(G2frame.GSASprojectfile,'rb')
607    if showProvenance: print ('loading from file: '+G2frame.GSASprojectfile)
608    wx.BeginBusyCursor()
609    try:
610        while True:
611            try:
612                if '2' in platform.python_version_tuple()[0]:
613                    data = cPickle.load(file)
614                else:
615                    data = cPickle.load(file,encoding='latin-1')
616            except EOFError:
617                break
618            datum = data[0]
619            # scan the GPX file for unexpected objects
620            if GSASIIpath.GetConfigValue('debug'):
621                objectScan(data,datum[0])
622            Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
623            if datum[0].startswith('PWDR'):               
624                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
625                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
626                G2frame.GPXtree.SetItemPyData(Id,datum[1][:3])  #temp. trim off junk (patch?)
627            elif datum[0].startswith('HKLF'): 
628                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
629                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
630                G2frame.GPXtree.SetItemPyData(Id,datum[1])
631            else:
632                G2frame.GPXtree.SetItemPyData(Id,datum[1])             
633                if datum[0] == 'Controls' and 'LastSavedUsing' in datum[1]:
634                    LastSavedUsing = datum[1]['LastSavedUsing']
635                if datum[0] == 'Controls' and 'PythonVersions' in datum[1] and GSASIIpath.GetConfigValue('debug') and showProvenance:
636                    print('DBG_Packages used to create .GPX file:')
637                    if 'dict' in str(type(datum[1]['PythonVersions'])):  #patch
638                        for p in sorted(datum[1]['PythonVersions'],key=lambda s: s.lower()):
639                            print({:<14s}: {:s}".format(p[0],p[1]))
640                    else:
641                        for p in datum[1]['PythonVersions']:
642                            print({:<12s} {:s}".format(p[0]+':',p[1]))
643            oldPDF = False
644            for datus in data[1:]:
645#patch - 1/23/17 PDF cleanup
646                if datus[0][:4] in ['I(Q)','S(Q)','F(Q)','G(R)']:
647                    oldPDF = True
648                    data[1][1][datus[0][:4]] = copy.deepcopy(datus[1][:2])
649                    continue
650#end PDF cleanup
651                sub = G2frame.GPXtree.AppendItem(Id,datus[0])
652#patch
653                if datus[0] == 'Instrument Parameters' and len(datus[1]) == 1:
654                    if datum[0].startswith('PWDR'):
655                        datus[1] = [dict(zip(datus[1][3],zip(datus[1][0],datus[1][1],datus[1][2]))),{}]
656                    else:
657                        datus[1] = [dict(zip(datus[1][2],zip(datus[1][0],datus[1][1]))),{}]
658                    for item in datus[1][0]:               #zip makes tuples - now make lists!
659                        datus[1][0][item] = list(datus[1][0][item])
660#end patch
661                G2frame.GPXtree.SetItemPyData(sub,datus[1])
662            if 'PDF ' in datum[0][:4] and oldPDF:
663                sub = G2frame.GPXtree.AppendItem(Id,'PDF Peaks')
664                G2frame.GPXtree.SetItemPyData(sub,{'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]})
665            if datum[0].startswith('IMG'):                   #retrieve image default flag & data if set
666                Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
667                if Data['setDefault']:
668                    G2frame.imageDefault = Data               
669        file.close()
670        if LastSavedUsing:
671            print('GPX load successful. Last saved with GSAS-II revision '+LastSavedUsing)
672        else:
673            print('project load successful')
674        G2frame.NewPlot = True
675    except Exception as errmsg:
676        if GSASIIpath.GetConfigValue('debug'):
677            print('\nError reading GPX file:',errmsg)
678            import traceback
679            print (traceback.format_exc())
680        msg = wx.MessageDialog(G2frame,message="Error reading file "+
681            str(G2frame.GSASprojectfile)+". This is not a current GSAS-II .gpx file",
682            caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
683        msg.ShowModal()
684    finally:
685        wx.EndBusyCursor()
686        G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0)
687    G2frame.SetTitleByGPX()
688   
689def ProjFileSave(G2frame):
690    'Save a GSAS-II project file'
691    if not G2frame.GPXtree.IsEmpty():
692        file = open(G2frame.GSASprojectfile,'wb')
693        print ('save to file: '+G2frame.GSASprojectfile)
694        # stick the file name into the tree and version info into tree so they are saved.
695        # (Controls should always be created at this point)
696        try:
697            Controls = G2frame.GPXtree.GetItemPyData(
698                G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
699            Controls['LastSavedAs'] = os.path.abspath(G2frame.GSASprojectfile)
700            Controls['LastSavedUsing'] = str(GSASIIpath.GetVersionNumber())
701            Controls['PythonVersions'] = G2frame.PackageVersions
702        except:
703            pass
704        wx.BeginBusyCursor()
705        try:
706            item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
707            while item:
708                data = []
709                name = G2frame.GPXtree.GetItemText(item)
710                data.append([name,G2frame.GPXtree.GetItemPyData(item)])
711                item2, cookie2 = G2frame.GPXtree.GetFirstChild(item)
712                while item2:
713                    name = G2frame.GPXtree.GetItemText(item2)
714                    data.append([name,G2frame.GPXtree.GetItemPyData(item2)])
715                    item2, cookie2 = G2frame.GPXtree.GetNextChild(item, cookie2)                           
716                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)                           
717                cPickle.dump(data,file,2)
718            file.close()
719            pth = os.path.split(os.path.abspath(G2frame.GSASprojectfile))[0]
720            if GSASIIpath.GetConfigValue('Save_paths'): G2G.SaveGPXdirectory(pth)
721            G2frame.LastGPXdir = pth
722        finally:
723            wx.EndBusyCursor()
724        print('project save successful')
725
726def SaveIntegration(G2frame,PickId,data,Overwrite=False):
727    'Save image integration results as powder pattern(s)'
728    waves = {'Cu':[1.54051,1.54433],'Ti':[2.74841,2.75207],'Cr':[2.28962,2.29351],
729        'Fe':[1.93597,1.93991],'Co':[1.78892,1.79278],'Mo':[0.70926,0.713543],
730        'Ag':[0.559363,0.563775]}
731    azms = G2frame.Integrate[1]
732    X = G2frame.Integrate[2][:-1]
733    N = len(X)
734    Id = G2frame.GPXtree.GetItemParent(PickId)
735    name = G2frame.GPXtree.GetItemText(Id)
736    name = name.replace('IMG ',data['type']+' ')
737    Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Comments'))
738    Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
739    if 'PWDR' in name:
740        if 'target' in data:
741            names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
742            codes = [0 for i in range(14)]
743        else:
744            names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
745            codes = [0 for i in range(12)]
746    elif 'SASD' in name:
747        names = ['Type','Lam','Zero','Azimuth'] 
748        codes = [0 for i in range(4)]
749        X = 4.*np.pi*npsind(X/2.)/data['wavelength']    #convert to q
750    Xminmax = [X[0],X[-1]]
751    Azms = []
752    dazm = 0.
753    if data['fullIntegrate'] and data['outAzimuths'] == 1:
754        Azms = [45.0,]                              #a poor man's average?
755    else:
756        for i,azm in enumerate(azms[:-1]):
757            if azm > 360. and azms[i+1] > 360.:
758                Azms.append(G2img.meanAzm(azm%360.,azms[i+1]%360.))
759            else:   
760                Azms.append(G2img.meanAzm(azm,azms[i+1]))
761        dazm = np.min(np.abs(np.diff(azms)))/2.
762    G2frame.IntgOutList = []
763    for i,azm in enumerate(azms[:-1]):
764        Aname = name+" Azm= %.2f"%((azm+dazm)%360.)
765        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
766        # if Overwrite delete any duplicate
767        if Overwrite and G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname):
768            print('Replacing '+Aname)
769            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname)
770            G2frame.GPXtree.Delete(item)
771        else:
772            nOcc = 0
773            while item:
774                Name = G2frame.GPXtree.GetItemText(item)
775                if Aname in Name:
776                    nOcc += 1
777                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
778            if nOcc:
779                Aname += '(%d)'%(nOcc)
780        Sample = G2obj.SetDefaultSample()       #set as Debye-Scherrer
781        Sample['Gonio. radius'] = data['distance']
782        Sample['Omega'] = data['GonioAngles'][0]
783        Sample['Chi'] = data['GonioAngles'][1]
784        Sample['Phi'] = data['GonioAngles'][2]
785        Sample['Azimuth'] = (azm+dazm)%360.    #put here as bin center
786        polariz = 0.99    #set default polarization for synchrotron radiation!
787        for item in Comments:
788            if 'polariz' in item:
789                try:
790                    polariz = float(item.split('=')[1])
791                except:
792                    polariz = 0.99
793            for key in ('Temperature','Pressure','Time','FreePrm1','FreePrm2','FreePrm3','Omega',
794                'Chi','Phi'):
795                if key.lower() in item.lower():
796                    try:
797                        Sample[key] = float(item.split('=')[1])
798                    except:
799                        pass
800            if 'label_prm' in item.lower():
801                for num in ('1','2','3'):
802                    if 'label_prm'+num in item.lower():
803                        Controls['FreePrm'+num] = item.split('=')[1].strip()
804        if 'PWDR' in Aname:
805            if 'target' in data:    #from lab x-ray 2D imaging data
806                wave1,wave2 = waves[data['target']]
807                parms = ['PXC',wave1,wave2,0.5,0.0,polariz,290.,-40.,30.,6.,-14.,0.0,0.0001,Azms[i]]
808            else:
809                parms = ['PXC',data['wavelength'],0.0,polariz,1.0,-0.10,0.4,0.30,1.0,0.0,0.0001,Azms[i]]
810        elif 'SASD' in Aname:
811            Sample['Trans'] = data['SampleAbs'][0]
812            parms = ['LXC',data['wavelength'],0.0,Azms[i]]
813        Y = G2frame.Integrate[0][i]
814        Ymin = np.min(Y)
815        Ymax = np.max(Y)
816        W = np.where(Y>0.,1./Y,1.e-6)                    #probably not true
817        Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Aname)
818        G2frame.IntgOutList.append(Id)
819        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)                   
820        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Limits'),copy.deepcopy([tuple(Xminmax),Xminmax]))
821        if 'PWDR' in Aname:
822            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Background'),[['chebyschev',1,3,1.0,0.0,0.0],
823                {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[]}])
824        inst = [dict(zip(names,zip(parms,parms,codes))),{}]
825        for item in inst[0]:
826            inst[0][item] = list(inst[0][item])
827        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),inst)
828        if 'PWDR' in Aname:
829            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
830            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Peak List'),{'sigDict':{},'peaks':[]})
831            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Index Peak List'),[[],[]])
832            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Unit Cells List'),[])
833            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection Lists'),{})
834        elif 'SASD' in Aname:             
835            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Substances'),G2pdG.SetDefaultSubstances())
836            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
837            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Models'),G2pdG.SetDefaultSASDModel())
838        valuesdict = {
839            'wtFactor':1.0,'Dummy':False,'ranId':ran.randint(0,sys.maxsize),'Offset':[0.0,0.0],'delOffset':0.02*Ymax,
840            'refOffset':-0.1*Ymax,'refDelt':0.1*Ymax,'Yminmax':[Ymin,Ymax]}
841        G2frame.GPXtree.SetItemPyData(Id,[valuesdict,
842            [np.array(X),np.array(Y),np.array(W),np.zeros(N),np.zeros(N),np.zeros(N)]])
843    return Id       #last powder pattern generated
844   
845def XYsave(G2frame,XY,labelX='X',labelY='Y',names=[]):
846    'Save XY table data'
847    pth = G2G.GetExportPath(G2frame)
848    dlg = wx.FileDialog(
849        G2frame, 'Enter csv filename for XY table', pth, '',
850        'XY table file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
851    try:
852        if dlg.ShowModal() == wx.ID_OK:
853            filename = dlg.GetPath()
854            filename = os.path.splitext(filename)[0]+'.csv'
855            File = open(filename,'w')
856        else:
857            filename = None
858    finally:
859        dlg.Destroy()
860    if not filename:
861        return
862    for i in range(len(XY)):
863        if len(names):
864            header = '%s,%s(%s)\n'%(labelX,labelY,names[i])
865        else:
866            header = '%s,%s(%d)\n'%(labelX,labelY,i)
867        File.write(header)
868        for x,y in XY[i].T:
869            File.write('%.3f,%.3f\n'%(x,y))   
870    File.close()
871    print (' XY data saved to: '+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 = G2G.askSaveDirectory(G2frame)
878        if not dirname: return
879    else:
880        defnam = exports[0].replace(' ','_')[5:]
881        filename = G2G.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 = open(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 = list(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 = list(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        This could be made faster for sequential fits by reducing the histogram list to only
1555        the active histogram being exported.
1556        '''
1557        self.parmDict = {}
1558        self.sigDict = {}
1559        rigidbodyDict = {}
1560        covDict = {}
1561        consDict = {}
1562        Histograms,Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1563        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1564        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1565        while item:
1566            name = self.G2frame.GPXtree.GetItemText(item)
1567            if name == 'Rigid bodies':
1568                 rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(item)
1569            elif name == 'Covariance':
1570                 covDict = self.G2frame.GPXtree.GetItemPyData(item)
1571            elif name == 'Constraints':
1572                 consDict = self.G2frame.GPXtree.GetItemPyData(item)
1573            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1574        rbVary,rbDict =  G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1575        self.parmDict.update(rbDict)
1576        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1577        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave =  G2stIO.GetPhaseData(
1578            Phases,RestraintDict=None,rbIds=rbIds,Print=False)
1579        self.parmDict.update(phaseDict)
1580        hapVary,hapDict,controlDict =  G2stIO.GetHistogramPhaseData(
1581            Phases,Histograms,Print=False,resetRefList=False)
1582        self.parmDict.update(hapDict)
1583        histVary,histDict,controlDict =  G2stIO.GetHistogramData(Histograms,Print=False)
1584        self.parmDict.update(histDict)
1585        self.parmDict.update(zip(
1586            covDict.get('varyList',[]),
1587            covDict.get('variables',[])))
1588        self.sigDict = dict(zip(
1589            covDict.get('varyList',[]),
1590            covDict.get('sig',[])))
1591        # expand to include constraints: first compile a list of constraints
1592        constList = []
1593        for item in consDict:
1594            if item.startswith('_'): continue
1595            constList += consDict[item]
1596        # now process the constraints
1597        G2mv.InitVars()
1598        constDict,fixedList,ignored = G2stIO.ProcessConstraints(constList)
1599        varyList = covDict.get('varyListStart')
1600        if varyList is None and len(constDict) == 0:
1601            # no constraints can use varyList
1602            varyList = covDict.get('varyList')
1603        elif varyList is None:
1604            # old GPX file from before pre-constraint varyList is saved
1605            print (' *** Old refinement: Please use Calculate/Refine to redo  ***')
1606            raise Exception(' *** Export aborted ***')
1607        else:
1608            varyList = list(varyList)
1609        # add symmetry-generated constraints
1610        rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(   
1611            G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Rigid bodies'))
1612        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1613        rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1614        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave = G2stIO.GetPhaseData(
1615            Phases,RestraintDict=None,rbIds=rbIds,Print=False) # generates atom symmetry constraints
1616        msg = G2mv.EvaluateMultipliers(constDict,phaseDict)
1617        if msg:
1618            print('Unable to interpret multiplier(s): '+msg)
1619            raise Exception(' *** CIF creation aborted ***')
1620        try:
1621            G2mv.GenerateConstraints(varyList,constDict,fixedList,self.parmDict)
1622            #print(G2mv.VarRemapShow(varyList))
1623        except:
1624            # this really should not happen
1625            print (' *** ERROR - constraints are internally inconsistent ***')
1626            errmsg, warnmsg = G2mv.CheckConstraints(varyList,constDict,fixedList)
1627            print ('Errors'+errmsg)
1628            if warnmsg: print ('Warnings'+warnmsg)
1629            raise Exception(' *** CIF creation aborted ***')
1630        # add the constrained values to the parameter dictionary
1631        G2mv.Dict2Map(self.parmDict,varyList)
1632        # and add their uncertainties into the esd dictionary (sigDict)
1633        if covDict.get('covMatrix') is not None:
1634            self.sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],covDict['varyList'],self.parmDict))
1635
1636    def loadTree(self):
1637        '''Load the contents of the data tree into a set of dicts
1638        (self.OverallParms, self.Phases and self.Histogram as well as self.powderDict
1639        & self.xtalDict)
1640       
1641        * The childrenless data tree items are overall parameters/controls for the
1642          entire project and are placed in self.OverallParms
1643        * Phase items are placed in self.Phases
1644        * Data items are placed in self.Histogram. The key for these data items
1645          begin with a keyword, such as PWDR, IMG, HKLF,... that identifies the data type.
1646        '''
1647        self.OverallParms = {}
1648        self.powderDict = {}
1649        self.xtalDict = {}
1650        self.Phases = {}
1651        self.Histograms = {}
1652        self.SeqRefdata = None
1653        self.SeqRefhist = None
1654        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1655        histType = None       
1656        if self.currentExportType == 'phase':
1657            # if exporting phases load them here
1658            sub = G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Phases')
1659            if not sub:
1660                print ('no phases found')
1661                return True
1662            item, cookie = self.G2frame.GPXtree.GetFirstChild(sub)
1663            while item:
1664                phaseName = self.G2frame.GPXtree.GetItemText(item)
1665                self.Phases[phaseName] =  self.G2frame.GPXtree.GetItemPyData(item)
1666                item, cookie = self.G2frame.GPXtree.GetNextChild(sub, cookie)
1667            return
1668        elif self.currentExportType == 'single':
1669            histType = 'HKLF'
1670        elif self.currentExportType == 'powder':
1671            histType = 'PWDR'
1672        elif self.currentExportType == 'image':
1673            histType = 'IMG'
1674
1675        if histType: # Loading just one kind of tree entry
1676            item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1677            while item:
1678                name = self.G2frame.GPXtree.GetItemText(item)
1679                if name.startswith(histType):
1680                    if self.Histograms.get(name): # there is already an item with this name
1681                        print('Histogram name '+str(name)+' is repeated. Renaming')
1682                        if name[-1] == '9':
1683                            name = name[:-1] + '10'
1684                        elif name[-1] in '012345678':
1685                            name = name[:-1] + str(int(name[-1])+1)
1686                        else:                           
1687                            name += '-1'
1688                    self.Histograms[name] = {}
1689                    # the main info goes into Data, but the 0th
1690                    # element contains refinement results, carry
1691                    # that over too now.
1692                    self.Histograms[name]['Data'] = self.G2frame.GPXtree.GetItemPyData(item)[1]
1693                    self.Histograms[name][0] = self.G2frame.GPXtree.GetItemPyData(item)[0]
1694                    item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1695                    while item2: 
1696                        child = self.G2frame.GPXtree.GetItemText(item2)
1697                        self.Histograms[name][child] = self.G2frame.GPXtree.GetItemPyData(item2)
1698                        item2, cookie2 = self.G2frame.GPXtree.GetNextChild(item, cookie2)
1699                item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1700            # index powder and single crystal histograms by number
1701            for hist in self.Histograms:
1702                if hist.startswith("PWDR"): 
1703                    d = self.powderDict
1704                elif hist.startswith("HKLF"): 
1705                    d = self.xtalDict
1706                else:
1707                    return                   
1708                i = self.Histograms[hist].get('hId')
1709                if i is None and not d.keys():
1710                    i = 0
1711                elif i is None or i in d.keys():
1712                    i = max(d.keys())+1
1713                d[i] = hist
1714            return
1715        # else standard load: using all interlinked phases and histograms
1716        self.Histograms,self.Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1717        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1718        while item:
1719            name = self.G2frame.GPXtree.GetItemText(item)
1720            item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1721            if not item2: 
1722                self.OverallParms[name] = self.G2frame.GPXtree.GetItemPyData(item)
1723            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1724        # index powder and single crystal histograms
1725        for hist in self.Histograms:
1726            i = self.Histograms[hist]['hId']
1727            if hist.startswith("PWDR"): 
1728                self.powderDict[i] = hist
1729            elif hist.startswith("HKLF"): 
1730                self.xtalDict[i] = hist
1731
1732    def dumpTree(self,mode='type'):
1733        '''Print out information on the data tree dicts loaded in loadTree.
1734        Used for testing only.
1735        '''
1736        if self.SeqRefdata and self.SeqRefhist:
1737            print('Note that dumpTree does not show sequential results')
1738        print ('\nOverall')
1739        if mode == 'type':
1740            def Show(arg): return type(arg)
1741        else:
1742            def Show(arg): return arg
1743        for key in self.OverallParms:
1744            print ('  '+key+Show(self.OverallParms[key]))
1745        print ('Phases')
1746        for key1 in self.Phases:
1747            print ('    '+key1+Show(self.Phases[key1]))
1748        print ('Histogram')
1749        for key1 in self.Histograms:
1750            print ('    '+key1+Show(self.Histograms[key1]))
1751            for key2 in self.Histograms[key1]:
1752                print ('      '+key2+Show(self.Histograms[key1][key2]))
1753
1754    def defaultSaveFile(self):
1755        return os.path.abspath(
1756            os.path.splitext(self.G2frame.GSASprojectfile
1757                             )[0]+self.extension)
1758       
1759    def askSaveFile(self):
1760        '''Ask the user to supply a file name
1761
1762        :returns: a file name (str) or None if Cancel is pressed
1763
1764        '''
1765        pth = G2G.GetExportPath(self.G2frame)
1766        if self.G2frame.GSASprojectfile:
1767            defnam = os.path.splitext(
1768                os.path.split(self.G2frame.GSASprojectfile)[1]
1769                )[0]+self.extension
1770        else:
1771            defnam = 'default' + self.extension
1772        return G2G.askSaveFile(self.G2frame,defnam,self.extension,self.longFormatName)
1773
1774    def askSaveDirectory(self):
1775        '''Ask the user to supply a directory name. Path name is used as the
1776        starting point for the next export path search.
1777
1778        :returns: a directory name (str) or None if Cancel is pressed
1779
1780        TODO: Can this be replaced with G2G.askSaveDirectory?
1781        '''
1782        pth = G2G.GetExportPath(self.G2frame)
1783        dlg = wx.DirDialog(
1784            self.G2frame, 'Input directory where file(s) will be written', pth,
1785            wx.DD_DEFAULT_STYLE)
1786        dlg.CenterOnParent()
1787        try:
1788            if dlg.ShowModal() == wx.ID_OK:
1789                filename = dlg.GetPath()
1790                self.G2frame.LastExportDir = filename
1791            else:
1792                filename = None
1793        finally:
1794            dlg.Destroy()
1795        return filename
1796
1797    # Tools for file writing.
1798    def OpenFile(self,fil=None,mode='w'):
1799        '''Open the output file
1800
1801        :param str fil: The name of the file to open. If None (default)
1802          the name defaults to self.dirname + self.filename.
1803          If an extension is supplied, it is not overridded,
1804          but if not, the default extension is used.
1805        :returns: the file object opened by the routine which is also
1806          saved as self.fp
1807        '''
1808        if mode == 'd': # debug mode
1809            self.fullpath = '(stdout)'
1810            self.fp = sys.stdout
1811            return
1812        if not fil:
1813            if not os.path.splitext(self.filename)[1]:
1814                self.filename += self.extension
1815            fil = os.path.join(self.dirname,self.filename)
1816        self.fullpath = os.path.abspath(fil)
1817        self.fp = open(self.fullpath,mode)
1818        return self.fp
1819
1820    def Write(self,line):
1821        '''write a line of output, attaching a line-end character
1822
1823        :param str line: the text to be written.
1824        '''
1825        self.fp.write(line+'\n')
1826       
1827    def CloseFile(self,fp=None):
1828        '''Close a file opened in OpenFile
1829
1830        :param file fp: the file object to be closed. If None (default)
1831          file object self.fp is closed.
1832        '''
1833        if self.fp == sys.stdout: return # debug mode
1834        if fp is None:
1835            fp = self.fp
1836            self.fp = None
1837        if fp is not None: fp.close()
1838       
1839    def SetSeqRef(self,data,hist):
1840        '''Set the exporter to retrieve results from a sequential refinement
1841        rather than the main tree
1842        '''
1843        self.SeqRefdata = data
1844        self.SeqRefhist = hist
1845        data_name = data[hist]
1846        for i,val in zip(data_name['varyList'],data_name['sig']):
1847            self.sigDict[i] = val
1848            self.sigDict[striphist(i)] = val
1849        for i in data_name['parmDict']:
1850            self.parmDict[striphist(i)] = data_name['parmDict'][i]
1851            self.parmDict[i] = data_name['parmDict'][i]
1852            # zero out the dA[xyz] terms, they would only bring confusion
1853            key = i.split(':')
1854            if len(key) < 3: continue
1855            if key[2].startswith('dA'):
1856                self.parmDict[i] = 0.0
1857        for i,(val,sig) in data_name.get('depParmDict',{}).items():
1858            self.parmDict[i] = val
1859            self.sigDict[i] = sig
1860        #GSASIIpath.IPyBreak()
1861
1862    def SetFromArray(self,hist,histname):
1863        '''Load a histogram into the exporter in preparation for use of the .Writer
1864        rather than the main tree. This is used in GSASIIscriptable when wx
1865        is not present.
1866        '''
1867        self.Histograms[histname] =  {}
1868        self.Histograms[histname]['Data'] = hist['data'][1]
1869        self.Histograms[histname]['Instrument Parameters'] = hist['Instrument Parameters']
1870        self.Histograms[histname]['Sample Parameters'] = hist['Sample Parameters']
1871
1872    # Tools to pull information out of the data arrays
1873    def GetCell(self,phasenam):
1874        """Gets the unit cell parameters and their s.u.'s for a selected phase
1875
1876        :param str phasenam: the name for the selected phase
1877        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1878          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1879          cell values and `cellSig` has their uncertainties.
1880        """
1881        if self.SeqRefdata and self.SeqRefhist:
1882            return self.GetSeqCell(phasenam,self.SeqRefdata[self.SeqRefhist])
1883        phasedict = self.Phases[phasenam] # pointer to current phase info
1884        try:
1885            pfx = str(phasedict['pId'])+'::'
1886            A,sigA = G2stIO.cellFill(pfx,phasedict['General']['SGData'],self.parmDict,self.sigDict)
1887            cellSig = G2stIO.getCellEsd(pfx,phasedict['General']['SGData'],A,
1888                self.OverallParms['Covariance'])  # returns 7 vals, includes sigVol
1889            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
1890            return cellList,cellSig
1891        except KeyError:
1892            cell = phasedict['General']['Cell'][1:]
1893            return cell,7*[0]
1894           
1895    def GetSeqCell(self,phasenam,data_name):
1896        """Gets the unit cell parameters and their s.u.'s for a selected phase
1897        and histogram in a sequential fit
1898
1899        :param str phasenam: the name for the selected phase
1900        :param dict data_name: the sequential refinement parameters for the selected histogram
1901        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1902          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1903          cell values and `cellSig` has their uncertainties.
1904        """
1905        phasedict = self.Phases[phasenam]
1906        SGdata = phasedict['General']['SGData']
1907        pId = phasedict['pId']
1908        RecpCellTerms = G2lat.cell2A(phasedict['General']['Cell'][1:7])
1909        ESDlookup = {}
1910        Dlookup = {}
1911        varied = [striphist(i) for i in data_name['varyList']]
1912        for item,val in data_name['newCellDict'].items():
1913            if item in varied:
1914                ESDlookup[val[0]] = item
1915                Dlookup[item] = val[0]
1916        A = RecpCellTerms[:]
1917        for i in range(6):
1918            var = str(pId)+'::A'+str(i)
1919            if var in ESDlookup:
1920                A[i] = data_name['newCellDict'][ESDlookup[var]][1] # override with refined value
1921        cellDict = dict(zip([str(pId)+'::A'+str(i) for i in range(6)],A))
1922        zeroDict = {i:0.0 for i in cellDict}
1923        A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata,cellDict,zeroDict)
1924        covData = {
1925            'varyList': [Dlookup.get(striphist(v),v) for v in data_name['varyList']],
1926            'covMatrix': data_name['covMatrix']
1927            }
1928        return list(G2lat.A2cell(A)) + [G2lat.calc_V(A)], G2stIO.getCellEsd(str(pId)+'::',SGdata,A,covData)
1929               
1930    def GetAtoms(self,phasenam):
1931        """Gets the atoms associated with a phase. Can be used with standard
1932        or macromolecular phases
1933
1934        :param str phasenam: the name for the selected phase
1935        :returns: a list of items for eac atom where each item is a list containing:
1936          label, typ, mult, xyz, and td, where
1937
1938          * label and typ are the atom label and the scattering factor type (str)
1939          * mult is the site multiplicity (int)
1940          * xyz is contains a list with four pairs of numbers:
1941            x, y, z and fractional occupancy and
1942            their standard uncertainty (or a negative value)
1943          * td is contains a list with either one or six pairs of numbers:
1944            if one number it is U\ :sub:`iso` and with six numbers it is
1945            U\ :sub:`11`, U\ :sub:`22`, U\ :sub:`33`, U\ :sub:`12`, U\ :sub:`13` & U\ :sub:`23`
1946            paired with their standard uncertainty (or a negative value)
1947        """
1948        phasedict = self.Phases[phasenam] # pointer to current phase info           
1949        cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1950        cfrac = cx+3
1951        fpfx = str(phasedict['pId'])+'::Afrac:'       
1952        atomslist = []
1953        for i,at in enumerate(phasedict['Atoms']):
1954            if phasedict['General']['Type'] == 'macromolecular':
1955                label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
1956            else:
1957                label = at[ct-1]
1958            fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1959            fsig = self.sigDict.get(fpfx+str(i),-0.009)
1960            mult = at[cs+1]
1961            typ = at[ct]
1962            xyz = []
1963            for j,v in enumerate(('x','y','z')):
1964                val = at[cx+j]
1965                pfx = str(phasedict['pId']) + '::A' + v + ':' + str(i)
1966                val = self.parmDict.get(pfx, val)
1967                dpfx = str(phasedict['pId'])+'::dA'+v+':'+str(i)
1968                sig = self.sigDict.get(dpfx,-0.000009)
1969                xyz.append((val,sig))
1970            xyz.append((fval,fsig))
1971            td = []
1972            if at[cia] == 'I':
1973                pfx = str(phasedict['pId'])+'::AUiso:'+str(i)
1974                val = self.parmDict.get(pfx,at[cia+1])
1975                sig = self.sigDict.get(pfx,-0.0009)
1976                td.append((val,sig))
1977            else:
1978                for i,var in enumerate(('AU11','AU22','AU33','AU12','AU13','AU23')):
1979                    pfx = str(phasedict['pId'])+'::'+var+':'+str(i)
1980                    val = self.parmDict.get(pfx,at[cia+2+i])
1981                    sig = self.sigDict.get(pfx,-0.0009)
1982                    td.append((val,sig))
1983            atomslist.append((label,typ,mult,xyz,td))
1984        return atomslist
1985######################################################################
1986def ExportPowderList(G2frame):
1987    '''Returns a list of extensions supported by :func:`GSASIIIO:ExportPowder`
1988    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
1989   
1990    :param wx.Frame G2frame: the GSAS-II main data tree window
1991    '''
1992    extList = []
1993    for obj in G2frame.exporterlist:
1994        if 'powder' in obj.exporttype:
1995            try:
1996                obj.Writer
1997                extList.append(obj.extension)
1998            except AttributeError:
1999                pass
2000    return extList
2001
2002def ExportPowder(G2frame,TreeName,fileroot,extension):
2003    '''Writes a single powder histogram using the Export routines.
2004    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
2005
2006    :param wx.Frame G2frame: the GSAS-II main data tree window
2007    :param str TreeName: the name of the histogram (PWDR ...) in the data tree
2008    :param str fileroot: name for file to be written, extension ignored
2009    :param str extension: extension for file to be written (start with '.'). Must
2010      match a powder export routine that has a Writer object.
2011    '''
2012    filename = os.path.abspath(os.path.splitext(fileroot)[0]+extension)
2013    for obj in G2frame.exporterlist:
2014        if obj.extension == extension and 'powder' in obj.exporttype:
2015            obj.currentExportType = 'powder'
2016            obj.InitExport(None)
2017            obj.loadTree() # load all histograms in tree into dicts
2018            if TreeName not in obj.Histograms:
2019                raise Exception('Histogram not found: '+str(TreeName))
2020            try:
2021                obj.Writer
2022            except AttributeError:
2023                continue
2024            try:
2025                obj.Writer(TreeName,filename)
2026                print('wrote file '+filename)
2027                return
2028            except Exception:
2029                print('Export Routine for '+extension+' failed.')
2030    else:
2031        print('No Export routine supports extension '+extension)
2032
2033def ExportSequential(G2frame,data,obj,exporttype):
2034    '''
2035    Used to export from every phase/dataset in a sequential refinement using
2036    a .Writer method for either projects or phases. Prompts to select histograms
2037    and for phase exports, which phase(s).
2038
2039    :param wx.Frame G2frame: the GSAS-II main data tree window
2040    :param dict data: the sequential refinement data object
2041    :param str exporttype: indicates the type of export ('project' or 'phase')
2042    '''
2043    if len(data['histNames']) == 0:
2044        G2G.G2MessageBox(G2frame,'There are no sequential histograms','Warning')
2045    obj.InitExport(None)
2046    obj.loadTree()
2047    obj.loadParmDict()
2048    if len(data['histNames']) == 1:
2049        histlist = data['histNames']
2050    else:
2051        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select histograms to export from list',
2052                                 'Select histograms',data['histNames'])
2053        if dlg.ShowModal() == wx.ID_OK:
2054            histlist = [data['histNames'][l] for l in dlg.GetSelections()]
2055            dlg.Destroy()
2056        else:
2057            dlg.Destroy()
2058            return
2059    if exporttype == 'Phase':
2060        phaselist = list(obj.Phases.keys())
2061        if len(obj.Phases) == 0:
2062            G2G.G2MessageBox(G2frame,'There are no phases in sequential ref.','Warning')
2063            return
2064        elif len(obj.Phases) > 1:
2065            dlg = G2G.G2MultiChoiceDialog(G2frame,'Select phases to export from list',
2066                                    'Select phases', phaselist)
2067            if dlg.ShowModal() == wx.ID_OK:
2068                phaselist = [phaselist[l] for l in dlg.GetSelections()]
2069                dlg.Destroy()
2070            else:
2071                dlg.Destroy()
2072                return
2073        filename = obj.askSaveFile()
2074        if not filename: return True
2075        obj.dirname,obj.filename = os.path.split(filename)
2076        print('Writing output to file '+str(obj.filename)+"...")
2077        mode = 'w'
2078        for p in phaselist:
2079            for h in histlist:
2080                obj.SetSeqRef(data,h)
2081                #GSASIIpath.IPyBreak()
2082                obj.Writer(h,phasenam=p,mode=mode)
2083                mode = 'a'
2084        print('...done')
2085    elif exporttype == 'Project':  # note that the CIF exporter is not yet ready for this
2086        filename = obj.askSaveFile()
2087        if not filename: return True
2088        obj.dirname,obj.filename = os.path.split(filename)
2089        print('Writing output to file '+str(obj.filename)+"...")
2090        mode = 'w'
2091        for h in histlist:
2092            obj.SetSeqRef(data,h)
2093            obj.Writer(h,mode=mode)
2094            print('\t'+str(h)+' written')
2095            mode = 'a'
2096        print('...done')
2097    elif exporttype == 'Powder':
2098        filename = obj.askSaveFile()
2099        if not filename: return True
2100        obj.dirname,obj.filename = os.path.split(filename)
2101        print('Writing output to file '+str(obj.filename)+"...")
2102        mode = 'w'
2103        for h in histlist:
2104            obj.SetSeqRef(data,h)
2105            obj.Writer(h,mode=mode)
2106            print('\t'+str(h)+' written')
2107            mode = 'a'
2108        print('...done')
2109
2110def ReadDIFFaX(DIFFaXfile):
2111    print ('read '+DIFFaXfile)
2112    Layer = {'Laue':'-1','Cell':[False,1.,1.,1.,90.,90.,90,1.],'Width':[[10.,10.],[False,False]],
2113        'Layers':[],'Stacking':[],'Transitions':[],'Toler':0.01,'AtInfo':{}}
2114    df = open(DIFFaXfile,'r')
2115    lines = df.readlines()
2116    df.close()
2117    struct = False
2118    Struct = []
2119    stack = False
2120    Stack = []
2121    trans = False
2122    Trans = []
2123    for diff in lines:
2124        diff = diff[:-1].lower()
2125        if '!'  in diff:
2126            continue
2127        while '}' in diff: #strip comments
2128            iB = diff.index('{')
2129            iF = diff.index('}')+1
2130            if iB:
2131                diff = diff[:iB]
2132            else:
2133                diff = diff[iF:]
2134        if not diff:
2135            continue
2136        if diff.strip() == 'instrumental':
2137            continue
2138        if diff.strip() == 'structural':
2139            struct = True
2140            continue
2141        elif diff.strip() == 'stacking':
2142            struct = False
2143            stack = True
2144            continue
2145        elif diff.strip() == 'transitions':
2146            stack = False
2147            trans = True
2148            continue
2149        diff = diff.strip()
2150        if struct:
2151            if diff:
2152                Struct.append(diff)
2153        elif stack:
2154            if diff:
2155                Stack.append(diff)
2156        elif trans:
2157            if diff:
2158                Trans.append(diff)
2159   
2160#STRUCTURE records
2161    laueRec = Struct[1].split()
2162    Layer['Laue'] = laueRec[0]
2163    if Layer['Laue'] == 'unknown' and len(laueRec) > 1:
2164        Layer['Toler'] = float(laueRec[1])    #tolerance for 'unknown'?
2165    if Layer['Laue'] == '2/m(1)': Layer['Laue'] = '2/m(c)'
2166    if Layer['Laue'] == '2/m(2)': Layer['Laue'] = '2/m(ab)'
2167    cell = Struct[0].split()
2168    Layer['Cell'] = [False,float(cell[0]),float(cell[1]),float(cell[2]),90.,90.,float(cell[3]),1.0]
2169    nLayers = int(Struct[2])
2170    N = 3
2171    if 'layer' not in Struct[3]:
2172        N = 4
2173        if Struct[3] != 'infinite':
2174            width = Struct[3].split()
2175            Layer['Width'][0] = [float(width[0]),float(width[1])]
2176    for nL in range(nLayers):
2177        if '=' in Struct[N]:
2178            name = Struct[N].split('=')
2179            sameas = int(name[1])-1
2180            Layer['Layers'].append({'Name':name[0],'SameAs':Layer['Layers'][sameas]['Name'],'Symm':'None','Atoms':[]})
2181            N += 1
2182            continue
2183        Symm = 'None'
2184        if 'centro' in Struct[N+1]: Symm = '-1'
2185        Layer['Layers'].append({'Name':Struct[N],'SameAs':'','Symm':Symm,'Atoms':[]})
2186        N += 2
2187        while 'layer' not in Struct[N]:
2188            atom = Struct[N][4:].split()
2189            atomType = G2el.FixValence(Struct[N][:4].replace(' ','').strip().capitalize())
2190            if atomType not in Layer['AtInfo']:
2191                Layer['AtInfo'][atomType] = G2el.GetAtomInfo(atomType)
2192            atomName = '%s(%s)'%(atomType,atom[0])
2193            newVals = []
2194            for val in atom[1:6]:
2195                if '/' in val:
2196                    newVals.append(eval(val+'.'))
2197                else:
2198                    newVals.append(float(val))               
2199            atomRec = [atomName,atomType,newVals[0],newVals[1],newVals[2],newVals[4],newVals[3]/78.9568]
2200            Layer['Layers'][-1]['Atoms'].append(atomRec)
2201            N += 1
2202            if N > len(Struct)-1:
2203                break
2204#TRANSITIONS records
2205    transArray = []
2206    N = 0
2207    for i in range(nLayers):
2208        transArray.append([])
2209        for j in range(nLayers):
2210            vals = Trans[N].split()
2211            newVals = []
2212            for val in vals[:4]:
2213                if '/' in val:
2214                    newVals.append(eval(val+'.'))
2215                else:
2216                    newVals.append(float(val))
2217            transArray[-1].append(newVals+['',False])
2218            N += 1
2219    Layer['Transitions'] = transArray
2220#STACKING records
2221    Layer['Stacking'] = [Stack[0],'']
2222    if Stack[0] == 'recursive':
2223        Layer['Stacking'][1] = Stack[1]
2224    elif Stack[0] == 'explicit':
2225        if Stack[1] == 'random':
2226            Layer['Stacking'][1] = Stack[1]
2227        else:
2228            Layer['Stacking'][1] = 'list'
2229            Layer['Stacking'].append('')
2230            for stack in Stack[2:]:
2231                Layer['Stacking'][2] += ' '+stack
2232    return Layer
2233
2234def readColMetadata(imagefile):
2235    '''Reads image metadata from a column-oriented metadata table
2236    (1-ID style .par file). Called by :func:`GetColumnMetadata`
2237   
2238    The .par file has any number of columns separated by spaces.
2239    The directory for the file must be specified in
2240    Config variable ``Column_Metadata_directory``.
2241    As an index to the .par file a second "label file" must be specified with the
2242    same file root name as the .par file but the extension must be .XXX_lbls (where
2243    .XXX is the extension of the image) or if that is not present extension
2244    .lbls.
2245
2246    :param str imagefile: the full name of the image file (with extension, directory optional)
2247
2248    :returns: a dict with parameter values. Named parameters will have the type based on
2249       the specified Python function, named columns will be character strings
2250   
2251    The contents of the label file will look like this::
2252   
2253        # define keywords
2254        filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34
2255        distance: float | 75
2256        wavelength:lambda keV: 12.398425/float(keV)|9
2257        pixelSize:lambda x: [74.8, 74.8]|0
2258        ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4
2259        Temperature: float|53
2260        FreePrm2: int | 34 | Free Parm2 Label
2261        # define other variables
2262        0:day
2263        1:month
2264        2:date
2265        3:time
2266        4:year
2267        7:I_ring
2268
2269    This file contains three types of lines in any order.
2270     * Named parameters are evaluated with user-supplied Python code (see
2271       subsequent information). Specific named parameters are used
2272       to determine values that are used for image interpretation (see table,
2273       below). Any others are copied to the Comments subsection of the Image
2274       tree item.
2275     * Column labels are defined with a column number (integer) followed by
2276       a colon (:) and a label to be assigned to that column. All labeled
2277       columns are copied to the Image's Comments subsection.
2278     * Comments are any line that does not contain a colon.
2279
2280    Note that columns are numbered starting at zero.
2281
2282    Any named parameter may be defined provided it is not a valid integer,
2283    but the named parameters in the table have special meanings, as descibed.
2284    The parameter name is followed by a colon. After the colon, specify
2285    Python code that defines or specifies a function that will be called to
2286    generate a value for that parameter.
2287
2288    Note that several keywords, if defined in the Comments, will be found and
2289    placed in the appropriate section of the powder histogram(s)'s Sample
2290    Parameters after an integration: ``Temperature``,``Pressure``,``Time``,
2291    ``FreePrm1``,``FreePrm2``,``FreePrm3``,``Omega``,``Chi``, and ``Phi``.
2292
2293    After the Python code, supply a vertical bar (|) and then a list of one
2294    more more columns that will be supplied as arguments to that function.
2295
2296    Note that the labels for the three FreePrm items can be changed by
2297    including that label as a third item with an additional vertical bar. Labels
2298    will be ignored for any other named parameters.
2299   
2300    The examples above are discussed here:
2301
2302    ``filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34``
2303        Here the function to be used is defined with a lambda statement::
2304       
2305          lambda x,y: "{}_{:0>6}".format(x,y)
2306
2307        This function will use the format function to create a file name from the
2308        contents of columns 33 and 34. The first parameter (x, col. 33) is inserted directly into
2309        the file name, followed by a underscore (_), followed by the second parameter (y, col. 34),
2310        which will be left-padded with zeros to six characters (format directive ``:0>6``).
2311
2312        When there will be more than one image generated per line in the .par file, an alternate way to
2313        generate list of file names takes into account the number of images generated::
2314
2315          lambda x,y,z: ["{}_{:0>6}".format(x,int(y)+i) for i in range(int(z))]
2316
2317        Here a third parameter is used to specify the number of images generated, where
2318        the image number is incremented for each image.
2319         
2320    ``distance: float | 75``
2321        Here the contents of column 75 will be converted to a floating point number
2322        by calling float on it. Note that the spaces here are ignored.
2323       
2324    ``wavelength:lambda keV: 12.398425/float(keV)|9``
2325        Here we define an algebraic expression to convert an energy in keV to a
2326        wavelength and pass the contents of column 9 as that input energy
2327       
2328    ``pixelSize:lambda x: [74.8, 74.8]|0``
2329        In this case the pixel size is a constant (a list of two numbers). The first
2330        column is passed as an argument as at least one argument is required, but that
2331        value is not used in the expression.
2332
2333    ``ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4``
2334        This example defines a parameter that takes items in the first five columns
2335        and formats them in a different way. This parameter is not one of the pre-defined
2336        parameter names below. Some external code could be used to change the month string
2337        (argument ``m``) to a integer from 1 to 12.
2338       
2339    ``FreePrm2: int | 34 | Free Parm2 Label``
2340        In this example, the contents of column 34 will be converted to an integer and
2341        placed as the second free-named parameter in the Sample Parameters after an
2342        integration. The label for this parameter will be changed to "Free Parm2 Label".
2343           
2344    **Pre-defined parameter names**
2345   
2346    =============  =========  ========  =====================================================
2347     keyword       required    type      Description
2348    =============  =========  ========  =====================================================
2349       filename    yes         str or   generates the file name prefix for the matching image
2350                               list     file (MyImage001 for file /tmp/MyImage001.tif) or
2351                                        a list of file names.
2352     polarization  no         float     generates the polarization expected based on the
2353                                        monochromator angle, defaults to 0.99.
2354       center      no         list of   generates the approximate beam center on the detector
2355                              2 floats  in mm, such as [204.8, 204.8].
2356       distance    yes        float     generates the distance from the sample to the detector
2357                                        in mm
2358       pixelSize   no         list of   generates the size of the pixels in microns such as
2359                              2 floats  [200.0, 200.0].
2360       wavelength  yes        float     generates the wavelength in Angstroms
2361    =============  =========  ========  =====================================================
2362   
2363    '''
2364    dir,fil = os.path.split(os.path.abspath(imagefile))
2365    imageName,ext = os.path.splitext(fil)
2366    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
2367    parfiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
2368    if len(parfiles) == 0:
2369        print('Sorry, No Column metadata (.par) file found in '+
2370              GSASIIpath.GetConfigValue('Column_Metadata_directory'))
2371        return {}
2372    for parFil in parfiles: # loop over all .par files (hope just 1) in image dir until image is found
2373        parRoot = os.path.splitext(parFil)[0]
2374        for e in (ext+'_lbls','.lbls'):
2375            if os.path.exists(parRoot+e):
2376                lblFil = parRoot+e
2377                break
2378        else:
2379            print('Warning: No labels definitions found for '+parFil)
2380            continue
2381        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFil)
2382        if errors:
2383            print('Errors in labels file '+lblFil)
2384            for i in errors: print('  '+i)
2385            continue
2386        else:
2387            print('Read '+lblFil)
2388        # scan through each line in this .par file, looking for the matching image rootname
2389        fp = open(parFil,'Ur')
2390        for iline,line in enumerate(fp):
2391            items = line.strip().split(' ')
2392            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
2393            if type(nameList) is str:
2394                if nameList != imageName: continue
2395                name = nameList
2396            else:
2397                for name in nameList:
2398                    if name == imageName: break # got a match
2399                else:
2400                    continue
2401            # parse the line and finish
2402            metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp)
2403            metadata['par file'] = parFil
2404            metadata['lbls file'] = lblFil
2405            print("Metadata read from {} line {}".format(parFil,iline+1))
2406            fp.close()
2407            return metadata
2408        else:
2409            print("Image {} not found in {}".format(imageName,parFil))
2410            fp.close()
2411            continue
2412        fp.close()
2413    else:
2414        print("Warning: No .par metadata for image {}".format(imageName))
2415        return {}
2416
2417def readColMetadataLabels(lblFil):
2418    '''Read the .*lbls file and setup for metadata assignments
2419    '''
2420    lbldict = {}
2421    keyExp = {}
2422    keyCols = {}
2423    labels = {}
2424    errors = []
2425    fp = open(lblFil,'Ur')         # read column labels
2426    for iline,line in enumerate(fp): # read label definitions
2427        line = line.strip()
2428        if not line or line[0] == '#': continue # comments
2429        items = line.split(':')
2430        if len(items) < 2: continue # lines with no colon are also comments
2431        # does this line a definition for a named parameter?
2432        key = items[0]
2433        try: 
2434            int(key)
2435        except ValueError: # try as named parameter since not a valid number
2436            items = line.split(':',1)[1].split('|')
2437            try:
2438                f = eval(items[0]) # compile the expression
2439                if not callable(f):
2440                    errors += ['Expression "{}" for key {} is not a function (line {})'.
2441                           format(items[0],key,iline)]
2442                    continue
2443                keyExp[key] = f
2444            except Exception as msg:
2445                errors += ['Expression "{}" for key {} is not valid (line {})'.
2446                           format(items[0],key,iline)]
2447                errors += [str(msg)]
2448                continue
2449            keyCols[key] = [int(i) for i in items[1].strip().split(',')]
2450            if key.lower().startswith('freeprm') and len(items) > 2:
2451                labels[key] = items[2]
2452            continue
2453        if len(items) == 2: # simple column definition
2454            lbldict[int(items[0])] = items[1]
2455    fp.close()
2456    if 'filename' not in keyExp:
2457        errors += ["File {} is invalid. No valid filename expression.".format(lblFil)]
2458    return labels,lbldict,keyCols,keyExp,errors
2459
2460def evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,ShowError=False):
2461    '''Evaluate the metadata for a line in the .par file
2462    '''
2463    metadata = {lbldict[j]:items[j] for j in lbldict}
2464    named = {}
2465    for key in keyExp:
2466        try:
2467            res = keyExp[key](*[items[j] for j in keyCols[key]])
2468        except:
2469            if ShowError:
2470                res = "*** error ***"
2471            else:
2472                continue
2473        named[key] = res
2474    metadata.update(named)
2475    for lbl in labels: # add labels for FreePrm's
2476        metadata['label_'+lbl[4:].lower()] = labels[lbl]
2477    return metadata
2478
2479def GetColumnMetadata(reader):
2480    '''Add metadata to an image from a column-type metadata file
2481    using :func:`readColMetadata`
2482   
2483    :param reader: a reader object from reading an image
2484   
2485    '''
2486    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
2487    parParms = readColMetadata(reader.readfilename)
2488    if not parParms: return # check for read failure
2489    specialKeys = ('filename',"polarization", "center", "distance", "pixelSize", "wavelength",)
2490    reader.Comments = ['Metadata from {} assigned by {}'.format(parParms['par file'],parParms['lbls file'])]
2491    for key in parParms:
2492        if key in specialKeys+('par file','lbls file'): continue
2493        reader.Comments += ["{} = {}".format(key,parParms[key])]
2494    if "polarization" in parParms:
2495        reader.Data['PolaVal'][0] = parParms["polarization"]
2496    else:
2497        reader.Data['PolaVal'][0] = 0.99
2498    if "center" in parParms:
2499        reader.Data['center'] = parParms["center"]
2500    if "pixelSize" in parParms:
2501        reader.Data['pixelSize'] = parParms["pixelSize"]
2502    if "wavelength" in parParms:
2503        reader.Data['wavelength'] = parParms['wavelength']
2504    else:
2505        print('Error: wavelength not defined in {}'.format(parParms['lbls file']))
2506    if "distance" in parParms:
2507        reader.Data['distance'] = parParms['distance']
2508        reader.Data['setdist'] = parParms['distance']
2509    else:
2510        print('Error: distance not defined in {}'.format(parParms['lbls file']))
2511
2512def testColumnMetadata(G2frame):
2513    '''Test the column-oriented metadata parsing, as implemented at 1-ID, by showing results
2514    when using a .par and .lbls pair.
2515   
2516     * Select a .par file, if more than one in selected dir.
2517     * Select the .*lbls file, if more than one matching .par file.
2518     * Parse the .lbls file, showing errors if encountered; loop until errors are fixed.
2519     * Search for an image or a line in the .par file and show the results when interpreted
2520     
2521    See :func:`readColMetadata` for more details.
2522    '''
2523    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'):
2524        G2G.G2MessageBox(G2frame,'The configuration option for I-ID Metadata is not set.\n'+
2525                         'Please use the File/Preferences menu to set Column_Metadata_directory',
2526                         'Warning')
2527        return
2528    parFiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
2529    if not parFiles: 
2530        G2G.G2MessageBox(G2frame,'No .par files found in directory {}. '
2531                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
2532                         '\nThis is set by config variable Column_Metadata_directory '+
2533                         '(Set in File/Preferences menu).',
2534                         'Warning')
2535        return
2536    parList = []
2537    for parFile in parFiles:
2538        lblList = []
2539        parRoot = os.path.splitext(parFile)[0]
2540        for f in glob.glob(parRoot+'.*lbls'):
2541            if os.path.exists(f): lblList.append(f)
2542        if not len(lblList):
2543            continue
2544        parList.append(parFile)
2545    if len(parList) == 0:
2546        G2G.G2MessageBox(G2frame,'No .lbls or .EXT_lbls file found for .par file(s) in directory {}. '
2547                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
2548                         '\nThis is set by config variable Column_Metadata_directory '+
2549                         '(Set in File/Preferences menu).',
2550                         'Warning')
2551        return
2552    elif len(parList) == 1:
2553        parFile = parList[0]
2554    else:
2555        dlg = G2G.G2SingleChoiceDialog(G2frame,
2556                'More than 1 .par file found. (Better if only 1!). Choose the one to test in '+
2557                GSASIIpath.GetConfigValue('Column_Metadata_directory'),
2558                'Choose .par file', [os.path.split(i)[1] for i in parList])
2559        if dlg.ShowModal() == wx.ID_OK:
2560            parFile = parList[dlg.GetSelection()]
2561            dlg.Destroy()
2562        else:
2563            dlg.Destroy()
2564            return
2565    # got .par file; now work on .*lbls file
2566    lblList = []
2567    parRoot = os.path.splitext(parFile)[0]
2568    for f in glob.glob(parRoot+'.*lbls'):
2569        if os.path.exists(f): lblList.append(f)
2570    if not len(lblList):
2571        raise Exception('How did this happen! No .*lbls files for '+parFile)
2572    elif len(lblList) == 1:
2573        lblFile = lblList[0]
2574    else:
2575        dlg = G2G.G2SingleChoiceDialog(G2frame,
2576                'Select label file for .par file '+parFile,
2577                'Choose label file', [os.path.split(i)[1] for i in lblList])
2578        if dlg.ShowModal() == wx.ID_OK:
2579            lblFile = lblList[dlg.GetSelection()]
2580            dlg.Destroy()
2581        else:
2582            dlg.Destroy()
2583            return
2584    # parse the labels file
2585    errors = True
2586    while errors:
2587        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFile)
2588        if errors:
2589            t = "Error reading file "+lblFile
2590            for l in errors:
2591                t += '\n'
2592                t += l
2593            t += "\n\nPlease edit the file and press OK (or Cancel to quit)"
2594            dlg = wx.MessageDialog(G2frame,message=t,
2595                caption="Read Error",style=wx.ICON_ERROR| wx.OK|wx.STAY_ON_TOP|wx.CANCEL)
2596            if dlg.ShowModal() != wx.ID_OK: return           
2597    # request a line number, read that line
2598    dlg = G2G.SingleStringDialog(G2frame,'Read what',
2599                                 'Enter a line number or an image file name (-1=last line)',
2600                                 '-1',size=(400,-1))
2601    if dlg.Show():
2602        fileorline = dlg.GetValue()
2603        dlg.Destroy()
2604    else:
2605        dlg.Destroy()
2606        return
2607    # and report the generated key pairs in metadata dict
2608    linenum = None
2609    try:
2610        linenum = int(fileorline)
2611    except:
2612        imageName = os.path.splitext(os.path.split(fileorline)[1])[0]
2613
2614    fp = open(parFile,'Ur')
2615    for iline,line in enumerate(fp):
2616        if linenum is not None:
2617            if iline == linenum:
2618                items = line.strip().split(' ')
2619                n = "Line {}".format(iline)
2620                break
2621            else:
2622                continue
2623        else:
2624            items = line.strip().split(' ')
2625            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
2626            if type(nameList) is str:
2627                if nameList != imageName: continue
2628                name = nameList
2629                break
2630            else:
2631                for name in nameList:
2632                    print (name,name == imageName)
2633                    if name == imageName:
2634                        n = "Image {} found in line {}".format(imageName,iline)
2635                        break # got a match
2636                else:
2637                    continue
2638                break
2639    else:
2640        if linenum is not None:
2641            n = "Line {}".format(iline)
2642        else:
2643            n = "Image {} not found. Reporting line {}".format(imageName,iline)
2644        items = line.strip().split(' ')
2645    fp.close()
2646    metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,True)
2647    title = "Results: ("+n+")"
2648    t = ['Files: '+parFile,lblFile,' ']
2649    n = ["Named parameters:"]
2650    l = ['',"Labeled columns:"]
2651    for key in sorted(metadata):
2652        if key == "filename" or key.startswith('label_prm'): continue
2653        if key in keyCols:
2654            n += [{} = {}".format(key,metadata[key])]
2655        elif key in lbldict.values():
2656            l += [{} = {}".format(key,metadata[key])]
2657        else:
2658            t += ["** Unexpected:  {}".format(key,metadata[key])]
2659    if type(metadata['filename']) is str:
2660        l += ["","Filename: "+ metadata['filename']]
2661    else:
2662        l += ["","Filename(s): "]
2663        for i,j in enumerate(metadata['filename']):
2664            if i: l[-1] += ', '
2665            l[-1] += j
2666    t += n + l + ['','Unused columns:']
2667    usedCols = list(lbldict.keys())
2668    for i in keyCols.values(): usedCols += i
2669    for i in range(len(items)):
2670        if i in usedCols: continue
2671        t += [{}: {}".format(i,items[i])]
2672    dlg = G2G.G2SingleChoiceDialog(None,title,'Column metadata parse results',t,
2673                                   monoFont=True,filterBox=False,size=(400,600),
2674                                   style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE|wx.OK)
2675    dlg.ShowModal()
2676
2677if __name__ == '__main__':
2678    import GSASIIdataGUI
2679    application = GSASIIdataGUI.GSASIImain(0)
2680    G2frame = application.main
2681    #app = wx.PySimpleApp()
2682    #G2frame = wx.Frame(None) # create a frame
2683    #frm.Show(True)
2684    #filename = '/tmp/notzip.zip'
2685    #filename = '/tmp/all.zip'
2686    #filename = '/tmp/11bmb_7652.zip'
2687   
2688    #selection=None, confirmoverwrite=True, parent=None
2689    #print ExtractFileFromZip(filename, selection='11bmb_7652.fxye',parent=frm)
2690    #print ExtractFileFromZip(filename,multipleselect=True)
2691    #                         #confirmread=False, confirmoverwrite=False)
2692
2693    # choicelist=[ ('a','b','c'),
2694    #              ('test1','test2'),('no choice',)]
2695    # titles = [ 'a, b or c', 'tests', 'No option here']
2696    # dlg = MultipleChoicesDialog(
2697    #     choicelist,titles,
2698    #     parent=frm)
2699    # if dlg.ShowModal() == wx.ID_OK:
2700    #     print 'Got OK'
2701    imagefile = '/tmp/NDC5_00237_3.ge3'
2702    Comments, Data, Npix, Image = GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None)
2703
2704    print("\n\nResults loaded to Comments, Data, Npix and Image\n\n")
2705
2706    GSASIIpath.IPyBreak_base()
Note: See TracBrowser for help on using the repository browser.