source: other-projects/nz-flag-design/trunk/similarity-2d/flag-processing.js@ 29906

Last change on this file since 29906 was 29906, checked in by davidb, 9 years ago

Changes resulting from writing the TPDL submission

File size: 19.8 KB
Line 
1"use strict";
2
3var progressVal = 0;
4
5var show_progress = 5;
6
7var start_hist_delay = 1000; // used to be 4000
8var mini_delay = 300;
9
10
11//var hasLocalStorage = (typeof(Storage) !== "undefined"); // ****
12var hasLocalStorage = false;
13
14var quantHSLHistogramArray;
15
16//var saveOnServer=true;
17var saveOnServer=false;
18var retrieveFromServer=true;
19
20
21function runlength_encode(int_array) {
22
23 var array_len = int_array.length;
24
25 if (array_len<2) {
26 console.warn("runlength_encode(): unable to run-length encode array of length " + array_len);
27 console.warn("returning null");
28 return null;
29 }
30
31 var int_array_rle = new Array();
32
33
34 var lock_val=int_array[0];
35 var next_val=int_array[1];
36 var i = 1;
37
38 while (i<array_len) {
39 var c=1;
40 while (next_val==lock_val) {
41 c++;
42 var next_pos = i+c-1;
43 if (next_pos<array_len) {
44 next_val = int_array[next_pos];
45 }
46 else {
47 // hit end of array
48 break;
49 }
50 }
51 // come out of a run-length sequence of same val
52 // next_val is one past the run-length sequence
53
54 int_array_rle.push(c);
55 int_array_rle.push(lock_val);
56
57 i+=c;
58
59 if (i<array_len) {
60 lock_val = next_val;
61 next_val = int_array[i];
62 }
63 else {
64 break;
65 }
66 }
67
68 return int_array_rle;
69}
70
71
72
73function runlength_decode(int_array_rle) {
74
75 var array_rle_len = int_array_rle.length;
76
77 if (array_rle_len<2) {
78 console.warn("runlength_decode(): unable to run-length decode array of length " + array_rle_len);
79 console.warn("returning null");
80 return null;
81 }
82
83 var int_array = new Array();
84 for (var i=0; i<array_rle_len; i+=2){
85 var rep = int_array_rle[i];
86 var val = int_array_rle[i+1];
87 for (var r=0; r<rep; r++) {
88 int_array.push(val);
89 }
90 }
91
92 return int_array;
93}
94
95
96
97function arrays_identical(int_array1, int_array2) {
98
99 var array_len1 = int_array1.length;
100 var array_len2 = int_array2.length;
101
102 if (array_len1 != array_len2) {
103 console.warn("arrays_identical(): array lengths differ, array1("+array_len1+") vs array2(" + array_len2 + ")");
104 return false;
105 }
106
107 var all_ident = true;
108
109 for (var i=0; i<array_len1; i++){
110 if (int_array1[i] != int_array2[i]) {
111 all_ident = false;
112 break;
113 }
114 }
115
116 return all_ident;
117}
118
119
120function drawImage(imageObj, canvasId) {
121 var imgCanvas = document.getElementById(canvasId),
122 context, imageData, data,
123 imageX = 0, imageY = 0,
124 imageWidth = imageObj.width,
125 imageHeight = imageObj.height;
126
127 context = imgCanvas.getContext('2d');
128 context.drawImage(imageObj, imageX, imageY, imageWidth, imageHeight);
129 imageData = context.getImageData(imageX, imageY, imageWidth, imageHeight);
130
131 return imageData;
132}
133
134
135
136function createHSLHistogramFromCanvasData(imageData, imageWidth, imageHeight, quant)
137{
138 var i;
139
140 var outputLength = quant * quant * quant;
141 var outputArray = new Array(outputLength);
142 for (i=0; i<outputLength; i++) {
143 outputArray[i] = 0;
144 }
145
146 var total = 0.0;
147
148 var log2 = Math.log(2);
149
150 //calculate value to shift by (log base 2)
151
152 var shift_sat = Math.floor(Math.log(quant)/log2);
153 var shift_hue = Math.floor(Math.log(quant*quant)/log2);
154
155 //console.log("bit-shift sat = " + shift_sat);
156 //console.log("bit-shift hue = " + shift_hue);
157
158 var data = imageData.data;
159
160 //Loop through each pixel
161 var y,p;
162 for (y=0,p=0; y<imageHeight; y++) {
163 var x;
164 for (x=0; x<imageWidth; x++,p+=4) {
165 // convert 4 byte data value into a colour pixel
166 var rgb_c = new RGBColour(data[p],data[p+1],data[p+2],data[p+3]/255.0);
167 var hsl = new rgb_c.getHSL();
168
169 // express h,s and b in the range 0..1
170
171 var h = hsl.h / 360.0;
172 var s = hsl.s / 100.0;
173 var b = hsl.l / 100.0;
174
175 var result = (Math.floor(h * (quant - 1)) << shift_hue)
176 | (Math.floor(s * (quant - 1)) << shift_sat)
177 | Math.floor(b * (quant - 1));
178
179 //number of opaque pixels
180 var count = hsl.a; // 'a' is naturally in the range 0..1
181
182 //add to the total number of opaque pixels
183 total += count;
184
185 //add the frequency of each colour to the histogram
186 outputArray[result] += count;
187 }
188 }
189
190 if (total == 0) {
191 // entire image must have been blank
192 // make total '1' to stop NaN being generated
193 total = 1;
194 }
195
196 //Normalise the histogram, based on the number of opaque pixels
197 for (i=0; i<outputArray.length; i++) {
198 outputArray[i] = outputArray[i] / total;
199 }
200
201 return outputArray;
202}
203
204
205function createHSLHistogram(imageObj, quant)
206{
207 if (!$('#hsl_canvas').length) {
208 $('<canvas>', {id: 'hsl_canvas'}).hide().appendTo('body');
209 }
210
211 var imageData = drawImage(imageObj,'hsl_canvas');
212
213 var outputArray = createHSLHistogramFromCanvasData(imageData,imageObj.width,imageObj.height,quant);
214
215/*
216 if (saveOnServer) {
217 console.log("Saving HSV quantized histogram on server: " + imageObj.title.toLowerCase())
218 //console.log("outputArray = " + JSON.stringify(outputArray));
219
220 var jsonFilename = imageObj.title.toLowerCase() + ".json";
221 var data = { jsonFilename: jsonFilename, jsonData: JSON.stringify(outputArray)};
222
223 console.log("Saving colour histogram data for " + imageObj.title.toLowerCase());
224 $.ajax({
225 type: "POST",
226 url: "../similarity-2d/save-json-data.jsp",
227 data: data
228 });
229 }
230*/
231
232 return outputArray;
233}
234
235var flag_comparison_array = Array(2);
236var clicked_on_count = 0;
237
238function quantizedColourHistogramComparison(quant_colour_hist1,quant_colour_hist2)
239{
240 // Assumes both histograms are of the same length
241
242 var i;
243 var quant_product = 0.0;
244
245 //Loop through and compare the histograms
246 for (i=0; i<quant_colour_hist1.length; i++) {
247 //take the overlap
248 quant_product += Math.min(quant_colour_hist1[i], quant_colour_hist2[i]);
249
250 // consider replacing with abs for difference
251 }
252
253 //console.log("Product for HSL histogram: " + quant_product);
254
255 return quant_product;
256}
257
258
259function displayFlags(img_list,$displayDiv,$progressArea,$progressBar)
260{
261 var img_list_len = img_list.length;
262
263 if (img_list_len==0) {
264 return;
265 }
266 else if (img_list_len>show_progress) {
267 $progressArea.slideDown();
268 }
269
270 img_list.sort();
271
272 var root_img_re = /^.*\/(.*?)\..*?$/;
273
274 var progress_step = 100.0 / img_list_len;
275
276 var callback_count = 0;
277
278 var i;
279 for (i=0; i<img_list.length; i++) {
280 var img_url = img_list[i];
281 var img_matches = root_img_re.exec(img_url);
282 var title = img_matches[1];
283
284 var imageObj = new Image();
285
286 imageObj.onload = function() {
287 callback_count++;
288 progressVal += progress_step;
289 $progressBar.progressbar('value',progressVal);
290
291 if (callback_count == img_list_len) {
292 if (img_list_len>show_progress) {
293 progressVal = 100;
294 $progressBar.progressbar('value',progressVal);
295 //$progressArea.slideUp();
296
297 // Now move on and start computing colour histograms
298 setTimeout(function()
299 { doneDisplayFlags(img_list,$progressArea,$progressBar); }
300 ,start_hist_delay);
301 }
302 }
303 };
304
305
306 /*
307$("h2").live('mousedown', function(e) {
308 if( (e.which == 1) ) {
309 alert("left button");
310 }if( (e.which == 3) ) {
311 alert("right button");
312 }else if( (e.which == 2) ) {
313 alert("middle button");
314 }
315 e.preventDefault();
316}).live('contextmenu', function(e){
317 e.preventDefault();
318});
319 */
320
321 imageObj.onclick = function(e) {
322 // Store result of HSL quantization in the image object/tag
323 if (!this.quantHSLHist) {
324 // Only need to compute this if it is not already stored in the object/tag
325 this.quantHSLHist = createHSLHistogram(this,16);
326 console.log(this.quantHSLHist);
327 }
328
329 if (e.which == 1 ) {
330 // Left => use in comparison pair
331 flag_comparison_array[clicked_on_count] = this.quantHSLHist;
332
333 clicked_on_count++;
334 if (clicked_on_count==2) {
335 // trigger comparison
336 alert("Similarity score: " + quantizedColourHistogramComparison(flag_comparison_array[0],flag_comparison_array[1]) + "%");
337 clicked_on_count=0;
338 }
339 }
340 else if (e.which == 2) {
341 // Middle => fix on this and do similarity with everything else
342 var fix_id = this.id;
343 e.preventDefault();
344 }
345
346 };
347
348
349 imageObj.src = img_url;
350 imageObj.height = 216;
351 imageObj.title = title.toUpperCase();
352 imageObj.country = imageObj.title;
353 imageObj.id = "flag-img-" + i;
354
355 $displayDiv.append(imageObj);
356 }
357}
358
359
360
361
362function calcSimilarityValuesChain(newFlagQuantHSLHist,
363 chain_count, img_obj_list, img_obj_list_len,
364 progressVal, progress_step,
365 $progressArea,$progressBar)
366{
367 var imgObj = img_obj_list[chain_count];
368
369 var existingQuantHSLHist = imgObj.quantHSLHist;
370
371 var similarity_score = quantizedColourHistogramComparison(newFlagQuantHSLHist,existingQuantHSLHist);
372 similarity_score = Math.round(similarity_score * 100) / 100; // round to 2 decimal places
373 similarity_score *= 100; // express as percentage
374
375 imgObj.similarityScore = similarity_score;
376 var img_title = imgObj.country;
377
378 if (isoCountryCodes != null) {
379 var country_code = imgObj.country;
380 img_title = isoCountryCodes[country_code] + " (" + country_code + ")";
381 }
382 imgObj.title = img_title + ", Similarity score: " + similarity_score + "%";
383 if (similarity_score>50) {
384 //imgObj.style.display = "block";
385 //imgObj.style.visibility = "visible";
386 }
387 else {
388 //imgObj.style.display = "none";
389 //imgObj.style.visibility = "hidden";
390 }
391
392
393
394 if (chain_count < img_obj_list_len-1) {
395 progressVal += progress_step;
396 if ($progressBar.progressbar) {
397 $progressBar.progressbar('value',progressVal);
398 }
399
400 setTimeout(function()
401 { calcSimilarityValuesChain(newFlagQuantHSLHist,
402 chain_count+1,img_obj_list, img_obj_list_len,
403 progressVal, progress_step,
404 $progressArea,$progressBar)
405 }
406 ,1 // any time delay causes break in 'rendering' thread, even 0!
407 );
408
409 }
410 else {
411 // done
412 $progressArea.find('span:first').text("Done.");
413 if (img_obj_list_len>show_progress) {
414 progressVal = 100;
415 if ($progressBar.progressbar) {
416 $progressBar.progressbar('value',progressVal);
417 }
418 $progressArea.slideUp();
419 }
420 }
421
422}
423
424function calcSimilarityValues(newFlagQuantHSLHist, $displayDiv,$progressArea,$progressBar)
425{
426 console.log("*** calcSimilarityValues()");
427
428 var iframe = $('#similarity-2d-iframe').contents();
429
430 var img_obj_list = iframe.find('#flagArea').children('img').toArray();
431
432 var img_obj_list_len = img_obj_list.length;
433 console.log("*** img obj list len = " + img_obj_list_len);
434
435 if (img_obj_list_len==0) {
436 return;
437 }
438 else if (img_obj_list_len>show_progress) {
439 // number of images to process non-trivial
440 $progressArea.slideDown();
441 }
442
443 for (var i=0; i<img_obj_list.length; i++) {
444 var imgObj = img_obj_list[i];
445 imgObj.style.display = "inline";
446 //imgObj.style.visibility = "visible";
447 }
448
449 var progress_step = 100.0 / img_obj_list_len;
450/*
451 var chain_count=0;
452
453 setTimeout(function() {
454 calcSimilarityValuesChain(newFlagQuantHSLHist,
455 chain_count, img_obj_list, img_obj_list_len,
456 progressVal, progress_step,
457 $progressArea,$progressBar)
458 }
459 ,100
460 );
461*/
462
463
464 for (var i=0; i<img_obj_list.length; i++) {
465 var imgObj = img_obj_list[i];
466
467 var existingQuantHSLHist = imgObj.quantHSLHist;
468
469 var similarity_score = quantizedColourHistogramComparison(newFlagQuantHSLHist,existingQuantHSLHist);
470 similarity_score = Math.round(similarity_score * 100) / 100; // round to 2 decimal places
471 similarity_score *= 100; // express as percentage
472
473 imgObj.similarityScore = similarity_score;
474 var img_title = imgObj.country;
475
476 if (isoCountryCodes != null) {
477 var country_code = imgObj.country;
478 img_title = isoCountryCodes[country_code] + " (" + country_code + ")";
479 }
480 imgObj.title = img_title + ", Similarity score: " + similarity_score + "%";
481 if (similarity_score>50) {
482 //imgObj.style.display = "inline";
483 //imgObj.style.visibility = "visible";
484 }
485 else {
486 //imgObj.style.display = "none";
487 //imgObj.style.visibility = "hidden";
488 }
489
490 progressVal += progress_step;
491 //console.log("*** i = " + i);
492 //console.log("*** pbar = " + $progressBar.progressbar);
493 if ($progressBar.progressbar) {
494 $progressBar.progressbar('value',progressVal);
495 }
496 }
497
498 //progressVal = 100;
499 //$progressBar.progressbar('value',progressVal);
500
501 if (img_obj_list_len>show_progress) {
502 $progressArea.slideUp();
503 }
504
505
506}
507
508function sortUsingAttribute(parent, childSelector, field) {
509 var items = parent.children(childSelector).sort(function(a, b) {
510 var vA = a.attr(field);
511 var vB = b.attr(field)
512 return (vA < vB) ? -1 : (vA > vB) ? 1 : 0;
513 });
514
515 parent.empty(childSelecctor);
516 parent.append(items);
517}
518
519
520function compareSimilarityValue(a,b) {
521
522 //var vA = a.similarityScore;
523 //var vB = b.similarityScore;
524 //return (vA < vB) ? -1 : (vA > vB) ? 1 : 0;
525
526 return (b.similarityScore - a.similarityScore);
527}
528
529function sortFlagsBySimilarityValue($iframe)
530{
531 var img_obj_list = $iframe.find('#flagArea > img').toArray();
532
533 var sorted_img_obj_list = img_obj_list.sort(compareSimilarityValue);
534 //console.log("*** sorted img len = " + sorted_img_obj_list.length);
535
536 $iframe.find('#flagArea').empty('img');
537
538 $iframe.find('#flagArea').append(sorted_img_obj_list);
539
540 //sortUsingAttribute('#flagArea','img','similarityValue');
541}
542
543
544
545function computeFlagHistogramChain(chain_count, img_list_len,
546 progressVal, progress_step,
547 $progressArea,$progressBar)
548{
549 var flag_id = "flag-img-" + chain_count;
550 console.log("Processing Image ID: " + flag_id);
551 var imageObj = document.getElementById(flag_id);
552
553 if (!imageObj.quantHSLHist) {
554 // Only need to compute this if it is not
555 // already stored in the object/tag
556
557 if (retrieveFromServer) {
558 // see if it is stored on the server
559 console.log("Retrieving stored colour histogram for " + imageObj.title.toLowerCase() + " ...");
560 $.ajax({
561 async: false,
562 dataType: "json",
563 url: "../similarity-2d/json-data/" + imageObj.title.toLowerCase() + ".json",
564 success: function(data) {
565 console.log("... success");
566 // 'data' is the array of integer vals we want
567 imageObj.quantHSLHist = data;
568 },
569 error: function() {
570 console.log("... not stored on server");
571 // not stored on the server (currently)
572 // => need to compute it
573 imageObj.quantHSLHist = createHSLHistogram(imageObj,16);
574 //console.log(imageObj.quantHSLHist);
575
576 }
577 });
578 }
579 else {
580 imageObj.quantHSLHist = createHSLHistogram(imageObj,16);
581 }
582
583 }
584
585 if (chain_count < img_list_len-1) {
586 progressVal += progress_step;
587 $progressBar.progressbar('value',progressVal);
588
589 setTimeout(function()
590 { computeFlagHistogramChain(chain_count+1,img_list_len,
591 progressVal, progress_step,
592 $progressArea,$progressBar)
593 }
594 ,mini_delay // any time delay causes break in 'rendering' thread, even 0!
595 );
596
597 }
598 else {
599 $progressArea.find('span:first').text("Done.");
600 if (img_list_len>show_progress) {
601 progressVal = 100;
602 $progressBar.progressbar('value',progressVal);
603 $progressArea.slideUp();
604 }
605
606 doneComputingHistograms(img_list_len);
607 }
608}
609
610
611function computeFlagHistograms(img_list,$progressArea,$progressBar)
612{
613 var img_list_len = img_list.length;
614
615
616 if (img_list_len==0) {
617 $progressArea.find('span:first').text("Empty list: no computation needed.");
618 return;
619 }
620
621
622 $progressArea.find('span:first').text("Computing histograms:");
623 progressVal = 0;
624
625 if (img_list_len>show_progress) {
626 $progressArea.slideDown();
627 }
628
629 var progress_step = 100.0 / img_list_len;
630
631 // Start the chaining process
632 setTimeout(function()
633 { computeFlagHistogramChain(0,img_list_len,
634 progressVal, progress_step,
635 $progressArea,$progressBar)
636 }
637 ,mini_delay // any time delay causes break in 'rendering' thread, even 0!
638 );
639
640}
641
642function doneComputingHistograms(img_list_len)
643{
644 quantHSLHistogramArray = new Array(img_list_len);
645 for (var i=0; i<img_list_len; i++) {
646 var flag_id = "flag-img-" + i;
647 var imageObj = document.getElementById(flag_id);
648 quantHSLHistogramArray[i] = imageObj.quantHSLHist;
649 }
650 var json_hist_data = JSON.stringify(quantHSLHistogramArray);
651
652 if (hasLocalStorage) {
653 // build up quantHSLHistogramArray from all the images
654
655 console.log("Storing computed HSL histograms in localStorage");
656 localStorage.quantHSLHistogramArray = json_hist_data;
657 }
658
659 if (saveOnServer) {
660 // run-length encode data, so less to send
661 var quantHSLHistogramArrayRLE = new Array(img_list_len);
662 for (var i=0; i<img_list_len; i++) {
663 //console.log("rle encoding ... i = " + i);
664 quantHSLHistogramArrayRLE[i] = runlength_encode(quantHSLHistogramArray[i])
665
666 //var test_decode_array = runlength_decode(quantHSLHistogramArrayRLE[i]);
667 //console.log("Checking decoded array: " + arrays_identical(test_decode_array,quantHSLHistogramArray[i]));
668 }
669
670
671 var json_hist_data_rle = JSON.stringify(quantHSLHistogramArrayRLE);
672
673 var json_filename = "all-hsv-histograms-rle.json";
674 var data = { jsonFilename: json_filename, jsonData: json_hist_data_rle};
675
676 console.log("Saving colour histogram data for all flags");
677 $.ajax({
678 type: "POST",
679 url: "../similarity-2d/save-json-data.jsp",
680 data: data
681 });
682 }
683}
684
685function doneDisplayFlags(img_list,$progressArea,$progressBar)
686{
687 console.log("doneDisplayFlags()");
688
689 var needToComputeHistograms = true;
690
691 if (hasLocalStorage) {
692
693 if (localStorage.quantHSLHistogramArray) {
694
695 console.log("Retrieving quantized HSL histograms from local storage");
696
697 var stored_hist_array = JSON.parse(localStorage["quantHSLHistogramArray"]);
698 if (stored_hist_array.length == img_list.length) {
699 // Assume if the number of images process hasn't changed, then neither
700 // has the stored historgram values
701
702 quantHSLHistogramArray = stored_hist_array;
703
704 var i;
705 for (i=0; i<img_list.length; i++) {
706 var flag_id = "flag-img-" + i;
707 var imageObj = document.getElementById(flag_id);
708 imageObj.quantHSLHist = quantHSLHistogramArray[i];
709 }
710
711 needToComputeHistograms = false;
712 }
713 else {
714 console.log("Different number of images detected => regenerating");
715 }
716 }
717 }
718
719 if (needToComputeHistograms) {
720
721 if (retrieveFromServer) {
722 // before computing histograms from scratch, see if resulting data stored on server
723
724 console.log("Retrieving stored colour histgram data for all flags ...");
725 $.ajax({
726 async: true,
727 dataType: "json",
728 url: "../similarity-2d/json-data/all-hsv-histograms-rle.json",
729 success: function(rle_data) {
730 console.log("... success");
731 if (rle_data.length != img_list.length) {
732 console.log("Stored histogram data out of date with flag list.");
733 console.log("rle len = " + rle_data.length + ", img_list len = " + img_list.length);
734 // what is stored on the sever differed to the number of images displayed
735 computeFlagHistograms(img_list,$progressArea,$progressBar);
736 }
737 else {
738 var img_list_len = img_list.length;
739 quantHSLHistogramArray = new Array(img_list_len);
740
741 // Need to rle decode each entry in this array
742 for (var i=0; i<img_list_len; i++) {
743 quantHSLHistogramArray[i] = runlength_decode(rle_data[i]);
744 var flag_id = "flag-img-" + i;
745 var imageObj = document.getElementById(flag_id);
746 imageObj.quantHSLHist = quantHSLHistogramArray[i];
747
748 }
749
750 needToComputeHistograms = false;
751
752 $progressArea.slideUp();
753 doneComputingHistograms(img_list.length);
754
755 }
756 },
757 error: function() {
758 console.log("... not stored on server");
759 computeFlagHistograms(img_list,$progressArea,$progressBar);
760 }
761 });
762
763
764 }
765 else {
766 computeFlagHistograms(img_list,$progressArea,$progressBar);
767 }
768 }
769 else {
770 $progressArea.slideUp();
771 doneComputingHistograms(img_list.length);
772 }
773}
774
775function rankBySimilarity(imageData,x_dim,y_dim)
776{
777 // quantizedColourHistogramComparison(quant_colour_hist1,quant_colour_hist2)
778
779}
Note: See TracBrowser for help on using the repository browser.