source: trunk/GSASIIIO.py @ 3426

Last change on this file since 3426 was 3426, checked in by toby, 3 years ago

Add scan of .gpx objects; try to fix bug on drag

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