source: trunk/GSASIIimgGUI.py @ 4325

Last change on this file since 4325 was 4325, checked in by vondreele, 22 months ago

small RMCProfile fixes
New auto spot mask version - works

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 187.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASII - image data display routines
3########### SVN repository information ###################
4# $Date: 2020-02-22 17:14:33 +0000 (Sat, 22 Feb 2020) $
5# $Author: vondreele $
6# $Revision: 4325 $
7# $URL: trunk/GSASIIimgGUI.py $
8# $Id: GSASIIimgGUI.py 4325 2020-02-22 17:14:33Z vondreele $
9########### SVN repository information ###################
10'''
11*GSASIIimgGUI: Image GUI*
12-------------------------
13
14Control image display and processing
15
16'''
17from __future__ import division, print_function
18import os
19import copy
20import glob
21import time
22import re
23import math
24import sys
25import wx
26import wx.lib.mixins.listctrl  as  listmix
27import wx.grid as wg
28import matplotlib as mpl
29import numpy as np
30import numpy.ma as ma
31import GSASIIpath
32GSASIIpath.SetVersionNumber("$Revision: 4325 $")
33import GSASIIimage as G2img
34import GSASIImath as G2mth
35import GSASIIElem as G2elem
36import GSASIIpwdGUI as G2pdG
37import GSASIIplot as G2plt
38import GSASIIIO as G2IO
39import GSASIIfiles as G2fil
40import GSASIIdataGUI as G2gd
41import GSASIIctrlGUI as G2G
42import GSASIIobj as G2obj
43import GSASIIpy3 as G2py3
44import ImageCalibrants as calFile
45
46# documentation build kludge. This prevents an error with sphinx 1.8.5 (fixed by 2.3) where all mock objects are of type _MockObject
47if (type(wx.ListCtrl).__name__ ==
48        type(listmix.ListCtrlAutoWidthMixin).__name__ ==
49        type(listmix.TextEditMixin).__name__):
50    print('Using Sphinx 1.8.5 _MockObject kludge fix in GSASIIimgGUI')
51    class Junk1(object): pass
52    listmix.ListCtrlAutoWidthMixin = Junk1
53    class Junk2(object): pass
54    listmix.TextEditMixin = Junk2
55
56VERY_LIGHT_GREY = wx.Colour(235,235,235)
57WACV = wx.ALIGN_CENTER_VERTICAL
58
59# trig functions in degrees
60sind = lambda x: math.sin(x*math.pi/180.)
61tand = lambda x: math.tan(x*math.pi/180.)
62cosd = lambda x: math.cos(x*math.pi/180.)
63asind = lambda x: 180.*math.asin(x)/math.pi
64tth2q = lambda t,w:4.0*math.pi*sind(t/2.0)/w
65tof2q = lambda t,C:2.0*math.pi*C/t
66atand = lambda x: 180.*math.atan(x)/math.pi
67atan2d = lambda y,x: 180.*math.atan2(y,x)/math.pi
68
69################################################################################
70##### Image Data
71################################################################################
72
73def GetImageZ(G2frame,data,newRange=False):
74    '''Gets image & applies dark, background & flat background corrections.
75
76    :param wx.Frame G2frame: main GSAS-II frame
77    :param: dict data: Image Controls dictionary
78
79    :returns: array sumImg: corrected image for background/dark/flat back
80    '''
81    # Note that routine GSASIIscriptable._getCorrImage is based on this
82    # so changes made here should be repeated there.
83   
84    Npix,imagefile,imagetag = G2IO.GetCheckImageFile(G2frame,G2frame.Image)
85    if imagefile is None: return []
86    formatName = data.get('formatName','')
87    sumImg = np.array(G2IO.GetImageData(G2frame,imagefile,True,ImageTag=imagetag,FormatName=formatName),dtype='int32')
88    if sumImg is None:
89        return []
90    darkImg = False
91    if 'dark image' in data:
92        darkImg,darkScale = data['dark image']
93        if darkImg:
94            Did = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, darkImg)
95            if Did:
96                Ddata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Did,'Image Controls'))
97                dformatName = Ddata.get('formatName','')
98                Npix,darkfile,imagetag = G2IO.GetCheckImageFile(G2frame,Did)
99                darkImage = G2IO.GetImageData(G2frame,darkfile,True,ImageTag=imagetag,FormatName=dformatName)
100                if darkImg is not None:               
101                    sumImg += np.array(darkImage*darkScale,dtype='int32')
102    if 'background image' in data:
103        backImg,backScale = data['background image']           
104        if backImg:     #ignores any transmission effect in the background image
105            Bid = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, backImg)
106            if Bid:
107                Npix,backfile,imagetag = G2IO.GetCheckImageFile(G2frame,Bid)
108                Bdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Bid,'Image Controls'))
109                bformatName = Bdata.get('formatName','')
110                backImage = G2IO.GetImageData(G2frame,backfile,True,ImageTag=imagetag,FormatName=bformatName)
111                if darkImg and backImage is not None:
112                    backImage += np.array(darkImage*darkScale/backScale,dtype='int32')
113                if backImage is not None:
114                    sumImg += np.array(backImage*backScale,dtype='int32')
115    if 'Gain map' in data:
116        gainMap = data['Gain map']
117        if gainMap:
118            GMid = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, gainMap)
119            if GMid:
120                Npix,gainfile,imagetag = G2IO.GetCheckImageFile(G2frame,GMid)
121                GMimage = G2IO.GetImageData(G2frame,gainfile,True,ImageTag=imagetag,FormatName=formatName)
122                sumImg = sumImg*GMimage/1000
123    sumImg -= int(data.get('Flat Bkg',0))
124    Imax = np.max(sumImg)
125    if 'range' not in data or newRange:
126        data['range'] = [(0,Imax),[0,Imax]]
127    return np.asarray(sumImg,dtype='int32')
128
129def UpdateImageData(G2frame,data):
130   
131    def OnPixVal(invalid,value,tc):
132        G2plt.PlotExposedImage(G2frame,newPlot=True,event=tc.event)
133       
134    #G2frame.dataWindow.ClearData()
135    G2frame.ImageZ = GetImageZ(G2frame,data)
136    mainSizer = G2frame.dataWindow.GetSizer()
137    mainSizer.Add(wx.StaticText(G2frame.dataWindow,
138        label='Do not change anything here unless you are absolutely sure!'),0,WACV)
139    mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Image size: %d by %d'%(data['size'][0],data['size'][1])),0,WACV)
140    pixSize = wx.FlexGridSizer(0,4,5,5)
141    pixLabels = [u' Pixel X-dimension (\xb5m)',u' Pixel Y-dimension (\xb5m)']
142    for i,[pixLabel,pix] in enumerate(zip(pixLabels,data['pixelSize'])):
143        pixSize.Add(wx.StaticText(G2frame.dataWindow,label=pixLabel),0,WACV)
144        pixVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['pixelSize'],i,nDig=(10,3),
145            typeHint=float,OnLeave=OnPixVal)
146        pixSize.Add(pixVal,0,WACV)
147    mainSizer.Add(pixSize,0)
148    distSizer = wx.BoxSizer(wx.HORIZONTAL)
149    distSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Set detector distance: '),0,WACV)
150    if 'setdist' not in data:
151        data['setdist'] = data['distance']
152    distSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'setdist',nDig=(10,4),
153            typeHint=float),0,WACV)
154    distSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Polarization: '),0,WACV)
155    if 'PolaVal' not in data:       #patch
156        data['PolaVal'] = [0.99,False]
157    distSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['PolaVal'],0,nDig=(10,4),
158        min=0.,max=1.,typeHint=float),0,WACV)
159    mainSizer.Add(distSizer,0)
160    if 'samplechangerpos' not in data or data['samplechangerpos'] is None:
161        data['samplechangerpos'] = 0.0
162    mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='Sample changer position %.2f mm'%data['samplechangerpos']),0,WACV)
163
164################################################################################
165##### Image Controls
166################################################################################                   
167blkSize = 128   #128 seems to be optimal; will break in polymask if >1024
168def UpdateImageControls(G2frame,data,masks,useTA=None,useMask=None,IntegrateOnly=False):
169    '''Shows and handles the controls on the "Image Controls"
170    data tree entry
171    '''
172#patch
173    if 'Flat Bkg' not in data:
174        data['Flat Bkg'] = 0.0
175    if 'Gain map' not in data:
176        data['Gain map'] = ''
177    if 'GonioAngles' not in data:
178        data['GonioAngles'] = [0.,0.,0.]
179    if 'DetDepth' not in data:
180        data['DetDepth'] = 0.
181    if 'SampleAbs' not in data:
182        data['SampleShape'] = 'Cylinder'
183        data['SampleAbs'] = [0.0,False]
184    if 'binType' not in data:
185        if 'PWDR' in data['type']:
186            data['binType'] = '2-theta'
187        elif 'SASD' in data['type']:
188            data['binType'] = 'log(q)'
189    if 'varyList' not in data:
190        data['varyList'] = {'dist':True,'det-X':True,'det-Y':True,'tilt':True,'phi':True,'dep':False,'wave':False}
191    if data['DetDepth'] > 0.5:
192        data['DetDepth'] /= data['distance']
193    if 'setdist' not in data:
194        data['setdist'] = data['distance']
195    if 'linescan' not in data:
196        data['linescan'] = [False,0.0]      #includes azimuth to draw line scan
197#end patch
198
199# Menu items
200
201    def OnCalibrate(event):
202        if not data['calibrant']:
203            G2G.G2MessageBox(G2frame,'No calibrant material specified.\n'+
204                             'Please correct this and try again.')
205            return
206        G2frame.GetStatusBar().SetStatusText('Select > 4 points on 1st used ring; LB to pick (shift key to force pick), RB on point to delete else RB to finish',1)
207        G2frame.ifGetRing = True
208               
209    def OnRecalibrate(event):
210        '''Use existing calibration values as starting point for a calibration
211        fit
212        '''
213        G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,data,masks)
214        wx.CallAfter(UpdateImageControls,G2frame,data,masks)
215       
216    def OnRecalibAll(event):
217        '''Use existing calibration values as starting point for a calibration
218        fit for a selected series of images
219        '''
220        Id = None
221        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
222        dlg = G2G.G2MultiChoiceDialog(G2frame,'Image calibration controls','Select images to recalibrate:',Names)
223        try:
224            if dlg.ShowModal() == wx.ID_OK:
225                Id =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential image calibration results')
226                if Id:
227                    SeqResult = G2frame.GPXtree.GetItemPyData(Id)
228                else:
229                    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential image calibration results')
230                SeqResult = {'SeqPseudoVars':{},'SeqParFitEqList':[]}
231                items = dlg.GetSelections()
232                G2frame.EnablePlot = False
233                for item in items:
234                    name = Names[item]
235                    print ('calibrating'+name)
236                    G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
237                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
238                    G2frame.ImageZ = GetImageZ(G2frame,Data)
239                    Data['setRings'] = True
240                    Mid = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks')
241                    Masks = G2frame.GPXtree.GetItemPyData(Mid)
242                    result = G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,Data,Masks)
243                    if not len(result):
244                        print('calibrant missing from local image calibrants files')
245                        return
246                    vals,varyList,sigList,parmDict,covar = result
247                    sigList = list(sigList)
248                    if 'dist' not in varyList:
249                        vals.append(parmDict['dist'])
250                        varyList.append('dist')
251                        sigList.append(None)
252                    vals.append(Data.get('setdist',Data['distance']))
253                    # add setdist to varylist etc. so that it is displayed in Seq Res table
254                    varyList.append('setdist')
255                    sigList.append(None)
256                    covar = np.lib.pad(covar, (0,1), 'constant')
257#                    vals.append(Data.get('samplechangerpos',Data['samplechangerpos']))
258#                    varyList.append('chgrpos')
259#                    sigList.append(None)
260                   
261                    SeqResult[name] = {'variables':vals,'varyList':varyList,'sig':sigList,'Rvals':[],
262                        'covMatrix':covar,'title':name,'parmDict':parmDict}
263                SeqResult['histNames'] = Names               
264                G2frame.GPXtree.SetItemPyData(Id,SeqResult)
265        finally:
266            dlg.Destroy()
267        print ('All selected images recalibrated - results in Sequential image calibration results')
268        G2frame.G2plotNB.Delete('Sequential refinement')    #clear away probably invalid plot
269        G2plt.PlotExposedImage(G2frame,event=None)
270        if Id: G2frame.GPXtree.SelectItem(Id)
271
272    def OnDistRecalib(event):
273        '''Assemble rings & calibration input for a series of images with
274        differing distances
275        '''
276        obsArr = np.array([]).reshape(0,4)
277        parmDict = {}
278        varList = []
279        HKL = {}
280        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
281        startID = G2frame.GPXtree.GetSelection()
282        dlg = G2G.G2MultiChoiceDialog(G2frame,'Image calibration controls','Select images to recalibrate:',Names)
283        try:
284            if dlg.ShowModal() == wx.ID_OK:
285                wx.BeginBusyCursor()
286                items = dlg.GetSelections()
287                print('Scanning for ring picks...')
288#                G2frame.EnablePlot = False
289                for item in items:
290                    name = Names[item]
291                    print ('getting rings for',name)
292                    G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
293                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
294                    key = str(int(Data['setdist']))
295                    # create a parameter dict for combined fit
296                    if 'wavelength' not in parmDict:
297                        parmDict['wavelength'] = Data['wavelength']
298                        if Data['varyList']['wave']:
299                            varList += ['wavelength']
300                            if Data['varyList']['dist']:
301                                G2G.G2MessageBox(G2frame,
302                                'You cannot vary individual detector positions and the global wavelength.\n\nChange flags for 1st image.',
303                                'Conflicting vars')
304                                return
305                        parmDict['dep'] = Data['DetDepth']
306                        if Data['varyList']['dep']:
307                            varList += ['dep']
308                        # distance flag determines if individual values are refined
309                        if not Data['varyList']['dist']:
310                            # starts as zero, single variable, always refined
311                            parmDict['deltaDist'] = 0.
312                            varList += ['deltaDist']
313                        parmDict['phi'] = Data['rotation']
314                        if Data['varyList']['phi']:
315                            varList += ['phi']
316                        parmDict['tilt'] = Data['tilt']
317                        if Data['varyList']['tilt']:
318                            varList += ['tilt']
319                    G2frame.ImageZ = GetImageZ(G2frame,Data)
320                    Data['setRings'] = True
321                    Mid = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks')
322                    Masks = G2frame.GPXtree.GetItemPyData(Mid)
323                    result = G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,Data,Masks,getRingsOnly=True)
324                    if not len(result):
325                        print('calibrant missing from local image calibrants files')
326                        return
327                    rings,HKL[key] = result
328                    # add detector set dist into data array, create a single really large array
329                    distarr = np.zeros_like(rings[:,2:3])
330                    if 'setdist' not in Data:
331                        print('Distance (setdist) not in image metadata')
332                        return
333                    distarr += Data['setdist']
334                    obsArr = np.concatenate((
335                        obsArr,
336                        np.concatenate((rings[:,0:2],distarr,rings[:,2:3]),axis=1)),axis=0)
337                    if 'deltaDist' not in parmDict:
338                        # starts as zero, variable refined for each image
339                         parmDict['delta'+key] = 0
340                         varList += ['delta'+key]
341                    for i,z in enumerate(['X','Y']):
342                        v = 'det-'+z
343                        if v+key in parmDict:
344                            print('Error: two images with setdist ~=',key)
345                            return
346                        parmDict[v+key] = Data['center'][i]
347                        if Data['varyList'][v]:
348                            varList += [v+key]
349                #GSASIIpath.IPyBreak()
350                print('\nFitting',obsArr.shape[0],'ring picks and',len(varList),'variables...')
351                result = G2img.FitMultiDist(obsArr,varList,parmDict,covar=True)
352                covar = result[3]
353                covData = {'title':'Multi-distance recalibrate','covMatrix':covar,'varyList':varList,'variables':result[1]}
354                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance')
355                G2frame.GPXtree.SetItemPyData(Id,covData)
356               
357                for item in items:
358                    name = Names[item]
359                    print ('updating',name)
360                    G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
361                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
362                    Data['wavelength'] = parmDict['wavelength']
363                    key = str(int(Data['setdist']))
364                    Data['center'] = [parmDict['det-X'+key],parmDict['det-Y'+key]]
365                    if 'deltaDist' in parmDict:
366                        Data['distance'] = Data['setdist'] - parmDict['deltaDist']
367                    else:
368                        Data['distance'] = Data['setdist'] - parmDict['delta'+key]
369                    Data['rotation'] = np.mod(parmDict['phi'],360.0)
370                    Data['tilt'] = parmDict['tilt']
371                    Data['DetDepth'] = parmDict['dep']
372                    #Data['chisq'] = chisq
373                    N = len(Data['ellipses'])
374                    Data['ellipses'] = []           #clear away individual ellipse fits
375                    for H in HKL[key][:N]:
376                        ellipse = G2img.GetEllipse(H[3],Data)
377                        Data['ellipses'].append(copy.deepcopy(ellipse+('b',)))
378                G2frame.EnablePlot = True
379                G2frame.GPXtree.SelectItem(G2frame.root) # there is probably a better way to force the reload of the current page
380                wx.CallAfter(G2frame.GPXtree.SelectItem,startID)
381                    #GSASIIpath.IPyBreak()
382                   
383               
384                # create a sequential table?
385#                Id =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential image calibration results')
386#                if Id:
387#                    SeqResult = G2frame.GPXtree.GetItemPyData(Id)
388#                else:
389#                    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential image calibration results')
390                #SeqResult = {'SeqPseudoVars':{},'SeqParFitEqList':[]}
391#                    vals,varyList,sigList,parmDict,covar = result
392#                    sigList = list(sigList)
393#                    if 'dist' not in varyList:
394#                        vals.append(parmDict['dist'])
395#                        varyList.append('dist')
396#                        sigList.append(None)
397#                    vals.append(Data.get('setdist',Data['distance']))
398#                    # add setdist to varylist etc. so that it is displayed in Seq Res table
399#                    varyList.append('setdist')
400#                    sigList.append(None)
401#                    covar = np.lib.pad(covar, (0,1), 'constant')
402#                    vals.append(Data.get('samplechangerpos',Data['samplechangerpos']))
403#                    varyList.append('chgrpos')
404#                    sigList.append(None)
405                   
406#                    SeqResult[name] = {'variables':vals,'varyList':varyList,'sig':sigList,'Rvals':[],
407#                        'covMatrix':covar,'title':name,'parmDict':parmDict}
408#                SeqResult['histNames'] = Names               
409#                G2frame.GPXtree.SetItemPyData(Id,SeqResult)
410            else:
411                wx.BeginBusyCursor()
412        finally:
413            dlg.Destroy()
414            wx.EndBusyCursor()
415
416#        print ('All selected images recalibrated - results in Sequential image calibration results')
417#        G2frame.G2plotNB.Delete('Sequential refinement')    #clear away probably invalid plot
418#        G2plt.PlotExposedImage(G2frame,event=None)
419#        G2frame.GPXtree.SelectItem(Id)
420
421       
422    def OnClearCalib(event):
423        data['ring'] = []
424        data['rings'] = []
425        data['ellipses'] = []
426        G2plt.PlotExposedImage(G2frame,event=event)
427           
428    def ResetThresholds():
429        Imin = max(0.,np.min(G2frame.ImageZ))
430        Imax = np.max(G2frame.ImageZ)
431        data['range'] = [(0,Imax),[Imin,Imax]]
432        masks['Thresholds'] = [(0,Imax),[Imin,Imax]]
433        G2frame.slideSizer.GetChildren()[1].Window.SetValue(Imax)   #tricky
434        G2frame.slideSizer.GetChildren()[4].Window.SetValue(Imin)   #tricky
435         
436    def OnIntegrate(event,useTA=None,useMask=None):
437        '''Integrate image in response to a menu event or from the AutoIntegrate
438        dialog. In the latter case, event=None.
439        TODO: think of not repeating x,y-->2th,azm calc for unchanging calibrations?
440        '''
441        CleanupMasks(masks)
442        sumImg = GetImageZ(G2frame,data)
443        if not masks['SpotMask']['spotMask'] is None:
444            sumImg = ma.array(sumImg,mask=masks['SpotMask']['spotMask'])
445        G2frame.Integrate = G2img.ImageIntegrate(sumImg,data,masks,blkSize,useTA=useTA,useMask=useMask)           
446        G2frame.PauseIntegration = G2frame.Integrate[-1]
447        del sumImg  #force cleanup
448        Id = G2IO.SaveIntegration(G2frame,G2frame.PickId,data,(event is None))
449        G2frame.PatternId = Id
450        G2frame.GPXtree.SelectItem(Id)
451        G2frame.GPXtree.Expand(Id)
452        for item in G2frame.MakePDF: item.Enable(True)
453       
454    def OnIntegrateAll(event):
455        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
456        dlg = G2G.G2MultiChoiceDialog(G2frame,'Image integration controls','Select images to integrate:',Names)
457        try:
458            if dlg.ShowModal() == wx.ID_OK:
459                items = dlg.GetSelections()
460                G2frame.EnablePlot = False
461                dlgp = wx.ProgressDialog("Elapsed time","2D image integrations",len(items)+1,
462                    style = wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT)
463                try:
464                    pId = 0
465                    oldData = {'tilt':0.,'distance':0.,'rotation':0.,'center':[0.,0.],'DetDepth':0.,'azmthOff':0.}
466                    oldMhash = 0
467                    for icnt,item in enumerate(items):
468                        dlgp.Raise()
469                        GoOn = dlgp.Update(icnt)
470                        if not GoOn[0]:
471                            break
472                        name = Names[item]
473                        G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
474                        CId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls')
475                        Data = G2frame.GPXtree.GetItemPyData(CId)
476                        same = True
477                        for item in ['tilt','distance','rotation','DetDepth','azmthOff']:
478                            if Data[item] != oldData[item]:
479                                same = False
480                        if (Data['center'][0] != oldData['center'][0] or
481                            Data['center'][1] != oldData['center'][1]):
482                                same = False
483                        if not same:
484                            t0 = time.time()
485                            useTA = G2img.MakeUseTA(Data,blkSize)
486                            print(' Use new image controls; new xy -> th,azm time %.3f'%(time.time()-t0))
487                        Masks = G2frame.GPXtree.GetItemPyData(
488                            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks'))
489                        Mhash = copy.deepcopy(Masks)
490                        Mhash.pop('Thresholds')
491                        Mhash = hash(str(Mhash))
492                        if  Mhash != oldMhash:
493                            t0 = time.time()
494                            useMask = G2img.MakeUseMask(Data,Masks,blkSize)
495                            print(' Use new mask; make mask time: %.3f'%(time.time()-t0))
496                            oldMhash = Mhash
497                        image = GetImageZ(G2frame,Data)
498                        G2frame.Integrate = G2img.ImageIntegrate(image,Data,Masks,blkSize,useTA=useTA,useMask=useMask)
499                        del image   #force cleanup
500                        pId = G2IO.SaveIntegration(G2frame,CId,Data)
501                        oldData = Data
502                finally:   
503                    dlgp.Destroy()
504                    G2frame.EnablePlot = True
505                    if pId:
506                        G2frame.GPXtree.SelectItem(pId)
507                        G2frame.GPXtree.Expand(pId)
508                        G2frame.PatternId = pId
509        finally:
510            dlg.Destroy()
511       
512    def OnCopyControls(event):
513        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
514        if len(Names) == 1:
515            G2frame.ErrorDialog('Nothing to copy controls to','There must be more than one "IMG" pattern')
516            return
517        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
518        Names.pop(Names.index(Source))
519# select targets & do copy
520        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy image controls','Copy controls from '+Source+' to:',Names)
521        try:
522            if dlg.ShowModal() == wx.ID_OK:
523                items = dlg.GetSelections()
524                G2frame.EnablePlot = False
525                for item in items:
526                    name = Names[item]
527                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
528                    CId = G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls')
529                    oldData = copy.deepcopy(G2frame.GPXtree.GetItemPyData(CId))
530                    Data = copy.deepcopy(data)
531                    Data['range'][0] = oldData['range'][0]
532                    Data['size'] = oldData['size']
533                    Data['GonioAngles'] = oldData.get('GonioAngles', [0.,0.,0.])
534                    Data['samplechangerpos'] = oldData.get('samplechangerpos',0.0)
535                    Data['ring'] = []
536                    Data['rings'] = []
537                    Data['ellipses'] = []
538                    if name == Data['dark image'][0]:
539                        Data['dark image'] = ['',-1.]
540                    if name == Data['background image'][0]:
541                        Data['background image'] = ['',-1.]
542                    G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Image Controls'),Data)
543        finally:
544            dlg.Destroy()
545            if G2frame.PickId: G2frame.GPXtree.SelectItem(G2frame.PickId)
546           
547    def OnCopySelected(event):
548        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
549        if len(Names) == 1:
550            G2frame.ErrorDialog('Nothing to copy controls to','There must be more than one "IMG" pattern')
551            return
552        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
553        # Assemble a list of item labels
554        keyList = ['type','color','wavelength','calibrant','distance','center','Oblique',
555                    'tilt','rotation','azmthOff','fullIntegrate','LRazimuth','setdist',
556                    'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
557                    'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg','varyList',
558                    'binType','SampleShape','PolaVal','SampleAbs','dark image','background image']
559        keyList.sort(key=lambda s: s.lower())
560        keyText = [i+' = '+str(data[i]) for i in keyList]
561        # sort both lists together, ordered by keyText
562        selectedKeys = []
563        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select which image controls\nto copy',
564            'Select image controls', keyText)
565        try:
566            if dlg.ShowModal() == wx.ID_OK:
567                selectedKeys = [keyList[i] for i in dlg.GetSelections()]
568        finally:
569            dlg.Destroy()
570        if not selectedKeys: return # nothing to copy
571        copyDict = {}
572        for parm in selectedKeys:
573            copyDict[parm] = data[parm]
574        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy image controls from\n'+Source+' to...',
575            'Copy image controls', Names)
576        try:
577            if dlg.ShowModal() == wx.ID_OK:
578                result = dlg.GetSelections()
579                for i in result: 
580                    item = Names[i]
581                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item)
582                    Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
583                    Controls.update(copy.deepcopy(copyDict))
584        finally:
585            dlg.Destroy()           
586               
587    def OnSaveControls(event):
588        pth = G2G.GetExportPath(G2frame)
589        dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', 
590            'image control files (*.imctrl)|*.imctrl',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
591        try:
592            if dlg.ShowModal() == wx.ID_OK:
593                filename = dlg.GetPath()
594                # make sure extension is .imctrl
595                filename = os.path.splitext(filename)[0]+'.imctrl'
596                G2fil.WriteControls(filename,data)
597        finally:
598            dlg.Destroy()
599       
600    def OnSaveMultiControls(event):
601        '''Save controls from multiple images
602        '''
603        imglist = []
604        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
605        while item:
606            name = G2frame.GPXtree.GetItemText(item)
607            if name.startswith('IMG '): 
608                imglist.append(name)               
609            item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
610        if not imglist:
611            print('No images!')
612            return
613        dlg = G2G.G2MultiChoiceDialog(G2frame, 'Which images to select?',
614            'Select images', imglist, wx.CHOICEDLG_STYLE)
615        try:
616            if dlg.ShowModal() == wx.ID_OK:
617                treeEntries = [imglist[i] for i in dlg.GetSelections()]
618        finally:
619            dlg.Destroy()
620        if not treeEntries:
621            print('No images selected!')
622            return
623        pth = G2G.GetExportPath(G2frame)
624        dlg = wx.DirDialog(
625            G2frame, 'Select directory for output files',pth,wx.DD_DEFAULT_STYLE)
626        dlg.CenterOnParent()
627        outdir = None
628        try:
629            if dlg.ShowModal() == wx.ID_OK:
630                outdir = dlg.GetPath()
631        finally:
632            dlg.Destroy()
633        if not outdir:
634            print('No directory')
635            return
636        for img in treeEntries:
637            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
638            data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
639                G2frame,item,'Image Controls'))
640            Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(item)
641            filename = os.path.join(outdir,
642                                    os.path.splitext(os.path.split(imagefile)[1])[0]
643                                    + '.imctrl')
644            print('writing '+filename)
645            G2fil.WriteControls(filename,data)
646           
647    def OnLoadControls(event):
648        pth = G2G.GetImportPath(G2frame)
649        if not pth: pth = '.'
650        dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', 
651            'image control files (*.imctrl)|*.imctrl',wx.FD_OPEN)
652        try:
653            if dlg.ShowModal() == wx.ID_OK:
654                filename = dlg.GetPath()
655                File = open(filename,'r')
656                Slines = File.readlines()
657                File.close()
658                G2fil.LoadControls(Slines,data)
659        finally:
660            dlg.Destroy()
661        G2frame.ImageZ = GetImageZ(G2frame,data)
662        ResetThresholds()
663        G2plt.PlotExposedImage(G2frame,event=event)
664        wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
665       
666    def OnLoadMultiControls(event):         #TODO: how read in multiple image controls & match them by 'twoth' tag?
667        print('This is not implemented yet, sorry')
668        G2G.G2MessageBox(G2frame,'This is not implemented yet, sorry')
669        return
670        pth = G2G.GetImportPath(G2frame)
671        if not pth: pth = '.'
672        controlsDict = {}
673        dlg = wx.FileDialog(G2frame, 'Choose image control files', pth, '', 
674            'image control files (*.imctrl)|*.imctrl',wx.FD_OPEN|wx.FD_MULTIPLE)
675        try:
676            if dlg.ShowModal() == wx.ID_OK:
677                filelist = dlg.GetPaths()
678                if len(filelist) == 0: return
679                for filename in filelist:
680                    File = open(filename,'r')
681                    Slines = File.readlines()
682                    for S in Slines:
683                        if S.find('twoth') == 0:
684                            indx = S.split(':')[1][:-1]     #remove '\n'!
685                    controlsDict[indx] = Slines
686                    File.close()
687        finally:
688            dlg.Destroy()
689        if not len(controlsDict):
690            return
691        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
692        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select images','Select images for updating controls:',
693            Names)
694        try:
695            if dlg.ShowModal() == wx.ID_OK:
696                images = dlg.GetSelections()
697                if not len(images):
698                    return
699                for image in images:
700                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Names[image])
701                    imctrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
702                    Slines = controlsDict[imctrls['twoth']]
703                    G2fil.LoadControls(Slines,imctrls)
704        finally:
705            dlg.Destroy()
706       
707    def OnTransferAngles(event):
708        '''Sets the integration range for the selected Images based on the difference in detector distance
709        '''
710        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
711        if len(Names) == 1:
712            G2frame.ErrorDialog('No images to transfer integration angles to','Need more "IMG"s')
713            return
714        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
715        Names.pop(Names.index(Source))
716        # select targets & do copy
717        extraopts = {"label_1":"Xfer scaled calib d-min", "value_1":False,
718                     "label_2":"Xfer scaled 2-theta min", "value_2":False,
719                     "label_3":"Xfer scaled 2-theta max", "value_3":True,
720                     "label_4":"Xfer fixed background  ", "value_4":False,
721                     }
722        dlg = G2G.G2MultiChoiceDialog(G2frame,'Xfer angles','Transfer integration range from '+Source+' to:',
723            Names,extraOpts=extraopts)
724        try:
725            if dlg.ShowModal() == wx.ID_OK:
726                for i in '_1','_2','_3','_4':
727                    if extraopts['value'+i]: break
728                else:
729                    G2G.G2MessageBox(G2frame,'Nothing to do!')
730                    return
731                xferAng = lambda tth,dist1,dist2: atand(dist1 * tand(tth) / dist2)
732                items = dlg.GetSelections()
733                G2frame.EnablePlot = False
734                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Source)
735                data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
736                ttmin0,ttmax0 = data['IOtth']
737                dist0 = data['distance']
738                wave0 = data['wavelength']
739                dsp0 = data['calibdmin']
740                flatBkg = data['Flat Bkg']
741                print('distance = {:.2f} integration range: [{:.4f}, {:.4f}], calib dmin {:.3f}'
742                            .format(dist0,ttmin0,ttmax0,dsp0))
743                for item in items:
744                    name = Names[item]
745                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
746                    data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
747                    dist1 = data['distance']
748                    if extraopts["value_2"]:
749                        data['IOtth'][0] = xferAng(ttmin0,dist0,dist1)
750                    if extraopts["value_3"]:
751                        data['IOtth'][1] = xferAng(ttmax0,dist0,dist1)
752                    if extraopts["value_1"]:
753                        ang1 = xferAng(2.0*asind(wave0/(2.*dsp0)),dist0,dist1)
754                        data['calibdmin'] = data['wavelength']/(2.*sind(ang1/2.))
755                        print('distance = {:.2f} integration range: [{:.4f}, {:.4f}], calib dmin {:.3f}'
756                            .format(dist1,data['IOtth'][0],data['IOtth'][1],data['calibdmin']))
757                    if extraopts['value_4']:
758                        data['Flat Bkg'] = flatBkg*(dist0/dist1)**2
759                    else:
760                        print('distance = {:.2f} integration range: [{:.4f}, {:.4f}]'
761                            .format(dist1,data['IOtth'][0],data['IOtth'][1]))
762        finally:
763            dlg.Destroy()
764            G2frame.GPXtree.SelectItem(G2frame.PickId)       
765           
766    def OnResetDist(event):
767        dlg = wx.MessageDialog(G2frame,'Are you sure you want to do this?',caption='Reset dist to set dist',style=wx.YES_NO|wx.ICON_EXCLAMATION)
768        if dlg.ShowModal() != wx.ID_YES:
769            dlg.Destroy()
770            return
771        dlg.Destroy()
772        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
773        dlg = G2G.G2MultiChoiceDialog(G2frame,'Reset dist','Reset dist to set dist for:',Names)
774        try:
775            if dlg.ShowModal() == wx.ID_OK:
776                items = dlg.GetSelections()
777                for item in items:
778                    name = Names[item]
779                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
780                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
781                    Data['distance'] = Data['setdist']
782        finally:
783            dlg.Destroy()
784        wx.CallAfter(UpdateImageControls,G2frame,data,masks)
785           
786# Sizers
787    Indx = {}                                   
788    def ComboSizer():
789
790        def OnDataType(event):
791            data['type'] = typeSel.GetValue()[:4]
792            if 'SASD' in data['type']:
793                data['SampleAbs'][0] = np.exp(-data['SampleAbs'][0]) #switch from muT to trans!
794                if data['binType'] == '2-theta': data['binType'] = 'log(q)'  #switch default bin type
795            elif 'PWDR' in data['type']:
796                data['SampleAbs'][0] = -np.log(data['SampleAbs'][0])  #switch from trans to muT!
797                if data['binType'] == 'log(q)': data['binType'] = '2-theta'  #switch default bin type                 
798            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
799   
800        def OnNewColorBar(event):
801            data['color'] = colSel.GetValue()
802            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
803       
804        def OnAzmthOff(invalid,value,tc):
805            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
806       
807        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
808        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Type of image data: '),0,WACV)
809        typeSel = wx.ComboBox(parent=G2frame.dataWindow,value=typeDict[data['type']],choices=typeList,
810            style=wx.CB_READONLY|wx.CB_DROPDOWN)
811        typeSel.SetValue(data['type'])
812        typeSel.Bind(wx.EVT_COMBOBOX, OnDataType)
813        comboSizer.Add(typeSel,0,WACV)
814        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Color bar '),0,WACV)
815        colSel = wx.ComboBox(parent=G2frame.dataWindow,value=data['color'],choices=colorList,
816            style=wx.CB_READONLY|wx.CB_DROPDOWN)
817        colSel.Bind(wx.EVT_COMBOBOX, OnNewColorBar)
818        comboSizer.Add(colSel,0,WACV)
819        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Azimuth offset '),0,WACV)
820        azmthOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'azmthOff',nDig=(10,2),
821            typeHint=float,OnLeave=OnAzmthOff)
822        comboSizer.Add(azmthOff,0,WACV)
823        return comboSizer
824       
825    def MaxSizer():
826        '''Defines a sizer with sliders and TextCtrl widgets for controlling the colormap
827        for the image, as well as callback routines.
828        '''
829        def OnNewVal(invalid,value,tc):
830            '''Called when a Imax or Imin value is typed into a Validated TextCrtl (which puts
831            the value into the data['range'] nested list).
832            This adjusts the slider positions to match the current values
833            '''
834            scaleSel.SetSelection(len(scaleChoices)-1)
835            r11 = min(max(Range[1][1],Range[1][0]+1),Range[0][1]) # keep values in range
836            if r11 != Range[1][1]:
837                Range[1][1] = r11
838                maxVal.SetValue(int(Range[1][1]))
839            r10 = max(min(Range[1][0],Range[1][1]-1),Range[0][0])
840            if r10 != Range[1][0]:
841                Range[1][0] = r10
842                minVal.SetValue(int(Range[1][0]))
843            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
844            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
845            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
846            maxSel.SetValue(sv1)
847            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
848            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
849            minSel.SetValue(sv0)
850            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
851            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
852            if mplOld:
853                Page.canvas.draw()
854            else:
855                Page.canvas.draw_idle()
856           
857        G2frame.prevMaxValue = None   
858        def OnMaxSlider(event):
859            val = maxSel.GetValue()
860            if G2frame.prevMaxValue == val: return # if this val has been processed, no need to repeat
861            scaleSel.SetSelection(len(scaleChoices)-1)
862            G2frame.prevMaxValue = val
863            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
864            Range[1][1] = int(0.5 + (val * sqrtDeltZero / 100.)**2 + Range[1][0] + 1)
865            maxVal.SetValue(int(0.5+Range[1][1]))
866            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
867            minSel.SetValue(int(0.5 + 100*(Range[1][0]/DeltOne)))
868            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
869            minSel.SetValue(sv0)
870            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
871            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
872            if mplOld:
873                Page.canvas.draw()
874            else:
875                Page.canvas.draw_idle()
876           
877        G2frame.prevMinValue = None   
878        def OnMinSlider(event):
879            val = minSel.GetValue()
880            scaleSel.SetSelection(len(scaleChoices)-1)
881            if G2frame.prevMinValue == val: return # if this val has been processed, no need to repeat
882            G2frame.prevMinValue = val
883            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
884            Range[1][0] = max(0,int(0.5 + val * DeltOne / 100 + Range[0][0]))
885            minVal.SetValue(int(Range[1][0]))
886            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
887            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
888            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
889            maxSel.SetValue(sv1)
890            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
891            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
892            if mplOld:
893                Page.canvas.draw()
894            else:
895                Page.canvas.draw_idle()
896           
897        def OnAutoSet(event):
898            '''Responds to a button labeled 95%, etc; Sets the Imax and Imin values
899            for the image so that 95% (etc.) of pixels are inside the color map limits.
900            An equal number of pixels are dropped at the minimum and maximum levels.
901            '''
902            try:
903                val = int(event.GetEventObject().GetStringSelection()[:-1])
904                margin = (100-val)/2.
905            except:
906                margin = 0
907                event.GetEventObject().SetSelection(0)
908            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
909            if margin == 0:
910                Range[1] = list(Range[0])
911            else:
912                Range[1][0] = int(np.percentile(Page.ImgObj.get_array().compressed(),margin))
913                Range[1][1] = int(np.percentile(Page.ImgObj.get_array().compressed(),100-margin))
914            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
915            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
916            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
917            maxSel.SetValue(sv1)
918            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
919            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
920            minSel.SetValue(sv0)
921            minVal.SetValue(int(Range[1][0]))
922            maxVal.SetValue(int(Range[1][1]))
923            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
924            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
925            if mplOld:
926                Page.canvas.draw()
927            else:
928                Page.canvas.draw_idle()
929               
930        def OnLineScan(event):
931            data['linescan'][0] = linescan.GetValue()
932            wx.CallAfter(UpdateImageControls,G2frame,data,masks)
933            G2plt.PlotExposedImage(G2frame,event=event)
934           
935        def OnNewLineScan(invalid,value,tc):
936            G2plt.PlotExposedImage(G2frame,event=None)
937           
938        def OnMoveAzm(event):
939            data['linescan'][1] += float(azmSpin.GetValue())
940            data['linescan'][1] = data['linescan'][1]%360.
941            G2frame.scanazm.SetValue(data['linescan'][1])
942            G2plt.PlotExposedImage(G2frame,event=event)
943
944        mplv = mpl.__version__.split('.')
945        mplOld = mplv[0] == '1' and int(mplv[1]) < 4 # use draw_idle for newer matplotlib versions
946        # Plot color scaling uses limits as below:
947        #   (Imin0, Imax0) => Range[0] = data['range'][0] # lowest to highest pixel intensity
948        #   [Imin, Imax] => Range[1] = data['range'][1] #   lowest to highest pixel intensity on cmap scale
949        maxSizer = wx.BoxSizer(wx.VERTICAL)
950        G2frame.slideSizer = wx.FlexGridSizer(2,3,5,5)
951        G2frame.slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Max intensity'),0,WACV)
952        # maxSel is a slider with 101 steps scaled from Imin+1 to Imax0 with sqrt scaling
953        # slider value = sv = 100 * sqrt((Imax-Imin-1)/(Imax0-Imin-1))
954        # Imax = (sv * sqrt(Imax0-Imin-1) / 100)**2 + Imin + 1
955        sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
956        sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
957        sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
958        maxSel = wx.Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv1)
959        G2frame.slideSizer.AddGrowableCol(2)
960        maxSel.Bind(wx.EVT_SLIDER, OnMaxSlider)
961        maxVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],1,min=Range[0][0]+1,
962            max=Range[0][1],OnLeave=OnNewVal)
963        G2frame.slideSizer.Add(maxVal,0,WACV)
964        G2frame.slideSizer.Add(maxSel,flag=wx.EXPAND|wx.ALL)
965        G2frame.slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min intensity'),0,WACV)
966        # minSel is a slider with 101 steps scaled from Imin0 to Imax-1 with linear scaling
967        # slider value = sv0 = 100 * (Imin-Imin0)/(Imax-Imin0-1)
968        # Imin = sv0 * (Imax-Imin0-1) / 100 + Imin0
969        DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
970        sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
971        minSel = wx.Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv0)
972        minSel.Bind(wx.EVT_SLIDER, OnMinSlider)
973        minVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],0,
974            max=Range[0][1],typeHint=int,OnLeave=OnNewVal)
975        G2frame.slideSizer.Add(minVal,0,WACV)
976        G2frame.slideSizer.Add(minSel,flag=wx.EXPAND|wx.ALL)
977        maxSizer.Add(G2frame.slideSizer,flag=wx.EXPAND|wx.ALL)
978        autoSizer = wx.BoxSizer(wx.HORIZONTAL)
979        autoSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Auto scaler '),0,WACV)
980        scaleChoices = ("100%","99%","95%","90%","80%","?")
981        scaleSel = wx.Choice(G2frame.dataWindow,choices=scaleChoices,size=(-1,-1))
982        if (Range[1][0] == Range[0][0] and
983            Range[1][1] == Range[0][1]):
984            scaleSel.SetSelection(0)
985        else:
986            scaleSel.SetSelection(len(scaleChoices)-1)
987        scaleSel.Bind(wx.EVT_CHOICE,OnAutoSet)
988        autoSizer.Add(scaleSel,0,WACV)
989        if data['linescan'][0]:
990            linescan = wx.CheckBox(G2frame.dataWindow,label=' Show line scan at azm = ')
991        else:
992            linescan = wx.CheckBox(G2frame.dataWindow,label=' Show line scan')
993        linescan.Bind(wx.EVT_CHECKBOX,OnLineScan)
994        linescan.SetValue(data['linescan'][0])
995        autoSizer.Add((5,0),0)
996        autoSizer.Add(linescan,0,WACV)
997        if data['linescan'][0]:
998            G2frame.scanazm = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['linescan'],1,min=0.,
999            max=360.,OnLeave=OnNewLineScan)
1000            autoSizer.Add(G2frame.scanazm,0,WACV)
1001            azmSpin = wx.SpinButton(G2frame.dataWindow,style=wx.SP_VERTICAL,size=wx.Size(20,25))
1002            azmSpin.SetValue(0)
1003            azmSpin.SetRange(-1,1)
1004            azmSpin.Bind(wx.EVT_SPIN, OnMoveAzm)
1005            autoSizer.Add(azmSpin,0,WACV)
1006
1007        maxSizer.Add(autoSizer)
1008        return maxSizer
1009       
1010    def CalibCoeffSizer():
1011       
1012        def OnCalRef(event):
1013            Obj = event.GetEventObject()
1014            name = Indx[Obj]
1015            data['varyList'][name] = Obj.GetValue()
1016           
1017        calibSizer = wx.FlexGridSizer(0,2,5,5)
1018        calibSizer.SetFlexibleDirection(wx.HORIZONTAL)
1019        calibSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration coefficients'),0,WACV)   
1020        calibSizer.Add((5,0),0)
1021        Names = ['det-X','det-Y','wave','dist','tilt','phi']
1022        if 'PWDR' in data['type']:
1023            Names.append('dep') 
1024        Parms = {'dist':['Distance',(10,3),data,'distance'],'det-X':['Beam center X',(10,3),data['center'],0],
1025            'det-Y':['Beam center Y',(10,3),data['center'],1],'tilt':['Tilt angle*',(10,3),data,'tilt'],
1026            'phi':['Tilt rotation*',(10,2),data,'rotation'],'dep':['Penetration*',(10,4),data,'DetDepth'],
1027            'wave':['Wavelength*',(10,6),data,'wavelength']}
1028        for name in Names:
1029            calSel = wx.CheckBox(parent=G2frame.dataWindow,label=Parms[name][0])
1030            calibSizer.Add(calSel,0,WACV)
1031            calSel.Bind(wx.EVT_CHECKBOX, OnCalRef)
1032            calSel.SetValue(data['varyList'][name])
1033            Indx[calSel] = name
1034            if name == 'wave':
1035                calVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Parms[name][2],
1036                    Parms[name][3],min=0.01,max=10.,nDig=Parms[name][1],typeHint=float)
1037            elif name == 'dep':
1038                calVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Parms[name][2],
1039                    Parms[name][3],min=0.0,max=0.2,nDig=Parms[name][1],typeHint=float)
1040            else:
1041                calVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Parms[name][2],
1042                    Parms[name][3],nDig=Parms[name][1],typeHint=float)
1043            calibSizer.Add(calVal,0,WACV)
1044        return calibSizer
1045   
1046    def IntegrateSizer():
1047       
1048        def OnNewBinType(event):
1049            data['binType'] = binSel.GetValue()
1050            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1051       
1052        def OnIOtth(invalid,value,tc):
1053            Ltth = float(G2frame.InnerTth.GetValue())
1054            Utth = float(G2frame.OuterTth.GetValue())
1055            if Ltth > Utth:
1056                Ltth,Utth = Utth,Ltth
1057            if 'q' in data['binType'].lower():
1058                data['IOtth'] = [2.*asind(Ltth*wave/(4.*math.pi)),2.*asind(Utth*wave/(4.*math.pi))]
1059            else:
1060                data['IOtth'] = [Ltth,Utth]
1061            G2frame.InnerTth.SetValue(Ltth)
1062            G2frame.OuterTth.SetValue(Utth)
1063            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1064       
1065        def OnLRazim(invalid,value,tc):
1066            Lazm = float(G2frame.Lazim.GetValue())%360.
1067            Razm = float(G2frame.Razim.GetValue())%360.
1068            if Lazm > Razm:
1069                Razm += 360.
1070            if data['fullIntegrate']:
1071                Razm = Lazm+360.
1072            G2frame.Lazim.SetValue(Lazm)
1073            G2frame.Razim.SetValue(Razm)
1074            data['LRazimuth'] = [Lazm,Razm]
1075            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1076               
1077        def OnNumOutAzms(invalid,value,tc):
1078            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1079           
1080        def OnNumOutBins(invalid,value,tc):
1081            data['outChannels'] = (data['outChannels']//4)*4
1082            outChan.SetValue(data['outChannels'])
1083       
1084        def OnOblique(event):
1085            data['Oblique'][1] = not data['Oblique'][1]
1086               
1087        def OnSampleShape(event):
1088            data['SampleShape'] = samShape.GetValue()
1089            if 'Cylind' in data['SampleShape']:
1090                data['SampleAbs'][0] = 0.0
1091            elif 'Fixed' in data['SampleShape']:
1092                data['SampleAbs'][0] = 1.0
1093            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1094                           
1095        def OnSamAbs(event):
1096            data['SampleAbs'][1] = not data['SampleAbs'][1]
1097                           
1098        def OnShowLines(event):
1099            data['showLines'] = not data['showLines']
1100            G2plt.PlotExposedImage(G2frame,event=event)
1101           
1102        def OnFullIntegrate(event):
1103            Lazm = float(G2frame.Lazim.GetValue())
1104            if data['fullIntegrate']:
1105                data['fullIntegrate'] = False
1106                data['LRazimuth'] = [Lazm,Lazm+20.]
1107            else:
1108                data['fullIntegrate'] = True
1109                data['LRazimuth'] = [Lazm,Lazm+360.]
1110            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1111            G2plt.PlotExposedImage(G2frame,event=event)
1112           
1113        def OnSetDefault(event):
1114            if data['setDefault']:
1115                G2frame.imageDefault = {}
1116                data['setDefault'] = False
1117            else:
1118                G2frame.imageDefault = copy.copy(data)
1119                data['setDefault'] = True
1120               
1121        def OnCenterAzm(event):
1122            data['centerAzm'] = not data['centerAzm']
1123            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1124               
1125        def OnApplyPola(event):
1126            data['PolaVal'][1] = not data['PolaVal'][1]
1127               
1128        dataSizer = wx.FlexGridSizer(0,2,5,3)
1129        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Integration coefficients'),0,WACV)   
1130        dataSizer.Add((5,0),0)
1131        if 'PWDR' in data['type']:
1132            binChoice = ['2-theta','Q']
1133        elif 'SASD' in data['type']:
1134            binChoice = ['2-theta','Q','log(q)']
1135        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Bin style: Constant step bins in'),0,WACV)           
1136        binSel = wx.ComboBox(parent=G2frame.dataWindow,value=data['binType'],choices=binChoice,
1137            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1138        binSel.Bind(wx.EVT_COMBOBOX, OnNewBinType)
1139        dataSizer.Add(binSel,0,WACV)
1140        binType = '2-theta'
1141        if 'q' in data['binType'].lower():
1142            binType = 'Q'
1143        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Inner/Outer '+binType),0,WACV)           
1144        IOtth = data['IOtth'][:]
1145        if 'q' in data['binType'].lower():
1146            wave = data['wavelength']
1147            IOtth = [4.*math.pi*sind(IOtth[0]/2.)/wave,4.*math.pi*sind(IOtth[1]/2.)/wave]
1148        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1149        G2frame.InnerTth = G2G.ValidatedTxtCtrl(G2frame.dataWindow,IOtth,0,nDig=(8,3,'f'),min=0.001,typeHint=float,OnLeave=OnIOtth)
1150        littleSizer.Add(G2frame.InnerTth,0,WACV)
1151        G2frame.OuterTth = G2G.ValidatedTxtCtrl(G2frame.dataWindow,IOtth,1,nDig=(8,3,'f'),min=0.001,typeHint=float,OnLeave=OnIOtth)
1152        littleSizer.Add(G2frame.OuterTth,0,WACV)
1153        dataSizer.Add(littleSizer,0,)
1154        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Start/End azimuth'),0,WACV)
1155        LRazim = data['LRazimuth']
1156        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1157        G2frame.Lazim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,LRazim,0,nDig=(6,1,'f'),typeHint=float,OnLeave=OnLRazim)
1158        littleSizer.Add(G2frame.Lazim,0,WACV)
1159        G2frame.Razim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,LRazim,1,nDig=(6,1,'f'),typeHint=float,OnLeave=OnLRazim)
1160        if data['fullIntegrate']:
1161            G2frame.Razim.Enable(False)
1162            G2frame.Razim.SetBackgroundColour(VERY_LIGHT_GREY)
1163            G2frame.Razim.SetValue(LRazim[0]+360.)
1164        littleSizer.Add(G2frame.Razim,0,WACV)
1165        dataSizer.Add(littleSizer,0,)
1166        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' No. 2-theta/azimuth bins'),0,WACV)
1167        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1168        outChan = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'outChannels',typeHint=int,min=10,OnLeave=OnNumOutBins)
1169        littleSizer.Add(outChan,0,WACV)
1170        outAzim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'outAzimuths',nDig=(10,4),min=1,typeHint=int,OnLeave=OnNumOutAzms)
1171        littleSizer.Add(outAzim,0,WACV)
1172        dataSizer.Add(littleSizer,0,)
1173        samplechoice = ['Cylinder','Fixed flat plate',]
1174        dataSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select sample shape'),0,WACV)
1175        samShape = wx.ComboBox(G2frame.dataWindow,value=data['SampleShape'],choices=samplechoice,
1176            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1177        samShape.Bind(wx.EVT_COMBOBOX,OnSampleShape)
1178        dataSizer.Add(samShape,0,WACV)
1179        #SampleShape - cylinder or flat plate choice?
1180        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1181        samabs = wx.CheckBox(parent=G2frame.dataWindow,label='Apply sample absorption?')
1182        dataSizer.Add(samabs,0,WACV)
1183        samabs.Bind(wx.EVT_CHECKBOX, OnSamAbs)
1184        samabs.SetValue(data['SampleAbs'][1])
1185        minmax = [0.,2.]
1186        if 'Cylind' in data['SampleShape']: #cylinder mu*R; flat plate transmission
1187            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='mu*R (0.00-2.0) '),0,WACV)
1188        elif 'Fixed' in data['SampleShape']:
1189            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='transmission '),0,WACV) #for flat plate
1190            minmax = [.05,1.0]
1191        samabsVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['SampleAbs'],0,nDig=(10,3),
1192            typeHint=float,min=minmax[0],max=minmax[1])
1193        littleSizer.Add(samabsVal,0,WACV)
1194        dataSizer.Add(littleSizer,0,)       
1195        if 'PWDR' in data['type']:
1196            littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1197            oblique = wx.CheckBox(parent=G2frame.dataWindow,label='Apply detector absorption?')
1198            dataSizer.Add(oblique,0,WACV)
1199            oblique.Bind(wx.EVT_CHECKBOX, OnOblique)
1200            oblique.SetValue(data['Oblique'][1])
1201            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='Value (0.01-0.99)  '),0,WACV)
1202            obliqVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Oblique'],0,nDig=(10,3),typeHint=float,min=0.01,max=0.99)
1203            littleSizer.Add(obliqVal,0,WACV)
1204            dataSizer.Add(littleSizer,0,)
1205        if 'SASD' in data['type']:
1206            littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1207            setPolariz = wx.CheckBox(parent=G2frame.dataWindow,label='Apply polarization?')
1208            dataSizer.Add(setPolariz,0,WACV)
1209            setPolariz.Bind(wx.EVT_CHECKBOX, OnApplyPola)
1210            setPolariz.SetValue(data['PolaVal'][1])
1211            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='Value (0.001-0.999)  '),0,WACV)
1212            polaVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['PolaVal'],0,nDig=(10,3),typeHint=float,min=0.001,max=0.999)
1213            littleSizer.Add(polaVal,0,WACV)
1214            dataSizer.Add(littleSizer,0,)
1215       
1216        showLines = wx.CheckBox(parent=G2frame.dataWindow,label='Show integration limits?')
1217        dataSizer.Add(showLines,0,WACV)
1218        showLines.Bind(wx.EVT_CHECKBOX, OnShowLines)
1219        showLines.SetValue(data['showLines'])
1220        fullIntegrate = wx.CheckBox(parent=G2frame.dataWindow,label='Do full integration?')
1221        dataSizer.Add(fullIntegrate,0,WACV)
1222        fullIntegrate.Bind(wx.EVT_CHECKBOX, OnFullIntegrate)
1223        fullIntegrate.SetValue(data['fullIntegrate'])
1224        setDefault = wx.CheckBox(parent=G2frame.dataWindow,label='Use for all new images?')
1225        dataSizer.Add(setDefault,0,WACV)
1226        setDefault.Bind(wx.EVT_CHECKBOX, OnSetDefault)
1227        setDefault.SetValue(data['setDefault'])
1228        centerAzm = wx.CheckBox(parent=G2frame.dataWindow,label='Azimuth at bin center?')
1229        dataSizer.Add(centerAzm,0,WACV)
1230        centerAzm.Bind(wx.EVT_CHECKBOX, OnCenterAzm)
1231        centerAzm.SetValue(data['centerAzm'])
1232        return dataSizer
1233       
1234    def BackSizer():
1235       
1236        global oldFlat
1237        def OnBackImage(event):
1238            data['background image'][0] = backImage.GetValue()
1239            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1240            ResetThresholds()
1241            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1242           
1243        def OnDarkImage(event):
1244            data['dark image'][0] = darkImage.GetValue()
1245            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1246            ResetThresholds()
1247            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1248           
1249        def OnFlatBkg(invalid,value,tc):
1250            global oldFlat
1251            G2frame.ImageZ += int(oldFlat-data['Flat Bkg'])
1252            oldFlat = data['Flat Bkg']
1253            ResetThresholds()
1254            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1255
1256        def OnMult(invalid,value,tc):
1257            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1258            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1259           
1260        def OnGainMap(event):
1261            data['Gain map'] = gainMap.GetValue()
1262            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1263            ResetThresholds()
1264            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1265       
1266        backSizer = wx.FlexGridSizer(0,6,5,5)
1267        oldFlat = data.get('Flat Bkg',0.)
1268
1269        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Dark image'),0,WACV)
1270        Choices = ['',]+G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
1271        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
1272        Choices.pop(Choices.index(Source))
1273        darkImage = wx.ComboBox(parent=G2frame.dataWindow,value=data['dark image'][0],choices=Choices,
1274            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1275        darkImage.Bind(wx.EVT_COMBOBOX,OnDarkImage)
1276        backSizer.Add(darkImage)
1277        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' multiplier'),0,WACV)
1278        darkMult = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['dark image'],1,nDig=(10,3),
1279            typeHint=float,OnLeave=OnMult)
1280        backSizer.Add(darkMult,0,WACV)
1281        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Flat Bkg: '),0,WACV)
1282        flatbkg = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Flat Bkg',nDig=(10,0),
1283            typeHint=float,OnLeave=OnFlatBkg)
1284        backSizer.Add(flatbkg,0,WACV)
1285
1286        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Background image'),0,WACV)
1287        backImage = wx.ComboBox(parent=G2frame.dataWindow,value=data['background image'][0],choices=Choices,
1288            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1289        backImage.Bind(wx.EVT_COMBOBOX,OnBackImage)
1290        backSizer.Add(backImage)
1291        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' multiplier'),0,WACV)
1292        backMult = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['background image'],1,nDig=(10,3),
1293            typeHint=float,OnLeave=OnMult)
1294        backSizer.Add(backMult,0,WACV)
1295        backSizer.Add((5,5),0)
1296        backSizer.Add((5,5),0)
1297        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Gain map'),0,WACV)
1298        gainMap = wx.ComboBox(parent=G2frame.dataWindow,value=data['Gain map'],choices=Choices,
1299            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1300        gainMap.Bind(wx.EVT_COMBOBOX,OnGainMap)
1301        backSizer.Add(gainMap)
1302        return backSizer
1303                       
1304    def CalibSizer():
1305               
1306        def OnNewCalibrant(event):
1307            data['calibrant'] = calSel.GetValue().strip()
1308            if data['calibrant']:
1309                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBRATE,enable=True)
1310                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMCALIBRATE,enable=True)
1311                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBALL,enable=True)
1312                data['calibskip'] = calFile.Calibrants[data['calibrant']][3]
1313                limits = calFile.Calibrants[data['calibrant']][4]
1314                data['calibdmin'],data['pixLimit'],data['cutoff'] = limits
1315                pixLimit.SetValue(str(limits[1]))
1316                cutOff.SetValue(limits[2])
1317                calibSkip.SetValue(str(data['calibskip']))
1318                G2frame.calibDmin.SetValue(limits[0])
1319            else:
1320                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBRATE,enable=False)
1321                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMCALIBRATE,enable=False)
1322                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBALL,enable=False)
1323               
1324        def OnCalibSkip(event):
1325            data['calibskip'] = int(calibSkip.GetValue())
1326           
1327        def OnPixLimit(event):
1328            data['pixLimit'] = int(pixLimit.GetValue())
1329           
1330        def OnSetRings(event):
1331            data['setRings'] = not data['setRings']
1332            G2plt.PlotExposedImage(G2frame,event=event)
1333   
1334        calibSizer = wx.FlexGridSizer(0,3,5,5)
1335        comboSizer = wx.BoxSizer(wx.HORIZONTAL)   
1336        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibrant '),0,WACV)
1337        calSel = wx.ComboBox(parent=G2frame.dataWindow,value=data['calibrant'],choices=calList,
1338            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1339        calSel.Bind(wx.EVT_COMBOBOX, OnNewCalibrant)
1340        comboSizer.Add(calSel,0,WACV)
1341        calibSizer.Add(comboSizer,0)
1342       
1343        comboSizer = wx.BoxSizer(wx.HORIZONTAL)   
1344        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calib lines to skip   '),0,WACV)
1345        calibSkip  = wx.ComboBox(parent=G2frame.dataWindow,value=str(data['calibskip']),choices=[str(i) for i in range(25)],
1346            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1347        calibSkip.Bind(wx.EVT_COMBOBOX, OnCalibSkip)
1348        comboSizer.Add(calibSkip,0,WACV)
1349        calibSizer.Add(comboSizer,0)
1350       
1351        comboSizer = wx.BoxSizer(wx.HORIZONTAL)       
1352        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min calib d-spacing '),0,WACV)
1353        G2frame.calibDmin = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'calibdmin',nDig=(10,2),typeHint=float,min=0.25)
1354        comboSizer.Add(G2frame.calibDmin,0,WACV)
1355        calibSizer.Add(comboSizer,0)
1356       
1357        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
1358        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min ring I/Ib '),0,WACV)
1359        cutOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'cutoff',nDig=(10,2),min=0.1)
1360        comboSizer.Add(cutOff,0,WACV)
1361        calibSizer.Add(comboSizer,0)
1362       
1363        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
1364        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Pixel search range '),0,WACV)
1365        pixLimit = wx.ComboBox(parent=G2frame.dataWindow,value=str(data['pixLimit']),choices=['1','2','5','10','15','20'],
1366            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1367        pixLimit.Bind(wx.EVT_COMBOBOX, OnPixLimit)
1368        comboSizer.Add(pixLimit,0,WACV)
1369        calibSizer.Add(comboSizer,0)
1370       
1371        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
1372        setRings = wx.CheckBox(parent=G2frame.dataWindow,label='Show ring picks?')
1373        comboSizer.Add(setRings,0)
1374        setRings.Bind(wx.EVT_CHECKBOX, OnSetRings)
1375        setRings.SetValue(data['setRings'])
1376        calibSizer.Add(comboSizer,0)
1377        return calibSizer
1378       
1379    def GonioSizer():
1380       
1381        def OnGlobalEdit(event):
1382            Names = []
1383            Items = []
1384            if G2frame.GPXtree.GetCount():
1385                Id, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
1386                while Id:
1387                    name = G2frame.GPXtree.GetItemText(Id)
1388                    if 'IMG' in name:
1389                        ctrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
1390                        Names.append(name)
1391                        Items.append(ctrls['GonioAngles'])
1392                    Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
1393                if len(Names) == 1:
1394                    G2frame.ErrorDialog('Nothing for global editing','There must be more than one "IMG" pattern')
1395                    return
1396                dlg = G2G.G2HistoDataDialog(G2frame,' Edit sample goniometer data:',
1397                    'Edit data',['Omega','Chi','Phi'],['%.2f','%.2f','%.2f'],Names,Items)
1398                try:
1399                    if dlg.ShowModal() == wx.ID_OK:
1400                        Id, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
1401                        while Id:
1402                            name = G2frame.GPXtree.GetItemText(Id)
1403                            if 'IMG' in name:
1404                                ctrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
1405                                vals = Items[Names.index(name)]
1406                                ctrls['GonioAngles'] = vals
1407                            Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
1408                finally:
1409                    dlg.Destroy()
1410                    G2frame.GPXtree.SelectItem(G2frame.PickId)
1411       
1412        gonioSizer = wx.BoxSizer(wx.HORIZONTAL)
1413        names = ['Omega','Chi','Phi']
1414        gonioSizer.Add(wx.StaticText(G2frame.dataWindow,-1,'Sample goniometer angles: '),0,WACV)
1415        for i,name in enumerate(names):
1416            gonioSizer.Add(wx.StaticText(G2frame.dataWindow,-1,name),0,WACV)
1417            angle = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['GonioAngles'],i,nDig=(8,2),typeHint=float)
1418            gonioSizer.Add(angle,0,WACV)
1419        globEdit = wx.Button(G2frame.dataWindow,-1,'Global edit')
1420        globEdit.Bind(wx.EVT_BUTTON,OnGlobalEdit)
1421        gonioSizer.Add(globEdit,0,WACV)
1422        return gonioSizer
1423       
1424    # UpdateImageControls: Image Controls main code             
1425                           
1426    #fix for old files:
1427    if 'azmthOff' not in data:
1428        data['azmthOff'] = 0.0
1429    if 'background image' not in data:
1430        data['background image'] = ['',-1.0]
1431    if 'dark image' not in data:
1432        data['dark image'] = ['',-1.0]
1433    if 'centerAzm' not in data:
1434        data['centerAzm'] = False
1435    if 'Oblique' not in data:
1436        data['Oblique'] = [0.5,False]
1437    if 'PolaVal' not in data:
1438        data['PolaVal'] = [0.99,False]
1439    #end fix
1440   
1441    if IntegrateOnly:
1442        Masks = G2frame.GPXtree.GetItemPyData(
1443            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks'))
1444        # Mhash = hash(str(Masks))  # TODO: implement this to save integration time (?)
1445        # if  Mhash != oldMhash:
1446        #     t0 = time.time()
1447        useMask = G2img.MakeUseMask(data,Masks,blkSize)
1448        #     print(' Use new mask; make mask time: %.3f'%(time.time()-t0))
1449        #     oldMhash = Mhash
1450        OnIntegrate(None,useTA=useTA,useMask=useMask)
1451        return
1452   
1453    G2frame.GetStatusBar().SetStatusText('* Global parameters in Multi-dist recalib.',1)
1454    colorList = sorted([m for m in mpl.cm.datad.keys() ],key=lambda s: s.lower())   #if not m.endswith("_r")
1455    calList = sorted([m for m in calFile.Calibrants.keys()],key=lambda s: s.lower())
1456    typeList = ['PWDR - powder diffraction data','SASD - small angle scattering data',]
1457    if not data.get('type'):                        #patch for old project files
1458        data['type'] = 'PWDR'
1459    typeDict = {'PWDR':typeList[0],'SASD':typeList[1],}
1460    G2frame.dataWindow.ClearData()
1461    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ImageMenu)
1462    G2frame.Bind(wx.EVT_MENU, OnCalibrate, id=G2G.wxID_IMCALIBRATE)
1463    G2frame.Bind(wx.EVT_MENU, OnRecalibrate, id=G2G.wxID_IMRECALIBRATE)
1464    G2frame.Bind(wx.EVT_MENU, OnRecalibAll, id=G2G.wxID_IMRECALIBALL)
1465    G2frame.Bind(wx.EVT_MENU, OnDistRecalib, id=G2G.wxID_IMDISTRECALIB)
1466    G2frame.Bind(wx.EVT_MENU, OnClearCalib, id=G2G.wxID_IMCLEARCALIB)
1467#    if data.get('calibrant'):
1468#        mode = True
1469#    else:
1470#        mode = False
1471#    G2frame.Enable(id=G2G.wxID_IMRECALIBRATE,enable=mode)
1472#    G2frame.Enable(id=G2G.wxID_IMCALIBRATE,enable=mode)
1473#    G2frame.Enable(id=G2G.wxID_IMRECALIBALL,enable=mode)
1474    G2frame.Bind(wx.EVT_MENU, OnIntegrate, id=G2G.wxID_IMINTEGRATE)
1475    G2frame.Bind(wx.EVT_MENU, OnIntegrateAll, id=G2G.wxID_INTEGRATEALL)
1476    G2frame.Bind(wx.EVT_MENU, OnCopyControls, id=G2G.wxID_IMCOPYCONTROLS)
1477    G2frame.Bind(wx.EVT_MENU, OnCopySelected, id=G2G.wxID_IMCOPYSELECTED)
1478    G2frame.Bind(wx.EVT_MENU, OnSaveControls, id=G2G.wxID_IMSAVECONTROLS)
1479    G2frame.Bind(wx.EVT_MENU, OnSaveMultiControls, id=G2G.wxID_SAVESELECTEDCONTROLS)
1480    G2frame.Bind(wx.EVT_MENU, OnLoadControls, id=G2G.wxID_IMLOADCONTROLS)
1481    G2frame.Bind(wx.EVT_MENU, OnLoadMultiControls, id=G2G.wxID_LOADELECTEDCONTROLS)
1482    G2frame.Bind(wx.EVT_MENU, OnTransferAngles, id=G2G.wxID_IMXFERCONTROLS)
1483    G2frame.Bind(wx.EVT_MENU, OnResetDist, id=G2G.wxID_IMRESETDIST)
1484    def OnDestroy(event):
1485        G2frame.autoIntFrame = None
1486    def OnAutoInt(event):
1487        if G2frame.autoIntFrame: # ensure only one open at a time
1488            G2frame.autoIntFrame.Raise()
1489            return
1490        PollTime = GSASIIpath.GetConfigValue('Autoint_PollTime',30.)
1491        G2frame.autoIntFrame = AutoIntFrame(G2frame,PollTime=PollTime)
1492        # debug code to reload code for window on each use
1493        #import GSASIIimgGUI
1494        #reload(GSASIIimgGUI)
1495        #G2frame.autoIntFrame = GSASIIimgGUI.AutoIntFrame(G2frame,PollTime=PollTime)
1496
1497        G2frame.autoIntFrame.Bind(wx.EVT_WINDOW_DESTROY,OnDestroy) # clean up name on window close
1498    G2frame.Bind(wx.EVT_MENU, OnAutoInt, id=G2G.wxID_IMAUTOINTEG)
1499    def OnIntPDFtool(event):
1500        import subprocess
1501        ex = sys.executable
1502        if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable
1503            if os.path.exists(ex+'w'): ex += 'w'
1504        if G2frame.GSASprojectfile:
1505            project = os.path.abspath(G2frame.GSASprojectfile)
1506        else:
1507            project = ''
1508        subprocess.Popen([ex,os.path.join(GSASIIpath.path2GSAS2,'GSASIIIntPDFtool.py'),project])
1509    G2frame.Bind(wx.EVT_MENU, OnIntPDFtool, id=G2G.wxID_IMINTEGPDFTOOL)
1510
1511    mainSizer = G2frame.dataWindow.GetSizer()
1512    mainSizer.Add((5,10),0)   
1513    mainSizer.Add(ComboSizer(),0,wx.ALIGN_LEFT)
1514    mainSizer.Add((5,5),0)
1515    Range = data['range'] # allows code to be same in Masks
1516    MaxSizer = MaxSizer()               #keep this so it can be changed in BackSizer   
1517    mainSizer.Add(MaxSizer,0,wx.ALIGN_LEFT|wx.EXPAND|wx.ALL)
1518   
1519    mainSizer.Add((5,5),0)
1520    DataSizer = wx.FlexGridSizer(0,2,5,0)
1521    DataSizer.Add(CalibCoeffSizer(),0)
1522    DataSizer.Add(IntegrateSizer(),0)       
1523    mainSizer.Add(DataSizer,0)
1524    mainSizer.Add((5,5),0)           
1525    mainSizer.Add(BackSizer(),0)
1526    mainSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration controls:'),0,WACV)
1527    mainSizer.Add((5,5),0)
1528    mainSizer.Add(CalibSizer(),0,WACV)
1529    mainSizer.Add((5,5),0)
1530    mainSizer.Add(GonioSizer(),0,WACV)   
1531    G2frame.dataWindow.SetDataSize()
1532   
1533################################################################################
1534##### Masks
1535################################################################################
1536def CleanupMasks(data):
1537    '''If a mask creation is not completed, an empty mask entry is created in the
1538    masks array. This cleans them out. It is called when the masks page is first loaded
1539    and before saving them or after reading them in. This should also probably be done
1540    before they are used for integration.
1541    '''
1542    for key in ['Points','Rings','Arcs','Polygons',]:
1543        data[key] = data.get(key,[])
1544        l1 = len(data[key])
1545        data[key] = [i for i in data[key] if len(i)]
1546        l2 = len(data[key])
1547        if GSASIIpath.GetConfigValue('debug') and l1 != l2:
1548            print ('DBG_Mask Cleanup: %s was %d entries, now %d'%(key,l1,l2))
1549   
1550def UpdateMasks(G2frame,data):
1551    '''Shows and handles the controls on the "Masks" data tree entry
1552    '''
1553   
1554    def OnTextMsg(event):
1555        Obj = event.GetEventObject()
1556        Obj.SetToolTipString('Drag this mask on 2D Powder Image with mouse to change ')
1557
1558    def Replot(*args,**kwargs):
1559        wx.CallAfter(G2plt.PlotExposedImage,G2frame)
1560
1561    def newReplot(*args,**kwargs):
1562        wx.CallAfter(G2plt.PlotExposedImage,G2frame,newPlot=True)
1563
1564    def onDeleteMask(event):
1565        Obj = event.GetEventObject()
1566        typ = Obj.locationcode.split('+')[1]
1567        num = int(Obj.locationcode.split('+')[2])
1568        del(data[typ][num])
1569        wx.CallAfter(UpdateMasks,G2frame,data)
1570        G2plt.PlotExposedImage(G2frame,event=event)
1571       
1572    def OnSpotChange(event):
1573        r,c = event.GetRow(),event.GetCol()
1574        if c == 2:
1575            del Spots[r]
1576            SpotTable.DeleteRow(r)
1577        else:
1578            Spots[r][2] = float(SpotGrid.GetCellValue(r,c))
1579        SpotGrid.ForceRefresh()
1580        wx.CallAfter(UpdateMasks,G2frame,data)
1581        G2plt.PlotExposedImage(G2frame,event=event)
1582        event.Skip()
1583
1584    def onDeleteFrame(event):
1585        data['Frames'] = []
1586        wx.CallAfter(UpdateMasks,G2frame,data)
1587        G2plt.PlotExposedImage(G2frame,event=event)
1588
1589    def OnCopyMask(event):
1590        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
1591        if len(Names) == 1:
1592            G2frame.ErrorDialog('Nothing to copy masks to','There must be more than one "IMG" pattern')
1593            return
1594        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
1595        Names.pop(Names.index(Source))
1596        Data = copy.deepcopy(data)
1597        Thresh = Data.pop('Thresholds')     # & remove it as well
1598        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy mask data','Copy masks from '+Source+' to:',Names)
1599        try:
1600            if dlg.ShowModal() == wx.ID_OK:
1601                items = dlg.GetSelections()
1602                for item in items:
1603                    name = Names[item]
1604                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1605                    MId = G2gd.GetGPXtreeItemId(G2frame,Id,'Masks')
1606                    Mask = G2frame.GPXtree.GetItemPyData(MId)
1607                    Mask.update(copy.deepcopy(Data))
1608                    Mask['Thresholds'][1][0] = Thresh[1][0]  #copy only lower threshold
1609                    G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'),Mask)
1610        finally:
1611            dlg.Destroy()
1612
1613    def OnSaveMask(event):
1614        CleanupMasks(data)
1615        pth = G2G.GetExportPath(G2frame)
1616        dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', 
1617            'image mask files (*.immask)|*.immask',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
1618        try:
1619            if dlg.ShowModal() == wx.ID_OK:
1620                filename = dlg.GetPath()
1621                filename = os.path.splitext(filename)[0]+'.immask'
1622                File = open(filename,'w')
1623                keys = ['Points','Rings','Arcs','Polygons','Xlines','Ylines','Frames','Thresholds']
1624                for key in keys:
1625                    File.write(key+':'+str(data[key])+'\n')
1626                File.close()
1627        finally:
1628            dlg.Destroy()
1629       
1630    def OnLoadMask(event):
1631        if event.Id == G2G.wxID_MASKLOADNOT:
1632            ignoreThreshold = True
1633        else:
1634            ignoreThreshold = False
1635        pth = G2G.GetImportPath(G2frame)
1636        if not pth: pth = '.'
1637        dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', 
1638            'image mask files (*.immask)|*.immask',wx.FD_OPEN)
1639        try:
1640            if dlg.ShowModal() == wx.ID_OK:
1641                filename = dlg.GetPath()
1642               
1643                G2fil.readMasks(filename,data,ignoreThreshold)
1644                wx.CallAfter(UpdateMasks,G2frame,data)
1645                G2plt.PlotExposedImage(G2frame,event=event)               
1646        finally:
1647            dlg.Destroy()
1648           
1649    def OnAutoSpotMask(event):
1650        'Do auto search for spot masks'
1651        if wx.MessageDialog(G2frame.dataWindow,'NB: This will clear any old spot masks',
1652            'Auto Spot Masks', wx.OK|wx.CANCEL).ShowModal() == wx.ID_OK:
1653            Controls = copy.deepcopy(G2frame.GPXtree.GetItemPyData( 
1654                G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls')))
1655            wave = Controls['wavelength']
1656            LUtth = np.array(Controls['IOtth'])
1657            dsp0 = wave/(2.0*sind(LUtth[0]/2.0))
1658            dsp1 = wave/(2.0*sind(LUtth[1]/2.0))
1659            x0 = G2img.GetDetectorXY(dsp0,0.0,Controls)[0]
1660            x1 = G2img.GetDetectorXY(dsp1,0.0,Controls)[0]   
1661            nChans = int(1000*(x1-x0)/Controls['pixelSize'][0])//2
1662            dlg = wx.ProgressDialog("Auto spot masking for %d bins"%nChans,"Processed 2-theta rings = ",nChans+3,
1663                style = wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT)
1664            time0 = time.time()
1665            data['SpotMask']['spotMask'] = G2img.AutoSpotMask(G2frame.ImageZ,data,Controls,nChans,dlg)
1666            print(' Autospot processing time: %.2f m'%((time.time()-time0)/60.))
1667            wx.CallAfter(UpdateMasks,G2frame,data)
1668            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1669
1670    def OnDeleteSpotMask(event):
1671        data['Points'] = []
1672        wx.CallAfter(UpdateMasks,G2frame,data)
1673        G2plt.PlotExposedImage(G2frame,newPlot=True,event=event)         
1674           
1675    def ToggleSpotMaskMode(event):
1676        G2plt.ToggleMultiSpotMask(G2frame)
1677       
1678    def OnNewArcMask(event):
1679        'Start a new arc mask'
1680        G2frame.MaskKey = 'a'
1681        G2plt.OnStartMask(G2frame)
1682       
1683    def OnNewRingMask(event):
1684        'Start a new ring mask'
1685        G2frame.MaskKey = 'r'
1686        G2plt.OnStartMask(G2frame)
1687       
1688    def OnNewXlineMask(event):
1689        'Start a new x-line mask'
1690        print('x')
1691        G2frame.MaskKey = 'x'
1692        G2plt.OnStartMask(G2frame)
1693       
1694    def OnNewYlineMask(event):
1695        'Start a new y-line mask'
1696        G2frame.MaskKey = 'y'
1697        G2plt.OnStartMask(G2frame)
1698       
1699    def OnNewPolyMask(event):
1700        'Start a new polygon mask'
1701        G2frame.MaskKey = 'p'
1702        G2plt.OnStartMask(G2frame)
1703       
1704    def OnNewFrameMask(event):
1705        'Start a new Frame mask'
1706        G2frame.MaskKey = 'f'
1707        G2plt.OnStartMask(G2frame)
1708       
1709    def MaxSizer():
1710        '''Defines a sizer with sliders and TextCtrl widgets for controlling the colormap
1711        for the image, as well as callback routines.
1712        '''
1713        def OnNewVal(invalid,value,tc):
1714            '''Called when a Imax or Imin value is typed into a Validated TextCrtl (which puts
1715            the value into the data['range'] nested list).
1716            This adjusts the slider positions to match the current values
1717            '''
1718            scaleSel.SetSelection(len(scaleChoices)-1)
1719            r11 = min(max(Range[1][1],Range[1][0]+1),Range[0][1]) # keep values in range
1720            if r11 != Range[1][1]:
1721                Range[1][1] = r11
1722                maxVal.SetValue(int(Range[1][1]))
1723            r10 = max(min(Range[1][0],Range[1][1]-1),Range[0][0])
1724            if r10 != Range[1][0]:
1725                Range[1][0] = r10
1726                minVal.SetValue(int(Range[1][0]))
1727            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1728            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1729            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1730            maxSel.SetValue(sv1)
1731            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
1732            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1733            minSel.SetValue(sv0)
1734            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1735            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
1736            if mplOld:
1737                Page.canvas.draw()
1738            else:
1739                Page.canvas.draw_idle()
1740           
1741        G2frame.prevMaxValue = None   
1742        def OnMaxSlider(event):
1743            val = maxSel.GetValue()
1744            if G2frame.prevMaxValue == val: return # if this val has been processed, no need to repeat
1745            scaleSel.SetSelection(len(scaleChoices)-1)
1746            G2frame.prevMaxValue = val
1747            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1748            Range[1][1] = int(0.5 + (val * sqrtDeltZero / 100.)**2 + Range[1][0] + 1)
1749            maxVal.SetValue(int(0.5+Range[1][1]))
1750            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
1751            minSel.SetValue(int(0.5 + 100*(Range[1][0]/DeltOne)))
1752            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1753            minSel.SetValue(sv0)
1754            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1755            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
1756            if mplOld:
1757                Page.canvas.draw()
1758            else:
1759                Page.canvas.draw_idle()
1760           
1761        G2frame.prevMinValue = None   
1762        def OnMinSlider(event):
1763            val = minSel.GetValue()
1764            scaleSel.SetSelection(len(scaleChoices)-1)
1765            if G2frame.prevMinValue == val: return # if this val has been processed, no need to repeat
1766            G2frame.prevMinValue = val
1767            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
1768            Range[1][0] = max(0,int(0.5 + val * DeltOne / 100 + Range[0][0]))
1769            minVal.SetValue(int(Range[1][0]))
1770            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1771            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1772            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1773            maxSel.SetValue(sv1)
1774            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1775            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
1776            if mplOld:
1777                Page.canvas.draw()
1778            else:
1779                Page.canvas.draw_idle()
1780           
1781        def OnAutoSet(event):
1782            '''Responds to a button labeled 95%, etc; Sets the Imax and Imin values
1783            for the image so that 95% (etc.) of pixels are inside the color map limits.
1784            An equal number of pixels are dropped at the minimum and maximum levels.
1785            '''
1786            try:
1787                val = int(event.GetEventObject().GetStringSelection()[:-1])
1788                margin = (100-val)/2.
1789            except:
1790                margin = 0
1791                event.GetEventObject().SetSelection(0)
1792            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1793            if margin == 0:
1794                Range[1] = list(Range[0])
1795            else:
1796                Range[1][0] = int(np.percentile(Page.ImgObj.get_array().compressed(),margin))
1797                Range[1][1] = int(np.percentile(Page.ImgObj.get_array().compressed(),100-margin))
1798            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1799            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1800            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1801            maxSel.SetValue(sv1)
1802            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
1803            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1804            minSel.SetValue(sv0)
1805            minVal.SetValue(int(Range[1][0]))
1806            maxVal.SetValue(int(Range[1][1]))
1807            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1808            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
1809            if mplOld:
1810                Page.canvas.draw()
1811            else:
1812                Page.canvas.draw_idle()
1813
1814        mplv = mpl.__version__.split('.')
1815        mplOld = mplv[0] == '1' and int(mplv[1]) < 4 # use draw_idle for newer matplotlib versions
1816        # Plot color scaling uses limits as below:
1817        #   (Imin0, Imax0) => Range[0] = data['range'][0] # lowest to highest pixel intensity
1818        #   [Imin, Imax] => Range[1] = data['range'][1] #   lowest to highest pixel intensity on cmap scale
1819       
1820        maxSizer = wx.BoxSizer(wx.VERTICAL)
1821        slideSizer = wx.FlexGridSizer(2,3,5,5)
1822        slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Max intensity'),0,WACV)
1823        # maxSel is a slider with 101 steps scaled from Imin+1 to Imax0 with sqrt scaling
1824        # slider value = sv = 100 * sqrt((Imax-Imin-1)/(Imax0-Imin-1))
1825        # Imax = (sv * sqrt(Imax0-Imin-1) / 100)**2 + Imin + 1
1826        sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1827        sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1828        sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1829        maxSel = wx.Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv1)
1830        maxVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],1,min=Range[0][0]+1,
1831            max=Range[0][1],OnLeave=OnNewVal)
1832        slideSizer.Add(maxVal,0,WACV)
1833        slideSizer.Add(maxSel,flag=wx.EXPAND|wx.ALL)
1834        slideSizer.AddGrowableCol(2)
1835        maxSel.Bind(wx.EVT_SLIDER, OnMaxSlider)
1836        slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min intensity'),0,WACV)
1837        # minSel is a slider with 101 steps scaled from Imin0 to Imax-1 with linear scaling
1838        # slider value = sv0 = 100 * (Imin-Imin0)/(Imax-Imin0-1)
1839        # Imin = sv0 * (Imax-Imin0-1) / 100 + Imin0
1840        DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
1841        sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1842        minVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],0,
1843            max=Range[0][1],typeHint=int,OnLeave=OnNewVal)
1844        slideSizer.Add(minVal,0,WACV)
1845        minSel = wx.Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv0)
1846        slideSizer.Add(minSel,flag=wx.EXPAND|wx.ALL)
1847        minSel.Bind(wx.EVT_SLIDER, OnMinSlider)
1848        maxSizer.Add(slideSizer,flag=wx.EXPAND|wx.ALL)
1849        autoSizer = wx.BoxSizer(wx.HORIZONTAL)
1850        autoSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Auto scaler '),0,WACV)
1851        scaleChoices = ("100%","99%","95%","90%","80%","?")
1852        scaleSel = wx.Choice(G2frame.dataWindow,choices=scaleChoices,size=(-1,-1))
1853        if (Range[1][0] == Range[0][0] and
1854            Range[1][1] == Range[0][1]):
1855            scaleSel.SetSelection(0)
1856        else:
1857            scaleSel.SetSelection(len(scaleChoices)-1)
1858        scaleSel.Bind(wx.EVT_CHOICE,OnAutoSet)
1859        autoSizer.Add(scaleSel,0,WACV)
1860        maxSizer.Add(autoSizer)
1861        return maxSizer
1862   
1863    def OnDelBtn(event):
1864        data['SpotMask'] = {'esdMul':3.,'spotMask':None}
1865        wx.CallAfter(UpdateMasks,G2frame,data)
1866               
1867   
1868    G2frame.dataWindow.ClearData()
1869    startScroll = None
1870    if G2frame.dataWindow:
1871        startScroll = G2frame.dataWindow.GetScrollPos(wx.VERTICAL) # save scroll position
1872    else:
1873        CleanupMasks(data) # posting page for 1st time; clean out anything unfinished
1874    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.MaskMenu)
1875    G2frame.Bind(wx.EVT_MENU, OnCopyMask, id=G2G.wxID_MASKCOPY)
1876    G2frame.Bind(wx.EVT_MENU, OnLoadMask, id=G2G.wxID_MASKLOAD)
1877    G2frame.Bind(wx.EVT_MENU, OnLoadMask, id=G2G.wxID_MASKLOADNOT)
1878    G2frame.Bind(wx.EVT_MENU, OnSaveMask, id=G2G.wxID_MASKSAVE)
1879    G2frame.Bind(wx.EVT_MENU, OnAutoSpotMask, id=G2G.wxID_FINDSPOTS)
1880    G2frame.Bind(wx.EVT_MENU, OnDeleteSpotMask, id=G2G.wxID_DELETESPOTS)
1881    G2frame.Bind(wx.EVT_MENU, ToggleSpotMaskMode, id=G2G.wxID_NEWMASKSPOT)
1882    G2frame.Bind(wx.EVT_MENU, OnNewArcMask, id=G2G.wxID_NEWMASKARC)
1883    G2frame.Bind(wx.EVT_MENU, OnNewRingMask, id=G2G.wxID_NEWMASKRING)
1884    G2frame.Bind(wx.EVT_MENU, OnNewXlineMask, id=G2G.wxID_NEWMASKXLINE)
1885    G2frame.Bind(wx.EVT_MENU, OnNewYlineMask, id=G2G.wxID_NEWMASKYLINE)
1886    G2frame.Bind(wx.EVT_MENU, OnNewPolyMask, id=G2G.wxID_NEWMASKPOLY)
1887    G2frame.Bind(wx.EVT_MENU, OnNewFrameMask, id=G2G.wxID_NEWMASKFRAME)
1888    if G2frame.MaskKey == 'f':
1889        G2frame.GetStatusBar().SetStatusText('Frame mask active - LB pick next point, RB close polygon',1)
1890    elif G2frame.MaskKey == 'p':
1891        G2frame.GetStatusBar().SetStatusText('Polygon mask active - LB pick next point, RB close polygon',1)
1892    elif G2frame.MaskKey == 'a':
1893        G2frame.GetStatusBar().SetStatusText('Arc mask active - LB pick arc location',1)
1894    elif G2frame.MaskKey == 'r':
1895        G2frame.GetStatusBar().SetStatusText('Ring mask active - LB pick ring location',1)
1896    elif G2frame.MaskKey == 'x':
1897        G2frame.GetStatusBar().SetStatusText('X-line mask active - LB pick x line of pixels',1)
1898    elif G2frame.MaskKey == 'y':
1899        G2frame.GetStatusBar().SetStatusText('Y-line mask active - LB pick y line of pixels',1)
1900    else:
1901        G2frame.GetStatusBar().SetStatusText("To add mask: press a,r,s,x,y,p or f on 2D image for arc/ring/spot/xline/yline/polygon/frame",1)
1902    mainSizer = G2frame.dataWindow.GetSizer()
1903    mainSizer.Add((5,10),0)
1904
1905    thresh = data['Thresholds']         #min/max intensity range
1906    Spots = data['Points']               #x,y,radius in mm
1907    Rings = data['Rings']               #radius, thickness
1908    Polygons = data['Polygons']         #3+ x,y pairs
1909    if 'Xlines' not in data:            #single rows/columns of bad pixels
1910        data['Xlines'] = []
1911        data['Ylines'] = []
1912    Xlines = data['Xlines']
1913    Ylines = data['Ylines']
1914    # not a good place for patch -- this not always called
1915    if 'Frames' not in data:
1916        data['Frames'] = []
1917    if 'SpotMask' not in data:
1918        data['SpotMask'] = {'esdMul':3.,'spotMask':None}
1919    frame = data['Frames']             #3+ x,y pairs
1920    Arcs = data['Arcs']                 #radius, start/end azimuth, thickness
1921
1922    ######################################################################
1923    CId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls')
1924    controlData = G2frame.GPXtree.GetItemPyData(CId)
1925    Range = controlData['range']
1926    MaxSizer = MaxSizer()               #keep this so it can be changed in BackSizer   
1927    mainSizer.Add(MaxSizer,0,wx.ALIGN_LEFT|wx.EXPAND|wx.ALL)
1928
1929    littleSizer = wx.FlexGridSizer(0,3,0,5)
1930    littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Lower/Upper limits '),0,WACV)
1931    Text = wx.TextCtrl(G2frame.dataWindow,value=str(thresh[0][0]),style=wx.TE_READONLY)
1932    littleSizer.Add(Text,0,WACV)
1933    Text.SetBackgroundColour(VERY_LIGHT_GREY)
1934    Text = wx.TextCtrl(G2frame.dataWindow,value=str(thresh[0][1]),style=wx.TE_READONLY)
1935    littleSizer.Add(Text,0,WACV)
1936    Text.SetBackgroundColour(VERY_LIGHT_GREY)
1937    littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Lower/Upper thresholds '),0,WACV)
1938    lowerThreshold = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=thresh[1],key=0,
1939        min=thresh[0][0],OnLeave=newReplot,typeHint=int)
1940    littleSizer.Add(lowerThreshold,0,WACV)
1941    upperThreshold = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=thresh[1],key=1,
1942        max=thresh[0][1],OnLeave=newReplot,typeHint=int)
1943    littleSizer.Add(upperThreshold,0,WACV)
1944    mainSizer.Add(littleSizer,0,)
1945    spotSizer = wx.BoxSizer(wx.HORIZONTAL)
1946    data['SpotMask']['esdMul'] = float(data['SpotMask']['esdMul'])
1947    spotSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Select n*sigma spot rejection (n=1-10): '),0,WACV)
1948    spotSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=data['SpotMask'],
1949        key='esdMul',min=1.,max=10.,size=(40,25)),0,WACV)
1950    numPix = 0
1951    if data['SpotMask']['spotMask'] is not None:
1952        numPix = np.count_nonzero(data['SpotMask']['spotMask'])
1953    spotSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Number of pixels in spot mask: %d  '%numPix),0,WACV)
1954    delbtn = wx.Button(G2frame.dataWindow,label='Clear spot mask')
1955    delbtn.Bind(wx.EVT_BUTTON,OnDelBtn)
1956    spotSizer.Add(delbtn,0,WACV)
1957    mainSizer.Add(spotSizer,0,WACV)
1958    if len(Spots):
1959        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Spot masks(on plot LB drag to move, shift-LB drag to resize, RB to delete)')
1960        lbl.SetBackgroundColour(wx.Colour(200,200,210))
1961        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
1962        colTypes = [wg.GRID_VALUE_STRING,wg.GRID_VALUE_FLOAT+':10,2',wg.GRID_VALUE_BOOL]
1963        colIds = ['position, mm','diameter, mm','Delete?']
1964        rowIds = [str(i) for i in range(len(Spots))]
1965        table = [['%.2f,%.2f'%(item[0],item[1]),item[2],False] for item in Spots]
1966        SpotTable = G2G.Table(table,rowLabels=rowIds,colLabels=colIds,types=colTypes)
1967        SpotGrid = G2G.GSGrid(G2frame.dataWindow)
1968        SpotGrid.SetTable(SpotTable,True)
1969        SpotGrid.AutoSizeColumns(True)
1970        SpotGrid.SetColSize(1,80)
1971        for r in range(len(Spots)):
1972            SpotGrid.SetCellStyle(r,0,VERY_LIGHT_GREY,True)
1973        if 'phoenix' in wx.version():
1974            SpotGrid.Bind(wg.EVT_GRID_CELL_CHANGED, OnSpotChange)
1975        else:
1976            SpotGrid.Bind(wg.EVT_GRID_CELL_CHANGE, OnSpotChange)
1977        mainSizer.Add(SpotGrid,0,)
1978    if Rings:
1979        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Ring masks')
1980        lbl.SetBackgroundColour(wx.Colour(200,200,210))
1981        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
1982        littleSizer = wx.FlexGridSizer(0,3,0,5)
1983        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' 2-theta,deg'),0,WACV)
1984        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' thickness, deg'),0,WACV)
1985        littleSizer.Add((5,0),0)
1986        for i in range(len(Rings)):
1987            if Rings[i]:
1988                ringText = wx.TextCtrl(parent=G2frame.dataWindow,value=("%.3f" % (Rings[i][0])),
1989                    style=wx.TE_READONLY)
1990                ringText.SetBackgroundColour(VERY_LIGHT_GREY)
1991                ringText.Bind(wx.EVT_ENTER_WINDOW,OnTextMsg)
1992                littleSizer.Add(ringText,0,WACV)
1993                ringThick = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=Rings[i],key=1,
1994                    min=0.001,max=1.,OnLeave=Replot,nDig=[8,3])
1995                littleSizer.Add(ringThick,0,WACV)
1996                ringDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
1997                    locationcode='Delete+Rings+'+str(i),handler=onDeleteMask)
1998                littleSizer.Add(ringDelete,0,WACV)
1999        mainSizer.Add(littleSizer,0,)
2000    if Arcs:
2001        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Arc masks')
2002        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2003        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
2004        littleSizer = wx.FlexGridSizer(0,4,0,5)
2005        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' 2-theta,deg'),0,WACV)
2006        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' azimuth, deg'),0,WACV)
2007        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' thickness, deg'),0,WACV)
2008        littleSizer.Add((5,0),0)
2009        for i in range(len(Arcs)):
2010            if Arcs[i]:
2011                tth,azimuth,thick = Arcs[i]
2012                arcText = wx.TextCtrl(parent=G2frame.dataWindow,value=("%.3f" % (tth)),
2013                    style=wx.TE_READONLY)
2014                arcText.SetBackgroundColour(VERY_LIGHT_GREY)
2015                arcText.Bind(wx.EVT_ENTER_WINDOW,OnTextMsg)
2016                littleSizer.Add(arcText,0,WACV)
2017                azmText = wx.TextCtrl(parent=G2frame.dataWindow,value=("%d,%d" % (azimuth[0],azimuth[1])),
2018                    style=wx.TE_READONLY)
2019                azmText.SetBackgroundColour(VERY_LIGHT_GREY)
2020                azmText.Bind(wx.EVT_ENTER_WINDOW,OnTextMsg)
2021                littleSizer.Add(azmText,0,WACV)
2022                arcThick = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=Arcs[i],key=2,
2023                    min=0.001,max=20.,OnLeave=Replot,nDig=[8,3])
2024                littleSizer.Add(arcThick,0,WACV)
2025                arcDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2026                    locationcode='Delete+Arcs+'+str(i),handler=onDeleteMask)
2027                littleSizer.Add(arcDelete,0,WACV)
2028        mainSizer.Add(littleSizer,0,)
2029       
2030    if Xlines:
2031        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' X line masks')
2032        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2033        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
2034        littleSizer = wx.FlexGridSizer(0,2,0,5)
2035        for i in range(len(Xlines)):
2036            if Xlines[i]:
2037                littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='at Y-pixel: %d'%(Xlines[i])),0,WACV)
2038                xlineDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2039                    locationcode='Delete+Xlines+'+str(i),handler=onDeleteMask)
2040                littleSizer.Add(xlineDelete,0,WACV)
2041        mainSizer.Add(littleSizer,0,)
2042       
2043    if Ylines:
2044        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Y line masks')
2045        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2046        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
2047        littleSizer = wx.FlexGridSizer(0,2,0,5)
2048        for i in range(len(Ylines)):
2049            if Ylines[i]:
2050                littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='at X-pixel: %d'%(Ylines[i])),0,WACV)
2051                ylineDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2052                    locationcode='Delete+Ylines+'+str(i),handler=onDeleteMask)
2053                littleSizer.Add(ylineDelete,0,WACV)
2054        mainSizer.Add(littleSizer,0,)
2055       
2056    if Polygons:
2057        lbl = wx.StaticText(parent=G2frame.dataWindow,
2058            label=' Polygon masks (on plot LB vertex drag to move, RB vertex drag to insert)')
2059        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2060        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
2061        littleSizer = wx.FlexGridSizer(0,2,0,5)
2062        for i in range(len(Polygons)):
2063            if Polygons[i]:
2064                polyList = []
2065                for x,y in Polygons[i]:
2066                    polyList.append("%.2f, %.2f"%(x,y))
2067                polyText = wx.ComboBox(G2frame.dataWindow,value=polyList[0],choices=polyList,style=wx.CB_READONLY)
2068                littleSizer.Add(polyText,0,WACV)
2069                polyDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2070                    locationcode='Delete+Polygons+'+str(i),handler=onDeleteMask)
2071                littleSizer.Add(polyDelete,0,WACV)
2072        mainSizer.Add(littleSizer,0,)
2073    if frame:
2074        lbl = wx.StaticText(parent=G2frame.dataWindow,
2075            label=' Frame mask (on plot LB vertex drag to move, RB vertex drag to insert)')
2076        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2077        mainSizer.Add(lbl,0,wx.EXPAND|wx.ALIGN_CENTER,0)
2078        littleSizer = wx.FlexGridSizer(0,2,0,5)
2079        frameList = []
2080        for x,y in frame:
2081            frameList.append("%.2f, %.2f"%(x,y))
2082        frameText = wx.ComboBox(G2frame.dataWindow,value=frameList[0],choices=frameList,style=wx.CB_READONLY)
2083        littleSizer.Add(frameText,0,WACV)
2084        frameDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2085            locationcode='Delete+Frame',handler=onDeleteFrame)
2086        littleSizer.Add(frameDelete,0,WACV)
2087        mainSizer.Add(littleSizer,0,)
2088    G2frame.dataWindow.SetDataSize()
2089    if startScroll: # reset scroll to saved position
2090        G2frame.dataWindow.Scroll(0,startScroll) # set to saved scroll position
2091        wx.Yield()
2092
2093################################################################################
2094##### Stress/Strain
2095################################################################################
2096
2097def UpdateStressStrain(G2frame,data):
2098    '''Shows and handles the controls on the "Stress/Strain"
2099    data tree entry
2100    '''
2101   
2102    def OnAppendDzero(event):
2103        data['d-zero'].append({'Dset':1.0,'Dcalc':0.0,'pixLimit':10,'cutoff':1.0,
2104            'ImxyObs':[[],[]],'ImtaObs':[[],[]],'ImtaCalc':[[],[]],'Emat':[1.0,1.0,1.0],'fixDset':False})
2105        UpdateStressStrain(G2frame,data)
2106       
2107    def OnUpdateDzero(event):
2108        for item in data['d-zero']:
2109            if item['Dcalc']:   #skip unrefined ones
2110                item['Dset'] = item['Dcalc']
2111        UpdateStressStrain(G2frame,data)
2112           
2113    def OnCopyStrSta(event):
2114        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
2115        if len(Names) == 1:
2116            G2frame.ErrorDialog('Nothing to copy controls to','There must be more than one "IMG" pattern')
2117            return
2118        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
2119        Names.pop(Names.index(Source))
2120        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy stress/strain controls','Copy controls from '+Source+' to:',Names)
2121        try:
2122            if dlg.ShowModal() == wx.ID_OK:
2123                items = dlg.GetSelections()
2124                for item in items:
2125                    name = Names[item]
2126                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
2127                    CId = G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain')
2128                    oldData = G2frame.GPXtree.GetItemPyData(CId)
2129                    load = oldData.get('Sample load',0.0)
2130                    Data = copy.deepcopy(data)
2131                    Data['Sample load'] = load
2132                    G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain'),Data)
2133        finally:
2134            dlg.Destroy()
2135            G2frame.GPXtree.SelectItem(G2frame.PickId)
2136
2137    def OnLoadStrSta(event):
2138        pth = G2G.GetImportPath(G2frame)
2139        if not pth: pth = '.'
2140        dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', 
2141            'image control files (*.strsta)|*.strsta',wx.FD_OPEN)
2142        try:
2143            if dlg.ShowModal() == wx.ID_OK:
2144                filename = dlg.GetPath()
2145                File = open(filename,'r')
2146                S = File.read()
2147                data = eval(S)
2148                Controls = G2frame.GPXtree.GetItemPyData(
2149                    G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2150                G2img.FitStrSta(G2frame.ImageZ,data,Controls)
2151                UpdateStressStrain(G2frame,data)
2152                G2plt.PlotExposedImage(G2frame,event=event)
2153                G2plt.PlotStrain(G2frame,data,newPlot=True)
2154                File.close()
2155        finally:
2156            dlg.Destroy()
2157
2158    def OnSaveStrSta(event):
2159        pth = G2G.GetExportPath(G2frame)
2160        dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', 
2161            'image control files (*.strsta)|*.strsta',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
2162        try:
2163            if dlg.ShowModal() == wx.ID_OK:
2164                filename = dlg.GetPath()
2165                File = open(filename,'w')
2166                keys = ['Type','Sample phi','Sample z','Sample load']
2167                keys2 = ['Dset','Dcalc','pixLimit','cutoff','Emat','fixDset']
2168                File.write('{\n\t')
2169                for key in keys:
2170                    if key in 'Type':
2171                        File.write("'"+key+"':'"+data[key]+"',")
2172                    else:
2173                        File.write("'"+key+"':"+str(data[key])+',')
2174                File.write('\n\t'+"'d-zero':[\n")
2175                for data2 in data['d-zero']:
2176                    File.write('\t\t{')
2177                    for key in keys2:
2178                        File.write("'"+key+"':"+str(data2[key])+',')
2179                    File.write("'ImxyObs':[[],[]],'ImtaObs':[[],[]],'ImtaCalc':[[],[]]},\n")
2180                File.write('\t]\n}')
2181                File.close()
2182        finally:
2183            dlg.Destroy()
2184           
2185    def OnStrStaSample(event):
2186        filename = ''
2187        pth = G2G.GetImportPath(G2frame)
2188        if not pth: pth = '.'
2189        dlg = wx.FileDialog(G2frame, 'Choose multihistogram metadata text file', pth, '', 
2190            'metadata file (*.*)|*.*',wx.FD_OPEN)
2191        try:
2192            if dlg.ShowModal() == wx.ID_OK:
2193                filename = dlg.GetPath()
2194                File = open(filename,'r')
2195                S = File.readline()
2196                newItems = []
2197                itemNames = []
2198                Comments = []
2199                while S:
2200                    if S[0] == '#':
2201                        Comments.append(S)
2202                        S = File.readline()
2203                        continue
2204                    S = S.replace(',',' ').replace('\t',' ')
2205                    Stuff = S[:-1].split()
2206                    itemNames.append(Stuff[0])
2207                    newItems.append(Stuff[1:])
2208                    S = File.readline()               
2209                File.close()
2210        finally:
2211            dlg.Destroy()
2212        if not filename:
2213            G2frame.ErrorDialog('Nothing to do','No file selected')
2214            return
2215        dataDict = dict(zip(itemNames,newItems))
2216        ifany = False
2217        Names = [' ','Sample phi','Sample z','Sample load']
2218        dlg = G2G.G2ColumnIDDialog( G2frame,' Choose multihistogram metadata columns:',
2219            'Select columns',Comments,Names,np.array(newItems).T)
2220        try:
2221            if dlg.ShowModal() == wx.ID_OK:
2222                colNames,newData = dlg.GetSelection()
2223                dataDict = dict(zip(itemNames,newData.T))
2224                for item in colNames:
2225                    if item != ' ':
2226                        ifany = True
2227        finally:
2228            dlg.Destroy()
2229        if not ifany:
2230            G2frame.ErrorDialog('Nothing to do','No columns identified')
2231            return
2232        histList = []
2233        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)       
2234        while item:
2235            name = G2frame.GPXtree.GetItemText(item)
2236            if name.startswith('IMG'):
2237                histList.append(name)
2238            item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
2239        colIds = {}
2240        for i,name in enumerate(colNames):
2241            if name != ' ':
2242                colIds[name] = i
2243        for hist in histList:
2244            name = hist.split()[1]  #this is file name
2245            if name in dataDict:
2246                newItems = {}
2247                for item in colIds:
2248                    newItems[item] = float(dataDict[name][colIds[item]])
2249                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist)
2250                stsrData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain'))
2251                stsrData.update(newItems)       
2252        UpdateStressStrain(G2frame,data)       
2253   
2254    def OnPlotStrSta(event):
2255        Controls = G2frame.GPXtree.GetItemPyData(
2256            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2257        RingInt = G2img.IntStrSta(G2frame.ImageZ,data,Controls)
2258        Names = ['d=%.3f'%(ring['Dcalc']) for ring in data['d-zero']]
2259        G2plt.PlotExposedImage(G2frame,event=event)
2260        G2frame.G2plotNB.Delete('Ring Intensities')
2261        G2plt.PlotXY(G2frame,RingInt,labelX='Azimuth',
2262            labelY='MRD',newPlot=True,Title='Ring Intensities',
2263            names=Names,lines=True)
2264       
2265    def OnSaveStrRing(event):
2266        Controls = G2frame.GPXtree.GetItemPyData(
2267            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2268        RingInt = G2img.IntStrSta(G2frame.ImageZ,data,Controls)
2269        Names = ['d=%.3f'%(ring['Dcalc']) for ring in data['d-zero']]
2270        pth = G2G.GetExportPath(G2frame)
2271        dlg = wx.FileDialog(G2frame, 'Choose strain ring intensity file', pth, '', 
2272            'ring intensity file (*.txt)|*.txt',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
2273        try:
2274            if dlg.ShowModal() == wx.ID_OK:
2275                filename = dlg.GetPath()
2276                File = open(filename,'w')
2277                for i,name in enumerate(Names):
2278                    File.write('%s%s\n'%(' Ring intensity for ',name))
2279                    File.write('%12s %12s\n'%('Azimuth','RMD'))
2280                    for item in RingInt[i].T:
2281                        File.write(' %12.3f %12.3f\n'%(item[0],item[1]))
2282                    File.write('\n')
2283                File.close()
2284        finally:
2285            dlg.Destroy()
2286               
2287               
2288    def OnFitStrSta(event):
2289        Controls = G2frame.GPXtree.GetItemPyData(
2290            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2291        G2img.FitStrSta(G2frame.ImageZ,data,Controls)
2292        print ('Strain fitting finished')
2293        UpdateStressStrain(G2frame,data)
2294        G2plt.PlotExposedImage(G2frame,event=event)
2295        G2plt.PlotStrain(G2frame,data,newPlot=True)
2296       
2297    def OnFitAllStrSta(event):
2298        choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
2299        od = {'label_1':'Copy to next','value_1':False,'label_2':'Reverse order','value_2':False}
2300        dlg = G2G.G2MultiChoiceDialog(G2frame,'Stress/Strain fitting','Select images to fit:',choices,extraOpts=od)
2301        names = []
2302        if dlg.ShowModal() == wx.ID_OK:
2303            for sel in dlg.GetSelections():
2304                names.append(choices[sel])
2305            Id =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential strain fit results')
2306            if Id:
2307                SeqResult = G2frame.GPXtree.GetItemPyData(Id)
2308            else:
2309                SeqResult = {}
2310                Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential strain fit results')
2311            SeqResult.update({'SeqPseudoVars':{},'SeqParFitEqList':[]})
2312        else:
2313            dlg.Destroy()
2314            return
2315        dlg.Destroy()
2316        if not names:
2317            return
2318        dlg = wx.ProgressDialog('Sequential IMG Strain fit','Data set name = '+names[0],len(names), 
2319            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT)         
2320        wx.BeginBusyCursor()
2321        goodnames = []
2322        if od['value_2']:
2323            names.reverse()
2324        try:
2325            varyList = []
2326            variables = []
2327            for i,name in enumerate(names):
2328                print (' Sequential strain fit for '+name)
2329                dlg.Raise()
2330                GoOn = dlg.Update(i,newmsg='Data set name = '+name)[0]
2331                if not GoOn:
2332                    break
2333                sId =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
2334                G2frame.Image = sId
2335                Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,sId, 'Image Controls'))
2336                StaCtrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,sId, 'Stress/Strain'))
2337                if not len(StaCtrls['d-zero']):
2338                    continue
2339                goodnames.append(name)
2340                Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(sId)
2341                image = GetImageZ(G2frame,Controls)
2342                sig = []
2343                if i and od['value_1']:
2344                    for j,ring in enumerate(StaCtrls['d-zero']):
2345                        ring['Emat'] = copy.copy(variables[4*j:4*j+3])
2346                varyList = []
2347                variables = []
2348                #get results from previous & put in StaCtrls
2349                G2img.FitStrSta(image,StaCtrls,Controls)
2350                G2plt.PlotStrain(G2frame,StaCtrls,newPlot=True)
2351                parmDict = {'Sample load':StaCtrls['Sample load'],}
2352                varyNames = ['e11','e12','e22']
2353                Nvar = 5*len(StaCtrls['d-zero'])
2354                coVar = np.zeros((Nvar,Nvar))
2355                for j,item in enumerate(StaCtrls['d-zero']):
2356                    variables += item['Emat']
2357                    sig += item['Esig']
2358                    varylist = ['%d;%s'%(j,Name) for Name in varyNames]
2359                    varyList += varylist
2360                    parmDict.update(dict(zip(varylist,item['Emat'])))
2361                    parmDict['%d;Dcalc'%(j)] = item['Dcalc']
2362                    variables.append(1.e6*(item['Dcalc']/item['Dset']-1.))
2363                    varyList.append('%d;h-mstrain'%(j))
2364                    sig.append(0)
2365                    parmDict['%d;Ivar'%(j)] = item['Ivar']
2366                    variables.append(item['Ivar'])
2367                    varyList.append('%d;Ivar'%(j))
2368                    sig.append(0)
2369                    j4 = j*5
2370                    coVar[j4:j4+3,j4:j4+3] = item['covMat']
2371                SeqResult[name] = {'variables':variables,'varyList':varyList,'sig':sig,'Rvals':[],
2372                    'covMatrix':coVar,'title':name,'parmDict':parmDict}
2373            else:
2374                SeqResult['histNames'] = goodnames
2375                dlg.Destroy()
2376                print (' ***** Sequential strain refinement successful *****')
2377        finally:
2378            wx.EndBusyCursor()   
2379        SeqResult['histNames'] = choices
2380        G2frame.GPXtree.SetItemPyData(Id,SeqResult)
2381        G2frame.GPXtree.SelectItem(Id)
2382        print ('All images fitted')
2383       
2384    def SamSizer():
2385       
2386        def OnStrainType(event):
2387            data['Type'] = strType.GetValue()
2388       
2389        samSizer = wx.FlexGridSizer(0,4,0,5)
2390        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Strain type: '),0,WACV)
2391        strType = wx.ComboBox(G2frame.dataWindow,value=data['Type'],choices=['True','Conventional'],
2392            style=wx.CB_READONLY|wx.CB_DROPDOWN)
2393        strType.SetValue(data['Type'])
2394        strType.Bind(wx.EVT_COMBOBOX, OnStrainType)
2395        samSizer.Add(strType,0,WACV)
2396        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Sample phi: '),0,WACV)
2397        samPhi = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Sample phi',nDig=(10,3),typeHint=float,min=-360.,max=360.)
2398        samSizer.Add(samPhi,0,WACV)
2399        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Sample delta-z(mm): '),0,WACV)
2400        samZ = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Sample z',nDig=(10,3),typeHint=float)
2401        samSizer.Add(samZ,0,WACV)
2402        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Sample load(MPa): '),0,WACV)
2403        samLoad = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Sample load',
2404                nDig=[8,3],typeHint=float,)
2405        samSizer.Add(samLoad,0,WACV)
2406
2407        return samSizer
2408       
2409    def DzeroSizer():
2410               
2411        def OnStrainChange(event):
2412#            print (event)
2413            r,c = event.GetRow(),event.GetCol()
2414            if c == 0:
2415                data['d-zero'][r]['Dset'] = min(max(float(StrainGrid.GetCellValue(r,c)),0.25),20.)
2416            elif c == 1:
2417                data['d-zero'][r]['fixDset'] = bool(StrainGrid.GetCellValue(r,c))
2418            elif c == 3:
2419                data['d-zero'][r]['cutoff'] = min(max(float(StrainGrid.GetCellValue(r,c)),0.5),20.)
2420            elif c == 4:
2421                data['d-zero'][r]['pixLimit'] = int(StrainGrid.GetCellValue(r,c))
2422            elif c == 8:
2423                del data['d-zero'][r]
2424                StrainTable.DeleteRow(r)
2425                wx.CallAfter(UpdateStressStrain,G2frame,data)
2426            G2plt.PlotExposedImage(G2frame,event=event)
2427            G2plt.PlotStrain(G2frame,data,newPlot=True)
2428           
2429        def OnSetCol(event):
2430            c = event.GetCol()
2431            if c == 1:
2432                StrainGrid.ClearSelection()
2433                StrainGrid.SelectCol(c,True)
2434                choice = ['Y - set all','N - set none',]
2435                dlg = wx.SingleChoiceDialog(G2frame,'Select option for '+StrainGrid.GetColLabelValue(c-1),
2436                    'Strain controls',choice)
2437                dlg.CenterOnParent()
2438                if dlg.ShowModal() == wx.ID_OK:
2439                    sel = dlg.GetSelection()
2440                    if sel == 0:
2441                        for row in range(StrainGrid.GetNumberRows()): data['d-zero'][row]['fixDset']=True
2442                    else:
2443                        for row in range(StrainGrid.GetNumberRows()): data['d-zero'][row]['fixDset']=False
2444                wx.CallAfter(UpdateStressStrain,G2frame,data)
2445           
2446        colTypes = [wg.GRID_VALUE_FLOAT+':10,5',wg.GRID_VALUE_BOOL,wg.GRID_VALUE_FLOAT+':10,5',wg.GRID_VALUE_FLOAT+':10,1',
2447            wg.GRID_VALUE_CHOICE+':1,2,5,10,15,20',]+3*[wg.GRID_VALUE_FLOAT+':10,2',]+[wg.GRID_VALUE_BOOL,]+2*[wg.GRID_VALUE_FLOAT+':10,2',]
2448        colIds = ['d-zero','Poisson\n mean?','d-zero ave','I/Ib','nPix','e11','e12','e22','Delete?','h-mustrain','Ivar']
2449        rowIds = [str(i) for i in range(len(data['d-zero']))]
2450        table = [[item['Dset'],item.get('fixDset',False),item['Dcalc'],item['cutoff'],item['pixLimit'], 
2451            item['Emat'][0],item['Emat'][1],item['Emat'][2],False,1.e6*(item['Dcalc']/item['Dset']-1.),item['Ivar']] for item in data['d-zero']]
2452        StrainTable = G2G.Table(table,rowLabels=rowIds,colLabels=colIds,types=colTypes)
2453        StrainGrid = G2G.GSGrid(G2frame.dataWindow)
2454        StrainGrid.SetTable(StrainTable,True)
2455        StrainGrid.AutoSizeColumns(True)
2456        for r in range(len(data['d-zero'])):
2457            StrainGrid.SetCellStyle(r,2,VERY_LIGHT_GREY,True)
2458            StrainGrid.SetCellStyle(r,5,VERY_LIGHT_GREY,True)
2459            StrainGrid.SetCellStyle(r,6,VERY_LIGHT_GREY,True)
2460            StrainGrid.SetCellStyle(r,7,VERY_LIGHT_GREY,True)
2461            StrainGrid.SetCellStyle(r,9,VERY_LIGHT_GREY,True)
2462            StrainGrid.SetCellStyle(r,10,VERY_LIGHT_GREY,True)
2463        if 'phoenix' in wx.version():
2464            StrainGrid.Bind(wg.EVT_GRID_CELL_CHANGED, OnStrainChange)
2465        else:
2466            StrainGrid.Bind(wg.EVT_GRID_CELL_CHANGE, OnStrainChange)
2467        StrainGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnSetCol)
2468        return StrainGrid
2469# patches
2470    if 'Sample load' not in data:
2471        data['Sample load'] = 0.0
2472# end patches
2473   
2474    G2frame.dataWindow.ClearData()
2475    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.StrStaMenu)
2476    G2frame.Bind(wx.EVT_MENU, OnAppendDzero, id=G2G.wxID_APPENDDZERO)
2477    G2frame.Bind(wx.EVT_MENU, OnUpdateDzero, id=G2G.wxID_UPDATEDZERO)
2478    G2frame.Bind(wx.EVT_MENU, OnFitStrSta, id=G2G.wxID_STRSTAFIT)
2479    G2frame.Bind(wx.EVT_MENU, OnPlotStrSta, id=G2G.wxID_STRSTAPLOT) 
2480    G2frame.Bind(wx.EVT_MENU, OnSaveStrRing, id=G2G.wxID_STRRINGSAVE) 
2481    G2frame.Bind(wx.EVT_MENU, OnFitAllStrSta, id=G2G.wxID_STRSTAALLFIT)
2482    G2frame.Bind(wx.EVT_MENU, OnCopyStrSta, id=G2G.wxID_STRSTACOPY)
2483    G2frame.Bind(wx.EVT_MENU, OnLoadStrSta, id=G2G.wxID_STRSTALOAD)
2484    G2frame.Bind(wx.EVT_MENU, OnSaveStrSta, id=G2G.wxID_STRSTASAVE)
2485    G2frame.Bind(wx.EVT_MENU, OnStrStaSample, id=G2G.wxID_STRSTSAMPLE)       
2486    if G2frame.StrainKey == 'a':    #probably doesn't happen
2487        G2frame.GetStatusBar().SetStatusText('Add strain ring active - LB pick d-zero value',1)
2488    else:
2489        G2frame.GetStatusBar().SetStatusText("To add strain data: On 2D Powder Image, key a:add ring",1)
2490       
2491    mainSizer = G2frame.dataWindow.GetSizer()
2492    mainSizer.Add((5,10),0)
2493    mainSizer.Add(SamSizer())
2494    mainSizer.Add((5,10),0)
2495    mainSizer.Add(DzeroSizer())
2496    G2frame.dataWindow.SetDataSize()
2497   
2498###########################################################################
2499# Autointegration
2500###########################################################################
2501def ReadMask(filename):
2502    'Read a mask (.immask) file'
2503    File = open(filename,'r')
2504    save = {}
2505    S = File.readline()
2506    while S:
2507        if S[0] == '#':
2508            S = File.readline()
2509            continue
2510        [key,val] = S.strip().split(':',1)
2511        if key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
2512            save[key] = eval(val)
2513        S = File.readline()
2514    File.close()
2515    CleanupMasks(save)
2516    return save
2517
2518def ReadControls(filename):
2519    'read an image controls (.imctrl) file'
2520    cntlList = ['wavelength','distance','tilt','invert_x','invert_y','type',
2521            'fullIntegrate','outChannels','outAzimuths','LRazimuth','IOtth','azmthOff','DetDepth',
2522            'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg',
2523            'PolaVal','SampleAbs','dark image','background image']
2524    File = open(filename,'r')
2525    save = {}
2526    S = File.readline()
2527    while S:
2528        if S[0] == '#':
2529            S = File.readline()
2530            continue
2531        [key,val] = S.strip().split(':',1)
2532        if key in ['type','calibrant','binType','SampleShape',]:    #strings
2533            save[key] = val
2534        elif key in ['rotation']:
2535            save[key] = float(val)
2536        elif key in ['center',]:
2537            if ',' in val:
2538                save[key] = eval(val)
2539            else:
2540                vals = val.strip('[] ').split()
2541                save[key] = [float(vals[0]),float(vals[1])] 
2542        elif key in cntlList:
2543            save[key] = eval(val)
2544        S = File.readline()
2545    File.close()
2546    return save
2547
2548def Read_imctrl(imctrl_file):
2549    '''Read an image control file and record control parms into a dict, with some simple
2550    type conversions
2551    '''
2552    save = {'filename':imctrl_file}
2553    immask_file = os.path.splitext(imctrl_file)[0]+'.immask'
2554    if os.path.exists(immask_file):
2555        save['maskfile'] = immask_file
2556    else:
2557        save['maskfile'] = '(none)'
2558    cntlList = ['wavelength','distance','tilt','invert_x','invert_y','type',
2559                        'fullIntegrate','outChannels','outAzimuths','LRazimuth','IOtth','azmthOff','DetDepth',
2560                        'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg',
2561                        'PolaVal','SampleAbs','dark image','background image','setdist']
2562    File = open(imctrl_file,'r')
2563    fullIntegrate = False
2564    try:
2565        S = File.readline()
2566        while S:
2567            if S[0] == '#':
2568                S = File.readline()
2569                continue
2570            [key,val] = S.strip().split(':',1)
2571            if val.find(':') != -1:
2572                #print 'rejecting ',key,val
2573                S = File.readline()
2574                continue
2575            if key in ['type','calibrant','binType','SampleShape',]:    #strings
2576                save[key] = val
2577            elif key == 'rotation':
2578                save[key] = float(val)
2579            elif key == 'fullIntegrate':
2580                fullIntegrate = eval(val)
2581            elif key == 'LRazimuth':
2582                vals = eval(val)
2583                save['LRazimuth_min'] = float(vals[0])
2584                save['LRazimuth_max'] = float(vals[1])
2585            elif key == 'IOtth':
2586                save['IOtth_min'],save['IOtth_max'] = eval(val)[0:2]
2587            elif key == 'center':
2588                if ',' in val:
2589                    vals = eval(val)
2590                else:
2591                    vals = val.strip('[] ').split()
2592                    vals = [float(vals[0]),float(vals[1])] 
2593                save['center_x'],save['center_y'] = vals[0:2]
2594            elif key in cntlList:
2595                save[key] = eval(val)
2596            S = File.readline()
2597    finally:
2598        File.close()
2599        if fullIntegrate: save['LRazimuth_min'],save['LRazimuth_max'] = 0.,360.
2600    return save
2601   
2602class AutoIntFrame(wx.Frame):
2603    '''Creates a wx.Frame window for the Image AutoIntegration.
2604    The intent is that this will be used as a non-modal dialog window.
2605   
2606    Implements a Start button that morphs into a pause and resume button.
2607    This button starts a processing loop that is repeated every
2608    :meth:`PollTime` seconds.
2609
2610    :param wx.Frame G2frame: main GSAS-II frame
2611    :param float PollTime: frequency in seconds to repeat calling the
2612      processing loop. (Default is 30.0 seconds.)
2613    '''
2614    def __init__(self,G2frame,PollTime=30.0):
2615        def OnStart(event):
2616            '''Called when the start button is pressed. Changes button label
2617            to Pause. When Pause is pressed the label changes to Resume.
2618            When either Start or Resume is pressed, the processing loop
2619            is started. When Pause is pressed, the loop is stopped.
2620            '''
2621            # check inputs for errors before starting
2622            #err = ''
2623            #if not any([self.params[fmt] for fmt in self.fmtlist]):
2624            #    err += '\nPlease select at least one output format\n'
2625            #if err:
2626            #    G2G.G2MessageBox(self,err)
2627            #    return
2628            self.Pause = False
2629            # change button label
2630            if self.btnstart.GetLabel() != 'Pause':
2631                self.btnstart.SetLabel('Pause')
2632                self.Status.SetStatusText('Press Pause to delay integration or Reset to prepare to reintegrate all images')
2633                if self.timer.IsRunning(): self.timer.Stop()
2634                self.PreventReEntryTimer = False
2635                if self.StartLoop():
2636                    G2G.G2MessageBox(self,'Error in setting up integration. See console')
2637                    return
2638                self.OnTimerLoop(None) # run once immediately
2639                if not self.Pause:
2640                    # no pause, so start timer to check for new files
2641                    self.timer.Start(int(1000*PollTime),oneShot=False)
2642                    return
2643            # we will get to this point if Paused
2644            self.OnPause()
2645           
2646        def OnReset(event):
2647            '''Called when Reset button is pressed. This stops the
2648            processing loop and resets the list of integrated files so
2649            all images can be reintegrated.
2650            '''
2651            self.btnstart.SetLabel('Restart')
2652            self.Status.SetStatusText('Press Restart to reload and re-integrate images matching filter')
2653            if self.timer.IsRunning(): self.timer.Stop()
2654            self.Reset = True
2655            self.Pause = True
2656           
2657        def OnQuit(event):
2658            '''Stop the processing loop and close the Frame
2659            '''
2660            if self.timer.IsRunning(): self.timer.Stop() # make sure we stop first
2661            wx.CallAfter(self.Destroy)
2662           
2663        def OnBrowse(event):
2664            '''Responds when the Browse button is pressed to load a file.
2665            The routine determines which button was pressed and gets the
2666            appropriate file type and loads it into the appropriate place
2667            in the dict.
2668            '''
2669            if btn3 == event.GetEventObject():
2670                dlg = wx.DirDialog(
2671                    self, 'Select directory for output files',
2672                    self.params['outdir'],wx.DD_DEFAULT_STYLE)
2673                dlg.CenterOnParent()
2674                try:
2675                    if dlg.ShowModal() == wx.ID_OK:
2676                        self.params['outdir'] = dlg.GetPath()
2677                        fInp3.SetValue(self.params['outdir'])
2678                finally:
2679                    dlg.Destroy()
2680                return
2681            elif btn4 == event.GetEventObject():
2682                msg = ''
2683                pth = G2G.GetExportPath(G2frame)
2684                dlg = wx.FileDialog(
2685                    self, 'Select a PDF parameter file',
2686                    pth, self.params['pdfprm'], 
2687                    "PDF controls file (*.pdfprm)|*.pdfprm",
2688                    wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
2689                dlg.CenterOnParent()
2690                try:
2691                    if dlg.ShowModal() == wx.ID_OK:
2692                        self.params['pdfprm'] = dlg.GetPath()
2693                        fInp4.SetValue(self.params['pdfprm'])
2694                        scanPDFprm()
2695                        msg = self.checkPDFprm(True)
2696                finally:
2697                    dlg.Destroy()
2698                if 'Error' in msg:
2699                    print(msg)
2700                    lbl = 'PDFPRM error'
2701                else:
2702                    msg = 'Information from file {}\n\n{}'.format(self.params['pdfprm'],msg)
2703                    lbl = 'PDFPRM information'
2704                G2G.G2MessageBox(self,msg,lbl)
2705                return
2706               
2707        def OnRadioSelect(event):
2708            '''Respond to a radiobutton selection and when in table
2709            mode, get distance-dependent parameters from user.
2710            '''
2711            self.Evaluator = None
2712            if self.useTable.GetValue():
2713                dlg = None
2714                try:
2715                    dlg = IntegParmTable(self) # create the dialog
2716                    dlg.CenterOnParent()
2717                    if dlg.ShowModal() == wx.ID_OK:
2718                        self.ImgTblParms = dlg.parms
2719                        self.IMfileList = dlg.IMfileList
2720                        self.Evaluator = DefineEvaluator(dlg)
2721                        self.params['Mode'] = 'table'
2722                        self.editTable.Enable(True)
2723                    else:
2724                        self.useActive.SetValue(True)
2725                finally:
2726                    if dlg: dlg.Destroy()
2727            elif self.useActive.GetValue():
2728                self.params['Mode'] = 'active'
2729                self.imageBase = G2frame.Image
2730                self.useActive.SetLabel("Active Image: "+
2731                        G2frame.GPXtree.GetItemText(self.imageBase))
2732                self.editTable.Enable(False)
2733            else:
2734                print('unexpected mode in OnRadioSelect')
2735
2736        def OnEditTable(event):
2737            '''Called to edit the distance-dependent parameter look-up table.
2738            Should be called only when table is defined and active.
2739            '''
2740            dlg = None
2741            try:
2742                dlg = IntegParmTable(self,self.ImgTblParms,self.IMfileList)
2743                dlg.CenterOnParent()
2744                if dlg.ShowModal() == wx.ID_OK:
2745                    self.ImgTblParms = dlg.parms
2746                    self.IMfileList = dlg.IMfileList
2747                    self.Evaluator = DefineEvaluator(dlg)
2748                    self.params['Mode'] = 'table'
2749                    self.editTable.Enable(True)
2750                else:
2751                    self.useActive.SetValue(True)
2752                    self.params['Mode'] = 'active'
2753                    self.imageBase = G2frame.Image
2754                    self.useActive.SetLabel("Active Image: "+
2755                            G2frame.GPXtree.GetItemText(self.imageBase))
2756                    self.editTable.Enable(False)
2757            finally:
2758                if dlg: dlg.Destroy()
2759               
2760        def showPDFctrls(event):
2761            '''Called to show or hide AutoPDF widgets. Note that fInp4 must be included in the
2762            sizer layout with .Show(True) before .Show(False) will work properly.
2763            '''
2764            fInp4.Enable(self.params['ComputePDF'])
2765            fInp4.Show(self.params['ComputePDF'])
2766            fInp4a.Enable(self.params['ComputePDF'])
2767            btn4.Enable(self.params['ComputePDF'])
2768            cOpt.Enable(self.params['ComputePDF'])
2769            if self.params['ComputePDF']:
2770                lbl4.SetForegroundColour("black")
2771                lbl4a.SetForegroundColour("black")
2772            else:
2773                lbl4.SetForegroundColour("gray")
2774                lbl4a.SetForegroundColour("gray")
2775                                   
2776        def scanPDFprm(**kw):
2777            fInp4.invalid = not os.path.exists(fInp4.GetValue())
2778            fInp4._IndicateValidity()
2779           
2780        def OnAutoScale(event):
2781            self.AutoScale = autoscale.GetValue()
2782           
2783        def OnAutoScaleName(event):
2784            self.AutoScaleName = scalename.GetValue()
2785            self.Scale[0] = self.AutoScales[self.AutoScaleName]
2786            scaleval.SetValue(self.Scale[0])
2787               
2788        ##################################################
2789        # beginning of __init__ processing
2790        ##################################################
2791        self.G2frame = G2frame
2792        self.ImgTblParms = None
2793        self.IMfileList = None
2794        self.Evaluator = None
2795        self.params = {}
2796        self.Reset = False
2797        self.Pause = False
2798        self.PreventReEntryShowMatch = False
2799        self.PreventReEntryTimer = False
2800        self.params['IMGfile'] = ''
2801        self.params['MaskFile'] = ''
2802        self.params['IgnoreMask'] = True
2803        self.fmtlist = G2IO.ExportPowderList(G2frame)
2804        self.timer = wx.Timer()
2805        self.timer.Bind(wx.EVT_TIMER,self.OnTimerLoop)
2806        self.imageBase = G2frame.Image
2807        self.params['ComputePDF'] = False
2808        self.params['pdfDmax'] = 0.0
2809        self.params['pdfprm'] = ''
2810        self.params['optPDF'] = True
2811        self.pdfControls = {}
2812        self.AutoScale = False
2813        self.Scale = [1.0,]
2814
2815        G2frame.GPXtree.GetSelection()
2816        size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(self.imageBase) 
2817        self.params['readdir'],fileroot = os.path.split(imagefile)
2818        self.params['filter'] = '*'+os.path.splitext(fileroot)[1]
2819        self.params['outdir'] = os.path.abspath(self.params['readdir'])
2820        Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
2821            G2frame,self.imageBase, 'Comments'))
2822        DefaultAutoScaleNames = GSASIIpath.GetConfigValue('Autoscale_ParmNames')
2823        self.AutoScaleName = GSASIIpath.GetConfigValue('DefaultAutoScale')
2824        self.AutoScales = {}
2825        if DefaultAutoScaleNames is not None:
2826            for comment in Comments:
2827                if '=' in comment:
2828                    name,val = comment.split('=',1) 
2829                    if name in DefaultAutoScaleNames:
2830                        try:
2831                            self.AutoScales[name] = float(val)
2832                            if name == self.AutoScaleName:
2833                                self.Scale[0] = float(val)
2834                        except ValueError:
2835                            continue
2836        wx.Frame.__init__(self, G2frame, title='Automatic Integration',
2837                          style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
2838        self.Status = self.CreateStatusBar()
2839        self.Status.SetStatusText('Press Start to load and integrate images matching filter')
2840        mnpnl = wx.Panel(self)
2841        mnsizer = wx.BoxSizer(wx.VERTICAL)
2842        # box for integration controls & masks input
2843        lbl = wx.StaticBox(mnpnl, wx.ID_ANY, "Integration Control")
2844        lblsizr = wx.StaticBoxSizer(lbl, wx.VERTICAL)
2845        lblsizr.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Use integration parameters from:'))
2846        self.useActive = wx.RadioButton(mnpnl, wx.ID_ANY, 
2847                            style = wx.RB_GROUP)
2848        self.useActive.Bind(wx.EVT_RADIOBUTTON, OnRadioSelect)
2849        self.useActive.SetLabel("Active Image: "+
2850                                G2frame.GPXtree.GetItemText(self.imageBase))
2851        lblsizr.Add(self.useActive,0,wx.EXPAND,1)
2852        self.useActive.SetValue(True)
2853        minisizer = wx.BoxSizer(wx.HORIZONTAL)
2854        self.useTable = wx.RadioButton(mnpnl, wx.ID_ANY, "From distance look-up table")
2855        minisizer.Add(self.useTable,0,wx.ALIGN_LEFT|wx.ALL,1)
2856        self.useTable.Bind(wx.EVT_RADIOBUTTON, OnRadioSelect)
2857        self.editTable = wx.Button(mnpnl,  wx.ID_ANY, "Edit table")
2858        minisizer.Add(self.editTable,0,wx.ALIGN_LEFT,10)
2859        self.editTable.Enable(False)
2860        self.editTable.Bind(wx.EVT_BUTTON, OnEditTable)
2861        # bind button and deactivate be default
2862        lblsizr.Add(minisizer)
2863        mnsizer.Add(lblsizr,0,wx.EXPAND,0)
2864
2865        # file filter stuff
2866        sizer = wx.BoxSizer(wx.HORIZONTAL)
2867        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Read images from '))
2868        self.readDir = G2G.ValidatedTxtCtrl(mnpnl,self.params,'readdir',
2869                            OnLeave=self.ShowMatchingFiles,size=(200,-1))
2870        sizer.Add(self.readDir,1,wx.EXPAND,1)
2871        btn3 = wx.Button(mnpnl, wx.ID_ANY, "Browse")
2872        btn3.Bind(wx.EVT_BUTTON, self.SetSourceDir)
2873        sizer.Add(btn3,0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
2874        mnsizer.Add(sizer,0,wx.EXPAND,0)
2875        # not yet implemented
2876        sizer = wx.BoxSizer(wx.HORIZONTAL)
2877        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Keep read images in tree '))
2878        self.params['keepReadImage'] = True
2879        keepImage = G2G.G2CheckBox(mnpnl,'',self.params,'keepReadImage')
2880        sizer.Add(keepImage)
2881        keepImage.Enable(False)
2882        sizer.Add((-1,-1),1,wx.EXPAND,1)
2883        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'  Image filter'))
2884        flterInp = G2G.ValidatedTxtCtrl(mnpnl,self.params,'filter',
2885                                        OnLeave=self.ShowMatchingFiles)
2886        sizer.Add(flterInp)
2887        mnsizer.Add(sizer,0,wx.EXPAND,0)
2888       
2889        self.ListBox = wx.ListBox(mnpnl,size=(-1,100))
2890        mnsizer.Add(self.ListBox,1,wx.EXPAND,1)
2891        self.ShowMatchingFiles(self.params['filter'])
2892
2893        # box for output selections
2894        lbl = wx.StaticBox(mnpnl, wx.ID_ANY, "Output settings")
2895        lblsizr = wx.StaticBoxSizer(lbl, wx.VERTICAL)
2896        sizer = wx.BoxSizer(wx.HORIZONTAL)
2897        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Write to: '),0,wx.ALIGN_CENTER_VERTICAL)
2898        fInp3 = G2G.ValidatedTxtCtrl(mnpnl,self.params,'outdir',notBlank=False,size=(300,-1))
2899        sizer.Add(fInp3,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
2900        btn3 = wx.Button(mnpnl,  wx.ID_ANY, "Browse")
2901        btn3.Bind(wx.EVT_BUTTON, OnBrowse)
2902        sizer.Add(btn3,0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
2903        lblsizr.Add(sizer,0,wx.EXPAND)
2904        sizer = wx.BoxSizer(wx.HORIZONTAL)
2905        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Select format(s):'))
2906        for dfmt in self.fmtlist:
2907            sizer.Add((6,2)) # add a bit of extra space
2908            fmt = dfmt[1:]
2909            self.params[fmt] = False
2910            btn = G2G.G2CheckBox(mnpnl,dfmt,self.params,fmt)
2911            sizer.Add(btn)
2912        lblsizr.Add(sizer)
2913        sizer = wx.BoxSizer(wx.HORIZONTAL)
2914        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Separate dir for each format: '))
2915        self.params['SeparateDir'] = False
2916        sizer.Add(G2G.G2CheckBox(mnpnl,'',self.params,'SeparateDir'))
2917        lblsizr.Add(sizer)
2918        if self.AutoScales:
2919            sizer = wx.BoxSizer(wx.HORIZONTAL)
2920            autoscale = wx.CheckBox(mnpnl,label='Do autoscaling with:')
2921            autoscale.Bind(wx.EVT_CHECKBOX,OnAutoScale)
2922            sizer.Add(autoscale,0,WACV)
2923            scalename = wx.ComboBox(mnpnl,value=self.AutoScaleName,choices=list(self.AutoScales.keys()),
2924                style=wx.CB_READONLY|wx.CB_DROPDOWN)
2925            scalename.Bind(wx.EVT_COMBOBOX,OnAutoScaleName)
2926            sizer.Add(scalename,0,WACV)
2927            sizer.Add(wx.StaticText(mnpnl,label=' to '),0,WACV)
2928            scaleval = G2G.ValidatedTxtCtrl(mnpnl,self.Scale,0,nDig=(10,2),min=1.)
2929            sizer.Add(scaleval,0,WACV)
2930            lblsizr.Add(sizer,0)
2931        #ToDO: Autonormalize, parm name?, scaling value?
2932        sizer = wx.BoxSizer(wx.HORIZONTAL)
2933        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Autocompute PDF:'),0,wx.ALIGN_CENTER_VERTICAL)
2934        sizer.Add(G2G.G2CheckBox(mnpnl,'',self.params,'ComputePDF',OnChange=showPDFctrls))
2935        lbl4a = wx.StaticText(mnpnl, wx.ID_ANY,'Max detector distance: ')
2936        sizer.Add(lbl4a,0,wx.ALIGN_CENTER_VERTICAL)
2937        fInp4a = G2G.ValidatedTxtCtrl(mnpnl,self.params,'pdfDmax',min=0.0)
2938        sizer.Add(fInp4a,0,wx.ALIGN_CENTER_VERTICAL)
2939        cOpt = G2G.G2CheckBox(mnpnl,'Optimize',self.params,'optPDF')
2940        sizer.Add(cOpt)
2941        lblsizr.Add(sizer,0)
2942        sizer = wx.BoxSizer(wx.HORIZONTAL)
2943        lbl4 = wx.StaticText(mnpnl, wx.ID_ANY,'PDF control: ')
2944        sizer.Add(lbl4,0,wx.ALIGN_CENTER_VERTICAL)
2945        fInp4 = G2G.ValidatedTxtCtrl(mnpnl,self.params,'pdfprm',notBlank=True,size=(300,-1),
2946                                     OnLeave=scanPDFprm)
2947        sizer.Add(fInp4,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
2948        btn4 = wx.Button(mnpnl,  wx.ID_ANY, "Browse")
2949        btn4.Bind(wx.EVT_BUTTON, OnBrowse)
2950        sizer.Add(btn4,0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
2951        lblsizr.Add(sizer,0,wx.EXPAND)
2952        mnsizer.Add(lblsizr,0,wx.ALIGN_CENTER|wx.EXPAND,1)
2953        # buttons on bottom
2954        mnsizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'AutoIntegration controls'),0,wx.TOP,5)
2955        sizer = wx.BoxSizer(wx.HORIZONTAL)
2956        sizer.Add((20,-1))
2957        self.btnstart = wx.Button(mnpnl,  wx.ID_ANY, "Start")
2958        self.btnstart.Bind(wx.EVT_BUTTON, OnStart)
2959        sizer.Add(self.btnstart)
2960        self.btnreset = wx.Button(mnpnl,  wx.ID_ANY, "Reset")
2961        self.btnreset.Bind(wx.EVT_BUTTON, OnReset)
2962        sizer.Add(self.btnreset)
2963        sizer.Add((20,-1),wx.EXPAND,1)
2964        self.btnclose = wx.Button(mnpnl,  wx.ID_ANY, "Close")
2965        self.btnclose.Bind(wx.EVT_BUTTON, OnQuit)
2966        sizer.Add(self.btnclose)
2967        sizer.Add((20,-1))
2968        mnsizer.Add(sizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP,5)
2969        # finish up window
2970        mnpnl.SetSizer(mnsizer)
2971        OnRadioSelect(None) # disable widgets
2972        mnsizer.Fit(self)
2973        self.CenterOnParent()
2974        self.Show()
2975        showPDFctrls(None)
2976
2977    def checkPDFprm(self,ShowContents=False):
2978        '''Read in the PDF (.pdfprm) parameter file and check for problems.
2979        If ShowContents is True, a formatted text version of some of the file
2980        contents is returned. If errors are found, the return string will contain
2981        the string "Error:" at least once.
2982        '''
2983        self.pdfControls = {}
2984        msg = ''
2985        File = None
2986        try:
2987            File = open(self.params['pdfprm'],'r')
2988            S = File.readline()
2989            while S:
2990                if '#' in S:
2991                    S = File.readline()
2992                    continue
2993                key,val = S.split(':',1)
2994                try:
2995                    self.pdfControls[key] = eval(val)
2996                except:
2997                    self.pdfControls[key] = val
2998                S = File.readline()
2999        except Exception as err:
3000            msg += 'PDF Processing Error: error with open or read of {}'.format(self.params['pdfprm'])
3001            if GSASIIpath.GetConfigValue('debug'):
3002                print('DBG_'+msg)
3003                print('DBG_'+err)
3004            self.pdfControls = {}
3005            return msg
3006        finally:
3007            if File: File.close()
3008        formula = ''
3009        for el in self.pdfControls['ElList']:
3010            if self.pdfControls['ElList'][el]['FormulaNo'] <= 0: continue
3011            if formula: formula += ' '
3012            formula += '{}({:.1f})'.format(el,self.pdfControls['ElList'][el]['FormulaNo'])
3013        if not formula:
3014            msg += 'Error: no chemical formula in file'
3015        for key in ['Sample Bkg.','Container','Container Bkg.']:
3016            if key not in self.pdfControls:
3017                if msg: msg += '\n'
3018                msg += 'Error: missing key in self.pdfControls: '+key
3019                continue
3020        if msg or not ShowContents: return msg  # stop on error
3021        msg += 'Default formula: '+formula+'\n'
3022        for key in ['Sample Bkg.','Container','Container Bkg.']:
3023            name = self.pdfControls[key]['Name']
3024            mult = self.pdfControls[key].get('Mult',0.0)
3025            if not name: continue
3026            msg += '\n{}: {:.2f} * "{}"'.format(key,mult,name)
3027            if not G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,name):
3028                msg += ' *** missing ***'
3029        return msg
3030   
3031    def SetSourceDir(self,event):
3032        '''Use a dialog to get a directory for image files
3033        '''
3034        dlg = wx.DirDialog(self, 'Select directory for image files',
3035                        self.params['readdir'],wx.DD_DEFAULT_STYLE)
3036        dlg.CenterOnParent()
3037        try:
3038            if dlg.ShowModal() == wx.ID_OK:
3039                self.params['readdir'] = dlg.GetPath()
3040            self.readDir.SetValue(self.params['readdir'])
3041            self.ShowMatchingFiles(None)
3042        finally:
3043            dlg.Destroy()
3044        return
3045       
3046    def ShowMatchingFiles(self,value,invalid=False,**kwargs):
3047        '''Find and show images in the tree and the image files matching the image
3048        file directory (self.params['readdir']) and the image file filter
3049        (self.params['filter']) and add this information to the GUI list box
3050        '''
3051        G2frame = self.G2frame
3052        if invalid: return
3053        msg = ''
3054        if self.PreventReEntryShowMatch: return
3055        self.PreventReEntryShowMatch = True
3056        imageFileList = []
3057        for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']):
3058            imgId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
3059            size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(imgId)
3060            if imagefile not in imageFileList: imageFileList.append(imagefile)
3061            if img not in G2frame.IntegratedList:
3062                if msg: msg += '\n'
3063                msg += '  ' + img
3064        if msg: msg = "Loaded images to integrate:\n" + msg + "\n"
3065        msg1 = ""
3066        try:
3067            if os.path.exists(self.params['readdir']): 
3068                imageList = sorted(
3069                    glob.glob(os.path.join(self.params['readdir'],self.params['filter'])))
3070                if not imageList:
3071                    msg1 = 'Warning: No files match search string '+os.path.join(self.params['readdir'],self.params['filter'])
3072                else:
3073                    for fil in imageList:
3074                        if fil not in imageFileList: msg1 += '\n  '+fil
3075                    if msg1:
3076                        msg += 'Files to integrate from '+os.path.join(self.params['readdir'],self.params['filter'])+msg1
3077                    else:
3078                        msg += 'No files found to read in '+self.params['readdir']
3079            else:
3080                msg += 'Warning: does not exist: '+self.params['readdir']
3081        except IndexError:
3082            msg += 'Error searching for files named '+os.path.join(self.params['readdir'],self.params['filter'])
3083        self.ListBox.Clear()
3084        self.ListBox.AppendItems(msg.split('\n'))
3085        self.PreventReEntryShowMatch = False
3086        return
3087       
3088    def OnPause(self):
3089        '''Respond to Pause, changes text on button/Status line, if needed
3090        Stops timer
3091        self.Pause should already be True
3092        '''
3093        if self.timer.IsRunning(): self.timer.Stop()
3094        if self.btnstart.GetLabel() == 'Restart':
3095            return
3096        if self.btnstart.GetLabel() != 'Resume':
3097            print('\nPausing autointegration\n')
3098            self.btnstart.SetLabel('Resume')
3099            self.Status.SetStatusText(
3100                    'Press Resume to continue integration or Reset to prepare to reintegrate all images')
3101        self.Pause = True
3102           
3103    def IntegrateImage(self,img,useTA=None,useMask=None):
3104        '''Integrates a single image. Ids for created PWDR entries (more than one is possible)
3105        are placed in G2frame.IntgOutList
3106        '''
3107        G2frame = self.G2frame
3108        imgId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
3109        G2frame.Image = imgId
3110        G2frame.PickId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls')
3111        # do integration
3112        size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(imgId)
3113        if self.AutoScale:
3114            Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3115                G2frame,imgId, 'Comments'))
3116            for comment in Comments:
3117                if '=' in comment:
3118                    name,val = comment.split('=',1) 
3119                    if name == self.AutoScaleName:
3120                        val = float(val)
3121                        if val > 0.:
3122                            Scale = self.Scale[0]/val
3123                        break
3124        masks = G2frame.GPXtree.GetItemPyData(
3125            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Masks'))
3126        data = G2frame.GPXtree.GetItemPyData(G2frame.PickId)
3127        # simulate a Image Controls press, since that is where the
3128        # integration is hidden
3129        UpdateImageControls(G2frame,data,masks,useTA=useTA,useMask=useMask,IntegrateOnly=True)
3130        G2frame.IntegratedList.append(img) # note this as integrated
3131        # split name and control number
3132        try:
3133            s = re.split(r'(\d+)\Z',os.path.split(os.path.splitext(imagefile)[0])[1])
3134        except AttributeError: # not sure why, but sometimes imagefile is a list here (should not be)!
3135            s = re.split(r'(\d+)\Z',os.path.split(os.path.splitext(imagefile[0])[0])[1])
3136        namepre = s[0]
3137        if len(s) > 1:
3138            namenum = s[1]
3139        else:
3140            namenum = ''
3141        for Id in G2frame.IntgOutList: # loop over newly created PWDR entry(ies)
3142            # save the created PWDR tree names so that a reset can delete them
3143            G2frame.Image = Id
3144            treename = G2frame.GPXtree.GetItemText(Id)
3145            G2frame.AutointPWDRnames.append(treename)
3146            # write out the images in the selected formats
3147            Sdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Sample Parameters'))
3148            if self.AutoScale:
3149                print ('Rescale by %.4f'%(Scale))
3150                y,w = G2frame.GPXtree.GetItemPyData(Id)[1][1:3]
3151                y *= Scale
3152                w /= Scale**2
3153            # determine the name for the current file
3154            fileroot = namepre
3155            if len(G2frame.IntgOutList) > 1:
3156                fileroot += "_AZM"
3157                if 'Azimuth' in Sdata:
3158                    fileroot += str(int(10*Sdata['Azimuth']))
3159                fileroot += "_" 
3160            fileroot += namenum
3161            # loop over selected formats
3162            for dfmt in self.fmtlist:
3163                if not self.params[dfmt[1:]]: continue
3164                if self.params['SeparateDir']:
3165                    subdir = dfmt[1:]
3166                else:
3167                    subdir = ''
3168                fil = os.path.join(self.params['outdir'],subdir,fileroot)
3169                G2IO.ExportPowder(G2frame,treename,fil+'.x',dfmt) # dummy extension (.x) is replaced before write)
3170               
3171    def EnableButtons(self,flag):
3172        '''Relabels and enable/disables the buttons at window bottom when auto-integration is running
3173        '''
3174        # for unclear reasons disabling these buttons causes OnRadioSelect to be invoked
3175        # on windows
3176        if sys.platform != "win32":
3177            for item in (self.btnstart,self.btnreset,self.btnclose): item.Enable(flag)
3178        self.btnstart.SetLabel('Pause')
3179        wx.Yield()
3180               
3181    def ResetFromTable(self,dist):
3182        '''Sets integration parameters based on values from
3183        the lookup table
3184        '''
3185        #dist = self.controlsDict['distance']
3186        interpDict,imgctrl,immask = self.Evaluator(dist) # interpolated calibration values
3187        if GSASIIpath.GetConfigValue('debug'):
3188            print ('DBG_interpolated values: ',interpDict)
3189        self.ImageControls = ReadControls(imgctrl)
3190        self.ImageControls.update(interpDict)
3191        self.ImageControls['showLines'] = True
3192        self.ImageControls['ring'] = []
3193        self.ImageControls['rings'] = []
3194        self.ImageControls['ellipses'] = []
3195        self.ImageControls['setDefault'] = False
3196        for i in 'range','size','GonioAngles':
3197            if i in self.ImageControls:
3198                del self.ImageControls[i]
3199        # load copy of Image Masks
3200        if immask:
3201            self.ImageMasks = ReadMask(immask)
3202            if list(self.ImageMasks['Thresholds'][0]) == self.ImageMasks['Thresholds'][1]:     #avoid copy of unchanged thresholds
3203                del self.ImageMasks['Thresholds']
3204        else:
3205            self.ImageMasks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],
3206                'SpotMask':{'esdMul':3.,'spotMask':None},}
3207       
3208    def StartLoop(self):
3209        '''Prepare to start autointegration timer loop.
3210        Save current Image params for use in future integrations
3211        also label the window so users understand what is being used
3212        '''
3213        print('\nStarting new autointegration\n')
3214        G2frame = self.G2frame
3215        # show current IMG base
3216        if self.params['Mode'] != 'table':
3217            self.useActive.SetLabel("Active Image: "+
3218                                    G2frame.GPXtree.GetItemText(self.imageBase))
3219            # load copy of Image Controls from current image and clean up
3220            # items that should not be copied
3221            self.ImageControls = copy.deepcopy(
3222                G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3223                    G2frame,self.imageBase, 'Image Controls')))
3224            self.ImageControls['showLines'] = True
3225            self.ImageControls['ring'] = []
3226            self.ImageControls['rings'] = []
3227            self.ImageControls['ellipses'] = []
3228            self.ImageControls['setDefault'] = False
3229            del self.ImageControls['range']
3230            del self.ImageControls['size']
3231            del self.ImageControls['GonioAngles']
3232            # load copy of Image Masks, keep thresholds
3233            self.ImageMasks = copy.deepcopy(
3234                G2frame.GPXtree.GetItemPyData(
3235                    G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Masks')))
3236            self.Thresholds = self.ImageMasks['Thresholds'][:]
3237            if list(self.Thresholds[0]) == self.Thresholds[1]:     #avoid copy of unchanged thresholds
3238                del self.ImageMasks['Thresholds']   
3239        # make sure all output directories exist
3240        if self.params['SeparateDir']:
3241            for dfmt in self.fmtlist:
3242                if not self.params[dfmt[1:]]: continue
3243                dir = os.path.join(self.params['outdir'],dfmt[1:])
3244                if not os.path.exists(dir): os.makedirs(dir)
3245        else:
3246            if not os.path.exists(self.params['outdir']):
3247                os.makedirs(self.params['outdir'])
3248        if self.Reset: # special things to do after Reset has been pressed
3249            self.G2frame.IntegratedList = []
3250           
3251            if self.params['Mode'] != 'table': # reset controls and masks for all IMG items in tree to master
3252                for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']):
3253                    # update controls from master
3254                    controlsDict = G2frame.GPXtree.GetItemPyData(
3255                        G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Image Controls'))
3256                    controlsDict.update(self.ImageControls)
3257                    # update masks from master
3258                    ImageMasks = G2frame.GPXtree.GetItemPyData(
3259                        G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Masks'))
3260                    ImageMasks.update(self.ImageMasks)
3261            # delete all PWDR items created after last Start was pressed
3262            idlist = []
3263            item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
3264            while item:
3265                itemName = G2frame.GPXtree.GetItemText(item)
3266                if itemName in G2frame.AutointPWDRnames:
3267                    idlist.append(item)
3268                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
3269            for item in idlist:
3270                G2frame.GPXtree.Delete(item)
3271            wx.Yield()
3272            self.Reset = False
3273        G2frame.AutointPWDRnames = [] # list of created PWDR tree item names
3274        G2frame.AutointPDFnames = [] # list of created PWDR tree item names
3275        # check that AutoPDF input is OK, offer chance to use alternate PWDRs if referenced ones
3276        # are not present
3277        if self.params['ComputePDF']:
3278            msg = self.checkPDFprm()
3279            if 'Error:' in msg:
3280                print(msg)
3281                return True
3282            fileList = []
3283            Id, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
3284            while Id:
3285                name = G2frame.GPXtree.GetItemText(Id)
3286                if name.startswith('PWDR '): fileList.append(name)
3287                Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
3288            if not fileList:
3289                print(msg)
3290                print('No PWDR entries to select')
3291                return True
3292            for key in ['Sample Bkg.','Container','Container Bkg.']:
3293                name = self.pdfControls[key]['Name']
3294                if not name: continue
3295                if not G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name):
3296                    indx = G2G.ItemSelector(fileList, self, header='Select PWDR item',
3297                                    title='Select a PWDR tree item for '+key+'\n(or cancel to quit)')
3298                    if indx is None:
3299                        print('No PWDR entry selected for '+key)
3300                        return True
3301                    self.pdfControls[key]['Name'] = fileList[indx]
3302        return False
3303               
3304    def OnTimerLoop(self,event):
3305        '''A method that is called every :meth:`PollTime` seconds that is
3306        used to check for new files and process them. Integrates new images.
3307        Also optionally sets up and computes PDF.
3308        This is called only after the "Start" button is pressed (then its label reads "Pause").
3309        '''
3310        def AutoIntegrateImage(imgId,useTA=None,useMask=None):
3311            '''Integrates an image that has been read into the data tree and updates the
3312            AutoInt window.
3313            '''
3314            img = G2frame.GPXtree.GetItemText(imgId)
3315            controlsDict = G2frame.GPXtree.GetItemPyData(
3316                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Image Controls'))
3317            ImageMasks = G2frame.GPXtree.GetItemPyData(
3318                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Masks'))
3319            if self.params['Mode'] == 'table': # look up parameter values from table
3320                useTA = None        #force remake of x,y-->2th,azm map
3321                self.ResetFromTable(controlsDict['setdist'])
3322            # update controls from master
3323            controlsDict.update(self.ImageControls)
3324            # update masks from master w/o Thresholds
3325            ImageMasks.update(self.ImageMasks)
3326            self.EnableButtons(False)
3327            try:
3328                self.IntegrateImage(img,useTA=useTA,useMask=useMask)
3329            finally:
3330                self.EnableButtons(True)
3331            self.G2frame.oldImagefile = '' # mark image as changed; reread as needed
3332            wx.Yield()
3333            self.ShowMatchingFiles(self.params['filter'])
3334            wx.Yield()
3335           
3336        def AutoComputePDF(imgId):
3337            '''Computes a PDF for a PWDR data tree tree item
3338            '''
3339            for pwdr in G2frame.AutointPWDRnames[:]:
3340                if not pwdr.startswith('PWDR '): continue
3341                if pwdr in G2frame.AutointPDFnames: continue
3342                PWid = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,pwdr)
3343                controlsDict = G2frame.GPXtree.GetItemPyData(
3344                    G2gd.GetGPXtreeItemId(G2frame,imgId, 'Image Controls'))
3345                if self.params['pdfDmax'] != 0 and controlsDict['distance'] > self.params['pdfDmax']:
3346                    print('Skipping PDF for '+pwdr+' due to detector position')
3347                    continue
3348                # Setup PDF
3349                Data = G2frame.GPXtree.GetItemPyData(PWid)[1]
3350                pwdrMin = np.min(Data[1])
3351                Parms = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3352                    G2frame,PWid,'Instrument Parameters'))[0]
3353                fullLimits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3354                    G2frame,PWid,'Limits'))[0]
3355                if 'C' in Parms['Type'][0]:
3356                    qMax = tth2q(fullLimits[1],G2mth.getWave(Parms))
3357                else:
3358                    qMax = tof2q(fullLimits[0],Parms['difC'][1])
3359                Qlimits = [0.9*qMax,qMax]
3360
3361                item = pwdr
3362                Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3363                    G2frame,imgId, 'Comments'))
3364                ElList = {}
3365                sumnum = 1.0
3366                for item in Comments:           #grab chemical formula from Comments, if there
3367                    if 'formula' in item[:15].lower():
3368                        formula = item.split('=')[1].split()
3369                        elems = formula[::2]
3370                        nums = formula[1::2]
3371                        formula = zip(elems,nums)
3372                        sumnum = 0.
3373                        for [elem,num] in formula:
3374                            ElData = G2elem.GetElInfo(elem,Parms)
3375                            ElData['FormulaNo'] = float(num)
3376                            sumnum += float(num)
3377                            ElList[elem] = ElData
3378                PDFnames = G2gd.GetGPXtreeDataNames(G2frame,['PDF ',])
3379                PDFid = G2obj.CreatePDFitems(G2frame,pwdr,ElList.copy(),Qlimits,sumnum,pwdrMin,PDFnames)
3380                if not PDFid: continue
3381                PDFdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3382                    G2frame,PDFid, 'PDF Controls'))
3383                PDFdata.update(copy.deepcopy(self.pdfControls))
3384                if ElList: PDFdata['ElList'] = ElList # override with formula from comments, if present
3385                PDFdata['Sample']['Name'] = pwdr
3386                # compute PDF
3387                wx.Yield()
3388                G2pdG.computePDF(G2frame,PDFdata)
3389                wx.Yield()
3390                G2frame.PatternId = PDFid
3391                G2plt.PlotISFG(G2frame,PDFdata,newPlot=False,plotType='G(R)')
3392                if self.params['optPDF']:
3393                    G2pdG.OptimizePDF(G2frame,PDFdata,maxCycles=10,)
3394                    wx.Yield()
3395                    G2plt.PlotISFG(G2frame,PDFdata,newPlot=False,plotType='G(R)')
3396                G2frame.AutointPDFnames.append(pwdr)
3397                # save names of PDF entry to be deleted later if needed
3398                G2frame.AutointPWDRnames.append(G2frame.GPXtree.GetItemText(PDFid))
3399           
3400        G2frame = self.G2frame
3401        try:
3402            self.currImageList = sorted(
3403                glob.glob(os.path.join(self.params['readdir'],self.params['filter'])))
3404            self.ShowMatchingFiles(self.params['filter'])
3405        except IndexError:
3406            self.currImageList = []
3407            return
3408
3409        if self.PreventReEntryTimer: return
3410        self.PreventReEntryTimer = True
3411        imageFileList = []
3412        # integrate the images that have already been read in, but
3413        # have not yet been processed           
3414        oldData = {'tilt':0.,'distance':0.,'rotation':0.,'center':[0.,0.],'DetDepth':0.,'azmthOff':0.}
3415        oldMhash = 0
3416        if 'useTA' not in dir(self):    #initial definition; reuse if after Resume
3417            self.useTA = None
3418            self.useMask = None
3419        for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']):
3420            imgId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
3421            size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(imgId)
3422            # Create a list of image files that have been read in
3423            if imagefile not in imageFileList: imageFileList.append(imagefile)
3424            # skip if already integrated
3425            if img in G2frame.IntegratedList: continue
3426            Data = G2frame.GPXtree.GetItemPyData(
3427                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Image Controls'))
3428            sameTA = True
3429            for item in ['tilt','distance','rotation','center','DetDepth','azmthOff']:
3430                if Data[item] != oldData[item]:
3431                    sameTA = False
3432            if not sameTA:
3433                t0 = time.time()
3434                self.useTA = G2img.MakeUseTA(Data,blkSize)
3435                print(' Use new image controls; xy->th,azm mtime: %.3f'%(time.time()-t0))
3436            Mask = G2frame.GPXtree.GetItemPyData(
3437                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Masks'))
3438            Mhash = copy.deepcopy(Mask)
3439            Mhash.pop('Thresholds')
3440            Mhash = hash(str(Mhash))
3441            if  Mhash != oldMhash:
3442                t0 = time.time()
3443                self.useMask = G2img.MakeUseMask(Data,Mask,blkSize)
3444                print(' Use new mask; make mask time: %.3f'%(time.time()-t0))
3445                oldMhash = Mhash
3446            AutoIntegrateImage(imgId,self.useTA,self.useMask)
3447            oldData = Data
3448            if self.pdfControls: AutoComputePDF(imgId)
3449            self.Pause |= G2frame.PauseIntegration
3450            if self.Pause:
3451                self.OnPause()
3452                self.PreventReEntryTimer = False
3453                self.Raise()
3454                return
3455
3456        # loop over image files matching glob, reading in any new ones
3457        if self.useTA is None or self.useMask is None:
3458            print('Integration will not be fast; there is no beginning image controls')     #TODO: work this out??
3459        for newImage in self.currImageList:
3460            if newImage in imageFileList or self.Pause: continue # already read?
3461            for imgId in G2IO.ReadImages(G2frame,newImage):
3462                AutoIntegrateImage(imgId,self.useTA,self.useMask)
3463                if self.pdfControls: AutoComputePDF(imgId)
3464                self.Pause |= G2frame.PauseIntegration
3465                if self.Pause:
3466                    self.OnPause()
3467                    self.PreventReEntryTimer = False
3468                    self.Raise()
3469                    return
3470        if GSASIIpath.GetConfigValue('debug'):
3471            import datetime
3472            print ("DBG_Timer tick at {:%d %b %Y %H:%M:%S}\n".format(datetime.datetime.now()))
3473        self.PreventReEntryTimer = False
3474        self.Raise()
3475
3476def DefineEvaluator(dlg):
3477    '''Creates a function that provides interpolated values for a given distance value
3478    '''
3479    def Evaluator(dist):
3480        '''Interpolate image parameters for a supplied distance value
3481
3482        :param float dist: distance to use for interpolation
3483        :returns: a list with 3 items:
3484
3485          * a dict with interpolated parameter values,
3486          * the closest imctrl and
3487          * the closest maskfile (or None)
3488        '''           
3489        x = np.array([float(i) for i in parms[0]])
3490        closest = abs(x-dist).argmin()
3491        D = {'setdist':dist}
3492        imctfile = IMfileList[closest]
3493        if parms[-1][closest].lower() != '(none)':
3494            maskfile = parms[-1][closest]
3495        else:
3496            maskfile = None
3497        for c in range(1,cols-1):
3498            lbl = ParmList[c]
3499            if lbl in nonInterpVars:
3500                if lbl in ['outChannels',]:
3501                    D[lbl] = int(float(parms[c][closest]))
3502                else:
3503                    D[lbl] = float(parms[c][closest])
3504            else:
3505                y = np.array([float(i) for i in parms[c]])
3506                D[lbl] = np.interp(dist,x,y)
3507        # full integration when angular range is 0
3508        D['fullIntegrate'] = (D['LRazimuth_min'] == D['LRazimuth_max'])
3509        # conversion for paired values
3510        for a,b in ('center_x','center_y'),('LRazimuth_min','LRazimuth_max'),('IOtth_min','IOtth_max'):
3511            r = a.split('_')[0]
3512            D[r] = [D[a],D[b]]
3513            if r in ['LRazimuth',]:
3514                D[r] = [int(D[a]),int(D[b])]
3515            del D[a]
3516            del D[b]
3517        return D,imctfile,maskfile
3518    # save local copies of values needed in Evaluator
3519    parms = dlg.ReadImageParmTable()
3520    IMfileList = dlg.IMfileList
3521    cols = dlg.list.GetColumnCount()
3522    ParmList = dlg.ParmList
3523    nonInterpVars = dlg.nonInterpVars
3524    return Evaluator
3525
3526class IntegParmTable(wx.Dialog):
3527    '''Creates a dialog window with a table of integration parameters.
3528    :meth:`ShowModal` will return wx.ID_OK if the process has been successful.
3529    In this case, :func:`DefineEvaluator` should be called to obtain a function that
3530    creates a dictionary with interpolated parameter values.
3531    '''
3532    ParmList = ('setdist','distance','center_x','center_y','wavelength','tilt','rotation','DetDepth',
3533            'LRazimuth_min','LRazimuth_max','IOtth_min','IOtth_max','outChannels',
3534            'maskfile',
3535            )
3536    nonInterpVars = ('tilt','rotation','LRazimuth_min','LRazimuth_max','IOtth_min','IOtth_max',
3537                     'outChannels')  # values in this list are taken from nearest rather than interpolated
3538    HeaderList = ('Set Dist','Calib Dist','X cntr','Y cntr','wavelength','tilt','rotation','DetDepth',
3539            'Azimuth min','Azimuth max','2Th min','2Th max','Int. pts',
3540            'Mask File',
3541            )
3542    def __init__(self,parent,parms=None,IMfileList=None,readFileList=None):
3543        self.G2frame = parent.G2frame
3544        dlg = None
3545        pth = ''
3546        wx.Dialog.__init__(self,parent,style=wx.RESIZE_BORDER|wx.DEFAULT_DIALOG_STYLE)
3547        if readFileList:
3548            self.parms,self.IMfileList = self.ReadFiles(readFileList)
3549        elif parms:
3550            self.parms = parms # list of values by column
3551            self.IMfileList = IMfileList # list of .imctrl file names for each entry in table
3552        else:
3553            self.parms = [] # list of values by column
3554            self.IMfileList = [] # list of .imctrl file names for each entry in table
3555            files = []
3556            try:
3557                pth = G2G.GetImportPath(self.G2frame)
3558                if not pth: pth = '.'
3559                dlg = wx.FileDialog(parent, 'Read previous table or build new table by selecting image control files', pth,
3560                    style=wx.FD_OPEN| wx.FD_MULTIPLE,
3561                    wildcard='Integration table (*.imtbl)|*.imtbl|image control files (.imctrl)|*.imctrl')
3562                dlg.CenterOnParent()
3563                if dlg.ShowModal() == wx.ID_OK:
3564                    files = dlg.GetPaths()
3565                    self.parms,self.IMfileList = self.ReadFiles(files)
3566            finally:
3567                if dlg: dlg.Destroy()
3568            if not files:
3569                wx.CallAfter(self.EndModal,wx.ID_CANCEL)
3570                return
3571        mainSizer = wx.BoxSizer(wx.VERTICAL)
3572        self.list = ImgIntLstCtrl(self, wx.ID_ANY,style=wx.LC_REPORT| wx.BORDER_SUNKEN)
3573        mainSizer.Add(self.list,1,wx.EXPAND,1)
3574        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3575        btn = wx.Button(self, wx.ID_OK)
3576        btnsizer.Add(btn)
3577        btn = wx.Button(self, wx.ID_ANY,'Save as file')
3578        btn.Bind(wx.EVT_BUTTON,self._onSave)
3579        btnsizer.Add(btn)
3580        btn = wx.Button(self, wx.ID_CLOSE,'Quit')
3581        btn.Bind(wx.EVT_BUTTON,self._onClose)
3582        btnsizer.Add(btn)
3583        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)   
3584        self.SetSizer(mainSizer)
3585        self.list.FillList(self.parms)
3586       
3587    def ReadFiles(self,files):
3588        '''Reads a list of .imctrl files or a single .imtbl file
3589        '''
3590        tmpDict = {}
3591        if not files: return
3592        # option 1, a dump from a previous save
3593        if os.path.splitext(files[0])[1] == '.imtbl':
3594            fp = open(files[0],'r')
3595            S = fp.readline()
3596            while S:
3597                if S[0] != '#':
3598                    [key,val] = S[:-1].split(':',1)
3599                    tmpDict[key] = eval(val)
3600                S = fp.readline()
3601            fp.close()
3602            # delete entries where files do not exist
3603            m1 = [i for i,f in enumerate(tmpDict['filenames']) if not os.path.exists(f)]
3604            if m1:
3605                print('\nimctrl file not found:')
3606                for i in m1: print('\t#'+str(i)+': '+tmpDict['filenames'][i])
3607            m2 = [i for i,f in enumerate(tmpDict['maskfile']) if not (os.path.exists(f) or f.startswith('('))]
3608            if m2:
3609                print('\nmask file not found')
3610                for i in m2: print('\t#'+str(i)+': '+tmpDict['maskfile'][i])
3611            m3 = [i for i,d in enumerate(tmpDict['distance']) if d < 0]
3612            if m3:
3613                print('\nDropping entries due to negative distance: '+str(m3))
3614            m = sorted(set(m1 + m2 + m3))
3615            m.reverse()
3616            for c in m:
3617                for key in tmpDict:
3618                    del tmpDict[key][c]
3619            fileList = tmpDict.get('filenames','[]')
3620            parms = []
3621            if 'setdist' not in tmpDict:
3622                print(u'Old file, recreate before using: {}'.format(files[0]))
3623                return [[]],[]
3624            for key in self.ParmList:
3625                try:
3626                    float(tmpDict[key][0])
3627                    parms.append([str(G2py3.FormatSigFigs(val1,sigfigs=5)) for val1 in tmpDict[key]])
3628                except ValueError:
3629                    parms.append(tmpDict[key])
3630                except IndexError:
3631                    print('No valid image control entries read')
3632                    wx.CallAfter(self.EndModal,wx.ID_CANCEL)
3633                    return [[]],[]
3634            return parms,fileList
3635        # option 2, read in a list of files
3636        for file in files: # read all files; place in dict by distance
3637            imgDict = Read_imctrl(file)
3638            dist = imgDict.get('setdist',imgDict['distance'])
3639            if dist is None:
3640                print('Skipping old file, redo: {}'.format(file))
3641            tmpDict[dist] = imgDict
3642        parms = [[] for key in self.ParmList]
3643        fileList = []
3644        for d in sorted(tmpDict):
3645            fileList.append(tmpDict[d].get('filename'))
3646            if d is None: continue
3647            if d < 0: continue
3648            for i,key in enumerate(self.ParmList):
3649                val = tmpDict[d].get(key)
3650                try:
3651                    val = str(G2py3.FormatSigFigs(val,sigfigs=5))
3652                except:
3653                    val = str(val)
3654                parms[i].append(val)
3655        return parms,fileList
3656   
3657    def ReadImageParmTable(self):
3658        '''Reads possibly edited values from the ListCtrl table and returns a list
3659        of values for each column.
3660        '''
3661        rows = self.list.GetItemCount()
3662        cols = self.list.GetColumnCount()
3663        parms = []
3664        for c in range(cols):
3665            parms.append([])
3666            for r in range(rows):
3667                parms[c].append(self.list.GetItem(r,c).GetText())
3668        return parms
3669
3670    def _onClose(self,event):
3671        'Called when Cancel button is pressed'
3672        self.EndModal(wx.ID_CANCEL)
3673       
3674    def _onSave(self,event):
3675        'Called when save button is pressed; creates a .imtbl file'
3676        fil = ''
3677        if self.G2frame.GSASprojectfile:
3678            fil = os.path.splitext(self.G2frame.GSASprojectfile)[0]+'.imtbl'
3679        dir,f = os.path.split(fil)
3680        pth = G2G.GetExportPath(self.G2frame)
3681        try:
3682            dlg = wx.FileDialog(self, 'Save table data as',
3683                        defaultDir=pth, defaultFile=f, style=wx.SAVE,
3684                        wildcard='G2 Image Param Table file (*.imtbl)|*.imtbl')
3685            dlg.CenterOnParent()
3686            if dlg.ShowModal() != wx.ID_OK: return
3687            fil = dlg.GetPath()
3688            fil = os.path.splitext(fil)[0]+'.imtbl'
3689        finally:
3690            dlg.Destroy()       
3691        parms = self.ReadImageParmTable()
3692        print('Writing image parameter table as '+fil)
3693        fp = open(fil,'w')
3694        for c in range(len(parms)-1):
3695            lbl = self.ParmList[c]
3696            fp.write(lbl+': '+str([eval(i) for i in parms[c]])+'\n')
3697        lbl = self.ParmList[c+1]
3698        fp.write(lbl+': '+str(parms[c+1])+'\n')
3699        lbl = 'filenames'
3700        fp.write(lbl+': '+str(self.IMfileList)+'\n')
3701        fp.close()
3702
3703class ImgIntLstCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin,listmix.TextEditMixin):
3704    '''Creates a custom ListCtrl for editing Image Integration parameters
3705    '''
3706    def __init__(self, parent, ID, pos=wx.DefaultPosition,size=(1000,200),style=0):
3707        self.parent=parent
3708        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
3709        listmix.ListCtrlAutoWidthMixin.__init__(self)
3710        listmix.TextEditMixin.__init__(self)
3711        self.Bind(wx.EVT_LEFT_DCLICK, self.OnDouble)
3712        #self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
3713       
3714    def FillList(self,parms):
3715        'Places the current parms into the table'
3716        # the use of InsertStringItem and SetStringItem are depricated in 4.0 but
3717        # I am not quite sure how to replace them with InsertItem and SetItem yet.
3718        # Perhaps switch to  ULC.UltimateListCtrl?
3719        #
3720        maxint = 2**31-1
3721        self.ClearAll()
3722        self.rowlen = len(self.parent.ParmList)
3723        for i,lbl in enumerate(self.parent.HeaderList):
3724            self.InsertColumn(i, lbl)
3725        for r,d in enumerate(parms[0]):
3726            if d is None: continue
3727            if d == 'None': continue
3728            if float(d) < 0: continue
3729            index = self.InsertStringItem(maxint, d)
3730            for j in range(1,len(parms)):
3731                self.SetStringItem(index, j, parms[j][r])
3732        for i,lbl in enumerate(self.parent.ParmList):
3733            self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
3734
3735    def OnDouble(self,evt):
3736        'respond to a double-click'
3737        self.CloseEditor()
3738        fil = '(none)'
3739        pth = G2G.GetImportPath(self.parent.G2frame)
3740        if not pth: pth = '.'
3741        try:
3742            dlg = wx.FileDialog(self, 'Select mask or control file to add (Press cancel if none)', pth,
3743                style=wx.FD_OPEN,wildcard='Add GSAS-II mask file (.immask)|*.immask|add image control file (.imctrl)|*.imctrl')
3744            dlg.CenterOnParent()
3745            if dlg.ShowModal() == wx.ID_OK:
3746                fil = dlg.GetPath()
3747        finally:
3748            dlg.Destroy()
3749        if os.path.splitext(fil)[1] != '.imctrl':
3750            self.SetStringItem(self.curRow, self.rowlen-1, fil)
3751            self.SetColumnWidth(self.rowlen-1, wx.LIST_AUTOSIZE)
3752        else:
3753            # insert or overwrite an instrument parameter set
3754            if not os.path.exists(fil):
3755                print('Does not exist: '+fil)
3756                return
3757            imgDict = Read_imctrl(fil)
3758            dist = imgDict['distance']
3759            parms = self.parent.ReadImageParmTable()
3760            x = np.array([float(i) for i in parms[0]])
3761            closest = abs(x-dist).argmin()
3762            closeX = x[closest]
3763            # fix IMfileList
3764            for c,lbl in enumerate(self.parent.ParmList):
3765                try:
3766                    vali = G2py3.FormatSigFigs(float(imgDict[lbl]),sigfigs=5)
3767                except ValueError:
3768                    vali = imgDict[lbl]
3769                if abs(closeX-dist) < 1.: # distance is within 1 mm, replace
3770                    parms[c][closest] = vali
3771                elif dist > closeX: # insert after
3772                    parms[c].insert(closest+1,vali)
3773                else:
3774                    parms[c].insert(closest,vali)
3775            if abs(closeX-dist) < 1.: # distance is within 1 mm, replace
3776                self.parent.IMfileList[closest] = fil
3777            elif dist > closeX: # insert after
3778                self.parent.IMfileList.insert(closest+1,fil)
3779            else:
3780                self.parent.IMfileList.insert(closest,fil)
3781            self.FillList(parms)
3782# Autointegration end
3783###########################################################################
3784
3785def testColumnMetadata(G2frame):
3786    '''Test the column-oriented metadata parsing, as implemented at 1-ID, by showing results
3787    when using a .par and .lbls pair.
3788   
3789     * Select a .par file, if more than one in selected dir.
3790     * Select the .*lbls file, if more than one matching .par file.
3791     * Parse the .lbls file, showing errors if encountered; loop until errors are fixed.
3792     * Search for an image or a line in the .par file and show the results when interpreted
3793     
3794    See :func:`GSASIIfiles.readColMetadata` for more details.
3795    '''
3796    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'):
3797        G2G.G2MessageBox(G2frame,'The configuration option for I-ID Metadata is not set.\n'+
3798                         'Please use the File/Preferences menu to set Column_Metadata_directory',
3799                         'Warning')
3800        return
3801    parFiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
3802    if not parFiles: 
3803        G2G.G2MessageBox(G2frame,'No .par files found in directory {}. '
3804                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
3805                         '\nThis is set by config variable Column_Metadata_directory '+
3806                         '(Set in File/Preferences menu).',
3807                         'Warning')
3808        return
3809    parList = []
3810    for parFile in parFiles:
3811        lblList = []
3812        parRoot = os.path.splitext(parFile)[0]
3813        for f in glob.glob(parRoot+'.*lbls'):
3814            if os.path.exists(f): lblList.append(f)
3815        if not len(lblList):
3816            continue
3817        parList.append(parFile)
3818    if len(parList) == 0:
3819        G2G.G2MessageBox(G2frame,'No .lbls or .EXT_lbls file found for .par file(s) in directory {}. '
3820                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
3821                         '\nThis is set by config variable Column_Metadata_directory '+
3822                         '(Set in File/Preferences menu).',
3823                         'Warning')
3824        return
3825    elif len(parList) == 1:
3826        parFile = parList[0]
3827    else:
3828        dlg = G2G.G2SingleChoiceDialog(G2frame,
3829                'More than 1 .par file found. (Better if only 1!). Choose the one to test in '+
3830                GSASIIpath.GetConfigValue('Column_Metadata_directory'),
3831                'Choose .par file', [os.path.split(i)[1] for i in parList])
3832        if dlg.ShowModal() == wx.ID_OK:
3833            parFile = parList[dlg.GetSelection()]
3834            dlg.Destroy()
3835        else:
3836            dlg.Destroy()
3837            return
3838    # got .par file; now work on .*lbls file
3839    lblList = []
3840    parRoot = os.path.splitext(parFile)[0]
3841    for f in glob.glob(parRoot+'.*lbls'):
3842        if os.path.exists(f): lblList.append(f)
3843    if not len(lblList):
3844        raise Exception('How did this happen! No .*lbls files for '+parFile)
3845    elif len(lblList) == 1:
3846        lblFile = lblList[0]
3847    else:
3848        dlg = G2G.G2SingleChoiceDialog(G2frame,
3849                'Select label file for .par file '+parFile,
3850                'Choose label file', [os.path.split(i)[1] for i in lblList])
3851        if dlg.ShowModal() == wx.ID_OK:
3852            lblFile = lblList[dlg.GetSelection()]
3853            dlg.Destroy()
3854        else:
3855            dlg.Destroy()
3856            return
3857    # parse the labels file
3858    errors = True
3859    while errors:
3860        labels,lbldict,keyCols,keyExp,errors = G2fil.readColMetadataLabels(lblFile)
3861        if errors:
3862            t = "Error reading file "+lblFile
3863            for l in errors:
3864                t += '\n'
3865                t += l
3866            t += "\n\nPlease edit the file and press OK (or Cancel to quit)"
3867            dlg = wx.MessageDialog(G2frame,message=t,
3868                caption="Read Error",style=wx.ICON_ERROR| wx.OK|wx.STAY_ON_TOP|wx.CANCEL)
3869            if dlg.ShowModal() != wx.ID_OK: return           
3870    # request a line number, read that line
3871    dlg = G2G.SingleStringDialog(G2frame,'Read what',
3872                                 'Enter a line number or an image file name (-1=last line)',
3873                                 '-1',size=(400,-1))
3874    if dlg.Show():
3875        fileorline = dlg.GetValue()
3876        dlg.Destroy()
3877    else:
3878        dlg.Destroy()
3879        return
3880    # and report the generated key pairs in metadata dict
3881    linenum = None
3882    try:
3883        linenum = int(fileorline)
3884    except:
3885        imageName = os.path.splitext(os.path.split(fileorline)[1])[0]
3886
3887    fp = open(parFile,'Ur')
3888    for iline,line in enumerate(fp):
3889        if linenum is not None:
3890            if iline == linenum:
3891                items = line.strip().split(' ')
3892                n = "Line {}".format(iline)
3893                break
3894            else:
3895                continue
3896        else:
3897            items = line.strip().split(' ')
3898            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
3899            if type(nameList) is str:
3900                if nameList != imageName: continue
3901                name = nameList
3902                break
3903            else:
3904                for name in nameList:
3905                    print (name,name == imageName)
3906                    if name == imageName:
3907                        n = "Image {} found in line {}".format(imageName,iline)
3908                        break # got a match
3909                else:
3910                    continue
3911                break
3912    else:
3913        if linenum is not None:
3914            n = "Line {}".format(iline)
3915        else:
3916            n = "Image {} not found. Reporting line {}".format(imageName,iline)
3917        items = line.strip().split(' ')
3918    fp.close()
3919    metadata = G2fil.evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,True)
3920    title = "Results: ("+n+")"
3921    t = ['Files: '+parFile,lblFile,' ']
3922    n = ["Named parameters:"]
3923    l = ['',"Labeled columns:"]
3924    for key in sorted(metadata):
3925        if key == "filename" or key.startswith('label_prm'): continue
3926        if key in keyCols:
3927            n += [{} = {}".format(key,metadata[key])]
3928        elif key in lbldict.values():
3929            l += [{} = {}".format(key,metadata[key])]
3930        else:
3931            t += ["** Unexpected:  {}".format(key,metadata[key])]
3932    if type(metadata['filename']) is str:
3933        l += ["","Filename: "+ metadata['filename']]
3934    else:
3935        l += ["","Filename(s): "]
3936        for i,j in enumerate(metadata['filename']):
3937            if i: l[-1] += ', '
3938            l[-1] += j
3939    t += n + l + ['','Unused columns:']
3940    usedCols = list(lbldict.keys())
3941    for i in keyCols.values(): usedCols += i
3942    for i in range(len(items)):
3943        if i in usedCols: continue
3944        t += [{}: {}".format(i,items[i])]
3945    dlg = G2G.G2SingleChoiceDialog(None,title,'Column metadata parse results',t,
3946                                   monoFont=True,filterBox=False,size=(400,600),
3947                                   style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE|wx.OK)
3948    dlg.ShowModal()
Note: See TracBrowser for help on using the repository browser.