You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

562 lines
18 KiB
Python

# Natural Language Toolkit: GUI Demo for Glue Semantics with Discourse
# Representation Theory (DRT) as meaning language
#
# Author: Dan Garrette <dhgarrette@gmail.com>
#
# Copyright (C) 2001-2019 NLTK Project
# URL: <http://nltk.org/>
# For license information, see LICENSE.TXT
try:
from six.moves.tkinter import (
Button,
Frame,
IntVar,
Label,
Listbox,
Menu,
Scrollbar,
Tk,
)
from six.moves.tkinter_font import Font
from nltk.draw.util import CanvasFrame, ShowText
except ImportError:
"""Ignore ImportError because tkinter might not be available."""
from nltk.util import in_idle
from nltk.tag import RegexpTagger
from nltk.parse import MaltParser
from nltk.sem.logic import Variable
from nltk.sem.drt import DrsDrawer, DrtVariableExpression
from nltk.sem.glue import DrtGlue
class DrtGlueDemo(object):
def __init__(self, examples):
# Set up the main window.
self._top = Tk()
self._top.title('DRT Glue Demo')
# Set up key bindings.
self._init_bindings()
# Initialize the fonts.self._error = None
self._init_fonts(self._top)
self._examples = examples
self._readingCache = [None for example in examples]
# The user can hide the grammar.
self._show_grammar = IntVar(self._top)
self._show_grammar.set(1)
# Set the data to None
self._curExample = -1
self._readings = []
self._drs = None
self._drsWidget = None
self._error = None
self._init_glue()
# Create the basic frames.
self._init_menubar(self._top)
self._init_buttons(self._top)
self._init_exampleListbox(self._top)
self._init_readingListbox(self._top)
self._init_canvas(self._top)
# Resize callback
self._canvas.bind('<Configure>', self._configure)
#########################################
## Initialization Helpers
#########################################
def _init_glue(self):
tagger = RegexpTagger(
[
('^(David|Mary|John)$', 'NNP'),
(
'^(walks|sees|eats|chases|believes|gives|sleeps|chases|persuades|tries|seems|leaves)$',
'VB',
),
('^(go|order|vanish|find|approach)$', 'VB'),
('^(a)$', 'ex_quant'),
('^(every)$', 'univ_quant'),
('^(sandwich|man|dog|pizza|unicorn|cat|senator)$', 'NN'),
('^(big|gray|former)$', 'JJ'),
('^(him|himself)$', 'PRP'),
]
)
depparser = MaltParser(tagger=tagger)
self._glue = DrtGlue(depparser=depparser, remove_duplicates=False)
def _init_fonts(self, root):
# See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
self._sysfont = Font(font=Button()["font"])
root.option_add("*Font", self._sysfont)
# TWhat's our font size (default=same as sysfont)
self._size = IntVar(root)
self._size.set(self._sysfont.cget('size'))
self._boldfont = Font(family='helvetica', weight='bold', size=self._size.get())
self._font = Font(family='helvetica', size=self._size.get())
if self._size.get() < 0:
big = self._size.get() - 2
else:
big = self._size.get() + 2
self._bigfont = Font(family='helvetica', weight='bold', size=big)
def _init_exampleListbox(self, parent):
self._exampleFrame = listframe = Frame(parent)
self._exampleFrame.pack(fill='both', side='left', padx=2)
self._exampleList_label = Label(
self._exampleFrame, font=self._boldfont, text='Examples'
)
self._exampleList_label.pack()
self._exampleList = Listbox(
self._exampleFrame,
selectmode='single',
relief='groove',
background='white',
foreground='#909090',
font=self._font,
selectforeground='#004040',
selectbackground='#c0f0c0',
)
self._exampleList.pack(side='right', fill='both', expand=1)
for example in self._examples:
self._exampleList.insert('end', (' %s' % example))
self._exampleList.config(height=min(len(self._examples), 25), width=40)
# Add a scrollbar if there are more than 25 examples.
if len(self._examples) > 25:
listscroll = Scrollbar(self._exampleFrame, orient='vertical')
self._exampleList.config(yscrollcommand=listscroll.set)
listscroll.config(command=self._exampleList.yview)
listscroll.pack(side='left', fill='y')
# If they select a example, apply it.
self._exampleList.bind('<<ListboxSelect>>', self._exampleList_select)
def _init_readingListbox(self, parent):
self._readingFrame = listframe = Frame(parent)
self._readingFrame.pack(fill='both', side='left', padx=2)
self._readingList_label = Label(
self._readingFrame, font=self._boldfont, text='Readings'
)
self._readingList_label.pack()
self._readingList = Listbox(
self._readingFrame,
selectmode='single',
relief='groove',
background='white',
foreground='#909090',
font=self._font,
selectforeground='#004040',
selectbackground='#c0f0c0',
)
self._readingList.pack(side='right', fill='both', expand=1)
# Add a scrollbar if there are more than 25 examples.
listscroll = Scrollbar(self._readingFrame, orient='vertical')
self._readingList.config(yscrollcommand=listscroll.set)
listscroll.config(command=self._readingList.yview)
listscroll.pack(side='right', fill='y')
self._populate_readingListbox()
def _populate_readingListbox(self):
# Populate the listbox with integers
self._readingList.delete(0, 'end')
for i in range(len(self._readings)):
self._readingList.insert('end', (' %s' % (i + 1)))
self._readingList.config(height=min(len(self._readings), 25), width=5)
# If they select a example, apply it.
self._readingList.bind('<<ListboxSelect>>', self._readingList_select)
def _init_bindings(self):
# Key bindings are a good thing.
self._top.bind('<Control-q>', self.destroy)
self._top.bind('<Control-x>', self.destroy)
self._top.bind('<Escape>', self.destroy)
self._top.bind('n', self.next)
self._top.bind('<space>', self.next)
self._top.bind('p', self.prev)
self._top.bind('<BackSpace>', self.prev)
def _init_buttons(self, parent):
# Set up the frames.
self._buttonframe = buttonframe = Frame(parent)
buttonframe.pack(fill='none', side='bottom', padx=3, pady=2)
Button(
buttonframe,
text='Prev',
background='#90c0d0',
foreground='black',
command=self.prev,
).pack(side='left')
Button(
buttonframe,
text='Next',
background='#90c0d0',
foreground='black',
command=self.next,
).pack(side='left')
def _configure(self, event):
self._autostep = 0
(x1, y1, x2, y2) = self._cframe.scrollregion()
y2 = event.height - 6
self._canvas['scrollregion'] = '%d %d %d %d' % (x1, y1, x2, y2)
self._redraw()
def _init_canvas(self, parent):
self._cframe = CanvasFrame(
parent,
background='white',
# width=525, height=250,
closeenough=10,
border=2,
relief='sunken',
)
self._cframe.pack(expand=1, fill='both', side='top', pady=2)
canvas = self._canvas = self._cframe.canvas()
# Initially, there's no tree or text
self._tree = None
self._textwidgets = []
self._textline = None
def _init_menubar(self, parent):
menubar = Menu(parent)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(
label='Exit', underline=1, command=self.destroy, accelerator='q'
)
menubar.add_cascade(label='File', underline=0, menu=filemenu)
actionmenu = Menu(menubar, tearoff=0)
actionmenu.add_command(
label='Next', underline=0, command=self.next, accelerator='n, Space'
)
actionmenu.add_command(
label='Previous', underline=0, command=self.prev, accelerator='p, Backspace'
)
menubar.add_cascade(label='Action', underline=0, menu=actionmenu)
optionmenu = Menu(menubar, tearoff=0)
optionmenu.add_checkbutton(
label='Remove Duplicates',
underline=0,
variable=self._glue.remove_duplicates,
command=self._toggle_remove_duplicates,
accelerator='r',
)
menubar.add_cascade(label='Options', underline=0, menu=optionmenu)
viewmenu = Menu(menubar, tearoff=0)
viewmenu.add_radiobutton(
label='Tiny',
variable=self._size,
underline=0,
value=10,
command=self.resize,
)
viewmenu.add_radiobutton(
label='Small',
variable=self._size,
underline=0,
value=12,
command=self.resize,
)
viewmenu.add_radiobutton(
label='Medium',
variable=self._size,
underline=0,
value=14,
command=self.resize,
)
viewmenu.add_radiobutton(
label='Large',
variable=self._size,
underline=0,
value=18,
command=self.resize,
)
viewmenu.add_radiobutton(
label='Huge',
variable=self._size,
underline=0,
value=24,
command=self.resize,
)
menubar.add_cascade(label='View', underline=0, menu=viewmenu)
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label='About', underline=0, command=self.about)
menubar.add_cascade(label='Help', underline=0, menu=helpmenu)
parent.config(menu=menubar)
#########################################
## Main draw procedure
#########################################
def _redraw(self):
canvas = self._canvas
# Delete the old DRS, widgets, etc.
if self._drsWidget is not None:
self._drsWidget.clear()
if self._drs:
self._drsWidget = DrsWidget(self._canvas, self._drs)
self._drsWidget.draw()
if self._error:
self._drsWidget = DrsWidget(self._canvas, self._error)
self._drsWidget.draw()
#########################################
## Button Callbacks
#########################################
def destroy(self, *e):
self._autostep = 0
if self._top is None:
return
self._top.destroy()
self._top = None
def prev(self, *e):
selection = self._readingList.curselection()
readingListSize = self._readingList.size()
# there are readings
if readingListSize > 0:
# if one reading is currently selected
if len(selection) == 1:
index = int(selection[0])
# if it's on (or before) the first item
if index <= 0:
self._select_previous_example()
else:
self._readingList_store_selection(index - 1)
else:
# select its first reading
self._readingList_store_selection(readingListSize - 1)
else:
self._select_previous_example()
def _select_previous_example(self):
# if the current example is not the first example
if self._curExample > 0:
self._exampleList_store_selection(self._curExample - 1)
else:
# go to the last example
self._exampleList_store_selection(len(self._examples) - 1)
def next(self, *e):
selection = self._readingList.curselection()
readingListSize = self._readingList.size()
# if there are readings
if readingListSize > 0:
# if one reading is currently selected
if len(selection) == 1:
index = int(selection[0])
# if it's on (or past) the last item
if index >= (readingListSize - 1):
self._select_next_example()
else:
self._readingList_store_selection(index + 1)
else:
# select its first reading
self._readingList_store_selection(0)
else:
self._select_next_example()
def _select_next_example(self):
# if the current example is not the last example
if self._curExample < len(self._examples) - 1:
self._exampleList_store_selection(self._curExample + 1)
else:
# go to the first example
self._exampleList_store_selection(0)
def about(self, *e):
ABOUT = (
"NLTK Discourse Representation Theory (DRT) Glue Semantics Demo\n"
+ "Written by Daniel H. Garrette"
)
TITLE = 'About: NLTK DRT Glue Demo'
try:
from six.moves.tkinter_messagebox import Message
Message(message=ABOUT, title=TITLE).show()
except:
ShowText(self._top, TITLE, ABOUT)
def postscript(self, *e):
self._autostep = 0
self._cframe.print_to_file()
def mainloop(self, *args, **kwargs):
"""
Enter the Tkinter mainloop. This function must be called if
this demo is created from a non-interactive program (e.g.
from a secript); otherwise, the demo will close as soon as
the script completes.
"""
if in_idle():
return
self._top.mainloop(*args, **kwargs)
def resize(self, size=None):
if size is not None:
self._size.set(size)
size = self._size.get()
self._font.configure(size=-(abs(size)))
self._boldfont.configure(size=-(abs(size)))
self._sysfont.configure(size=-(abs(size)))
self._bigfont.configure(size=-(abs(size + 2)))
self._redraw()
def _toggle_remove_duplicates(self):
self._glue.remove_duplicates = not self._glue.remove_duplicates
self._exampleList.selection_clear(0, 'end')
self._readings = []
self._populate_readingListbox()
self._readingCache = [None for ex in self._examples]
self._curExample = -1
self._error = None
self._drs = None
self._redraw()
def _exampleList_select(self, event):
selection = self._exampleList.curselection()
if len(selection) != 1:
return
self._exampleList_store_selection(int(selection[0]))
def _exampleList_store_selection(self, index):
self._curExample = index
example = self._examples[index]
self._exampleList.selection_clear(0, 'end')
if example:
cache = self._readingCache[index]
if cache:
if isinstance(cache, list):
self._readings = cache
self._error = None
else:
self._readings = []
self._error = cache
else:
try:
self._readings = self._glue.parse_to_meaning(example)
self._error = None
self._readingCache[index] = self._readings
except Exception as e:
self._readings = []
self._error = DrtVariableExpression(Variable('Error: ' + str(e)))
self._readingCache[index] = self._error
# add a star to the end of the example
self._exampleList.delete(index)
self._exampleList.insert(index, (' %s *' % example))
self._exampleList.config(
height=min(len(self._examples), 25), width=40
)
self._populate_readingListbox()
self._exampleList.selection_set(index)
self._drs = None
self._redraw()
def _readingList_select(self, event):
selection = self._readingList.curselection()
if len(selection) != 1:
return
self._readingList_store_selection(int(selection[0]))
def _readingList_store_selection(self, index):
reading = self._readings[index]
self._readingList.selection_clear(0, 'end')
if reading:
self._readingList.selection_set(index)
self._drs = reading.simplify().normalize().resolve_anaphora()
self._redraw()
class DrsWidget(object):
def __init__(self, canvas, drs, **attribs):
self._drs = drs
self._canvas = canvas
canvas.font = Font(
font=canvas.itemcget(canvas.create_text(0, 0, text=''), 'font')
)
canvas._BUFFER = 3
self.bbox = (0, 0, 0, 0)
def draw(self):
(right, bottom) = DrsDrawer(self._drs, canvas=self._canvas).draw()
self.bbox = (0, 0, right + 1, bottom + 1)
def clear(self):
self._canvas.create_rectangle(self.bbox, fill="white", width="0")
def demo():
examples = [
'John walks',
'David sees Mary',
'David eats a sandwich',
'every man chases a dog',
# 'every man believes a dog yawns',
# 'John gives David a sandwich',
'John chases himself',
# 'John persuades David to order a pizza',
# 'John tries to go',
# 'John tries to find a unicorn',
# 'John seems to vanish',
# 'a unicorn seems to approach',
# 'every big cat leaves',
# 'every gray cat leaves',
# 'every big gray cat leaves',
# 'a former senator leaves',
# 'John likes a cat',
# 'John likes every cat',
# 'he walks',
# 'John walks and he leaves'
]
DrtGlueDemo(examples).mainloop()
if __name__ == '__main__':
demo()