drt_glue_demo.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. # Natural Language Toolkit: GUI Demo for Glue Semantics with Discourse
  2. # Representation Theory (DRT) as meaning language
  3. #
  4. # Author: Dan Garrette <dhgarrette@gmail.com>
  5. #
  6. # Copyright (C) 2001-2020 NLTK Project
  7. # URL: <http://nltk.org/>
  8. # For license information, see LICENSE.TXT
  9. try:
  10. from tkinter import (
  11. Button,
  12. Frame,
  13. IntVar,
  14. Label,
  15. Listbox,
  16. Menu,
  17. Scrollbar,
  18. Tk,
  19. )
  20. from tkinter.font import Font
  21. from nltk.draw.util import CanvasFrame, ShowText
  22. except ImportError:
  23. """Ignore ImportError because tkinter might not be available."""
  24. from nltk.util import in_idle
  25. from nltk.tag import RegexpTagger
  26. from nltk.parse import MaltParser
  27. from nltk.sem.logic import Variable
  28. from nltk.sem.drt import DrsDrawer, DrtVariableExpression
  29. from nltk.sem.glue import DrtGlue
  30. class DrtGlueDemo(object):
  31. def __init__(self, examples):
  32. # Set up the main window.
  33. self._top = Tk()
  34. self._top.title("DRT Glue Demo")
  35. # Set up key bindings.
  36. self._init_bindings()
  37. # Initialize the fonts.self._error = None
  38. self._init_fonts(self._top)
  39. self._examples = examples
  40. self._readingCache = [None for example in examples]
  41. # The user can hide the grammar.
  42. self._show_grammar = IntVar(self._top)
  43. self._show_grammar.set(1)
  44. # Set the data to None
  45. self._curExample = -1
  46. self._readings = []
  47. self._drs = None
  48. self._drsWidget = None
  49. self._error = None
  50. self._init_glue()
  51. # Create the basic frames.
  52. self._init_menubar(self._top)
  53. self._init_buttons(self._top)
  54. self._init_exampleListbox(self._top)
  55. self._init_readingListbox(self._top)
  56. self._init_canvas(self._top)
  57. # Resize callback
  58. self._canvas.bind("<Configure>", self._configure)
  59. #########################################
  60. ## Initialization Helpers
  61. #########################################
  62. def _init_glue(self):
  63. tagger = RegexpTagger(
  64. [
  65. ("^(David|Mary|John)$", "NNP"),
  66. (
  67. "^(walks|sees|eats|chases|believes|gives|sleeps|chases|persuades|tries|seems|leaves)$",
  68. "VB",
  69. ),
  70. ("^(go|order|vanish|find|approach)$", "VB"),
  71. ("^(a)$", "ex_quant"),
  72. ("^(every)$", "univ_quant"),
  73. ("^(sandwich|man|dog|pizza|unicorn|cat|senator)$", "NN"),
  74. ("^(big|gray|former)$", "JJ"),
  75. ("^(him|himself)$", "PRP"),
  76. ]
  77. )
  78. depparser = MaltParser(tagger=tagger)
  79. self._glue = DrtGlue(depparser=depparser, remove_duplicates=False)
  80. def _init_fonts(self, root):
  81. # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
  82. self._sysfont = Font(font=Button()["font"])
  83. root.option_add("*Font", self._sysfont)
  84. # TWhat's our font size (default=same as sysfont)
  85. self._size = IntVar(root)
  86. self._size.set(self._sysfont.cget("size"))
  87. self._boldfont = Font(family="helvetica", weight="bold", size=self._size.get())
  88. self._font = Font(family="helvetica", size=self._size.get())
  89. if self._size.get() < 0:
  90. big = self._size.get() - 2
  91. else:
  92. big = self._size.get() + 2
  93. self._bigfont = Font(family="helvetica", weight="bold", size=big)
  94. def _init_exampleListbox(self, parent):
  95. self._exampleFrame = listframe = Frame(parent)
  96. self._exampleFrame.pack(fill="both", side="left", padx=2)
  97. self._exampleList_label = Label(
  98. self._exampleFrame, font=self._boldfont, text="Examples"
  99. )
  100. self._exampleList_label.pack()
  101. self._exampleList = Listbox(
  102. self._exampleFrame,
  103. selectmode="single",
  104. relief="groove",
  105. background="white",
  106. foreground="#909090",
  107. font=self._font,
  108. selectforeground="#004040",
  109. selectbackground="#c0f0c0",
  110. )
  111. self._exampleList.pack(side="right", fill="both", expand=1)
  112. for example in self._examples:
  113. self._exampleList.insert("end", (" %s" % example))
  114. self._exampleList.config(height=min(len(self._examples), 25), width=40)
  115. # Add a scrollbar if there are more than 25 examples.
  116. if len(self._examples) > 25:
  117. listscroll = Scrollbar(self._exampleFrame, orient="vertical")
  118. self._exampleList.config(yscrollcommand=listscroll.set)
  119. listscroll.config(command=self._exampleList.yview)
  120. listscroll.pack(side="left", fill="y")
  121. # If they select a example, apply it.
  122. self._exampleList.bind("<<ListboxSelect>>", self._exampleList_select)
  123. def _init_readingListbox(self, parent):
  124. self._readingFrame = listframe = Frame(parent)
  125. self._readingFrame.pack(fill="both", side="left", padx=2)
  126. self._readingList_label = Label(
  127. self._readingFrame, font=self._boldfont, text="Readings"
  128. )
  129. self._readingList_label.pack()
  130. self._readingList = Listbox(
  131. self._readingFrame,
  132. selectmode="single",
  133. relief="groove",
  134. background="white",
  135. foreground="#909090",
  136. font=self._font,
  137. selectforeground="#004040",
  138. selectbackground="#c0f0c0",
  139. )
  140. self._readingList.pack(side="right", fill="both", expand=1)
  141. # Add a scrollbar if there are more than 25 examples.
  142. listscroll = Scrollbar(self._readingFrame, orient="vertical")
  143. self._readingList.config(yscrollcommand=listscroll.set)
  144. listscroll.config(command=self._readingList.yview)
  145. listscroll.pack(side="right", fill="y")
  146. self._populate_readingListbox()
  147. def _populate_readingListbox(self):
  148. # Populate the listbox with integers
  149. self._readingList.delete(0, "end")
  150. for i in range(len(self._readings)):
  151. self._readingList.insert("end", (" %s" % (i + 1)))
  152. self._readingList.config(height=min(len(self._readings), 25), width=5)
  153. # If they select a example, apply it.
  154. self._readingList.bind("<<ListboxSelect>>", self._readingList_select)
  155. def _init_bindings(self):
  156. # Key bindings are a good thing.
  157. self._top.bind("<Control-q>", self.destroy)
  158. self._top.bind("<Control-x>", self.destroy)
  159. self._top.bind("<Escape>", self.destroy)
  160. self._top.bind("n", self.next)
  161. self._top.bind("<space>", self.next)
  162. self._top.bind("p", self.prev)
  163. self._top.bind("<BackSpace>", self.prev)
  164. def _init_buttons(self, parent):
  165. # Set up the frames.
  166. self._buttonframe = buttonframe = Frame(parent)
  167. buttonframe.pack(fill="none", side="bottom", padx=3, pady=2)
  168. Button(
  169. buttonframe,
  170. text="Prev",
  171. background="#90c0d0",
  172. foreground="black",
  173. command=self.prev,
  174. ).pack(side="left")
  175. Button(
  176. buttonframe,
  177. text="Next",
  178. background="#90c0d0",
  179. foreground="black",
  180. command=self.next,
  181. ).pack(side="left")
  182. def _configure(self, event):
  183. self._autostep = 0
  184. (x1, y1, x2, y2) = self._cframe.scrollregion()
  185. y2 = event.height - 6
  186. self._canvas["scrollregion"] = "%d %d %d %d" % (x1, y1, x2, y2)
  187. self._redraw()
  188. def _init_canvas(self, parent):
  189. self._cframe = CanvasFrame(
  190. parent,
  191. background="white",
  192. # width=525, height=250,
  193. closeenough=10,
  194. border=2,
  195. relief="sunken",
  196. )
  197. self._cframe.pack(expand=1, fill="both", side="top", pady=2)
  198. canvas = self._canvas = self._cframe.canvas()
  199. # Initially, there's no tree or text
  200. self._tree = None
  201. self._textwidgets = []
  202. self._textline = None
  203. def _init_menubar(self, parent):
  204. menubar = Menu(parent)
  205. filemenu = Menu(menubar, tearoff=0)
  206. filemenu.add_command(
  207. label="Exit", underline=1, command=self.destroy, accelerator="q"
  208. )
  209. menubar.add_cascade(label="File", underline=0, menu=filemenu)
  210. actionmenu = Menu(menubar, tearoff=0)
  211. actionmenu.add_command(
  212. label="Next", underline=0, command=self.next, accelerator="n, Space"
  213. )
  214. actionmenu.add_command(
  215. label="Previous", underline=0, command=self.prev, accelerator="p, Backspace"
  216. )
  217. menubar.add_cascade(label="Action", underline=0, menu=actionmenu)
  218. optionmenu = Menu(menubar, tearoff=0)
  219. optionmenu.add_checkbutton(
  220. label="Remove Duplicates",
  221. underline=0,
  222. variable=self._glue.remove_duplicates,
  223. command=self._toggle_remove_duplicates,
  224. accelerator="r",
  225. )
  226. menubar.add_cascade(label="Options", underline=0, menu=optionmenu)
  227. viewmenu = Menu(menubar, tearoff=0)
  228. viewmenu.add_radiobutton(
  229. label="Tiny",
  230. variable=self._size,
  231. underline=0,
  232. value=10,
  233. command=self.resize,
  234. )
  235. viewmenu.add_radiobutton(
  236. label="Small",
  237. variable=self._size,
  238. underline=0,
  239. value=12,
  240. command=self.resize,
  241. )
  242. viewmenu.add_radiobutton(
  243. label="Medium",
  244. variable=self._size,
  245. underline=0,
  246. value=14,
  247. command=self.resize,
  248. )
  249. viewmenu.add_radiobutton(
  250. label="Large",
  251. variable=self._size,
  252. underline=0,
  253. value=18,
  254. command=self.resize,
  255. )
  256. viewmenu.add_radiobutton(
  257. label="Huge",
  258. variable=self._size,
  259. underline=0,
  260. value=24,
  261. command=self.resize,
  262. )
  263. menubar.add_cascade(label="View", underline=0, menu=viewmenu)
  264. helpmenu = Menu(menubar, tearoff=0)
  265. helpmenu.add_command(label="About", underline=0, command=self.about)
  266. menubar.add_cascade(label="Help", underline=0, menu=helpmenu)
  267. parent.config(menu=menubar)
  268. #########################################
  269. ## Main draw procedure
  270. #########################################
  271. def _redraw(self):
  272. canvas = self._canvas
  273. # Delete the old DRS, widgets, etc.
  274. if self._drsWidget is not None:
  275. self._drsWidget.clear()
  276. if self._drs:
  277. self._drsWidget = DrsWidget(self._canvas, self._drs)
  278. self._drsWidget.draw()
  279. if self._error:
  280. self._drsWidget = DrsWidget(self._canvas, self._error)
  281. self._drsWidget.draw()
  282. #########################################
  283. ## Button Callbacks
  284. #########################################
  285. def destroy(self, *e):
  286. self._autostep = 0
  287. if self._top is None:
  288. return
  289. self._top.destroy()
  290. self._top = None
  291. def prev(self, *e):
  292. selection = self._readingList.curselection()
  293. readingListSize = self._readingList.size()
  294. # there are readings
  295. if readingListSize > 0:
  296. # if one reading is currently selected
  297. if len(selection) == 1:
  298. index = int(selection[0])
  299. # if it's on (or before) the first item
  300. if index <= 0:
  301. self._select_previous_example()
  302. else:
  303. self._readingList_store_selection(index - 1)
  304. else:
  305. # select its first reading
  306. self._readingList_store_selection(readingListSize - 1)
  307. else:
  308. self._select_previous_example()
  309. def _select_previous_example(self):
  310. # if the current example is not the first example
  311. if self._curExample > 0:
  312. self._exampleList_store_selection(self._curExample - 1)
  313. else:
  314. # go to the last example
  315. self._exampleList_store_selection(len(self._examples) - 1)
  316. def next(self, *e):
  317. selection = self._readingList.curselection()
  318. readingListSize = self._readingList.size()
  319. # if there are readings
  320. if readingListSize > 0:
  321. # if one reading is currently selected
  322. if len(selection) == 1:
  323. index = int(selection[0])
  324. # if it's on (or past) the last item
  325. if index >= (readingListSize - 1):
  326. self._select_next_example()
  327. else:
  328. self._readingList_store_selection(index + 1)
  329. else:
  330. # select its first reading
  331. self._readingList_store_selection(0)
  332. else:
  333. self._select_next_example()
  334. def _select_next_example(self):
  335. # if the current example is not the last example
  336. if self._curExample < len(self._examples) - 1:
  337. self._exampleList_store_selection(self._curExample + 1)
  338. else:
  339. # go to the first example
  340. self._exampleList_store_selection(0)
  341. def about(self, *e):
  342. ABOUT = (
  343. "NLTK Discourse Representation Theory (DRT) Glue Semantics Demo\n"
  344. + "Written by Daniel H. Garrette"
  345. )
  346. TITLE = "About: NLTK DRT Glue Demo"
  347. try:
  348. from tkinter.messagebox import Message
  349. Message(message=ABOUT, title=TITLE).show()
  350. except:
  351. ShowText(self._top, TITLE, ABOUT)
  352. def postscript(self, *e):
  353. self._autostep = 0
  354. self._cframe.print_to_file()
  355. def mainloop(self, *args, **kwargs):
  356. """
  357. Enter the Tkinter mainloop. This function must be called if
  358. this demo is created from a non-interactive program (e.g.
  359. from a secript); otherwise, the demo will close as soon as
  360. the script completes.
  361. """
  362. if in_idle():
  363. return
  364. self._top.mainloop(*args, **kwargs)
  365. def resize(self, size=None):
  366. if size is not None:
  367. self._size.set(size)
  368. size = self._size.get()
  369. self._font.configure(size=-(abs(size)))
  370. self._boldfont.configure(size=-(abs(size)))
  371. self._sysfont.configure(size=-(abs(size)))
  372. self._bigfont.configure(size=-(abs(size + 2)))
  373. self._redraw()
  374. def _toggle_remove_duplicates(self):
  375. self._glue.remove_duplicates = not self._glue.remove_duplicates
  376. self._exampleList.selection_clear(0, "end")
  377. self._readings = []
  378. self._populate_readingListbox()
  379. self._readingCache = [None for ex in self._examples]
  380. self._curExample = -1
  381. self._error = None
  382. self._drs = None
  383. self._redraw()
  384. def _exampleList_select(self, event):
  385. selection = self._exampleList.curselection()
  386. if len(selection) != 1:
  387. return
  388. self._exampleList_store_selection(int(selection[0]))
  389. def _exampleList_store_selection(self, index):
  390. self._curExample = index
  391. example = self._examples[index]
  392. self._exampleList.selection_clear(0, "end")
  393. if example:
  394. cache = self._readingCache[index]
  395. if cache:
  396. if isinstance(cache, list):
  397. self._readings = cache
  398. self._error = None
  399. else:
  400. self._readings = []
  401. self._error = cache
  402. else:
  403. try:
  404. self._readings = self._glue.parse_to_meaning(example)
  405. self._error = None
  406. self._readingCache[index] = self._readings
  407. except Exception as e:
  408. self._readings = []
  409. self._error = DrtVariableExpression(Variable("Error: " + str(e)))
  410. self._readingCache[index] = self._error
  411. # add a star to the end of the example
  412. self._exampleList.delete(index)
  413. self._exampleList.insert(index, (" %s *" % example))
  414. self._exampleList.config(
  415. height=min(len(self._examples), 25), width=40
  416. )
  417. self._populate_readingListbox()
  418. self._exampleList.selection_set(index)
  419. self._drs = None
  420. self._redraw()
  421. def _readingList_select(self, event):
  422. selection = self._readingList.curselection()
  423. if len(selection) != 1:
  424. return
  425. self._readingList_store_selection(int(selection[0]))
  426. def _readingList_store_selection(self, index):
  427. reading = self._readings[index]
  428. self._readingList.selection_clear(0, "end")
  429. if reading:
  430. self._readingList.selection_set(index)
  431. self._drs = reading.simplify().normalize().resolve_anaphora()
  432. self._redraw()
  433. class DrsWidget(object):
  434. def __init__(self, canvas, drs, **attribs):
  435. self._drs = drs
  436. self._canvas = canvas
  437. canvas.font = Font(
  438. font=canvas.itemcget(canvas.create_text(0, 0, text=""), "font")
  439. )
  440. canvas._BUFFER = 3
  441. self.bbox = (0, 0, 0, 0)
  442. def draw(self):
  443. (right, bottom) = DrsDrawer(self._drs, canvas=self._canvas).draw()
  444. self.bbox = (0, 0, right + 1, bottom + 1)
  445. def clear(self):
  446. self._canvas.create_rectangle(self.bbox, fill="white", width="0")
  447. def demo():
  448. examples = [
  449. "John walks",
  450. "David sees Mary",
  451. "David eats a sandwich",
  452. "every man chases a dog",
  453. # 'every man believes a dog yawns',
  454. # 'John gives David a sandwich',
  455. "John chases himself",
  456. # 'John persuades David to order a pizza',
  457. # 'John tries to go',
  458. # 'John tries to find a unicorn',
  459. # 'John seems to vanish',
  460. # 'a unicorn seems to approach',
  461. # 'every big cat leaves',
  462. # 'every gray cat leaves',
  463. # 'every big gray cat leaves',
  464. # 'a former senator leaves',
  465. # 'John likes a cat',
  466. # 'John likes every cat',
  467. # 'he walks',
  468. # 'John walks and he leaves'
  469. ]
  470. DrtGlueDemo(examples).mainloop()
  471. if __name__ == "__main__":
  472. demo()