I'm using some code from my university's lecturer for a new project and everything seems to be working except for that one little piece, where I want to import "data.Frame" and I just don't know what's missing so my code would function. If anyone knows the solution or could give me an alternative so I could import the needed "Frame" - would be awesome!
I'm using eclipse!
The code is:
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import data.Frame; // HERE IS THE PROBLEM!
/**
* Compares the annotations of two or more annotators and groups them together,
* before they are written to a csv file.
*/
public class ComparisonWriter
{
private String content;
private final int A1 = 1;
private final int A2 = 2;
private final int A3 = 3;
// private final int A4 = 4;
private int numberOfAnnotators = 3;
String outputFileName = "data/200Saetze/Gruppe 1/comparison_buchpreis2_alle.csv";
String inputA1 = "data/200Saetze/Gruppe 1/Daniela/buchpreis2.xml";
String inputA2 = "data/200Saetze/Gruppe 1/Inga/buchpreis2.xml";
String inputA3 = "data/200Saetze/Gruppe 1/Stefan/buchpreis2.xml";
public ComparisonWriter()
{
content = "SatzID;Annotator;Frame;SE;exact;partial;Source;Annotator;SourceString;" +
"exact;exact ohne leer;exact ohne leer (SE);partial;koreferent;SourceFlag;exact;Target;Annotator;" +
"TargetString;exact;exact ohne leer;exact ohne leer (SE);partial;koreferent;TargetFlag;;FrameFlag";
AnnotationReader annotationReaderA1 = new AnnotationReader(inputA1);
Map<String, List<Frame>> seAnnotator1 = annotationReaderA1.getAllSubjectiveExpressions();
AnnotationReader annotationReaderA2 = new AnnotationReader(inputA2);
Map<String, List<Frame>> seAnnotator2 = annotationReaderA2.getAllSubjectiveExpressions();
AnnotationReader annotationReaderA3 = new AnnotationReader(inputA3);
Map<String, List<Frame>> seAnnotator3 = annotationReaderA3.getAllSubjectiveExpressions();
Set<String> sentences = seAnnotator1.keySet();
for (String sentence : sentences)
{
//add leading zeros
content += "\n\n" + sentence + ";" + annotationReaderA1.sentenceStrings.get(sentence);
//add the annotations in a sorted order
Map<Integer, List<Frame>> allFramesInSentence = new HashMap<Integer, List<Frame>>();
allFramesInSentence.put(A1, seAnnotator1.get(sentence));
allFramesInSentence.put(A2, seAnnotator2.get(sentence));
allFramesInSentence.put(A3, seAnnotator3.get(sentence));
// allFramesInSentence.put(A4, seAnnotator4.get(sentence));
//
//get the one with the most annotations
int largest = getIndexOfLargestList(allFramesInSentence);
if(largest == 0)
continue;
for (int i = 0; i < allFramesInSentence.get(largest).size(); i++)
{
Frame frame = allFramesInSentence.get(largest).get(i);
content += "\n\n;A" + largest + ";" + frame;
frame.setConsidered(true);
findOverlappingAnnotations(allFramesInSentence, largest, frame);
}
//Check, if there are not considered annotations in one of the frame lists for that sentence.
for (int a = 1; a <= 4; a++)
{
List<Frame> frameList = allFramesInSentence.get(a);
if(a != largest && frameList != null)
{
for (Frame frame : frameList)
{
if(frame.isConsidered() == false)
{
content += "\n\n;A" + a + ";" + frame;
frame.setConsidered(true);
findOverlappingAnnotations(allFramesInSentence, a, frame);
}
}
}
}
}
writeContentToFile(content, outputFileName, false);
}
/**
* Find overlapping annotations.
* #param frames - list of frames, potentially overlapping with the given one.
* #param largest
* #param frame - frame in question.
*/
private void findOverlappingAnnotations(Map<Integer, List<Frame>> frames,
int largest, Frame frame)
{
for (int a = 1; a <= 4; a++)
{
//If j is not the current largest frame list and there are annotated frames
//in from this annotator (a)
if(a != largest && frames.get(a) != null)
{
for (Frame compareFrame : frames.get(a))
{
addOverlappingAnnotations2Conent(frame, a, compareFrame);
}
}
}
}
/**
* Add overlapping Annotations (measured by matching ids) to the content attribute.
* #param frame
* #param a - Annotator index
* #param compareFrame
*/
private void addOverlappingAnnotations2Conent(Frame frame, int a,
Frame compareFrame)
{
List<String> terminalIDs = compareFrame.getTerminalIDs();
for (String id : terminalIDs)
{
if(compareFrame.isConsidered())
break;
if(frame.getTerminalIDs().contains(id))
{
//Write it to the content
content += "\n;A" + a + ";" + compareFrame;
compareFrame.setConsidered(true);
break;
}
}
}
/**
* Get the index of the largest frame list in the map.
* #param frames - a map with all the frames for each annotator (key: annotator)
* #return The index of the largest frame list.
*/
private int getIndexOfLargestList(Map<Integer, List<Frame>> frames)
{
int size = 0;
int largest = 0;
for(int a = 0; a <= numberOfAnnotators; a++)
{
if(frames.get(a) != null)
{
if(frames.get(a).size() > size)
largest = a;
}
}
return largest;
}
You basically need Frame class in your classpath. If you have this class in some jar, then add that jar to your classpath.
You need to get a copy of the source code or class file for the Frame class and add it to your project. The source code should be in a file called Frame.java.
Related
We are using a Stream to search an ArrayList of strings the Dictionary file is sorted & contains 307107 words all in lower case
We are using the findFirst to look for a match from the text in a TextArea
As long as the word is misspelled beyond the 3 character the search has favoriable results
If the misspelled word is like this "Charriage" the results are nothing close to a match
The obvious goal is to get as close to correct without the need to look at an enormous number of words
Here is the text we are tesing
Tak acheive it hommaker and aparent as Chariage NOT ME Charriag add missing vowel to Cjarroage
We have made some major changes to the stream search filters with reasonable improvements
We will edit the posted code to include ONLY the part of the code where the search is failing
And below that the code changes made to the stream filters
Before the code change if the searchString had a misspelled char at position 1 no results were found in the dictionary the new search filters fixed that
We also added more search information by increasing the number of char for endsWith
So what is still failing! If the searchString(misspelled word) is missing a char at the end of the word and if the word has an incorrect char from position 1 to 4 the search fails
We are working on adding & removing char but we are not sure this is a workable solution
Comments or code will be greatly appreciated if you would like the complete project we will post on GitHub Just ask in the comments
The question is still how to fix this search filter when multiple char are missing from the misspelled word?
After multiple hours of searching for a FREE txt Dictionary this is one of the best
A side bar fact it has 115726 words that are > 5 in length and have a vowel at the end of the word. That means it has 252234 words with no vowel at the end
Does that mean we have a 32% chance of fixing the issue by adding a vowel to the end of the searchString? NOT a question just an odd fact!
HERE is a link to the dictionary download and place the words_alpha.txt file on C drive at C:/A_WORDS/words_alpha.txt");
words_alpha.txt
Code Before Changes
}if(found != true){
lvListView.setStyle("-fx-font-size:18.0;-fx-background-color: white;-fx-font-weight:bold;");
for(int indexSC = 0; indexSC < simpleArray.length;indexSC++){
String NewSS = txtMonitor.getText().toLowerCase();
if(NewSS.contains(" ")||(NewSS.matches("[%&/0-9]"))){
String NOT = txtMonitor.getText().toLowerCase();
txtTest.setText(NOT+" Not in Dictionary");
txaML.appendText(NOT+" Not in Dictionary");
onCheckSpelling();
return;
}
int a = NewSS.length();
int Z;
if(a == 0){// manage CR test with two CR's
Z = 0;
}else if(a == 3){
Z = 3;
}else if(a > 3 && a < 5){
Z = 4;
}else if(a >= 5 && a < 8){
Z = 4;
}else{
Z = 5;
}
System.out.println("!!!! NewSS "+NewSS+" a "+a+" ZZ "+Z);
if(Z == 0){// Manage CR in TextArea
noClose = true;
strSF = "AA";
String NOT = txtMonitor.getText().toLowerCase();
//txtTo.setText("Word NOT in Dictionary");// DO NO SEARCH
//txtTest.setText("Word NOT in Dictionaary");
txtTest.setText("Just a Space");
onCheckSpelling();
}else{
txtTest.setText("");
txaML.clear();
txtTest.setText("Word NOT in Dictionaary");
txaML.appendText("Word NOT in Dictionaary");
String strS = searchString.substring(0,Z).toLowerCase();
strSF = strS;
}
// array & list use in stream to add results to ComboBox
List<String> cs = Arrays.asList(simpleArray);
ArrayList<String> list = new ArrayList<>();
cs.stream().filter(s -> s.startsWith(strSF))
//.forEach(System.out::println);
.forEach(list :: add);
for(int X = 0; X < list.size();X++){
String A = (String) list.get(X);
Improved New Code
}if(found != true){
for(int indexSC = 0; indexSC < simpleArray.length;indexSC++){
String NewSS = txtMonitor.getText().toLowerCase();
if(NewSS.contains(" ")||(NewSS.matches("[%&/0-9]"))){
String NOT = txtMonitor.getText().toLowerCase();
txtTest.setText(NOT+" Not in Dictionary");
onCheckSpelling();
return;
}
int a = NewSS.length();
int Z;
if(a == 0){// manage CR test with two CR's
Z = 0;
}else if(a == 3){
Z = 3;
}else if(a > 3 && a < 5){
Z = 4;
}else if(a >= 5 && a < 8){
Z = 4;
}else{
Z = 5;
}
if(Z == 0){// Manage CR
noClose = true;
strSF = "AA";
String NOT = txtMonitor.getText().toLowerCase();
txtTest.setText("Just a Space");
onCheckSpelling();
}else{
txtTest.setText("");
txtTest.setText("Word NOT in Dictionaary");
String strS = searchString.substring(0,Z).toLowerCase();
strSF = strS;
}
ArrayList list = new ArrayList<>();
List<String> cs = Arrays.asList(simpleArray);
// array list & list used in stream foreach filter results added to ComboBox
// Code below provides variables for refined search
int W = txtMonitor.getText().length();
String nF = txtMonitor.getText().substring(0, 1).toLowerCase();
String nE = txtMonitor.getText().substring(W - 2, W);
if(W > 7){
nM = txtMonitor.getText().substring(W-5, W);
System.out.println("%%%%%%%% nE "+nE+" nF "+nF+" nM = "+nM);
}else{
nM = txtMonitor.getText().substring(W-1, W);
System.out.println("%%%%%%%% nE "+nE+" nF "+nF+" nM = "+nM);
}
cs.stream().filter(s -> s.startsWith(strSF)
|| s.startsWith(nF, 0)
&& s.length()<= W+2
&& s.endsWith(nE)
&& s.startsWith(nF)
&& s.contains(nM))
.forEach(list :: add);
for(int X = 0; X < list.size();X++){
String A = (String) list.get(X);
sort(list);
cboSelect.setStyle("-fx-font-weight:bold;-fx-font-size:18.0;");
cboSelect.getItems().add(A);
}// Add search results to cboSelect
break;
Here is a screen shot of the FXML file the controls are named the same as the names used in our code with the exception of the ComboBox
I am adding a JavaFX answer. This app uses Levenshtein Distance. You have to click on Check Spelling to start. You can select a word from the list to replace the current word being checked. I notice Levenshtein Distance returns lots of words so you might want to find other ways to reduce the list down even more.
Main
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application
{
public static void main(String[] args)
{
launch(args);
}
TextArea taWords = new TextArea("Tak Carrage thiss on hoemaker answe");
TextField tfCurrentWordBeingChecked = new TextField();
//TextField tfMisspelledWord = new TextField();
ListView<String> lvReplacementWords = new ListView();
TextField tfReplacementWord = new TextField();
Button btnCheckSpelling = new Button("Check Spelling");
Button btnReplaceWord = new Button("Replace Word");
List<String> wordList = new ArrayList();
List<String> returnList = new ArrayList();
HandleLevenshteinDistance handleLevenshteinDistance = new HandleLevenshteinDistance();
ObservableList<String> listViewData = FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage)
{
setupListView();
handleBtnCheckSpelling();
handleBtnReplaceWord();
VBox root = new VBox(taWords, tfCurrentWordBeingChecked, lvReplacementWords, tfReplacementWord, btnCheckSpelling, btnReplaceWord);
root.setSpacing(5);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public void handleBtnCheckSpelling()
{
btnCheckSpelling.setOnAction(actionEvent -> {
if (btnCheckSpelling.getText().equals("Check Spelling")) {
wordList = new ArrayList(Arrays.asList(taWords.getText().split(" ")));
returnList = new ArrayList(Arrays.asList(taWords.getText().split(" ")));
loadWord();
btnCheckSpelling.setText("Check Next Word");
}
else if (btnCheckSpelling.getText().equals("Check Next Word")) {
loadWord();
}
});
}
public void handleBtnReplaceWord()
{
btnReplaceWord.setOnAction(actionEvent -> {
int indexOfWordToReplace = returnList.indexOf(tfCurrentWordBeingChecked.getText());
returnList.set(indexOfWordToReplace, tfReplacementWord.getText());
taWords.setText(String.join(" ", returnList));
btnCheckSpelling.fire();
});
}
public void setupListView()
{
lvReplacementWords.setItems(listViewData);
lvReplacementWords.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
tfReplacementWord.setText(newSelection);
});
}
private void loadWord()
{
if (wordList.size() > 0) {
tfCurrentWordBeingChecked.setText(wordList.get(0));
wordList.remove(0);
showPotentialCorrectSpellings();
}
}
private void showPotentialCorrectSpellings()
{
List<String> potentialCorrentSpellings = handleLevenshteinDistance.getPotentialCorretSpellings(tfCurrentWordBeingChecked.getText().trim());
listViewData.setAll(potentialCorrentSpellings);
}
}
CustomWord Class
/**
*
* #author blj0011
*/
public class CustomWord
{
private int distance;
private String word;
public CustomWord(int distance, String word)
{
this.distance = distance;
this.word = word;
}
public String getWord()
{
return word;
}
public void setWord(String word)
{
this.word = word;
}
public int getDistance()
{
return distance;
}
public void setDistance(int distance)
{
this.distance = distance;
}
#Override
public String toString()
{
return "CustomWord{" + "distance=" + distance + ", word=" + word + '}';
}
}
HandleLevenshteinDistance Class
/**
*
* #author blj0011
*/
public class HandleLevenshteinDistance
{
private List<String> dictionary = new ArrayList<>();
public HandleLevenshteinDistance()
{
try {
//Load DictionaryFrom file
//See if the dictionary file exists. If it don't download it from Github.
File file = new File("alpha.txt");
if (!file.exists()) {
FileUtils.copyURLToFile(
new URL("https://raw.githubusercontent.com/dwyl/english-words/master/words_alpha.txt"),
new File("alpha.txt"),
5000,
5000);
}
//Load file content to a List of Strings
dictionary = FileUtils.readLines(file, Charset.forName("UTF8"));
}
catch (IOException ex) {
ex.printStackTrace();
}
}
public List<String> getPotentialCorretSpellings(String misspelledWord)
{
LevenshteinDistance levenshteinDistance = new LevenshteinDistance();
List<CustomWord> customWords = new ArrayList();
dictionary.stream().forEach((wordInDictionary) -> {
int distance = levenshteinDistance.apply(misspelledWord, wordInDictionary);
if (distance <= 2) {
customWords.add(new CustomWord(distance, wordInDictionary));
}
});
Collections.sort(customWords, (CustomWord o1, CustomWord o2) -> o1.getDistance() - o2.getDistance());
List<String> returnList = new ArrayList();
customWords.forEach((item) -> {
System.out.println(item.getDistance() + " - " + item.getWord());
returnList.add(item.getWord());
});
return returnList;
}
}
You just needed to go a little further out into the Dictionary
We are sure you were getting a lot of suggested words from the Dictionary?
We tested your code and sometimes it found 3000 or more possible matches WOW
So here is the BIG improvement. It still needs a lot of testing we used this line for our tests with 100% favorable results.
Tske Charriage to hommaker and hommake as hommaer
Our fear is if the speller really butchers the word this improvement might solve that degree of misspelling
We are sure you know that if the first letter is wrong this will not work
Like zenophobe for xenophobe
Here is the BIG improvement tada
cs.stream().filter(s -> s.startsWith(strSF)
|| s.startsWith(nF, 0)
&& s.length() > 1 && s.length() <= W+3 // <== HERE
&& s.endsWith(nE)
&& s.startsWith(nF)
&& s.contains(nM))
.forEach(list :: add);
You can send the check to my address 55 48 196 195
This question is a possible duplicate: Search suggestion in strings
I think you should be using something similar to Levenshtein Distance or Jaro Winkler Distance. If you can use Apache's Commons. I would suggest using Apache Commons Lang. It has an implementation of Levenshtein Distance. The example demos this implementation. If you set the distance to (distance <= 2), you will potentially get more results.
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
/**
*
* #author blj0011
*/
public class Main
{
public static void main(String[] args)
{
try {
System.out.println("Hello World!");
File file = new File("alpha.txt");
if (!file.exists()) {
FileUtils.copyURLToFile(
new URL("https://raw.githubusercontent.com/dwyl/english-words/master/words_alpha.txt"),
new File("alpha.txt"),
5000,
5000);
}
List<String> lines = FileUtils.readLines(file, Charset.forName("UTF8"));
//lines.forEach(System.out::println);
lines.stream().forEach(line -> {
int distance = StringUtils.getLevenshteinDistance(line, "zorilta");
//System.out.println(line + ": " + distance);
if (distance <= 1) {
System.out.println("Did you mean: " + line);
}
});
}
catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Output distance <= 1
Building JavaTestingGround 1.0
------------------------------------------------------------------------
--- exec-maven-plugin:1.5.0:exec (default-cli) # JavaTestingGround ---
Hello World!
Did you mean: zorilla
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 1.329 s
Finished at: 2019-11-01T11:02:48-05:00
Final Memory: 7M/30M
Distance <= 2
Hello World!
Did you mean: corita
Did you mean: gorilla
Did you mean: zoril
Did you mean: zorilla
Did you mean: zorillas
Did you mean: zorille
Did you mean: zorillo
Did you mean: zorils
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 1.501 s
Finished at: 2019-11-01T14:03:33-05:00
Final Memory: 7M/34M
See the possible duplicate for more details about Levenshtein Distance.
I'm creating some code that prints a message to console with a border around it to reinforce my programming knowledge. I'm having issues with this specific snippet of code that should be splitting a large string into an array of strings which can then be printed
//splits message into multiple parts
//lines is an integer representing how many lines the text would take up within the provided border
//panewidth is an integer representing the desired size of the window created by the borders
String[] MessageParts = new String[lines];
for (int i = 0; i < lines; i++){
MessageParts[i] = (message.substring(i*(panewidth-2), (i+1)*(panewidth - 2)));
//
//HACK
System.out.println(MessageParts[i]);
//
}
Full code:
ChrisMadeaGame Class:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package chrismadeagame;
/**
*
* #author 570694
*/
public class ChrisMadeaGame {
/**
* #param args the command line arguments
*/
//Generates a statholder object for score
StatHolder Score = new StatHolder();
StatHolder Turns = new StatHolder();
public static void main(String[] args) {
// TODO code application logic here
ChrisMadeaGame ChrisMadeaGame = new ChrisMadeaGame();
ChrisMadeaGame.display("Test");
}
public void display(String message) {
//Width of pane goes here
final int panewidth = 80;
//The character used for the border
final String BorderChar = "*";
//The character used for whitespace
final String WhitespaceChar = " ";
//Calculates how many lines will be necessary to print the message. Always rounds up to an integer
final int lines = (int) Math.ceil((panewidth - 2)/message.length());
//
//HACK
System.out.println(lines);
System.out.println(message.length());
System.out.println(panewidth);
System.out.println((panewidth - 2)/message.length());
//
//splits message into multiple parts
String[] MessageParts = new String[lines];
for (int i = 0; i < lines; i++){
MessageParts[i] = (message.substring(i*(panewidth-2), (i+1)*(panewidth - 2)));
//
//HACK
System.out.println(MessageParts[i]);
//
}
//Prints out the top border
for (int i = 0; i < panewidth; i++){
System.out.print(BorderChar);
}
System.out.println("");
//Prints the score line
System.out.print(BorderChar);
System.out.print("");
//Figures out how much whitespace there needs to be after printing the score info
System.out.print("Score: " + Score.get() + " Turns: " + Turns.get());
for (int i = 0; i < panewidth -17 - Score.length() - Turns.length(); i++){
System.out.print(WhitespaceChar);
}
System.out.print(BorderChar);
System.out.println("");
//prints the message
for (int i = 0; i < lines; i++){
System.out.print(BorderChar);
System.out.print(MessageParts[i]);
System.out.print(BorderChar);
System.out.println("");
}
}
}
StatHolder Class:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package chrismadeagame;
/**
*
* #author 570694
*/
public class StatHolder{
//Generic object for holding a single integer
private int stat;
//Constructor
public StatHolder(int newStat){
stat = newStat;
}
public StatHolder(){
stat = 0;
}
//Methods
public void set(int stat){};
public int get(){return stat;};
public int length(){
return String.valueOf(stat).length();
}
};
Stack Trace:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 78
at java.lang.String.substring(String.java:1907)
at chrismadeagame.ChrisMadeaGame.display(ChrisMadeaGame.java:50)
at chrismadeagame.ChrisMadeaGame.main(ChrisMadeaGame.java:25)
Java Result: 1
As you can see there is java.lang.StringIndexOutOfBoundsException: String index out of range: 78 There is no 78th position in your array.
Please check the value for the number of lines because that's what is the size of the array you are defining:
String[] MessageParts = new String[lines];
It actually depends on the length of your message:
final int lines = (int) Math.ceil((panewidth - 2)/message.length());
what kind of exception do you get? IndexOutOfBOunds?
If that is the case then the string that you pass as parameter can not be sliced in so many parts as the number "lines"
................................
.XXXXXXXXXXXXXXX.....XXXXXXXXXX.
.X.....X.......X.....X........X.
.X.....X.......XXXXXXX........X.
.XXXXXXXXXXXX.................X.
.X....X.....X.................X.
.X....X.....XXXX..............X.
.XXXXXX........X..............X.
......X........X..............X.
......X........X..............X.
......X........X..............X.
......XXXXXXXXXXXXXXXXXXXXXXXXX.
................................
Looking for an algorithm to find the largest area. Here, "area" is defined as a number of dots (.) bounded by Xs.
private static void readFile(File inputFile) throws IOException {
Scanner fileScanner = new Scanner(inputFile);
Point previousPoint = null;
int rowCount = 0;
while(fileScanner.hasNext()){
String line = fileScanner.next();
String[] points = line.split(" ");
for(int columnCount=0;columnCount<points.length;columnCount++){
if(points[columnCount].equalsIgnoreCase("x")){
Point currentPoint = new Point();
currentPoint.setxValue(columnCount);
currentPoint.setyValue(rowCount);
}
}
rowCount++;
}
}
This is my first and struggling to move further.
This algorithm should work. You just need to implement it in Java.
Load the file into a char[][]. (1 char[] per line)
Loop through the char[][] (2 dimensionally)
upon finding a '.', perform flood fill, changing all '.' to ',', also incrementing a counter on every change.
At the end of flood fill, compare this counter with a globally set maximum. If it's higher, then set it as the new highest. (If the edges are not a proper boundary, then do not set this counter if you reached an edge during flood fill by setting a flag during 3)
Return the highest you set.
If you have any specific problems with the Java implementation, then let me know
Geobits:
Note: If you want to exclude the area "outside" any boxes, flood as
usual, but discard any area that hits the edge during the fill(skip
step 2.2 for that flood).
When doing the flood fill, you have 2 types of boundaries. A wall ('X'), and the edge of the array(which you need to explicitly check for to avoid OutOfBounds exceptions). If you hit an out of bounds, keep doing the fill, but set a flag so you know later to not consider the number you counted for the biggest box.
I was given this as assignment in an interview process and this is the compile and running code
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class FindArea {
public static void main(String[] args)
{
String fileName="C:\\map.txt";
FindArea area = new FindArea();
try{
FileReader inputFile = new FileReader(fileName);
BufferedReader bufferReader = new BufferedReader(inputFile);
char[][] twoArray= new char[100][100];
String line;
int i=0;
while ((line = bufferReader.readLine()) != null) {
twoArray[i] = line.toCharArray();
System.out.println(line);
i++;
}
bufferReader.close();
System.out.println("file read");
System.out.println("Max area: " + area.getMaxArea(twoArray));
} catch(Exception e) {
System.out.println("error : " + e.getMessage());
}
}
/**
* Get the maximum area from the given map
*
* #param charArray
* #return
*/
private int getMaxArea(char[][] charArray) {
HashMap<Integer, ArrayList<String>> numberOfBoxes = convertToBoxes(charArray);
numberOfBoxes = mergeOverlapAreas(numberOfBoxes);
int largeSize = 0;
for (Integer key : numberOfBoxes.keySet()) {
ArrayList<String> list = numberOfBoxes.get(key);
System.out.println("Key : " + key + " Size : " + list.size());
if (largeSize < list.size()) {
largeSize = list.size();
}
}
return largeSize;
}
/**
* Convert the 2d Array to HashMap
* Key being the count of boxes and
* Value being the list of indexes associations
*
* #param charArray
* #return
*/
private HashMap<Integer, ArrayList<String>> convertToBoxes(char[][] charArray) {
HashMap<Integer, ArrayList<String>> numberOfBoxes = new HashMap<Integer, ArrayList<String>>();
int boxes = 0;
for(int i=1; i<charArray.length; i++) {
for (int j=0; j<charArray[i].length; j++) {
if (charArray[i][j] == '.') {
boolean isExists = false;
for(Integer key : numberOfBoxes.keySet()) {
ArrayList<String> arrList = numberOfBoxes.get(key);
if(arrList != null) {
if(arrList.contains((i-1) + "-" + j) ||
arrList.contains(i + "-" + (j-1))) {
isExists = true;
arrList.add(i + "-" + j);
numberOfBoxes.put(key, arrList);
}
}
}
if (!isExists) {
ArrayList<String> list = new ArrayList<String>();
list.add(i + "-" + j);
numberOfBoxes.put(boxes, list);
boxes++;
}
}
}
}
return numberOfBoxes;
}
/**
* Check for the points exists in more than one area
* #param numberOfBoxes
* #return
*/
private HashMap<Integer, ArrayList<String>> mergeOverlapAreas( HashMap<Integer, ArrayList<String>> numberOfBoxes) {
for(Integer key : numberOfBoxes.keySet()) {
ArrayList<String> list1 = numberOfBoxes.get(key);
for (Integer key2 : numberOfBoxes.keySet()) {
if (key < key2) {
ArrayList<String> list2 = numberOfBoxes.get(key2);
Iterator<String> listIter = list2.iterator();
while(listIter.hasNext()) {
if (list1.contains(listIter.next())) {
list1.addAll(list2);
Set<String> noDuplicates = new HashSet<String>(list1);
numberOfBoxes.put(key, new ArrayList<String>(noDuplicates));
break;
}
}
}
}
}
return numberOfBoxes;
}
}
Here's an algorithm that's an alternative to flood fill. This method sweeps through the 2d array and whenever you encounter a node(pixel) that's outside to the left (right, top, bottom), it flags the current node as outside, ie if your neighbour is 'outside', you're marked 'outside' too.
The algorithm continues like this until there're no more updates. That means that all the nodes that are reachable from the 'outside' have been flagged. BTW, this is a very similar problem to level sets functions and updating them (where flood fill is also used). The nice this about this method is that it is ideal for parallelization.
1. Load 2D Symbol Array from File
2. hasupdates = false
3. Create 'isinside' bool array -> {
if(symbolarray[row][col] == '.' and row or col is at boundary)
isinside[row][col] = false
else
isinside[row][col] = true
}
4. do{
Do a sweep from left to right (for all rows) -> //This loop can be run parallely on all rows.
If (!isinside[row][col-1] and symbolarray[row][col] == '.'){
isinside[row][col] = false //mark current value as 'outside'
hasupdates = true
}
Do similar sweeps from right to left, top to bottom(all columns) and bottom to top.
}while(hasupdates)
5. Go through 'isinside' array and count the number of falses.
If you have huge files where you have to do this area calculation, you can have the sweeps along the rows and columns run parallely, because each row update (column update) is independent of the other updates.
I am creating the Sudoku game and I am trying to provide options to save, save as, and open games. I am using JFileChooser to do this. I am able to save (or "save as") but when I try to open a saved file, I get an error. I am new to programming and I'm hoping someone could spot the issue and educate me on how to read in the contents of the Sudoku board when I am saving (as well as how to deal with re-creating the Sudoku board when I open the file). I hear there is an easier way to deal with this using InputStream/OutputStream instead of Reader/Writer...
Here is my code for the inner class that implements this (I don't know if there's a way to post my entire class without exceeding the character limit for this text box.):
EDIT:
// this inner class provides a JMenuBar object at the top of
// the board
class MenuAtTop extends JMenuBar implements ActionListener{
// SudokuMain object we are dealing with
private SudokuMain main;
// the "File" menu
private JMenu fileMenu;
// the "New Game" option
private JMenuItem newGame;
// the "Open" option
private JMenuItem open;
// the "Save" option
private JMenuItem save;
// the "Save As" option
private JMenuItem saveAs;
// the "Reset" option
private JMenuItem reset;
// the "Quit" option
private JMenuItem quit;
// the ability to choose files
private JFileChooser choose;
// the saved file
// // compiler would not allow "static" keyword
private File fileSaved = null;
private Object opener;
// JDialog object to create a dialog box to prompt
// user for new game information
private JDialog createNewWin;
/**
* Constructs MenuAtTop object.
*
* #param m The SudokuMain object to be referred to.
*/
public MenuAtTop(final SudokuMain m) {
main = m;
opener = null;
choose = new JFileChooser();
// instantiate and bind to reference
fileMenu = new JMenu("File");
add(fileMenu);
// instantiate and bind to reference
newGame = new JMenuItem("New Game");
newGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
ActionEvent.CTRL_MASK));
fileMenu.add(newGame);
newGame.addActionListener(this);
open = new JMenuItem("Open");
open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
ActionEvent.CTRL_MASK));
fileMenu.add(open);
// add action listener to "Open" option
open.addActionListener(this);
save = new JMenuItem("Save");
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
ActionEvent.CTRL_MASK));
fileMenu.add(save);
// add action listener to "Save" option
save.addActionListener(this);
saveAs = new JMenuItem("Save As");
saveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
ActionEvent.CTRL_MASK));
fileMenu.add(saveAs);
// add action listener to "Save As" option
saveAs.addActionListener(this);
reset = new JMenuItem("Reset");
reset.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R,
ActionEvent.CTRL_MASK));
fileMenu.add(reset);
// add action listener to "Reset" option
reset.addActionListener(this);
quit = new JMenuItem("Quit");
quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
ActionEvent.CTRL_MASK));
fileMenu.add(quit);
// add action listener to "Quit" option
quit.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(newGame)) {
setEnabled(false);
// create dialog box prompting for the new board information
createNewWin = new Dialog1(main, "Create New Board", true);
// make it visible
createNewWin.setVisible(true);
fileSaved = null;
} else if(e.getSource().equals(open)) {
int returnVal = choose.showOpenDialog(main.win);
if(returnVal == JFileChooser.APPROVE_OPTION) {
boolean error = false;
File openFile = choose.getSelectedFile();
try {
FileInputStream fis = new FileInputStream(openFile);
ObjectInputStream ois = new ObjectInputStream(fis);
opener = ois.readObject();
SudokuBase sudoku = (SudokuBoard) opener;
ois.close();
} catch (Exception exc) {
exc.printStackTrace();
JOptionPane.showMessageDialog(main.win, "Error opening file.");
error = true;
}
// "opener" reads something and it is of type SudokuBase
if(opener != null && opener instanceof SudokuBase){
main.north.remove(main.rowColRegStates);
main.west.remove(main.symbols);
main.east.remove(main.view);
main.view = new SudokuView((SudokuBase) opener);
main.rowColRegStates = new ShowStates(main.view);
main.symbols = new SetSymbols(main.view);
main.north.add(main.rowColRegStates);
main.west.add(main.symbols);
main.east.add(main.view);
main.win.requestFocus();
fileSaved = openFile;
} else {
if(error) {
JOptionPane.showMessageDialog(main.win, "Incorrect file type.");
}
}
}
} else if(e.getSource().equals(save)) {
if(fileSaved == null) {
saveAsPrompt();
} else {
try {
FileOutputStream fos = new FileOutputStream(fileSaved);
ObjectOutputStream oos = new ObjectOutputStream(fos);
board.writeToStream(fos);
oos.writeObject(board);
oos.close();
} catch (Exception exc) {
JOptionPane.showMessageDialog(main.win, "Error saving file.");
}
}
} else if(e.getSource().equals(saveAs)) {
saveAsPrompt();
} else if(e.getSource().equals(reset)) {
int n = JOptionPane.showConfirmDialog(main.win,
"Any player values will" +
" be lost. Proceed?",
"Warning!", 2);
if(n == JOptionPane.OK_OPTION) {
main.board.reset();
main.view.repaint();
}
} else if(e.getSource().equals(quit)) {
closePrompt();
}
}
// This method prompts the user to choose a file to save to,
// and then saves the file.
private int saveAsPrompt() {
boolean saveError;
int rtn = choose.showSaveDialog(main.win);
if(rtn == JFileChooser.APPROVE_OPTION) {
saveError = false;
File fileSaveAs = choose.getSelectedFile();
try {
board.writeToStream(new FileOutputStream(fileSaveAs));
} catch (Exception e) {
JOptionPane.showMessageDialog(main.win, "Error saving file.");
saveError = true;
}
if(!saveError) {
fileSaved = fileSaveAs;
}
}
return rtn;
}
// This method prompts the user whether they want to save before
// closing, only if changes occurred.
private void closePrompt() {
if(true) {
int n = JOptionPane.showConfirmDialog(main.win, "Save game?");
if(n == JOptionPane.YES_OPTION) {
int saved = saveAsPrompt();
if(saved != JFileChooser.CANCEL_OPTION){
main.win.dispose();
}
} else if(n == JOptionPane.NO_OPTION) {
main.win.dispose();
}
}
else { // no changes were made
main.win.dispose();
}
}
}
Here's the class that holds the data (it is extended by SudokuBoard):
// Allow short name access to following classes
import java.util.Observable;
import java.io.InputStream;
import java.io.OutputStream;
public abstract class SudokuBase extends Observable {
// rows per region
private int rows;
// columns per region
private int columns;
// size of a region (rows * columns)
private int size;
// array of each element of entire sudoku board
private int[] grid;
// the masked 8-bit "given" value constant
private static final int GIVEN_MASK = 0x00000100;
// the bitwise complement of the masked "given" constant,
// which produces an unmasked constant
private static final int GIVEN_UNMASK = ~ GIVEN_MASK;
/**
* Enumerated type to store constants that indicate the "State" of
* a specified row, column, or region.
*/
public enum State {COMPLETE, INCOMPLETE, ERROR};
/**
* Constructs SudokuBase object.
*
* #param layoutRows The number of rows per region.
* #param layoutColumns The number of columns per region.
*/
public SudokuBase(int layoutRows, int layoutColumns) {
rows = layoutRows;
columns = layoutColumns;
size = columns * rows;
grid = new int[size*size];
}
/**
* Gets the number of rows per region.
*
* #return The rows per region.
*/
public int getRowsPerRegion() {
return rows;
}
/**
* Gets the number of columns per region.
*
* #return The columns per region.
*/
public int getColumnsPerRegion() {
return columns;
}
/**
* Gets the size of the region (rows * columns).
*
* #return The size of the region.
*/
public int getBoardSize() {
return size;
}
// gets the index of the specified row and column for the grid
private int getIndex(int row, int col) {
// handle invalid arguments
if(row < 0 || row >= size || col < 0 || col >= size) {
String msg = "Error in location";
throw new IllegalArgumentException(msg);
}
return row * size + col;
}
/**
* Gets the value of the element at the specified row
* and column on the grid.
*
* #param row The specified row.
* #param col The specified column.
* #return The value of the element at the specified row and column.
*/
public int getValue(int row, int col) {
return grid[getIndex(row, col)] & GIVEN_UNMASK;
}
/**
* Sets the desired value at the specified row and column.
*
* #param row The specified row.
* #param col The specified column.
* #param value The specified value to be set.
*/
public void setValue(int row, int col, int value) {
// handle invalid argument
if(value < 0 || value > size) {
String msg = "Value out of range: " + value;
throw new IllegalArgumentException(msg);
}
// handle attempt to set a value for a "given" location
if(isGiven(row, col)) {
String msg = "Cannot set given location: " + row + ", " + col;
throw new IllegalStateException(msg);
}
grid[getIndex(row, col)] = value;
setChanged();
notifyObservers();
}
/**
* This method checks the status of the "givens" bit.
*
* #param row The specified row.
* #param col The specified column.
* #return Whether or not the specified location is a "given" value.
*/
public boolean isGiven(int row, int col) {
return (grid[getIndex(row, col)] & GIVEN_MASK) == GIVEN_MASK;
}
/**
* This method sets non-zero values on the Sudoku board with the
* "givens" bit.
*/
public void fixGivens() {
for(int i = 0; i < grid.length; i++)
if(grid[i] != 0)
grid[i] |= GIVEN_MASK;
setChanged();
notifyObservers();
}
/**
* This abstract method gets the "State" (COMPLETE, INCOMPLETE,
* or ERROR) of the specified row.
*
* #param n The specified row.
* #return The "State" of the row.
*/
public abstract State getRowState(int n);
/**
* This abstract method gets the "State" (COMPLETE, INCOMPLETE,
* or ERROR) of the specified column.
*
* #param n The specified column.
* #return The "State" of the column.
*/
public abstract State getColumnState(int n);
/**
* This abstract method gets the "State" (COMPLETE, INCOMPLETE,
* or ERROR) of the specified region.
*
* #param n The specified region.
* #return The "State" of the region.
*/
public abstract State getRegionState(int n);
/**
* Represents the Sudoku board as a grid of appropriate characters.
*
* #return The string representation of the Sudoku board.
*/
public String toString() {
String board = "";
for(int i = 0; i < size; i ++) {
for(int j = 0; j < size; j ++)
board += charFor(i, j) + " ";
board += "\n";
}
return board;
}
// this method provides a character for all possible values encountered on the
// Sudoku board, to be utilized in "toString()"
private String charFor(int i, int j) {
int v = getValue(i, j);
// negative value (invalid)
if(v < 0) {
return "?";
} else if(v == 0) { // blank or zero value
return ".";
} else if(v < 36) { // value from 1 to (size * size)
return Character.toString(Character.forDigit(v, 36)).toUpperCase();
} else { // non-numeric input or v >= size * size (both invalid)
return "?";
}
}
/**
* This method reads from an input stream.
*
* #param is The input stream to read from.
*/
protected void readFromStream(InputStream is) {
}
/**
* This method writes to an output stream.
*
* #param os The output stream to write to.
*/
protected void writeToStream(OutputStream os) {
try {
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.close();
} catch(IOException e) {
}
}
/**
* Gets the "raw" value directly, not having checked whether there is an
* unfixed error message.
*
* #param row The row where the raw value is located.
* #param col The column where the raw value is located.
* #return The raw value.
*/
protected int getRawValue(int row, int col) {
return grid[getIndex(row, col)];
}
/**
* Sets the raw value directly, not having checked whether there is an
* unfixed error message.
*
* #param row The row where the raw value is to be located.
* #param col The column where the raw value is to be located.
*/
protected void setRawValue(int row, int col, int value) {
grid[getIndex(row, col)] = value;
}
protected void reset() {
for(int row = 0; row < rows; row++) {
for(int col = 0; col < columns; col++) {
if(!isGiven(row, col)) {
grid[getIndex(row, col)] = 0;
}
}
}
}
}
Well I cannot give a full answer and I do not want to browse the full source code. But a few pointers for you to find some solution:
Never catch Exceptions like that while developing an application:
} catch (Exception e) {
JOptionPane.showMessageDialog(main.win, "Error saving file.");
saveError = true;
}
With this, you completely loose the chance to detect errors. At least add the following line to your exception handling:
e.printStackTrace();
Normally you would log the exception and so on, but with that you see the source of your error at the console. Better than nothing.
To your more specific problem:
You seem to write an Object to a file holding all the configuration. In your read method something goes wrong. Probably you do not read the same object as you write or something like that. Hard to say without any code. Try to get the exception stack trace and figure out what the problem is. If you cannot figure it out, edit your question with more specific information and I will try to give better directions.
EDIT:
Here is a small example showing serialization of objects for a Sudoku like game:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class SerializationExample {
public static void main(String args[]) throws IOException, ClassNotFoundException {
final File target = new File(System.getProperty("java.io.tmp"), "mySerializedObject.txt");
Map<Integer, Integer> initialState = new HashMap<Integer, Integer>();
initialState.put(1, 1);
initialState.put(21, 3);
// ...
GameState state = new GameState(10, initialState);
state.setField(2, 2);
state.setField(3, 8);
System.out.println("Game state before writing to file: " + state);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(target));
out.writeObject(state);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(target));
Object gameStateReadFromFile = in.readObject();
GameState readGameState = (GameState)gameStateReadFromFile;
System.out.println("Read from file: " + readGameState);
}
private static class GameState implements Serializable {
private int[] fields;
private int boardSize;
private int[] fixedFields;
public GameState(int boardSize, Map<Integer, Integer> initialState) {
this.boardSize = boardSize;
this.fields = new int[boardSize * boardSize];
this.fixedFields = new int[this.fields.length];
for (Entry<Integer, Integer> entry : initialState.entrySet()) {
this.fixedFields[entry.getKey()] = entry.getValue();
}
}
public void setField(int index, int value) {
this.fields[index] = value;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("\nFixed fields: ");
appendArray(builder, this.fixedFields);
builder.append("\nSet fields: ");
appendArray(builder, this.fields);
return builder.toString();
}
private void appendArray(StringBuilder builder, int[] fieldArray) {
for (int i = 0; i < fieldArray.length; ++i) {
if (fieldArray[i] != 0) {
builder.append("row ").append(i / this.boardSize).append(" column ").append(i % this.boardSize)
.append(" has value ")
.append(fieldArray[i]).append(",");
}
}
}
}
}
I'm trying to write my own implementation of Watershed Segmentation for a project. I have a version that returns something resembling the correct segmentation given really trivial pictures. Unfortunately, it's super-slow/inefficient and it may or may not terminate in all cases.
I've been working from the description in "Digital Image Processing," by Woods and Gonzales, and from the Watershed Wikipedia page. The general algorithm is coded and included below, but I have a feeling I'm looping over a lot of things I do not need to be. I appreciate any and all help here, thanks in advance.
public static Set<Point> watershed(double[][] im) {
//Get the Gradient Magnitude to use as our "topographic map."
double t1 = System.currentTimeMillis();
double[][] edges = EdgeDetection.sobelMagnitude(im);
//Only iterate over values in the gradient magnitude to speed up.
double[] histogram = Image.getHistogram(edges);
Image.drawHistogram(histogram, Color.red);
int minPixelValue = 0;
for (int i = 0; i < histogram.length; i++) {
if (histogram[i] > 0) {
minPixelValue = i;
break;
}
}
int h = im.length;
int w = im[0].length;
//SE is a 3x3 structuring element for morphological dilation.
boolean[][] SE = {{true, true, true}, {true, true, true}, {true, true, true}};
//Keeping track of last iteration's components to see if two flooded together.
ArrayList<Set<Point>> lastComponents = connectedComponents(getSet(EdgeDetection.underThreshold(edges, minPixelValue + 1)));
ArrayList<Set<Point>> components;
Set<Point> boundary = new HashSet<Point>();
for (int i = minPixelValue + 1; i < 256; i++) {
if (histogram[i] != 0) {
System.out.println("BEHHH " + i);
t1 = System.currentTimeMillis();
ArrayList<Integer> damLocations = new ArrayList<Integer>();
HashMap<Integer, ArrayList<Integer>> correspondingSets = new HashMap<Integer, ArrayList<Integer>>();
//Figure out which of the old sets the new sets incorporated.
//Here is where we check if sets flooded into eachother.
//System.out.println("Checking for flooding");
components = connectedComponents(getSet(EdgeDetection.underThreshold(edges, i)));
for (int nc = 0; nc < components.size(); nc++) {
//System.out.println("Checking component " + nc);
Set<Point> newComponent = components.get(nc);
for (int oc = 0; oc < lastComponents.size(); oc++) {
//System.out.println(" Against component " + oc);
Set<Point> oldComponent = lastComponents.get(oc);
if (numberInCommon(newComponent, oldComponent) > 0) {
//System.out.println(" In there.");
ArrayList<Integer> oldSetsContained;
if (correspondingSets.containsKey(nc)) {
oldSetsContained = correspondingSets.get(nc);
damLocations.add(nc);
} else {
//System.out.println(" Nope.");
oldSetsContained = new ArrayList<Integer>();
}
oldSetsContained.add(oc);
correspondingSets.put(nc, oldSetsContained);
}
}
}
System.out.println("Calculating overlapping sets: " + (System.currentTimeMillis() - t1));
//System.out.println("Check done.");
for (int key : correspondingSets.keySet()) {
Integer[] cs = new Integer[correspondingSets.get(key).size()];
correspondingSets.get(key).toArray(cs);
if (cs.length == 1) {
//System.out.println("Set " + cs[0] + " has grown without flooding.");
} else {
//System.out.println("The following sets have flooded together: " + Arrays.toString(cs));
}
}
//Build Damns to prevent flooding
for (int c : damLocations) {
System.out.println("Building dam for component " + c);
Set<Point> bigComponent = components.get(c);
System.out.println("Total size: " + bigComponent.size());
ArrayList<Set<Point>> littleComponent = new ArrayList<Set<Point>>();
for (int lcindex : correspondingSets.get(c)) {
littleComponent.add(lastComponents.get(lcindex));
}
Set<Point> unionSet = new HashSet<Point>(boundary);
for (Set<Point> lc : littleComponent) {
unionSet = union(unionSet, lc);
}
System.out.println("Building union sets: " + (System.currentTimeMillis() - t1));
while (intersection(unionSet, bigComponent).size() < bigComponent.size()) {
for (int lIndex = 0; lIndex < littleComponent.size(); lIndex++) {
Set<Point> lc = littleComponent.get(lIndex);
Set<Point> lcBoundary = extractBoundaries(lc, SE, h, w);
Set<Point> toAdd = new HashSet<Point>();
Set<Point> otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
otherComponents = extractBoundaries(otherComponents, SE, h, w);
for (Point pt : lcBoundary) {
Set<Point> eightNbrs = get8Neighborhood(pt);
for (Point nbr : eightNbrs) {
if (bigComponent.contains(nbr) & !boundary.contains(nbr)) {
Set<Point> neighborNbr = get8Neighborhood(nbr);
if (intersection(neighborNbr, otherComponents).size() > 0) {
boundary.add(nbr);
edges[nbr.y][nbr.x] = 256;
break;
} else if (!lc.contains(nbr)) {
toAdd.add(nbr);
//if(i==65)System.out.println("Adding point " + nbr.y + " " + nbr.x);
} else {
//if(i==65)System.out.println("Already in here " + nbr.y + " " + nbr.x);
}
}
}
}
t1 = System.currentTimeMillis();
lc.addAll(toAdd);
toAdd.removeAll(toAdd);
littleComponent.set(lIndex, lc);
unionSet = new HashSet<Point>(boundary);
for (Set<Point> ltc : littleComponent) {
unionSet = union(unionSet, ltc);
}
System.out.println("This is a donk " + intersection(unionSet, bigComponent).size());
otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
}
}
}
}
}
boundary = close(boundary,h,w);
Image.drawSet(boundary, h, w);
return boundary;
}
The algorithm as it seems is at most O(n^2).
You have many many nested loops.. I was not able to find the Woods description.
This code by Christopher Mei implements the algorithm: it is a really simple implementation.
WatershedPixel.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.*;
/**
* The aim of WatershedPixel is to enable
* sorting the pixels of an Image according
* to their grayscale value.
*
* This is the first step of the Vincent
* and Soille Watershed algorithm (1991)
*
**/
public class WatershedPixel implements Comparable {
/** Value used to initialise the image */
final static int INIT = -1;
/** Value used to indicate the new pixels that
* are going to be processed (intial value
* at each level)
**/
final static int MASK = -2;
/** Value indicating that the pixel belongs
* to a watershed.
**/
final static int WSHED = 0;
/** Fictitious pixel **/
final static int FICTITIOUS = -3;
/** x coordinate of the pixel **/
private int x;
/** y coordinate of the pixel **/
private int y;
/** grayscale value of the pixel **/
private byte height;
/** Label used in the Watershed immersion algorithm **/
private int label;
/** Distance used for working on pixels */
private int dist;
/** Neighbours **/
private Vector neighbours;
public WatershedPixel(int x, int y, byte height) {
this.x = x;
this.y = y;
this.height = height;
label = INIT;
dist = 0;
neighbours = new Vector(8);
}
public WatershedPixel() {
label = FICTITIOUS;
}
public void addNeighbour(WatershedPixel neighbour) {
/*IJ.write("In Pixel, adding :");
IJ.write(""+neighbour);
IJ.write("Add done");
*/
neighbours.add(neighbour);
}
public Vector getNeighbours() {
return neighbours;
}
public String toString() {
return new String("("+x+","+y+"), height : "+getIntHeight()+", label : "+label+", distance : "+dist);
}
public final byte getHeight() {
return height;
}
public final int getIntHeight() {
return (int) height&0xff;
}
public final int getX() {
return x;
}
public final int getY() {
return y;
}
/** Method to be able to use the Collections.sort static method. **/
public int compareTo(Object o) {
if(!(o instanceof WatershedPixel))
throw new ClassCastException();
WatershedPixel obj = (WatershedPixel) o;
if( obj.getIntHeight() < getIntHeight() )
return 1;
if( obj.getIntHeight() > getIntHeight() )
return -1;
return 0;
}
public void setLabel(int label) {
this.label = label;
}
public void setLabelToINIT() {
label = INIT;
}
public void setLabelToMASK() {
label = MASK;
}
public void setLabelToWSHED() {
label = WSHED;
}
public boolean isLabelINIT() {
return label == INIT;
}
public boolean isLabelMASK() {
return label == MASK;
}
public boolean isLabelWSHED() {
return label == WSHED;
}
public int getLabel() {
return label;
}
public void setDistance(int distance) {
dist = distance;
}
public int getDistance() {
return dist;
}
public boolean isFICTITIOUS() {
return label == FICTITIOUS;
}
public boolean allNeighboursAreWSHED() {
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( !r.isLabelWSHED() )
return false;
}
return true;
}
}
WatershedFIFO.java
/*
* Watershed plugin
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.util.*;
import ij.*;
/** This class implements a FIFO queue that
* uses the same formalism as the Vincent
* and Soille algorithm (1991)
**/
public class WatershedFIFO {
private LinkedList watershedFIFO;
public WatershedFIFO() {
watershedFIFO = new LinkedList();
}
public void fifo_add(WatershedPixel p) {
watershedFIFO.addFirst(p);
}
public WatershedPixel fifo_remove() {
return (WatershedPixel) watershedFIFO.removeLast();
}
public boolean fifo_empty() {
return watershedFIFO.isEmpty();
}
public void fifo_add_FICTITIOUS() {
watershedFIFO.addFirst(new WatershedPixel());
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedFIFO.size(); i++) {
ret.append( ((WatershedPixel)watershedFIFO.get(i)).toString() );
ret.append( "\n" );
}
return ret.toString();
}
}
WatershedStructure.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.process.*;
import ij.*;
import java.awt.*;
/**
* WatershedStructure contains the pixels
* of the image ordered according to their
* grayscale value with a direct access to their
* neighbours.
*
**/
public class WatershedStructure {
private Vector watershedStructure;
public WatershedStructure(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
Rectangle r = ip.getRoi();
int width = ip.getWidth();
int offset, topOffset, bottomOffset, i;
watershedStructure = new Vector(r.width*r.height);
/** The structure is filled with the pixels of the image. **/
for (int y=r.y; y<(r.y+r.height); y++) {
offset = y*width;
IJ.showProgress(0.1+0.3*(y-r.y)/(r.height));
for (int x=r.x; x<(r.x+r.width); x++) {
i = offset + x;
int indiceY = y-r.y;
int indiceX = x-r.x;
watershedStructure.add(new WatershedPixel(indiceX, indiceY, pixels[i]));
}
}
/** The WatershedPixels are then filled with the reference to their neighbours. **/
for (int y=0; y<r.height; y++) {
offset = y*width;
topOffset = offset+width;
bottomOffset = offset-width;
IJ.showProgress(0.4+0.3*(y-r.y)/(r.height));
for (int x=0; x<r.width; x++) {
WatershedPixel currentPixel = (WatershedPixel)watershedStructure.get(x+offset);
if(x+1<r.width) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+topOffset));
}
if(x-1>=0) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+topOffset));
}
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+topOffset));
}
}
Collections.sort(watershedStructure);
//IJ.showProgress(0.8);
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedStructure.size() ; i++) {
ret.append( ((WatershedPixel) watershedStructure.get(i)).toString() );
ret.append( "\n" );
ret.append( "Neighbours :\n" );
Vector neighbours = ((WatershedPixel) watershedStructure.get(i)).getNeighbours();
for(int j=0 ; j<neighbours.size() ; j++) {
ret.append( ((WatershedPixel) neighbours.get(j)).toString() );
ret.append( "\n" );
}
ret.append( "\n" );
}
return ret.toString();
}
public int size() {
return watershedStructure.size();
}
public WatershedPixel get(int i) {
return (WatershedPixel) watershedStructure.get(i);
}
}
Watershed_Algorithm.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import ij.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import ij.gui.*;
import ij.plugin.frame.PlugInFrame;
import java.awt.*;
import java.util.*;
/**
* This algorithm is an implementation of the watershed immersion algorithm
* written by Vincent and Soille (1991).
*
* #Article{Vincent/Soille:1991,
* author = "Lee Vincent and Pierre Soille",
* year = "1991",
* keywords = "IMAGE-PROC SKELETON SEGMENTATION GIS",
* institution = "Harvard/Paris+Louvain",
* title = "Watersheds in digital spaces: An efficient algorithm
* based on immersion simulations",
* journal = "IEEE PAMI, 1991",
* volume = "13",
* number = "6",
* pages = "583--598",
* annote = "Watershed lines (e.g. the continental divide) mark the
* boundaries of catchment regions in a topographical map.
* The height of a point on this map can have a direct
* correlation to its pixel intensity. WIth this analogy,
* the morphological operations of closing (or opening)
* can be understood as smoothing the ridges (or filling
* in the valleys). Develops a new algorithm for obtaining
* the watershed lines in a graph, and then uses this in
* developing a new segmentation approach based on the
* {"}depth of immersion{"}.",
* }
*
* A review of Watershed algorithms can be found at :
* http://www.cs.rug.nl/~roe/publications/parwshed.pdf
*
* #Article{RoeMei00,
* author = "Roerdink and Meijster",
* title = "The Watershed Transform: Definitions, Algorithms and
* Parallelization Strategies",
* journal = "FUNDINF: Fundamenta Informatica",
* volume = "41",
* publisher = "IOS Press",
* year = "2000",
* }
**/
public class Watershed_Algorithm implements PlugInFilter {
private int threshold;
final static int HMIN = 0;
final static int HMAX = 256;
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about"))
{showAbout(); return DONE;}
return DOES_8G+DOES_STACKS+SUPPORTS_MASKING+NO_CHANGES;
}
public void run(ImageProcessor ip) {
boolean debug = false;
IJ.showStatus("Sorting pixels...");
IJ.showProgress(0.1);
/** First step : the pixels are sorted according to increasing grey values **/
WatershedStructure watershedStructure = new WatershedStructure(ip);
if(debug)
IJ.write(""+watershedStructure.toString());
IJ.showProgress(0.8);
IJ.showStatus("Start flooding...");
if(debug)
IJ.write("Starting algorithm...\n");
/** Start flooding **/
WatershedFIFO queue = new WatershedFIFO();
int curlab = 0;
int heightIndex1 = 0;
int heightIndex2 = 0;
for(int h=HMIN; h<HMAX; h++) /*Geodesic SKIZ of level h-1 inside level h */ {
for(int pixelIndex = heightIndex1 ; pixelIndex<watershedStructure.size() ; pixelIndex++) /*mask all pixels at level h*/ {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex1 = pixelIndex;
break;
}
p.setLabelToMASK();
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(q.getLabel()>=0) {/*Initialise queue with neighbours at level h of current basins or watersheds*/
p.setDistance(1);
queue.fifo_add(p);
break;
} // end if
} // end for
} // end for
int curdist = 1;
queue.fifo_add_FICTITIOUS();
while(true) /** extend basins **/{
WatershedPixel p = queue.fifo_remove();
if(p.isFICTITIOUS())
if(queue.fifo_empty())
break;
else {
queue.fifo_add_FICTITIOUS();
curdist++;
p = queue.fifo_remove();
}
if(debug) {
IJ.write("\nWorking on :");
IJ.write(""+p);
}
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* Labelling p by inspecting neighbours */{
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(debug)
IJ.write("Neighbour : "+q);
/* Original algorithm :
if( (q.getDistance() < curdist) &&
(q.getLabel()>0 || q.isLabelWSHED()) ) {*/
if( (q.getDistance() <= curdist) &&
(q.getLabel()>=0) ) {
/* q belongs to an existing basin or to a watershed */
if(q.getLabel() > 0) {
if( p.isLabelMASK() )
// Removed from original algorithm || p.isLabelWSHED() )
p.setLabel(q.getLabel());
else
if(p.getLabel() != q.getLabel())
p.setLabelToWSHED();
} // end if lab>0
else
if(p.isLabelMASK())
p.setLabelToWSHED();
}
else
if( q.isLabelMASK() &&
(q.getDistance() == 0) ) {
if(debug)
IJ.write("Adding value");
q.setDistance( curdist+1 );
queue.fifo_add( q );
}
} // end for, end processing neighbours
if(debug) {
IJ.write("End processing neighbours");
IJ.write("New val :\n"+p);
IJ.write("Queue :\n"+queue);
}
} // end while (loop)
/* Detect and process new minima at level h */
for(int pixelIndex = heightIndex2 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex2 = pixelIndex;
break;
}
p.setDistance(0); /* Reset distance to zero */
if(p.isLabelMASK()) { /* the pixel is inside a new minimum */
curlab++;
p.setLabel(curlab);
queue.fifo_add(p);
while(!queue.fifo_empty()) {
WatershedPixel q = queue.fifo_remove();
Vector neighbours = q.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* inspect neighbours of p2*/{
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( r.isLabelMASK() ) {
r.setLabel(curlab);
queue.fifo_add(r);
}
}
} // end while
} // end if
} // end for
} /** End of flooding **/
IJ.showProgress(0.9);
IJ.showStatus("Putting result in a new image...");
/** Put the result in a new image **/
int width = ip.getWidth();
ImageProcessor outputImage = new ByteProcessor(width, ip.getHeight());
byte[] newPixels = (byte[]) outputImage.getPixels();
for(int pixelIndex = 0 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.isLabelWSHED() && !p.allNeighboursAreWSHED())
newPixels[p.getX()+p.getY()*width] = (byte)255;
}
IJ.showProgress(1);
IJ.showStatus("Displaying result...");
new ImagePlus("Watershed", outputImage).show();
}
void showAbout() {
IJ.showMessage("About Watershed_Algorithm...",
"This plug-in filter calculates the watershed of a 8-bit images.\n" +
"It uses the immersion algorithm written by Vincent and Soille (1991)\n"
);
}
}