Skip to content

Commit 6038e0b

Browse files
committed
imglab: add yolo export
1 parent c0b1ad9 commit 6038e0b

File tree

5 files changed

+227
-22
lines changed

5 files changed

+227
-22
lines changed

imglab/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Amit Kumar Gupta
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

imglab/index.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html>
33

44
<head>
5+
56
<link rel="apple-touch-icon" sizes="57x57" href="img/favicons/apple-icon-57x57.png">
67
<link rel="apple-touch-icon" sizes="60x60" href="img/favicons/apple-icon-60x60.png">
78
<link rel="apple-touch-icon" sizes="72x72" href="img/favicons/apple-icon-72x72.png">
@@ -30,7 +31,7 @@
3031
gtag('config', 'G-4812VFLT3M');
3132
</script>
3233
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4922790412146333" crossorigin="anonymous"></script>
33-
34+
3435
<title>ImgLab - Image Annotation tool</title>
3536

3637
<link rel="stylesheet" href="css/style.min.css">
@@ -154,6 +155,7 @@
154155
</script>
155156
<script src="js/settings.js"></script>
156157
<script src="js/thirdparty/svg.min.js"></script>
158+
<script src="js/thirdparty/jszip.min.js"></script>
157159
<script src="js/thirdparty/svg.draw.min.js"></script>
158160
<script src="js/thirdparty/svg.select.min.js"></script>
159161
<script src="js/thirdparty/svg.resize.min.js"></script>

imglab/js/prompt.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ $(function () {
22
$.confirm({
33
title: "",
44
content: `
5-
<p style="color: red;text-align: center;font-size: 2em;">Please bookmark the new URL. Imglab.in will no longer be in use. And ready for sale. Contact me on linkedin or github.</p>
65
<div id="featurepopup">
76
<div class="row text-center">
87
<div class="col-md-3">
@@ -88,19 +87,18 @@ function displayDonationPrompt() {
8887
title: "Donate",
8988
content: `<div style="text-align:center;">
9089
91-
<div><a onclick="javascript:logSponsor('paypal')" href="https://paypal.me/naturalintelligence" target="_blank"><img src="img/support_paypal.svg" width="200px"></a></div>
92-
<div><a onclick="javascript:logSponsor('github')" href="https://github.com/sponsors/NaturalIntelligence" target="_blank"><img src="https://github.com/NaturalIntelligence/ThankYouBackers/raw/main/github_sponsor.png" ></a></div>
90+
<div><a onclick="javascript:logPaypal()" href="https://paypal.me/naturalintelligence" target="_blank"><img src="img/support_paypal.svg" width="200px"></a></div>
9391
9492
<div>`,
9593
escapeKey: true,
9694
backgroundDismiss: true,
9795
});
9896
}
9997

100-
function logSponsor(target) {
98+
function logPaypal() {
10199
gtag("event", "click", {
102100
event_category: "outbound",
103-
event_label: target,
101+
event_label: "paypal",
104102
transport_type: "beacon",
105103
});
106104
}

imglab/js/savefile.js

+187-16
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@ function selectFileTypeToSave(){
22
$.dialog({
33
title: 'Save/Export as',
44
content: `<div style="text-align:center;">
5-
<div>
6-
<button class="btn btn-primary savebtn" onclick="javascript:saveAsNimn()" id="saveAsNimn">Project file</button>
7-
</div>
8-
<div>
9-
<button class="btn btn-primary savebtn" onclick="javascript:saveAsDlibXML()" id="saveAsNimn">Dlib XML</button>
10-
</div>
11-
<div>
12-
<button class="btn btn-primary savebtn" onclick="javascript:saveAsDlibPts()" id="saveAsNimn">Dlib pts</button>
13-
</div>
14-
<div>
15-
<button class="btn btn-primary savebtn" onclick="javascript:saveAsCOCO()" id="saveAsCOCO">COCO JSON</button>
16-
</div>
17-
<div>
18-
<button class="btn btn-primary savebtn" onclick="javascript:saveAsPascalVOC()" id="saveAsPascalVOC">Pascal VOC XML</button>
19-
</div>
20-
<div>`,
5+
<div>
6+
<button class="btn btn-primary savebtn" onclick="javascript:saveAsNimn()" id="saveAsNimn">Project file</button>
7+
</div>
8+
<div>
9+
<button class="btn btn-primary savebtn" onclick="javascript:saveAsDlibXML()" id="saveAsNimn">Dlib XML</button>
10+
</div>
11+
<div>
12+
<button class="btn btn-primary savebtn" onclick="javascript:saveAsDlibPts()" id="saveAsNimn">Dlib pts</button>
13+
</div>
14+
<div>
15+
<button class="btn btn-primary savebtn" onclick="javascript:saveAsCOCO()" id="saveAsCOCO">COCO JSON</button>
16+
</div>
17+
<div>
18+
<button class="btn btn-primary savebtn" onclick="javascript:saveAsPascalVOC()" id="saveAsPascalVOC">Pascal VOC XML</button>
19+
</div>
20+
<div>
21+
<button class="btn btn-primary savebtn" onclick="javascript:saveAsYoloV5Pytorch()" id="saveAsYoloV5Pytorch">YOLO V5 Pytorch</button>
22+
</div>
23+
<div>`,
2124
escapeKey: true,
2225
backgroundDismiss: true,
2326
});
@@ -131,6 +134,174 @@ function saveAsPascalVOC(){
131134

132135
}
133136

137+
/**
138+
* Save labelled data as YOLO supported TXT Files file.
139+
* It will export files in a zip format
140+
*/
141+
142+
function saveAsYoloV5Pytorch() {
143+
// COUNT TOTAL MASKED IMAGES
144+
var totalLabbeledImages = 0;
145+
for (const key in labellingData) {
146+
if(labellingData[key].shapes[0] && labellingData[key].shapes[0].points.length > 0){
147+
totalLabbeledImages++;
148+
}
149+
}
150+
151+
// MODAL FOR RECEIVING DATA SPLIT INPUTS
152+
$.dialog({
153+
title: `Split ${totalLabbeledImages} Labeled Images into :`,
154+
content: `<div class="col w-75 m-auto">
155+
<div class="row">
156+
<div ref="label-data" class="col"> Train </div>
157+
<div ref="label-data" class="col"> Test </div>
158+
<div ref="label-data" class="col"> Valid </div>
159+
</div>
160+
<div class="row justify-content-between">
161+
<input type="text" class="col" value="${Math.floor(totalLabbeledImages/100*70)}" onchange="javascript:validateImageSplit()" style="width: 20px;" placeholder="">
162+
<input type="text" class="col" value="${Math.floor(totalLabbeledImages/100*30)}" onchange="javascript:validateImageSplit()" style="width: 20px;" placeholder="">
163+
<input type="text" class="col" value="${Math.floor(totalLabbeledImages/100*10)}" onchange="javascript:validateImageSplit()" style="width: 20px;" placeholder="">
164+
</div>
165+
</div>
166+
<div class="d-flex justify-content-center">
167+
<span class="mt-2 w-75" id="splitMsg" > </span>
168+
</div>
169+
<div class="d-flex" style="text-align:center;">
170+
<button class="btn btn-primary savebtn" onclick="javascript:yoloDataRendering()">Generate</button>
171+
</div>`,
172+
escapeKey: true,
173+
backgroundDismiss: true,
174+
});
175+
}
176+
177+
function validateImageSplit(){
178+
// COLLECTING USER INPUT VALUES
179+
const valueElements = document.querySelectorAll('[style="width: 20px;"]');
180+
const trainImages = parseInt(valueElements[0].value);
181+
const testImages = parseInt(valueElements[1].value);
182+
const validImages = parseInt(valueElements[2].value);
183+
184+
var totalLabbeledImages = 0;
185+
for (const key in labellingData) {
186+
if(labellingData[key].shapes[0] && labellingData[key].shapes[0].points.length > 0){
187+
totalLabbeledImages++;
188+
}
189+
}
190+
191+
var remainingImages = totalLabbeledImages -trainImages -testImages -validImages;
192+
if(remainingImages > 0){
193+
document.getElementById('splitMsg').innerText = `${remainingImages} Labelled Images remaining to split.`;
194+
}
195+
196+
if(trainImages + testImages + validImages == totalLabbeledImages){
197+
document.getElementById('splitMsg').innerText = "";
198+
return [trainImages, testImages, validImages, true];
199+
}else{
200+
showSnackBar(`Total of ( Train+Test+Valid ) must be ${totalLabbeledImages}.`);
201+
return [0, 0, 0, false];
202+
}
203+
}
204+
205+
function yoloDataRendering() {
206+
var [train, test, valid, isValuesValid] = validateImageSplit();
207+
208+
// CHECK FOR VALID INPUTS
209+
if (!isValuesValid) return;
210+
211+
// COLLECT AND SAVE UNIQUE LABELS
212+
const labels = new Set();
213+
for (const image in labellingData) {
214+
const img = labellingData[image];
215+
img.shapes.forEach((shape) => {
216+
labels.add(shape.label);
217+
});
218+
}
219+
220+
const zip = new JSZip();
221+
const finalLabels = [...labels];
222+
223+
var trainImgLimit = train;
224+
var testImgLimit = train + test;
225+
var validImgLimit = testImgLimit + valid;
226+
var currImage = 0;
227+
228+
// CREATE DATA.YAML FILE
229+
const dataYamlFile = `train: ../train/images\nval: ../valid/images\ntest: ../test/images\n\nnc: ${finalLabels.length}\nnames: ['${finalLabels.join("','")}']`;
230+
zip.file("data.yaml", dataYamlFile);
231+
232+
// GENERATE YOLO REQUIRED FORMAT DATA
233+
for (const image in labellingData) {
234+
let outputArr = [];
235+
let fileName = image.substring(0, image.lastIndexOf(".")) + ".txt";
236+
237+
// RECONSTRUCT IMAGES
238+
let base64Image = document.querySelector(`[label="${image}"]`).src;
239+
var byteCharacters = atob(base64Image.split(",")[1]);
240+
var byteNumbers = new Array(byteCharacters.length);
241+
for (var i = 0; i < byteCharacters.length; i++) {
242+
byteNumbers[i] = byteCharacters.charCodeAt(i);
243+
}
244+
var byteArray = new Uint8Array(byteNumbers);
245+
var blob = new Blob([byteArray], { type: "image/jpg" }); // Specify the appropriate MIME type
246+
247+
// ITRATE THROUGH EACH SHAPE
248+
labellingData[image].shapes.forEach((shape) => {
249+
let currArr = [];
250+
currArr.push(finalLabels.indexOf(shape.label));
251+
252+
// CALCULATE POINTS
253+
if (shape.points[0][0]) {
254+
let coordinates = shape.points;
255+
coordinates.forEach((point) => {
256+
let xNorm = point[0] / labellingData[image].size.width;
257+
let yNorm = point[1] / labellingData[image].size.height;
258+
currArr.push(xNorm, yNorm);
259+
});
260+
} else {
261+
let [xMin, yMin, w, h] = shape.points;
262+
const yoloXCenter = (xMin + w / 2) / labellingData[image].size.width;
263+
const yoloYCenter = (yMin + h / 2) / labellingData[image].size.height;
264+
const yoloBoxWidth = w / labellingData[image].size.width;
265+
const yoloBoxHeight = h / labellingData[image].size.height;
266+
currArr.push(yoloXCenter, yoloYCenter, yoloBoxWidth, yoloBoxHeight);
267+
}
268+
outputArr.push(currArr.join(" "));
269+
});
270+
271+
// ADD FILES TO ZIP IF LABELLED
272+
if (outputArr.length > 0) {
273+
if (currImage < trainImgLimit) {
274+
saveDirectory = "train";
275+
} else if (currImage < testImgLimit) {
276+
saveDirectory = "test";
277+
} else if (currImage <= validImgLimit) {
278+
saveDirectory = "valid";
279+
}
280+
currImage++;
281+
282+
zip.file(`${saveDirectory}/images/${image}`, blob);
283+
zip.file(`${saveDirectory}/labels/${fileName}`, outputArr.join("\n"));
284+
}
285+
}
286+
287+
// SET FILE NAME
288+
var curTimeStamp = new Date();
289+
var timeStamp = `${curTimeStamp.getDate()}/ ${curTimeStamp.getMonth() + 1}/ ${curTimeStamp.getFullYear()}/
290+
${curTimeStamp.getHours()}: ${curTimeStamp.getMinutes()}: ${curTimeStamp.getSeconds()}`;
291+
292+
// CREATE ZIP AND SAVE
293+
zip.generateAsync({ type: "blob" })
294+
.then(function (content) {
295+
saveAs(content, timeStamp + ".zip");
296+
showSnackBar(`File will be downloaded automatically`);
297+
analytics_reportExportType("yoloV5PyTorch");
298+
})
299+
.catch(function (error) {
300+
showSnackBar("Error occoured while creating ZIP file");
301+
console.error("Error creating ZIP file:", error);
302+
});
303+
}
304+
134305
/**
135306
* Save given data to a file
136307
* @param {*} data

imglab/js/thirdparty/jszip.min.js

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)