Skip to content

Commit 380c318

Browse files
Merge pull request #69 from jonathanrocher/exercises_and_advanced_traitsui
Exercises and advanced traitsui slides
2 parents e7b53c1 + 9bb9e45 commit 380c318

File tree

9 files changed

+423
-146
lines changed

9 files changed

+423
-146
lines changed

slides/02_traits.md

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,13 @@ class Child(HasStrictTraits):
200200

201201
@observe('father.last_name')
202202
def _dad_name_updated(self, event):
203-
print(Father name changed to', self.father.last_name)
203+
print('Father name changed to', self.father.last_name)
204204

205205
```
206206

207207
```python
208208
dad = Parent(last_name='Zubizaretta')
209-
c = Child(father=Parent)
209+
c = Child(father=dad)
210210
```
211211

212212
```python
@@ -426,7 +426,7 @@ Hint: Use `os.listdir` and `os.path.getsize`
426426
from math import pi
427427
from traits.api import Range, Float, Property, cached_property
428428

429-
class Circle(HasTraits):
429+
class Circle(HasStrictTraits):
430430
radius = Range(0.0, 1000.0)
431431
area = Property(Float, observe='radius')
432432

@@ -459,7 +459,7 @@ c.area
459459
import numpy as np
460460
from traits.api import Array, Range, observe
461461

462-
class Beats(HasTraits):
462+
class Beats(HasStrictTraits):
463463
f1 = Range(1.0, 200.0, value=100)
464464
f2 = Range(low=1.0, high=200.0, value=104)
465465
signal = Array(dtype=float, shape=(None,))
@@ -504,7 +504,7 @@ t = Thing()
504504
```
505505

506506
```python
507-
type(c.age)
507+
type(t.age)
508508
```
509509

510510
<!-- #region slideshow={"slide_type": "slide"} -->
@@ -515,15 +515,15 @@ type(c.age)
515515
<!-- #endregion -->
516516

517517
```python
518-
from traits.api import HasTraits
518+
from traits.api import Event, File, HasStrictTraits, Instance, observe, Str
519519

520520
class DataFile(HasStrictTraits):
521521
file = File
522522
data_changed = Event
523523

524524

525525
class DataReader(HasStrictTraits):
526-
file = DataFile
526+
file = Instance(DataFile)
527527
content = Str
528528

529529
@observe("file.data_changed")
@@ -540,12 +540,79 @@ r = DataReader(file=f)
540540
f.data_changed = True
541541
```
542542

543-
544543
<!-- #region slideshow={"slide_type": "slide"} -->
545544
## Exercise time!
546545

547-
- Take the simple example without traits
548-
- Create a simple Traits model for it
546+
- From the starting script (`stage1_starting_script/face_detect.py`), extract
547+
an object that represents an image file.
548+
- The class for the object should:
549+
- Be a traits model, i.e., inherit from `HasStrictTraits`, and, expose
550+
- Attributes:
551+
- `filepath`: the absolute path to the image file
552+
- `metadata`: a dictionary storing EXIF data
553+
- `data` a numpy array containing the RGB data
554+
- `faces`: a list containing detected faces
555+
- Methods:
556+
`detect_faces`: returns the list of detected faces
557+
- Be reactive:
558+
- Ensure `metadata` and `data` are updated with `filepath` is modified
559+
- Copy `stage1_starting_script/face_detect.py` to `stage2.1_traited_script` and
560+
work there
549561
- *Do not do any plotting in the model!*
550562

563+
- Hint for computing RGB data:
564+
565+
```python
566+
import numpy as np
567+
import PIL.Image
568+
569+
with PIL.Image.open(filepath) as img:
570+
data = np.asarray(img)
571+
```
572+
- Example images available at `ets_tutorial/sample_images` for testing
573+
574+
<!-- #endregion -->
575+
576+
<!-- #region slideshow={"slide_type": "slide"} -->
577+
### Solution
578+
`stage2.1_traited_script/traited_face_detect.py`
579+
<!-- #endregion -->
580+
581+
<!-- #region slideshow={"slide_type": "slide"} -->
582+
## Exercise time!
583+
584+
- Develop another traits model, one that represents a folder containing several
585+
image files
586+
- The class for the object should expose:
587+
- Attributes:
588+
- `directory`: the absolute path to the folder
589+
- `images`: a list of `ImageFile` instances from the previous exercise
590+
- `data`: a pandas `DataFrame` to store metadata for each file in the folder
591+
- Be reactive:
592+
- Ensure `images` and `data` are updated when `directory` is modified
593+
- Override `__init__` to ensure directory exists at object initialization
594+
- Save work in `stage2.1_traited_script/image_folder.py`
595+
596+
- Hints:
597+
- Create a `DataFrame` from `List(Dict)`:
598+
```python
599+
import pandas as pd
600+
>>> records = [
601+
{'A': 5, 'B': 0, 'C': 3, 'D': 3},
602+
{'A': 7, 'B': 9, 'C': 3, 'D': 5},
603+
{'A': 2, 'B': 4, 'C': 7, 'D': 6}
604+
]
605+
>>> df = pd.DataFrame(records)
606+
A B C D
607+
0 5 0 3 3
608+
1 7 9 3 5
609+
2 2 4 7 6
610+
```
611+
- `os.path.isdir(directory)` to determine if `directory` is valid
612+
613+
<!-- #endregion -->
614+
615+
<!-- #region slideshow={"slide_type": "slide"} -->
616+
### Solution
617+
`stage2.1_traited_script/image_folder.py`
551618
<!-- #endregion -->

slides/03_traitsui.md

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ jupyter:
55
extension: .md
66
format_name: markdown
77
format_version: '1.3'
8-
jupytext_version: 1.14.0
8+
jupytext_version: 1.13.7
99
kernelspec:
10-
display_name: Python 3 (ipykernel)
10+
display_name: Python 3
1111
language: python
1212
name: python3
1313
---
@@ -231,6 +231,44 @@ p.edit_traits()
231231
<!-- #region slideshow={"slide_type": "slide"} -->
232232
## Specifying an editor
233233

234+
- Editors: encapsulate display instructions for a trait type
235+
- Hide GUI-toolkit code behind an abstraction layer
236+
- All standard traits has a predefined editor that is automatically
237+
displayed when the trait is displayed, unless overridden
238+
<!-- #endregion -->
239+
240+
<!-- #region slideshow={"slide_type": "slide"} -->
241+
## Examples
242+
243+
This code automatically uses `StrEditor`, the default for `Str` traits:
244+
<!-- #endregion -->
245+
246+
```python
247+
class Stringy(HasStrictTraits):
248+
characters = Str()
249+
250+
s = Stringy(characters='<b>Stringy characters</b>')
251+
s.edit_traits(
252+
view=View(
253+
Item("characters")
254+
)
255+
)
256+
```
257+
<!-- #region slideshow={"slide_type": "slide"} -->
258+
This code uses an HTMLEditor:
259+
<!-- #endregion -->
260+
```python
261+
from traitsui.api import HTMLEditor
262+
s.edit_traits(
263+
view=View(
264+
Item("characters", editor=HTMLEditor())
265+
)
266+
)
267+
```
268+
269+
<!-- #region slideshow={"slide_type": "slide"} -->
270+
271+
## A few useful editors
234272
- We illustrate the powerful `InstanceEditor` here
235273
- Consider the following
236274

@@ -313,9 +351,140 @@ sam = Person(name='Sam', age=29, bff=frodo)
313351
sam.edit_traits()
314352
```
315353

354+
<!-- #region slideshow={"slide_type": "slide"} -->
355+
- Another useful editor allows us to interface with `DataFrame`s
356+
<!-- #endregion -->
357+
358+
```python
359+
import pandas as pd
360+
from traits.api import Event, Instance, Int
361+
from traitsui.api import ModelView
362+
from traitsui.ui_editors.data_frame_editor import DataFrameEditor
363+
364+
class FramedData(HasStrictTraits):
365+
data = Instance(pd.DataFrame)
366+
367+
def _data_default(self):
368+
return pd.DataFrame([
369+
{'A': 5, 'B': 0, 'C': 3, 'D': 3},
370+
{'A': 7, 'B': 9, 'C': 3, 'D': 5},
371+
{'A': 2, 'B': 4, 'C': 7, 'D': 6}
372+
])
373+
374+
class FramedDataView(ModelView):
375+
model = Instance(FramedData)
376+
377+
view = View(
378+
Item("model.data", editor=DataFrameEditor(editable=True))
379+
)
380+
381+
FramedDataView(model=FramedData()).edit_traits()
382+
```
316383

384+
<!-- #region slideshow={"slide_type": "slide"} -->
385+
## Enter plotting
386+
- Another useful editor is the `MplFigureEditor`
387+
- Allows interacting with `matplotlib.figure.Figure` instances
388+
- Included in the `ets_tutorial` package bundled in this repository
389+
<!-- #endregion -->
317390

318391
<!-- #region slideshow={"slide_type": "slide"} -->
392+
Example:
393+
<!-- #endregion -->
394+
```python
395+
from matplotlib.figure import Figure
396+
import numpy as np
397+
from skimage.data import chelsea
398+
from traits.api import Array, HasStrictTraits, Instance
399+
from traitsui.api import View, Item
400+
401+
from ets_tutorial.util.mpl_figure_editor import MplFigureEditor
402+
403+
class ImageViewer(HasStrictTraits):
404+
data = Array()
405+
406+
figure = Instance(Figure)
407+
408+
traits_view = View(
409+
Item("figure", editor=MplFigureEditor(), show_label=False)
410+
)
411+
412+
def _data_default(self):
413+
return chelsea()
414+
415+
def _figure_default(self):
416+
figure = Figure()
417+
axes = figure.add_subplot(111)
418+
axes.imshow(chelsea())
419+
return figure
420+
421+
ImageViewer().edit_traits()
422+
```
423+
<!-- #region slideshow={"slide_type": "slide"} -->
424+
## The ModelView object:
425+
- We want our science model to be free of UI code
426+
- But it's still useful for models and views to respond to changes to one
427+
another -- `ModelView`s
428+
- `ModelView`s also monitor UI toolkit events like window creation,
429+
closing, user clicking OK or Cancel buttons
430+
- Example:
431+
<!-- #endregion -->
432+
433+
```python
434+
from traits.api import observe
435+
class Image(HasStrictTraits):
436+
data = Array()
437+
438+
def _data_default(self):
439+
return chelsea()
440+
441+
class ImageView(ModelView):
442+
model = Instance(Image)
443+
444+
figure = Instance(Figure)
445+
446+
view = View(
447+
Item("figure", editor=MplFigureEditor(), show_label=False)
448+
)
449+
450+
@observe("model.data")
451+
def build_mpl_figure(self, event):
452+
figure = Figure()
453+
axes = figure.add_subplot(111)
454+
axes.imshow(self.model.data)
455+
self.figure = figure
456+
457+
```
458+
459+
```python
460+
image = Image()
461+
image_view = ImageView(model=image)
462+
image_view.edit_traits()
463+
```
464+
465+
```python
466+
from skimage.data import astronaut
467+
image.data = astronaut()
468+
```
469+
470+
<!-- #region slideshow={"slide_type": "slide"} -->
471+
## Exercise time!
472+
- Starting from where we left off in Stage 2.1:
473+
- Create a `ModelView` for the `ImageFile` object that displays its filepath
474+
(readonly), and the image array in a matplotlib figure
475+
- Ensure figure is updated if the `filepath` attribute of `ImageFile` is
476+
modified
477+
- Create a `ModelView` for the `ImageFolder` object that displays the directory
478+
(readonly) and the `DataFrame`
479+
- Bonus points:
480+
- What mechanism would we use to hide the `DataFrame` if the directory doesn't have any images
481+
and instead show a helpful message?
482+
- Hint: keyword arguments for `Item`
483+
<!-- #endregion -->
484+
<!-- #region slideshow={"slide_type": "slide"} -->
485+
## Solution
486+
487+
<!-- #endregion -->
319488
## Toolkit selection
320489

321490
- TraitsUI supports: Qt or wxPython

0 commit comments

Comments
 (0)