MechMicroMan / DefDAP

A python library for correlating EBSD and HRDIC data
Apache License 2.0
33 stars 14 forks source link

Interactive buttons for deciding slip character #9

Closed AllanHarte closed 4 years ago

AllanHarte commented 6 years ago

Hello python people...

I would like a function to decide between types of slip character in individual grains. Slip character can be planar, wavy or diffuse. I cannot envisage a way of automating the code to make this distinction (other than using machine learning... which is something that we may want to discuss by the way!) and so I need a function that pops up several buttons that I can click to decide on the slip character myself.

In the DataAnalysisUtilities package there is a function that links grains from one EBSD map to another EBSD map (e.g. maps of the same area but pre- and pos-deformation). This uses a "link" button, but I do not need to do anything as complicated as linking two data sets.

Does anyone know of a simple interactive function for a button that I could use?

Thanks, A

mikesmic commented 6 years ago

You can add buttons (from matplotlib.widgets) to a figure window and then you can set a function that is called when it is clicked. See startLinking in the Linker class for an example. Although you'll want to use the on_clicked() function of the button to attach the event handler rather than how I've done it there (https://matplotlib.org/api/widgets_api.html#matplotlib.widgets.Button.on_clicked). You have to be careful with the button you create going out of scope and being discarded as well because then the click will not work and you don't get an error message (very frustrating to figure out and fix).

I would suggest having a member function of the grain class that sets the type of slip for that grain from an input argument then call that from some buttons added to the figure that shows individual grains. You might have to use a lambda expression to pass the different argument to the click event function from each button, I'm not sure on that though.

Come and see me if you want some more explanation.

JQFonseca commented 6 years ago

Another quicker(?) solution would be to just ask for keyboard input: c for coarse, w for wavy, d for diffuse.

And I don't know about an automatic method but a max/min ratio of a hoop integral of the FFT at a given radius, might be a good way of distinguishing between the different kinds of slip. And more reproducible!

João

On Tue, 2018-05-01 at 05:29 -0700, Michael Atkinson wrote:

You can add buttons (from matplotlib.widgets) to a figure window and then you can set a function that is called when it is clicked. See startLinking in the Linker class for an example. Although you'll want to use the on_clicked() function of the button to attach the event handler rather than how I've done it there (https://matplotlib.org/ap i/widgets_api.html#matplotlib.widgets.Button.on_clicked). You have to be careful with the button you create going out of scope and being discarded as well because then the click will not work and you don't get an error message (very frustrating to figure out and fix). I would suggest having a member function of the grain class that sets the type of slip for that grain from an input argument then call that from some buttons added to the figure that shows individual grains. You might have to use a lambda expression to pass the different argument to the click event function from each button, I'm not sure on that though. Come and see me if you want some more explanation. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

JQFonseca commented 6 years ago

You probably need the autocorrelation array, not the FFT.

Sent from my iPhone

On 1 May 2018, at 16:56, joao.q.fonseca@gmail.com wrote:

Another quicker(?) solution would be to just ask for keyboard input: c for coarse, w for wavy, d for diffuse.

And I don't know about an automatic method but a max/min ratio of a hoop integral of the FFT at a given radius, might be a good way of distinguishing between the different kinds of slip. And more reproducible!

João

On Tue, 2018-05-01 at 05:29 -0700, Michael Atkinson wrote: You can add buttons (from matplotlib.widgets) to a figure window and then you can set a function that is called when it is clicked. See startLinking in the Linker class for an example. Although you'll want to use the on_clicked() function of the button to attach the event handler rather than how I've done it there (https://matplotlib.org/ap i/widgets_api.html#matplotlib.widgets.Button.on_clicked). You have to be careful with the button you create going out of scope and being discarded as well because then the click will not work and you don't get an error message (very frustrating to figure out and fix). I would suggest having a member function of the grain class that sets the type of slip for that grain from an input argument then call that from some buttons added to the figure that shows individual grains. You might have to use a lambda expression to pass the different argument to the click event function from each button, I'm not sure on that though. Come and see me if you want some more explanation. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

AllanHarte commented 6 years ago

Thanks both for your suggestions. I will look into implementing an automatic function before I try this button.. using the FFT may well be the way forward. I will report back when I've given this a go!

AllanHarte commented 6 years ago

Only just got around to looking at this yesterday and today. I found that automating the routine (using filters to get the slip bands and then an FFT or autocorrelation to get the angles) was unreliable, so I've gone with the manual buttons approach. You need to make sure the Matplotlib backend is in osx and then you can run the following to add these two functions to the Grain class:

`def getGrainDefMap(self): x0, y0, xmax, ymax = self.extremeCoords

# initialise array with nans so area not in grain displays white
grainMaxShear = np.full((ymax - y0 + 1, xmax - x0 + 1), np.nan, dtype=float)

for coord, maxShear in zip(self.coordList, self.maxShearList):
    grainMaxShear[coord[1] - y0, coord[0] - x0] = maxShear

return grainMaxShear

hrdic.Grain.getGrainDefMap = getGrainDefMap`

`def analyseSlipCharacter(self):

fig, ax = plt.subplots()

# get the grain data
grainNumber = DicMap.grainList.index(self)

# plot the grain data
x=getGrainDefMap(self)
plt.imshow(x,vmax=0.06)
plt.colorbar(shrink=0.8, label="$\\epsilon_{eff}$")
plt.title("Grain "+str(grainNumber))

# create buttons
button_axcut={}
button={}

button_axcut['Planar'] = plt.axes([0.01,0.0, 0.06, 0.06])
button['Planar'] = Button(button_axcut['Planar'] ,'Planar', color='white')
button['Planar'].label.set_fontsize(8)

button_axcut['Diffuse'] = plt.axes([0.08,0.0, 0.06, 0.06])
button['Diffuse'] = Button(button_axcut['Diffuse'] ,'Diffuse', color='white')
button['Diffuse'].label.set_fontsize(8)

button_axcut['PAndD'] = plt.axes([0.15,0.0, 0.06, 0.06])
button['PAndD'] = Button(button_axcut['PAndD'] ,'PAndD', color='white')
button['PAndD'].label.set_fontsize(8)

button_axcut['Cross'] = plt.axes([0.22,0.0, 0.06, 0.06])
button['Cross'] = Button(button_axcut['Cross'],'Cross', color='white')
button['Cross'].label.set_fontsize(8)

button_axcut['Bifurcation'] = plt.axes([0.29,0.0, 0.1, 0.06])
button['Bifurcation'] = Button(button_axcut['Bifurcation'],'Bifurcation', color='white')
button['Bifurcation'].label.set_fontsize(8)

button_axcut['GB fading'] = plt.axes([0.40,0.0, 0.1, 0.06])
button['GB fading'] = Button(button_axcut['GB fading'],'GB fading', color='white')
button['GB fading'].label.set_fontsize(8)

button_axcut['Impingement'] = plt.axes([0.51,0.0, 0.12, 0.06])
button['Impingement'] = Button(button_axcut['Impingement'],'Impingement', color='white')
button['Impingement'].label.set_fontsize(8)

# number of slip planes
button_axcut['0'] = plt.axes([0.64,0.0, 0.03, 0.06])
button['0'] = Button(button_axcut['0'],'0', color='white')
button['0'].label.set_fontsize(8)

button_axcut['1'] = plt.axes([0.69,0.0, 0.03, 0.06])
button['1'] = Button(button_axcut['1'],'1', color='white')
button['1'].label.set_fontsize(8)

button_axcut['2'] = plt.axes([0.74,0.0, 0.03, 0.06])
button['2'] = Button(button_axcut['2'],'2', color='white')
button['2'].label.set_fontsize(8)

button_axcut['3'] = plt.axes([0.79,0.0, 0.03, 0.06])
button['3'] = Button(button_axcut['3'],'3', color='white')
button['3'].label.set_fontsize(8)

button_axcut['4'] = plt.axes([0.84,0.0, 0.03, 0.06])
button['4'] = Button(button_axcut['4'],'4', color='white')
button['4'].label.set_fontsize(8)

# Restart in case mistake is made
button_axcut['Restart'] = plt.axes([0.89,0.0, 0.1, 0.06])
button['Restart'] = Button(button_axcut['Restart'],'Restart', color='white')

# create emply list attributed to grain object
self.slipCharacter=[]

# upon click of button...
def click(event):
    for b in ["Planar", "Diffuse", "PAndD", "Cross","Bifurcation","GB fading","Impingement","0","1","2","3","4"]:
        if button[b].ax == event.inaxes:
            button[b].ax.set_facecolor('green')
            button[b].color = 'green' # supposed to turn green on click but this is not curtrently working
            button[b].ax.figure.canvas.draw()
            print(b) # print because green currently not working
            self.slipCharacter.append(b) # append grain liost
        else:
            button[b].ax.set_facecolor('white')
            button[b].color = 'white'
    if button["Restart"].ax == event.inaxes:
        button[b].ax.set_facecolor('green')
        button[b].color = 'green'
        self.slipCharacter = [] # restart by re-creating empty list
        print("[]")
    fig.canvas.draw_idle()

for b in ["Planar", "Diffuse","PAndD", "Cross","Bifurcation","GB fading","Impingement","0","1","2","3","4","Restart"]:
    button[b].on_clicked(click)

# for doing this is a loop, plot one figure at a time with block=True
plt.show(block=True)

hrdic.Grain.analyseSlipCharacter = analyseSlipCharacter`

Then run the code for, e.g. grain number 30 with:

DicMap.grainList[30].analyseSlipCharacter()

Once you close the figure the buttons that you press will be appended to the list DicMap.grainList[30].slipCharacter

AllanHarte commented 6 years ago

Hold on the code for those functions didn't quite post properly... let me try again:

` def getGrainDefMap(self): x0, y0, xmax, ymax = self.extremeCoords

# initialise array with nans so area not in grain displays white
grainMaxShear = np.full((ymax - y0 + 1, xmax - x0 + 1), np.nan, dtype=float)

for coord, maxShear in zip(self.coordList, self.maxShearList):
    grainMaxShear[coord[1] - y0, coord[0] - x0] = maxShear

return grainMaxShear

hrdic.Grain.getGrainDefMap = getGrainDefMap

def analyseSlipCharacter(self):

fig, ax = plt.subplots()

# get the grain data
grainNumber = DicMap.grainList.index(self)

# plot the grain data
x=getGrainDefMap(self)
plt.imshow(x,vmax=0.06)
plt.colorbar(shrink=0.8, label="$\\epsilon_{eff}$")
plt.title("Grain "+str(grainNumber))

# create buttons
button_axcut={}
button={}

button_axcut['Planar'] = plt.axes([0.01,0.0, 0.06, 0.06])
button['Planar'] = Button(button_axcut['Planar'] ,'Planar', color='white')
button['Planar'].label.set_fontsize(8)

button_axcut['Diffuse'] = plt.axes([0.08,0.0, 0.06, 0.06])
button['Diffuse'] = Button(button_axcut['Diffuse'] ,'Diffuse', color='white')
button['Diffuse'].label.set_fontsize(8)

button_axcut['PAndD'] = plt.axes([0.15,0.0, 0.06, 0.06])
button['PAndD'] = Button(button_axcut['PAndD'] ,'PAndD', color='white')
button['PAndD'].label.set_fontsize(8)

button_axcut['Cross'] = plt.axes([0.22,0.0, 0.06, 0.06])
button['Cross'] = Button(button_axcut['Cross'],'Cross', color='white')
button['Cross'].label.set_fontsize(8)

button_axcut['Bifurcation'] = plt.axes([0.29,0.0, 0.1, 0.06])
button['Bifurcation'] = Button(button_axcut['Bifurcation'],'Bifurcation', color='white')
button['Bifurcation'].label.set_fontsize(8)

button_axcut['GB fading'] = plt.axes([0.40,0.0, 0.1, 0.06])
button['GB fading'] = Button(button_axcut['GB fading'],'GB fading', color='white')
button['GB fading'].label.set_fontsize(8)

button_axcut['Impingement'] = plt.axes([0.51,0.0, 0.12, 0.06])
button['Impingement'] = Button(button_axcut['Impingement'],'Impingement', color='white')
button['Impingement'].label.set_fontsize(8)

# number of slip planes
button_axcut['0'] = plt.axes([0.64,0.0, 0.03, 0.06])
button['0'] = Button(button_axcut['0'],'0', color='white')
button['0'].label.set_fontsize(8)

button_axcut['1'] = plt.axes([0.69,0.0, 0.03, 0.06])
button['1'] = Button(button_axcut['1'],'1', color='white')
button['1'].label.set_fontsize(8)

button_axcut['2'] = plt.axes([0.74,0.0, 0.03, 0.06])
button['2'] = Button(button_axcut['2'],'2', color='white')
button['2'].label.set_fontsize(8)

button_axcut['3'] = plt.axes([0.79,0.0, 0.03, 0.06])
button['3'] = Button(button_axcut['3'],'3', color='white')
button['3'].label.set_fontsize(8)

button_axcut['4'] = plt.axes([0.84,0.0, 0.03, 0.06])
button['4'] = Button(button_axcut['4'],'4', color='white')
button['4'].label.set_fontsize(8)

# Restart in case mistake is made
button_axcut['Restart'] = plt.axes([0.89,0.0, 0.1, 0.06])
button['Restart'] = Button(button_axcut['Restart'],'Restart', color='white')

# create emply list attributed to grain object
self.slipCharacter=[]

# upon click of button...
def click(event):
    for b in ["Planar", "Diffuse", "PAndD", "Cross","Bifurcation","GB fading","Impingement","0","1","2","3","4"]:
        if button[b].ax == event.inaxes:
            button[b].ax.set_facecolor('green')
            button[b].color = 'green' # supposed to turn green on click but this is not curtrently working
            button[b].ax.figure.canvas.draw()
            print(b) # print because green currently not working
            self.slipCharacter.append(b) # append grain liost
        else:
            button[b].ax.set_facecolor('white')
            button[b].color = 'white'
    if button["Restart"].ax == event.inaxes:
        button[b].ax.set_facecolor('green')
        button[b].color = 'green'
        self.slipCharacter = [] # restart by re-creating empty list
        print("[]")
    fig.canvas.draw_idle()

for b in ["Planar", "Diffuse","PAndD", "Cross","Bifurcation","GB fading","Impingement","0","1","2","3","4","Restart"]:
    button[b].on_clicked(click)

# for doing this is a loop, plot one figure at a time with block=True
plt.show(block=True)

hrdic.Grain.analyseSlipCharacter = analyseSlipCharacter

`

AllanHarte commented 6 years ago

Ok for some reason the first and last lines of the code aren't formatted properly here but all of the code is there. The only issue with the code is that the buttons are supposed to turn green when you press them but this doesn't seem to work properly. Maybe an issue with the backend.

JQFonseca commented 6 years ago

Thanks Allan. Do you have some code for what you tried to do automatically? I am all up for the pragmatic approach and getting results but I am just thinking of having something that will scale up in the future.

João

On 13 Jul 2018, at 11:09, AllanHarte notifications@github.com wrote:

Ok for some reason the first and last lines of the code aren't formatted properly here but all of the code is there. The only issue with the code is that the buttons are supposed to turn green when you press them but this doesn't seem to work properly. Maybe an issue with the backend.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

AllanHarte commented 6 years ago

I'll put together a notebook with the things that I've tried

mikesmic commented 6 years ago

Looks good. The buttons not changing colour is probably to do with canvas not redrawing. Try adding something like fig.canvas.draw() after you make a change. Although I wouldn't bother past trying that.