JaiSurya commited on
Commit
48bc30e
·
1 Parent(s): dd5a313
.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ .venv
3
+ .git
4
+ .gitignore
5
+ .python-version
6
+ *.pyc
7
+ *.pyo
8
+ *.pyd
9
+ .DS_Store
10
+ uv.lock
11
+ docs
.flaskenv ADDED
@@ -0,0 +1 @@
 
 
1
+ FLASK_APP=main.py
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ uv.lock
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ ENV FLASK_APP=main.py
6
+ ENV FLASK_ENV=production
7
+ ENV PORT=7860
8
+
9
+ RUN apt-get update && apt-get install -y \
10
+ libopencv-dev \
11
+ build-essential \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ COPY requirements.txt .
15
+
16
+ RUN pip install --no-cache-dir -r requirements.txt
17
+
18
+ COPY . .
19
+
20
+ EXPOSE 7860
21
+
22
+ CMD ["python","-m","gevent.pywsgi","-b","0.0.0.0:7860","main:app"]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Cut Out
3
- emoji: 🦀
4
  colorFrom: gray
5
  colorTo: pink
6
  sdk: docker
@@ -9,4 +9,55 @@ license: apache-2.0
9
  short_description: An AI image segmentation demo made with Flask and Mobile SAM
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Cut Out
3
+ emoji: ✂️
4
  colorFrom: gray
5
  colorTo: pink
6
  sdk: docker
 
9
  short_description: An AI image segmentation demo made with Flask and Mobile SAM
10
  ---
11
 
12
+ # Cut Out
13
+ ![Python](https://img.shields.io/badge/python-3.12+-blue.svg)
14
+
15
+ Cut out is a image segmentation implementation project that allows users to upload an image, select a region of interest and generate a cutout image with the selected region. In this project, I used Meta's [Segment Anything Model](https://github.com/facebookresearch/sam2/tree/main) which is state of the art deep learning model in the image segmentation task. To try the demo visit [here](). To try the Meta's demo try [here](https://segment-anything.com/demo).
16
+
17
+ ## Demo
18
+
19
+ https://github.com/user-attachments/assets/98ee0372-a640-419a-8580-1ebf100566ad
20
+
21
+
22
+ ## Tech Stack
23
+ 1. Python
24
+ 2. Flask
25
+ 3. Numpy
26
+ 4. Pillow
27
+ 5. Ultralytics
28
+ 6. Torch
29
+ 7. Torchvision
30
+ 8. Werkzeug
31
+
32
+ ## Installation
33
+
34
+ ### Github
35
+ You can clone this repository by
36
+ ```bash
37
+ $ git clone https://github.com/jaisuryaprabu/cut-out.git
38
+ $ cd cut-out
39
+ ```
40
+ ### Create the virtual environment
41
+ ```bash
42
+ $ python -m venv <your_venv_name>
43
+ $ .venv/Scripts/activate.ps1 <or> source .venv/Scripts/activate
44
+ ```
45
+ ### Export the FLASK_APP
46
+ Enter this command
47
+ ```bash
48
+ $ export FLASK_APP=main.py
49
+ ```
50
+
51
+ ### Run the app
52
+ ```bash
53
+ $ python -m gevent.pywsgi -b 0.0.0.0:7860 main:app
54
+ ```
55
+ ## Contribution
56
+ Contributions are warmly welcomed 🤗 To contribute :
57
+ 1. Fork this repository
58
+ 2. Make your changes and commit them
59
+ 3. Explain about the new feature or improvements
60
+ 4. Open a pull request with a description of your changes
61
+
62
+ ## Future Works
63
+ - [ ] Video segmentation
app/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+
3
+ app = Flask(__name__)
4
+
5
+ # always import the router below the app to avoid circular imports
6
+ from app import logics
7
+
8
+ # in the flask, the handlers for the application routers are called as view funtions
app/logics.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import cv2
4
+ from ultralytics import SAM
5
+ from ultralytics.utils.downloads import safe_download
6
+ from PIL import Image
7
+ from flask import render_template, request, redirect, url_for, send_from_directory
8
+ from werkzeug.utils import secure_filename
9
+
10
+ from app import app
11
+
12
+ # Configurations
13
+ UPLOAD_FOLDER = os.path.join(app.root_path, 'static', 'uploads')
14
+ MODEL_DIR = os.path.join(app.root_path,'models')
15
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
16
+
17
+ os.makedirs(UPLOAD_FOLDER,exist_ok=True)
18
+ os.makedirs(MODEL_DIR,exist_ok=True)
19
+
20
+ MODEL_PATH = os.path.join(MODEL_DIR,'mobile_sam.pt')
21
+ if not os.path.exists(MODEL_PATH):
22
+ safe_download(url="https://github.com/ultralytics/assets/releases/download/v8.2.0/mobile_sam.pt", dir=MODEL_DIR)
23
+
24
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
25
+ model = SAM(MODEL_PATH)
26
+
27
+ # File handling functions
28
+ def clear_uploads_folder():
29
+ """
30
+ Clear all files in the uploads folder
31
+ """
32
+ if os.path.exists(UPLOAD_FOLDER):
33
+ for filename in os.listdir(UPLOAD_FOLDER):
34
+ file_path = os.path.join(UPLOAD_FOLDER, filename)
35
+ try:
36
+ if os.path.isfile(file_path) or os.path.islink(file_path):
37
+ os.unlink(file_path)
38
+ except Exception as e:
39
+ print(f"Error deleting file {file_path}: {e}")
40
+
41
+ def is_allowed_file(filename):
42
+ """
43
+ Checking if a file has an allowed extension
44
+ """
45
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
46
+
47
+ def save_file(file, filename):
48
+ """
49
+ Save the uploaded file to the uploads folder and return the file path.
50
+ """
51
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
52
+ file.save(file_path)
53
+ return file_path
54
+
55
+ def generate_preview(x, y, filename):
56
+ """
57
+ Generate a preview of the selected region of interest using SAM.
58
+ Returns the preview image path or None if unsuccessful.
59
+ """
60
+ image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
61
+ image = cv2.imread(image_path)
62
+ if image is None:
63
+ return None
64
+
65
+ x_int = int(round(x))
66
+ y_int = int(round(y))
67
+
68
+ results = model.predict(source=image_path, points=[x_int, y_int], labels=[1], verbose=False)
69
+
70
+ if not results or not results[0].masks or len(results[0].masks) == 0:
71
+ return None
72
+
73
+ try:
74
+ mask = results[0].masks.data[0].cpu().numpy()
75
+ except (IndexError, AttributeError):
76
+ return None
77
+
78
+ mask = (mask < 0.5).astype(np.uint8) * 255
79
+
80
+ h, w = image.shape[:2]
81
+ if mask.shape != (h, w):
82
+ mask = cv2.resize(mask, (w, h), interpolation=cv2.INTER_NEAREST)
83
+
84
+ overlay = np.zeros_like(image, dtype=np.uint8)
85
+ overlay[mask == 255] = (0, 0, 255) # Red color
86
+
87
+ alpha = 0.5
88
+ preview_image = cv2.addWeighted(image, 1 - alpha, overlay, alpha, 0.0)
89
+
90
+ preview_image_path = "preview_" + filename
91
+ preview_path = os.path.join(app.config['UPLOAD_FOLDER'], preview_image_path)
92
+ cv2.imwrite(preview_path, preview_image)
93
+
94
+ return preview_image_path
95
+
96
+ def generate_segment(x, y, filename):
97
+ """
98
+ Segment the original image for the final result using SAM.
99
+ Returns the original filename and segmented filename with alpha channel, or (filename, None) if unsuccessful.
100
+ """
101
+ image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
102
+ image = cv2.imread(image_path)
103
+ image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
104
+ if image is None:
105
+ return filename, None
106
+
107
+ x_int = int(round(x))
108
+ y_int = int(round(y))
109
+
110
+ results = model.predict(source=image_path, points=[x_int, y_int], labels=[1], verbose=False)
111
+
112
+ if not results or not results[0].masks or len(results[0].masks) == 0:
113
+ return filename, None
114
+
115
+ try:
116
+ mask = results[0].masks.data[0].cpu().numpy()
117
+ except (IndexError, AttributeError):
118
+ return filename, None
119
+
120
+ mask = (mask < 0.5).astype(np.uint8) * 255
121
+
122
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
123
+ image_rgba = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2RGBA)
124
+ image_rgba[:, :, 3] = mask # Set alpha channel
125
+
126
+ segmented_filename = "segmented_" + filename
127
+ segmented_path = os.path.join(app.config['UPLOAD_FOLDER'], segmented_filename)
128
+ cv2.imwrite(segmented_path, image_rgba)
129
+
130
+ return filename, segmented_filename
131
+
132
+ # Flask Routes
133
+ @app.route('/', methods=['GET', 'POST'])
134
+ def index():
135
+ # Clear the uploads folder when showing the upload card
136
+ if request.method == 'GET':
137
+ clear_uploads_folder()
138
+
139
+ if request.method == 'POST':
140
+ if 'file' not in request.files:
141
+ return render_template('main.html', error="No file part")
142
+ file = request.files['file']
143
+ if file.filename == '':
144
+ return render_template('main.html', error="No selected file")
145
+ if file and is_allowed_file(file.filename):
146
+ filename = secure_filename(file.filename)
147
+ save_file(file, filename)
148
+ return render_template('main.html', uploaded_file=filename)
149
+ return render_template('main.html')
150
+
151
+ @app.route('/preview', methods=['POST'])
152
+ def preview():
153
+ x = request.form.get('x', '')
154
+ y = request.form.get('y', '')
155
+ filename = request.form.get('filename', '')
156
+ if not x or not y or not filename:
157
+ return render_template('main.html', uploaded_file=filename, error="Please select a region on the image")
158
+ try:
159
+ x = float(x)
160
+ y = float(y)
161
+ except ValueError:
162
+ return render_template('main.html', uploaded_file=filename, error="Invalid coordinates")
163
+
164
+ preview_file = generate_preview(x, y, filename)
165
+ if preview_file is None:
166
+ return render_template('main.html',uploaded_file=filename,error="There is no object in the selected region")
167
+
168
+ return render_template('main.html', uploaded_file=filename, preview_file=preview_file, x_coord=x, y_coord=y)
169
+
170
+ @app.route('/segment', methods=['POST'])
171
+ def segment():
172
+ x = request.form.get('x', '')
173
+ y = request.form.get('y', '')
174
+ filename = request.form.get('filename', '')
175
+
176
+ if not x or not y or not filename:
177
+ return render_template('main.html', uploaded_file=filename, error="Please select a region on the image")
178
+
179
+ try:
180
+ x = float(x)
181
+ y = float(y)
182
+ except ValueError:
183
+ return render_template('main.html', uploaded_file=filename, error="Invalid coordinates")
184
+
185
+ uploaded_file, result_file = generate_segment(x, y, filename)
186
+
187
+ return render_template('main.html', uploaded_file=uploaded_file, result_file=result_file)
188
+
189
+ @app.route('/reset', methods=['GET'])
190
+ def reset():
191
+ # Clear the uploads folder before returning to the upload phase
192
+ clear_uploads_folder()
193
+ return redirect(url_for('index'))
194
+
195
+ @app.route('/static/uploads/<filename>')
196
+ def uploaded_file(filename):
197
+ return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
198
+
199
+ @app.route('/download/<filename>')
200
+ def download_file(filename):
201
+ return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True)
202
+
203
+ @app.route('/about')
204
+ def about_page():
205
+ return render_template('about.html')
app/models/mobile_sam.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6dbb90523a35330fedd7f1d3dfc66f995213d81b29a5ca8108dbcdd4e37d6c2f
3
+ size 40728226
app/static/styles.css ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* 🌙 Dark Theme Base Styles */
2
+ :root {
3
+ --bg-dark: #121212;
4
+ --bg-light: #1e1e1e;
5
+ --text-light: #ffffff;
6
+ --text-muted: #b0b0b0;
7
+ --primary-color: #F1F1F2;
8
+ --secondary-color: #A1D6E2;
9
+ --error-color : red;
10
+ --border-color: #333;
11
+ --hover-color: #A1D6E2;
12
+ }
13
+
14
+ /* 🌐 Global Reset */
15
+ * {
16
+ margin: 0;
17
+ padding: 0;
18
+ box-sizing: border-box;
19
+ font-family: 'Poppins', sans-serif;
20
+ }
21
+
22
+ body {
23
+ background-color: var(--bg-dark);
24
+ color: var(--text-light);
25
+ line-height: 1.6;
26
+ }
27
+
28
+ a {
29
+ color: var(--text-muted);
30
+ text-decoration: none;
31
+ margin: 0 10px;
32
+ transition: color 0.3s;
33
+ }
34
+
35
+ a:hover {
36
+ color: var(--primary-color);
37
+ }
38
+
39
+ /* 🔝 Navigation Bar */
40
+ .navbar {
41
+ background: var(--bg-light);
42
+ padding: 1rem 2rem;
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ border-bottom: 2px solid var(--border-color);
47
+ }
48
+
49
+ .navbar h1 {
50
+ color: var(--text-light);
51
+ font-size: 1.8rem;
52
+ }
53
+
54
+ .navbar ul {
55
+ list-style: none;
56
+ display: flex;
57
+ gap: 1rem;
58
+ }
59
+
60
+ .navbar ul li {
61
+ display: inline;
62
+ }
63
+
64
+ .navbar ul li a {
65
+ text-decoration: none;
66
+ color: var(--text-muted);
67
+ font-weight: 500;
68
+ padding: 0.5rem 1rem;
69
+ transition: color 0.3s;
70
+ }
71
+
72
+ .navbar ul li a:hover {
73
+ color: var(--primary-color);
74
+ }
75
+
76
+ /* 📌 Card Component */
77
+ /* Card Transition Animation */
78
+ .card {
79
+ background: var(--bg-light);
80
+ padding: 2rem;
81
+ margin: 5rem auto;
82
+ max-width: 400px;
83
+ border-radius: 10px;
84
+ text-align: center;
85
+ box-shadow: 0px 4px 10px rgba(255, 255, 255, 0.1);
86
+ border: 1px solid var(--border-color);
87
+ opacity: 0;
88
+ transform: translateY(20px);
89
+ animation: fadeIn 0.5s ease forwards;
90
+ }
91
+
92
+ @keyframes fadeIn {
93
+ to {
94
+ opacity: 1;
95
+ transform: translateY(0);
96
+ }
97
+ }
98
+
99
+ .card h2 {
100
+ color: var(--text-light);
101
+ margin-bottom: 1rem;
102
+ }
103
+
104
+ .card p {
105
+ color: var(--text-muted);
106
+ margin-bottom: 1rem;
107
+ }
108
+
109
+ .card .upload-btn {
110
+ display: block;
111
+ width: 100%;
112
+ background: var(--secondary-color);
113
+ border: none;
114
+ color: var(--bg-dark);
115
+ padding: 10px;
116
+ font-size: 1rem;
117
+ font-weight: bold;
118
+ cursor: pointer;
119
+ border-radius: 5px;
120
+ transition: background 0.3s;
121
+ text-decoration: none;
122
+ }
123
+
124
+ .card .upload-btn:hover {
125
+ background: var(--primary-color);
126
+ }
127
+ .card .a {
128
+ color: var(--text-muted);
129
+ text-decoration: none;
130
+ margin: 0 10px;
131
+ transition: color 0.3s;
132
+ }
133
+ .card .a:hover {
134
+ color: var(--primary-color);
135
+ }
136
+
137
+ /* 🔘 Buttons */
138
+ .btn {
139
+ display: inline-block;
140
+ padding: 12px 20px;
141
+ font-size: 1rem;
142
+ font-weight: bold;
143
+ border-radius: 5px;
144
+ border: none;
145
+ cursor: pointer;
146
+ text-align: center;
147
+ transition: background 0.3s, transform 0.2s;
148
+ text-decoration: none;
149
+ }
150
+
151
+ .btn-primary {
152
+ background: var(--primary-color);
153
+ color: var(--bg-dark);
154
+ }
155
+
156
+ .btn-primary:hover {
157
+ background: var(--hover-color);
158
+ }
159
+
160
+ .btn-secondary {
161
+ background: var(--secondary-color);
162
+ color: var(--bg-dark);
163
+ margin-left: 10px;
164
+ }
165
+
166
+ .btn-secondary:hover {
167
+ background: var(--primary-color);
168
+ }
169
+
170
+ .btn:active {
171
+ transform: scale(0.95);
172
+ }
173
+
174
+ /* 📏 Separator Line */
175
+ .separator {
176
+ height: 2px;
177
+ background: var(--border-color);
178
+ margin: 2rem 0;
179
+ }
180
+
181
+ /* 📌 Footer */
182
+ .footer {
183
+ background: var(--bg-light);
184
+ padding: 1rem;
185
+ text-align: center;
186
+ border-top: 2px solid var(--border-color);
187
+ margin: 5rem auto;
188
+ }
189
+
190
+ .footer h2 {
191
+ color: var(--text-light);
192
+ text-align: left;
193
+ }
194
+ .footer p {
195
+ color: var(--text-light);
196
+ text-align: left;
197
+ }
198
+ .footer ul li {
199
+ display: inline;
200
+ margin : 5px 5px;
201
+ }
202
+
203
+ .footer ul li a {
204
+ text-decoration: none;
205
+ color: var(--text-muted);
206
+ font-weight: 500;
207
+ padding: 0.5rem 1rem;
208
+ transition: color 0.3s;
209
+ }
210
+
211
+ .footer ul li a:hover {
212
+ color: var(--primary-color);
213
+ }
214
+
215
+ /* Error Message */
216
+ .error {
217
+ color: var(--error-color);
218
+ font-size: 1rem;
219
+ margin-top: 10px;
220
+ }
221
+
222
+ /* Upload Input Styling */
223
+ input[type="file"] {
224
+ display: block;
225
+ margin: 10px auto;
226
+ padding: 10px;
227
+ background: var(--bg-light);
228
+ border: 1px solid var(--border-color);
229
+ color: var(--text-light);
230
+ cursor: pointer;
231
+ }
232
+
233
+ /* Preview Image Styling */
234
+ .preview-image {
235
+ width: 100%;
236
+ max-width: 300px;
237
+ height: auto;
238
+ display: block;
239
+ margin: 20px auto;
240
+ border-radius: 10px;
241
+ border: 2px solid var(--border-color);
242
+ transition: border-color 0.3s;
243
+ }
244
+
245
+ .preview-image.preview-active {
246
+ border-color: var(--secondary-color);
247
+ }
app/templates/about.html ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title> About | Cut Out ✂️</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cut Out | Image segmentation</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ </head>
9
+ <body>
10
+ {% include 'navbar.html' %}
11
+ <div class="card">
12
+ <h1>About</h1>
13
+ <p>This project is developed by <a href="https://x.com/__Jaisurya"">Jaisurya Prabakaran</a> using the flask in python. The inspiration of this project is from the demo of <a href="https://segment-anything.com">Segment Anything Model</a> by Meta. The model used in this project is <a href="https://github.com/facebookresearch/sam2/tree/main">SAM 2 Tiny</a> version</p>
14
+ </div>
15
+ {% include 'footer.html' %}
16
+ </body>
17
+ </html>
app/templates/base.html ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cut Out | Image segmentation</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
9
+ </head>
10
+ <body>
11
+ {% include 'navbar.html' %}
12
+ <div>
13
+ {% block content %}{% endblock %}
14
+ </div>
15
+ {%include 'footer.html' %}
16
+ </body>
17
+ </html>
app/templates/footer.html ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <footer class="footer">
2
+ <h2>Links 🔗</h2>
3
+ <ul>
4
+ <li><a href="https://x.com/__Jaisurya"> X</a></li>
5
+ <li><a href="https://www.linkedin.com/in/jaisurya-prabakaran-3619aa226/">Linked In</a></li>
6
+ <li><a href="https://github.com/JaiSuryaPrabu/cut-out">Code</a></li>
7
+ </ul>
8
+ </footer>
app/templates/main.html ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <!-- Upload card -->
5
+ {% if not uploaded_file %}
6
+ <div class="card">
7
+ <h2>Upload Your File</h2>
8
+ <p>Click the button below to select and upload your file. Ensure png, jpg, jpeg files are selected</p>
9
+ <form action="/" method="POST" enctype="multipart/form-data">
10
+ <input type="file" name="file" id="file-input" accept=".jpg, .jpeg, .png">
11
+ <button type="submit" class="upload-btn">Upload Photo</button>
12
+ </form>
13
+ {% if error %}
14
+ <p class="error">{{ error }}</p>
15
+ {% endif %}
16
+ </div>
17
+ {% endif %}
18
+
19
+ <!-- Selection card -->
20
+ {% if uploaded_file and not result_file %}
21
+ <div class="card">
22
+ <h2>Select the Object</h2>
23
+ <p>Click on the region of interest to make a cutout</p>
24
+ <img src="{{ url_for('static', filename='uploads/' + (preview_file if preview_file else uploaded_file)) }}" alt="Uploaded Image" class="preview-image{% if preview_file %} preview-active{% endif %}" id="preview-image">
25
+ <form id="preview-form" action="/preview" method="POST" style="display: none;">
26
+ <input type="hidden" name="x" id="preview-x-coord">
27
+ <input type="hidden" name="y" id="preview-y-coord">
28
+ <input type="hidden" name="filename" value="{{ uploaded_file }}">
29
+ </form>
30
+ <form id="segment-form" action="/segment" method="POST">
31
+ <!-- Pre-fill x and y coordinates if available -->
32
+ <input type="hidden" name="x" id="x-coord" value="{{ x_coord if x_coord is not none else '' }}">
33
+ <input type="hidden" name="y" id="y-coord" value="{{ y_coord if y_coord is not none else '' }}">
34
+ <input type="hidden" name="filename" value="{{ uploaded_file }}">
35
+ <button type="submit" class="btn btn-primary" id="submit-btn" {% if not preview_file %}disabled{% endif %}>Submit</button>
36
+ </form>
37
+ <a href="{{ url_for('reset') }}" class="btn btn-secondary" style="margin-top: 10px;">Back</a>
38
+ {% if error %}
39
+ <p class="error">{{ error }}</p>
40
+ {% endif %}
41
+ </div>
42
+ {% endif %}
43
+
44
+ <!-- Result card -->
45
+ {% if result_file %}
46
+ <div class="card">
47
+ <h2>Result Image</h2>
48
+ <p>The selected region has been removed from the original image.</p>
49
+ <img src="{{ url_for('static', filename='uploads/' + result_file) }}" alt="Result Image" class="preview-image">
50
+ <a href="{{ url_for('download_file', filename=result_file) }}" class="btn btn-primary" style="margin-top: 10px;">Download Result</a>
51
+ <a href="{{ url_for('reset') }}" class="btn btn-secondary" style="margin-top: 10px;">Back</a>
52
+ </div>
53
+ {% endif %}
54
+
55
+ <script>
56
+ document.getElementById('preview-image')?.addEventListener('click', function(e) {
57
+ // Get the image element
58
+ const img = e.target;
59
+ const rect = img.getBoundingClientRect();
60
+
61
+ // Calculate the x, y coordinates relative to the image
62
+ const x = e.clientX - rect.left;
63
+ const y = e.clientY - rect.top;
64
+
65
+ // Update hidden inputs with coordinates
66
+ document.getElementById('preview-x-coord').value = x;
67
+ document.getElementById('preview-y-coord').value = y;
68
+ document.getElementById('x-coord').value = x;
69
+ document.getElementById('y-coord').value = y;
70
+
71
+ document.getElementById('submit-btn').disabled = false;
72
+ document.getElementById('preview-form').submit();
73
+ });
74
+ </script>
75
+ {% endblock %}
app/templates/navbar.html ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <nav class="navbar">
2
+ <h1><a href="/">Cut Out ✂️</a></h1>
3
+ <ul>
4
+ <li><a href="/about">About</a></li>
5
+ </ul>
6
+ </nav>
docs/Problems.md ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Deployment Problems
2
+ ## Docker
3
+ * I created the `dockerfile` for the python:3.12 base image with installing the dependencies using pip which leads to error of building the `sam2` package because it has `pyproject.yaml` based configurations
4
+ * Then updated the `dockerfile` to copy my virtual environment to the docker, the build is success but the runtime fails because the image is `debian` based, my OS is windows
docs/Progress.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Progress
2
+
3
+ ## Setup and organizing
4
+ 1. Initialize the `uv` with `uv init` command
5
+ 2. Install the flask by `uv pip install flask` command
6
+ 3. Created the `app` directory
7
+ 4. Created the `app/__init__.py` for adding the backend code inside the `app` directory
8
+ 5. Created the `app/routes.py` for adding the view functions
9
+ 6. Exported the `FLASK_APP=main.py` to the environment
10
+ 7. Tested the execution of the flask app by `flask run` command
11
+ 8. Installed the python dot env for saving the environment variables, helpful for using environment variable and to install use `uv pip install python-dotenv`
12
+ 9. Created the `.flaskenv` for storing the environmental variables for this application
13
+ 10. Created a directory named `app/templates`
14
+ 11. Inside the `app/templates/` directory, created the `index.html`
15
+ 12. By using the `render_template()` function the `html` code is rendered in the webpage.
16
+ 13. Can able to add `title` arg in the `render_template()` as it is optional and `html` contains the `{% if title %}` and `{% else %}` and `{% endif %}` for conditional rendering.
17
+ 14. Created a base template in `base.html` that acts as `layout.jsx` in react based applications.
18
+ 15. Updated the `app/templates/index.html` with the block named `content`
19
+ 16. To make the active reload of the flask app, run with `flask run --debug` command
20
+ 17. Created `app/static` folder for storing the `css` files
21
+ 18. Splitted the `base.html` into multiple files like `main.html`, `navbar.html` and `footer.html`
22
+
23
+ ## Backend logic implementation
24
+ 1. Added the file upload logic to the `routes.py` and updated the `main.html` for previewing the file
25
+ 2. Added the cards in the main html file for uploading the image, previewing the image and getting the result image.
26
+ 3. Downloaded the SAM 2 model from the sam2 github repository
27
+ 4. Inference code is added to get the final result
28
+ 5. For better user experience overlay image is added in the `preview_image()` function
main.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from gevent.pywsgi import WSGIServer
2
+ from app import app
3
+
4
+ if __name__ == '__main__':
5
+ http_server = WSGIServer(('0.0.0.0', 7860), app)
6
+ http_server.serve_forever()
pyproject.toml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "cut-out"
3
+ version = "0.1.0"
4
+ description = "Image segmentation project"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ ]
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ flask
2
+ numpy
3
+ pillow
4
+ python-dotenv
5
+ ultralytics
6
+ werkzeug
7
+ gevent