Can Günen commited on
Commit
64dc6be
·
1 Parent(s): 2ae1ac2

changed the variables names and fixed some functions

Browse files
Files changed (2) hide show
  1. app.py +37 -42
  2. distortion.py +69 -86
app.py CHANGED
@@ -1,10 +1,8 @@
1
- import cv2
2
  import gradio as gr
3
- import numpy as np
4
- from distortion import generate_matrix, get_select_coords, correct_image, track, allign_tab
5
 
6
 
7
- SHARED_UI_WARNING = f'''##### Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tempus dignissim odio, at elementum erat vulputate sit amet. Vestibulum sodales viverra fermentum. In ac hendrerit dolor, vitae mattis odio. Maecenas suscipit consectetur suscipit. Curabitur sodales dui eget neque venenatis tincidunt. In sed libero mi. Nam sollicitudin metus urna, sit amet sagittis ex laoreet sed.
8
 
9
  Pellentesque nunc turpis, porta ut accumsan eget, iaculis nec odio. Praesent fringilla a sem sed elementum. Proin orci justo, rutrum et feugiat eleifend, auctor sed odio. Maecenas posuere urna tortor, ut euismod ligula mattis sed. Sed ipsum velit, pretium sed lacinia sed, placerat eget urna. Mauris lobortis mi vitae odio luctus viverra sed eget urna. Pellentesque blandit semper felis sed congue. Aenean congue enim id euismod finibus.
10
 
@@ -13,16 +11,17 @@ Quisque vitae lacus ac nunc hendrerit consequat. Donec rhoncus ultrices erat, vi
13
  '''
14
 
15
  with gr.Blocks() as demo:
16
- title = """<p><h1 align="center" style="font-size: 36px;">Auto Allign Quadrilateral Workpiece</h1></p>"""
17
  gr.HTML(title)
18
- with gr.Tab("Allign"):
19
-
 
20
  with gr.Row():
21
  with gr.Column():
22
- image_input = gr.Image(label="Unmasked Image", type="filepath")
23
- image_output = gr.Image(label="Masked Image", type="filepath")
24
- file_input = gr.File(label="Select O-Matrix", type="file")
25
- third_image = gr.Image(label="Detected Shape")
26
 
27
  with gr.Column():
28
  first_slider = gr.Slider(0,255, value=0, label="1. Slider")
@@ -31,54 +30,50 @@ with gr.Blocks() as demo:
31
  forth_slider = gr.Slider(0,255, value=255,label="4. Slider")
32
  fifth_slider = gr.Slider(0,255, value=255,label="5. Slider")
33
  sixth_slider = gr.Slider(0,255, value=255,label="6. Slider")
34
- current_values = gr.Textbox("Current vallues of sliders")
35
- download_dxf = gr.Button("Generate DXF File")
36
- last_output = gr.File(label="Generated dxf file", type="file")
37
 
38
- download_dxf.click(fn=allign_tab, inputs=[image_output, file_input], outputs=[last_output, third_image])
39
 
40
-
41
- first_slider.change(track, inputs=[image_input, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[image_output, current_values])
42
- second_slider.change(track, inputs=[image_input, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[image_output, current_values])
43
- third_slider.change(track, inputs=[image_input, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[image_output, current_values])
44
- forth_slider.change(track, inputs=[image_input, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[image_output, current_values])
45
- fifth_slider.change(track, inputs=[image_input, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[image_output, current_values])
46
- sixth_slider.change(track, inputs=[image_input, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[image_output, current_values])
47
 
48
 
49
- with gr.Tab("Pick Corners"):
50
  with gr.Row():
51
  with gr.Column():
52
  title = """<p><h1 align="center" style="font-size: 24px;">First, let's correct the distorted Image</h1></p>"""
53
  gr.HTML(title)
54
- distorted_input = gr.Image(label="Select distorted Image", type="filepath")
55
- yml_input = gr.File(label="Select O-Matrix (.yml file)", type="file")
56
- correct_button = gr.Button("Correct the Image")
57
 
58
  with gr.Column():
59
- title = """<p><h1 align="center" style="font-size: 24px;">Then, let's mark the corners of the Workpiece</h1></p>"""
60
  gr.HTML(title)
61
- corrected_img = gr.Image(label="Corrected Image")
62
  dxf_output = gr.File(type="file")
63
 
64
- corrected_img.select(get_select_coords, [corrected_img], dxf_output)
65
- correct_button.click(fn=correct_image, inputs=[distorted_input, yml_input], outputs=corrected_img)
66
 
67
- with gr.Tab("O-Matrix"):
68
  with gr.Row():
69
  with gr.Column():
70
- distorted_image = gr.Image(label="Selected Image", type="filepath")
71
-
72
  with gr.Row():
73
- chess_vert_input = gr.Textbox(label="Enter the number of squares in vertical direction")
74
- chess_horz_input = gr.Textbox(label="Enter the number of squares in horizontal direction")
75
- download_dxf = gr.Button("Generate the YAML File")
76
- error_box = gr.Textbox(label="Error", visible=False)
77
-
78
- yaml_file = gr.File()
79
- download_dxf.click(generate_matrix, inputs=[distorted_image, chess_vert_input, chess_horz_input], outputs= yaml_file)
80
  with gr.Column():
81
- gr.Markdown(SHARED_UI_WARNING)
82
-
83
 
84
  demo.launch(debug=True)
 
 
1
  import gradio as gr
2
+ from distortion import generate_matrix, corner_tab, correct_image, slider, color_tab
 
3
 
4
 
5
+ CHESSBOARD_TUTORIAL = '''##### Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tempus dignissim odio, at elementum erat vulputate sit amet. Vestibulum sodales viverra fermentum. In ac hendrerit dolor, vitae mattis odio. Maecenas suscipit consectetur suscipit. Curabitur sodales dui eget neque venenatis tincidunt. In sed libero mi. Nam sollicitudin metus urna, sit amet sagittis ex laoreet sed.
6
 
7
  Pellentesque nunc turpis, porta ut accumsan eget, iaculis nec odio. Praesent fringilla a sem sed elementum. Proin orci justo, rutrum et feugiat eleifend, auctor sed odio. Maecenas posuere urna tortor, ut euismod ligula mattis sed. Sed ipsum velit, pretium sed lacinia sed, placerat eget urna. Mauris lobortis mi vitae odio luctus viverra sed eget urna. Pellentesque blandit semper felis sed congue. Aenean congue enim id euismod finibus.
8
 
 
11
  '''
12
 
13
  with gr.Blocks() as demo:
14
+ title = """<p><h1 align="center" style="font-size: 36px;">Auto Align Quadrilateral Workpieces</h1></p>"""
15
  gr.HTML(title)
16
+ with gr.Tab("Color Alignment"):
17
+ title = """<p><h1 align="center" style="font-size: 24px;">We will use this tab to align the workpieces after masking wit slider inputs</h1></p>"""
18
+ gr.HTML(title)
19
  with gr.Row():
20
  with gr.Column():
21
+ unmasked_distorted_image = gr.Image(label="Unmasked + Distorted Image", type="filepath")
22
+ masked_distorted_image = gr.Image(label="Masked + Distorted Image", type="filepath")
23
+ camera_matrix = gr.File(label="Camera Matrix", type="file")
24
+ detected_shape = gr.Image(label="Detected Shape")
25
 
26
  with gr.Column():
27
  first_slider = gr.Slider(0,255, value=0, label="1. Slider")
 
30
  forth_slider = gr.Slider(0,255, value=255,label="4. Slider")
31
  fifth_slider = gr.Slider(0,255, value=255,label="5. Slider")
32
  sixth_slider = gr.Slider(0,255, value=255,label="6. Slider")
33
+ generate_dxf = gr.Button("Generate DXF File")
34
+ dxf_file = gr.File(label="Generated DXF file", type="file")
 
35
 
36
+ generate_dxf.click(fn=color_tab, inputs=[masked_distorted_image, camera_matrix], outputs=[dxf_file, detected_shape])
37
 
38
+ first_slider.change(slider, inputs=[unmasked_distorted_image, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[masked_distorted_image])
39
+ second_slider.change(slider, inputs=[unmasked_distorted_image, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[masked_distorted_image])
40
+ third_slider.change(slider, inputs=[unmasked_distorted_image, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[masked_distorted_image])
41
+ forth_slider.change(slider, inputs=[unmasked_distorted_image, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[masked_distorted_image])
42
+ fifth_slider.change(slider, inputs=[unmasked_distorted_image, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[masked_distorted_image])
43
+ sixth_slider.change(slider, inputs=[unmasked_distorted_image, first_slider, second_slider, third_slider, forth_slider, fifth_slider, sixth_slider], outputs=[masked_distorted_image])
 
44
 
45
 
46
+ with gr.Tab("Corner Allignment"):
47
  with gr.Row():
48
  with gr.Column():
49
  title = """<p><h1 align="center" style="font-size: 24px;">First, let's correct the distorted Image</h1></p>"""
50
  gr.HTML(title)
51
+ distorted_image = gr.Image(label="Distorted Image", type="filepath")
52
+ yml_input = gr.File(label="Camera Matrix", type="file")
53
+ correct_button = gr.Button("Correct Image")
54
 
55
  with gr.Column():
56
+ title = """<p><h1 align="center" style="font-size: 24px;">Then mark the corners of the Workpiece</h1></p>"""
57
  gr.HTML(title)
58
+ corrected_image = gr.Image(label="Corrected Image")
59
  dxf_output = gr.File(type="file")
60
 
61
+ corrected_image.select(corner_tab, [corrected_image], dxf_output)
62
+ correct_button.click(fn=correct_image, inputs=[distorted_image, yml_input], outputs=corrected_image)
63
 
64
+ with gr.Tab("Matrix Generation"):
65
  with gr.Row():
66
  with gr.Column():
67
+ distorted_image = gr.Image(label="Chessboard Image", type="filepath")
 
68
  with gr.Row():
69
+ chess_vert_input = gr.Textbox(label="Number of squares in vertical direction")
70
+ chess_horz_input = gr.Textbox(label="Number of squares in horizontal direction")
71
+
72
+ yaml_button = gr.Button("Generate the YML File")
73
+ yaml_file = gr.File(label="YML File")
74
+ yaml_button.click(generate_matrix, inputs=[distorted_image, chess_vert_input, chess_horz_input], outputs= yaml_file)
75
+
76
  with gr.Column():
77
+ gr.Markdown(CHESSBOARD_TUTORIAL)
 
78
 
79
  demo.launch(debug=True)
distortion.py CHANGED
@@ -6,48 +6,89 @@ from pathlib import Path
6
 
7
  coordis = []
8
 
9
- def save_coefficients(mtx, dist, path):
10
  """Save camera matrix and distortion coefficients to file."""
11
  cv_file = cv2.FileStorage(path, cv2.FILE_STORAGE_WRITE)
12
  cv_file.write('K', mtx)
13
  cv_file.write('D', dist)
14
  cv_file.release()
15
-
16
-
17
- def load_coefficients_yaml(path):
18
  """Load camera matrix and distortion coefficients from file."""
19
- cv_file = cv2.FileStorage(path.name, cv2.FILE_STORAGE_READ)
20
  camera_matrix = cv_file.getNode('K').mat()
21
  dist_matrix = cv_file.getNode('D').mat()
22
  cv_file.release()
23
  return [camera_matrix, dist_matrix]
24
 
25
-
26
  def correct_image(image, yaml):
27
  image = cv2.imread(image)
28
- mtx, dist = load_coefficients_yaml(yaml)
29
- dst = cv2.undistort(image, mtx, dist, None, None)
30
  return dst
31
 
32
 
 
33
 
34
- def load_coefficients(path):
35
- """Load camera matrix and distortion coefficients from file."""
36
- cv_file = cv2.FileStorage(path, cv2.FILE_STORAGE_READ)
37
- camera_matrix = cv_file.getNode('K').mat()
38
- dist_matrix = cv_file.getNode('D').mat()
39
- cv_file.release()
40
- return [camera_matrix, dist_matrix]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- def get_select_coords(img, evt: gr.SelectData):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  row, col = evt.index
45
  coordis.append([row, col])
46
 
47
  if len(coordis) == 4 :
48
  coordinates = np.array(coordis)
49
- print("shape of second array:", coordinates.shape)
50
- print(coordinates)
51
  dwg = ezdxf.new("R2010")
52
  msp = dwg.modelspace()
53
  dwg.layers.new(name="greeny green lines", dxfattribs={"color": 3})
@@ -57,7 +98,13 @@ def get_select_coords(img, evt: gr.SelectData):
57
  msp.add_line((coordinates[2][0], -coordinates[2][1]), (coordinates[3][0], -coordinates[3][1]))
58
  msp.add_line((coordinates[3][0], -coordinates[3][1]), (coordinates[0][0], -coordinates[0][1]))
59
 
60
- dwg_file = dwg.saveas("output.dxf")
 
 
 
 
 
 
61
  coordis.clear()
62
 
63
  return "output.dxf"
@@ -70,7 +117,6 @@ def generate_matrix(filename, board_vert, board_horz):
70
 
71
  # Define the checkerboard pattern size and criteria for corner detection
72
  CHECKERBOARD = (int(board_vert)-1, int(board_horz)-1)
73
- #CHECKERBOARD = (9, 7)
74
  criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
75
 
76
  # Initialize object points and image points arrays
@@ -101,19 +147,17 @@ def generate_matrix(filename, board_vert, board_horz):
101
  try:
102
  ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(object_points, image_points, gray.shape[::-1], None, None)
103
  # Save camera matrix and distortion coefficients to file
104
- save_coefficients(mtx, dist, f"{filename_stem}.yml")
105
  return f"{filename_stem}.yml"
106
  except:
107
  print("Please check the Chessboard Dimensions")
108
 
109
 
110
- def track(img, h1, s1, v1, h2, s2, v2):
111
  # Load the image
112
  img = cv2.imread(img)
113
 
114
-
115
  while(1):
116
- #hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
117
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
118
 
119
  # Create the NumPy arrays
@@ -124,70 +168,9 @@ def track(img, h1, s1, v1, h2, s2, v2):
124
  lower_red = np.array([int(x) for x in lower_red])
125
  upper_red = np.array([int(x) for x in upper_red])
126
 
127
- total = np.array([h1, s1, v1, h2, s2, v2])
128
 
129
  mask = cv2.inRange(img, lower_red, upper_red)
130
  res = cv2.bitwise_and(img,img, mask= mask)
131
- #hsv = cv2.cvtColor(res, cv2.COLOR_BGR2HSV)
132
-
133
- return res, total
134
-
135
- def allign_tab(file_path, yaml):
136
-
137
-
138
- coordinates = []
139
- # Load the masked image
140
- img = cv2.imread(file_path)
141
-
142
- mtx, dist = load_coefficients_yaml(yaml)
143
-
144
- img = cv2.undistort(img, mtx, dist, None, None)
145
-
146
- height, width, _ = img.shape
147
-
148
- # Convert the image to grayscale
149
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
150
-
151
- # Apply a threshold to convert the grayscale image into a binary image
152
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
153
-
154
- # Find the contours in the binary image
155
- contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
156
-
157
- # Select the contour with the largest area (the object we want to extract the corners from)
158
- contour = max(contours, key=cv2.contourArea)
159
 
160
- # Approximate the contour with a polygon
161
- epsilon = 0.01 * cv2.arcLength(contour, True)
162
- approx = cv2.approxPolyDP(contour, epsilon, True)
163
- print(approx)
164
-
165
- # Draw the polygon on the original image
166
- cv2.polylines(img, [approx], True, (0, 255, 0), thickness=2)
167
 
168
- # cv2.imshow('image', img)
169
- # cv2.waitKey(0)
170
- # cv2.destroyAllWindows()
171
-
172
- doc = ezdxf.new("R2010", setup=True)
173
- msp = doc.modelspace()
174
-
175
- # Print the coordinates of the corners
176
- for corner in approx:
177
- x, y = corner[0]
178
- coordinates.append([x,y])
179
- #print(f"({x}, {y})")
180
-
181
-
182
- msp.add_line((coordinates[0][0], -coordinates[0][1]), (coordinates[1][0], -coordinates[1][1]))
183
- msp.add_line((coordinates[1][0], -coordinates[1][1]), (coordinates[2][0], -coordinates[2][1]))
184
- msp.add_line((coordinates[2][0], -coordinates[2][1]), (coordinates[3][0], -coordinates[3][1]))
185
- msp.add_line((coordinates[3][0], -coordinates[3][1]), (coordinates[0][0], -coordinates[0][1]))
186
- msp.add_line((0,0), (0, -height))
187
- msp.add_line((0, -height), (width, -height))
188
- msp.add_line((width, -height), (width, 0))
189
- msp.add_line((width, 0), (0,0))
190
-
191
-
192
- dwf_file = doc.saveas("output.dxf")
193
- return "output.dxf", img
 
6
 
7
  coordis = []
8
 
9
+ def save_matrix(mtx, dist, path):
10
  """Save camera matrix and distortion coefficients to file."""
11
  cv_file = cv2.FileStorage(path, cv2.FILE_STORAGE_WRITE)
12
  cv_file.write('K', mtx)
13
  cv_file.write('D', dist)
14
  cv_file.release()
15
+
16
+ def load_matrix(path):
 
17
  """Load camera matrix and distortion coefficients from file."""
18
+ cv_file = cv2.FileStorage(path, cv2.FILE_STORAGE_READ)
19
  camera_matrix = cv_file.getNode('K').mat()
20
  dist_matrix = cv_file.getNode('D').mat()
21
  cv_file.release()
22
  return [camera_matrix, dist_matrix]
23
 
 
24
  def correct_image(image, yaml):
25
  image = cv2.imread(image)
26
+ mtx, dist = load_matrix(yaml.name)
27
+ dst = cv2.undistort(image, mtx, dist, None, None)
28
  return dst
29
 
30
 
31
+ def color_tab(file_path, yaml):
32
 
33
+ coordinates = []
34
+ img = cv2.imread(file_path)
35
+
36
+ mtx, dist = load_matrix(yaml.name)
37
+
38
+ img = cv2.undistort(img, mtx, dist, None, None)
39
+
40
+ height, width, _ = img.shape
41
+
42
+ # Convert the image to grayscale
43
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
44
+
45
+ # Apply a threshold to convert the grayscale image into a binary image
46
+ _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
47
+
48
+ # Find the contours in the binary image
49
+ contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
50
+
51
+ # Select the contour with the largest area (the object we want to extract the corners from)
52
+ contour = max(contours, key=cv2.contourArea)
53
+
54
+ # Approximate the contour with a polygon
55
+ epsilon = 0.01 * cv2.arcLength(contour, True)
56
+ approx = cv2.approxPolyDP(contour, epsilon, True)
57
 
58
+ # Draw the polygon on the original image
59
+ cv2.polylines(img, [approx], True, (0, 255, 0), thickness=2)
60
+
61
+
62
+ doc = ezdxf.new("R2010", setup=True)
63
+ msp = doc.modelspace()
64
+
65
+ # Print the coordinates of the corners
66
+ for corner in approx:
67
+ x, y = corner[0]
68
+ coordinates.append([x,y])
69
+
70
+ #This method of adding line is brute force and needs to be changed, find a way to generalize for polygons
71
+ msp.add_line((coordinates[0][0], -coordinates[0][1]), (coordinates[1][0], -coordinates[1][1]))
72
+ msp.add_line((coordinates[1][0], -coordinates[1][1]), (coordinates[2][0], -coordinates[2][1]))
73
+ msp.add_line((coordinates[2][0], -coordinates[2][1]), (coordinates[3][0], -coordinates[3][1]))
74
+ msp.add_line((coordinates[3][0], -coordinates[3][1]), (coordinates[0][0], -coordinates[0][1]))
75
+ msp.add_line((0,0), (0, -height))
76
+ msp.add_line((0, -height), (width, -height))
77
+ msp.add_line((width, -height), (width, 0))
78
+ msp.add_line((width, 0), (0,0))
79
+
80
+ doc.saveas("output.dxf")
81
+ return "output.dxf", img
82
+
83
+ def corner_tab(img, evt: gr.SelectData):
84
+
85
+ height, width, _ = img.shape
86
 
87
  row, col = evt.index
88
  coordis.append([row, col])
89
 
90
  if len(coordis) == 4 :
91
  coordinates = np.array(coordis)
 
 
92
  dwg = ezdxf.new("R2010")
93
  msp = dwg.modelspace()
94
  dwg.layers.new(name="greeny green lines", dxfattribs={"color": 3})
 
98
  msp.add_line((coordinates[2][0], -coordinates[2][1]), (coordinates[3][0], -coordinates[3][1]))
99
  msp.add_line((coordinates[3][0], -coordinates[3][1]), (coordinates[0][0], -coordinates[0][1]))
100
 
101
+ msp.add_line((0,0), (0, -height))
102
+ msp.add_line((0, -height), (width, -height))
103
+ msp.add_line((width, -height), (width, 0))
104
+ msp.add_line((width, 0), (0,0))
105
+
106
+
107
+ dwg.saveas("output.dxf")
108
  coordis.clear()
109
 
110
  return "output.dxf"
 
117
 
118
  # Define the checkerboard pattern size and criteria for corner detection
119
  CHECKERBOARD = (int(board_vert)-1, int(board_horz)-1)
 
120
  criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
121
 
122
  # Initialize object points and image points arrays
 
147
  try:
148
  ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(object_points, image_points, gray.shape[::-1], None, None)
149
  # Save camera matrix and distortion coefficients to file
150
+ save_matrix(mtx, dist, f"{filename_stem}.yml")
151
  return f"{filename_stem}.yml"
152
  except:
153
  print("Please check the Chessboard Dimensions")
154
 
155
 
156
+ def slider(img, h1, s1, v1, h2, s2, v2):
157
  # Load the image
158
  img = cv2.imread(img)
159
 
 
160
  while(1):
 
161
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
162
 
163
  # Create the NumPy arrays
 
168
  lower_red = np.array([int(x) for x in lower_red])
169
  upper_red = np.array([int(x) for x in upper_red])
170
 
 
171
 
172
  mask = cv2.inRange(img, lower_red, upper_red)
173
  res = cv2.bitwise_and(img,img, mask= mask)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ return res
 
 
 
 
 
 
176