source: trunk/GSASIIimgGUI.py @ 4219

Last change on this file since 4219 was 4219, checked in by toby, 3 years ago

add kludge for sphinx 1.8.5 build

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