/* BTDZS tool basic © 2013 by Russell Cottrell Please visit russellcottrell.com */ #target photoshop app.bringToFront(); if (app.documents.length == 0) alert("Please open at least one document."); else { // main // var strtRulerUnits = app.preferences.rulerUnits; app.preferences.rulerUnits = Units.PIXELS; var docW = parseInt(app.activeDocument.width); var docH = parseInt(app.activeDocument.height); var docMin = Math.min(docW, docH); var sampleW, sampleH; var docCount = app.documents.length; var startW = 50; var startH = 20; var startPxCount = startW * startH; var pxCount = startPxCount; var secPPC = 38; var baseT = Math.round((docCount * (secPPC / 60)) * 10) / 10; // seconds per pxCount / 60, rounded var ok = true; var docWMin = 1000000; var docHMin = 1000000; var docArray = new Array(); for (var k=0; k 1 ? 's' : '') + ' open.' } dlg.inputGrp.prog = dlg.inputGrp.add('progressbar'); with (dlg.inputGrp.prog) { location = [20, 270]; size = [360, 10]; value = 0; maxvalue = 360; visible = false; } dlg.inputGrp.btnGrp = dlg.inputGrp.add('group'); with (dlg.inputGrp.btnGrp) { location = [135, 135]; size = [130, 24]; } dlg.inputGrp.btnGrp.okBtn = dlg.inputGrp.btnGrp.add("button", undefined, "ok", { name:"ok" }); with (dlg.inputGrp.btnGrp.okBtn) { location = [0, 0]; size = [60, 24]; } dlg.inputGrp.btnGrp.okBtn.onClick = function() { with (dlg.inputGrp) { titleGrp.enabled = false; dataTxt.enabled = true; sortGrp.enabled = false; btnGrp.okBtn.enabled = false; } iniTxtOrder = dlg.inputGrp.sortGrp.eOrder.selection.index; iniFile.open("w"); iniFile.writeln(iniTxtOrder); iniFile.close(); doIt(); } dlg.inputGrp.btnGrp.cancelBtn = dlg.inputGrp.btnGrp.add("button", undefined, "cancel", { name:"cancel" }); with (dlg.inputGrp.btnGrp.cancelBtn) { location = [70, 0]; size = [60, 24]; } dlg.inputGrp.btnGrp.cancelBtn.onClick = function() { ok = false; dlg.close(); } dlg.inputGrp.helpTxt = dlg.inputGrp.add('edittext', undefined, undefined, { multiline:true } ); with (dlg.inputGrp.helpTxt) { location = [20, 185]; size = [360, 335]; text = 'To use the BTDZS tool, start with a series of images of a uniform target, preferably out-of-focus to eliminate any actual image detail.\r\n\r\n' + 'Append zone numbers to the filenames after an underscore and before the file extension, in the form of IMG_0123_VI.tif for zone VI, and the script will automatically extract them. (Tip: when doing multiple batch processes, do this to the RAW files.)\r\n\r\n' + 'Crop a nice uniform central area; 100-300px square is a convenient size range; but don’t otherwise resize/resample them.\r\n\r\n' + 'Process them together, then open them all in Photoshop. Run the script: File–Scripts–Browse.\r\n\r\n' + 'Note: when saving the text or .csv, files with the same name will be overwritten without warning. Unless the file is already open by another application, in which case it will not be saved, again without warning. It’s a Photoshop scripting thing.\r\n\r\n' + 'Tested with Photoshop CS2 and CS5 using Windows XP 32-bit, and Windows 7 64-bit.\r\n\r\n' + '© 2013 by Russell Cottrell\r\n' + 'Beyond the Digital Zone System\r\n' + 'Please visit russellcottrell.com'; } dlg.outputGrp = dlg.add('group'); with (dlg.outputGrp) { location = [0, 0]; size = [400, 540]; visible = false; } dlg.outputGrp.output = dlg.outputGrp.add('edittext', undefined, undefined, { multiline:true }); with (dlg.outputGrp.output) { location = [10, 10]; size = [380, 486]; } dlg.outputGrp.btnGrp = dlg.outputGrp.add('group'); with (dlg.outputGrp.btnGrp) { location = [10, 506]; size = [380, 24]; } dlg.outputGrp.btnGrp.saveBtn = dlg.outputGrp.btnGrp.add("button", undefined, "save text"); with (dlg.outputGrp.btnGrp.saveBtn) { location = [85, 0]; size = [70, 24]; } dlg.outputGrp.btnGrp.saveBtn.onClick = saveTxt; dlg.outputGrp.btnGrp.saveCsvBtn = dlg.outputGrp.btnGrp.add("button", undefined, "save .csv"); with (dlg.outputGrp.btnGrp.saveCsvBtn) { location = [165, 0]; size = [70, 24]; } dlg.outputGrp.btnGrp.saveCsvBtn.onClick = saveCsv; dlg.outputGrp.btnGrp.cancelBtn = dlg.outputGrp.btnGrp.add("button", undefined, "close", { name:"cancel" }); with (dlg.outputGrp.btnGrp.cancelBtn) { location = [320, 0]; size = [60, 24]; } dlg.outputGrp.btnGrp.cancelBtn.onClick = function() { dlg.close(); } dlg.show(); function setText() { var wMin, hMin; wMin = docW; hMin = docH; dlg.inputGrp.dataTxt.text = txt; } ////////////////////////////// function doIt() { // do it // var tTxt = ""; if (dlg.inputGrp.titleGrp.titleTxt.text.replace(/\s/g, "") != "") { tTxt = dlg.inputGrp.titleGrp.titleTxt.text; bigTxt = tTxt + "\r\n\r\n========\r\n\r\n"; } var strtTime = new Date().getTime(); var endTime, t; var pixelTxt = ""; csv = "image,,zone\r\n" + ",\r\n" + tTxt + ",\r\n" + ",,,L*mean,L* SNR"; var totalPxCount = 0; var tpc = 0; var progMax = dlg.inputGrp.prog.maxvalue; var LOnly = true; for (k=0; k 0) { if (LMin > i) LMin = i; if (LMax < i) LMax = i; } } LMean = LSum / pxCount; for (i = 0; i <= 255; i++) { temp = i - LMean; DELSquaredSum += temp * temp * pChannel[i]; DELCubedSum += Math.pow(temp, 3) * pChannel[i]; if (temp < 0) { nSquaredSum += temp * temp * pChannel[i]; nN += pChannel[i]; } else if (temp > 0) { pSquaredSum += temp * temp * pChannel[i]; pN += pChannel[i]; } } temp = 100 / 255; // histogram range is 0-255; L* is 0-100 LMean *= temp; LMin *= temp; LMax *= temp; DELSquaredSum *= Math.pow(temp, 2); nSquaredSum *= Math.pow(temp, 2); pSquaredSum *= Math.pow((100 / 255), 2); DELCubedSum *= Math.pow(temp, 3); tpc += pxCount; dlg.inputGrp.prog.value = Math.round((tpc / totalPxCount) * progMax); if (!ok) { // cancel button docRef.activeHistoryState = docRef.historyStates[docRef.historyStates.length - (2 + hs)]; app.preferences.rulerUnits = strtRulerUnits; return; } } // get pixel data // docRef.activeHistoryState = docRef.historyStates[docRef.historyStates.length - (2 + hs)]; LSD = Math.sqrt(DELSquaredSum / pxCount); if (roundTo(LSD) != 0) SNRL = LMean / LSD; else if (roundTo(LMean) == 0) SNRL = "undefined"; else SNRL = "infinity"; SNRLdB = toDecibels(SNRL); nSemiD = (nN > 0 ? Math.sqrt(nSquaredSum / nN) : 0); pSemiD = (pN > 0 ? Math.sqrt(pSquaredSum / pN) : 0); if (roundTo(LSD) != 0) skew = DELCubedSum / (pxCount * Math.pow(LSD, 3)); else skew = 0; docArray[k]["txt1"] = docRef.name + "\r\n\r\n" + "n = " + pxCount + "\r\n\r\n" + "L*mean = " + roundTo(LMean) + "\r\n" + "SNR = " + (isFinite(SNRL) ? roundTo(SNRL) : SNRL) + (!LOnly ? "/" + (isFinite(SNR) ? roundTo(SNR) : SNR) : "") + "\r\n\r\n"; // + docArray[k]["txt2"] = ""; docArray[k]["txt3"] = "========\r\n\r\n"; docArray[k]["csv1"] = "\r\n" + docRef.name + ",," + extractZone(docRef.name) + "," + roundTo(LMean) + "," + (isFinite(SNRL) ? roundTo(SNRL) : ""); docArray[k]["csv2"] = ""; docArray[k]["csv3"] = ""; docArray[k]["LMean"] = LMean; docArray[k]["DESD"] = DESD; docArray[k]["LSD"] = LSD; } // image loop // dlg.inputGrp.dataTxt.text = "Analyzing . . ." var LMid = 50; if (iniTxtOrder == 2) { // find the midpoint if sorted middle outward docArray = docArray.sort(function(a, b) { var aName = a["img"].name; var bName = b["img"].name; if (aName < bName) return -1; else if (aName > bName) return 1; else return 0; } ); LMid = docArray[0]["LMean"]; } docArray = docArray.sort(function(a, b) { var aMean = parseFloat(a["LMean"]); var bMean = parseFloat(b["LMean"]); var aName = a["img"].name; var bName = b["img"].name; // 0 = dark to light // 1 = light to dark // 2 = middle outward // 3 = none if ((iniTxtOrder == 0) || ((iniTxtOrder == 2) && (aMean > LMid) && (bMean > LMid))) { if (aName < bName) return -1; // a is darker else if (aName > bName) return 1; // a is lighter else return 0; } else if ((iniTxtOrder == 1) || ((iniTxtOrder == 2) && (aMean < LMid) && (bMean < LMid))) { if (aName > bName) return -1; else if (aName < bName) return 1; else return 0; } else if ((iniTxtOrder == 2) || (iniTxtOrder == 3)) { if (aMean < bMean) return -1; else if (aMean > bMean) return 1; else return 0; } } ); for (k=0; k 1 ? 's' : ''); bigTxt += "\r\n\r\n" + ".csv file:\r\n\r\n" + csv; dlg.outputGrp.output.text = bigTxt; dlg.inputGrp.visible = false; dlg.outputGrp.visible = true; } // do it // } // main // function getLabColor(x, y) { docRef.selection.select( new Array (new Array(x,y), new Array(x+1,y), new Array(x+1,y+1), new Array(x,y+1)), SelectionType.REPLACE); var pChannel; var pColor = new SolidColor(); var i; pChannel = docRef.channels["Lightness"].histogram; for (i = 0; i <= 255; i++) { if (pChannel[i]) { pColor.lab.l = i * (100/255); break; } } pChannel = docRef.channels["a"].histogram; for (i = 0; i <= 255; i++) { if (pChannel[i]) { pColor.lab.a = i - 128; break; } } pChannel = docRef.channels["b"].histogram; for (i = 0; i <= 255; i++) { if (pChannel[i]) { pColor.lab.b = i - 128; break; } } docRef.activeHistoryState = docRef.historyStates[docRef.historyStates.length - 2]; return pColor; } function DeltaE(referenceColor, anotherColor, scale) { var temp = (scale ? 100/255 : 1); // a and b scaled to a range of 100 var L1 = referenceColor.lab.l; var a1 = referenceColor.lab.a * temp; var b1 = referenceColor.lab.b * temp; var L2 = anotherColor.lab.l; var a2 = anotherColor.lab.a * temp; var b2 = anotherColor.lab.b * temp; return Math.sqrt( Math.pow(L2 - L1, 2) + Math.pow(a2 - a1, 2) + Math.pow(b2 - b1, 2)); } function DeltaAB(referenceColor, anotherColor, scale) { var temp = (scale ? 100/255 : 1); // a and b scaled to a range of 100 var a1 = referenceColor.lab.a * temp; var b1 = referenceColor.lab.b * temp; var a2 = anotherColor.lab.a * temp; var b2 = anotherColor.lab.b * temp; return Math.sqrt( Math.pow(a2 - a1, 2) + Math.pow(b2 - b1, 2)); } function roundTo(n) { n = (Math.round(n * 1000) / 1000).toString(); if (n.search(/\./) == -1) n += "."; n += "000"; return n.substr(0, n.search(/\./) + 4); } function saveTxt() { var outputFile = File.saveDialog('Save Text', 'Text Documents:*.txt, All Files:*'); if (outputFile) { outputFile.encoding = "UTF-8"; outputFile.open("w"); outputFile.write(bigTxt); outputFile.close(); } } function saveCsv() { var outputFile = File.saveDialog('Save .csv File', 'CSV (Comma delimited):*.csv, Text Documents:*.txt, All Files:*'); if (outputFile) { outputFile.open("w"); outputFile.write(csv); outputFile.close(); } } function toDecibels(n) { if (n == "infinity") return "infinity"; else if (n == "undefined") return "undefined"; else if (roundTo(n) == 0) return "negative infinity"; else return 20 * (Math.log(n) / Math.log(10)); } function extractZone(str) { return " " + str.replace(/(^.*_)|(\.[^\.]*$)/g, "") + " "; }