source: trunk/GSASIIIO.py @ 3772

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

add GUI (seen in debug mode only) for general (equation specified) restraints

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