source: trunk/GSASIIimgGUI.py @ 4332

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

faster build of spot mask
spot masks now work in integrate all

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