├── .gitignore ├── README.md └── src ├── com └── zarkonnen │ ├── fruitbat │ └── atrio │ │ ├── ATRReader.java │ │ ├── ATRWriter.java │ │ ├── README │ │ └── SimpleATRReader.java │ └── longan │ ├── Histogram.java │ ├── LetterTestDataCategoriser.java │ ├── Longan.java │ ├── Main.java │ ├── Metadata.java │ ├── Util.java │ ├── Visualizer.java │ ├── better │ ├── AggressiveLetterSplittingPostProcessor.java │ ├── BetterChunker2.java │ ├── BetterLetterFinder.java │ ├── IntensityHistogramPreProcessor.java │ ├── LetterSplittingPostProcessor.java │ ├── LetterXComparator.java │ ├── RotationFixingPreProcessor.java │ ├── SpeckleEliminator.java │ └── WordXComparator.java │ ├── data │ ├── Column.java │ ├── Letter.java │ ├── Line.java │ ├── Picture.java │ ├── Result.java │ └── Word.java │ ├── hmm │ └── Hmm.java │ ├── nnidentifier │ ├── Config.java │ ├── Counter.java │ ├── DiscriminatorNet.java │ ├── Example.java │ ├── ExampleGenerator2.java │ ├── FastLoadingNetwork.java │ ├── Identifier.java │ ├── IdentifierNet.java │ ├── NNShapeGen.java │ ├── NearestNeighbour.java │ ├── NetworkIO.java │ ├── ProfileGen.java │ ├── SVM.java │ ├── TreePredict.java │ ├── Visualizer.java │ ├── data │ │ ├── georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo.json │ │ ├── georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_aspectRatios.json │ │ ├── georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_sizes.json │ │ ├── georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_targets_0 │ │ ├── georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_weights_0 │ │ ├── georgia_optima_times_timesnewroman__b-ucr-uc.json │ │ ├── georgia_optima_times_timesnewroman__b-ucr-uc_tree │ │ ├── georgia_optima_times_timesnewroman__i!.json │ │ ├── georgia_optima_times_timesnewroman__i!_comparisons │ │ ├── georgia_optima_times_timesnewroman__i-ucl1i!.json │ │ ├── georgia_optima_times_timesnewroman__i-ucl1i!_numberOfParts.json │ │ ├── georgia_optima_times_timesnewroman__ij.json │ │ ├── georgia_optima_times_timesnewroman__ij_comparisons │ │ └── source.json │ ├── identifier-alt.lns │ ├── identifier.lns │ └── network │ │ ├── Connection.java │ │ ├── Layer.java │ │ ├── Network.java │ │ ├── Node.java │ │ ├── Output.java │ │ ├── Util.java │ │ └── Weight.java │ ├── simple │ └── SimpleWordPlaintextConverter.java │ └── stage │ ├── Chunker.java │ ├── LetterFinder.java │ ├── LetterIdentifier.java │ ├── PostProcessor.java │ ├── PreProcessor.java │ └── ResultConverter.java └── org ├── apache └── commons │ └── cli │ ├── AlreadySelectedException.java │ ├── BasicParser.java │ ├── CommandLine.java │ ├── CommandLineParser.java │ ├── GnuParser.java │ ├── HelpFormatter.java │ ├── MissingArgumentException.java │ ├── MissingOptionException.java │ ├── Option.java │ ├── OptionBuilder.java │ ├── OptionGroup.java │ ├── OptionValidator.java │ ├── Options.java │ ├── ParseException.java │ ├── Parser.java │ ├── PatternOptionBuilder.java │ ├── PosixParser.java │ ├── TypeHandler.java │ ├── UnrecognizedOptionException.java │ ├── Util.java │ ├── overview.html │ └── package.html └── json ├── CDL.java ├── Cookie.java ├── CookieList.java ├── HTTP.java ├── HTTPTokener.java ├── JSONArray.java ├── JSONException.java ├── JSONML.java ├── JSONObject.java ├── JSONString.java ├── JSONStringer.java ├── JSONTokener.java ├── JSONWriter.java ├── XML.java └── XMLTokener.java /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | build.xml 3 | manifest.mf 4 | nbproject 5 | lib/junit/ 6 | lib/junit_4/ 7 | lib/nblibraries.properties 8 | lib/CopyLibs 9 | dist 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](http://www.zarkonnen.com/static/bumble/media/uploads/longan_logo_200.png) 2 | 3 | The aim of this project is to write a reasonably (competent, modular, understandable) OCR system. 4 | 5 | This project is licensed under the Apache 2.0 licence. 6 | 7 | [Current Project State](http://www.zarkonnen.com/the_state_of_longan/) 8 | -------------------------------------------------------------------------------- /src/com/zarkonnen/fruitbat/atrio/ATRWriter.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.fruitbat.atrio; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.util.Collection; 22 | 23 | /** 24 | * Writes records of UTF-8 fields into an ASCII-based format called Atomic Text Records. When used 25 | * append-only, ATRReader (and SimpleATRReader) will ignore all fields and records that failed to be 26 | * written in their entirety, making appending fields and records atomic in practice. 27 | * 28 | * The format only uses visible ASCII characters in the range of " " (32) to "~" (126). The record 29 | * start marker is line feed, and the field end marker is tab, making the format readable as TSV. 30 | * However note that records not terminated with "%" and fields not started with ":" should be 31 | * ignored. 32 | * 33 | * Backslash, colon, tab, line feed, percent and carriage return are all escaped using two-character 34 | * escape codes, while all other characters not in the range of " " to "~" are represented as 35 | * six-digit hexadecimal representations of their unicode code points. 36 | */ 37 | public class ATRWriter { 38 | // The range in which characters (with the exception of backslash, %, lf, :, tab, and cr) can be 39 | // put in directly. 40 | static final int RAW_MIN = 32; 41 | static final int RAW_MAX = 126; 42 | 43 | // Backslash used for all escape sequences, u for unicode hex codes. 44 | static final int ESCAPE = (int) '\\'; static final int UNICODE_E = (int) 'u'; 45 | 46 | // : starts fields, \t ends them. 47 | static final int F_START = (int) ':'; static final int F_START_E = (int) 'c'; 48 | static final int F_END = (int) '\t'; static final int F_END_E = (int) 't'; 49 | 50 | // \n starts records, % ends them. 51 | static final int R_START = (int) '\n'; static final int R_START_E = (int) 'n'; 52 | static final int R_END = (int) '%'; static final int R_END_E = (int) 'p'; 53 | 54 | // Also escaping carriage return for sanity purposes. 55 | static final int CR = (int) '\r'; static final int CR_E = (int) 'r'; 56 | 57 | // Base numbers for quickly encoding hexadecimal. 58 | static final int ZERO = (int) '0'; static final int A = (int) 'a'; 59 | 60 | private final OutputStream out; 61 | 62 | /** Note: ATRWriter is unbuffered. */ 63 | public ATRWriter(OutputStream out) { this.out = out; } 64 | public void close() throws IOException { out.close(); } 65 | public void flush() throws IOException { out.flush(); } 66 | 67 | public void startRecord() throws IOException { 68 | out.write(R_START); 69 | } 70 | 71 | public void endRecord() throws IOException { 72 | out.write(R_END); 73 | } 74 | 75 | /** 76 | * Writes a record containing the fields. If the previous record wasn't ended, it will be 77 | * ignored. 78 | */ 79 | public void writeRecord(Collection fields) throws IOException { 80 | startRecord(); 81 | for (String f : fields) { write(f); } 82 | endRecord(); 83 | } 84 | 85 | public void write(String field) throws IOException { 86 | out.write(F_START); 87 | int charIndex = 0; 88 | while (charIndex < field.length()) { 89 | int codePoint = field.codePointAt(charIndex); 90 | charIndex += Character.charCount(codePoint); 91 | switch (codePoint) { 92 | case ESCAPE: 93 | out.write(ESCAPE); out.write(ESCAPE); break; 94 | case F_START: 95 | out.write(ESCAPE); out.write(F_START_E); break; 96 | case F_END: 97 | out.write(ESCAPE); out.write(F_END_E); break; 98 | case R_START: 99 | out.write(ESCAPE); out.write(R_START_E); break; 100 | case R_END: 101 | out.write(ESCAPE); out.write(R_END_E); break; 102 | case CR: 103 | out.write(ESCAPE); out.write(CR_E); break; 104 | default: { 105 | if (codePoint >= RAW_MIN && codePoint <= RAW_MAX) { 106 | out.write(codePoint); 107 | } else { 108 | // Write the codepoint in its full glory. To do this, we write the escape 109 | // char, then the unicode escape char, then six hexadecimal digits. 110 | out.write(ESCAPE); 111 | out.write(UNICODE_E); 112 | for (int shift = 5 * 4; shift >= 0; shift -= 4) { 113 | int nyb = (codePoint >>> shift) % 16; 114 | out.write(nyb < 10 ? ZERO + nyb : A - 10 + nyb); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | out.write(F_END); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/com/zarkonnen/fruitbat/atrio/README: -------------------------------------------------------------------------------- 1 | Atomic Text Records is the storage system used by Fruitbat. 2 | 3 | When used append-only, ATRReader (and SimpleATRReader) will ignore all fields and records that failed to be written in their entirety, making appending fields and records atomic in practice. 4 | 5 | The format only uses visible ASCII characters in the range of " " (32) to "~" (126). The record start marker is line feed, and the field end marker is tab, making the format readable as TSV. However note that records not terminated with "%" and fields not started with ":" should be ignored. 6 | 7 | Backslash, colon, tab, line feed, percent and carriage return are all escaped using two-character escape codes, while all other characters not in the range of " " to "~" are represented as six-digit hexadecimal representations of their unicode code points. 8 | 9 | An ATR file (without aborted fields or records) looks like this: 10 | 11 | :a :b :c % 12 | :x\ny :fruit :y :\u000b17\t % 13 | -------------------------------------------------------------------------------- /src/com/zarkonnen/fruitbat/atrio/SimpleATRReader.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.fruitbat.atrio; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | /** Shorter, more-commented, low-performing reference implementation. */ 28 | public class SimpleATRReader { 29 | private final BufferedReader in; 30 | private boolean waitingForRecordStart = true; 31 | public SimpleATRReader(InputStream in) { 32 | this.in = new BufferedReader(new InputStreamReader(in)); 33 | } 34 | public void close() throws IOException { in.close(); } 35 | 36 | public List readRecord() throws IOException { 37 | // Since records start with a newline, we want to throw away the first one. 38 | if (waitingForRecordStart) { 39 | waitingForRecordStart = false; 40 | if (in.readLine() == null) { return null; } 41 | } 42 | while (true) { 43 | String line = in.readLine(); 44 | // If there are no more lines, we've reached the end of the stream. 45 | if (line == null) { return null; } 46 | // There may be multiple aborted records on this line, so let's find the first end 47 | // marker and ignore everything else. 48 | int recordEndMarker = line.indexOf("%"); 49 | // If there is no end marker, the line's defective, so try again. 50 | if (recordEndMarker == -1) { continue; } 51 | line = line.substring(0, recordEndMarker); 52 | // Now we can split along field start markers. 53 | String[] rawFields = line.split("[:]", -1); 54 | // Of course the record may just be empty. 55 | if (rawFields.length == 0) { return Collections.emptyList(); } 56 | // Note that the first field of the split must be empty, otherwise the second one didn't 57 | // actually start itself. 58 | int firstFieldIndex = rawFields[0].length() == 0 ? 1 : 2; 59 | // Now we go through the fields processing the escape codes, and ignoring those that do 60 | // not end with a field end marker. 61 | ArrayList fields = new ArrayList(rawFields.length); 62 | for (int i = firstFieldIndex; i < rawFields.length; i++) { 63 | String field = rawFields[i]; 64 | if (!field.endsWith("\t")) { continue; } 65 | // First, let's deal with the hard bits: Unicode replacements. 66 | int backslashUIndex = -1; 67 | try { 68 | while ((backslashUIndex = field.indexOf("\\u", backslashUIndex + 1)) != -1) { 69 | // First, make sure there's enough space for the hexadecimal codepoint. 70 | if (backslashUIndex > field.length() - 7) { 71 | // No? Field's defective, 72 | continue; 73 | } 74 | // Use the next 6 letters after the backslash-u as a hexadecimal number. 75 | String hexNum = field.substring(backslashUIndex + 2, backslashUIndex + 8); 76 | String chars = new String(Character.toChars(Integer.parseInt(hexNum, 16))); 77 | // Now slot those chars in, replacing the original sequence. We can be sure 78 | // that the second substring won't fail us because the final character is a 79 | // field end marker (a tab). 80 | field = field.substring(0, backslashUIndex) + chars + 81 | field.substring(backslashUIndex + 8); 82 | } 83 | } catch (NumberFormatException e) { 84 | // Broken field! 85 | continue; 86 | } 87 | // With that over, we now replace the other escape sequences. 88 | field = field.replace("\\r", "\r"); 89 | field = field.replace("\\n", "\n"); 90 | field = field.replace("\\p", "%"); 91 | field = field.replace("\\c", ":"); 92 | field = field.replace("\\t", "\t"); 93 | field = field.replace("\\\\", "\\"); 94 | // Finally, get rid of the field end marker and put the field in. 95 | fields.add(field.substring(0, field.length() - 1)); 96 | } 97 | 98 | return fields; 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/Longan.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import com.zarkonnen.longan.better.AggressiveLetterSplittingPostProcessor; 21 | import com.zarkonnen.longan.better.BetterChunker2; 22 | import com.zarkonnen.longan.better.BetterLetterFinder; 23 | import com.zarkonnen.longan.better.IntensityHistogramPreProcessor; 24 | import com.zarkonnen.longan.better.LetterSplittingPostProcessor; 25 | import com.zarkonnen.longan.better.RotationFixingPreProcessor; 26 | import com.zarkonnen.longan.better.SpeckleEliminator; 27 | import com.zarkonnen.longan.data.Result; 28 | import com.zarkonnen.longan.nnidentifier.Identifier; 29 | import com.zarkonnen.longan.simple.SimpleWordPlaintextConverter; 30 | import com.zarkonnen.longan.stage.*; 31 | import java.awt.image.BufferedImage; 32 | import java.util.ArrayList; 33 | import java.util.HashMap; 34 | import java.util.List; 35 | 36 | public class Longan { 37 | public static final String VERSION = "0.9"; 38 | 39 | public final boolean enableOpenCL; 40 | public final List preProcessors; 41 | public final LetterFinder letterFinder; 42 | public final LetterIdentifier letterIdentifier; 43 | public final Chunker chunker; 44 | public final List postProcessors; 45 | 46 | public static Longan getDefaultImplementation() { 47 | return getDefaultImplementation(true); 48 | } 49 | 50 | public static Longan getDefaultImplementation(boolean enableOpenCL) { 51 | ArrayList preps = new ArrayList(); 52 | preps.add(new IntensityHistogramPreProcessor()); 53 | preps.add(new RotationFixingPreProcessor()); 54 | ArrayList pps = new ArrayList(); 55 | pps.add(new LetterSplittingPostProcessor()); 56 | //pps.add(new AggressiveLetterSplittingPostProcessor()); 57 | pps.add(new SpeckleEliminator()); 58 | return new Longan( 59 | preps, 60 | new BetterLetterFinder(), 61 | new BetterChunker2(), 62 | new Identifier(), 63 | pps, 64 | enableOpenCL 65 | ); 66 | } 67 | 68 | public static String recognize(BufferedImage img) { 69 | return new SimpleWordPlaintextConverter().convert(getDefaultImplementation().process(img)); 70 | } 71 | 72 | public Longan( 73 | List preProcessors, 74 | LetterFinder letterFinder, 75 | Chunker chunker, 76 | LetterIdentifier letterIdentifier, 77 | List postProcessors, 78 | boolean enableOpenCL) 79 | { 80 | this.preProcessors = preProcessors; 81 | this.letterFinder = letterFinder; 82 | this.letterIdentifier = letterIdentifier; 83 | this.chunker = chunker; 84 | this.postProcessors = postProcessors; 85 | this.enableOpenCL = enableOpenCL; 86 | } 87 | 88 | public Result process(BufferedImage img) { 89 | HashMap metadata = new HashMap(); 90 | metadata.put("enableOpenCL", "" + enableOpenCL); 91 | for (PreProcessor pp : preProcessors) { 92 | img = pp.process(img, metadata); 93 | } 94 | ArrayList Letters = letterFinder.find(img, metadata); 95 | Result result = chunker.chunk(Letters, img, metadata); 96 | letterIdentifier.identify(result); 97 | 98 | for (PostProcessor pp : postProcessors) { 99 | pp.process(result, this); 100 | } 101 | letterIdentifier.finish(); 102 | return result; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/Metadata.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan; 2 | 3 | import java.util.HashMap; 4 | 5 | public class Metadata { 6 | private final HashMap data = new HashMap(); 7 | 8 | public boolean has(Key key) { 9 | return data.containsKey(key); 10 | } 11 | 12 | public T get(Key key, T fallback) { 13 | return (T) (data.containsKey(key) ? data.get(key) : fallback); 14 | } 15 | 16 | public T get(Key key) { 17 | if (!data.containsKey(key)) { throw new RuntimeException("Metadata not found for " + key.name + "."); } 18 | return (T) data.get(key); 19 | } 20 | 21 | public void put(Key key, T value) { 22 | data.put(key, value); 23 | } 24 | 25 | public static Key key(String name, Class cl) { 26 | return new Key(name); 27 | } 28 | 29 | public static class Key { 30 | public final String name; 31 | 32 | public Key(String name) { 33 | this.name = name; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o2) { 38 | return o2 instanceof Key && ((Key) o2).name.equals(name); 39 | } 40 | 41 | @Override 42 | public int hashCode() { return name.hashCode(); } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/Util.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import java.awt.Color; 21 | import java.awt.Graphics; 22 | import java.awt.image.BufferedImage; 23 | 24 | public class Util { 25 | static BufferedImage cropMaskAndAdjust(BufferedImage src, Letter r, int intensityAdjustment) { 26 | BufferedImage maskedSrc = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_RGB); 27 | Graphics g = maskedSrc.getGraphics(); 28 | g.drawImage( 29 | src, 30 | 0, 0, 31 | r.width, r.height, 32 | r.x, r.y, 33 | r.x + r.width, r.y + r.height, 34 | null); 35 | int white = Color.WHITE.getRGB(); 36 | for (int y = 0; y < r.height; y++) { 37 | for (int x = 0; x < r.width; x++) { 38 | boolean hasMask = false; 39 | for (int dy = -1; dy < 2; dy++) { for (int dx = -1; dx < 2; dx++) { 40 | int ny = y + dy; 41 | int nx = x + dx; 42 | if (ny >= 0 && ny < r.height && nx >= 0 && nx < r.width) { 43 | hasMask |= r.mask[ny][nx]; 44 | } 45 | }} 46 | if (!hasMask) { 47 | maskedSrc.setRGB(x, y, white); 48 | } else { 49 | Color rgb = new Color(maskedSrc.getRGB(x, y)); 50 | rgb = new Color( 51 | Math.min(255, Math.max(0, rgb.getRed() + intensityAdjustment)), 52 | Math.min(255, Math.max(0, rgb.getGreen() + intensityAdjustment)), 53 | Math.min(255, Math.max(0, rgb.getBlue() + intensityAdjustment)) 54 | ); 55 | maskedSrc.setRGB(x, y, rgb.getRGB()); 56 | } 57 | } 58 | } 59 | return maskedSrc; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/Visualizer.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Column; 20 | import com.zarkonnen.longan.data.Letter; 21 | import com.zarkonnen.longan.data.Line; 22 | import com.zarkonnen.longan.data.Result; 23 | import com.zarkonnen.longan.data.Word; 24 | import com.zarkonnen.longan.stage.ResultConverter; 25 | import java.awt.BasicStroke; 26 | import java.awt.Color; 27 | import java.awt.Font; 28 | import java.awt.Graphics2D; 29 | import java.awt.Rectangle; 30 | import java.awt.image.BufferedImage; 31 | import java.io.IOException; 32 | import java.io.OutputStream; 33 | import javax.imageio.ImageIO; 34 | 35 | public class Visualizer implements ResultConverter { 36 | public BufferedImage convert(Result result) { 37 | Graphics2D g = result.img.createGraphics(); 38 | 39 | float thickness = (result.img.getWidth() < result.img.getHeight() ? result.img.getWidth() : result.img.getHeight()) / 500f; 40 | if (thickness < 1.0f) { thickness = 1.0f; } 41 | g.setStroke(new BasicStroke(thickness)); 42 | 43 | // Columns 44 | g.setColor(new Color(0, 0, 0, 63)); 45 | for (Column c : result.columns) { 46 | c.regenBoundingRect(); 47 | g.drawRect(c.boundingRect.x, c.boundingRect.y, c.boundingRect.width, c.boundingRect.height); 48 | } 49 | 50 | // Letter positions 51 | g.setColor(new Color(255, 0, 0, 191)); 52 | for (Column c : result.columns) { 53 | for (Line l : c.lines) { 54 | for (Word w : l.words) { 55 | for (Letter letter : w.letters) { 56 | g.drawRect(letter.x, letter.y, 57 | letter.width, letter.height); 58 | } 59 | } 60 | } 61 | } 62 | 63 | // Lines 64 | g.setColor(new Color(0, 127, 0, 191)); 65 | for (Column c : result.columns) { 66 | for (Line line : c.lines) { 67 | Letter prevLetter = null; 68 | for (Word word : line.words) { 69 | for (Letter letter : word.letters) { 70 | if (prevLetter != null) { 71 | g.drawLine( 72 | prevLetter.x + prevLetter.width / 2, 73 | prevLetter.y + prevLetter.height / 2, 74 | letter.x + letter.width / 2, 75 | letter.y + letter.height / 2 76 | ); 77 | } 78 | prevLetter = letter; 79 | } 80 | } 81 | } 82 | } 83 | 84 | // Words 85 | g.setColor(new Color(0, 0, 255, 191)); 86 | for (Column c : result.columns) { 87 | for (Line line : c.lines) { 88 | for (Word word : line.words) { 89 | Rectangle wr = word.boundingRect; 90 | if (wr == null) { continue; } 91 | g.drawRect(wr.x - (int) thickness * 2, wr.y - (int) thickness * 2, wr.width + (int) thickness * 4, wr.height + (int) thickness * 4); 92 | } 93 | } 94 | } 95 | 96 | // Letters 97 | g.setColor(new Color(100, 0, 200, 191)); 98 | g.setFont(new Font("Verdana", Font.PLAIN, 16)); 99 | for (Column c : result.columns) { 100 | for (Line l : c.lines) { 101 | for (Word w : l.words) { 102 | for (Letter letter : w.letters) { 103 | g.drawString(letter.bestLetter(), letter.x + letter.width / 2, letter.y + letter.height / 2); 104 | } 105 | } 106 | } 107 | } 108 | 109 | g.dispose(); 110 | return result.img; 111 | } 112 | 113 | public void write(BufferedImage output, OutputStream stream) throws IOException { 114 | ImageIO.write(output, "png", stream); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/AggressiveLetterSplittingPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import com.zarkonnen.longan.Longan; 21 | import com.zarkonnen.longan.data.Column; 22 | import com.zarkonnen.longan.data.Line; 23 | import com.zarkonnen.longan.data.Result; 24 | import com.zarkonnen.longan.data.Word; 25 | import com.zarkonnen.longan.nnidentifier.Identifier; 26 | import com.zarkonnen.longan.stage.PostProcessor; 27 | import java.awt.Color; 28 | import java.awt.image.BufferedImage; 29 | import java.util.ArrayList; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | /** 34 | * Post-processor that takes low-scoring letters and checks if they're meant to be multiple letters, 35 | * by actually sawing them in half. 36 | */ 37 | public class AggressiveLetterSplittingPostProcessor implements PostProcessor { 38 | static final double LOW_SCORE_BOUNDARY = 0.75; 39 | static final double RECURSE_COST = 0.03; 40 | static final double SAW_WIDTH_TOLERANCE = 1.2; 41 | static final int MAX_RECURSION = 3; 42 | static final double MIN_IMPLAUSIBILITY_SCORE_DELTA = 0.02; 43 | static final double MIN_IMPROVEMENT = 0.14; 44 | 45 | static final double MAX_SZ_DEV = 1.6; 46 | 47 | int q = 0; 48 | 49 | ArrayList split(Letter srcLetter, Letter topLetter, Word word, Line line, Column c, Result result, Longan longan, int recursion) { 50 | if (recursion > MAX_RECURSION) { return null; } 51 | ArrayList output = new ArrayList(); 52 | ArrayList lrs = sawApart(srcLetter, result.img); 53 | if (lrs == null) { return null; } 54 | for (Letter letter : lrs) { 55 | if (letter.width == 0 || letter.height == 0) { 56 | continue; 57 | } 58 | 59 | longan.letterIdentifier.reIdentify(letter, topLetter, word, line, c, result); 60 | ArrayList sub = split(letter, topLetter, word, line, c, result, longan, recursion + 1); 61 | if (sub == null) { 62 | output.add(letter); 63 | } else { 64 | output.addAll(sub); 65 | } 66 | } 67 | 68 | return output.isEmpty() || worstScoreIn(output, c) - RECURSE_COST < bestPlausibleScore(srcLetter, c) ? null : output; 69 | } 70 | 71 | double bestPlausibleScore(Letter l, Column c) { 72 | double score = 0.0; 73 | HashMap expectedSizes = 74 | c.metadata.has(Identifier.IDENTIFIER_USED) 75 | ? c.metadata.get(Identifier.IDENTIFIER_USED).expectedRelativeSizes 76 | : new HashMap(); 77 | for (Map.Entry, Double> e : l.possibleLetters.entrySet()) { 78 | boolean plausible = false; 79 | for (String pLetter : e.getKey()) { 80 | if (expectedSizes.containsKey(pLetter) && 81 | l.relativeSize < expectedSizes.get(pLetter) * MAX_SZ_DEV) 82 | { 83 | plausible = true; 84 | break; 85 | } 86 | } 87 | if (plausible) { score = Math.max(score, e.getValue()); } 88 | } 89 | return score; 90 | } 91 | 92 | double worstScoreIn(ArrayList list, Column c) { 93 | double ws = 1; 94 | for (Letter l : list) { ws = Math.min(ws, bestPlausibleScore(l, c)); } 95 | return ws; 96 | } 97 | 98 | double avgPlausibleScoreIn(ArrayList list, Column c) { 99 | double acc = 0; 100 | for (Letter l : list) { acc += bestPlausibleScore(l, c); } 101 | return acc / list.size(); 102 | } 103 | 104 | public void process(Result result, Longan longan) { 105 | for (Column c : result.columns) { 106 | for (Line line : c.lines) { 107 | for (Word word : line.words) { 108 | lp: for (int i = 0; i < word.letters.size(); i++) { 109 | Letter l = word.letters.get(i); 110 | double score = l.bestScore(); 111 | double plausibleScore = bestPlausibleScore(l, c); 112 | boolean implausibleLetter = plausibleScore + MIN_IMPLAUSIBILITY_SCORE_DELTA < score; 113 | if (plausibleScore < LOW_SCORE_BOUNDARY) { 114 | ArrayList newLs = split(l, l, word, line, c, result, longan, 0); 115 | if (newLs != null && (implausibleLetter || avgPlausibleScoreIn(newLs, c) > plausibleScore + MIN_IMPROVEMENT)) { 116 | word.letters.remove(i); 117 | word.letters.addAll(i, newLs); 118 | i += newLs.size() - 1; 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | static ArrayList sawApart(Letter l, BufferedImage img) { 128 | int bestSawPosition = -1; 129 | int lowestResistance = Integer.MAX_VALUE; 130 | for (int saw = l.width / 4; saw < l.width * 3 / 4 - 1; saw++) { 131 | int resistance = 0; 132 | for (int blade = 0; blade < l.height; blade++) { 133 | if (l.mask[blade][saw]) { 134 | Color c = new Color(img.getRGB(l.x + saw, l.y + blade)); 135 | resistance += (255 * 3 - c.getRed() - c.getGreen() - c.getBlue()); 136 | } 137 | } 138 | if (resistance < lowestResistance) { 139 | lowestResistance = resistance; 140 | bestSawPosition = saw; 141 | } 142 | } 143 | if (bestSawPosition != -1) { 144 | // Widen the saw. 145 | int sawLeftWiden = 0; 146 | for (int saw = bestSawPosition - 1; saw > 0; saw--) { 147 | int resistance = 0; 148 | for (int blade = 0; blade < l.height; blade++) { 149 | if (l.mask[blade][saw]) { 150 | Color c = new Color(img.getRGB(l.x + saw, l.y + blade)); 151 | resistance += (255 * 3 - c.getRed() - c.getGreen() - c.getBlue()); 152 | } 153 | } 154 | if (resistance <= lowestResistance * SAW_WIDTH_TOLERANCE) { 155 | sawLeftWiden++; 156 | } else { 157 | break; 158 | } 159 | } 160 | int sawRightWiden = 0; 161 | for (int saw = bestSawPosition; saw < l.width - 2; saw++) { 162 | int resistance = 0; 163 | for (int blade = 0; blade < l.height; blade++) { 164 | if (l.mask[blade][saw]) { 165 | Color c = new Color(img.getRGB(l.x + saw, l.y + blade)); 166 | resistance += (255 * 3 - c.getRed() - c.getGreen() - c.getBlue()); 167 | } 168 | } 169 | if (resistance <= lowestResistance * SAW_WIDTH_TOLERANCE) { 170 | sawRightWiden++; 171 | } else { 172 | break; 173 | } 174 | } 175 | bestSawPosition -= sawLeftWiden; 176 | return l.splitAlongXAxis(bestSawPosition, sawLeftWiden + sawRightWiden); 177 | } else { 178 | return null; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/BetterLetterFinder.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import com.zarkonnen.longan.stage.LetterFinder; 21 | import java.awt.Color; 22 | import java.awt.Point; 23 | import java.awt.image.BufferedImage; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.LinkedList; 27 | 28 | public class BetterLetterFinder implements LetterFinder { 29 | public ArrayList find(BufferedImage img, HashMap metadata) { 30 | if (!metadata.containsKey("blackWhiteBoundary")) { 31 | new IntensityHistogramPreProcessor().process(img, metadata); 32 | } 33 | int blackWhiteBoundary = Integer.parseInt(metadata.get("blackWhiteBoundary")); 34 | 35 | int[][] scan = new int[img.getHeight()][img.getWidth()]; 36 | for (int y = 0; y < img.getHeight(); y++) { 37 | for (int x = 0; x < img.getWidth(); x++) { 38 | Color c = new Color(img.getRGB(x, y)); 39 | int intensity = (c.getRed() + c.getGreen() + c.getBlue()) / 3; 40 | scan[y][x] = intensity > blackWhiteBoundary ? 0 : 1; 41 | } 42 | } 43 | 44 | ArrayList rs = new ArrayList(); 45 | int floodID = 2; 46 | for (int searchY = 0; searchY < img.getHeight(); searchY++) { 47 | for (int searchX = 0; searchX < img.getWidth(); searchX++) { 48 | if (scan[searchY][searchX] == 1) { 49 | Letter r = new Letter(searchX, searchY, 1, 1); 50 | LinkedList floodQueue = new LinkedList(); 51 | Point p = new Point(searchX, searchY); 52 | floodQueue.add(p); 53 | floodFill(scan, floodQueue, r, floodID++); 54 | rs.add(r); 55 | } 56 | } 57 | } 58 | 59 | return rs; 60 | } 61 | 62 | private void floodFill(int[][] scan, LinkedList floodQueue, Letter r, int floodID) { 63 | while (floodQueue.size() > 0) { 64 | Point p = floodQueue.poll(); 65 | int y = p.y; 66 | int x = p.x; 67 | scan[y][x] = floodID; 68 | r.add(x, y); 69 | for (int dy = -1; dy < 2; dy++) { for (int dx = -1; dx < 2; dx++) { 70 | int y2 = y + dy; 71 | int x2 = x + dx; 72 | if (y2 >= 0 && y2 < scan.length && x2 >= 0 && x2 < scan[0].length && scan[y2][x2] == 1) { 73 | scan[y2][x2] = -1; // -1 means "reserved" 74 | Point p2 = new Point(x2, y2); 75 | floodQueue.add(p2); 76 | } 77 | }} 78 | } 79 | 80 | // Fill in mask. 81 | if (r.x > 0) { 82 | r.x--; 83 | r.width++; 84 | } 85 | if (r.y > 0) { 86 | r.y--; 87 | r.height++; 88 | } 89 | if (r.x + r.width < scan[0].length) { 90 | r.width++; 91 | } 92 | if (r.y + r.height < scan.length) { 93 | r.height++; 94 | } 95 | r.mask = new boolean[r.height][r.width]; 96 | for (int my = 0; my < r.height; my++) { 97 | for (int mx = 0; mx < r.width; mx++) { 98 | r.mask[my][mx] = scan[r.y + my][r.x + mx] == floodID; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/IntensityHistogramPreProcessor.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | import com.zarkonnen.longan.Histogram; 4 | import com.zarkonnen.longan.stage.PreProcessor; 5 | import java.awt.Color; 6 | import java.awt.Graphics2D; 7 | import java.awt.image.BufferedImage; 8 | import java.util.HashMap; 9 | 10 | /* 11 | * Copyright 2011 David Stark 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); 14 | * you may not use this file except in compliance with the License. 15 | * You may obtain a copy of the License at 16 | * 17 | * http://www.apache.org/licenses/LICENSE-2.0 18 | * 19 | * Unless required by applicable law or agreed to in writing, software 20 | * distributed under the License is distributed on an "AS IS" BASIS, 21 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | * See the License for the specific language governing permissions and 23 | * limitations under the License. 24 | */ 25 | 26 | /** 27 | * Generates an intensity histogram of the input image to determine where the crossover point 28 | * between black and white is, and what shade of light gray is the default "white" of the image. 29 | */ 30 | public class IntensityHistogramPreProcessor implements PreProcessor { 31 | public static Histogram generate(BufferedImage img) { 32 | Histogram hg = new Histogram(256); 33 | 34 | BufferedImage img2 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); 35 | Graphics2D g = img2.createGraphics(); 36 | g.drawImage(img, 0, 0, 100, 100, null); 37 | 38 | final int h = img2.getHeight(); 39 | final int w = img2.getWidth(); 40 | for (int y = 0; y < h; y++) { 41 | for (int x = 0; x < w; x++) { 42 | Color c = new Color(img2.getRGB(x, y)); 43 | int intensity = (c.getRed() + c.getGreen() + c.getBlue()) / 3; 44 | hg.add(intensity); 45 | } 46 | } 47 | 48 | hg.convolve(new double[] { 1.0/49, 2.0/49, 3.0/49, 4.0/49, 5.0/49, 6.0/49, 7.0/49, 6.0/49, 5.0/49, 4.0/49, 3.0/49, 2.0/49, 1.0/49 }); 49 | hg.convolve(new double[] { 1.0/49, 2.0/49, 3.0/49, 4.0/49, 5.0/49, 6.0/49, 7.0/49, 6.0/49, 5.0/49, 4.0/49, 3.0/49, 2.0/49, 1.0/49 }); 50 | hg.convolve(new double[] { 3000.0 / img2.getWidth() / img2.getHeight() }); // Get rid of minor wobbles 51 | 52 | return hg; 53 | } 54 | 55 | public static int getBlackWhiteBoundary(Histogram hg) { 56 | return hg.blackWhiteBoundary(); 57 | } 58 | 59 | public static int getStandardWhite(Histogram hg) { 60 | return hg.postIndexMean(getBlackWhiteBoundary(hg)); 61 | } 62 | 63 | public BufferedImage process(BufferedImage img, HashMap metadata) { 64 | Histogram hg = generate(img); 65 | /*try { 66 | ImageIO.write(hg.toImage(), "png", new File("/Users/zar/Desktop/hg.png")); 67 | } catch (Exception e) { 68 | e.printStackTrace(); 69 | } 70 | //System.exit(0);*/ 71 | int bwb = getBlackWhiteBoundary(hg); 72 | if (!metadata.containsKey("blackWhiteBoundary")) { 73 | metadata.put("blackWhiteBoundary", "" + bwb); 74 | } 75 | if (!metadata.containsKey("standardWhite")) { 76 | metadata.put("standardWhite", "" + Math.min(255, hg.postIndexMean(bwb))); 77 | } 78 | return img; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/LetterSplittingPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import com.zarkonnen.longan.Longan; 21 | import com.zarkonnen.longan.data.Column; 22 | import com.zarkonnen.longan.data.Line; 23 | import com.zarkonnen.longan.data.Result; 24 | import com.zarkonnen.longan.data.Word; 25 | import com.zarkonnen.longan.stage.PostProcessor; 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | 29 | /** 30 | * Post-processor that takes low-scoring letters and checks if they're meant to be multiple letters. 31 | */ 32 | public class LetterSplittingPostProcessor implements PostProcessor { 33 | static final double LOW_SCORE_BOUNDARY = 0.8; 34 | static final double MIN_IMPROVEMENT = 0.1; 35 | static final double ALWAYS_ACCEPT_BOUNDARY = 0.95; 36 | 37 | public void process(Result result, Longan longan) { 38 | for (Column c : result.columns) { 39 | for (Line line : c.lines) { 40 | for (Word word : line.words) { 41 | lp: for (int i = 0; i < word.letters.size(); i++) { 42 | Letter l = word.letters.get(i); 43 | //System.out.print(l.bestLetter()); 44 | double bestScore = l.bestScore(); 45 | if (bestScore < LOW_SCORE_BOUNDARY && l.components.size() > 1) { 46 | //System.out.print("?"); 47 | Collections.sort(l.components, new LetterXComparator()); 48 | ArrayList ls = new ArrayList(); 49 | for (Letter lr : l.components) { 50 | // Ignore fragments, they're probably what messed this up. 51 | if (lr.fragment) { continue; } 52 | longan.letterIdentifier.reIdentify(lr, l, word, line, c, result); 53 | if (lr.bestScore() < Math.min(ALWAYS_ACCEPT_BOUNDARY, bestScore + MIN_IMPROVEMENT) || 54 | lr.bestLetter().equals(l.bestLetter())) 55 | { 56 | continue lp; 57 | } 58 | ls.add(lr); 59 | } 60 | word.letters.remove(i); 61 | word.letters.addAll(i, ls); 62 | word.regenBoundingRect(); 63 | i += ls.size() - 1; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/LetterXComparator.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | import com.zarkonnen.longan.data.Letter; 4 | import java.util.Comparator; 5 | 6 | class LetterXComparator implements Comparator { 7 | public int compare(Letter r0, Letter r1) { 8 | return r0.x - r1.x; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/RotationFixingPreProcessor.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.Histogram; 20 | import com.zarkonnen.longan.stage.PreProcessor; 21 | import java.awt.Color; 22 | import java.awt.Graphics2D; 23 | import java.awt.RenderingHints; 24 | import java.awt.image.BufferedImage; 25 | import java.util.HashMap; 26 | 27 | public class RotationFixingPreProcessor implements PreProcessor { 28 | static final int TILT_RANGE = 40; 29 | static final double TILT_DELTA = 0.1 / 180 * Math.PI; 30 | static final double MIN_ADJUST = 0.4 / 180 * Math.PI; 31 | static final int IMAGE_H = 400; 32 | static final int MAX_DIM = 280; 33 | 34 | public BufferedImage process(BufferedImage img, HashMap metadata) { 35 | if (!metadata.containsKey("standardWhite") || !metadata.containsKey("blackWhiteBoundary")) { 36 | new IntensityHistogramPreProcessor().process(img, metadata); 37 | } 38 | int standardWhite = Integer.parseInt(metadata.get("standardWhite")); 39 | int blackWhiteBoundary = Integer.parseInt(metadata.get("blackWhiteBoundary")); 40 | double rotation = determineRotation(img, 0.0, blackWhiteBoundary); 41 | if (Math.abs(rotation) > MIN_ADJUST) { 42 | BufferedImage img2 = new BufferedImage( 43 | (int) (img.getWidth() * (1.0 + Math.sin(Math.abs(rotation)))), 44 | (int) (img.getHeight() * (1.0 + Math.sin(Math.abs(rotation)))), 45 | img.getType() 46 | ); 47 | Graphics2D g2 = img2.createGraphics(); 48 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 49 | g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); 50 | g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 51 | g2.setColor(new Color(standardWhite, standardWhite, standardWhite)); 52 | g2.fillRect(0, 0, img2.getWidth(), img2.getHeight()); 53 | g2.translate(img2.getWidth() / 2, img2.getHeight() / 2); 54 | g2.rotate(rotation); 55 | g2.translate(-img.getWidth() / 2, -img.getHeight() / 2); 56 | g2.drawImage(img, 0, 0, null); 57 | img = img2; 58 | } 59 | return img; 60 | } 61 | 62 | double determineRotation(BufferedImage img, double initialRotation, int blackWhiteBoundary) { 63 | int w = 0; 64 | int h = 0; 65 | if (img.getWidth() > img.getHeight()) { 66 | w = MAX_DIM; 67 | h = MAX_DIM * img.getHeight() / img.getWidth(); 68 | } else { 69 | w = MAX_DIM * img.getWidth() / img.getHeight(); 70 | h = MAX_DIM; 71 | } 72 | BufferedImage scaled = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 73 | Graphics2D sg = scaled.createGraphics(); 74 | sg.drawImage(img, 0, 0, w, h, null); 75 | BufferedImage out1 = new BufferedImage(IMAGE_H, IMAGE_H, BufferedImage.TYPE_INT_RGB); 76 | double bestRotation = initialRotation; 77 | double bestStdDev = 0.0; 78 | for (int t = 0; t < TILT_RANGE * 2; t++) { 79 | double tilt = (t - TILT_RANGE) * TILT_DELTA; 80 | Graphics2D g1 = out1.createGraphics(); 81 | g1.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 82 | g1.setColor(Color.WHITE); 83 | g1.fillRect(0, 0, IMAGE_H, IMAGE_H); 84 | g1.translate(IMAGE_H / 2, IMAGE_H / 2); 85 | g1.rotate(tilt + initialRotation); 86 | g1.translate(-w / 2, -h / 2); 87 | g1.drawImage(scaled, 0, 0, null); 88 | 89 | Histogram ch = new Histogram(IMAGE_H / 2); 90 | for (int y = 0; y < IMAGE_H * 3 / 4; y++) { 91 | int contactsHorizontal = 0; 92 | for (int x = IMAGE_H / 4; x < IMAGE_H * 3 / 4; x++) { 93 | Color c = new Color(out1.getRGB(x, y)); 94 | int intensity = (c.getRed() + c.getGreen() + c.getBlue()) / 3; 95 | if (intensity < blackWhiteBoundary) { 96 | contactsHorizontal++; 97 | } 98 | } 99 | ch.add(contactsHorizontal); 100 | } 101 | 102 | double stdDev = ch.stdDev(); 103 | if (stdDev > bestStdDev) { 104 | bestRotation = initialRotation + tilt; 105 | bestStdDev = stdDev; 106 | } 107 | } 108 | 109 | return bestRotation; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/SpeckleEliminator.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | import com.zarkonnen.longan.Longan; 4 | import com.zarkonnen.longan.data.Column; 5 | import com.zarkonnen.longan.data.Letter; 6 | import com.zarkonnen.longan.data.Line; 7 | import com.zarkonnen.longan.data.Result; 8 | import com.zarkonnen.longan.data.Word; 9 | import com.zarkonnen.longan.stage.PostProcessor; 10 | import java.util.Iterator; 11 | 12 | public class SpeckleEliminator implements PostProcessor { 13 | 14 | public void process(Result result, Longan longan) { 15 | /*if (!result.metadata.containsKey("letterToWordSpacingBoundary")) { return; } 16 | int letterToWordSpacingBoundary = Integer.parseInt(result.metadata.get("letterToWordSpacingBoundary"));*/ 17 | for (Column c : result.columns) { 18 | for (Line line : c.lines) { 19 | for (Word word : line.words) { 20 | for (int i = 0; i < word.letters.size(); i++) { 21 | Letter l = word.letters.get(i); 22 | if (l.fragment && !l.bestLetter().matches("[.',-]")) { 23 | word.letters.remove(i); 24 | word.regenBoundingRect(); 25 | i--; 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | for (Iterator cit = result.columns.iterator(); cit.hasNext();) { 33 | Column c = cit.next(); 34 | boolean lRem = false; 35 | for (Iterator lit = c.lines.iterator(); lit.hasNext();) { 36 | Line l = lit.next(); 37 | boolean wRem = false; 38 | for (Iterator wit = l.words.iterator(); wit.hasNext();) { 39 | if (wit.next().letters.isEmpty()) { 40 | wRem = true; 41 | wit.remove(); 42 | } 43 | } 44 | if (l.words.isEmpty()) { 45 | lRem = true; 46 | lit.remove(); 47 | } else { 48 | if (wRem) { l.regenBoundingRect(); } 49 | } 50 | } 51 | if (c.lines.isEmpty()) { 52 | cit.remove(); 53 | } else { 54 | if (lRem) { c.regenBoundingRect(); } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/better/WordXComparator.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.better; 2 | 3 | import com.zarkonnen.longan.data.Word; 4 | import java.util.Comparator; 5 | 6 | class WordXComparator implements Comparator { 7 | public int compare(Word w0, Word w1) { 8 | return w0.boundingRect.x - w1.boundingRect.x; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/data/Column.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.data; 2 | 3 | import com.zarkonnen.longan.Metadata; 4 | import java.awt.Rectangle; 5 | import java.util.ArrayList; 6 | 7 | public class Column { 8 | public final Metadata metadata = new Metadata(); 9 | public ArrayList lines = new ArrayList(); 10 | public Rectangle boundingRect = null; 11 | 12 | public int averageXStart() { 13 | int x = 0; 14 | for (Line l : lines) { 15 | x += l.boundingRect.x; 16 | } 17 | return x / lines.size(); 18 | } 19 | 20 | public void add(Line l) { 21 | lines.add(l); 22 | if (boundingRect == null) { 23 | boundingRect = new Rectangle(l.boundingRect); 24 | } else { 25 | boundingRect.add(l.boundingRect); 26 | } 27 | } 28 | 29 | public void regenBoundingRect() { 30 | boundingRect = null; 31 | for (Line l : lines) { 32 | if (boundingRect == null) { 33 | boundingRect = new Rectangle(l.boundingRect); 34 | } else { 35 | boundingRect.add(l.boundingRect); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/data/Letter.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.data; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.awt.Rectangle; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | public class Letter extends Rectangle { 25 | public final HashMap, java.lang.Double> possibleLetters = 26 | new HashMap, java.lang.Double>(); 27 | public double relativeLineOffset = 0.0; 28 | public double relativeSize = 1.0; 29 | public boolean[][] mask; 30 | public boolean fragment; 31 | public ArrayList components = new ArrayList(); 32 | 33 | public Letter(int x, int y, int width, int height) { 34 | super(x, y, width, height); 35 | } 36 | 37 | public void setScores(HashMap, java.lang.Double> sc) { 38 | possibleLetters.clear(); 39 | possibleLetters.putAll(sc); 40 | } 41 | 42 | public Letter add(Letter lr2) { 43 | Rectangle newR = new Rectangle(this); 44 | newR.add(lr2); 45 | boolean[][] newMask = new boolean[newR.height][newR.width]; 46 | for (int my = 0; my < newR.height; my++) { 47 | for (int mx = 0; mx < newR.width; mx++) { 48 | int thisY = newR.y + my - y; 49 | int thisX = newR.x + mx - x; 50 | if (thisY >= 0 && thisY < height && thisX >= 0 && thisX < width) { 51 | newMask[my][mx] |= mask[thisY][thisX]; 52 | } 53 | int lr2Y = newR.y + my - lr2.y; 54 | int lr2X = newR.x + mx - lr2.x; 55 | if (lr2Y >= 0 && lr2Y < lr2.height && lr2X >= 0 && lr2X < lr2.width) { 56 | try { 57 | newMask[my][mx] |= lr2.mask[lr2Y][lr2X]; 58 | } catch (Exception e) { 59 | System.out.println("JAM"); 60 | } 61 | } 62 | } 63 | } 64 | 65 | Letter newLR = new Letter(newR.x, newR.y, newR.width, newR.height); 66 | newLR.mask = newMask; 67 | if (components.isEmpty()) { 68 | newLR.components.add(this); 69 | } else { 70 | newLR.components.addAll(components); 71 | } 72 | if (lr2.components.isEmpty()) { 73 | newLR.components.add(lr2); 74 | } else { 75 | newLR.components.addAll(components); 76 | } 77 | newLR.relativeSize = Math.sqrt(relativeSize * relativeSize + lr2.relativeSize * lr2.relativeSize); 78 | newLR.relativeLineOffset = (relativeLineOffset + lr2.relativeLineOffset) / 2; 79 | return newLR; 80 | } 81 | 82 | public ArrayList splitAlongXAxis(int xSplit, int splitW) { 83 | ArrayList lrs = new ArrayList(); 84 | // Left side 85 | boolean[][] newMask = new boolean[height][xSplit]; 86 | for (int my = 0; my < height; my++) { 87 | System.arraycopy(mask[my], 0, newMask[my], 0, xSplit); 88 | } 89 | Letter left = new Letter(x, y, xSplit, height); 90 | left.mask = newMask; 91 | left.relativeLineOffset = relativeLineOffset; 92 | left.relativeSize = Math.sqrt(relativeSize * xSplit / width); 93 | left.fragment = fragment; 94 | left.cropVertically(); 95 | lrs.add(left); 96 | // Right side 97 | newMask = new boolean[height][width - xSplit - splitW]; 98 | for (int my = 0; my < height; my++) { 99 | System.arraycopy(mask[my], xSplit + splitW, newMask[my], 0, width - xSplit - splitW); 100 | } 101 | Letter right = new Letter(x + xSplit + splitW, y, width - xSplit - splitW, height); 102 | right.mask = newMask; 103 | right.relativeLineOffset = relativeLineOffset; 104 | right.relativeSize = Math.sqrt(relativeSize * (width - xSplit - splitW) / width); 105 | right.fragment = fragment; 106 | right.cropVertically(); 107 | lrs.add(right); 108 | return lrs; 109 | } 110 | 111 | void cropVertically() { 112 | int oldWidth = width; 113 | int oldHeight = height; 114 | int topCrop = 0; 115 | crop: while (topCrop < height) { 116 | for (int mx = 0; mx < width; mx++) { 117 | if (mask[topCrop][mx]) { 118 | break crop; 119 | } 120 | } 121 | topCrop++; 122 | } 123 | int bottomCrop = 0; 124 | crop: while (bottomCrop < height - topCrop) { 125 | for (int mx = 0; mx < width; mx++) { 126 | if (mask[height - bottomCrop - 1][mx]) { 127 | break crop; 128 | } 129 | } 130 | bottomCrop++; 131 | } 132 | if (topCrop != 0 || bottomCrop != 0) { 133 | boolean[][] newMask = new boolean[height - topCrop - bottomCrop][width]; 134 | System.arraycopy(mask, topCrop, newMask, 0, height - topCrop - bottomCrop); 135 | mask = newMask; 136 | y += topCrop; 137 | height -= topCrop + bottomCrop; 138 | relativeLineOffset = 0; // qqDPS 139 | relativeSize = relativeSize * Math.sqrt(width * height) / Math.sqrt(oldWidth * oldHeight); 140 | } 141 | } 142 | 143 | public ArrayList bestLetterClass() { 144 | ArrayList bestL = null; 145 | double bestP = -100000000; 146 | for (Map.Entry, java.lang.Double> entry : possibleLetters.entrySet()) { 147 | if (entry.getValue() > bestP) { 148 | bestL = entry.getKey(); 149 | bestP = entry.getValue(); 150 | } 151 | } 152 | return bestL; 153 | } 154 | 155 | public String bestLetter() { 156 | return bestLetterClass().get(0); 157 | } 158 | 159 | public double bestScore() { 160 | double bestScore = 0.0; 161 | for (double score : possibleLetters.values()) { 162 | if (score > bestScore) { 163 | bestScore = score; 164 | } 165 | } 166 | return bestScore; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/data/Line.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.data; 2 | 3 | import java.awt.Rectangle; 4 | import java.util.ArrayList; 5 | 6 | public class Line { 7 | public ArrayList words = new ArrayList(); 8 | public Rectangle boundingRect = null; 9 | public double avgLetterWidth = 0.0; 10 | public double avgLetterHeight = 0.0; 11 | 12 | public void add(Word w) { 13 | words.add(w); 14 | if (boundingRect == null) { 15 | boundingRect = new Rectangle(w.boundingRect); 16 | } else { 17 | boundingRect.add(w.boundingRect); 18 | } 19 | avgLetterWidth = avgLetterWidth(); 20 | avgLetterHeight = avgLetterHeight(); 21 | } 22 | 23 | public void regenBoundingRect() { 24 | boundingRect = null; 25 | for (Word w : words) { 26 | if (boundingRect == null) { 27 | boundingRect = new Rectangle(w.boundingRect); 28 | } else { 29 | boundingRect.add(w.boundingRect); 30 | } 31 | } 32 | avgLetterWidth = avgLetterWidth(); 33 | avgLetterHeight = avgLetterHeight(); 34 | } 35 | 36 | public int xDist(Rectangle r2) { 37 | if (boundingRect.x + boundingRect.width < r2.x) { 38 | return r2.x - boundingRect.x - boundingRect.width; 39 | } 40 | if (r2.x + r2.width < boundingRect.x) { 41 | return boundingRect.x - r2.x - r2.width; 42 | } 43 | return 0; 44 | } 45 | 46 | public int yDist(Rectangle r2) { 47 | if (boundingRect.y + boundingRect.height < r2.y) { 48 | return r2.y - boundingRect.y - boundingRect.height; 49 | } 50 | if (r2.y + r2.height < boundingRect.y) { 51 | return boundingRect.y - r2.y - r2.height; 52 | } 53 | return 0; 54 | } 55 | 56 | private double avgLetterHeight() { 57 | double h = 0; 58 | int n = 0; 59 | for (Word w : words) { 60 | for (Letter l : w.letters) { 61 | h += l.height; 62 | } 63 | n += w.letters.size(); 64 | } 65 | return h / n; 66 | } 67 | 68 | private double avgLetterWidth() { 69 | double w = 0; 70 | int n = 0; 71 | for (Word word : words) { 72 | for (Letter l : word.letters) { 73 | w += l.width; 74 | } 75 | n += word.letters.size(); 76 | } 77 | return w / n; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/data/Picture.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.data; 2 | 3 | import java.awt.Rectangle; 4 | 5 | public class Picture { 6 | public Rectangle location; 7 | public boolean[][] mask; 8 | 9 | public Picture(Rectangle location, boolean[][] mask) { 10 | this.location = location; 11 | this.mask = mask; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/data/Result.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.data; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | 7 | public class Result { 8 | public HashMap metadata; 9 | public BufferedImage img; 10 | public ArrayList columns = new ArrayList(); 11 | public ArrayList pictures = new ArrayList(); 12 | } 13 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/data/Word.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.data; 2 | 3 | import java.awt.Rectangle; 4 | import java.util.ArrayList; 5 | 6 | public class Word { 7 | public ArrayList letters = new ArrayList(); 8 | public Rectangle boundingRect = null; 9 | 10 | public Word() {} 11 | 12 | public Word(Letter l) { 13 | add(l); 14 | } 15 | 16 | public void add(Letter l) { 17 | letters.add(l); 18 | if (boundingRect == null) { 19 | boundingRect = new Rectangle(l); 20 | } else { 21 | boundingRect.add(l); 22 | } 23 | } 24 | 25 | public void regenBoundingRect() { 26 | boundingRect = null; 27 | for (Letter l : letters) { 28 | if (boundingRect == null) { 29 | boundingRect = new Rectangle(l); 30 | } else { 31 | boundingRect.add(l); 32 | } 33 | } 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | StringBuilder sb = new StringBuilder(); 39 | for (Letter l : letters) { sb.append(l.bestLetter() == null ? "?" : l.bestLetter()); } 40 | return sb.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/hmm/Hmm.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.hmm; 2 | 3 | public class Hmm { 4 | public double initialProbability(String ngram) { return 0; } 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/Counter.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | 10 | public class Counter implements Comparator> { 11 | public final HashMap counts = new HashMap(); 12 | 13 | public void increment(T t) { 14 | if (counts.containsKey(t)) { 15 | counts.put(t, counts.get(t) + 1); 16 | } else { 17 | counts.put(t, 1); 18 | } 19 | } 20 | 21 | public ArrayList> sortedCountsHighestFirst() { 22 | ArrayList> cs = new ArrayList>(counts.entrySet()); 23 | Collections.sort(cs, this); 24 | return cs; 25 | } 26 | 27 | public int compare(Entry t, Entry t1) { 28 | return t1.getValue() - t.getValue(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/DiscriminatorNet.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import com.zarkonnen.longan.nnidentifier.network.Connection; 4 | import com.zarkonnen.longan.nnidentifier.network.Network; 5 | import com.zarkonnen.longan.nnidentifier.network.Weight; 6 | import com.zarkonnen.longan.nnidentifier.network.Layer; 7 | import com.zarkonnen.longan.nnidentifier.network.Node; 8 | import java.util.ArrayList; 9 | import java.util.Random; 10 | 11 | import static com.zarkonnen.longan.nnidentifier.network.Util.*; 12 | 13 | public class DiscriminatorNet { 14 | Network nw; 15 | Random r; 16 | 17 | public DiscriminatorNet(long seed) { 18 | r = new Random(seed); 19 | Layer input = new Layer("Input"); 20 | for (int y = 0; y < 28; y++) { for (int x = 0; x < 28; x++) { 21 | input.nodes.add(new Node("input " + y + "/" + x)); 22 | }} 23 | // Bias node! 24 | Node biasN = new Node("input bias"); 25 | biasN.activation = 1.0f; 26 | 27 | Layer h1 = new Layer("H1 (conv)"); 28 | for (int m = 0; m < 6; m++) { 29 | for (int y = 0; y < 24; y++) { for (int x = 0; x < 24; x++) { 30 | h1.nodes.add(new Node("H1." + m + " " + y + "/" + x)); 31 | }} 32 | } 33 | 34 | Layer h2 = new Layer("H2 (subsampling)"); 35 | for (int m = 0; m < 6; m++) { 36 | for (int y = 0; y < 12; y++) { for (int x = 0; x < 12; x++) { 37 | h2.nodes.add(new Node("H2." + m + " " + y + "/" + x)); 38 | }} 39 | } 40 | 41 | Layer h3 = new Layer("H3 (conv)"); 42 | for (int m = 0; m < 16; m++) { 43 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { 44 | h3.nodes.add(new Node("H3." + m + " " + y + "/" + x)); 45 | }} 46 | } 47 | 48 | Layer h4 = new Layer("H4 (subsampling)"); 49 | for (int m = 0; m < 16; m++) { 50 | for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { 51 | h4.nodes.add(new Node("H4." + m + " " + y + "/" + x)); 52 | }} 53 | } 54 | 55 | Layer output = new Layer("Output"); 56 | output.nodes.add(new Node("Output")); 57 | 58 | // Connect input to h1 59 | for (int m = 0; m < 6; m++) { 60 | for (int wY = 0; wY < 5; wY++) { for (int wX = 0; wX < 5; wX++) { 61 | Weight w = new Weight(rnd(-2.0f / 26, 2.0f / 26, r)); 62 | input.weights.add(w); 63 | for (int y = 0; y < 24; y++) { for (int x = 0; x < 24; x++) { 64 | new Connection( 65 | input.nodes.get( 66 | (y + wY) * 28 + 67 | (x + wX) 68 | ), 69 | h1.nodes.get( 70 | m * 24 * 24 + 71 | y * 24 + 72 | x 73 | ), 74 | w 75 | ); 76 | }} 77 | }} 78 | 79 | // Bias 80 | Weight w = new Weight(rnd(-2.0f / 26, 2.0f / 26, r)); 81 | input.weights.add(w); 82 | for (int y = 0; y < 24; y++) { for (int x = 0; x < 24; x++) { 83 | new Connection( 84 | biasN, 85 | h1.nodes.get( 86 | m * 24 * 24 + 87 | y * 24 + 88 | x 89 | ), 90 | w 91 | ); 92 | }} 93 | } 94 | 95 | // Connect h1 to h2 96 | for (int m = 0; m < 6; m++) { 97 | Weight w = new Weight(0.25f); 98 | h1.weights.add(w); 99 | for (int y = 0; y < 12; y++) { for (int x = 0; x < 12; x++) { 100 | for (int dy = 0; dy < 2; dy++) { for (int dx = 0; dx < 2; dx++) { 101 | new Connection( 102 | h1.nodes.get( 103 | m * 24 * 24 + 104 | (y * 2 + dy) * 24 + 105 | (x * 2 + dx) 106 | ), 107 | h2.nodes.get( 108 | m * 12 * 12 + 109 | y * 12 + 110 | x 111 | ), 112 | w 113 | ); 114 | }} 115 | }} 116 | } 117 | 118 | // Connect h2 to h3 119 | boolean X = true; 120 | boolean O = false; 121 | boolean[][] table = { 122 | {X, O, O, O, X, X, X, O, O, X, X, X, X, O, X, X}, 123 | {X, X, O, O, O, X, X, X, O, O, X, X, X, X, O, X}, 124 | {X, X, X, O, O, O, X, X, X, O, O, X, O, X, X, X}, 125 | {O, X, X, X, O, O, X, X, X, X, O, O, X, O, X, X}, 126 | {O, O, X, X, X, O, O, X, X, X, X, O, X, X, O, X}, 127 | {O, O, O, X, X, X, O, O, X, X, X, X, O, X, X, X} 128 | }; 129 | 130 | for (int m1 = 0; m1 < 6; m1++) { 131 | for (int m3 = 0; m3 < 16; m3++) { 132 | if (!table[m1][m3]) { continue; } 133 | for (int wY = 0; wY < 5; wY++) { for (int wX = 0; wX < 5; wX++) { 134 | Weight w = new Weight(rnd(-2.0f / 76, 2.0f / 76, r)); 135 | h2.weights.add(w); 136 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { 137 | new Connection( 138 | h2.nodes.get( 139 | m1 * 12 * 12 + 140 | (y + wY) * 12 + 141 | (x + wX) 142 | ), 143 | h3.nodes.get( 144 | m3 * 8 * 8 + 145 | y * 8 + 146 | x 147 | ), 148 | w 149 | ); 150 | }} 151 | }} 152 | } 153 | } 154 | 155 | // Add biases 156 | for (int m3 = 0; m3 < 16; m3++) { 157 | // Add bias 158 | Weight w = new Weight(rnd(-2.0f / 76, 2.0f / 76, r)); 159 | h2.weights.add(w); 160 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { 161 | new Connection( 162 | biasN, 163 | h3.nodes.get( 164 | m3 * 8 * 8 + 165 | y * 8 + 166 | x 167 | ), 168 | w 169 | ); 170 | }} 171 | } 172 | 173 | // Connect h3 to h4 174 | for (int m3 = 0; m3 < 16; m3++) { 175 | Weight w = new Weight(0.25f); 176 | h3.weights.add(w); 177 | for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { 178 | for (int dy = 0; dy < 2; dy++) { for (int dx = 0; dx < 2; dx++) { 179 | new Connection( 180 | h3.nodes.get( 181 | m3 * 8 * 8 + 182 | (y * 2 + dy) * 8 + 183 | (x * 2 + dx) 184 | ), 185 | h4.nodes.get( 186 | m3 * 4 * 4 + 187 | y * 4 + 188 | x 189 | ), 190 | w 191 | ); 192 | }} 193 | }} 194 | } 195 | 196 | // Connect h4 to output (full connection) 197 | for (Node h4N : h4.nodes) { 198 | for (Node oN : output.nodes) { 199 | Weight w = new Weight(rnd(-2.0f / 193, 2.0f / 193, r)); 200 | h4.weights.add(w); 201 | new Connection(h4N, oN, w); 202 | } 203 | } 204 | // Add biases to output. 205 | for (Node oN : output.nodes) { 206 | Weight w = new Weight(rnd(-2.0f / 193, 2.0f / 193, r)); 207 | h4.weights.add(w); 208 | new Connection(biasN, oN, w); 209 | } 210 | 211 | ArrayList layers = new ArrayList(); 212 | layers.add(input); 213 | layers.add(h1); 214 | layers.add(h2); 215 | layers.add(h3); 216 | layers.add(h4); 217 | layers.add(output); 218 | 219 | nw = new Network(layers); 220 | } 221 | 222 | public void train(Example ex, float n, float m) { 223 | nw.train(ex.input, ex.target, n, m); 224 | } 225 | 226 | public float[] run(float[] input) { 227 | return nw.run(input); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/Example.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import java.awt.image.BufferedImage; 4 | 5 | public class Example { 6 | public String letter; 7 | public float[] input; 8 | public float[] target; 9 | public BufferedImage original; 10 | public String path; 11 | 12 | public Example(String letter, float[] input, float[] target) { 13 | this.letter = letter; 14 | this.input = input; 15 | this.target = target; 16 | } 17 | 18 | float inputDistanceTo(Example e2) { 19 | float d = 0.0f; 20 | for (int i = 0; i < input.length; i++) { 21 | d += (input[i] - e2.input[i]) * (input[i] - e2.input[i]); 22 | } 23 | return (float) Math.sqrt(d); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/ExampleGenerator2.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import java.awt.Color; 4 | import java.awt.Font; 5 | import java.awt.Graphics2D; 6 | import java.awt.image.BufferedImage; 7 | import java.awt.image.BufferedImageOp; 8 | import java.awt.image.ConvolveOp; 9 | import java.awt.image.Kernel; 10 | import java.util.Random; 11 | 12 | public class ExampleGenerator2 { 13 | public static BufferedImage makeLetterImage(String l, Config.FontType font, Random r) { 14 | int intensity = 0; 15 | int size = 50 + r.nextInt(50); 16 | return getLetter(l, font.font, font.italic, 1, size, 17 | intensity, 0, 18 | intensity + 200 + r.nextInt(40), 19 | r.nextInt(30) + r.nextInt(20) + r.nextInt(20), 20 | new int[] {1, 1, 1, 1}, r); 21 | } 22 | 23 | public static BufferedImage makeCorrectlyVariableLetterImage(String l, Config.FontType font, Random r) { 24 | int intensity = r.nextInt(30) + r.nextInt(30) + r.nextInt(50); 25 | int size = 30 + r.nextInt(40); 26 | return getLetter(l, font.font, font.italic, r.nextInt(2), size, 27 | intensity, -Math.PI / 60 + r.nextDouble() * Math.PI / 30, 28 | Math.min(255, intensity + 160 + r.nextInt(50)), 29 | 30 + r.nextInt(30), 30 | new int[] {r.nextInt(2), r.nextInt(2), r.nextInt(2), r.nextInt(2)}, r); 31 | } 32 | 33 | public static BufferedImage makeSemiVariableLetterImage(String l, Config.FontType font, Random r) { 34 | int intensity = r.nextInt(30) + r.nextInt(30) + r.nextInt(80); 35 | int size = 30 + r.nextInt(40); 36 | return getLetter(l, font.font, font.italic, r.nextInt(5), size, 37 | intensity, -Math.PI / 20 + r.nextDouble() * Math.PI / 10, 38 | intensity + 160 + r.nextInt(50), 39 | r.nextInt(30) + r.nextInt(20) + r.nextInt(20), 40 | new int[] {r.nextInt(2), r.nextInt(2), r.nextInt(2), r.nextInt(2)}, r); 41 | } 42 | 43 | static BufferedImage getLetter(String l, String font, boolean italic, int blurIterations, int size, int color, double rot, int cropBoundary, 44 | int noise, int[] widen, Random r) { 45 | BufferedImage img = new BufferedImage(size * 4, size * 4, BufferedImage.TYPE_INT_RGB); 46 | Graphics2D g = img.createGraphics(); 47 | g.setColor(Color.WHITE); 48 | g.fillRect(0, 0, size * 4, size * 4); 49 | g.setFont(new Font(font, italic ? Font.ITALIC : Font.PLAIN, size)); 50 | g.setColor(new Color(color, color, color)); 51 | g.rotate(rot); 52 | g.drawString(l, size * 2, size * 2); 53 | for (int i = 0; i < blurIterations; i++) { 54 | img = noise(img, noise, r); 55 | img = blur(img); 56 | } 57 | img = crop(img, cropBoundary, widen); 58 | if (img == null) { 59 | System.out.println("fish"); 60 | } 61 | return img == null ? getLetter(l, font, italic, blurIterations == 0 ? 0 : 1, size * 2, 0, rot, cropBoundary, noise, widen, r) : img; 62 | } 63 | 64 | static BufferedImage getSimpleLetter(String l, String font, boolean italic) { 65 | BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); 66 | Graphics2D g = img.createGraphics(); 67 | g.setColor(Color.WHITE); 68 | g.fillRect(0, 0, 100, 100); 69 | g.setFont(new Font(font, italic ? Font.ITALIC : Font.PLAIN, 40)); 70 | g.setColor(Color.BLACK); 71 | g.drawString(l, 50, 50); 72 | return crop(img, 127, new int[] { 0, 1, 0, 1 }); 73 | } 74 | 75 | static BufferedImage weaksauculate(BufferedImage img, int color) { 76 | for (int y = 0; y < img.getHeight(); y++) { for (int x = 0; x < img.getWidth(); x++) { 77 | Color c = new Color(img.getRGB(x, y)); 78 | int val = Math.min(255, (c.getRed() + c.getGreen() + c.getBlue() + color * 8) / 3); 79 | img.setRGB(x, y, new Color(val, val, val).getRGB()); 80 | }} 81 | return img; 82 | } 83 | 84 | static BufferedImage noise(BufferedImage img, int amt, Random r) { 85 | for (int y = 0; y < img.getHeight(); y++) { for (int x = 0; x < img.getWidth(); x++) { 86 | Color c = new Color(img.getRGB(x, y)); 87 | int val = Math.min(255, (c.getRed() + c.getGreen() + c.getBlue() + r.nextInt(amt * 3 + 1)) / 3); 88 | img.setRGB(x, y, new Color(val, val, val).getRGB()); 89 | }} 90 | return img; 91 | } 92 | 93 | public static BufferedImage blur(BufferedImage img) { 94 | BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, new float[] { 95 | 1 / 9f, 1 / 9f, 1 / 9f, 96 | 1 / 9f, 1 / 9f, 1 / 9f, 97 | 1 / 9f, 1 / 9f, 1 / 9f 98 | })); 99 | img = op.filter(img, null); 100 | // Get rid of stupid border 101 | Graphics2D g = img.createGraphics(); 102 | g.setColor(Color.WHITE); 103 | g.fillRect(0, 0, img.getWidth(), 1); 104 | g.fillRect(0, img.getHeight() - 1, img.getWidth(), 1); 105 | g.fillRect(0, 0, 1, img.getHeight()); 106 | g.fillRect(img.getWidth() - 1, 0, 1, img.getHeight()); 107 | return img; 108 | } 109 | 110 | public static BufferedImage crop(BufferedImage img, int bound, int[] widen) { 111 | int top = 0; 112 | lp: while (top < img.getHeight()) { 113 | for (int x = 0; x < img.getWidth(); x++) { 114 | Color c = new Color(img.getRGB(x, top)); 115 | if (c.getRed() + c.getGreen() + c.getBlue() < bound * 3) { 116 | break lp; 117 | } 118 | } 119 | top++; 120 | } 121 | int bottom = img.getHeight() - 1; 122 | lp: while (bottom > top) { 123 | for (int x = 0; x < img.getWidth(); x++) { 124 | Color c = new Color(img.getRGB(x, bottom)); 125 | if (c.getRed() + c.getGreen() + c.getBlue() < bound * 3) { 126 | break lp; 127 | } 128 | } 129 | bottom--; 130 | } 131 | int left = 0; 132 | lp: while (left < img.getWidth()) { 133 | for (int y = 0; y < img.getHeight(); y++) { 134 | Color c = new Color(img.getRGB(left, y)); 135 | if (c.getRed() + c.getGreen() + c.getBlue() < bound * 3) { 136 | break lp; 137 | } 138 | } 139 | left++; 140 | } 141 | int right = img.getWidth() - 1; 142 | lp: while (right > left) { 143 | for (int y = 0; y < img.getHeight(); y++) { 144 | Color c = new Color(img.getRGB(right, y)); 145 | if (c.getRed() + c.getGreen() + c.getBlue() < bound * 3) { 146 | break lp; 147 | } 148 | } 149 | right--; 150 | } 151 | 152 | if (right - left <= 0 || bottom - top <= 0) { 153 | if (bound == 254) { return null; } 154 | return crop(img, 254, widen); 155 | } 156 | 157 | left = Math.max(0, left - widen[0]); 158 | right = Math.min(img.getWidth() - 1, right + widen[1]); 159 | top = Math.max(0, top - widen[2]); 160 | bottom = Math.min(img.getHeight() - 1, bottom + widen[3]); 161 | 162 | BufferedImage img2 = new BufferedImage(right - left, bottom - top, BufferedImage.TYPE_INT_RGB); 163 | Graphics2D g = img2.createGraphics(); 164 | g.drawImage(img, 0, 0, right - left, bottom - top, left, top, right, bottom, null); 165 | 166 | return img2; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/FastLoadingNetwork.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import com.zarkonnen.longan.nnidentifier.network.Layer; 4 | import com.zarkonnen.longan.nnidentifier.network.Network; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | import java.io.OutputStream; 10 | import java.util.ArrayList; 11 | 12 | public class FastLoadingNetwork { 13 | public ArrayList layers = new ArrayList(); 14 | public int inputSize; 15 | 16 | public FastLoadingNetwork initFromNetwork(Network n) { 17 | inputSize = n.layers.get(0).nodes.size(); 18 | for (int i = 1; i < n.layers.size(); i++) { 19 | layers.add(new FastLayer(n.layers.get(i), n.layers.get(i - 1))); 20 | } 21 | return this; 22 | } 23 | 24 | public FastLoadingNetwork loadShape(InputStream in) throws IOException { 25 | ObjectInputStream ois = new ObjectInputStream(in); 26 | inputSize = ois.readInt(); 27 | int numLayers = ois.readInt(); 28 | for (int layerN = 0; layerN < numLayers; layerN++) { 29 | FastLayer l = new FastLayer(); 30 | layers.add(l); 31 | l.numNodes = ois.readInt(); 32 | l.numWeights = ois.readInt(); 33 | l.totalNumConnections = ois.readInt(); 34 | l.hasBias = ois.readBoolean(); 35 | l.connectionOffsets = new int[l.numNodes]; 36 | l.numConnections = new int[l.numNodes]; 37 | l.connections = new int[l.totalNumConnections]; 38 | l.weightConnections = new int[l.totalNumConnections]; 39 | l.weights = new float[l.numWeights]; 40 | l.biases = new float[l.numNodes]; 41 | l.output = new float[l.numNodes]; 42 | for (int i = 0; i < l.connectionOffsets.length; i++) { 43 | l.connectionOffsets[i] = ois.readInt(); 44 | } 45 | for (int i = 0; i < l.numConnections.length; i++) { 46 | l.numConnections[i] = ois.readInt(); 47 | } 48 | for (int i = 0; i < l.connections.length; i++) { 49 | l.connections[i] = ois.readInt(); 50 | } 51 | for (int i = 0; i < l.weightConnections.length; i++) { 52 | l.weightConnections[i] = ois.readInt(); 53 | } 54 | } 55 | return this; 56 | } 57 | 58 | public void saveShape(OutputStream out) throws IOException { 59 | ObjectOutputStream oos = new ObjectOutputStream(out); 60 | oos.writeInt(inputSize); 61 | oos.writeInt(layers.size()); 62 | for (FastLayer l : layers) { 63 | oos.writeInt(l.numNodes); 64 | oos.writeInt(l.numWeights); 65 | oos.writeInt(l.totalNumConnections); 66 | oos.writeBoolean(l.hasBias); 67 | for (int i = 0; i < l.connectionOffsets.length; i++) { 68 | oos.writeInt(l.connectionOffsets[i]); 69 | } 70 | for (int i = 0; i < l.numConnections.length; i++) { 71 | oos.writeInt(l.numConnections[i]); 72 | } 73 | for (int i = 0; i < l.connections.length; i++) { 74 | oos.writeInt(l.connections[i]); 75 | } 76 | for (int i = 0; i < l.weightConnections.length; i++) { 77 | oos.writeInt(l.weightConnections[i]); 78 | } 79 | } 80 | oos.flush(); 81 | } 82 | 83 | public FastLoadingNetwork loadWeights(InputStream in) throws IOException { 84 | ObjectInputStream ois = new ObjectInputStream(in); 85 | for (FastLayer l : layers) { 86 | for (int i = 0; i < l.weights.length; i++) { 87 | l.weights[i] = ois.readFloat(); 88 | } 89 | for (int i = 0; i < l.biases.length; i++) { 90 | l.biases[i] = ois.readFloat(); 91 | } 92 | } 93 | return this; 94 | } 95 | 96 | public void saveWeights(OutputStream out) throws IOException { 97 | ObjectOutputStream oos = new ObjectOutputStream(out); 98 | for (FastLayer l : layers) { 99 | for (int i = 0; i < l.weights.length; i++) { 100 | oos.writeFloat(l.weights[i]); 101 | } 102 | for (int i = 0; i < l.biases.length; i++) { 103 | oos.writeFloat(l.biases[i]); 104 | } 105 | } 106 | oos.flush(); 107 | } 108 | 109 | public FastLoadingNetwork cloneWithSameShape() { 110 | FastLoadingNetwork n2 = new FastLoadingNetwork(); 111 | n2.inputSize = inputSize; 112 | for (FastLayer l : layers) { n2.layers.add(l.cloneWithSameShape()); } 113 | return n2; 114 | } 115 | 116 | public float[] run(float[] input) { 117 | for (FastLayer l : layers) { 118 | l.run(input); 119 | input = l.output; 120 | } 121 | return input; 122 | } 123 | 124 | public static class FastLayer { 125 | int numNodes; 126 | int numWeights; 127 | int totalNumConnections; 128 | boolean hasBias; 129 | int[] connectionOffsets; 130 | int[] numConnections; 131 | int[] connections; 132 | int[] weightConnections; 133 | float[] weights; 134 | float[] biases; 135 | float[] output; 136 | 137 | public FastLayer() {} 138 | 139 | public FastLayer(Layer layer, Layer prevLayer) { 140 | // Analyse the layer. 141 | numNodes = layer.nodes.size(); 142 | numWeights = prevLayer.weights.size(); 143 | hasBias = hasBias(layer); 144 | connectionOffsets = new int[numNodes]; 145 | numConnections = new int[numNodes]; 146 | totalNumConnections = 0; 147 | for (int node = 0; node < numNodes; node++) { 148 | connectionOffsets[node] = totalNumConnections; 149 | numConnections[node] = layer.nodes.get(node).incoming.size() - (hasBias ? 1 : 0); 150 | totalNumConnections += numConnections[node]; 151 | } 152 | connections = new int [totalNumConnections]; 153 | weightConnections = new int[totalNumConnections]; 154 | weights = new float[numWeights]; 155 | biases = new float[numNodes]; 156 | for (int w = 0; w < numWeights; w++) { 157 | weights[w] = prevLayer.weights.get(w).value; 158 | } 159 | for (int node = 0; node < numNodes; node++) { 160 | for (int input = 0; input < numConnections[node]; input++) { 161 | connections[connectionOffsets[node] + input] = prevLayer.nodes.indexOf( 162 | layer.nodes.get(node).incoming.get(input).input); 163 | weightConnections[connectionOffsets[node] + input] = prevLayer.weights.indexOf( 164 | layer.nodes.get(node).incoming.get(input).weight); 165 | } 166 | if (hasBias) { 167 | biases[node] = (float) layer.nodes.get(node).incoming.get(numConnections[node]).weight.value; 168 | } 169 | } 170 | 171 | output = new float[numNodes]; 172 | } 173 | 174 | public void run(float[] prevLayer) { 175 | for (int n = 0; n < numNodes; n++) { 176 | float x = biases[n]; 177 | for (int i = 0; i < numConnections[n]; i += 1) { 178 | x += weights[weightConnections[connectionOffsets[n] + i]] 179 | * prevLayer[connections[connectionOffsets[n] + i]]; 180 | } 181 | output[n] = (float) Math.tanh(x); 182 | } 183 | } 184 | 185 | public FastLayer cloneWithSameShape() { 186 | FastLayer l2 = new FastLayer(); 187 | l2.numNodes = numNodes; 188 | l2.numWeights = numWeights; 189 | l2.totalNumConnections = totalNumConnections; 190 | l2.hasBias = hasBias; 191 | l2.connectionOffsets = connectionOffsets; 192 | l2.numConnections = numConnections; 193 | l2.connections = connections; 194 | l2.weightConnections = weightConnections; 195 | l2.weights = new float[weights.length]; 196 | l2.biases = new float[biases.length]; 197 | l2.output = new float[output.length]; 198 | return l2; 199 | } 200 | } 201 | 202 | // NB: Horrible. 203 | public static boolean hasBias(Layer layer) { 204 | return layer.nodes.get(0).incoming.get(layer.nodes.get(0).incoming.size() - 1).input.name.toLowerCase().contains("bias"); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/IdentifierNet.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import com.zarkonnen.longan.nnidentifier.network.Connection; 4 | import com.zarkonnen.longan.nnidentifier.network.Network; 5 | import com.zarkonnen.longan.nnidentifier.network.Weight; 6 | import com.zarkonnen.longan.nnidentifier.network.Layer; 7 | import com.zarkonnen.longan.nnidentifier.network.Node; 8 | import java.util.ArrayList; 9 | import java.util.Random; 10 | 11 | import static com.zarkonnen.longan.nnidentifier.network.Util.*; 12 | 13 | public class IdentifierNet { 14 | public Network nw; 15 | Random r; 16 | 17 | public IdentifierNet(long seed) { 18 | r = new Random(seed); 19 | Layer input = new Layer("Input"); 20 | for (int y = 0; y < 28; y++) { for (int x = 0; x < 28; x++) { 21 | input.nodes.add(new Node("input " + y + "/" + x)); 22 | }} 23 | // Bias node! 24 | Node biasN = new Node("input bias"); 25 | biasN.activation = 1.0f; 26 | 27 | Layer h1 = new Layer("H1 (conv)"); 28 | for (int m = 0; m < 6; m++) { 29 | for (int y = 0; y < 24; y++) { for (int x = 0; x < 24; x++) { 30 | h1.nodes.add(new Node("H1." + m + " " + y + "/" + x)); 31 | }} 32 | } 33 | 34 | Layer h2 = new Layer("H2 (subsampling)"); 35 | for (int m = 0; m < 6; m++) { 36 | for (int y = 0; y < 12; y++) { for (int x = 0; x < 12; x++) { 37 | h2.nodes.add(new Node("H2." + m + " " + y + "/" + x)); 38 | }} 39 | } 40 | 41 | Layer h3 = new Layer("H3 (conv)"); 42 | for (int m = 0; m < 16; m++) { 43 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { 44 | h3.nodes.add(new Node("H3." + m + " " + y + "/" + x)); 45 | }} 46 | } 47 | 48 | Layer h4 = new Layer("H4 (subsampling)"); 49 | for (int m = 0; m < 16; m++) { 50 | for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { 51 | h4.nodes.add(new Node("H4." + m + " " + y + "/" + x)); 52 | }} 53 | } 54 | 55 | Layer output = new Layer("Output"); 56 | for (int i = 0; i < ProfileGen.OUTPUT_SIZE; i++) { 57 | output.nodes.add(new Node("Output " + i)); 58 | } 59 | 60 | // Connect input to h1 61 | for (int m = 0; m < 6; m++) { 62 | for (int wY = 0; wY < 5; wY++) { for (int wX = 0; wX < 5; wX++) { 63 | Weight w = new Weight(rnd(-2.0f / 26, 2.0f / 26, r)); 64 | input.weights.add(w); 65 | for (int y = 0; y < 24; y++) { for (int x = 0; x < 24; x++) { 66 | new Connection( 67 | input.nodes.get( 68 | (y + wY) * 28 + 69 | (x + wX) 70 | ), 71 | h1.nodes.get( 72 | m * 24 * 24 + 73 | y * 24 + 74 | x 75 | ), 76 | w 77 | ); 78 | }} 79 | }} 80 | 81 | // Bias 82 | Weight w = new Weight(rnd(-2.0f / 26, 2.0f / 26, r)); 83 | input.weights.add(w); 84 | for (int y = 0; y < 24; y++) { for (int x = 0; x < 24; x++) { 85 | new Connection( 86 | biasN, 87 | h1.nodes.get( 88 | m * 24 * 24 + 89 | y * 24 + 90 | x 91 | ), 92 | w 93 | ); 94 | }} 95 | } 96 | 97 | // Connect h1 to h2 98 | for (int m = 0; m < 6; m++) { 99 | Weight w = new Weight(0.25f); 100 | h1.weights.add(w); 101 | for (int y = 0; y < 12; y++) { for (int x = 0; x < 12; x++) { 102 | for (int dy = 0; dy < 2; dy++) { for (int dx = 0; dx < 2; dx++) { 103 | new Connection( 104 | h1.nodes.get( 105 | m * 24 * 24 + 106 | (y * 2 + dy) * 24 + 107 | (x * 2 + dx) 108 | ), 109 | h2.nodes.get( 110 | m * 12 * 12 + 111 | y * 12 + 112 | x 113 | ), 114 | w 115 | ); 116 | }} 117 | }} 118 | } 119 | 120 | // Connect h2 to h3 121 | boolean X = true; 122 | boolean O = false; 123 | boolean[][] table = { 124 | {X, O, O, O, X, X, X, O, O, X, X, X, X, O, X, X}, 125 | {X, X, O, O, O, X, X, X, O, O, X, X, X, X, O, X}, 126 | {X, X, X, O, O, O, X, X, X, O, O, X, O, X, X, X}, 127 | {O, X, X, X, O, O, X, X, X, X, O, O, X, O, X, X}, 128 | {O, O, X, X, X, O, O, X, X, X, X, O, X, X, O, X}, 129 | {O, O, O, X, X, X, O, O, X, X, X, X, O, X, X, X} 130 | }; 131 | 132 | for (int m1 = 0; m1 < 6; m1++) { 133 | for (int m3 = 0; m3 < 16; m3++) { 134 | if (!table[m1][m3]) { continue; } 135 | for (int wY = 0; wY < 5; wY++) { for (int wX = 0; wX < 5; wX++) { 136 | Weight w = new Weight(rnd(-2.0f / 76, 2.0f / 76, r)); 137 | h2.weights.add(w); 138 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { 139 | new Connection( 140 | h2.nodes.get( 141 | m1 * 12 * 12 + 142 | (y + wY) * 12 + 143 | (x + wX) 144 | ), 145 | h3.nodes.get( 146 | m3 * 8 * 8 + 147 | y * 8 + 148 | x 149 | ), 150 | w 151 | ); 152 | }} 153 | }} 154 | } 155 | } 156 | 157 | // Add biases 158 | for (int m3 = 0; m3 < 16; m3++) { 159 | // Add bias 160 | Weight w = new Weight(rnd(-2.0f / 76, 2.0f / 76, r)); 161 | h2.weights.add(w); 162 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { 163 | new Connection( 164 | biasN, 165 | h3.nodes.get( 166 | m3 * 8 * 8 + 167 | y * 8 + 168 | x 169 | ), 170 | w 171 | ); 172 | }} 173 | } 174 | 175 | // Connect h3 to h4 176 | for (int m3 = 0; m3 < 16; m3++) { 177 | Weight w = new Weight(0.25f); 178 | h3.weights.add(w); 179 | for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { 180 | for (int dy = 0; dy < 2; dy++) { for (int dx = 0; dx < 2; dx++) { 181 | new Connection( 182 | h3.nodes.get( 183 | m3 * 8 * 8 + 184 | (y * 2 + dy) * 8 + 185 | (x * 2 + dx) 186 | ), 187 | h4.nodes.get( 188 | m3 * 4 * 4 + 189 | y * 4 + 190 | x 191 | ), 192 | w 193 | ); 194 | }} 195 | }} 196 | } 197 | 198 | // Connect h4 to output (full connection) 199 | for (Node h4N : h4.nodes) { 200 | for (Node oN : output.nodes) { 201 | Weight w = new Weight(rnd(-2.0f / 193, 2.0f / 193, r)); 202 | h4.weights.add(w); 203 | new Connection(h4N, oN, w); 204 | } 205 | } 206 | // Add biases to output. 207 | for (Node oN : output.nodes) { 208 | Weight w = new Weight(rnd(-2.0f / 193, 2.0f / 193, r)); 209 | h4.weights.add(w); 210 | new Connection(biasN, oN, w); 211 | } 212 | 213 | ArrayList layers = new ArrayList(); 214 | layers.add(input); 215 | layers.add(h1); 216 | layers.add(h2); 217 | layers.add(h3); 218 | layers.add(h4); 219 | layers.add(output); 220 | 221 | nw = new Network(layers); 222 | } 223 | 224 | public void train(Example ex, float n, float m) { 225 | nw.train(ex.input, ex.target, n, m); 226 | } 227 | 228 | public float[] run(float[] input) { 229 | return nw.run(input); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/NNShapeGen.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | 8 | public class NNShapeGen { 9 | public static void main(String[] args) throws FileNotFoundException, IOException { 10 | FileOutputStream fos = new FileOutputStream(new File(args[0])); 11 | new FastLoadingNetwork().initFromNetwork(new IdentifierNet(0).nw).saveShape(fos); 12 | fos.close(); 13 | /*fos = new FileOutputStream(new File(args[1])); 14 | new FastLoadingNetwork().initFromNetwork(new DiscriminatorNet(0).nw).saveShape(fos); 15 | fos.close();*/ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/NearestNeighbour.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import com.zarkonnen.longan.nnidentifier.Config.LetterClass; 4 | import com.zarkonnen.longan.nnidentifier.Config.NearestNeighbourIdentifier; 5 | import com.zarkonnen.longan.nnidentifier.network.Util; 6 | import java.io.IOException; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class NearestNeighbour { 14 | public static class Comparisons { 15 | HashMap> cmps = new HashMap>(); 16 | 17 | public double leastError(ArrayList classMembers, float[] input) { 18 | double leastErr = -1; 19 | for (String l : classMembers) { 20 | for (float[] cmp : cmps.get(l)) { 21 | double error = 0.0; 22 | for (int y = 6; y < 22; y++) { for (int x = 6; x < 22; x++) { 23 | error += (input[y * 28 + x] - cmp[y * 28 + x]) * (input[y * 28 + x] - cmp[y * 28 + x]); 24 | }} 25 | leastErr = leastErr == -1 ? error : Math.min(error, leastErr); 26 | } 27 | } 28 | return leastErr; 29 | } 30 | } 31 | 32 | public static void write(Comparisons c, ObjectOutputStream oos) throws IOException { 33 | oos.writeInt(0); // Version 34 | oos.writeInt(c.cmps.size()); 35 | for (Map.Entry> e : c.cmps.entrySet()) { 36 | oos.writeUTF(e.getKey()); 37 | oos.writeInt(e.getValue().size()); 38 | for (float[] cmp : e.getValue()) { 39 | for (int i = 0; i < 28 * 28; i++) { oos.writeFloat(cmp[i]); } 40 | } 41 | } 42 | } 43 | 44 | public static Comparisons read(ObjectInputStream ois) throws IOException { 45 | if (ois.readInt() > 0) { 46 | throw new IOException("Comparisons format too modern: I only support up to version 0."); 47 | } 48 | Comparisons c = new Comparisons(); 49 | int n = ois.readInt(); 50 | for (int i = 0; i < n; i++) { 51 | ArrayList lCmps = new ArrayList(); 52 | c.cmps.put(ois.readUTF(), lCmps); 53 | int m = ois.readInt(); 54 | for (int j = 0; j < m; j++) { 55 | float[] cmp = new float[28 * 28]; 56 | lCmps.add(cmp); 57 | for (int k = 0; k < 28 * 28; k++) { 58 | cmp[k] = ois.readFloat(); 59 | } 60 | } 61 | } 62 | return c; 63 | } 64 | 65 | public static Comparisons createComparisons(NearestNeighbourIdentifier nni) { 66 | Comparisons c = new Comparisons(); 67 | for (LetterClass lc : nni.classes) { 68 | for (String letter : lc.members) { 69 | ArrayList l = new ArrayList(); 70 | c.cmps.put(letter, l); 71 | for (Config.FontType ft : nni.fonts) { 72 | l.add(Util.getInputForNN( 73 | ExampleGenerator2.getSimpleLetter(letter, ft.font, ft.italic), 74 | false)); 75 | } 76 | } 77 | } 78 | 79 | return c; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/Visualizer.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier; 2 | 3 | import com.zarkonnen.longan.nnidentifier.network.Network; 4 | import com.zarkonnen.longan.nnidentifier.network.Weight; 5 | import java.awt.Canvas; 6 | import java.awt.Color; 7 | import java.awt.Graphics; 8 | import java.awt.image.BufferedImage; 9 | import java.io.File; 10 | import javax.imageio.ImageIO; 11 | import javax.swing.JFrame; 12 | 13 | public class Visualizer { 14 | static final int W = 1000; 15 | static final int H = 600; 16 | 17 | static final Color CONN = new Color(220, 220, 240); 18 | 19 | static class VisFrame extends JFrame { 20 | Canvas c; 21 | 22 | public VisFrame() { 23 | super("Neural Network Training"); 24 | c = new Canvas(); 25 | 26 | getContentPane().add(c); 27 | setSize(W, H); 28 | setVisible(true); 29 | c.createBufferStrategy(2); 30 | } 31 | 32 | public void update(Network in, int pass) { 33 | visualize(c.getBufferStrategy().getDrawGraphics(), in, pass); 34 | c.getBufferStrategy().show(); 35 | } 36 | } 37 | 38 | static void weight(Graphics g, int x, int y, int gridSize, Weight w) { 39 | int intensity = (int) ((w.value + 1.0f) / 2.0f * 250.0f); 40 | if (intensity < 0) { intensity = 0; } 41 | if (intensity > 250) { intensity = 250; } 42 | /*int r = Math.max(0, 125 - intensity) * 2; 43 | int green = Math.max(0, intensity - 125) * 2; 44 | int b = Math.max(0, Math.abs(intensity - 125) - 50) * 2; 45 | g.setColor(new Color(r, green, b));*/ 46 | g.setColor(new Color(intensity, intensity, intensity)); 47 | g.fillRect(x, y, gridSize, gridSize); 48 | /*g.setColor(Color.BLUE); 49 | g.drawRect(x * GR_SZ, y * GR_SZ, GR_SZ, GR_SZ);*/ 50 | } 51 | 52 | public static void saveFrame(Network in, int pass) { 53 | /*BufferedImage img = new BufferedImage(W, H, BufferedImage.TYPE_INT_RGB); 54 | visualize(img.getGraphics(), in, pass); 55 | try { 56 | ImageIO.write(img, "png", new File("/Users/zar/Desktop/film/" + pass + ".png")); 57 | } catch (Exception e) { 58 | e.printStackTrace(); 59 | }*/ 60 | } 61 | 62 | public static void visualize(Graphics g, Network in, int pass) { 63 | g.setColor(Color.WHITE); 64 | g.fillRect(0, 0, W, H); 65 | g.translate(30, 30); 66 | 67 | // so we have 6 blocks of 25+1 in the first layah 68 | int weightIndex = 0; 69 | for (int m = 0; m < 6; m++) { 70 | for (int wY = 0; wY < 5; wY++) { for (int wX = 0; wX < 5; wX++) { 71 | weight(g, 72 | (m * 12 + wX) * 10, 73 | wY * 10, 74 | 10, 75 | in.layers.get(0).weights.get(weightIndex++) 76 | ); 77 | }} 78 | weight(g, 79 | (m * 12 + 5) * 10, 80 | 0, 81 | 10, 82 | in.layers.get(0).weights.get(weightIndex++)); 83 | } 84 | 85 | // Draw connections 86 | g.setColor(CONN); 87 | for (int m = 0; m < 6; m++) { 88 | g.drawLine(m * 12 * 10, 5 * 10 + 2, m * 12 * 10, 8 * 10 - 2); 89 | } 90 | 91 | // In the second layah we have 6 individual scaling factors with no bias 92 | weightIndex = 0; 93 | for (int m = 0; m < 6; m++) { 94 | weight(g, 95 | m * 12 * 10, 96 | 8 * 10, 97 | 10, 98 | in.layers.get(1).weights.get(weightIndex++) 99 | ); 100 | } 101 | 102 | 103 | // Connect h2 to h3 104 | boolean X = true; 105 | boolean O = false; 106 | boolean[][] table = { 107 | {X, O, O, O, X, X, X, O, O, X, X, X, X, O, X, X}, 108 | {X, X, O, O, O, X, X, X, O, O, X, X, X, X, O, X}, 109 | {X, X, X, O, O, O, X, X, X, O, O, X, O, X, X, X}, 110 | {O, X, X, X, O, O, X, X, X, X, O, O, X, O, X, X}, 111 | {O, O, X, X, X, O, O, X, X, X, X, O, X, X, O, X}, 112 | {O, O, O, X, X, X, O, O, X, X, X, X, O, X, X, X} 113 | }; 114 | 115 | // Draw connections 116 | int[] m3Sizes = new int[16]; 117 | for (int m1 = 0; m1 < 6; m1++) { 118 | for (int m3 = 0; m3 < 16; m3++) { 119 | if (!table[m1][m3]) { continue; } 120 | m3Sizes[m3]++; 121 | } 122 | } 123 | int[] m3Offsets = new int[16]; 124 | for (int m3 = 1; m3 < 16; m3++) { 125 | m3Offsets[m3] = m3Sizes[m3 - 1] + m3Offsets[m3 - 1]; 126 | } 127 | 128 | g.setColor(CONN); 129 | int[] m3Indices = new int[16]; 130 | for (int m1 = 0; m1 < 6; m1++) { 131 | for (int m3 = 0; m3 < 16; m3++) { 132 | if (!table[m1][m3]) { continue; } 133 | g.drawLine(m1 * 12 * 10, 9 * 10 + 2, (m3Indices[m3] * 6 + m3Offsets[m3] * 6 + m3 * 8) * 2, 248); 134 | m3Indices[m3]++; 135 | } 136 | } 137 | 138 | // The next one is where it gets to the scary bit 139 | weightIndex = 0; 140 | m3Indices = new int[16]; 141 | for (int m1 = 0; m1 < 6; m1++) { 142 | for (int m3 = 0; m3 < 16; m3++) { 143 | if (!table[m1][m3]) { continue; } 144 | for (int wY = 0; wY < 5; wY++) { for (int wX = 0; wX < 5; wX++) { 145 | weight(g, 146 | (m3Indices[m3] * 6 + m3Offsets[m3] * 6 + m3 * 8 + wX) * 2, 147 | 250 + wY * 2, 148 | 2, 149 | in.layers.get(2).weights.get(weightIndex++) 150 | ); 151 | }} 152 | m3Indices[m3]++; 153 | } 154 | } 155 | 156 | // Biases! 157 | for (int m3 = 0; m3 < 16; m3++) { 158 | weight(g, 159 | (m3Indices[m3] * 6 + m3Offsets[m3] * 6 + m3 * 8) * 2, 160 | 250, 161 | 2, 162 | in.layers.get(2).weights.get(weightIndex++) 163 | ); 164 | } 165 | 166 | // Boxes 167 | g.setColor(CONN); 168 | for (int m3 = 0; m3 < 16; m3++) { 169 | g.drawRect( 170 | m3Offsets[m3] * 12 + m3 * 16 - 2, 171 | 248, 172 | m3Sizes[m3] * 12 + 5, 173 | 13); 174 | g.drawLine( 175 | m3Offsets[m3] * 12 + m3 * 16, 176 | 261, 177 | m3Offsets[m3] * 12 + m3 * 16, 178 | 278); 179 | } 180 | 181 | // h3 -> h4, a scaling layer 182 | weightIndex = 0; 183 | for (int m3 = 0; m3 < 16; m3++) { 184 | weight(g, 185 | (m3Offsets[m3] * 6 + m3 * 8) * 2, 186 | 280, 187 | 5, 188 | in.layers.get(3).weights.get(weightIndex++) 189 | ); 190 | } 191 | 192 | // h4 to output! 193 | weightIndex = 0; 194 | for (int m3 = 0; m3 < 16; m3++) { 195 | for (int out = 0; out < ProfileGen.OUTPUT_SIZE; out++) { 196 | weight(g, 197 | (m3Offsets[m3] * 6 + m3 * 8) * 2 + (out % 8) * 4, 198 | 320 + (out / 8) * 4, 199 | 4, 200 | in.layers.get(4).weights.get(weightIndex++) 201 | ); 202 | } 203 | } 204 | 205 | for (int out = 0; out < ProfileGen.OUTPUT_SIZE; out++) { 206 | weight(g, 207 | (out % 8) * 4, 208 | 420 + (out / 8) * 4, 209 | 4, 210 | in.layers.get(4).weights.get(weightIndex++) 211 | ); 212 | } 213 | 214 | g.setColor(CONN); 215 | g.drawLine(0, 286, 922, 315); 216 | g.drawLine(922, 286, 0, 315); 217 | g.drawRect( 218 | -3, 219 | 317, 220 | 925, 221 | 169 222 | ); 223 | 224 | /* 225 | // Connect h4 to output (full connection) 226 | for (Node h4N : h4.nodes) { 227 | for (Node oN : output.nodes) { 228 | Weight w = new Weight(rnd(-2.0f / 193, 2.0f / 193, r)); 229 | h4.weights.add(w); 230 | new Connection(h4N, oN, w); 231 | } 232 | } 233 | // Add biases to output. 234 | for (Node oN : output.nodes) { 235 | Weight w = new Weight(rnd(-2.0f / 193, 2.0f / 193, r)); 236 | h4.weights.add(w); 237 | new Connection(biasN, oN, w); 238 | }*/ 239 | 240 | g.setColor(Color.BLACK); 241 | g.drawString("Pass " + pass, 0, -12); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo.json: -------------------------------------------------------------------------------- 1 | { 2 | "numberOfNetworks": 1, 3 | "classes": [ 4 | "a", 5 | "b", 6 | "cC", 7 | "d", 8 | "e", 9 | "f", 10 | "g", 11 | "h", 12 | "i", 13 | "j", 14 | "k", 15 | "l", 16 | "m", 17 | "n", 18 | "oO0", 19 | "p", 20 | "q", 21 | "r", 22 | "sS", 23 | "t", 24 | "uU", 25 | "vV", 26 | "wW", 27 | "xX", 28 | "y", 29 | "zZ", 30 | "A", 31 | "B", 32 | "D", 33 | "E", 34 | "F", 35 | "G", 36 | "H", 37 | "I", 38 | "J", 39 | "K", 40 | "L", 41 | "M", 42 | "N", 43 | "Q", 44 | "P", 45 | "R", 46 | "T", 47 | "Y", 48 | "!", 49 | "@", 50 | "£", 51 | "$", 52 | "%", 53 | "&", 54 | "(", 55 | ")", 56 | "'", 57 | ".", 58 | ",", 59 | ":", 60 | ";", 61 | "/", 62 | "?", 63 | "+", 64 | "-", 65 | "1", 66 | "2", 67 | "3", 68 | "4", 69 | "5", 70 | "6", 71 | "7", 72 | "8", 73 | "9" 74 | ], 75 | "root": true, 76 | "seed": 31415926, 77 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 78 | "type": "nnIdentifier", 79 | "fonts": [ 80 | { 81 | "font": "Georgia", 82 | "italic": false 83 | }, 84 | { 85 | "font": "Optima", 86 | "italic": false 87 | }, 88 | { 89 | "font": "Times", 90 | "italic": false 91 | }, 92 | { 93 | "font": "Times New Roman", 94 | "italic": false 95 | } 96 | ], 97 | "proportionalInput": true, 98 | "version": "1.0" 99 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_aspectRatios.json: -------------------------------------------------------------------------------- 1 | { 2 | "!": 0.21085229966808913, 3 | "&": 0.9798976608187133, 4 | "'": 0.390625, 5 | "$": 0.5228098193896338, 6 | "%": 1.0535305041883989, 7 | "£": 0.7100225225225225, 8 | "+": 1.0089285714285714, 9 | "(": 0.31357957356844046, 10 | ")": 0.3140743781206423, 11 | ".": 0.96875, 12 | "/": 0.42736012328117595, 13 | ",": 0.6140231092436975, 14 | "-": 2.7, 15 | "3": 0.6064189189189189, 16 | "2": 0.7186507936507937, 17 | "1": 0.4722222222222222, 18 | "0": 0.7552681992337164, 19 | "7": 0.6411411411411412, 20 | "6": 0.652659238185554, 21 | "5": 0.6298798798798799, 22 | "4": 0.7201308451308451, 23 | ";": 0.2857204465363858, 24 | ":": 0.2906410256410257, 25 | "9": 0.6437687687687688, 26 | "8": 0.6359649122807018, 27 | "?": 0.50358582266477, 28 | "D": 0.9870709995709995, 29 | "E": 0.7861271986271986, 30 | "F": 0.7365240240240241, 31 | "G": 0.9665718349928876, 32 | "@": 0.9874340260597063, 33 | "A": 1.0015444015444015, 34 | "B": 0.8204954954954955, 35 | "C": 0.8644302196933775, 36 | "L": 0.7791827541827542, 37 | "M": 1.2323841698841698, 38 | "N": 1.000563063063063, 39 | "O": 0.9798877825193615, 40 | "H": 1.0015336765336764, 41 | "I": 0.408011583011583, 42 | "J": 0.5290348488022907, 43 | "K": 0.9612022737022736, 44 | "U": 0.9868618618618619, 45 | "T": 0.8403689403689404, 46 | "W": 1.3626126126126126, 47 | "V": 0.9799174174174174, 48 | "Q": 0.7869251020846765, 49 | "P": 0.7494637494637495, 50 | "S": 0.6530346135609293, 51 | "R": 0.9252895752895753, 52 | "Y": 0.9741312741312742, 53 | "X": 0.9747050622050621, 54 | "Z": 0.8608376233376234, 55 | "f": 0.5193376068376069, 56 | "g": 0.6689189189189189, 57 | "d": 0.6737800300300301, 58 | "e": 0.8551851851851852, 59 | "b": 0.6756092983227129, 60 | "c": 0.8366666666666666, 61 | "a": 0.8863390313390314, 62 | "n": 1.0242165242165242, 63 | "o": 0.9614814814814814, 64 | "l": 0.3140491452991453, 65 | "m": 1.5893874643874646, 66 | "j": 0.2979416262342125, 67 | "k": 0.7028617216117217, 68 | "h": 0.6896672771672772, 69 | "i": 0.32253748109011265, 70 | "w": 1.4557549857549859, 71 | "v": 1.0000284900284901, 72 | "u": 0.9900569800569801, 73 | "t": 0.4841445467483181, 74 | "s": 0.6825925925925926, 75 | "r": 0.7065527065527065, 76 | "q": 0.6979323308270677, 77 | "p": 0.7097249034749035, 78 | "z": 0.8806089743589743, 79 | "y": 0.7073210573210573, 80 | "x": 1.0120192307692308 81 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_sizes.json: -------------------------------------------------------------------------------- 1 | { 2 | "!": 0.782583419738937, 3 | "&": 1.6746367968907643, 4 | "'": 0.46215979243578187, 5 | "$": 1.47552104977532, 6 | "%": 1.773282818114597, 7 | "£": 1.4167835741580772, 8 | "+": 1.2697495651012498, 9 | "(": 1.174076387420754, 10 | ")": 1.1760263389873253, 11 | ".": 0.3534952165615652, 12 | "/": 1.2220132132319497, 13 | ",": 0.5627875836201995, 14 | "-": 0.4173522613119735, 15 | "3": 1.309049174227119, 16 | "2": 1.305607383651079, 17 | "1": 1.0517907877694601, 18 | "0": 1.367087907710486, 19 | "7": 1.3464092352950736, 20 | "6": 1.3772773546288264, 21 | "5": 1.3435086952574327, 22 | "4": 1.4073549093372315, 23 | ";": 0.8243191501609275, 24 | ":": 0.6440231565009689, 25 | "9": 1.3581888425235367, 26 | "8": 1.3500010254794852, 27 | "?": 1.2059592309284082, 28 | "D": 1.6461916795207663, 29 | "E": 1.4605901930322736, 30 | "F": 1.4160750246887415, 31 | "G": 1.6751483235903708, 32 | "@": 1.8254759839633303, 33 | "A": 1.668889164879837, 34 | "B": 1.4967227142131592, 35 | "C": 1.5843031046649934, 36 | "L": 1.4542906756804324, 37 | "M": 1.8388385567538519, 38 | "N": 1.691666402168374, 39 | "O": 1.6866772134927281, 40 | "H": 1.656867748913612, 41 | "I": 1.0391056627804591, 42 | "J": 1.256173988804262, 43 | "K": 1.6183718207489663, 44 | "U": 1.6803010256565836, 45 | "T": 1.517859462942989, 46 | "W": 1.9764991788394841, 47 | "V": 1.6749698079877893, 48 | "Q": 1.8821022824074767, 49 | "P": 1.433241123481771, 50 | "S": 1.3770771869804648, 51 | "R": 1.5898984239475629, 52 | "Y": 1.6327575136919077, 53 | "X": 1.6310933943649237, 54 | "Z": 1.5376984608706337, 55 | "f": 1.254867641435316, 56 | "g": 1.3911079074348336, 57 | "d": 1.4548370494587761, 58 | "e": 1.1155860399198225, 59 | "b": 1.4671525643512058, 60 | "c": 1.1030187391469592, 61 | "a": 1.1458228198559743, 62 | "n": 1.180406011849224, 63 | "o": 1.182600046287185, 64 | "l": 0.9667467137585112, 65 | "m": 1.4728664719342484, 66 | "j": 1.209171993451588, 67 | "k": 1.4530803630025113, 68 | "h": 1.4385906612192332, 69 | "i": 0.952322941224455, 70 | "w": 1.441310835572025, 71 | "v": 1.1940825637289745, 72 | "u": 1.187393933242088, 73 | "t": 1.0490057938961221, 74 | "s": 0.9960481749723749, 75 | "r": 0.9784778797281346, 76 | "q": 1.430306103043781, 77 | "p": 1.432802631670465, 78 | "z": 1.0874458375481602, 79 | "y": 1.4208739348559383, 80 | "x": 1.1649100606106926 81 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_targets_0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_targets_0 -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_weights_0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__abcc-ucdefghijklmnoo_weights_0 -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__b-ucr-uc.json: -------------------------------------------------------------------------------- 1 | { 2 | "classes": [ 3 | "B", 4 | "R" 5 | ], 6 | "root": false, 7 | "seed": 31415926, 8 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 9 | "type": "treeIdentifier", 10 | "fonts": [ 11 | { 12 | "font": "Georgia", 13 | "italic": false 14 | }, 15 | { 16 | "font": "Optima", 17 | "italic": false 18 | }, 19 | { 20 | "font": "Times", 21 | "italic": false 22 | }, 23 | { 24 | "font": "Times New Roman", 25 | "italic": false 26 | } 27 | ], 28 | "version": "1.0" 29 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__b-ucr-uc_tree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__b-ucr-uc_tree -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__i!.json: -------------------------------------------------------------------------------- 1 | { 2 | "classes": [ 3 | "i", 4 | "!" 5 | ], 6 | "root": false, 7 | "seed": 31415926, 8 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 9 | "type": "nearestNeighbourIdentifier", 10 | "fonts": [ 11 | { 12 | "font": "Georgia", 13 | "italic": false 14 | }, 15 | { 16 | "font": "Optima", 17 | "italic": false 18 | }, 19 | { 20 | "font": "Times", 21 | "italic": false 22 | }, 23 | { 24 | "font": "Times New Roman", 25 | "italic": false 26 | } 27 | ], 28 | "version": "1.0" 29 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__i!_comparisons: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__i!_comparisons -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__i-ucl1i!.json: -------------------------------------------------------------------------------- 1 | { 2 | "classes": [ 3 | "Il1", 4 | "i!" 5 | ], 6 | "root": false, 7 | "seed": 31415926, 8 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 9 | "type": "numberOfPartsIdentifier", 10 | "fonts": [ 11 | { 12 | "font": "Georgia", 13 | "italic": false 14 | }, 15 | { 16 | "font": "Optima", 17 | "italic": false 18 | }, 19 | { 20 | "font": "Times", 21 | "italic": false 22 | }, 23 | { 24 | "font": "Times New Roman", 25 | "italic": false 26 | } 27 | ], 28 | "version": "1.0" 29 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__i-ucl1i!_numberOfParts.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "firstIsAboveBoundary": false, 4 | "numberOfPartsBoundary": 1 5 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__ij.json: -------------------------------------------------------------------------------- 1 | { 2 | "classes": [ 3 | "i", 4 | "j" 5 | ], 6 | "root": false, 7 | "seed": 31415926, 8 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 9 | "type": "nearestNeighbourIdentifier", 10 | "fonts": [ 11 | { 12 | "font": "Georgia", 13 | "italic": false 14 | }, 15 | { 16 | "font": "Optima", 17 | "italic": false 18 | }, 19 | { 20 | "font": "Times", 21 | "italic": false 22 | }, 23 | { 24 | "font": "Times New Roman", 25 | "italic": false 26 | } 27 | ], 28 | "version": "1.0" 29 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__ij_comparisons: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/data/georgia_optima_times_timesnewroman__ij_comparisons -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/data/source.json: -------------------------------------------------------------------------------- 1 | { 2 | "contents": [ 3 | { 4 | "numberOfNetworks": 1, 5 | "classes": [ 6 | "a", 7 | "b", 8 | "cC", 9 | "d", 10 | "e", 11 | "f", 12 | "g", 13 | "h", 14 | "i", 15 | "j", 16 | "k", 17 | "l", 18 | "m", 19 | "n", 20 | "oO0", 21 | "p", 22 | "q", 23 | "r", 24 | "sS", 25 | "t", 26 | "uU", 27 | "vV", 28 | "wW", 29 | "xX", 30 | "y", 31 | "zZ", 32 | "A", 33 | "B", 34 | "D", 35 | "E", 36 | "F", 37 | "G", 38 | "H", 39 | "I", 40 | "J", 41 | "K", 42 | "L", 43 | "M", 44 | "N", 45 | "Q", 46 | "P", 47 | "R", 48 | "T", 49 | "Y", 50 | "!", 51 | "@", 52 | "£", 53 | "$", 54 | "%", 55 | "&", 56 | "(", 57 | ")", 58 | "'", 59 | ".", 60 | ",", 61 | ":", 62 | ";", 63 | "/", 64 | "?", 65 | "+", 66 | "-", 67 | "1", 68 | "2", 69 | "3", 70 | "4", 71 | "5", 72 | "6", 73 | "7", 74 | "8", 75 | "9" 76 | ], 77 | "root": true, 78 | "seed": 31415926, 79 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 80 | "type": "nnIdentifier", 81 | "fonts": [ 82 | { 83 | "font": "Georgia", 84 | "italic": false 85 | }, 86 | { 87 | "font": "Optima", 88 | "italic": false 89 | }, 90 | { 91 | "font": "Times", 92 | "italic": false 93 | }, 94 | { 95 | "font": "Times New Roman", 96 | "italic": false 97 | } 98 | ], 99 | "proportionalInput": true, 100 | "version": "1.0" 101 | }, 102 | { 103 | "classes": [ 104 | "i", 105 | "!" 106 | ], 107 | "root": false, 108 | "seed": 31415926, 109 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 110 | "type": "nearestNeighbourIdentifier", 111 | "fonts": [ 112 | { 113 | "font": "Georgia", 114 | "italic": false 115 | }, 116 | { 117 | "font": "Optima", 118 | "italic": false 119 | }, 120 | { 121 | "font": "Times", 122 | "italic": false 123 | }, 124 | { 125 | "font": "Times New Roman", 126 | "italic": false 127 | } 128 | ], 129 | "version": "1.0" 130 | }, 131 | { 132 | "classes": [ 133 | "i", 134 | "j" 135 | ], 136 | "root": false, 137 | "seed": 31415926, 138 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 139 | "type": "nearestNeighbourIdentifier", 140 | "fonts": [ 141 | { 142 | "font": "Georgia", 143 | "italic": false 144 | }, 145 | { 146 | "font": "Optima", 147 | "italic": false 148 | }, 149 | { 150 | "font": "Times", 151 | "italic": false 152 | }, 153 | { 154 | "font": "Times New Roman", 155 | "italic": false 156 | } 157 | ], 158 | "version": "1.0" 159 | }, 160 | { 161 | "classes": [ 162 | "Il1", 163 | "i!" 164 | ], 165 | "root": false, 166 | "seed": 31415926, 167 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 168 | "type": "numberOfPartsIdentifier", 169 | "fonts": [ 170 | { 171 | "font": "Georgia", 172 | "italic": false 173 | }, 174 | { 175 | "font": "Optima", 176 | "italic": false 177 | }, 178 | { 179 | "font": "Times", 180 | "italic": false 181 | }, 182 | { 183 | "font": "Times New Roman", 184 | "italic": false 185 | } 186 | ], 187 | "version": "1.0" 188 | }, 189 | { 190 | "classes": [ 191 | "B", 192 | "R" 193 | ], 194 | "root": false, 195 | "seed": 31415926, 196 | "sampleSentence": "The quick brown fox jumped over the lazy dog.", 197 | "type": "treeIdentifier", 198 | "fonts": [ 199 | { 200 | "font": "Georgia", 201 | "italic": false 202 | }, 203 | { 204 | "font": "Optima", 205 | "italic": false 206 | }, 207 | { 208 | "font": "Times", 209 | "italic": false 210 | }, 211 | { 212 | "font": "Times New Roman", 213 | "italic": false 214 | } 215 | ], 216 | "version": "1.0" 217 | } 218 | ], 219 | "type": "config", 220 | "version": "1.0" 221 | } -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/identifier-alt.lns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/identifier-alt.lns -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/identifier.lns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zarkonnen/Longan/e426c43001d298286dad1c1bf82297422c5e67d0/src/com/zarkonnen/longan/nnidentifier/identifier.lns -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Connection.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | public class Connection { 4 | public final Node input; 5 | public final Node output; 6 | public final Weight weight; 7 | 8 | public Connection(Node input, Node output, Weight weight) { 9 | this.input = input; 10 | this.output = output; 11 | this.weight = weight; 12 | weight.connections.add(this); 13 | input.outgoing.add(this); 14 | output.incoming.add(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Layer.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Layer { 6 | public final ArrayList nodes; 7 | public final ArrayList weights; 8 | public final String name; 9 | 10 | public Layer(String name) { 11 | this.name = name; 12 | nodes = new ArrayList(); 13 | weights = new ArrayList(); 14 | } 15 | 16 | public Layer(String name, ArrayList nodes, ArrayList weights) { 17 | this.name = name; 18 | this.nodes = nodes; 19 | this.weights = weights; 20 | } 21 | 22 | public void update() { 23 | for (Node n : nodes) { n.update(); } 24 | } 25 | 26 | public void calculateDelta() { 27 | for (Node n : nodes) { n.calculateDelta(); } 28 | } 29 | 30 | public float adjustWeights(float n, float m) { 31 | float total = 0.0f; 32 | for (Weight w : weights) { total += Math.abs(w.adjust(n, m)); } 33 | return total / weights.size(); 34 | } 35 | 36 | String getDetails() { 37 | String d = ""; 38 | for (Node n : nodes) { 39 | d += n.name + " act=" + n.activation + " delta=" + n.delta + "\n"; 40 | /*for (Connection c : n.incoming) { 41 | d += c.weight.value + "\n"; 42 | }*/ 43 | } 44 | return d; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Network.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Network { 6 | public final ArrayList layers; 7 | 8 | public Network(ArrayList layers) { 9 | this.layers = layers; 10 | } 11 | 12 | public double cutBelowThreshold(double t) { 13 | int nCut = 0; 14 | double nConns = 0; 15 | for (Layer l : layers) { 16 | nConns += l.weights.size(); 17 | for (Weight w : l.weights) { 18 | if (Math.abs(w.value) < t) { 19 | w.value = 0; 20 | nCut++; 21 | } 22 | } 23 | } 24 | return nCut / nConns; 25 | } 26 | 27 | public void train(float[] input, float[] target, float[] n, float[] m) { 28 | setInput(input); 29 | update(); 30 | setTargets(target); 31 | calculateDelta(); 32 | adjustWeights(n, m); 33 | } 34 | 35 | public void train(float[] input, float[] target, float n, float m) { 36 | setInput(input); 37 | update(); 38 | setTargets(target); 39 | calculateDelta(); 40 | adjustWeights(n, m); 41 | } 42 | 43 | public float[] run(float[] input) { 44 | setInput(input); 45 | update(); 46 | float[] output = new float[layers.get(layers.size() - 1).nodes.size()]; 47 | for (int i = 0; i < output.length; i++) { 48 | output[i] = layers.get(layers.size() - 1).nodes.get(i).activation; 49 | } 50 | return output; 51 | } 52 | 53 | public void setInput(float[] inputs) { 54 | ArrayList iNodes = layers.get(0).nodes; 55 | assert(inputs.length == iNodes.size()); 56 | for (int i = 0; i < inputs.length; i++) { 57 | iNodes.get(i).activation = inputs[i]; 58 | } 59 | } 60 | 61 | public void setTargets(float[] targets) { 62 | ArrayList outputs = layers.get(layers.size() - 1).nodes; 63 | assert(targets.length == outputs.size()); 64 | for (int i = 0; i < targets.length; i++) { 65 | outputs.get(i).delta = targets[i] - outputs.get(i).activation; 66 | } 67 | } 68 | 69 | public void calculateDelta() { 70 | // Don't calc delta for output layer. 71 | for (int i = layers.size() - 2; i >= 0; i--) { 72 | layers.get(i).calculateDelta(); 73 | } 74 | } 75 | 76 | public void adjustWeights(float n, float m) { 77 | for (int i = layers.size() - 1; i >= 0; i--) { 78 | layers.get(i).adjustWeights(n, m); 79 | } 80 | } 81 | 82 | public void adjustWeights(float[] n, float[] m) { 83 | for (int i = layers.size() - 1; i >= 0; i--) { 84 | layers.get(i).adjustWeights(n[i], m[i]); 85 | } 86 | } 87 | 88 | public void update() { 89 | for (Layer l : layers) { l.update(); } 90 | } 91 | 92 | public String getDetails() { 93 | String d = ""; 94 | for (Layer l : layers) { 95 | d += l.getDetails() + "\n"; 96 | } 97 | return d; 98 | } 99 | 100 | public int numWeights() { 101 | int n = 0; 102 | for (Layer l : layers) { 103 | n += l.weights.size(); 104 | } 105 | return n; 106 | } 107 | 108 | public int numNodes() { 109 | int n = 0; 110 | for (Layer l : layers) { 111 | n += l.nodes.size(); 112 | } 113 | return n; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Node.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | import static com.zarkonnen.longan.nnidentifier.network.Util.*; 4 | import java.util.ArrayList; 5 | 6 | public class Node { 7 | public final String name; 8 | public float activation; 9 | public float delta; 10 | public final ArrayList incoming = new ArrayList(); 11 | public final ArrayList outgoing = new ArrayList(); 12 | 13 | public Node(String name) { 14 | this.name = name; 15 | } 16 | 17 | public void update() { 18 | if (incoming.isEmpty()) { return; } 19 | float sum = 0.0f; 20 | for (Connection in : incoming) { 21 | sum += in.input.activation * in.weight.value; 22 | } 23 | activation = (float) Math.tanh(sum); 24 | } 25 | 26 | void calculateDelta() { 27 | float error = 0.0f; 28 | for (Connection out : outgoing) { 29 | error += out.output.delta * out.weight.value; 30 | } 31 | delta = dSigmoid(activation) * error; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Output.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | public class Output { 4 | public String l; 5 | public double[] data; 6 | 7 | public Output(String l, double[] data) { 8 | this.l = l; 9 | this.data = data; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Util.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | import com.zarkonnen.longan.data.Letter; 4 | import java.awt.Color; 5 | import java.awt.Graphics2D; 6 | import java.awt.RenderingHints; 7 | import java.awt.image.BufferedImage; 8 | import java.util.Random; 9 | 10 | public class Util { 11 | private Util() {} 12 | 13 | public static double sigmoid(double x) { 14 | return Math.tanh(x); 15 | } 16 | 17 | public static float dSigmoid(float y) { 18 | return 1.0f - y * y; 19 | } 20 | 21 | public static float rnd(float from, float to, Random r) { 22 | return (to - from) * r.nextFloat() + from; 23 | } 24 | 25 | public static BufferedImage convertInputToImg(float[] in) { 26 | int sz = (int) Math.sqrt(in.length); 27 | int w = sz; 28 | int h = sz; 29 | if (sz * sz != in.length) { 30 | w = ((int) Math.sqrt(in.length / 2)) * 2; 31 | h = ((int) Math.sqrt(in.length / 2)); 32 | } 33 | BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 34 | for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { 35 | int intensity = (int) ((in[y * w + x] + 1.0f) / 2.0f * 255.0f); 36 | intensity = Math.max(0, Math.min(255, intensity)); 37 | Color c = new Color(intensity, intensity, intensity); 38 | img.setRGB(x, y, c.getRGB()); 39 | } } 40 | BufferedImage img2 = new BufferedImage(w * 10, h * 10, BufferedImage.TYPE_INT_RGB); 41 | Graphics2D g = img2.createGraphics(); 42 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 43 | g.drawImage(img, 0, 0, w * 10, h * 10, null); 44 | return img2; 45 | } 46 | 47 | public static BufferedImage convertInputToImg(float[][] in) { 48 | BufferedImage img = new BufferedImage(in[0].length, in.length, BufferedImage.TYPE_INT_RGB); 49 | for (int y = 0; y < in.length; y++) { for (int x = 0; x < in[0].length; x++) { 50 | int intensity = (int) ((in[y][x] + 1.0f) / 2.0f * 255.0f); 51 | intensity = Math.max(0, Math.min(255, intensity)); 52 | Color c = new Color(intensity, intensity, intensity); 53 | img.setRGB(x, y, c.getRGB()); 54 | } } 55 | BufferedImage img2 = new BufferedImage(in[0].length * 10, in.length * 10, BufferedImage.TYPE_INT_RGB); 56 | Graphics2D g = img2.createGraphics(); 57 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 58 | g.drawImage(img, 0, 0, in[0].length * 10, in.length * 10, null); 59 | return img2; 60 | } 61 | 62 | public static float[] convertImgToInput(BufferedImage src) { 63 | float[] result = new float[src.getWidth() * src.getHeight()]; 64 | for (int y = 0; y < src.getHeight(); y++) { for (int x = 0; x < src.getWidth(); x++) { 65 | Color c = new Color(src.getRGB(x, y)); 66 | result[y * src.getWidth() + x] = (c.getRed() + c.getGreen() + c.getBlue()) / 255.0f / 1.5f - 1; 67 | } } 68 | return result; 69 | } 70 | 71 | public static float[] getTargetForNN(BufferedImage src, boolean proportional) { 72 | BufferedImage scaledSrc = new BufferedImage(16, 8, BufferedImage.TYPE_INT_RGB); 73 | Graphics2D g = scaledSrc.createGraphics(); 74 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 75 | g.setColor(Color.WHITE); 76 | g.fillRect(0, 0, 16, 8); 77 | int width = 0; 78 | int xOffset = 0; 79 | int height = 0; 80 | int yOffset = 0; 81 | if (proportional) { 82 | if (src.getWidth() > src.getHeight()) { 83 | width = 16; 84 | height = 8 * src.getHeight() / src.getWidth(); 85 | yOffset = (8 - height) / 2; 86 | } else { 87 | height = 8; 88 | width = 16 * src.getWidth() / src.getHeight(); 89 | xOffset = (16 - width) / 2; 90 | } 91 | } else { 92 | width = 16; 93 | height = 8; 94 | xOffset = 0; 95 | yOffset = 0; 96 | } 97 | g.drawImage(src, xOffset, yOffset, xOffset + width, yOffset + height, null); 98 | src = scaledSrc; 99 | float[] result = new float[16 * 8]; 100 | for (int y = 0; y < 8; y++) { for (int x = 0; x < 16; x++) { 101 | Color c = new Color(src.getRGB(x, y)); 102 | result[y * 16 + x] = (c.getRed() + c.getGreen() + c.getBlue()) / 255.0f / 1.5f - 1; 103 | } } 104 | return result; 105 | } 106 | 107 | public static float[] getInputForNN(BufferedImage src, boolean proportional) { 108 | BufferedImage scaledSrc = new BufferedImage(28, 28, BufferedImage.TYPE_INT_RGB); 109 | Graphics2D g = scaledSrc.createGraphics(); 110 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 111 | g.setColor(Color.WHITE); 112 | g.fillRect(0, 0, 28, 28); 113 | int width = 0; 114 | int xOffset = 0; 115 | int height = 0; 116 | int yOffset = 0; 117 | if (proportional) { 118 | if (src.getWidth() > src.getHeight()) { 119 | width = 16; 120 | height = 16 * src.getHeight() / src.getWidth(); 121 | yOffset = (16 - height) / 2; 122 | } else { 123 | height = 16; 124 | width = 16 * src.getWidth() / src.getHeight(); 125 | xOffset = (16 - width) / 2; 126 | } 127 | } else { 128 | width = 16; 129 | height = 16; 130 | xOffset = 0; 131 | yOffset = 0; 132 | } 133 | g.drawImage(src, 6 + xOffset, 6 + yOffset, 6 + xOffset + width, 6 + yOffset + height, 0, 0, src.getWidth(), src.getHeight(), null); 134 | src = scaledSrc; 135 | float[] result = new float[28 * 28]; 136 | for (int y = 0; y < 28; y++) { for (int x = 0; x < 28; x++) { 137 | Color c = new Color(src.getRGB(x, y)); 138 | result[y * 28 + x] = (c.getRed() + c.getGreen() + c.getBlue()) / 255.0f / 1.5f - 1; 139 | } } 140 | return result; 141 | } 142 | 143 | public static float[] getInputForNN(Letter r, BufferedImage src, int intensityAdjustment, boolean proportional) { 144 | // Masking 145 | BufferedImage maskedSrc = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_RGB); 146 | Graphics2D g = maskedSrc.createGraphics(); 147 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 148 | g.drawImage( 149 | src, 150 | 0, 0, 151 | r.width, r.height, 152 | r.x, r.y, 153 | r.x + r.width, r.y + r.height, 154 | null); 155 | int white = Color.WHITE.getRGB(); 156 | for (int y = 0; y < r.height; y++) { 157 | for (int x = 0; x < r.width; x++) { 158 | boolean hasMask = false; 159 | for (int dy = -1; dy < 2; dy++) { for (int dx = -1; dx < 2; dx++) { 160 | int ny = y + dy; 161 | int nx = x + dx; 162 | if (ny >= 0 && ny < r.height && nx >= 0 && nx < r.width) { 163 | hasMask |= r.mask[ny][nx]; 164 | } 165 | }} 166 | if (!hasMask) { 167 | maskedSrc.setRGB(x, y, white); 168 | } 169 | } 170 | } 171 | src = maskedSrc; 172 | BufferedImage scaledSrc = new BufferedImage(28, 28, BufferedImage.TYPE_INT_RGB); 173 | g = scaledSrc.createGraphics(); 174 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 175 | g.setColor(Color.WHITE); 176 | g.fillRect(0, 0, 28, 28); 177 | int width = 0; 178 | int xOffset = 0; 179 | int height = 0; 180 | int yOffset = 0; 181 | if (proportional) { 182 | if (r.width > r.height) { 183 | width = 16; 184 | height = 16 * r.height / r.width; 185 | yOffset = (16 - height) / 2; 186 | } else { 187 | height = 16; 188 | width = 16 * r.width / r.height; 189 | xOffset = (16 - width) / 2; 190 | } 191 | } else { 192 | width = 16; 193 | height = 16; 194 | xOffset = 0; 195 | yOffset = 0; 196 | } 197 | 198 | g.drawImage( 199 | src, 200 | 6 + xOffset, 6 + yOffset, 201 | 6 + xOffset + width, 6 + yOffset + height, 202 | 0, 0, 203 | r.width, r.height, 204 | null); 205 | src = scaledSrc; 206 | float[] result = new float[28 * 28]; 207 | for (int y = 0; y < 28; y++) { for (int x = 0; x < 28; x++) { 208 | Color c = new Color(src.getRGB(x, y)); 209 | result[y * 28 + x] = (c.getRed() + c.getGreen() + c.getBlue() + intensityAdjustment * 3) / 255.0f / 1.5f - 1; 210 | } } 211 | return result; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/nnidentifier/network/Weight.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.nnidentifier.network; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Weight { 6 | public float value; 7 | public float lastAdjustment; 8 | public final ArrayList connections = new ArrayList(); 9 | 10 | public Weight(float value) { 11 | this.value = value; 12 | } 13 | 14 | double adjust(float n, float m) { 15 | float change = 0.0f; 16 | for (Connection c : connections) { 17 | change += c.output.delta * c.input.activation; 18 | } 19 | value += n * change + m * lastAdjustment; 20 | lastAdjustment = change; 21 | return change; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/simple/SimpleWordPlaintextConverter.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.simple; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Column; 20 | import com.zarkonnen.longan.data.Letter; 21 | import com.zarkonnen.longan.data.Line; 22 | import com.zarkonnen.longan.data.Result; 23 | import com.zarkonnen.longan.data.Word; 24 | import com.zarkonnen.longan.stage.ResultConverter; 25 | import java.io.IOException; 26 | import java.io.OutputStream; 27 | import java.io.OutputStreamWriter; 28 | 29 | public class SimpleWordPlaintextConverter implements ResultConverter { 30 | public String convert(Result result) { 31 | StringBuilder sb = new StringBuilder(); 32 | for (Column c : result.columns) { 33 | for (Line l : c.lines) { 34 | for (Word w : l.words) { 35 | for (Letter letter : w.letters) { 36 | sb.append(letter.bestLetter()); 37 | } 38 | sb.append(" "); 39 | } 40 | sb.append("\n"); 41 | } 42 | sb.append("\n"); 43 | } 44 | return sb.toString(); 45 | } 46 | 47 | public void write(String output, OutputStream stream) throws IOException { 48 | OutputStreamWriter osw = new OutputStreamWriter(stream); 49 | osw.write(output); 50 | osw.flush(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/stage/Chunker.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.stage; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import com.zarkonnen.longan.data.Result; 21 | import java.awt.image.BufferedImage; 22 | import java.util.ArrayList; 23 | import java.util.HashMap; 24 | 25 | public interface Chunker { 26 | public Result chunk(ArrayList rects, BufferedImage img, HashMap metadata); 27 | } 28 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/stage/LetterFinder.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.stage; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Letter; 20 | import java.awt.image.BufferedImage; 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | 24 | public interface LetterFinder { 25 | public ArrayList find(BufferedImage img, HashMap metadata); 26 | } 27 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/stage/LetterIdentifier.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.stage; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Column; 20 | import com.zarkonnen.longan.data.Letter; 21 | import com.zarkonnen.longan.data.Line; 22 | import com.zarkonnen.longan.data.Result; 23 | import com.zarkonnen.longan.data.Word; 24 | 25 | public interface LetterIdentifier { 26 | public void identify(Result result); 27 | public void reIdentify(Letter l, Letter source, Word word, Line line, Column column, Result result); 28 | public void discriminateTopLetterClass(Letter l, Letter source, Word word, Line line, Column column, Result result); 29 | public void finish(); 30 | } 31 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/stage/PostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.stage; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.Longan; 20 | import com.zarkonnen.longan.data.Result; 21 | 22 | public interface PostProcessor { 23 | public void process(Result result, Longan longan); 24 | } 25 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/stage/PreProcessor.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.stage; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.awt.image.BufferedImage; 20 | import java.util.HashMap; 21 | 22 | public interface PreProcessor { 23 | public BufferedImage process(BufferedImage img, HashMap metadata); 24 | } 25 | -------------------------------------------------------------------------------- /src/com/zarkonnen/longan/stage/ResultConverter.java: -------------------------------------------------------------------------------- 1 | package com.zarkonnen.longan.stage; 2 | 3 | /* 4 | * Copyright 2011 David Stark 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.zarkonnen.longan.data.Result; 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | 23 | public interface ResultConverter { 24 | public T convert(Result result); 25 | public void write(T output, OutputStream stream) throws IOException; 26 | } 27 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/AlreadySelectedException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * Thrown when more than one option in an option group 22 | * has been provided. 23 | * 24 | * @author John Keyes ( john at integralsource.com ) 25 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 26 | */ 27 | public class AlreadySelectedException extends ParseException 28 | { 29 | /** The option group selected. */ 30 | private OptionGroup group; 31 | 32 | /** The option that triggered the exception. */ 33 | private Option option; 34 | 35 | /** 36 | * Construct a new AlreadySelectedException 37 | * with the specified detail message. 38 | * 39 | * @param message the detail message 40 | */ 41 | public AlreadySelectedException(String message) 42 | { 43 | super(message); 44 | } 45 | 46 | /** 47 | * Construct a new AlreadySelectedException 48 | * for the specified option group. 49 | * 50 | * @param group the option group already selected 51 | * @param option the option that triggered the exception 52 | * @since 1.2 53 | */ 54 | public AlreadySelectedException(OptionGroup group, Option option) 55 | { 56 | this("The option '" + option.getKey() + "' was specified but an option from this group " 57 | + "has already been selected: '" + group.getSelected() + "'"); 58 | this.group = group; 59 | this.option = option; 60 | } 61 | 62 | /** 63 | * Returns the option group where another option has been selected. 64 | * 65 | * @return the related option group 66 | * @since 1.2 67 | */ 68 | public OptionGroup getOptionGroup() 69 | { 70 | return group; 71 | } 72 | 73 | /** 74 | * Returns the option that was added to the group and triggered the exception. 75 | * 76 | * @return the related option 77 | * @since 1.2 78 | */ 79 | public Option getOption() 80 | { 81 | return option; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/BasicParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * The class BasicParser provides a very simple implementation of 22 | * the {@link Parser#flatten(Options,String[],boolean) flatten} method. 23 | * 24 | * @author John Keyes (john at integralsource.com) 25 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 26 | */ 27 | public class BasicParser extends Parser 28 | { 29 | /** 30 | *

A simple implementation of {@link Parser}'s abstract 31 | * {@link Parser#flatten(Options, String[], boolean) flatten} method.

32 | * 33 | *

Note: options and stopAtNonOption 34 | * are not used in this flatten method.

35 | * 36 | * @param options The command line {@link Options} 37 | * @param arguments The command line arguments to be parsed 38 | * @param stopAtNonOption Specifies whether to stop flattening 39 | * when an non option is found. 40 | * @return The arguments String array. 41 | */ 42 | protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) 43 | { 44 | // just echo the arguments 45 | return arguments; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/CommandLineParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * A class that implements the CommandLineParser interface 22 | * can parse a String array according to the {@link Options} specified 23 | * and return a {@link CommandLine}. 24 | * 25 | * @author John Keyes (john at integralsource.com) 26 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 27 | */ 28 | public interface CommandLineParser 29 | { 30 | /** 31 | * Parse the arguments according to the specified options. 32 | * 33 | * @param options the specified Options 34 | * @param arguments the command line arguments 35 | * @return the list of atomic option and value tokens 36 | * 37 | * @throws ParseException if there are any problems encountered 38 | * while parsing the command line tokens. 39 | */ 40 | CommandLine parse(Options options, String[] arguments) throws ParseException; 41 | 42 | /** 43 | * Parse the arguments according to the specified options and 44 | * properties. 45 | * 46 | * @param options the specified Options 47 | * @param arguments the command line arguments 48 | * @param properties command line option name-value pairs 49 | * @return the list of atomic option and value tokens 50 | * 51 | * @throws ParseException if there are any problems encountered 52 | * while parsing the command line tokens. 53 | */ 54 | /* To maintain binary compatibility, this is commented out. 55 | It is still in the abstract Parser class, so most users will 56 | still reap the benefit. 57 | CommandLine parse(Options options, String[] arguments, Properties properties) 58 | throws ParseException; 59 | */ 60 | 61 | /** 62 | * Parse the arguments according to the specified options. 63 | * 64 | * @param options the specified Options 65 | * @param arguments the command line arguments 66 | * @param stopAtNonOption specifies whether to continue parsing the 67 | * arguments if a non option is encountered. 68 | * 69 | * @return the list of atomic option and value tokens 70 | * @throws ParseException if there are any problems encountered 71 | * while parsing the command line tokens. 72 | */ 73 | CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException; 74 | 75 | /** 76 | * Parse the arguments according to the specified options and 77 | * properties. 78 | * 79 | * @param options the specified Options 80 | * @param arguments the command line arguments 81 | * @param properties command line option name-value pairs 82 | * @param stopAtNonOption specifies whether to continue parsing the 83 | * 84 | * @return the list of atomic option and value tokens 85 | * @throws ParseException if there are any problems encountered 86 | * while parsing the command line tokens. 87 | */ 88 | /* To maintain binary compatibility, this is commented out. 89 | It is still in the abstract Parser class, so most users will 90 | still reap the benefit. 91 | CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption) 92 | throws ParseException; 93 | */ 94 | } 95 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/GnuParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * The class GnuParser provides an implementation of the 25 | * {@link Parser#flatten(Options, String[], boolean) flatten} method. 26 | * 27 | * @author John Keyes (john at integralsource.com) 28 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 29 | */ 30 | public class GnuParser extends Parser 31 | { 32 | /** 33 | * This flatten method does so using the following rules: 34 | *
    35 | *
  1. If an {@link Option} exists for the first character of 36 | * the arguments entry AND an {@link Option} 37 | * does not exist for the whole argument then 38 | * add the first character as an option to the processed tokens 39 | * list e.g. "-D" and add the rest of the entry to the also.
  2. 40 | *
  3. Otherwise just add the token to the processed tokens list.
  4. 41 | *
42 | * 43 | * @param options The Options to parse the arguments by. 44 | * @param arguments The arguments that have to be flattened. 45 | * @param stopAtNonOption specifies whether to stop flattening when 46 | * a non option has been encountered 47 | * @return a String array of the flattened arguments 48 | */ 49 | protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) 50 | { 51 | List tokens = new ArrayList(); 52 | 53 | boolean eatTheRest = false; 54 | 55 | for (int i = 0; i < arguments.length; i++) 56 | { 57 | String arg = arguments[i]; 58 | 59 | if ("--".equals(arg)) 60 | { 61 | eatTheRest = true; 62 | tokens.add("--"); 63 | } 64 | else if ("-".equals(arg)) 65 | { 66 | tokens.add("-"); 67 | } 68 | else if (arg.startsWith("-")) 69 | { 70 | String opt = Util.stripLeadingHyphens(arg); 71 | 72 | if (options.hasOption(opt)) 73 | { 74 | tokens.add(arg); 75 | } 76 | else 77 | { 78 | if (opt.indexOf('=') != -1 && options.hasOption(opt.substring(0, opt.indexOf('=')))) 79 | { 80 | // the format is --foo=value or -foo=value 81 | tokens.add(arg.substring(0, arg.indexOf('='))); // --foo 82 | tokens.add(arg.substring(arg.indexOf('=') + 1)); // value 83 | } 84 | else if (options.hasOption(arg.substring(0, 2))) 85 | { 86 | // the format is a special properties option (-Dproperty=value) 87 | tokens.add(arg.substring(0, 2)); // -D 88 | tokens.add(arg.substring(2)); // property=value 89 | } 90 | else 91 | { 92 | eatTheRest = stopAtNonOption; 93 | tokens.add(arg); 94 | } 95 | } 96 | } 97 | else 98 | { 99 | tokens.add(arg); 100 | } 101 | 102 | if (eatTheRest) 103 | { 104 | for (i++; i < arguments.length; i++) 105 | { 106 | tokens.add(arguments[i]); 107 | } 108 | } 109 | } 110 | 111 | return (String[]) tokens.toArray(new String[tokens.size()]); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/MissingArgumentException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * Thrown when an option requiring an argument 22 | * is not provided with an argument. 23 | * 24 | * @author John Keyes (john at integralsource.com) 25 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 26 | */ 27 | public class MissingArgumentException extends ParseException 28 | { 29 | /** The option requiring additional arguments */ 30 | private Option option; 31 | 32 | /** 33 | * Construct a new MissingArgumentException 34 | * with the specified detail message. 35 | * 36 | * @param message the detail message 37 | */ 38 | public MissingArgumentException(String message) 39 | { 40 | super(message); 41 | } 42 | 43 | /** 44 | * Construct a new MissingArgumentException 45 | * with the specified detail message. 46 | * 47 | * @param option the option requiring an argument 48 | * @since 1.2 49 | */ 50 | public MissingArgumentException(Option option) 51 | { 52 | this("Missing argument for option: " + option.getKey()); 53 | this.option = option; 54 | } 55 | 56 | /** 57 | * Return the option requiring an argument that wasn't provided 58 | * on the command line. 59 | * 60 | * @return the related option 61 | * @since 1.2 62 | */ 63 | public Option getOption() 64 | { 65 | return option; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/MissingOptionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | import java.util.List; 21 | import java.util.Iterator; 22 | 23 | /** 24 | * Thrown when a required option has not been provided. 25 | * 26 | * @author John Keyes ( john at integralsource.com ) 27 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 28 | */ 29 | public class MissingOptionException extends ParseException 30 | { 31 | /** The list of missing options */ 32 | private List missingOptions; 33 | 34 | /** 35 | * Construct a new MissingSelectedException 36 | * with the specified detail message. 37 | * 38 | * @param message the detail message 39 | */ 40 | public MissingOptionException(String message) 41 | { 42 | super(message); 43 | } 44 | 45 | /** 46 | * Constructs a new MissingSelectedException with the 47 | * specified list of missing options. 48 | * 49 | * @param missingOptions the list of missing options 50 | * @since 1.2 51 | */ 52 | public MissingOptionException(List missingOptions) 53 | { 54 | this(createMessage(missingOptions)); 55 | this.missingOptions = missingOptions; 56 | } 57 | 58 | /** 59 | * Return the list of options (as strings) missing in the command line parsed. 60 | * 61 | * @return the missing options 62 | * @since 1.2 63 | */ 64 | public List getMissingOptions() 65 | { 66 | return missingOptions; 67 | } 68 | 69 | /** 70 | * Build the exception message from the specified list of options. 71 | * 72 | * @param missingOptions 73 | * @since 1.2 74 | */ 75 | private static String createMessage(List missingOptions) 76 | { 77 | StringBuffer buff = new StringBuffer("Missing required option"); 78 | buff.append(missingOptions.size() == 1 ? "" : "s"); 79 | buff.append(": "); 80 | 81 | Iterator it = missingOptions.iterator(); 82 | while (it.hasNext()) 83 | { 84 | buff.append(it.next()); 85 | if (it.hasNext()) 86 | { 87 | buff.append(", "); 88 | } 89 | } 90 | 91 | return buff.toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/OptionGroup.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | import java.io.Serializable; 21 | import java.util.Collection; 22 | import java.util.HashMap; 23 | import java.util.Iterator; 24 | import java.util.Map; 25 | 26 | /** 27 | * A group of mutually exclusive options. 28 | * 29 | * @author John Keyes ( john at integralsource.com ) 30 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 31 | */ 32 | public class OptionGroup implements Serializable 33 | { 34 | private static final long serialVersionUID = 1L; 35 | 36 | /** hold the options */ 37 | private Map optionMap = new HashMap(); 38 | 39 | /** the name of the selected option */ 40 | private String selected; 41 | 42 | /** specified whether this group is required */ 43 | private boolean required; 44 | 45 | /** 46 | * Add the specified Option to this group. 47 | * 48 | * @param option the option to add to this group 49 | * @return this option group with the option added 50 | */ 51 | public OptionGroup addOption(Option option) 52 | { 53 | // key - option name 54 | // value - the option 55 | optionMap.put(option.getKey(), option); 56 | 57 | return this; 58 | } 59 | 60 | /** 61 | * @return the names of the options in this group as a 62 | * Collection 63 | */ 64 | public Collection getNames() 65 | { 66 | // the key set is the collection of names 67 | return optionMap.keySet(); 68 | } 69 | 70 | /** 71 | * @return the options in this group as a Collection 72 | */ 73 | public Collection getOptions() 74 | { 75 | // the values are the collection of options 76 | return optionMap.values(); 77 | } 78 | 79 | /** 80 | * Set the selected option of this group to name. 81 | * 82 | * @param option the option that is selected 83 | * @throws AlreadySelectedException if an option from this group has 84 | * already been selected. 85 | */ 86 | public void setSelected(Option option) throws AlreadySelectedException 87 | { 88 | // if no option has already been selected or the 89 | // same option is being reselected then set the 90 | // selected member variable 91 | if (selected == null || selected.equals(option.getOpt())) 92 | { 93 | selected = option.getOpt(); 94 | } 95 | else 96 | { 97 | throw new AlreadySelectedException(this, option); 98 | } 99 | } 100 | 101 | /** 102 | * @return the selected option name 103 | */ 104 | public String getSelected() 105 | { 106 | return selected; 107 | } 108 | 109 | /** 110 | * @param required specifies if this group is required 111 | */ 112 | public void setRequired(boolean required) 113 | { 114 | this.required = required; 115 | } 116 | 117 | /** 118 | * Returns whether this option group is required. 119 | * 120 | * @return whether this option group is required 121 | */ 122 | public boolean isRequired() 123 | { 124 | return required; 125 | } 126 | 127 | /** 128 | * Returns the stringified version of this OptionGroup. 129 | * 130 | * @return the stringified representation of this group 131 | */ 132 | public String toString() 133 | { 134 | StringBuffer buff = new StringBuffer(); 135 | 136 | Iterator iter = getOptions().iterator(); 137 | 138 | buff.append("["); 139 | 140 | while (iter.hasNext()) 141 | { 142 | Option option = (Option) iter.next(); 143 | 144 | if (option.getOpt() != null) 145 | { 146 | buff.append("-"); 147 | buff.append(option.getOpt()); 148 | } 149 | else 150 | { 151 | buff.append("--"); 152 | buff.append(option.getLongOpt()); 153 | } 154 | 155 | buff.append(" "); 156 | buff.append(option.getDescription()); 157 | 158 | if (iter.hasNext()) 159 | { 160 | buff.append(", "); 161 | } 162 | } 163 | 164 | buff.append("]"); 165 | 166 | return buff.toString(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/OptionValidator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * Validates an Option string. 22 | * 23 | * @author John Keyes ( john at integralsource.com ) 24 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 25 | * @since 1.1 26 | */ 27 | class OptionValidator 28 | { 29 | /** 30 | * Validates whether opt is a permissable Option 31 | * shortOpt. The rules that specify if the opt 32 | * is valid are: 33 | * 34 | *
    35 | *
  • opt is not NULL
  • 36 | *
  • a single character opt that is either 37 | * ' '(special case), '?', '@' or a letter
  • 38 | *
  • a multi character opt that only contains 39 | * letters.
  • 40 | *
41 | * 42 | * @param opt The option string to validate 43 | * @throws IllegalArgumentException if the Option is not valid. 44 | */ 45 | static void validateOption(String opt) throws IllegalArgumentException 46 | { 47 | // check that opt is not NULL 48 | if (opt == null) 49 | { 50 | return; 51 | } 52 | 53 | // handle the single character opt 54 | else if (opt.length() == 1) 55 | { 56 | char ch = opt.charAt(0); 57 | 58 | if (!isValidOpt(ch)) 59 | { 60 | throw new IllegalArgumentException("illegal option value '" + ch + "'"); 61 | } 62 | } 63 | 64 | // handle the multi character opt 65 | else 66 | { 67 | char[] chars = opt.toCharArray(); 68 | 69 | for (int i = 0; i < chars.length; i++) 70 | { 71 | if (!isValidChar(chars[i])) 72 | { 73 | throw new IllegalArgumentException("opt contains illegal character value '" + chars[i] + "'"); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Returns whether the specified character is a valid Option. 81 | * 82 | * @param c the option to validate 83 | * @return true if c is a letter, ' ', '?' or '@', 84 | * otherwise false. 85 | */ 86 | private static boolean isValidOpt(char c) 87 | { 88 | return isValidChar(c) || c == ' ' || c == '?' || c == '@'; 89 | } 90 | 91 | /** 92 | * Returns whether the specified character is a valid character. 93 | * 94 | * @param c the character to validate 95 | * @return true if c is a letter. 96 | */ 97 | private static boolean isValidChar(char c) 98 | { 99 | return Character.isJavaIdentifierPart(c); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/ParseException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * Base for Exceptions thrown during parsing of a command-line. 22 | * 23 | * @author bob mcwhirter (bob @ werken.com) 24 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 25 | */ 26 | public class ParseException extends Exception 27 | { 28 | /** 29 | * Construct a new ParseException 30 | * with the specified detail message. 31 | * 32 | * @param message the detail message 33 | */ 34 | public ParseException(String message) 35 | { 36 | super(message); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/PatternOptionBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.net.URL; 23 | import java.util.Date; 24 | 25 | /** 26 | *

27 | * Allows Options to be created from a single String. 28 | * The pattern contains various single character flags and via 29 | * an optional punctuation character, their expected type. 30 | *

31 | * 32 | * 33 | * 34 | * 35 | * 36 | * 37 | * 38 | * 39 | * 40 | *
a-a flag
b@-b [classname]
c>-c [filename]
d+-d [classname] (creates object via empty contructor)
e%-e [number] (creates Double/Long instance depeding on existing of a '.')
f/-f [url]
g:-g [string]
41 | * 42 | *

43 | * For example, the following allows command line flags of '-v -p string-value -f /dir/file'. 44 | * The exclamation mark precede a mandatory option. 45 | *

46 | * Options options = PatternOptionBuilder.parsePattern("vp:!f/"); 47 | * 48 | *

49 | * TODO These need to break out to OptionType and also 50 | * to be pluggable. 51 | *

52 | * 53 | * @version $Revision: 734339 $, $Date: 2009-01-13 21:56:47 -0800 (Tue, 13 Jan 2009) $ 54 | */ 55 | public class PatternOptionBuilder 56 | { 57 | /** String class */ 58 | public static final Class STRING_VALUE = String.class; 59 | 60 | /** Object class */ 61 | public static final Class OBJECT_VALUE = Object.class; 62 | 63 | /** Number class */ 64 | public static final Class NUMBER_VALUE = Number.class; 65 | 66 | /** Date class */ 67 | public static final Class DATE_VALUE = Date.class; 68 | 69 | /** Class class */ 70 | public static final Class CLASS_VALUE = Class.class; 71 | 72 | /// can we do this one?? 73 | // is meant to check that the file exists, else it errors. 74 | // ie) it's for reading not writing. 75 | 76 | /** FileInputStream class */ 77 | public static final Class EXISTING_FILE_VALUE = FileInputStream.class; 78 | 79 | /** File class */ 80 | public static final Class FILE_VALUE = File.class; 81 | 82 | /** File array class */ 83 | public static final Class FILES_VALUE = File[].class; 84 | 85 | /** URL class */ 86 | public static final Class URL_VALUE = URL.class; 87 | 88 | /** 89 | * Retrieve the class that ch represents. 90 | * 91 | * @param ch the specified character 92 | * @return The class that ch represents 93 | */ 94 | public static Object getValueClass(char ch) 95 | { 96 | switch (ch) 97 | { 98 | case '@': 99 | return PatternOptionBuilder.OBJECT_VALUE; 100 | case ':': 101 | return PatternOptionBuilder.STRING_VALUE; 102 | case '%': 103 | return PatternOptionBuilder.NUMBER_VALUE; 104 | case '+': 105 | return PatternOptionBuilder.CLASS_VALUE; 106 | case '#': 107 | return PatternOptionBuilder.DATE_VALUE; 108 | case '<': 109 | return PatternOptionBuilder.EXISTING_FILE_VALUE; 110 | case '>': 111 | return PatternOptionBuilder.FILE_VALUE; 112 | case '*': 113 | return PatternOptionBuilder.FILES_VALUE; 114 | case '/': 115 | return PatternOptionBuilder.URL_VALUE; 116 | } 117 | 118 | return null; 119 | } 120 | 121 | /** 122 | * Returns whether ch is a value code, i.e. 123 | * whether it represents a class in a pattern. 124 | * 125 | * @param ch the specified character 126 | * @return true if ch is a value code, otherwise false. 127 | */ 128 | public static boolean isValueCode(char ch) 129 | { 130 | return ch == '@' 131 | || ch == ':' 132 | || ch == '%' 133 | || ch == '+' 134 | || ch == '#' 135 | || ch == '<' 136 | || ch == '>' 137 | || ch == '*' 138 | || ch == '/' 139 | || ch == '!'; 140 | } 141 | 142 | /** 143 | * Returns the {@link Options} instance represented by pattern. 144 | * 145 | * @param pattern the pattern string 146 | * @return The {@link Options} instance 147 | */ 148 | public static Options parsePattern(String pattern) 149 | { 150 | char opt = ' '; 151 | boolean required = false; 152 | Object type = null; 153 | 154 | Options options = new Options(); 155 | 156 | for (int i = 0; i < pattern.length(); i++) 157 | { 158 | char ch = pattern.charAt(i); 159 | 160 | // a value code comes after an option and specifies 161 | // details about it 162 | if (!isValueCode(ch)) 163 | { 164 | if (opt != ' ') 165 | { 166 | OptionBuilder.hasArg(type != null); 167 | OptionBuilder.isRequired(required); 168 | OptionBuilder.withType(type); 169 | 170 | // we have a previous one to deal with 171 | options.addOption(OptionBuilder.create(opt)); 172 | required = false; 173 | type = null; 174 | opt = ' '; 175 | } 176 | 177 | opt = ch; 178 | } 179 | else if (ch == '!') 180 | { 181 | required = true; 182 | } 183 | else 184 | { 185 | type = getValueClass(ch); 186 | } 187 | } 188 | 189 | if (opt != ' ') 190 | { 191 | OptionBuilder.hasArg(type != null); 192 | OptionBuilder.isRequired(required); 193 | OptionBuilder.withType(type); 194 | 195 | // we have a final one to deal with 196 | options.addOption(OptionBuilder.create(opt)); 197 | } 198 | 199 | return options; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/TypeHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | import java.io.File; 21 | 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | 25 | import java.util.Date; 26 | 27 | /** 28 | * This is a temporary implementation. TypeHandler will handle the 29 | * pluggableness of OptionTypes and it will direct all of these types 30 | * of conversion functionalities to ConvertUtils component in Commons 31 | * already. BeanUtils I think. 32 | * 33 | * @version $Revision: 741425 $, $Date: 2009-02-05 22:10:54 -0800 (Thu, 05 Feb 2009) $ 34 | */ 35 | public class TypeHandler 36 | { 37 | /** 38 | * Returns the Object of type obj 39 | * with the value of str. 40 | * 41 | * @param str the command line value 42 | * @param obj the type of argument 43 | * @return The instance of obj initialised with 44 | * the value of str. 45 | */ 46 | public static Object createValue(String str, Object obj) 47 | throws ParseException 48 | { 49 | return createValue(str, (Class) obj); 50 | } 51 | 52 | /** 53 | * Returns the Object of type clazz 54 | * with the value of str. 55 | * 56 | * @param str the command line value 57 | * @param clazz the type of argument 58 | * @return The instance of clazz initialised with 59 | * the value of str. 60 | */ 61 | public static Object createValue(String str, Class clazz) 62 | throws ParseException 63 | { 64 | if (PatternOptionBuilder.STRING_VALUE == clazz) 65 | { 66 | return str; 67 | } 68 | else if (PatternOptionBuilder.OBJECT_VALUE == clazz) 69 | { 70 | return createObject(str); 71 | } 72 | else if (PatternOptionBuilder.NUMBER_VALUE == clazz) 73 | { 74 | return createNumber(str); 75 | } 76 | else if (PatternOptionBuilder.DATE_VALUE == clazz) 77 | { 78 | return createDate(str); 79 | } 80 | else if (PatternOptionBuilder.CLASS_VALUE == clazz) 81 | { 82 | return createClass(str); 83 | } 84 | else if (PatternOptionBuilder.FILE_VALUE == clazz) 85 | { 86 | return createFile(str); 87 | } 88 | else if (PatternOptionBuilder.EXISTING_FILE_VALUE == clazz) 89 | { 90 | return createFile(str); 91 | } 92 | else if (PatternOptionBuilder.FILES_VALUE == clazz) 93 | { 94 | return createFiles(str); 95 | } 96 | else if (PatternOptionBuilder.URL_VALUE == clazz) 97 | { 98 | return createURL(str); 99 | } 100 | else 101 | { 102 | return null; 103 | } 104 | } 105 | 106 | /** 107 | * Create an Object from the classname and empty constructor. 108 | * 109 | * @param classname the argument value 110 | * @return the initialised object, or null if it couldn't create 111 | * the Object. 112 | */ 113 | public static Object createObject(String classname) 114 | throws ParseException 115 | { 116 | Class cl = null; 117 | 118 | try 119 | { 120 | cl = Class.forName(classname); 121 | } 122 | catch (ClassNotFoundException cnfe) 123 | { 124 | throw new ParseException("Unable to find the class: " + classname); 125 | } 126 | 127 | Object instance = null; 128 | 129 | try 130 | { 131 | instance = cl.newInstance(); 132 | } 133 | catch (Exception e) 134 | { 135 | throw new ParseException(e.getClass().getName() + "; Unable to create an instance of: " + classname); 136 | } 137 | 138 | return instance; 139 | } 140 | 141 | /** 142 | * Create a number from a String. If a . is present, it creates a 143 | * Double, otherwise a Long. 144 | * 145 | * @param str the value 146 | * @return the number represented by str, if str 147 | * is not a number, null is returned. 148 | */ 149 | public static Number createNumber(String str) 150 | throws ParseException 151 | { 152 | try 153 | { 154 | if (str.indexOf('.') != -1) 155 | { 156 | return Double.valueOf(str); 157 | } 158 | else 159 | { 160 | return Long.valueOf(str); 161 | } 162 | } 163 | catch (NumberFormatException e) 164 | { 165 | throw new ParseException(e.getMessage()); 166 | } 167 | } 168 | 169 | /** 170 | * Returns the class whose name is classname. 171 | * 172 | * @param classname the class name 173 | * @return The class if it is found, otherwise return null 174 | */ 175 | public static Class createClass(String classname) 176 | throws ParseException 177 | { 178 | try 179 | { 180 | return Class.forName(classname); 181 | } 182 | catch (ClassNotFoundException e) 183 | { 184 | throw new ParseException("Unable to find the class: " + classname); 185 | } 186 | } 187 | 188 | /** 189 | * Returns the date represented by str. 190 | * 191 | * @param str the date string 192 | * @return The date if str is a valid date string, 193 | * otherwise return null. 194 | */ 195 | public static Date createDate(String str) 196 | throws ParseException 197 | { 198 | throw new UnsupportedOperationException("Not yet implemented"); 199 | } 200 | 201 | /** 202 | * Returns the URL represented by str. 203 | * 204 | * @param str the URL string 205 | * @return The URL is str is well-formed, otherwise 206 | * return null. 207 | */ 208 | public static URL createURL(String str) 209 | throws ParseException 210 | { 211 | try 212 | { 213 | return new URL(str); 214 | } 215 | catch (MalformedURLException e) 216 | { 217 | throw new ParseException("Unable to parse the URL: " + str); 218 | } 219 | } 220 | 221 | /** 222 | * Returns the File represented by str. 223 | * 224 | * @param str the File location 225 | * @return The file represented by str. 226 | */ 227 | public static File createFile(String str) 228 | throws ParseException 229 | { 230 | return new File(str); 231 | } 232 | 233 | /** 234 | * Returns the File[] represented by str. 235 | * 236 | * @param str the paths to the files 237 | * @return The File[] represented by str. 238 | */ 239 | public static File[] createFiles(String str) 240 | throws ParseException 241 | { 242 | // to implement/port: 243 | // return FileW.findFiles(str); 244 | throw new UnsupportedOperationException("Not yet implemented"); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/UnrecognizedOptionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * Exception thrown during parsing signalling an unrecognized 22 | * option was seen. 23 | * 24 | * @author bob mcwhiter (bob @ werken.com) 25 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 26 | */ 27 | public class UnrecognizedOptionException extends ParseException 28 | { 29 | /** The unrecognized option */ 30 | private String option; 31 | 32 | /** 33 | * Construct a new UnrecognizedArgumentException 34 | * with the specified detail message. 35 | * 36 | * @param message the detail message 37 | */ 38 | public UnrecognizedOptionException(String message) 39 | { 40 | super(message); 41 | } 42 | 43 | /** 44 | * Construct a new UnrecognizedArgumentException 45 | * with the specified option and detail message. 46 | * 47 | * @param message the detail message 48 | * @param option the unrecognized option 49 | * @since 1.2 50 | */ 51 | public UnrecognizedOptionException(String message, String option) 52 | { 53 | this(message); 54 | this.option = option; 55 | } 56 | 57 | /** 58 | * Returns the unrecognized option. 59 | * 60 | * @return the related option 61 | * @since 1.2 62 | */ 63 | public String getOption() 64 | { 65 | return option; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/Util.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.apache.commons.cli; 19 | 20 | /** 21 | * Contains useful helper methods for classes within this package. 22 | * 23 | * @author John Keyes (john at integralsource.com) 24 | * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $ 25 | */ 26 | class Util 27 | { 28 | /** 29 | * Remove the hyphens from the begining of str and 30 | * return the new String. 31 | * 32 | * @param str The string from which the hyphens should be removed. 33 | * 34 | * @return the new String. 35 | */ 36 | static String stripLeadingHyphens(String str) 37 | { 38 | if (str == null) 39 | { 40 | return null; 41 | } 42 | if (str.startsWith("--")) 43 | { 44 | return str.substring(2, str.length()); 45 | } 46 | else if (str.startsWith("-")) 47 | { 48 | return str.substring(1, str.length()); 49 | } 50 | 51 | return str; 52 | } 53 | 54 | /** 55 | * Remove the leading and trailing quotes from str. 56 | * E.g. if str is '"one two"', then 'one two' is returned. 57 | * 58 | * @param str The string from which the leading and trailing quotes 59 | * should be removed. 60 | * 61 | * @return The string without the leading and trailing quotes. 62 | */ 63 | static String stripLeadingAndTrailingQuotes(String str) 64 | { 65 | if (str.startsWith("\"")) 66 | { 67 | str = str.substring(1, str.length()); 68 | } 69 | if (str.endsWith("\"")) 70 | { 71 | str = str.substring(0, str.length() - 1); 72 | } 73 | return str; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/overview.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 |

Commons CLI -- version 1.2

20 | 21 |

The commons-cli package aides in parsing command-line arguments.

22 | 23 |

Allow command-line arguments to be parsed against a descriptor of 24 | valid options (long and short), potentially with arguments.

25 | 26 |

command-line arguments may be of the typical String[] 27 | form, but also may be a java.util.List. Indexes allow 28 | for parsing only a portion of the command-line. Also, functionality 29 | for parsing the command-line in phases is built in, allowing for 30 | 'cvs-style' command-lines, where some global options are specified 31 | before a 'command' argument, and command-specific options are 32 | specified after the command argument: 33 | 34 | 35 |

36 |         myApp -p <port> command -p <printer>
37 |     
38 | 39 | 40 | 41 |

The homepage for the project is 42 | Apache Commons/ 43 | 44 | -------------------------------------------------------------------------------- /src/org/apache/commons/cli/package.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | Commons CLI 1.2 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/org/json/Cookie.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Convert a web browser cookie specification to a JSONObject and back. 29 | * JSON and Cookies are both notations for name/value pairs. 30 | * @author JSON.org 31 | * @version 2010-12-24 32 | */ 33 | public class Cookie { 34 | 35 | /** 36 | * Produce a copy of a string in which the characters '+', '%', '=', ';' 37 | * and control characters are replaced with "%hh". This is a gentle form 38 | * of URL encoding, attempting to cause as little distortion to the 39 | * string as possible. The characters '=' and ';' are meta characters in 40 | * cookies. By convention, they are escaped using the URL-encoding. This is 41 | * only a convention, not a standard. Often, cookies are expected to have 42 | * encoded values. We encode '=' and ';' because we must. We encode '%' and 43 | * '+' because they are meta characters in URL encoding. 44 | * @param string The source string. 45 | * @return The escaped result. 46 | */ 47 | public static String escape(String string) { 48 | char c; 49 | String s = string.trim(); 50 | StringBuffer sb = new StringBuffer(); 51 | int length = s.length(); 52 | for (int i = 0; i < length; i += 1) { 53 | c = s.charAt(i); 54 | if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { 55 | sb.append('%'); 56 | sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); 57 | sb.append(Character.forDigit((char)(c & 0x0f), 16)); 58 | } else { 59 | sb.append(c); 60 | } 61 | } 62 | return sb.toString(); 63 | } 64 | 65 | 66 | /** 67 | * Convert a cookie specification string into a JSONObject. The string 68 | * will contain a name value pair separated by '='. The name and the value 69 | * will be unescaped, possibly converting '+' and '%' sequences. The 70 | * cookie properties may follow, separated by ';', also represented as 71 | * name=value (except the secure property, which does not have a value). 72 | * The name will be stored under the key "name", and the value will be 73 | * stored under the key "value". This method does not do checking or 74 | * validation of the parameters. It only converts the cookie string into 75 | * a JSONObject. 76 | * @param string The cookie specification string. 77 | * @return A JSONObject containing "name", "value", and possibly other 78 | * members. 79 | * @throws JSONException 80 | */ 81 | public static JSONObject toJSONObject(String string) throws JSONException { 82 | String name; 83 | JSONObject jo = new JSONObject(); 84 | Object value; 85 | JSONTokener x = new JSONTokener(string); 86 | jo.put("name", x.nextTo('=')); 87 | x.next('='); 88 | jo.put("value", x.nextTo(';')); 89 | x.next(); 90 | while (x.more()) { 91 | name = unescape(x.nextTo("=;")); 92 | if (x.next() != '=') { 93 | if (name.equals("secure")) { 94 | value = Boolean.TRUE; 95 | } else { 96 | throw x.syntaxError("Missing '=' in cookie parameter."); 97 | } 98 | } else { 99 | value = unescape(x.nextTo(';')); 100 | x.next(); 101 | } 102 | jo.put(name, value); 103 | } 104 | return jo; 105 | } 106 | 107 | 108 | /** 109 | * Convert a JSONObject into a cookie specification string. The JSONObject 110 | * must contain "name" and "value" members. 111 | * If the JSONObject contains "expires", "domain", "path", or "secure" 112 | * members, they will be appended to the cookie specification string. 113 | * All other members are ignored. 114 | * @param jo A JSONObject 115 | * @return A cookie specification string 116 | * @throws JSONException 117 | */ 118 | public static String toString(JSONObject jo) throws JSONException { 119 | StringBuffer sb = new StringBuffer(); 120 | 121 | sb.append(escape(jo.getString("name"))); 122 | sb.append("="); 123 | sb.append(escape(jo.getString("value"))); 124 | if (jo.has("expires")) { 125 | sb.append(";expires="); 126 | sb.append(jo.getString("expires")); 127 | } 128 | if (jo.has("domain")) { 129 | sb.append(";domain="); 130 | sb.append(escape(jo.getString("domain"))); 131 | } 132 | if (jo.has("path")) { 133 | sb.append(";path="); 134 | sb.append(escape(jo.getString("path"))); 135 | } 136 | if (jo.optBoolean("secure")) { 137 | sb.append(";secure"); 138 | } 139 | return sb.toString(); 140 | } 141 | 142 | /** 143 | * Convert %hh sequences to single characters, and 144 | * convert plus to space. 145 | * @param string A string that may contain 146 | * + (plus) and 147 | * %hh sequences. 148 | * @return The unescaped string. 149 | */ 150 | public static String unescape(String string) { 151 | int length = string.length(); 152 | StringBuffer sb = new StringBuffer(); 153 | for (int i = 0; i < length; ++i) { 154 | char c = string.charAt(i); 155 | if (c == '+') { 156 | c = ' '; 157 | } else if (c == '%' && i + 2 < length) { 158 | int d = JSONTokener.dehexchar(string.charAt(i + 1)); 159 | int e = JSONTokener.dehexchar(string.charAt(i + 2)); 160 | if (d >= 0 && e >= 0) { 161 | c = (char)(d * 16 + e); 162 | i += 2; 163 | } 164 | } 165 | sb.append(c); 166 | } 167 | return sb.toString(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/org/json/CookieList.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Convert a web browser cookie list string to a JSONObject and back. 31 | * @author JSON.org 32 | * @version 2010-12-24 33 | */ 34 | public class CookieList { 35 | 36 | /** 37 | * Convert a cookie list into a JSONObject. A cookie list is a sequence 38 | * of name/value pairs. The names are separated from the values by '='. 39 | * The pairs are separated by ';'. The names and the values 40 | * will be unescaped, possibly converting '+' and '%' sequences. 41 | * 42 | * To add a cookie to a cooklist, 43 | * cookielistJSONObject.put(cookieJSONObject.getString("name"), 44 | * cookieJSONObject.getString("value")); 45 | * @param string A cookie list string 46 | * @return A JSONObject 47 | * @throws JSONException 48 | */ 49 | public static JSONObject toJSONObject(String string) throws JSONException { 50 | JSONObject jo = new JSONObject(); 51 | JSONTokener x = new JSONTokener(string); 52 | while (x.more()) { 53 | String name = Cookie.unescape(x.nextTo('=')); 54 | x.next('='); 55 | jo.put(name, Cookie.unescape(x.nextTo(';'))); 56 | x.next(); 57 | } 58 | return jo; 59 | } 60 | 61 | 62 | /** 63 | * Convert a JSONObject into a cookie list. A cookie list is a sequence 64 | * of name/value pairs. The names are separated from the values by '='. 65 | * The pairs are separated by ';'. The characters '%', '+', '=', and ';' 66 | * in the names and values are replaced by "%hh". 67 | * @param jo A JSONObject 68 | * @return A cookie list string 69 | * @throws JSONException 70 | */ 71 | public static String toString(JSONObject jo) throws JSONException { 72 | boolean b = false; 73 | Iterator keys = jo.keys(); 74 | String string; 75 | StringBuffer sb = new StringBuffer(); 76 | while (keys.hasNext()) { 77 | string = keys.next().toString(); 78 | if (!jo.isNull(string)) { 79 | if (b) { 80 | sb.append(';'); 81 | } 82 | sb.append(Cookie.escape(string)); 83 | sb.append("="); 84 | sb.append(Cookie.escape(jo.getString(string))); 85 | b = true; 86 | } 87 | } 88 | return sb.toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/org/json/HTTP.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Iterator; 28 | 29 | /** 30 | * Convert an HTTP header to a JSONObject and back. 31 | * @author JSON.org 32 | * @version 2010-12-24 33 | */ 34 | public class HTTP { 35 | 36 | /** Carriage return/line feed. */ 37 | public static final String CRLF = "\r\n"; 38 | 39 | /** 40 | * Convert an HTTP header string into a JSONObject. It can be a request 41 | * header or a response header. A request header will contain 42 | *

{
 43 |      *    Method: "POST" (for example),
 44 |      *    "Request-URI": "/" (for example),
 45 |      *    "HTTP-Version": "HTTP/1.1" (for example)
 46 |      * }
47 | * A response header will contain 48 | *
{
 49 |      *    "HTTP-Version": "HTTP/1.1" (for example),
 50 |      *    "Status-Code": "200" (for example),
 51 |      *    "Reason-Phrase": "OK" (for example)
 52 |      * }
53 | * In addition, the other parameters in the header will be captured, using 54 | * the HTTP field names as JSON names, so that
 55 |      *    Date: Sun, 26 May 2002 18:06:04 GMT
 56 |      *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
 57 |      *    Cache-Control: no-cache
58 | * become 59 | *
{...
 60 |      *    Date: "Sun, 26 May 2002 18:06:04 GMT",
 61 |      *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
 62 |      *    "Cache-Control": "no-cache",
 63 |      * ...}
64 | * It does no further checking or conversion. It does not parse dates. 65 | * It does not do '%' transforms on URLs. 66 | * @param string An HTTP header string. 67 | * @return A JSONObject containing the elements and attributes 68 | * of the XML string. 69 | * @throws JSONException 70 | */ 71 | public static JSONObject toJSONObject(String string) throws JSONException { 72 | JSONObject jo = new JSONObject(); 73 | HTTPTokener x = new HTTPTokener(string); 74 | String token; 75 | 76 | token = x.nextToken(); 77 | if (token.toUpperCase().startsWith("HTTP")) { 78 | 79 | // Response 80 | 81 | jo.put("HTTP-Version", token); 82 | jo.put("Status-Code", x.nextToken()); 83 | jo.put("Reason-Phrase", x.nextTo('\0')); 84 | x.next(); 85 | 86 | } else { 87 | 88 | // Request 89 | 90 | jo.put("Method", token); 91 | jo.put("Request-URI", x.nextToken()); 92 | jo.put("HTTP-Version", x.nextToken()); 93 | } 94 | 95 | // Fields 96 | 97 | while (x.more()) { 98 | String name = x.nextTo(':'); 99 | x.next(':'); 100 | jo.put(name, x.nextTo('\0')); 101 | x.next(); 102 | } 103 | return jo; 104 | } 105 | 106 | 107 | /** 108 | * Convert a JSONObject into an HTTP header. A request header must contain 109 | *
{
110 |      *    Method: "POST" (for example),
111 |      *    "Request-URI": "/" (for example),
112 |      *    "HTTP-Version": "HTTP/1.1" (for example)
113 |      * }
114 | * A response header must contain 115 | *
{
116 |      *    "HTTP-Version": "HTTP/1.1" (for example),
117 |      *    "Status-Code": "200" (for example),
118 |      *    "Reason-Phrase": "OK" (for example)
119 |      * }
120 | * Any other members of the JSONObject will be output as HTTP fields. 121 | * The result will end with two CRLF pairs. 122 | * @param jo A JSONObject 123 | * @return An HTTP header string. 124 | * @throws JSONException if the object does not contain enough 125 | * information. 126 | */ 127 | public static String toString(JSONObject jo) throws JSONException { 128 | Iterator keys = jo.keys(); 129 | String string; 130 | StringBuffer sb = new StringBuffer(); 131 | if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { 132 | sb.append(jo.getString("HTTP-Version")); 133 | sb.append(' '); 134 | sb.append(jo.getString("Status-Code")); 135 | sb.append(' '); 136 | sb.append(jo.getString("Reason-Phrase")); 137 | } else if (jo.has("Method") && jo.has("Request-URI")) { 138 | sb.append(jo.getString("Method")); 139 | sb.append(' '); 140 | sb.append('"'); 141 | sb.append(jo.getString("Request-URI")); 142 | sb.append('"'); 143 | sb.append(' '); 144 | sb.append(jo.getString("HTTP-Version")); 145 | } else { 146 | throw new JSONException("Not enough material for an HTTP header."); 147 | } 148 | sb.append(CRLF); 149 | while (keys.hasNext()) { 150 | string = keys.next().toString(); 151 | if (!string.equals("HTTP-Version") && !string.equals("Status-Code") && 152 | !string.equals("Reason-Phrase") && !string.equals("Method") && 153 | !string.equals("Request-URI") && !jo.isNull(string)) { 154 | sb.append(string); 155 | sb.append(": "); 156 | sb.append(jo.getString(string)); 157 | sb.append(CRLF); 158 | } 159 | } 160 | sb.append(CRLF); 161 | return sb.toString(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/org/json/HTTPTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The HTTPTokener extends the JSONTokener to provide additional methods 29 | * for the parsing of HTTP headers. 30 | * @author JSON.org 31 | * @version 2010-12-24 32 | */ 33 | public class HTTPTokener extends JSONTokener { 34 | 35 | /** 36 | * Construct an HTTPTokener from a string. 37 | * @param string A source string. 38 | */ 39 | public HTTPTokener(String string) { 40 | super(string); 41 | } 42 | 43 | 44 | /** 45 | * Get the next token or string. This is used in parsing HTTP headers. 46 | * @throws JSONException 47 | * @return A String. 48 | */ 49 | public String nextToken() throws JSONException { 50 | char c; 51 | char q; 52 | StringBuffer sb = new StringBuffer(); 53 | do { 54 | c = next(); 55 | } while (Character.isWhitespace(c)); 56 | if (c == '"' || c == '\'') { 57 | q = c; 58 | for (;;) { 59 | c = next(); 60 | if (c < ' ') { 61 | throw syntaxError("Unterminated string."); 62 | } 63 | if (c == q) { 64 | return sb.toString(); 65 | } 66 | sb.append(c); 67 | } 68 | } 69 | for (;;) { 70 | if (c == 0 || Character.isWhitespace(c)) { 71 | return sb.toString(); 72 | } 73 | sb.append(c); 74 | c = next(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/org/json/JSONException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /** 4 | * The JSONException is thrown by the JSON.org classes when things are amiss. 5 | * @author JSON.org 6 | * @version 2010-12-24 7 | */ 8 | public class JSONException extends Exception { 9 | private static final long serialVersionUID = 0; 10 | private Throwable cause; 11 | 12 | /** 13 | * Constructs a JSONException with an explanatory message. 14 | * @param message Detail about the reason for the exception. 15 | */ 16 | public JSONException(String message) { 17 | super(message); 18 | } 19 | 20 | public JSONException(Throwable cause) { 21 | super(cause.getMessage()); 22 | this.cause = cause; 23 | } 24 | 25 | public Throwable getCause() { 26 | return this.cause; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/org/json/JSONString.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | /** 3 | * The JSONString interface allows a toJSONString() 4 | * method so that a class can change the behavior of 5 | * JSONObject.toString(), JSONArray.toString(), 6 | * and JSONWriter.value(Object). The 7 | * toJSONString method will be used instead of the default behavior 8 | * of using the Object's toString() method and quoting the result. 9 | */ 10 | public interface JSONString { 11 | /** 12 | * The toJSONString method allows a class to produce its own JSON 13 | * serialization. 14 | * 15 | * @return A strictly syntactically correct JSON text. 16 | */ 17 | public String toJSONString(); 18 | } 19 | -------------------------------------------------------------------------------- /src/org/json/JSONStringer.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2006 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.io.StringWriter; 28 | 29 | /** 30 | * JSONStringer provides a quick and convenient way of producing JSON text. 31 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 32 | * added, so the results are ready for transmission or storage. Each instance of 33 | * JSONStringer can produce one JSON text. 34 | *

35 | * A JSONStringer instance provides a value method for appending 36 | * values to the 37 | * text, and a key 38 | * method for adding keys before values in objects. There are array 39 | * and endArray methods that make and bound array values, and 40 | * object and endObject methods which make and bound 41 | * object values. All of these methods return the JSONWriter instance, 42 | * permitting cascade style. For example,

43 |  * myString = new JSONStringer()
44 |  *     .object()
45 |  *         .key("JSON")
46 |  *         .value("Hello, World!")
47 |  *     .endObject()
48 |  *     .toString();
which produces the string
49 |  * {"JSON":"Hello, World!"}
50 | *

51 | * The first method called must be array or object. 52 | * There are no methods for adding commas or colons. JSONStringer adds them for 53 | * you. Objects and arrays can be nested up to 20 levels deep. 54 | *

55 | * This can sometimes be easier than using a JSONObject to build a string. 56 | * @author JSON.org 57 | * @version 2008-09-18 58 | */ 59 | public class JSONStringer extends JSONWriter { 60 | /** 61 | * Make a fresh JSONStringer. It can be used to build one JSON text. 62 | */ 63 | public JSONStringer() { 64 | super(new StringWriter()); 65 | } 66 | 67 | /** 68 | * Return the JSON text. This method is used to obtain the product of the 69 | * JSONStringer instance. It will return null if there was a 70 | * problem in the construction of the JSON text (such as the calls to 71 | * array were not properly balanced with calls to 72 | * endArray). 73 | * @return The JSON text. 74 | */ 75 | public String toString() { 76 | return this.mode == 'd' ? this.writer.toString() : null; 77 | } 78 | } 79 | --------------------------------------------------------------------------------