source: trunk/GSASIIIO.py @ 3330

Last change on this file since 3330 was 3330, checked in by vondreele, 5 years ago

modify Bruker image import to keep "TARGET" and use it in SaveIntegration? to put Ka1/Ka2 wavelengths into PWDR Instrument Parameters

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