Alpha 0.32 Commit - multi-threaded

Rewrote architecture to support multi-threaded worker instances.
Added a performance log (still untested) to log run times.
dev
Peery 7 years ago
parent f5a4438867
commit dc25d1f2ab

@ -1,32 +1,39 @@
package peery;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import peery.file.FileHandler;
import peery.log.Log;
import peery.log.LogLevel;
import peery.picture.ImageAnalyzer;
public class Mosaic {
public class Mosaic extends Thread{
public static final String versionString = "Alpha-0.21";
public static final String programmName = "Mosaic",
versionString = "Alpha-0.32";
private static final String outputName = "Output";
private static final int gridX = 100, gridY = 100, targetMulti = 4,
private static final int gridX = 200, gridY = 200, targetMulti = 2,
alphaThreshhold = 30,
adaptionCount = 300;
adaptionCount = 300,
inputWorkerLimit = 10,
targetWorkerLimit = 4,
matchWorkerLimit = 10,
placeWorkerLimit = 2;
private static final double adaptionStep = 1.1, gridErrorThresh = 0.15;
/*
*
* Performance:
*
*
* FIX:
* TODO investigate picture stretching -> is ImageUtils.resizeImage() even used?
* TODO alphaThreshhold is currently dead
* somewhere I'm loosing pixels from the target (the output is cut off as if it was smaller)
* investigate picture stretching -> is ImageUtils.resizeImage() even used?
* alphaThreshhold is currently dead
*
* Feature:
* TODO explore keeping Input Image Ratio's
* TODO explore guarantee of usage of at least once, each image.
* explore keeping Input Image Ratio's
* explore guarantee of usage of at least once, each image.
*/
public FileHandler fh;
@ -34,93 +41,73 @@ public class Mosaic {
public Mosaic(){
fh = new FileHandler("resources");
Log.log(LogLevel.Info, "Starting Mosaic "+versionString);
ia = new ImageAnalyzer(fh);
Log.log(LogLevel.Info, "Starting "+programmName+" "+versionString);
ia = new ImageAnalyzer(fh, inputWorkerLimit, targetWorkerLimit, matchWorkerLimit, placeWorkerLimit, alphaThreshhold);
this.start();
}
public void run(){
Log.log.perfLog("Started "+programmName+" v."+versionString);
Log.log.perfLog("Starting indexing ...");
prepMatching();
//Log.log(LogLevel.Error, ia.slotClassifications.toString()+" prep:"+ia.isPrepInProgress());
//Log.log(LogLevel.Error, ia.slotClassifications.toString()+" prep:"+ia.isPrepInProgress());
while(ia.isPrepInProgress()){
try {
Mosaic.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.log.perfLog("Finished Index!");
Log.log.perfLog("Calculating Matches and reloading Index ...");
prepAssembly();
while(ia.isMatchInProgress()){
try {
Mosaic.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.log.perfLog("Finished Matching!");
Log.log.perfLog("Starting placement of images ...");
createMosaic();
while(ia.isPlaceInProgress()){
try {
Mosaic.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.log.perfLog("Finished Placement!");
Log.log(LogLevel.Info, "Finished placement. Output is done ...");
Log.log.perfLog("Saving output to file ...");
fh.saveImage(ia.canvas, new File(fh.OutputFolder+fh.fs+outputName+"-"+fh.OutputFolder.listFiles().length+".png"));
Log.log.perfLog("Everything done! Exiting ...");
Log.log.finishPerfLog();
}
/**
* Prepares the assembly of the Mosaic.
*
* Starts threads to index all not indexed images and rasterizes and classifies the Target.
*/
public void prepMatching(){
ia.updateIndex();
ia.rasterizeTarget(gridX, gridY, targetMulti, gridErrorThresh, adaptionCount, adaptionStep);
ia.classifyTarget();
}
public void prepAssembly(){
ia.reloadIndex();
ia.calculateMatches();
}
public void createMosaic(){
ia.rasterizeTarget(gridX, gridY, targetMulti, gridErrorThresh, adaptionCount, adaptionStep);
HashMap<String, Integer> index = fh.loadIndex();
//Loading index
if(index == null){
Log.log(LogLevel.Info, "No index loaded. Working with empty one ...");
index = new HashMap<String, Integer>();
}
//
//Preparing Setup
ArrayList<String> fileList = fh.listInputFiles();
if(fileList.size() == 0){
Log.log(LogLevel.Error, "No files in Input folder! There NEED to be at least some Images! Exiting...");
System.exit(1);
}
Log.log(LogLevel.Info, "Starting classification of Input now ...");
int count = 0;
for(String path: fileList){
Log.log(LogLevel.Info, "Processing file "+(count++)+"/"+fileList.size()+" ...");
File file = new File(path);
BufferedImage img = fh.loadImage(file);
if(img == null){
continue;
}
int rgb = ia.classifyImage(img, file, alphaThreshhold);
fh.appendToIndex(index, file, rgb);
}
Log.log(LogLevel.Info, "Finished classification. Index is ready for production. Reloading index ...");
index = fh.loadIndex();
if(index == null){
Log.log(LogLevel.Error, "No index after Classification of Input! Can't continue! Exiting...");
System.exit(1);
}
Log.log(LogLevel.Debug, "Canvas is "+ia.canvas.getWidth()+"x"+ia.canvas.getHeight()+" big.");
Log.log(LogLevel.Debug, "Grid will span "+ia.slotWidth*gridX+"x"+ia.slotHeight*gridY+" .");
Log.log(LogLevel.Info, "Starting classification of target slots.");
count = 0;
int maxSlot = ia.slotX*ia.slotY;
int[][] slotRGBs = new int[ia.slotX][ia.slotY];
for(int x = 0; x < ia.slotX; x++){
for(int y = 0; y < ia.slotY; y++){
Log.log(LogLevel.Info, "Processing slot "+(count++)+"/"+maxSlot+" ...");
slotRGBs[x][y] = ia.classifySlot(x, y, ia.target);
}
}
Log.log(LogLevel.Info, "Finished classification of target slots.");
Log.log(LogLevel.Info, "Matching indexed Images on slots ...");
count = 0;
ArrayList<File> fileHolder = new ArrayList<File>();
ArrayList<BufferedImage> imgHolder = new ArrayList<BufferedImage>();
for(int x = 0; x < slotRGBs.length; x++){
for(int y = 0; y < slotRGBs[x].length; y++){
Log.log(LogLevel.Info, "Matching slot "+(count++)+"/"+maxSlot+" ...");
HashMap<String, Integer> matches = ia.matchClosestIndex(index, slotRGBs[x][y], 0);
String match = matches.keySet().toArray()[0].toString();
File file = new File(fh.InputImagesFolder+fh.fs+match);
/*
BufferedImage plain = new BufferedImage(ia.slotWidth, ia.slotHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) plain.getGraphics();
g2.setColor(new Color(slotRGBs[x][y]));
g2.fillRect(0, 0, plain.getWidth(), plain.getHeight());
g2.dispose();
ia.placeImage(x, y, plain);
*/
BufferedImage img;
if(!fileHolder.contains(file)){
fileHolder.add(file);
img = fh.loadImage(file);
imgHolder.add(img);
}else{
int i = fileHolder.indexOf(file);
img = imgHolder.get(i);
}
ia.placeImage(x, y, img);
}
}
Log.log(LogLevel.Info, "Finished matching. Output is done ...");
fh.saveImage(ia.canvas, new File(fh.OutputFolder+fh.fs+outputName+"-"+fh.OutputFolder.listFiles().length+".png"));
Log.log(LogLevel.Info, "Starting Creation of Mosaic !");
ia.placeFragments();
}
public static void main(String[] args){

@ -140,13 +140,15 @@ public class FileHandler {
}
}
public void appendToIndex(HashMap<String, Integer> index, File file, int rgb){
try {
Log.log(LogLevel.Debug, "Checking index entries for "+file.getName());
if(!indexFile.createNewFile() && loadIndexEntry(index, file) != null){
Log.log(LogLevel.Debug, "An Entry seems to already exist for "+file.getName());
return;
}
/**
* Plainly appends the given file and rgb value to the end of the index.
*
* CHECK FOR DUPLICATES BEFOREHAND.
* @param file
* @param rgb
*/
public synchronized void appendToIndex(File file, int rgb){
try{
BufferedWriter bw = new BufferedWriter(new FileWriter(indexFile, true));
bw.write(rgb+";"+file.getName()+"\n");
bw.flush();
@ -184,7 +186,13 @@ public class FileHandler {
*/
public HashMap<String, Integer> loadIndex(){
if(!this.indexFile.exists()){
Log.log(LogLevel.Info, "No Index file found. Nothing to load then...");
Log.log(LogLevel.Info, "No Index file found. Nothing to load then. Creating empty one ...");
try {
indexFile.createNewFile();
} catch (IOException e) {
Log.log(LogLevel.Error, "Couldn't create Index file! Are write permissions missing?");
e.printStackTrace();
}
return null;
}
HashMap<String, Integer> indexData = new HashMap<String, Integer>();
@ -200,6 +208,7 @@ public class FileHandler {
Log.log(LogLevel.Critical, "Could not open index file! Do I have read permissions?");
e.printStackTrace();
}
Log.log(LogLevel.Debug, "Sucessfully loaded index!");
return indexData;
}

@ -4,13 +4,14 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import peery.file.FileHandler;
public class Log {
public static Log log;
public static final boolean silenceDebug = true,
public static final boolean silenceDebug = false,
appendEvents = false, appendErrors = false;
private final static String readmeText =
@ -25,13 +26,17 @@ public class Log {
private String location;
public final File eventFile, errorFile;
private BufferedWriter eventWriter, errorWriter;
private File perfFile;
private BufferedWriter eventWriter, errorWriter, perfWriter;
private ArrayList<Long> nanoTimes;
public Log(String location, FileHandler fh){
this.location = location;
this.eventFile = new File(location+fh.fs+"eventLog.log");
this.errorFile = new File(location+fh.fs+"ERROR.log");
this.perfFile = new File(location+fh.fs+"perf.log");
this.nanoTimes = new ArrayList<Long>();
try {
if(!this.eventFile.exists()){
@ -40,15 +45,20 @@ public class Log {
if(!this.errorFile.exists()){
this.errorFile.createNewFile();
}
if(!this.perfFile.exists()){
this.perfFile.createNewFile();
}
this.eventWriter = new BufferedWriter(new FileWriter(eventFile, appendEvents));
this.errorWriter = new BufferedWriter(new FileWriter(errorFile, appendErrors));
this.perfWriter = new BufferedWriter(new FileWriter(perfFile, true));
perfWriter.write("\n\n");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public static void initLog(String location, FileHandler fh){
public static synchronized void initLog(String location, FileHandler fh){
if(Log.log != null){
return;
}
@ -70,15 +80,18 @@ public class Log {
}
public static void log(int logLvl, String message){
Log.log.logs(logLvl, message);
Log.log(LogLevel.values()[logLvl], message);
}
public static void log(LogLevel lv, String message){
if(silenceDebug && LogLevel.Debug == lv){
return;
}
Log.log.logs(lv.ordinal(), message);
}
@SuppressWarnings("unused")
public void logs(int logLvl, String message){
public synchronized void logs(int logLvl, String message){
String prefix = LogLevel.values()[logLvl].toString();
prefix = "["+prefix+"]";
BufferedWriter logWriter;
@ -105,6 +118,41 @@ public class Log {
}
}
public synchronized void perfLog(String message){
long currentTime = System.nanoTime();
String timeStamp = new java.util.Date().toString()+"|"+currentTime;
this.nanoTimes.add(currentTime);
try {
perfWriter.write(message+"\n");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void finishPerfLog(){
String[] stages = {
"Indexing & Rasterization", // 2 - 1 1
"Matching", // 4 - 3 2
"Placement", // 6 - 5 3
"Saving" // 8 - 7 4
};
for(int i = 1; i <= stages.length; i++){
long duration = nanoTimes.get(i*2) - nanoTimes.get(i*2-1);
try {
perfWriter.write(stages[i-1]+": "+duration+"\n");
} catch (IOException e) {
e.printStackTrace();
}
}
try {
perfWriter.flush();
perfWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void spawnReadMe(FileHandler fh){
File readme = new File(Log.log.location+fh.fs+"README.txt");
Log.log(LogLevel.Info, "Spawning README file at "+readme.getAbsolutePath());

@ -7,41 +7,297 @@ import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import peery.file.FileHandler;
import peery.log.Log;
import peery.log.LogLevel;
import peery.picture.worker.PlacementWorker;
import peery.picture.worker.PlainImageAnalyzerWorker;
import peery.picture.worker.MatchWorker;
import peery.picture.worker.TargetImageAnalyzerWorker;
public class ImageAnalyzer {
public BufferedImage target;
private FileHandler fh;
public FileHandler fh;
private Dimension /*biggestInputSize,*/ targetSize;
public int slotWidth, slotHeight, slotX, slotY;
public int preSlotWidth, preSlotHeight, postSlotWidth, postSlotHeight, slotX, slotY, slotCount;
public BufferedImage canvas;
private HashMap<String, Integer> index;
public ImageAnalyzer(FileHandler fh){
private int alphaThreshhold;
//Input Classification Worker
private int inputWorkersLimit;
private static final String inputWorkerName = "inputWorker";
private PlainImageAnalyzerWorker[] inputWorkers;
private ArrayList<File> inputFiles;
//
//Target Classification Worker
private int targetWorkersLimit;
private static final String targetWorkerName = "targetWorker";
private TargetImageAnalyzerWorker[] targetWorkers;
public HashMap<String, Integer> slotClassifications; //TODO change back private
//
//Matching Worker
private int matchWorkersLimit;
private static final String matchWorkerName = "matchWorker";
private MatchWorker[] matchWorkers;
private HashMap<int[], File[]> fileMap;
//
//Placement Worker
private static final String placeWorkerName = "placeWorker";
private PlacementWorker placeWorker;
//
public ImageAnalyzer(FileHandler fh, int inputWorkersLimit, int targetWorkersLimit,
int matchWorkersLimit, int placeWorkersLimit, int alphaThreshhold){
this.fh = fh;
this.target = fh.loadImage(fh.TargetImageFile);
this.alphaThreshhold = alphaThreshhold;
this.inputWorkersLimit = inputWorkersLimit;
this.targetWorkersLimit = targetWorkersLimit;
this.matchWorkersLimit = matchWorkersLimit;
this.slotClassifications = new HashMap<String, Integer>();
this.fileMap = new HashMap<int[], File[]>();
this.inputFiles = new ArrayList<File>();
this.inputWorkers = new PlainImageAnalyzerWorker[inputWorkersLimit];
this.targetWorkers = new TargetImageAnalyzerWorker[targetWorkersLimit];
this.matchWorkers = new MatchWorker[matchWorkersLimit];
this.index = fh.loadIndex();
}
public void rasterizeTarget(int gridX, int gridY, float targetSizeMultiplier, double gridErrorThresh, int adaptionThreshhold, double adaptionStep){
/**
* Checks if any of the Worker's are still out there doing stuff.
*
* @return true if at least one worker of any kind is still alive!
*/
public boolean isPrepInProgress(){
for(PlainImageAnalyzerWorker pWorker: inputWorkers){
if(pWorker != null && pWorker.isAlive()){
return true;
}
}
for(TargetImageAnalyzerWorker tWorker: targetWorkers){
if(tWorker != null && tWorker.isAlive()){
return true;
}
}
return false;
}
public boolean isMatchInProgress(){
for(MatchWorker mWorker: matchWorkers){
if(mWorker != null && mWorker.isAlive()){
return true;
}
}
return false;
}
public boolean isPlaceInProgress(){
if(this.placeWorker != null && this.placeWorker.isAlive()){
return true;
}
return false;
}
public synchronized void reloadIndex(){
Log.log(LogLevel.Debug, "Reloading index!");
this.index = fh.loadIndex();
}
/**
* Updates the index by spawning worker threads to classify new input Images.
*
* Checks for already indexed images before adding them to the workload.
* To check if workers are still running check worker.getRunning() or worker.isAlive() (latter preferred)
* Worker instances are found in this.inputWorkers
*/
public void updateIndex(){
Log.log(LogLevel.Info, "Starting to update Index.");
Log.log(LogLevel.Debug, "Filling work list with (unread) input files ...");
for(File f: fh.InputImagesFolder.listFiles()){
if(this.fh.loadIndexEntry(index, f) == null){
Log.log(LogLevel.Debug, "Unindexed input file "+f.getName()+" found! Adding to worklist ...");
inputFiles.add(f);
}else{
Log.log(LogLevel.Debug, "Already indexed input file "+f.getName()+" found!");
}
}
Log.log(LogLevel.Info, "Work list filled with "+inputFiles.size()+" file(s)!");
if(inputFiles.size() <= 0){
Log.log(LogLevel.Info, "No work then! Ending Index Update ...");
return;
}
Log.log(LogLevel.Info, "Spawning input workers ...");
inputWorkers = new PlainImageAnalyzerWorker[inputWorkersLimit];
int count = 0;
for(int i = 0; i < inputWorkersLimit; i++){
if(inputFiles.size() <= i){
Log.log(LogLevel.Debug, "Allowed more workers than ther was work. Stopped spawning on "+i+" worker(s)!");
break;
}
inputWorkers[i] = new PlainImageAnalyzerWorker(this, inputWorkerName+Integer.toString(i),
inputFiles.get(i), this.alphaThreshhold);
count++;
}
Log.log(LogLevel.Info, count+" worker(s) spawned!");
}
/**
* Securely retrieves new workload for PlainImageAnalyzerWorker instances.
*
* Returns null when the list is empty.
* @return File for PlainImageAnalyzerWorker to work with.
*/
public synchronized File getNewInputFile(){
if(inputFiles.size() == 0){
return null;
}
File file = inputFiles.get(0);
inputFiles.remove(0);
return file;
}
/**
* Starts classification of the target image's slots via spawning threaded workers.
*
* To check if workers are still running check worker.getRunning() or worker.isAlive() (latter preferred)
* Worker instances are found in this.targetWorkers
*/
public void classifyTarget(){
Log.log(LogLevel.Info, "Starting Target Classification. Calculating workload and spawning worker(s) ...");
this.targetWorkers = new TargetImageAnalyzerWorker[targetWorkersLimit];
int workload = this.slotCount / this.targetWorkersLimit;
int initialWork = this.slotCount % workload;
int currWorker = 0;
if(initialWork != 0){
this.targetWorkers[0] = new TargetImageAnalyzerWorker(this, this.targetWorkerName+"0", 0, initialWork);
currWorker++;
}
int currWork = initialWork;
for(int i = currWorker; i < targetWorkersLimit; i++){
targetWorkers[i] = new TargetImageAnalyzerWorker(this, this.targetWorkerName+Integer.toString(i), currWork, currWork+workload);
currWork += workload;
}
Log.log(LogLevel.Info, "Spawned "+(currWorker+1)+" target worker(s)");
}
/**
* Adds the given classification map to the classification map of the TargetImage.
*
* (invoked by target worker instances to deliver finished workloads)
* @param clFragment HashMap with classifications and coordinates (slot) as key.
*/
public synchronized void addSlotClassifications(HashMap<int[], Integer> clFragment){
for(int[] key: clFragment.keySet()){
if(!slotClassifications.containsKey(ImageUtils.parseCoord(key))){
this.slotClassifications.put(ImageUtils.parseCoord(key), clFragment.get(key));
/*if(key[0] == 199 && key[1] == 0){
Log.log(LogLevel.Error, "ImageAnalyzer.addSlotClassifications() - key[0]==30 && key[1]==0");
Log.log(LogLevel.Error, "Brrrriiiing!");
Log.log(LogLevel.Error, "parseCoord: "+ImageUtils.parseCoord(key));
Log.log(LogLevel.Error, "deliveredFrag: "+clFragment.get(key));
Log.log(LogLevel.Error, "result: "+this.slotClassifications.get(ImageUtils.parseCoord(key)));
Log.log(LogLevel.Error, "");
}*/
}else{
Log.log(LogLevel.Error, "Multiple classifcation of target slot detected! Workloads were not sliced correctly or coordinates are screwed up!");
continue;
}
}
}
public void calculateMatches(){
Log.log(LogLevel.Info, "Starting to match slots to indexed Images. Spawning workers ...");
this.matchWorkers = new MatchWorker[matchWorkersLimit];
int workload = this.slotCount / this.matchWorkersLimit;
int initialWork = this.slotCount % workload;
int currWorker = 0;
int[] lel = {62, 68};
if(initialWork != 0){
this.matchWorkers[0] = new MatchWorker(this, matchWorkerName+"0", 0, initialWork, 0,
slotClassifications, index);
currWorker++;
}
int currWork = initialWork;
for(int i = currWorker; i < matchWorkersLimit; i++){
matchWorkers[i] = new MatchWorker(this, matchWorkerName+"0", currWork, currWork+workload, 0,
slotClassifications, index);;
currWork += workload;
}
Log.log(LogLevel.Info, "Spawned "+(currWorker+1)+" match worker(s)");
}
/**
* Merges the given fileMapFragment with this instance's one.
*
* (invoked by MatchWorker instances)
* @param fileMapFragment
*/
public synchronized void addFileMaps(HashMap<int[], File[]> fileMapFragment){
for(int[] key: fileMapFragment.keySet()){
if(!fileMap.containsKey(key)){
this.fileMap.put(key, fileMapFragment.get(key));
}else{
Log.log(LogLevel.Error, "Multiple fileMap fragments for "+key.toString()+" available! Something went wrong ...");
continue;
}
}
}
/**
* Starts processing the fileMap and queues workloads for PlacementWorker instances and spawns them.
*/
public void placeFragments(){
Log.log(LogLevel.Info, "Starting to gather Coordinates for each Image.");
HashMap<File, ArrayList<int[]>> coordMap = new HashMap<File, ArrayList<int[]>>();
for(int[] coord: fileMap.keySet()){
File file = fileMap.get(coord)[0];
if(!coordMap.containsKey(file)){
coordMap.put(file, new ArrayList<int[]>());
}
coordMap.get(file).add(coord);
}
Log.log(LogLevel.Info, "Start placing fragments on the target.");
this.placeWorker = new PlacementWorker(this, this.placeWorkerName, coordMap);
}
public void rasterizeTarget(int gridX, int gridY, float targetSizeMultiplier,
double gridErrorThresh, int adaptionCount, double adaptionStep){
Log.log(LogLevel.Info, "Rasterizing target image ...");
Log.log(LogLevel.Info, "Aiming for "+gridX+"*"+gridY+"="+(gridX*gridY)+" tiles ...");
Log.log(LogLevel.Debug, "Target is "+target.getWidth()+"x"+target.getHeight()+" big!");
//this.biggestInputSize = this.fh.loadBiggestDimension();
this.targetSize = new Dimension(this.target.getWidth(), this.target.getHeight());
//TMP
int count = 0;
DecimalFormat df = new DecimalFormat("#");
while(true){
double realSlotWidth = ((targetSize.width*targetSizeMultiplier)/gridX);
double realSlotHeight = ((targetSize.height*targetSizeMultiplier)/gridY);
Log.log(LogLevel.Debug, "Perfect slot Size would be "+realSlotWidth+"x"+realSlotHeight);
double widthLoss = Math.abs(realSlotWidth - Double.parseDouble(df.format(realSlotWidth)));
double heightLoss = Math.abs(realSlotHeight - Double.parseDouble(df.format(realSlotHeight)));
double resultSlotWidth = ((targetSize.width*targetSizeMultiplier)/gridX);
double resultSlotHeight = ((targetSize.height*targetSizeMultiplier)/gridY);
postSlotWidth = (int)resultSlotWidth;
postSlotHeight = (int)resultSlotHeight;
Log.log(LogLevel.Debug, "Perfect slot Size would be "+resultSlotWidth+"x"+resultSlotHeight);
double widthLoss = Math.abs(resultSlotWidth - Double.parseDouble(df.format(resultSlotWidth)));
double heightLoss = Math.abs(resultSlotHeight - Double.parseDouble(df.format(resultSlotHeight)));
if(widthLoss > gridErrorThresh || heightLoss > gridErrorThresh){
Log.log(LogLevel.Critical, "Grid misplacement error exceeded threshhold! Error is width:"+widthLoss+" height:"+heightLoss);
if(widthLoss > gridErrorThresh){
@ -52,7 +308,7 @@ public class ImageAnalyzer {
}
Log.log(LogLevel.Info, "This is the "+(++count)+". adaption to ease the error.");
Log.log(LogLevel.Info, "Aiming for "+gridX+"*"+gridY+"="+(gridX*gridY)+" tiles ...");
if(count > adaptionThreshhold){
if(count > adaptionCount){
Log.log(LogLevel.Critical, "Could not adapt to grid misplacement error! The result might be cut off or missing parts!");
break;
}
@ -63,74 +319,25 @@ public class ImageAnalyzer {
}
slotWidth = (int) ((targetSize.width*targetSizeMultiplier)/gridX);
slotHeight = (int) ((targetSize.height*targetSizeMultiplier)/gridY);
preSlotWidth = (int) ((targetSize.width)/gridX);
preSlotHeight = (int) ((targetSize.height)/gridY);
//
Log.log(LogLevel.Debug, "Target will be "+(int)(targetSize.width*targetSizeMultiplier)
+"x"+(int)(targetSize.height*targetSizeMultiplier)+" big.");
Log.log(LogLevel.Debug, "Slots are "+slotWidth+"x"+slotHeight+" big.");
Log.log(LogLevel.Debug, "Slots are "+preSlotWidth+"x"+preSlotHeight+" big.");
slotX = gridX;
slotY = gridY;
slotCount = gridX*gridY;
//ia.slotWidth*gridX+"x"+ia.slotHeight*gridY
//canvas = new BufferedImage((int)(targetSize.getWidth()*targetSizeMultiplier), (int)(targetSize.getHeight()*targetSizeMultiplier), BufferedImage.TYPE_INT_ARGB);
canvas = new BufferedImage(slotWidth*gridX, slotHeight*gridY, BufferedImage.TYPE_INT_ARGB);
canvas = new BufferedImage(postSlotWidth*gridX, postSlotHeight*gridY, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) canvas.getGraphics();
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
/**
* Compares the given rgb value with the index and returns a HashMap of the closest hits.
* Unlikely to yield more than 1 result without deviationPercentage
* @param index
* @param rgb
* @param deviation How much the matches can be different from the perfect match, and still be taken.
* @return
*/
public HashMap<String, Integer> matchClosestIndex(HashMap<String, Integer> index, int rgb, int deviation){
Log.log(LogLevel.Debug, "Searching for closest match for rgb:"+rgb+" in the index with deviation of "+deviation+".");
assert(deviation < 100 && 0 <= deviation);
if(index == null){
Log.log(LogLevel.Critical, "No Index was given for rgb matching. Loading it from file ...");
index = this.fh.loadIndex();
}
HashMap<String, Double> metrics = new HashMap<String, Double>();
double currBest = -1;
for(String key: index.keySet()){
double metric = getMetric(index.get(key), rgb);
metrics.put(key, metric);
if(currBest > metric || currBest == -1){
currBest = metric;
}
}
Log.log(LogLevel.Debug, "Calculated all metrics for rgb:"+rgb+" !");
HashMap<String, Integer> matches = new HashMap<String, Integer>();
for(String key: metrics.keySet()){
if(metrics.get(key) == currBest || metrics.get(key) < currBest+(currBest*(deviation/(double)100))){
matches.put(key, index.get(key));
}
}
Log.log(LogLevel.Debug, "Grabbed all good matches for rgb:"+rgb+" ! Got "+matches.size()+" ...");
return matches;
}
/**
* Calculates the euclidian metric of two given rgb values.
* @param rgb1
* @param rgb2
* @return
*/
private double getMetric(int rgb1, int rgb2){
ColorModel cm = ColorModel.getRGBdefault();
double result = Math.sqrt(Math.pow((cm.getRed(rgb1)-cm.getRed(rgb2)), 2)+
Math.pow(cm.getGreen(rgb1)-cm.getGreen(rgb2), 2)+
Math.pow(cm.getBlue(rgb1)-cm.getBlue(rgb2), 2));
return result;
}
/**
* Places the given input in its specified slot (gridX, gridY) on the canvas.
* Assumes and checks boundaries calculated by rasterizeTarget().
@ -140,75 +347,19 @@ public class ImageAnalyzer {
* @param canvas
* @return
*/
public void placeImage(int gridX, int gridY, BufferedImage input){
public synchronized void placeImage(int gridX, int gridY, BufferedImage input){
assert(gridX < slotX && gridY < slotY);
assert(input.getWidth() < slotWidth && input.getHeight() < slotHeight);
assert(input.getWidth() < postSlotWidth && input.getHeight() < postSlotHeight);
Graphics2D g2 = (Graphics2D)canvas.getGraphics();
g2.drawImage(input, gridX*slotWidth, gridY*slotHeight, slotWidth, slotHeight, null);
g2.drawImage(input, gridX*postSlotWidth, gridY*postSlotHeight, postSlotWidth, postSlotHeight, null);
g2.dispose();
//Log.log(LogLevel.Error, "Drawn picture at "+gridX*postSlotWidth+" "+gridY*postSlotHeight+" with "+input.getWidth()+"x"+input.getHeight());
}
/**
* Plain calculation of average Color of an Image via RGB.
* Takes average of RGB values of each pixel.
* Does not account for alpha.
* @param img Image to process
* @param alphaThreshhold value under which pixels are discarded
* @return RGB value of the average color
*/
public int classifyImage(BufferedImage img, File file, int alphaTreshhold){
int width = img.getWidth();
int height = img.getHeight();
Log.log(LogLevel.Debug, "Starting classification of "+file.getName()+" with width:"+
width+" and height:"+height+" ...");
ColorModel cm = ColorModel.getRGBdefault();
float red = 0, green = 0, blue = 0;
int pixels = 0;
for(int x = 0; x < img.getWidth(); x++){
for(int y = 0; y < img.getHeight(); y++){
int rgb = img.getRGB(x, y);
red += cm.getRed(rgb);
blue += cm.getBlue(rgb);
green += cm.getGreen(rgb);
pixels++;
}
}
red = red/pixels;
green = green/pixels;
blue = blue/pixels;
int rgb = new Color((int)red, (int)green, (int)blue).getRGB();
Log.log(LogLevel.Debug, "Classified "+file.getPath()+" with following rgb result: value:"+rgb+
" red:"+red+", green:"+green+", blue:"+blue);
return rgb;
}
public int classifySlot(int gridX, int gridY, BufferedImage target){
int slotWidth = target.getWidth()/slotX,
slotHeight = target.getHeight()/slotY;
BufferedImage subImage = target.getSubimage(gridX*slotWidth, gridY*slotHeight, slotWidth, slotHeight);
Log.log(LogLevel.Debug, "Slicing slot "+gridX+"x"+gridY+" out of the target for classification ...");
ColorModel cm = ColorModel.getRGBdefault();
float red = 0, green = 0, blue = 0;
int pixels = 0;
for(int x = 0; x < subImage.getWidth(); x++){
for(int y = 0; y < subImage.getHeight(); y++){
int rgb = subImage.getRGB(x, y);
red += cm.getRed(rgb);
green += cm.getGreen(rgb);
blue += cm.getBlue(rgb);
pixels++;
}
}
red = red/pixels;
green = green/pixels;
blue = blue/pixels;
int rgb = new Color((int)red, (int)green, (int)blue).getRGB();
Log.log(LogLevel.Debug, "Classified slot "+gridX+"x"+gridY+" with following rgb result: value:"+rgb+
" red:"+red+", green:"+green+", blue:"+blue);
return rgb;
}
/*
public static void main(String[] args){
System.out.println("ZHE DEBUG");

@ -37,5 +37,32 @@ public class ImageUtils {
return img;
}
}
/**
* Converts from a numbered Slot to a specific slot start coordinate.
*
* @param num
* @param preMagnification true if slot sizes before the magnification are to be used.
* @return
*/
public static int[] getSlotCoord(ImageAnalyzer ia, int num, boolean preMagnification){
int slotWidth, slotHeight;
if(preMagnification){
slotWidth = ia.preSlotWidth;
slotHeight = ia.preSlotHeight;
}else{
slotWidth = ia.postSlotWidth;
slotHeight = ia.postSlotHeight;
}
//TODO -----> FIX überschlag von Zeile 0 in 1; x zählt zu viel!
int ySlots = num/(ia.slotY-1);
int xSlots = num%(ia.slotX-1);
int[] coords = {xSlots*slotWidth, ySlots*slotHeight};
return coords;
}
public static String parseCoord(int[] coord){
return coord[0]+"-"+coord[1];
}
}

@ -0,0 +1,23 @@
package peery.picture.worker;
import peery.picture.ImageAnalyzer;
public abstract class ImageAnalyzerWorker extends Thread{
protected ImageAnalyzer ia;
protected boolean running;
public ImageAnalyzerWorker(ImageAnalyzer ia, String name){
this.ia = ia;
this.setName(name);
running = true;
}
public abstract void run();
public boolean getRunning(){
return this.running;
}
}

@ -0,0 +1,125 @@
package peery.picture.worker;
import java.awt.image.ColorModel;
import java.io.File;
import java.util.HashMap;
import peery.log.Log;
import peery.log.LogLevel;
import peery.picture.ImageAnalyzer;
import peery.picture.ImageUtils;
public class MatchWorker extends ImageAnalyzerWorker{
private int startCoord, endCoord, deviation;
HashMap<String, Integer> slotClassifications;
HashMap<String, Integer> index;
public MatchWorker(ImageAnalyzer ia, String name, int startCoord, int endCoord, int deviation,
HashMap<String, Integer> slotClassifications, HashMap<String, Integer> index){
super(ia, name);
this.deviation = deviation;
this.startCoord = startCoord;
this.endCoord = endCoord;
this.slotClassifications = slotClassifications;
this.index = index;
this.ia = ia;
this.start();
}
@Override
public void run() {
Log.log(LogLevel.Debug, "["+this.getName()+"] has started its duty!");
HashMap<int[], File[]> fileMap = new HashMap<int[], File[]>();
for(int i = startCoord; i < endCoord; i++){
int[] coord = {i%ia.slotX, i/ia.slotX};
if(coord[0]+ia.preSlotWidth >= ia.target.getWidth() || coord[1]+ia.preSlotHeight >= ia.target.getHeight()){
//Dirty FIX
//This will inevitably land outside the Raster otherwise. I should prevent these Coords from the start
//Log.log(LogLevel.Error, "Didn't classify "+coord[0]+" "+coord[1]);
continue;
}
if(coord[0] == ia.slotX-1 || coord[1] == ia.slotY-1){
//Dirty Fix
//for avoiding non-existant keys in the slotClassifications.
continue;
}
if(index == null || slotClassifications.get(ImageUtils.parseCoord(coord)) == null){ //TODO remove
Log.log(LogLevel.Error, "MatchWorker run() -> slotClass.get(ImageUtils.parse(coord)==null");
Log.log(LogLevel.Error, "BRrrring"+slotClassifications.get(ImageUtils.parseCoord(coord)));
Log.log(LogLevel.Error, "parsed: "+ImageUtils.parseCoord(coord));
Log.log(LogLevel.Error, ""+coord[0]+" "+coord[1]);
Log.log(LogLevel.Error, "");
for(String key: slotClassifications.keySet()){
//Log.log(LogLevel.Error, key);
}
}
HashMap<String, Integer> matches = matchClosestIndex(index, slotClassifications.get(ImageUtils.parseCoord(coord)), deviation);
Log.log(LogLevel.Debug, "["+this.getName()+"] Matched "+matches.keySet().size()+" image(s) to slot "+i+" !");
File[] files = new File[matches.keySet().size()];
for(int n = 0; n < files.length; n++){
files[n] = new File(ia.fh.InputImagesFolder.getAbsolutePath()+ia.fh.fs+(String) matches.keySet().toArray()[n]);
}
fileMap.put(coord, files);
}
ia.addFileMaps(fileMap);
Log.log(LogLevel.Info, "["+this.getName()+"] This worker has finished its chunk! Terminating ...");
}
/**
* Compares the given rgb value with the index and returns a HashMap of the closest hits.
* Unlikely to yield more than 1 result without deviationPercentage
* @param index
* @param rgb
* @param deviation How much the matches can be different from the perfect match, and still be taken.
* @return
*/
public HashMap<String, Integer> matchClosestIndex(HashMap<String, Integer> index, int rgb, int deviation){
if(index == null){
System.out.println("Briiing!");
}
Log.log(LogLevel.Debug, "Searching for closest match for rgb:"+rgb+" in the index with deviation of "+deviation+".");
assert(deviation < 100 && 0 <= deviation);
if(index == null){
Log.log(LogLevel.Critical, "No Index was given for rgb matching. Exiting!");
return null;
}
HashMap<String, Double> metrics = new HashMap<String, Double>();
double currBest = -1;
for(String key: index.keySet()){
double metric = getMetric(index.get(key), rgb);
metrics.put(key, metric);
if(currBest > metric || currBest == -1){
currBest = metric;
}
}
Log.log(LogLevel.Debug, "Calculated all metrics for rgb:"+rgb+" !");
HashMap<String, Integer> matches = new HashMap<String, Integer>();
for(String key: metrics.keySet()){
if(metrics.get(key) == currBest || metrics.get(key) < currBest+(currBest*(deviation/(double)100))){
matches.put(key, index.get(key));
}
}
Log.log(LogLevel.Debug, "Grabbed all good matches for rgb:"+rgb+" ! Got "+matches.size()+" ...");
return matches;
}
/**
* Calculates the euclidian metric of two given rgb values.
* @param rgb1
* @param rgb2
* @return
*/
private double getMetric(int rgb1, int rgb2){
ColorModel cm = ColorModel.getRGBdefault();
double result = Math.sqrt(Math.pow((cm.getRed(rgb1)-cm.getRed(rgb2)), 2)+
Math.pow(cm.getGreen(rgb1)-cm.getGreen(rgb2), 2)+
Math.pow(cm.getBlue(rgb1)-cm.getBlue(rgb2), 2));
return result;
}
}

@ -0,0 +1,47 @@
package peery.picture.worker;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import peery.log.Log;
import peery.log.LogLevel;
import peery.picture.ImageAnalyzer;
public class PlacementWorker extends ImageAnalyzerWorker{
private HashMap<File, ArrayList<int[]>> coordMap;
public PlacementWorker(ImageAnalyzer ia, String name, HashMap<File, ArrayList<int[]>> coordMap){
super(ia, name);
this.coordMap = coordMap;
this.start();
}
@Override
public void run() {
Log.log(LogLevel.Info, "Worker "+this.getName()+" commencing work!");
for(File file: coordMap.keySet()){
if(coordMap.get(file) == null){
continue;
}
BufferedImage img = ia.fh.loadImage(file);
ArrayList<int[]> coords = coordMap.get(file);
Log.log(LogLevel.Info, "["+this.getName()+"] Going to place "+file.getName()+" "+coords.size()+" time(s)!");
int count = 0;
for(int[] coord: coords){
ia.placeImage(coord[0], coord[1], img);
if(count % 100 == 0){
Log.log(LogLevel.Info, "["+this.getName()+"] Placed "+count+"/"+coords.size()+" instances! Continuing ...");
}
count++;
}
Log.log(LogLevel.Debug, "["+this.getName()+"] Finished placing all "+file.getName()+" ! Switching file!");
}
Log.log(LogLevel.Info, "["+this.getName()+"] Finished all work! Terminating ...");
}
}

@ -0,0 +1,90 @@
package peery.picture.worker;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.File;
import peery.log.Log;
import peery.log.LogLevel;
import peery.picture.ImageAnalyzer;
public class PlainImageAnalyzerWorker extends ImageAnalyzerWorker{
private File file;
private int alphaThreshhold;
public PlainImageAnalyzerWorker(ImageAnalyzer ia, String name, File file, int alphaThreshhold){
super(ia, name);
this.alphaThreshhold = alphaThreshhold;
this.file = file;
this.start();
}
public void run(){
Log.log(LogLevel.Info, "Worker "+this.getName()+" up and running! Let's roll...");
while(running){
System.out.println(file.getAbsolutePath());
BufferedImage img = this.ia.fh.loadImage(file);
if(img == null){
this.file = ia.getNewInputFile();
if(this.file == null){
running = false;
break;
}
}
int rgb = this.classifyImage(img, file, alphaThreshhold);
ia.fh.appendToIndex(file, rgb);
Log.log(LogLevel.Debug, "["+this.getName()+"] finished workunit:"+file.getName()+" !");
file = ia.getNewInputFile();
while(file != null && !file.exists() ){
Log.log(LogLevel.Critical, "Non-existent file eneded up as a task for worker "+this.getName()+" !");
file = ia.getNewInputFile();
Log.log(LogLevel.Debug, "["+this.getName()+"] got new workunit:"+file.getName()+" !");
}
if(file == null){
Log.log(LogLevel.Debug, "["+this.getName()+"] New workload was null ! Ran out of work ...");
running = false;
break;
}
}
Log.log(LogLevel.Info, "Worker "+this.getName()+" has finished work! Terminating ...");
}
/**
* Plain calculation of average Color of an Image via RGB.
* Takes average of RGB values of each pixel.
* Does not account for alpha.
* @param img Image to process
* @param alphaThreshhold value under which pixels are discarded
* @return RGB value of the average color
*/
public int classifyImage(BufferedImage img, File file, int alphaTreshhold){
int width = img.getWidth();
int height = img.getHeight();
Log.log(LogLevel.Debug, "Starting classification of "+file.getName()+" with width:"+
width+" and height:"+height+" ...");
ColorModel cm = ColorModel.getRGBdefault();
float red = 0, green = 0, blue = 0;
int pixels = 0;
for(int x = 0; x < img.getWidth(); x++){
for(int y = 0; y < img.getHeight(); y++){
int rgb = img.getRGB(x, y);
red += cm.getRed(rgb);
blue += cm.getBlue(rgb);
green += cm.getGreen(rgb);
pixels++;
}
}
red = red/pixels;
green = green/pixels;
blue = blue/pixels;
int rgb = new Color((int)red, (int)green, (int)blue).getRGB();
Log.log(LogLevel.Debug, "Classified "+file.getPath()+" with following rgb result: value:"+rgb+
" red:"+red+", green:"+green+", blue:"+blue);
return rgb;
}
}

@ -0,0 +1,114 @@
package peery.picture.worker;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.File;
import java.util.HashMap;
import peery.log.Log;
import peery.log.LogLevel;
import peery.picture.ImageAnalyzer;
import peery.picture.ImageUtils;
public class TargetImageAnalyzerWorker extends ImageAnalyzerWorker{
private ImageAnalyzer ia;
private int startCoord, endCoord;
/**
*
* @param ia ImageAnalyzer to work for
* @param name Thread name to use (should be unique)
* @param img Image to work on
* @param startCoord slot x coordinate to start from
* @param endCoord slot y coordinate to start from
*/
public TargetImageAnalyzerWorker(ImageAnalyzer ia, String name, int startCoord, int endCoord){
super(ia, name);
this.ia = ia;
this.startCoord = startCoord;
this.endCoord = endCoord;
this.start();
}
public void run(){
Log.log(LogLevel.Info, "Worker "+this.getName()+" up and running! Let's roll ...");
HashMap<int[], Integer> result = this.classifyArea(startCoord, endCoord);
ia.addSlotClassifications(result);
Log.log(LogLevel.Info, "Worker "+this.getName()+" finished work! Terminating ...");
}
/**
* Classifies the specified Area of the target image and returns the average colors.
*
* Returns a HashMap with the slot coordinates (slot) as key.
* @param startCoord slotX to start in
* @param endCoord slotY to start in
* @param workArea number of slots to work through
* @param target image to work on
*/
public HashMap<int[], Integer> classifyArea(int startCoord, int endCoord){
BufferedImage target = ia.target;
HashMap<int[], Integer> results = new HashMap<int[], Integer>();
System.out.println("Was ordered: "+startCoord+" "+endCoord);
for(int i = startCoord; i < endCoord; i++){
int[] coordPixels = ImageUtils.getSlotCoord(ia, i, true);
if(coordPixels[0]+ia.preSlotWidth >= ia.target.getWidth() || coordPixels[1]+ia.preSlotHeight >= ia.target.getHeight()){
//Dirty FIX
//This will inevitably land outside the Raster otherwise. I should prevent these Coords from the start
//Log.log(LogLevel.Error, "Didn't classify "+coordPixels[0]+" "+coordPixels[1]);
continue;
}
int rgb = classifySlot(coordPixels[0], coordPixels[1], ia.preSlotWidth, ia.preSlotHeight, ia.target);
int[] coordSlot = {coordPixels[0]/ia.preSlotWidth, coordPixels[1]/ia.preSlotHeight};
/*if(coordPixels[0] == 210 && coordPixels[1] == 0){//TODO remove
System.out.println("Brrrring!");
Log.log(LogLevel.Error, "Brrrriiing"+rgb+" ");
System.out.println(rgb);
System.out.println(coordSlot[0]+" "+coordSlot[1]);
}*/
results.put(coordSlot, rgb);
}
return results;
}
/**
* Classifies a single specified slot on the given Image.
* @param gridX Slot specification in X
* @param gridY Slot specification in Y
* @param slotWidth Slot size in x direction
* @param slotHeight Slot size in y direction
* @param target Image to work on
* @return average Color as RGB value
*/
public int classifySlot(int gridX, int gridY, int slotWidth, int slotHeight, BufferedImage target){
System.out.println(gridX+" "+gridY+" "+slotWidth+" "+slotHeight);
Log.log(LogLevel.Debug, "["+this.getName()+"] Slicing slot "+gridX+"x"+gridY+" out of the target for classification ...");
BufferedImage subImage = target.getSubimage(gridX, gridY, slotWidth, slotHeight);
ColorModel cm = ColorModel.getRGBdefault();
float red = 0, green = 0, blue = 0;
int pixels = 0;
for(int x = 0; x < subImage.getWidth(); x++){
for(int y = 0; y < subImage.getHeight(); y++){
int rgb = subImage.getRGB(x, y);
red += cm.getRed(rgb);
green += cm.getGreen(rgb);
blue += cm.getBlue(rgb);
pixels++;
}
}
red = red/pixels;
green = green/pixels;
blue = blue/pixels;
int rgb = new Color((int)red, (int)green, (int)blue).getRGB();
Log.log(LogLevel.Debug, "["+this.getName()+"] Classified slot "+gridX+"x"+gridY+" with following rgb result: value:"+rgb+
" red:"+red+", green:"+green+", blue:"+blue);
return rgb;
}
}
Loading…
Cancel
Save