Skip to content

Commit 420adf5

Browse files
Merge pull request #54 from jonathanrocher/feature/add_stage6
Feature: add stage6
2 parents e0c08e4 + fbc795f commit 420adf5

45 files changed

Lines changed: 836 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Second real version of the pycasa ETS pyface application
2+
Building on the application state 5.1, this version adds a button to the folder
3+
and file views so the faces can be detected.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
__version__ = "0.0.1"

stage6_branded_application/pycasa/app/__init__.py

Whitespace-only changes.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# coding=utf-8
2+
""" TaskApplication object for the Pycasa app.
3+
"""
4+
import logging
5+
6+
from pyface.tasks.api import TasksApplication, TaskFactory
7+
from pyface.api import SplashScreen
8+
from pyface.action.api import Action
9+
from pyface.action.schema.api import SchemaAddition, SGroup
10+
11+
from ..ui.tasks.pycasa_task import PycasaTask
12+
from ..ui.image_resources import app_icon, new_icon
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class PycasaApplication(TasksApplication):
18+
""" An application to explore image files and detect faces.
19+
"""
20+
id = "pycasa_application"
21+
22+
name = "Pycasa"
23+
24+
description = "An example Tasks application that explores image files."
25+
26+
def _task_factories_default(self):
27+
return [
28+
TaskFactory(
29+
id='pycasa.pycasa_task_factory',
30+
name="Main Pycasa Task Factory",
31+
factory=PycasaTask
32+
)
33+
]
34+
35+
def _icon_default(self):
36+
return app_icon
37+
38+
def _splash_screen_default(self):
39+
splash_screen = SplashScreen(image=app_icon)
40+
return splash_screen
41+
42+
def create_new_task_window(self):
43+
from pyface.tasks.task_window_layout import TaskWindowLayout
44+
45+
layout = TaskWindowLayout()
46+
layout.items = [self.task_factories[0].id]
47+
window = self.create_window(layout=layout)
48+
self.add_window(window)
49+
window.title += " {}".format(len(self.windows))
50+
return window
51+
52+
def create_new_task_menu(self):
53+
return SGroup(
54+
Action(name="New",
55+
accelerator='Ctrl+N',
56+
on_perform=self.create_new_task_window,
57+
image=new_icon),
58+
id='NewGroup', name='NewGroup',
59+
)
60+
61+
def _extra_actions_default(self):
62+
extra_actions = [
63+
SchemaAddition(id='pycasa.custom_new',
64+
factory=self.create_new_task_menu,
65+
path="MenuBar/File/OpenGroup",
66+
absolute_position="first")
67+
]
68+
return extra_actions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
from pycasa.app.app import PycasaApplication
3+
4+
5+
def main():
6+
app = PycasaApplication()
7+
app.run()
8+
9+
10+
if __name__ == '__main__':
11+
main()

stage6_branded_application/pycasa/model/__init__.py

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from os.path import expanduser
2+
from traits.api import Directory, Event, HasStrictTraits
3+
4+
5+
class FileBrowser(HasStrictTraits):
6+
root = Directory(expanduser("~"))
7+
8+
#: Item last double-clicked on in the tree view
9+
requested_item = Event
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# General imports
2+
import os
3+
import PIL.Image
4+
from PIL.ExifTags import TAGS
5+
from skimage import data
6+
from skimage.feature import Cascade
7+
import numpy as np
8+
9+
# ETS imports
10+
from traits.api import Array, cached_property, Dict, File, HasStrictTraits, \
11+
List, Property
12+
13+
SUPPORTED_FORMATS = [".png", ".jpg", ".jpeg"]
14+
15+
16+
class ImageFile(HasStrictTraits):
17+
""" Model to hold an image file.
18+
"""
19+
filepath = File
20+
21+
metadata = Property(Dict, depends_on="filepath")
22+
23+
data = Property(Array, depends_on="filepath")
24+
25+
faces = List
26+
27+
def to_array(self):
28+
file_ext = os.path.splitext(self.filepath)[1].lower()
29+
if not self.filepath or file_ext not in SUPPORTED_FORMATS:
30+
return np.array([])
31+
32+
with PIL.Image.open(self.filepath) as img:
33+
return np.asarray(img)
34+
35+
@cached_property
36+
def _get_data(self):
37+
return self.to_array()
38+
39+
@cached_property
40+
def _get_metadata(self):
41+
file_ext = os.path.splitext(self.filepath)[1].lower()
42+
if not self.filepath or file_ext not in SUPPORTED_FORMATS:
43+
return {}
44+
45+
with PIL.Image.open(self.filepath) as img:
46+
exif = img._getexif()
47+
48+
if exif:
49+
return {TAGS[k]: v for k, v in exif.items()
50+
if k in TAGS}
51+
else:
52+
return {}
53+
54+
def detect_faces(self, scale_factor=1.2, step_ratio=1, min_size=60,
55+
max_size=600):
56+
""" Detect faces in the image.
57+
"""
58+
trained_file = data.lbp_frontal_face_cascade_filename()
59+
detector = Cascade(trained_file)
60+
faces = detector.detect_multi_scale(img=self.data,
61+
scale_factor=scale_factor,
62+
step_ratio=step_ratio,
63+
min_size=(min_size, min_size),
64+
max_size=(max_size, max_size))
65+
self.faces = faces
66+
return faces
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# General imports
2+
import os
3+
4+
import pandas as pd
5+
import numpy as np
6+
7+
# ETS imports
8+
from traits.api import Directory, Event, HasStrictTraits, Instance
9+
10+
# Local imports
11+
from .image_file import ImageFile, SUPPORTED_FORMATS
12+
13+
FILENAME_COL = "filename"
14+
15+
NUM_FACE_COL = "Num. faces"
16+
17+
18+
class ImageFolder(HasStrictTraits):
19+
""" Model to hold an image folder.
20+
"""
21+
path = Directory
22+
23+
data = Instance(pd.DataFrame)
24+
25+
data_updated = Event
26+
27+
def __init__(self, **traits):
28+
# Don't forget this!
29+
super(ImageFolder, self).__init__(**traits)
30+
if not os.path.isdir(self.path):
31+
msg = f"Unable to create an ImageFolder from {self.path} since" \
32+
f" it is not a valid directory."
33+
raise ValueError(msg)
34+
35+
self.data = self.to_dataframe()
36+
37+
def to_dataframe(self):
38+
if not self.path:
39+
return pd.DataFrame({FILENAME_COL: [], NUM_FACE_COL: []})
40+
41+
data = []
42+
for filename in os.listdir(self.path):
43+
file_ext = os.path.splitext(filename)[1].lower()
44+
if file_ext in SUPPORTED_FORMATS:
45+
filepath = os.path.join(self.path, filename)
46+
img_file = ImageFile(filepath=filepath)
47+
file_data = {FILENAME_COL: filename, NUM_FACE_COL: np.nan}
48+
try:
49+
file_data.update(img_file.metadata)
50+
except Exception:
51+
pass
52+
data.append(file_data)
53+
54+
return pd.DataFrame(data)
55+
56+
def compute_num_faces(self, **kwargs):
57+
cols = list(self.data.columns)
58+
for i, filename in enumerate(self.data[FILENAME_COL]):
59+
print(filename)
60+
filepath = os.path.join(self.path, filename)
61+
img_file = ImageFile(filepath=filepath)
62+
faces = img_file.detect_faces(**kwargs)
63+
j = cols.index(NUM_FACE_COL)
64+
self.data.iloc[i, j] = len(faces)
65+
self.data_updated = True

stage6_branded_application/pycasa/model/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)