source: trunk/GSASIIIO.py @ 3333

Last change on this file since 3333 was 3333, checked in by vondreele, 4 years ago

add new Parm menu item Load Multi Contols for load image controls for a suite of images based on 2-theta setting for detector position.

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