source: trunk/GSASIIIO.py @ 3373

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

make sequential refinement setting work like a global flag; add indicator to g2frame legend; clean up constrain selection by showing only wildcards in seq; and not showing them in for non-eq.; speed up some calls of G2stIO.GetHistogramPhaseData? but providing only current histogram (in seq. ref.)

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