cm107 commited on
Commit
c9a11d0
1 Parent(s): 76b26cd

Added the /show/shapes page as a demonstration for how work with images.

Browse files
app.py CHANGED
@@ -1,6 +1,8 @@
1
  import flask
2
  import os
3
  import mypkg
 
 
4
 
5
  app = flask.Flask(__name__, template_folder="./templates/")
6
 
@@ -30,6 +32,71 @@ def evaluate(expression: str):
30
 
31
  return flask.render_template('evaluate-expression.html', expression=expression, result=result)
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  if __name__ == '__main__':
35
  app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
 
1
  import flask
2
  import os
3
  import mypkg
4
+ from mypkg import draw
5
+ from mypkg.convert import Convert
6
 
7
  app = flask.Flask(__name__, template_folder="./templates/")
8
 
 
32
 
33
  return flask.render_template('evaluate-expression.html', expression=expression, result=result)
34
 
35
+ @app.route('/show/circle')
36
+ def show_circle():
37
+ img = draw.circle(width=500, height=500)
38
+ image = Convert.cv_to_base64(img)
39
+ return flask.render_template('show-image.html', image=image, imageLabel='Circle')
40
+
41
+ @app.route('/show/shapes')
42
+ def show_shapes():
43
+ return flask.render_template('show-shapes.html')
44
+
45
+ @app.route('/draw', methods=['GET'])
46
+ def draw_target():
47
+ """
48
+ Try testing with:
49
+ curl -X GET "localhost:5000/draw?target=blank"
50
+ curl -X GET "localhost:5000/draw?target=circle"
51
+ curl -X GET "localhost:5000/draw?target=rectangle"
52
+ """
53
+ # TODO: Call this from javascript for toggling between images.
54
+ target = flask.request.args.get('target', default='blank', type=str)
55
+ width = flask.request.args.get('width', default=500, type=int)
56
+ height = flask.request.args.get('height', default=500, type=int)
57
+
58
+ if target == 'blank':
59
+ img = draw.blank(width=width, height=height)
60
+ elif target == 'circle':
61
+ img = draw.circle(width=width, height=height)
62
+ elif target == 'rectangle':
63
+ img = draw.rectangle(width=width, height=height)
64
+ else:
65
+ raise ValueError(f'Unsupported target: {target}')
66
+
67
+ image = Convert.cv_to_base64(img)
68
+ return flask.jsonify(
69
+ isError=False,
70
+ message='Success',
71
+ statusCode=200,
72
+ data=image
73
+ )
74
+
75
+ @app.route('/echo', methods=['GET', 'POST'])
76
+ def echo():
77
+ """
78
+ Try testing with:
79
+ curl -X GET "localhost:5000/echo?a=hello&b=world"
80
+ curl -X POST "localhost:5000/echo?a=hello&b=world"
81
+ """
82
+ msg = 'Echo:'
83
+ for key, val in flask.request.args.to_dict().items():
84
+ msg += f'\n\t{key}={val}'
85
+ if flask.request.method == 'GET':
86
+ return flask.jsonify(
87
+ isError=False,
88
+ message='Success',
89
+ statusCode=200,
90
+ data=msg
91
+ )
92
+ else:
93
+ print(msg)
94
+ return flask.jsonify(
95
+ isError=False,
96
+ message='Success',
97
+ statusCode=200,
98
+ # data=msg
99
+ )
100
 
101
  if __name__ == '__main__':
102
  app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
mypkg/convert.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import cv2
3
+ from PIL import Image as pilImage
4
+ import numpy as np
5
+ import numpy.typing as npt
6
+ from base64 import b64encode
7
+
8
+ __all__ = ["Convert"]
9
+
10
+ class Convert:
11
+ @staticmethod
12
+ def cv_to_pil(img: np.ndarray) -> pilImage.Image:
13
+ result = img.copy()
14
+ if result.ndim == 2: # grayscale
15
+ pass
16
+ else:
17
+ assert result.ndim == 3
18
+ if result.shape[2] == 3: # rgb
19
+ result = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
20
+ elif result.shape[2] == 4: # rgba
21
+ result = cv2.cvtColor(result, cv2.COLOR_RGBA2BGRA)
22
+ else:
23
+ raise ValueError
24
+ return pilImage.fromarray(result)
25
+
26
+ @staticmethod
27
+ def pil_to_cv(img: pilImage.Image | npt.NDArray[np.uint8]) -> np.ndarray:
28
+ if type(img) is pilImage.Image:
29
+ result = np.array(img, dtype=np.uint8)
30
+ elif type(img) is np.ndarray:
31
+ pass
32
+ else:
33
+ raise TypeError
34
+
35
+ if result.ndim == 2: # grayscale
36
+ pass
37
+ else:
38
+ assert result.ndim == 3
39
+ if result.shape[2] == 3: # rgb
40
+ result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
41
+ elif result.shape[2] == 4: # rgba
42
+ result = cv2.cvtColor(result, cv2.COLOR_RGBA2BGRA)
43
+ else:
44
+ raise ValueError
45
+ return result
46
+
47
+ @staticmethod
48
+ def cv_to_base64(img: np.ndarray) -> str:
49
+ return Convert.pil_to_base64(Convert.cv_to_pil(img))
50
+
51
+ @staticmethod
52
+ def pil_to_base64(img: pilImage.Image | npt.NDArray[np.uint8]) -> str:
53
+ if type(img) is pilImage.Image:
54
+ pass
55
+ elif type(img) is np.ndarray:
56
+ img = pilImage.fromarray(img.astype('uint8'))
57
+ else:
58
+ raise TypeError
59
+
60
+ # https://stackoverflow.com/a/59583262/13797085
61
+ file_object = io.BytesIO()
62
+ img.save(file_object, 'PNG')
63
+ base64img = "data:image/png;base64," \
64
+ + b64encode(file_object.getvalue()).decode('ascii')
65
+ return base64img
mypkg/draw.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import numpy.typing as npt
4
+
5
+ def blank(
6
+ width: int=500, height: int=500,
7
+ background: tuple[int, int, int]=(0,0,0)
8
+ ) -> npt.NDArray[np.uint8]:
9
+ img = np.zeros((height, width, 3), dtype=np.uint8)
10
+ img[:, :, :] = background
11
+ return img
12
+
13
+ def circle(
14
+ width: int=500, height: int=500,
15
+ radius: int=None,
16
+ color: tuple[int, int, int]=(0,0,255),
17
+ background: tuple[int, int, int]=(0,0,0)
18
+ ) -> npt.NDArray[np.uint8]:
19
+ if radius is None:
20
+ radius = int(0.5 * max(width, height) * 0.8)
21
+ center = (int(width/2), int(height/2))
22
+ img = blank(
23
+ width=width, height=height,
24
+ background=background
25
+ )
26
+ return cv2.circle(img, center, radius, color, -1, cv2.LINE_AA)
27
+
28
+ def rectangle(
29
+ width: int=500, height: int=500,
30
+ pt1: tuple[int, int]=None, pt2: tuple[int, int]=None,
31
+ color: tuple[int, int, int]=(0,0,255),
32
+ background: tuple[int, int, int]=(0,0,0)
33
+ ) -> npt.NDArray[np.uint8]:
34
+ img = blank(
35
+ width=width, height=height,
36
+ background=background
37
+ )
38
+ if pt1 is None:
39
+ pt1 = (int(0.2 * width), int(0.2 * height))
40
+ if pt2 is None:
41
+ pt2 = (int(0.8 * width), int(0.8 * height))
42
+ return cv2.rectangle(img, pt1, pt2, color, -1, cv2.LINE_AA)
static/css/show-shapes.css ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #imgPlaceholder {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ width: 500px;
6
+ }
7
+
8
+ #imgNavBtns {
9
+ display: flex;
10
+ justify-content: center;
11
+ }
12
+
13
+ .imgNavBtn+.imgNavBtn {
14
+ margin-left: 10px;
15
+ }
16
+
17
+ .imgNavBtn {
18
+ margin-top: 15px;
19
+ background-color: black;
20
+ border: none;
21
+ color: red;
22
+ padding: 10px 20px;
23
+ text-align: center;
24
+ text-decoration: none;
25
+ display: inline-block;
26
+ font-size: 32px;
27
+ cursor: pointer;
28
+ }
29
+
30
+ #drop_zone {
31
+ border: 5px solid blue;
32
+ /* width: 200px;
33
+ height: 100px; */
34
+ width: fit-content;
35
+ height: fit-content;
36
+ }
37
+
38
+ #droppedImages {
39
+ margin-top: 15px;
40
+ padding: 10px 20px;
41
+ display: block;
42
+ justify-content: center;
43
+ position: relative;
44
+ }
45
+
46
+ #droppedImages img {
47
+ display: inline-block;
48
+ width: 100px;
49
+ }
static/js/show-shapes.js ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ImageNavigator {
2
+ constructor() {
3
+ this.targets = [
4
+ 'blank',
5
+ 'circle',
6
+ 'rectangle'
7
+ ];
8
+ this.sources = [];
9
+ this.idx = 0;
10
+ this.imgPlaceholder = document.getElementById('imgPlaceholder');
11
+ // this.imgData = null;
12
+ this.prevBtn = document.getElementById('prevBtn');
13
+ this.nextBtn = document.getElementById('nextBtn');
14
+ }
15
+
16
+ async getImgData(target) {
17
+ try {
18
+ let res = await fetch(
19
+ '../draw?target=' + target,
20
+ {
21
+ method: 'GET'
22
+ }
23
+ );
24
+ return await res.json();
25
+ } catch (error) {
26
+ console.log(error);
27
+ }
28
+ }
29
+
30
+ async fetchImgData(target) {
31
+ let imgDataObj = await this.getImgData(target);
32
+ if (imgDataObj != null) {
33
+ // this.imgData = imgDataObj.data;
34
+ // this.imgPlaceholder.innerHTML = '<img src=' + this.imgData + '>';
35
+ return imgDataObj.data;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ toggleImage(change) {
41
+ this.idx += change;
42
+ if (this.idx < 0)
43
+ this.idx += this.sources.length;
44
+ else
45
+ this.idx %= this.sources.length;
46
+ this.imgPlaceholder.src = this.sources[this.idx];
47
+ }
48
+
49
+ toPrevImage() {
50
+ this.toggleImage(-1);
51
+ }
52
+
53
+ toNextImage() {
54
+ this.toggleImage(1);
55
+ }
56
+
57
+ async initialize() {
58
+ // this.targets.forEach(
59
+ // target => {
60
+ // let targetSrc = await this.fetchImgData(target);
61
+ // this.sources.push(targetSrc);
62
+ // }
63
+ // )
64
+ for (var i = 0; i < this.targets.length; i++) {
65
+ var target = this.targets[i];
66
+ let targetSrc = await this.fetchImgData(target);
67
+ this.sources.push(targetSrc);
68
+ }
69
+ // this.imgPlaceholder.src = this.sources[this.idx];
70
+ this.toggleImage(0);
71
+
72
+ this.prevBtn.addEventListener('mouseup', e => this.toPrevImage());
73
+ this.nextBtn.addEventListener('mouseup', e => this.toNextImage());
74
+ }
75
+ }
76
+
77
+ var imageNav = new ImageNavigator();
78
+ imageNav.initialize();
79
+
80
+
81
+ class DropHandler {
82
+ constructor() {
83
+ this.dropZone = document.getElementById('drop_zone');
84
+ this.droppedImages = document.getElementById('droppedImages');
85
+ }
86
+
87
+ validateImage(image) {
88
+ var validTypes = ['image/jpeg', 'image/png', 'image/gif'];
89
+ if (validTypes.indexOf(image.type) == -1) {
90
+ console.log('Invalid File Type: ' + image.type);
91
+ return false;
92
+ }
93
+
94
+ var maxSizeInBytes = 10e6; //10MB
95
+ if (image.size > maxSizeInBytes) {
96
+ console.log('File is too large: ' + image.size);
97
+ return false
98
+ }
99
+
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Refer to https://soshace.com/the-ultimate-guide-to-drag-and-drop-image-uploading-with-pure-javascript/
105
+ * @param {object} event
106
+ */
107
+ processDrop(event) {
108
+ console.log('File(s) dropped');
109
+
110
+ // Prevent default behavior (Prevent file from being opened)
111
+ event.preventDefault();
112
+
113
+ var dt = event.dataTransfer;
114
+ var files = dt.files;
115
+ if (files.length > 0) {
116
+ for (var i = 0; i < files.length; i++) {
117
+ if (this.validateImage(files[i])) {
118
+ var img = document.createElement('img');
119
+ var reader = new FileReader();
120
+ reader.onload = function (e) {
121
+ img.src = e.target.result;
122
+ imageNav.sources.push(e.target.result);
123
+ }
124
+ reader.readAsDataURL(files[i]);
125
+ this.droppedImages.appendChild(img);
126
+ }
127
+ }
128
+ }
129
+ else
130
+ console.log('Dropped ' + files.length + ' files.');
131
+ }
132
+
133
+ processDragOver(event) {
134
+ console.log('File(s) in drop zone');
135
+
136
+ // Prevent default behavior (Prevent file from being opened)
137
+ event.preventDefault();
138
+ }
139
+
140
+ initialize() {
141
+ this.dropZone.addEventListener('drop', e => this.processDrop(e));
142
+ this.dropZone.addEventListener('dragover', e => this.processDragOver(e));
143
+ }
144
+ }
145
+
146
+ var dropHandler = new DropHandler();
147
+ dropHandler.initialize();
templates/index.html CHANGED
@@ -70,6 +70,9 @@
70
  </li>
71
  </ul>
72
  </li>
 
 
 
73
  </ul>
74
 
75
  <script type="text/javascript" src="../static/js/test_page.js"></script>
 
70
  </li>
71
  </ul>
72
  </li>
73
+ <li>
74
+ <a href="/show/shapes">Draw some shapes using python's cv2 and display in html page.</a>
75
+ </li>
76
  </ul>
77
 
78
  <script type="text/javascript" src="../static/js/test_page.js"></script>
templates/show-image.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width" />
7
+ <title>Show {{ imageLabel }}</title>
8
+ <link rel="stylesheet" href="../static/css/main.css" />
9
+ </head>
10
+
11
+ <body>
12
+ <h1>Showing {{ imageLabel }}</h1>
13
+ <img src="{{ image }}">
14
+
15
+ <h1>Navigation</h1>
16
+ <li>
17
+ <a href="../..">Home</a>
18
+ </li>
19
+ </body>
20
+
21
+ </html>
templates/show-shapes.html ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width" />
7
+ <title>Show Shapes</title>
8
+ <link rel="stylesheet" href="../static/css/main.css" />
9
+ <link rel="stylesheet" href="../static/css/show-shapes.css" />
10
+ </head>
11
+
12
+ <body>
13
+ <h1>Showing Shapes</h1>
14
+ <img id="imgPlaceholder"
15
+ src="https://img.freepik.com/premium-vector/update-concept-application-loading-process-symbol-web-screen-vector-illustration-flat_186332-1253.jpg?w=500">
16
+ <div id="imgNavBtns">
17
+ <button id="prevBtn" class="imgNavBtn">Previous</button>
18
+ <button id="nextBtn" class="imgNavBtn">Next</button>
19
+ </div>
20
+
21
+ <h1>Drag & Drop Testing</h1>
22
+ <div id="drop_zone">
23
+ <p>
24
+ Drag one or more images into this <i>drop zone</i>.
25
+ They will be added to the image navigator.
26
+ </p>
27
+ <div id="droppedImages">
28
+ <!-- <img
29
+ src="https://img.freepik.com/premium-vector/update-concept-application-loading-process-symbol-web-screen-vector-illustration-flat_186332-1253.jpg?w=500">
30
+ <img
31
+ src="https://img.freepik.com/premium-vector/update-concept-application-loading-process-symbol-web-screen-vector-illustration-flat_186332-1253.jpg?w=500"> -->
32
+ </div>
33
+ </div>
34
+
35
+ <h1>Navigation</h1>
36
+ <li>
37
+ <a href="../..">Home</a>
38
+ </li>
39
+
40
+ <script type="text/javascript" src="../static/js/show-shapes.js"></script>
41
+ </body>
42
+
43
+ </html>