source: trunk/GSASIIIO.py @ 4184

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

fix GPX files with a colour object in them

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