embody_plot.py 7.84 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python

"""
Visualize emBODY data

This python script is based on matlab code found from:
https://version.aalto.fi/gitlab/eglerean/embody/tree/master/matlab


Requirements:
    - python 3+
    - matplotlib
    - numpy
    - scipy

Run:
python embody_plot.py
Ossi Laine's avatar
Ossi Laine committed
18

19
20
21
22
"""

import sys
import time
23
import datetime
24
25
26
import json
import resource
import mysql.connector as mariadb
27
28
29
30
31
32
33
34
35
36
37
import io
import urllib, base64
import argparse

import numpy as np
import scipy.ndimage as ndimage
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure

Ossi Laine's avatar
Ossi Laine committed
38
from flask_socketio import emit
root's avatar
root committed
39
from app import socketio, app
root's avatar
root committed
40
from config import Config
Ossi Laine's avatar
Ossi Laine committed
41

42

Ossi Laine's avatar
Ossi Laine committed
43
# Hard coded image size for default embody image
44
45
46
47
WIDTH = 207
HEIGHT = 600

# image paths
Ossi Laine's avatar
Ossi Laine committed
48
DEFAULT_IMAGE_PATH = './app/static/img/dummy_600.png'
49
IMAGE_PATH_MASK = './app/static/img/dummy_600_mask.png'
root's avatar
root committed
50
STATIC_PATH = './app/static/embody_drawings/'
51
52
53

# Interpolation methods
METHODS = ['none','bilinear', 'bicubic', 'gaussian']
54

55
56
57
58
59
# SELECT methods
SELECT_ALL = ("SELECT coordinates from embody_answer")
SELECT_BY_EXP_ID = 'select coordinates from embody_answer as em JOIN (SELECT idanswer_set FROM answer_set as a JOIN experiment as e ON a.experiment_idexperiment=e.idexperiment AND e.idexperiment=%s) as ida ON em.answer_set_idanswer_set=ida.idanswer_set'
SELECT_BY_ANSWER_SET = 'select coordinates from embody_answer WHERE answer_set_idanswer_set=%s'
SELECT_BY_PAGE = 'select coordinates from embody_answer WHERE page_idpage=%s'
Ossi Laine's avatar
Ossi Laine committed
60
SELECT_BY_PAGE_AND_PICTURE = 'select coordinates from embody_answer where page_idpage=%s and embody_question_idembody=%s'
61
62
63
64
65
66
67
68
69

# Get date
now = datetime.datetime.now()
DATE_STRING = now.strftime("%Y-%m-%d")


class MyDB(object):

    def __init__(self):
root's avatar
root committed
70
71
72
73
74
        self._db_connection = mariadb.connect(
		user = Config.MYSQL_USER, 
		password = Config.MYSQL_PASSWORD, 
		database = Config.MYSQL_DB
	)
75
76
77
78
79
80
81
82
        self._db_cur = self._db_connection.cursor()

    def query(self, query, params):
        return self._db_cur.execute(query, params)

    def __del__(self):
        self._db_connection.close()

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

def matlab_style_gauss2D(shape=(1,1),sigma=5):
    """2D gaussian mask - should give the same result as MATLAB's
    fspecial('gaussian',[shape],[sigma])"""

    m,n = [(ss-1.)/2. for ss in shape]
    y,x = np.ogrid[-m:m+1,-n:n+1]
    h = np.exp( -(x*x + y*y) / (2.*sigma*sigma) )
    h[ h < np.finfo(h.dtype).eps*h.max() ] = 0
    sumh = h.sum()
    if sumh != 0:
        h /= sumh
    return h


98
99
def map_coordinates(a,b,c=None):
    return [a,b,c]
100
101


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def timeit(method):
    def timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time()

        if 'log_time' in kw:
            name = kw.get('log_name', method.__name__.upper())
            kw['log_time'][name] = int((te - ts) * 1000)
        else:
            print('%r  %2.2f ms' % \
                  (method.__name__, (te - ts) * 1000))
        return result

    return timed

root's avatar
root committed
118
import sys
119
120

@timeit
Ossi Laine's avatar
Ossi Laine committed
121
def get_coordinates(idpage, idembody=None, select_clause=SELECT_BY_PAGE_AND_PICTURE):
122
123
124
125
    """Select all drawn points from certain stimulus and plot them onto 
    the human body"""

    db = MyDB()
Ossi Laine's avatar
Ossi Laine committed
126
    db.query(select_clause, (idpage,idembody))
127
128
129
130

    # Get coordinates
    coordinates = format_coordinates(db._db_cur)

Ossi Laine's avatar
Ossi Laine committed
131
132
133
134
135
136
    if idembody:
        # Get image path
        image_query = db.query('SELECT picture from embody_question where idembody=%s', (idembody,))
        image_path = db._db_cur.fetchone()[0]
        image_path = './app' + image_path

root's avatar
root committed
137

Ossi Laine's avatar
Ossi Laine committed
138
139
140
141
142
        # Draw image
        plt = plot_coordinates(coordinates, image_path)
    else:
        plt = plot_coordinates(coordinates, DEFAULT_IMAGE_PATH)

143
144

    # Save image to ./app/static/ 
Ossi Laine's avatar
Ossi Laine committed
145
    img_filename = 'PAGE-' + str(idpage) + '-' + DATE_STRING + '.png'
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    plt.savefig(STATIC_PATH + img_filename)

    # Return image path to function caller
    return img_filename


def format_coordinates(cursor):
    # Init coordinate arrays and radius of point
    x=[]
    y=[]
    r=[]
    standard_radius=13

    # Loop through all of the saved coordinates and push them to coordinates arrays
    for coordinate in cursor:
Ossi Laine's avatar
Ossi Laine committed
161

162
163
164
165
166
167
168
169
170
        try:
            coordinates = json.loads(coordinate[0])
            x.extend(coordinates['x'])
            y.extend(coordinates['y'])
            r.extend(coordinates['r'])
        except KeyError:
            standard_radiuses = np.full((1, len(coordinates['x'])), standard_radius).tolist()[0]
            r.extend(standard_radiuses)
            continue
root's avatar
root committed
171
172
173
        except ValueError as err:
            app.logger.info(err)
            continue
174
175
176
177
178
179
180

    return {
        "x":x,
        "y":y,
        "coordinates":list(map(map_coordinates, x,y,r))
    }

181

Ossi Laine's avatar
Ossi Laine committed
182
def plot_coordinates(coordinates, image_path=DEFAULT_IMAGE_PATH):
183

184
185
186
    # Total amount of points
    points_count = len(coordinates['coordinates']) 

187
    # Load image to a plot
Ossi Laine's avatar
Ossi Laine committed
188
189
    image = mpimg.imread(image_path)
    image_data = image.shape
190
191
192
193
194
195
196

    # Init plots
    fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2)


    # Draw circles from coordinates (imshow don't need interpolation)
    # TODO: set sigma according to brush size!
197
    ax2.set_title("gaussian disk around points / raw image")
Ossi Laine's avatar
Ossi Laine committed
198
199
200

    # set height/width from image
    frame = np.zeros((image_data[0] + 10,image_data[1] + 10))
201

Ossi Laine's avatar
Ossi Laine committed
202
    if image_path == DEFAULT_IMAGE_PATH:
root's avatar
root committed
203
        #app.logger.info(coordinates)
204

Ossi Laine's avatar
Ossi Laine committed
205
        for idx, point in enumerate(coordinates["coordinates"]):
root's avatar
root committed
206
207
208
209
210
211

            try:
            	frame[int(point[1]), int(point[0])] = 1
            except IndexError as err:
            	app.logger.info(err)

Ossi Laine's avatar
Ossi Laine committed
212
213
214
215
216
217
            point = ndimage.gaussian_filter(frame, sigma=5)
            ax2.imshow(point, cmap='hot', interpolation='none')

            # Try to send progress information to socket.io
            try:
                emit('progress', {'done':idx+1/points_count, 'from':points_count})
218
                socketio.sleep(0)
Ossi Laine's avatar
Ossi Laine committed
219
            except RuntimeError as err:
220
                print(err)
Ossi Laine's avatar
Ossi Laine committed
221
                continue
222

Ossi Laine's avatar
Ossi Laine committed
223
224
        image_mask = mpimg.imread(IMAGE_PATH_MASK)
        ax2.imshow(image_mask)
root's avatar
root committed
225

Ossi Laine's avatar
Ossi Laine committed
226
227
228
229
230
    else:
        # TODO: gaussian disk appearing only on empty spaces in the pictures
        # -> at the moment this implementation works only for the default image
        # with pre-created image mask (IMAGE_PATH_MASK)
        ax2.imshow(image)
231

232
233
234
235
236
    # Plot coordinates as points
    ax1.set_title("raw points")
    ax1.plot(coordinates["x"],coordinates["y"], 'ro', alpha=0.2)
    ax1.imshow(image, alpha=0.6)

root's avatar
root committed
237
238
    app.logger.info("iamge plotted")

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
    # return figure for saving/etc...
    return fig

    '''
    # Return image as bytes 
    fig = plt.gcf()
    imgdata = io.BytesIO()
    fig.savefig(imgdata, format='png')
    imgdata.seek(0)  # rewind the data
    return imgdata.read()

    #Show image
    mng = plt.get_current_fig_manager()
    mng.resize(*mng.window.maxsize())
    plt.show()
    '''

if __name__=='__main__':
    
    arg_parser = argparse.ArgumentParser(description='Draw bodily maps of emotions')
    arg_parser.add_argument('-s','--stimulus', help='Select drawn points from certain stimulus', required=False, action='store_true')
    arg_parser.add_argument('-e','--experiment', help='Select drawn points from certain experiment', required=False, action='store_true')
    arg_parser.add_argument('-a','--answer-set', help='Select drawn points from certain answer_set', required=False, action='store_true')
    arg_parser.add_argument('integers', metavar='N', type=int, nargs='+', help='an integer for the accumulator')
    args = vars(arg_parser.parse_args())
    value = args['integers'][0]

    if args['stimulus']:
Ossi Laine's avatar
Ossi Laine committed
267
        get_coordinates(value, None, SELECT_BY_PAGE)
268
    elif args['experiment']:
Ossi Laine's avatar
Ossi Laine committed
269
        get_coordinates(value, None, SELECT_BY_EXP_ID)
270
    elif args['answer_set']:
Ossi Laine's avatar
Ossi Laine committed
271
        get_coordinates(value, None, SELECT_BY_EXP_ID)
272
273
274
    else:
        print("No arguments given. Exit.")
        sys.exit(0)
275
276
277