Kimang18 commited on
Commit
9bbf7fe
·
1 Parent(s): 31ca948

add application file and dependencies

Browse files
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.9
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . .
16
+ CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:7860"]
app.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import uuid
4
+
5
+ from flask import Flask, redirect, url_for, request, flash, session
6
+ from flask import render_template
7
+ from flask import send_file
8
+
9
+ app = Flask(__name__)
10
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
11
+ app.config["SECRET_KEY"] = '#thaistartsfirst!'
12
+ app.config["IMAGES"] = './images'
13
+ app.config["OUT"] = "./anno"
14
+ app.config["LABELS"] = []
15
+ app.config["HEAD"] = 0
16
+ app.config["SESSION_PERMANENT"] = False
17
+ try:
18
+ os.mkdir(app.config["OUT"])
19
+ os.mkdir(app.config["IMAGES"])
20
+ except:
21
+ pass
22
+
23
+
24
+ @app.route('/', methods=['GET', 'POST'])
25
+ def index():
26
+ user_id = session.get('_id')
27
+ if user_id is None:
28
+ user_id = uuid.uuid4()
29
+ session['_id'] = user_id
30
+ with open(f"{app.config['OUT']}/{user_id}.csv", 'w') as f:
31
+ f.write("image,id,name,xMin,xMax,yMin,yMax\n")
32
+
33
+ if request.method == 'POST':
34
+ if 'file' not in request.files:
35
+ flash('No files selected')
36
+ return redirect('/')
37
+ img_folder = f'{app.config["IMAGES"]}/{user_id}'
38
+ try:
39
+ os.mkdir(img_folder)
40
+ except:
41
+ print('user already has an active session')
42
+ files = request.files.getlist("file")
43
+ filenames = []
44
+ for f in files:
45
+ f.save(os.path.join(img_folder, f.filename))
46
+ filenames.append(f.filename)
47
+ app.config["FILES"] = filenames
48
+ print(app.config["FILES"], app.config["HEAD"])
49
+ return redirect('/tagger', code=302)
50
+ else:
51
+ return render_template('index.html')
52
+
53
+
54
+ @app.route('/tagger')
55
+ def tagger():
56
+ if (app.config["HEAD"] == len(app.config["FILES"])):
57
+ # done annotating the batch of images
58
+ app.config["HEAD"] = 0
59
+ return redirect(url_for('final'))
60
+ user_id = session.get('_id')
61
+ directory = app.config["IMAGES"] + f'/{user_id}'
62
+ image = app.config["FILES"][app.config["HEAD"]]
63
+ labels = app.config["LABELS"]
64
+ not_end = not(app.config["HEAD"] == len(app.config["FILES"]) - 1)
65
+ # print(not_end)
66
+ return render_template('tagger.html', not_end=not_end, directory=directory, image=image, labels=labels, head=app.config["HEAD"] + 1, len=len(app.config["FILES"]))
67
+
68
+
69
+ @app.route('/next')
70
+ def next():
71
+ image = app.config["FILES"][app.config["HEAD"]]
72
+ app.config["HEAD"] = app.config["HEAD"] + 1
73
+ user_id = session.get("_id")
74
+ with open(f"{app.config['OUT']}/{user_id}.csv", 'a+') as f:
75
+ for label in app.config["LABELS"]:
76
+ f.write(image + "," +
77
+ label["id"] + "," +
78
+ label["name"] + "," +
79
+ str(round(float(label["xMin"]))) + "," +
80
+ str(round(float(label["xMax"]))) + "," +
81
+ str(round(float(label["yMin"]))) + "," +
82
+ str(round(float(label["yMax"]))) + "\n")
83
+ app.config["LABELS"] = []
84
+ return redirect(url_for('tagger'))
85
+
86
+ @app.route("/final")
87
+ def final():
88
+ return render_template('final.html')
89
+
90
+ @app.route('/add/<id>')
91
+ def add(id):
92
+ xMin = request.args.get("xMin")
93
+ xMax = request.args.get("xMax")
94
+ yMin = request.args.get("yMin")
95
+ yMax = request.args.get("yMax")
96
+ app.config["LABELS"].append({"id":id, "name":"", "xMin":xMin, "xMax":xMax, "yMin":yMin, "yMax":yMax})
97
+ return redirect(url_for('tagger'))
98
+
99
+ @app.route('/remove/<id>')
100
+ def remove(id):
101
+ index = int(id) - 1
102
+ del app.config["LABELS"][index]
103
+ for label in app.config["LABELS"][index:]:
104
+ label["id"] = str(int(label["id"]) - 1)
105
+ return redirect(url_for('tagger'))
106
+
107
+ @app.route('/label/<id>')
108
+ def label(id):
109
+ name = request.args.get("name")
110
+ app.config["LABELS"][int(id) - 1]["name"] = name
111
+ return redirect(url_for('tagger'))
112
+
113
+ @app.route('/image/<f>')
114
+ def images(f):
115
+ user_id = session.get('_id')
116
+ images = app.config["IMAGES"] + f'/{user_id}'
117
+ return send_file(images +'/'+f)
118
+
119
+ @app.route('/download')
120
+ def download():
121
+ user_id = session.get('_id')
122
+ anno_path = f"{app.config['OUT']}/{user_id}.csv"
123
+ directory = app.config["IMAGES"] + f'/{user_id}'
124
+ shutil.copyfile(anno_path, f'{directory}/annotations.csv')
125
+ shutil.make_archive('final', 'zip', directory)
126
+ return send_file('final.zip',
127
+ mimetype='text/csv',
128
+ download_name='final.zip',
129
+ as_attachment=True)
130
+
131
+ if __name__ == "__main__":
132
+ app.run(debug="True")
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask==3.1.1
2
+ jinja2==3.1.6
3
+ gunicorn
static/images/logo_black.png ADDED
templates/final.html ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html style="height:100%;">
3
+ <head>
4
+ <title>Download Annotations</title>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
7
+ </head>
8
+ <body>
9
+ <header class="flex items-center justify-between bg-white p-4 sm:p-6 rounded-lg shadow-md mb-8 flex-wrap">
10
+ <!-- Left Section: Two Logos -->
11
+ <div class="flex items-center space-x-4 mb-4 sm:mb-0 w-full sm:w-auto justify-center sm:justify-start">
12
+ <!-- Logo 1
13
+ <img src="{{ url_for('static', filename='images/AI-FARM-logo-version 2 -20-JULY-2023-01.png') }}" alt="AIFarm" class="squared-full shadow-sm" height="75px" width="75px">
14
+ -->
15
+ <!-- Logo 1
16
+ <img src="{{ url_for('static', filename='images/FACTORYAI-FINAL-SQUARE-16-AUG-2023.png') }}" alt="FactoryAI" class="squared-full shadow-sm">
17
+ -->
18
+ <!-- Logo 2 -->
19
+ <img src="{{ url_for('static', filename='images/logo_black.png') }}" alt="KrongAI" class="squared-full shadow-sm" height="120cqh">
20
+ </div>
21
+
22
+ <!-- Middle Section: Title -->
23
+ <div class="flex-grow text-center mb-4 sm:mb-0 order-first sm:order-none w-full sm:w-auto">
24
+ <h1 class="text-3xl sm:text-4xl font-bold text-gray-800 leading-tight">
25
+ 🇰🇭 By Cambodian, for the development of AI 🇰🇭
26
+ </h1>
27
+ </div>
28
+
29
+ <!-- Right Section: Placeholder (can be used for user avatar/menu later) -->
30
+ <!-- Added a placeholder to help balance the flex layout, though not strictly required by prompt -->
31
+ <div class="w-full sm:w-auto flex justify-center sm:justify-end">
32
+ <!-- Placeholder for potential future elements, e.g., user icon -->
33
+ <div class="w-[128px] h-0 sm:h-[60px]"></div>
34
+ </div>
35
+ </header>
36
+
37
+ <div class="container" style="margin-top:20px;">
38
+ <center>
39
+ <div class="jumbotron">
40
+ <h2>Download the Annotations as CSV File</h2>
41
+ <button id="downloadButton" style="margin-top:10px;" class="btn btn-success">Download</button>
42
+ <div id="messageBox" class="mt-8 p-4 bg-blue-100 border border-blue-400 text-blue-700 rounded-lg hidden" role="alert">
43
+ <p id="messageText" class="font-medium"></p>
44
+ </div>
45
+ <script>
46
+ document.getElementById('downloadButton').addEventListener('click', function() {
47
+ var messageBox = document.getElementById('messageBox');
48
+ var messageText = document.getElementById('messageText');
49
+ messageText.textContent = 'Thank you for helping our Cambodia!';
50
+ messageBox.classList.remove('hidden', 'bg-red-100', 'border-red-400', 'text-red-700', 'bg-green-100', 'border-green-400', 'text-green-700', 'bg-blue-100', 'border-blue-400', 'text-blue-700');
51
+ messageBox.classList.add('bg-green-100', 'border-green-400', 'text-green-700');
52
+ messageBox.classList.remove('hidden');
53
+
54
+ // Trigger the download by creating a temporary link and clicking it
55
+ var link = document.createElement('a');
56
+ link.href = "{{ url_for('download', download='annotations.csv') }}";
57
+ link.download = 'my_file.txt'; // This is optional but good practice
58
+ document.body.appendChild(link);
59
+ link.click();
60
+ document.body.removeChild(link);
61
+
62
+ // Now, redirect the user after a short delay
63
+ setTimeout(function() {
64
+
65
+ window.location.href = "{{ url_for('index') }}";
66
+ }, 3000); // 1000ms = 1 second delay. Adjust as needed.
67
+ });
68
+ </script>
69
+ </div>
70
+ </center>
71
+ </div>
72
+ <!-- FOOTER ADDED HERE -->
73
+ <footer class="mt-8 mb-4 text-center text-gray-500 text-sm w-full max-w-4xl">
74
+ Thanks Huggingface 🤗
75
+ </footer>
76
+ </body>
77
+ </html>
templates/index.html ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html style="height:100%;">
3
+ <head>
4
+ <title>Download Annotations</title>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
7
+ </head>
8
+ <body>
9
+ <header class="flex items-center justify-between bg-white p-4 sm:p-6 rounded-lg shadow-md mb-8 flex-wrap">
10
+ <!-- Left Section: Two Logos -->
11
+ <div class="flex items-center space-x-4 mb-4 sm:mb-0 w-full sm:w-auto justify-center sm:justify-start">
12
+ <!-- Logo 1
13
+ <img src="{{ url_for('static', filename='images/AI-FARM-logo-version 2 -20-JULY-2023-01.png') }}" alt="AIFarm" class="squared-full shadow-sm" height="75px" width="75px">
14
+ -->
15
+ <!-- Logo 1
16
+ <img src="{{ url_for('static', filename='images/FACTORYAI-FINAL-SQUARE-16-AUG-2023.png') }}" alt="FactoryAI" class="squared-full shadow-sm">
17
+ -->
18
+ <!-- Logo 2 -->
19
+ <img src="{{ url_for('static', filename='images/logo_black.png') }}" alt="KrongAI" class="squared-full shadow-sm" height="120cqh">
20
+ </div>
21
+
22
+ <!-- Middle Section: Title -->
23
+ <div class="flex-grow text-center mb-4 sm:mb-0 order-first sm:order-none w-full sm:w-auto">
24
+ <h1 class="text-3xl sm:text-4xl font-bold text-gray-800 leading-tight">
25
+ 🇰🇭 By Cambodian, for the development of AI 🇰🇭
26
+ </h1>
27
+ </div>
28
+
29
+ <!-- Right Section: Placeholder (can be used for user avatar/menu later) -->
30
+ <!-- Added a placeholder to help balance the flex layout, though not strictly required by prompt -->
31
+ <div class="w-full sm:w-auto flex justify-center sm:justify-end">
32
+ <!-- Placeholder for potential future elements, e.g., user icon -->
33
+ <div class="w-[128px] h-0 sm:h-[60px]"></div>
34
+ </div>
35
+ </header>
36
+ <div class="container">
37
+ <center>
38
+ <div class="jumbotron" style="margin-top:20px;">
39
+ <h2 style="margin:-20px 0 40px 0;">Image Annotator</h2>
40
+ <h1 style="margin:-20px 0 40px 0;">for Object Detection</h1>
41
+ <form enctype="multipart/form-data" method="post">
42
+ <label class="btn btn-default">
43
+ <input id="file" name="file" type="file" multiple/>
44
+ </label>
45
+ <br>
46
+ <button type="submit" class="btn btn-success" style="margin-top:30px;">Submit</button>
47
+ </form>
48
+ </div>
49
+ </center>
50
+ </div>
51
+ <!-- FOOTER ADDED HERE -->
52
+ <footer class="mt-8 mb-4 text-center text-gray-500 text-sm w-full max-w-4xl">
53
+ Thanks Huggingface 🤗
54
+ </footer>
55
+ <script>
56
+ var folder = document.getElementById("folder");
57
+ console.log(folder.value)
58
+ </script>
59
+ </body>
60
+ </html>
templates/tagger.html ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html style="height:100%;">
3
+ <head>
4
+ <title>Annotate Your Images</title>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
7
+ <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/cerulean/bootstrap.min.css" rel="stylesheet">
8
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
9
+ </head>
10
+
11
+ <style>
12
+ .sidebar{
13
+ width: 25%;
14
+ height: 100%;
15
+ float: left;
16
+ z-index: 10;
17
+ margin-bottom: 0px;
18
+ }
19
+ .content{
20
+ width: 75%;
21
+ height: 100%;
22
+ float: right;
23
+ z-index: 10;
24
+ margin-bottom:0px;
25
+ }
26
+ .row{
27
+ padding: 10px 5px;
28
+ border-bottom: 3px solid black;
29
+ }
30
+ .image-name{
31
+ font-size: 16px;
32
+ }
33
+ .counter{
34
+ border: 2px solid red;
35
+ border-radius: 50%;
36
+ font-size: 18px;
37
+ }
38
+ .content-image{
39
+ margin-top:10px;
40
+ }
41
+ </style>
42
+
43
+ <body style="height:100%;">
44
+ <header class="flex items-center justify-between bg-white p-4 sm:p-6 rounded-lg shadow-md mb-8 flex-wrap">
45
+ <!-- Left Section: Two Logos -->
46
+ <div class="flex items-center space-x-4 mb-4 sm:mb-0 w-full sm:w-auto justify-center sm:justify-start">
47
+ <!-- Logo 1
48
+ <img src="{{ url_for('static', filename='images/AI-FARM-logo-version 2 -20-JULY-2023-01.png') }}" alt="AIFarm" class="squared-full shadow-sm" height="75px" width="75px">
49
+ -->
50
+ <!-- Logo 1
51
+ <img src="{{ url_for('static', filename='images/FACTORYAI-FINAL-SQUARE-16-AUG-2023.png') }}" alt="FactoryAI" class="squared-full shadow-sm">
52
+ -->
53
+ <!-- Logo 2 -->
54
+ <img src="{{ url_for('static', filename='images/logo_black.png') }}" alt="KrongAI" class="squared-full shadow-sm" height="120cqh">
55
+ </div>
56
+
57
+ <!-- Middle Section: Title -->
58
+ <div class="flex-grow text-center mb-4 sm:mb-0 order-first sm:order-none w-full sm:w-auto">
59
+ <h1 class="text-3xl sm:text-4xl font-bold text-gray-800 leading-tight">
60
+ 🇰🇭 By Cambodian, for the development of AI 🇰🇭
61
+ </h1>
62
+ </div>
63
+
64
+ <!-- Right Section: Placeholder (can be used for user avatar/menu later) -->
65
+ <!-- Added a placeholder to help balance the flex layout, though not strictly required by prompt -->
66
+ <div class="w-full sm:w-auto flex justify-center sm:justify-end">
67
+ <!-- Placeholder for potential future elements, e.g., user icon -->
68
+ <div class="w-[128px] h-0 sm:h-[60px]"></div>
69
+ </div>
70
+ </header>
71
+
72
+ <nav id="sidebar" class="sidebar">
73
+ <div class="panel panel-default" style="height:100%;">
74
+ <div class="panel-heading">
75
+ <h3 class="panel-title"><b><center>Labels</center></b></h3>
76
+ </div>
77
+ <script>
78
+ var label = function(id, name) {
79
+ window.location.replace("/label/" + id + "?name=" + name);
80
+ }
81
+ </script>
82
+ <div class="panel-body">
83
+ <div class="list-group">
84
+ {% for label in labels %}
85
+ <div class="list-group-item">
86
+ <div class="input-group">
87
+ <span class="input-group-addon" id="id">{{ label.id }}</span>
88
+ {% if label.name %}
89
+ <text style="background-color:#E5E7E9;" class="form-control custom-control" style="resize:none">{{ label.name }}</text>
90
+ {% else %}
91
+ <input id= "{{ label.id }}" onkeydown="if (event.keyCode == 13) { label(this.id, this.value); }" type="text" class="form-control" placeholder="Label Name" autofocus></input>
92
+ {% endif %}
93
+ <span class="input-group-btn">
94
+ <button id= "{{ label.id }}" class="btn btn-danger" onclick="window.location.replace('/remove/' + this.id)" type="button"><b>-<b></button>
95
+ </span>
96
+ </div>
97
+ </div>
98
+ {% endfor %}
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </nav>
103
+ <div id="content" class="container content">
104
+ <div class="row">
105
+ <span class="counter"><b>{{ head }} / {{ len }}</b></span>
106
+ <span class="image-name">{{image}}</span>
107
+ {% if not_end %}
108
+ <a href="/next" class="btn btn-primary" style="float:right; font-size:18px;" type="button">
109
+ <span class="glyphicon glyphicon-arrow-right"></span>
110
+ </a>
111
+ {% else %}
112
+ <a href="/next" class="btn btn-primary" style="float:right; font-size:18px;" type="button">
113
+ <span class="glyphicon glyphicon-ok"></span>
114
+ </a>
115
+ {% endif %}
116
+ </div>
117
+ <div class="content-image">
118
+ <canvas id="canvas" style="width:100%; height:80%; margin: 0; padding: 0;"></canvas>
119
+ </div>
120
+
121
+ <script>
122
+ var labels = {{ labels|tojson|safe }};
123
+ var c = document.getElementById("canvas");
124
+ var ctx = c.getContext("2d");
125
+ var drawLabels = function(id, xMin, xMax, yMin, yMax) {
126
+ ctx.strokeStyle = "red";
127
+ //ctx.fillStyle = "red";
128
+ ctx.rect(xMin, yMin, xMax - xMin, yMax - yMin);
129
+ ctx.lineWidth="4";
130
+ ctx.stroke();
131
+ ctx.font = "30px Arial";
132
+ ctx.fillText("id: " + id, xMin,yMin-10);
133
+ };
134
+
135
+ //load and display image
136
+ var image = new Image();
137
+ image.onload = function(e) {
138
+ ctx.canvas.width = image.width;
139
+ ctx.canvas.height = image.height;
140
+ c.width = image.width;
141
+ c.height = image.height;
142
+ ctx.drawImage(image, 0, 0);
143
+ console.log(labels);
144
+ for (i = 0; i < labels.length; i++){
145
+ drawLabels(labels[i].id, labels[i].xMin, labels[i].xMax, labels[i].yMin, labels[i].yMax);
146
+ }
147
+ };
148
+ image.style.display="block";
149
+ image.src = "image/{{ image }}";
150
+
151
+ // this flage is true when the user is dragging the mouse
152
+ var isDown=false;
153
+ // these vars will hold the starting mouse position
154
+ var startX, startY, mouseX, mouseY, endX, endY;
155
+
156
+ function calcPoints(startX, startY, endX, endY){
157
+ var temp = 0;
158
+ if(startX>endX){
159
+ temp = startX;
160
+ startX = endX;
161
+ endX = temp;
162
+ }
163
+ if(startY>endY){
164
+ temp = startY;
165
+ startY = endY;
166
+ endY = temp;
167
+ }
168
+ return [startX,startY,endX,endY]
169
+ }
170
+
171
+ function handleMouseDown(e){
172
+ e.preventDefault();
173
+ e.stopPropagation();
174
+ // save the starting x/y of the rectangle
175
+
176
+ startX=parseInt((image.width / c.scrollWidth) * e.offsetX);
177
+ startY=parseInt((image.height / c.scrollHeight) * e.offsetY);
178
+ // set a flag indicating the drag has begun
179
+ isDown=true;
180
+ }
181
+
182
+ function handleMouseUp(e){
183
+ e.preventDefault();
184
+ e.stopPropagation();
185
+ // the drag is over, clear the dragging flag
186
+ if(isDown){
187
+ endX = parseInt((image.width / c.scrollWidth) * e.offsetX);
188
+ endY = parseInt((image.height / c.scrollHeight) * e.offsetY);
189
+ [startX,startY,endX,endY] = calcPoints(startX,startY,endX,endY)
190
+ window.location.replace("/add/" + (labels.length + 1) +
191
+ "?xMin=" + startX +
192
+ "&xMax=" + endX +
193
+ "&yMin=" + startY +
194
+ "&yMax=" + endY);
195
+ isDown=false;
196
+ }
197
+ }
198
+
199
+ function handleMouseOut(e){
200
+ e.preventDefault();
201
+ e.stopPropagation();
202
+ // the drag is over, clear the dragging flag
203
+ if(isDown){
204
+ endX = parseInt((image.width / c.scrollWidth) * e.offsetX);
205
+ endY = parseInt((image.height / c.scrollHeight) * e.offsetY);
206
+ [startX,startY,endX,endY] = calcPoints(startX,startY,endX,endY)
207
+ window.location.replace("/add/" + (labels.length + 1) +
208
+ "?xMin=" + startX +
209
+ "&xMax=" + endX +
210
+ "&yMin=" + startY +
211
+ "&yMax=" + endY);
212
+ isDown=false;
213
+ }
214
+ }
215
+
216
+ function handleMouseMove(e){
217
+ e.preventDefault();
218
+ e.stopPropagation();
219
+ // if we're not dragging, just return
220
+ if(!isDown){return;}
221
+ // get the current mouse position
222
+ mouseX=parseInt((image.width / c.scrollWidth) * e.offsetX);
223
+ mouseY=parseInt((image.height / c.scrollHeight) * e.offsetY);
224
+ ctx.strokeStyle = "red";
225
+ ctx.lineWidth="4";
226
+ ctx.stroke();
227
+ // clear the canvas
228
+ ctx.clearRect(0,0,canvas.width,canvas.height);
229
+ ctx.drawImage(image, 0, 0);
230
+ // calculate the rectangle width/height based
231
+ // on starting vs current mouse position
232
+ var width=mouseX-startX;
233
+ var height=mouseY-startY;
234
+
235
+ // draw a new rect from the start position
236
+ // to the current mouse position
237
+ ctx.strokeRect(startX,startY,width,height);
238
+ }
239
+ // listen for mouse events
240
+ $("#canvas").mousedown(function(e){handleMouseDown(e);});
241
+ $("#canvas").mousemove(function(e){handleMouseMove(e);});
242
+ $("#canvas").mouseup(function(e){handleMouseUp(e);});
243
+ $("#canvas").mouseout(function(e){handleMouseOut(e);});
244
+
245
+ </script>
246
+ </div>
247
+ <!-- FOOTER ADDED HERE -->
248
+ <footer class="mt-8 mb-4 text-center text-gray-500 text-sm w-full max-w-4xl">
249
+ Thanks Huggingface 🤗
250
+ </footer>
251
+ </body>
252
+ </html>