├── README.md └── src ├── test ├── resources │ ├── Lena.png │ ├── Lenna.png │ ├── blue.png │ ├── brown.png │ ├── green.png │ ├── red.png │ ├── Lenna90.png │ ├── ballon.jpg │ ├── catMono.png │ ├── BlackWhite.png │ ├── Lenna180.png │ ├── Lenna270.png │ ├── TestShapes.png │ ├── catCustom.png │ ├── copyright.jpg │ ├── lowQuality.jpg │ ├── thumbnail.jpg │ ├── brownOpacity.png │ ├── highQuality.jpg │ ├── highQualityBase.png │ ├── highQualityDark.png │ ├── highQualityHue.png │ ├── highQualityBright.png │ └── LennaSaltAndPepper.png ├── java │ └── com │ │ ├── github │ │ └── kilianB │ │ │ ├── clustering │ │ │ ├── KMeansTest.java │ │ │ └── KMeansPlusPlusTest.java │ │ │ ├── MiscUtilTest.java │ │ │ ├── pcg │ │ │ ├── PcgTest.java │ │ │ ├── RandomBurdenTest.java │ │ │ ├── sync │ │ │ │ ├── PcgRRTest.java │ │ │ │ └── PcgRSTest.java │ │ │ ├── cas │ │ │ │ ├── PcgRSCasTest.java │ │ │ │ └── PcgRRCasTest.java │ │ │ ├── lock │ │ │ │ ├── PcgRRLockedTest.java │ │ │ │ └── PcgRSLockedTest.java │ │ │ ├── fast │ │ │ │ └── PcgRSFastTest.java │ │ │ └── AdvanceBackup.java │ │ │ ├── datastructures │ │ │ ├── ClusterResultTest.java │ │ │ ├── KMeansTest.java │ │ │ ├── tree │ │ │ │ ├── NodeInfoTest.java │ │ │ │ └── binaryTreeFuzzy │ │ │ │ │ └── FuzzyBinaryTreeTest.java │ │ │ └── CircularLinkedHashMapTest.java │ │ │ ├── matcher │ │ │ ├── categorize │ │ │ │ ├── CategoricalMatcherTest.java │ │ │ │ ├── KMeansClassifierTest.java │ │ │ │ ├── WeightedCategoricalMatcherTest.java │ │ │ │ └── CategorizeBaseTest.java │ │ │ └── SingleImageMatcherTest.java │ │ │ ├── CryptoUtilTest.java │ │ │ ├── StringUtilTest.java │ │ │ ├── concurrency │ │ │ └── NamedThreadFactoryTest.java │ │ │ ├── mutable │ │ │ ├── MutableBooleanTest.java │ │ │ ├── MutableLongTest.java │ │ │ └── MutableByteTest.java │ │ │ ├── TestResources.java │ │ │ └── NetworkUtilTest.java │ │ └── jstarcraft │ │ └── dip │ │ ├── color │ │ ├── ImageUtilTest.java │ │ └── ColorUtilTest.java │ │ └── lsh │ │ ├── RotAverageHashTest.java │ │ ├── RotationalTestBase.java │ │ ├── MedianHashTest.java │ │ ├── PerceptiveHashTest.java │ │ ├── GrayscaleHashTest.java │ │ ├── HogHashTest.java │ │ ├── kernel │ │ └── MaximumKernelTest.java │ │ ├── experimental │ │ ├── HogHashDualTest.java │ │ └── HogHashAngularEncodedTest.java │ │ ├── AverageHashTest.java │ │ └── RotPHashTest.java └── c++ │ └── UnsignedMath.cpp └── main ├── resources └── imageHashLogo.png └── java └── com ├── github └── kilianB │ ├── examples │ ├── images │ │ ├── ballon.jpg │ │ ├── copyright.jpg │ │ ├── lowQuality.jpg │ │ ├── thumbnail.jpg │ │ └── highQuality.jpg │ └── CompareImages.java │ ├── clustering │ ├── ClusterAlgorithm.java │ ├── distance │ │ ├── DistanceFunction.java │ │ ├── ManhattanDistance.java │ │ ├── EuclideanDistance.java │ │ └── JaccardIndex.java │ ├── KMeansPlusPlus.java │ └── DBScan.java │ ├── matcher │ ├── categorize │ │ ├── supervised │ │ │ ├── randomForest │ │ │ │ ├── OptimizationCriteria.java │ │ │ │ ├── TestData.java │ │ │ │ ├── TreeNode.java │ │ │ │ ├── LeafNode.java │ │ │ │ └── InnerNode.java │ │ │ └── LabeledImage.java │ │ ├── CategorizationResult.java │ │ └── AbstractCategoricalMatcher.java │ ├── exotic │ │ └── SingleImageMatcher.java │ ├── persistent │ │ └── ConsecutiveMatcher.java │ └── PlainImageMatcher.java │ ├── pcg │ ├── IncompatibleGeneratorException.java │ ├── cas │ │ └── PcgRSCas.java │ └── lock │ │ └── PcgRSLocked.java │ ├── mutable │ ├── Mutable.java │ ├── MutableBoolean.java │ ├── MutableFloat.java │ ├── MutableDouble.java │ ├── MutableLong.java │ ├── MutableInteger.java │ └── MutableByte.java │ ├── PlainAutoCloseable.java │ ├── Experimental.java │ ├── datastructures │ ├── CircularHashset.java │ ├── tree │ │ ├── NodeInfo.java │ │ ├── binaryTree │ │ │ ├── Leaf.java │ │ │ └── Node.java │ │ ├── binaryTreeFuzzy │ │ │ └── FuzzyNode.java │ │ └── Result.java │ ├── KMeansPlusPlus.java │ ├── Pair.java │ └── Triple.java │ ├── concurrency │ ├── RunnableWithCallback.java │ └── DelayedConsumerHashMap.java │ └── statBenchmark │ └── PractRandInterface.java └── jstarcraft └── dip ├── lsh.txt ├── random.txt ├── transform.txt ├── ir.txt ├── color ├── color.txt └── AbstractPixel.java ├── lsh ├── kernel │ ├── ImageConverter.java │ ├── MaximumKernel.java │ ├── SobelFilter.java │ ├── ScharrFilter.java │ └── MinimumKernel.java ├── MedianHash.java ├── GrayscaleHash.java ├── experimental │ └── HogHashAngularEncoded.java └── HashBuilder.java ├── detection └── detection.txt ├── hash └── HashUtil.java └── dip.txt /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/README.md -------------------------------------------------------------------------------- /src/test/resources/Lena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/Lena.png -------------------------------------------------------------------------------- /src/test/resources/Lenna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/Lenna.png -------------------------------------------------------------------------------- /src/test/resources/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/blue.png -------------------------------------------------------------------------------- /src/test/resources/brown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/brown.png -------------------------------------------------------------------------------- /src/test/resources/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/green.png -------------------------------------------------------------------------------- /src/test/resources/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/red.png -------------------------------------------------------------------------------- /src/test/resources/Lenna90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/Lenna90.png -------------------------------------------------------------------------------- /src/test/resources/ballon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/ballon.jpg -------------------------------------------------------------------------------- /src/test/resources/catMono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/catMono.png -------------------------------------------------------------------------------- /src/test/resources/BlackWhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/BlackWhite.png -------------------------------------------------------------------------------- /src/test/resources/Lenna180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/Lenna180.png -------------------------------------------------------------------------------- /src/test/resources/Lenna270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/Lenna270.png -------------------------------------------------------------------------------- /src/test/resources/TestShapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/TestShapes.png -------------------------------------------------------------------------------- /src/test/resources/catCustom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/catCustom.png -------------------------------------------------------------------------------- /src/test/resources/copyright.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/copyright.jpg -------------------------------------------------------------------------------- /src/test/resources/lowQuality.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/lowQuality.jpg -------------------------------------------------------------------------------- /src/test/resources/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/thumbnail.jpg -------------------------------------------------------------------------------- /src/test/resources/brownOpacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/brownOpacity.png -------------------------------------------------------------------------------- /src/test/resources/highQuality.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/highQuality.jpg -------------------------------------------------------------------------------- /src/main/resources/imageHashLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/main/resources/imageHashLogo.png -------------------------------------------------------------------------------- /src/test/resources/highQualityBase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/highQualityBase.png -------------------------------------------------------------------------------- /src/test/resources/highQualityDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/highQualityDark.png -------------------------------------------------------------------------------- /src/test/resources/highQualityHue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/highQualityHue.png -------------------------------------------------------------------------------- /src/test/resources/highQualityBright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/highQualityBright.png -------------------------------------------------------------------------------- /src/test/resources/LennaSaltAndPepper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/test/resources/LennaSaltAndPepper.png -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/examples/images/ballon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/main/java/com/github/kilianB/examples/images/ballon.jpg -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/examples/images/copyright.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/main/java/com/github/kilianB/examples/images/copyright.jpg -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/examples/images/lowQuality.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/main/java/com/github/kilianB/examples/images/lowQuality.jpg -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/examples/images/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/main/java/com/github/kilianB/examples/images/thumbnail.jpg -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/examples/images/highQuality.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HongZhaoHua/jstarcraft-dip/HEAD/src/main/java/com/github/kilianB/examples/images/highQuality.jpg -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/clustering/KMeansTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering; 2 | 3 | /** 4 | * @author Kilian 5 | * 6 | */ 7 | class KMeansTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh.txt: -------------------------------------------------------------------------------- 1 | 图片识别: 2 | https://blog.csdn.net/mylovepan/category_9990627.html 3 | 4 | 图像相似算法aHash、dHash、pHash解析与对比: 5 | https://blog.csdn.net/notzuonotdied/article/details/95727107 6 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/random.txt: -------------------------------------------------------------------------------- 1 | PCG, A Family of Better Random Number Generators: 2 | https://www.pcg-random.org 3 | 4 | PCG — Java Implementation. High quality fast random number generator: 5 | https://github.com/KilianB/pcg-java 6 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/ClusterAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering; 2 | 3 | /** 4 | * @author Kilian 5 | * 6 | */ 7 | public interface ClusterAlgorithm { 8 | 9 | ClusterResult cluster(double[][] data); 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/supervised/randomForest/OptimizationCriteria.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize.supervised.randomForest; 2 | 3 | /** 4 | * @author Kilian 5 | * 6 | */ 7 | public enum OptimizationCriteria { 8 | GINIY_IMPURITY, 9 | F1_SCORE 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/MiscUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | /** 4 | * @author Kilian Misc classes are hard to test within java. We can neither let 5 | * java restart not determine the OS without our methods. 6 | */ 7 | @Deprecated 8 | class MiscUtilTest { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/PcgTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class PcgTest { 8 | 9 | @Test 10 | public void seedNotZero() { 11 | assertTrue(Pcg.UNIQUE_SEED.get() != 0); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/transform.txt: -------------------------------------------------------------------------------- 1 | Java实现快速傅里叶变换(FFT): 2 | https://www.jianshu.com/p/a765679b1826 3 | 4 | 傅里叶变换看不懂,5分钟教你快速理解! 5 | https://baijiahao.baidu.com/s?id=1636833728798493906 6 | 7 | 傅里叶分析之掐死教程: 8 | https://zhuanlan.zhihu.com/p/19763358 9 | 10 | JTransforms is the first, open source, multithreaded FFT library written in pure Java: 11 | https://github.com/wendykierp/JTransforms 12 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/ir.txt: -------------------------------------------------------------------------------- 1 | Image Retrieval: 2 | https://yongyuan.name/blog/categories.html#Image%20Retrieval 3 | 4 | 图像检索:Hashing图像检索源码及数据库总结: 5 | http://yongyuan.name/blog/codes-of-hash-for-image-retrieval.html 6 | 7 | HABIR Toolkit: 8 | https://github.com/willard-yuan/hashing-baseline-for-image-retrieval 9 | 10 | 图像检索: 11 | https://www.cnblogs.com/wangguchangqing/category/1241422.html 12 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/datastructures/ClusterResultTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | /** 6 | * @author Kilian 7 | * 8 | */ 9 | class ClusterResultTest { 10 | 11 | /** 12 | * getBestFitCluster and lookupClusterIdForKnownHash should return the same 13 | * values for known hashes. 14 | */ 15 | @Test 16 | void lookupEquality() { 17 | 18 | // TODO 19 | // fail(); 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/color/color.txt: -------------------------------------------------------------------------------- 1 | 色彩空间与色彩模型 RGB/CMYK/YCbCr/HSB/HSI/HSV: 2 | https://blog.csdn.net/weixin_42078760/article/details/80652062 3 | 4 | 图像处理中常用的彩色模型: 5 | https://blog.csdn.net/u012507022/article/details/51523385 6 | 7 | 色彩空间RGB/CMYK/HSL/HSB/HSV/Lab/YUV基础理论及转换方法: 8 | https://www.zhoulujun.cn/html/theory/multimedia/CG-CV-IP/6112.html 9 | 10 | Convert color between RGB,HSL,HSB,CMYK,YUV,YIQ,XYZ,xyY,Lab,Luv,LCH: 11 | https://github.com/ibireme/yy_color_convertor 12 | 13 | YUV转RGB代码: 14 | https://blog.csdn.net/huiguixian/article/details/17334195 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/distance/DistanceFunction.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering.distance; 2 | 3 | import java.util.DoubleSummaryStatistics; 4 | 5 | /** 6 | * @author Kilian 7 | * 8 | */ 9 | public interface DistanceFunction { 10 | 11 | public double distance(double[] v0, double[] v1); 12 | 13 | public double distance(DoubleSummaryStatistics[] v0, double[] v1); 14 | 15 | public double distanceSquared(double[] v0, double[] v1); 16 | 17 | public double distanceSquared(DoubleSummaryStatistics[] v0, double[] v1); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/pcg/IncompatibleGeneratorException.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg; 2 | 3 | /** 4 | * Exception thrown when random number generators interact with each other but 5 | * are not in the correct state to be compared. 6 | * 7 | * @author Kilian 8 | * 9 | */ 10 | public class IncompatibleGeneratorException extends IllegalArgumentException { 11 | 12 | private static final long serialVersionUID = 6715594414038407215L; 13 | 14 | public IncompatibleGeneratorException(String message) { 15 | super(message); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/matcher/categorize/CategoricalMatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize; 2 | 3 | import com.jstarcraft.dip.lsh.AverageHash; 4 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 5 | 6 | /** 7 | * @author Kilian 8 | * 9 | */ 10 | class CategoricalMatcherTest extends CategorizeBaseTest { 11 | 12 | @Override 13 | CategoricalMatcher getInstance() { 14 | CategoricalMatcher matcher = new CategoricalMatcher(.2); 15 | HashingAlgorithm hasher = new AverageHash(32); 16 | matcher.addHashingAlgorithm(hasher); 17 | return matcher; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/supervised/randomForest/TestData.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize.supervised.randomForest; 2 | 3 | import java.awt.image.BufferedImage; 4 | 5 | class TestData { 6 | 7 | protected BufferedImage b0; 8 | protected boolean match; 9 | 10 | /** 11 | * @param b0 12 | * @param b1 13 | * @param match 14 | */ 15 | public TestData(BufferedImage b0, boolean match) { 16 | super(); 17 | this.b0 = b0; 18 | this.match = match; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "TestData [b0=" + b0.hashCode() + ", match=" + match + "]"; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/Mutable.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | /** 4 | * Mutable class wrapper for basic java data types. Mutable classes are useful 5 | * in lambda expressions or anonymous classes which want to alter the content of 6 | * a variable but are limited to final or effective final variables. 7 | * 8 | * @author Kilian 9 | */ 10 | public interface Mutable { 11 | /** 12 | * Get an object encapsulating the current value 13 | * 14 | * @return The current value as an immutable base object 15 | */ 16 | T getValue(); 17 | 18 | /** 19 | * Set the internal field to the new value 20 | * 21 | * @param newValue the new value 22 | */ 23 | void setValue(T newValue); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/kernel/ImageConverter.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.kernel; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * A filter modifies the content of an image while leaving the source image 8 | * intact. 9 | * 10 | * @author Kilian 11 | * @since 2.0.0 12 | */ 13 | /** 14 | * 图像转换器 15 | * 16 | * @author Birdy 17 | * 18 | */ 19 | public interface ImageConverter { 20 | 21 | /** 22 | * Apply the filter to the input image and return an altered copy 23 | * 24 | * @param image the input image to apply the filter on 25 | * @return the altered image 26 | */ 27 | public BufferedImage convert(BufferedImage image); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/PlainAutoCloseable.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | /** 4 | * Auto {@link java.lang.AutoCloseable} without throwing an exception 5 | *

6 | * An object that may hold resources (such as file or socket handles) until it 7 | * is closed. The {@link #close()} method of an {@code AutoCloseable} object is 8 | * called automatically when exiting a {@code 9 | * try}-with-resources block for which the object has been declared in the 10 | * resource specification header. This construction ensures prompt release, 11 | * avoiding resource exhaustion exceptions and errors that may otherwise occur. 12 | * 13 | * @author Kilian 14 | * 15 | */ 16 | public interface PlainAutoCloseable extends AutoCloseable { 17 | 18 | void close(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/Experimental.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | import static java.lang.annotation.ElementType.PACKAGE; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.SOURCE; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Inherited; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.Target; 11 | 12 | @Documented 13 | @Inherited 14 | @Retention(SOURCE) 15 | @Target({ TYPE, PACKAGE }) 16 | /** 17 | * Annotation indicating that a class or method is not ready to be used yet and 18 | * might change quickly between versions. 19 | * 20 | * @author Kilian 21 | * 22 | */ 23 | public @interface Experimental { 24 | String value(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/supervised/randomForest/TreeNode.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize.supervised.randomForest; 2 | 3 | import java.awt.image.BufferedImage; 4 | 5 | abstract class TreeNode { 6 | 7 | /** 8 | * Check if the buffered image is considered a match by this subtree. This 9 | * method takes into account itself and all subsequent lower nodes. To receive 10 | * an accurate result this function should only be called on the root node. 11 | * 12 | * @param bi the buffered image to check 13 | * @param precomputedHashes 14 | * @return true if the image is considered a match, false otherwise 15 | */ 16 | public abstract int[] predictAgainstAll(BufferedImage bi); 17 | 18 | /** 19 | * 20 | * @param depth 21 | */ 22 | protected abstract void printTree(int depth); 23 | 24 | /** 25 | * 26 | */ 27 | public void printTree() { 28 | printTree(0); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/matcher/categorize/KMeansClassifierTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize; 2 | 3 | import static com.github.kilianB.TestResources.ballon; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.jstarcraft.dip.lsh.AverageHash; 9 | 10 | /** 11 | * @author Kilian 12 | * 13 | */ 14 | class KMeansClassifierTest extends CategorizeBaseTest { 15 | 16 | @Override 17 | CategoricalImageMatcher getInstance() { 18 | return new KMeansClassifier(3, new AverageHash(32)); 19 | } 20 | 21 | @Test 22 | void distanceIdentity() { 23 | CategoricalImageMatcher matcher = getInstance(); 24 | 25 | CategorizationResult pair = matcher.categorizeImageAndAdd(ballon, "ballon"); 26 | // Category 27 | assertEquals(0, (int) pair.getCategory()); 28 | // Dostance 29 | assertEquals(Double.NaN, (double) pair.getQuality()); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/RandomBurdenTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | 5 | import java.util.Random; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | /** 10 | * Test cases our class inherited due to extending the Random class 11 | * 12 | * @author Kilian 13 | * 14 | */ 15 | public abstract class RandomBurdenTest { 16 | 17 | @Test 18 | public void setSeed() { 19 | Random rng = getInstance(); 20 | 21 | rng.setSeed(15); 22 | int[] randomInts = new int[10]; 23 | for (int i = 0; i < randomInts.length; i++) { 24 | randomInts[i] = rng.nextInt(); 25 | } 26 | 27 | rng.setSeed(15); 28 | int[] randomInts2 = new int[10]; 29 | for (int i = 0; i < randomInts.length; i++) { 30 | randomInts2[i] = rng.nextInt(); 31 | } 32 | assertArrayEquals(randomInts, randomInts2); 33 | } 34 | 35 | protected abstract Random getInstance(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/CircularHashset.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | 6 | /** 7 | * 8 | * @author Kilian 9 | * @since 1.1.0 10 | */ 11 | public class CircularHashset { 12 | 13 | private CircularHashset() { 14 | }; 15 | 16 | public static Set create(int capacity) { 17 | // return Collections.newSetFromMap(new Base(capacity)); 18 | return Collections.newSetFromMap(new CircularLinkedHashMap(capacity)); 19 | } 20 | 21 | // private static class Base extends LinkedHashMap{ 22 | // 23 | // private static final long serialVersionUID = 4853774520170918913L; 24 | // private int capacity = 0; 25 | // 26 | // public Base(int capacity) { 27 | // this.capacity = capacity; 28 | // } 29 | // 30 | // @Override 31 | // protected boolean removeEldestEntry(Map.Entry eldest) { 32 | // return this.size() >= capacity; 33 | // } 34 | // } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/tree/NodeInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree; 2 | 3 | import com.github.kilianB.datastructures.tree.binaryTree.Node; 4 | 5 | /** 6 | * Helper class to iteratively search the tree { 7 | * 8 | * @author Kilian 9 | * 10 | */ 11 | public class NodeInfo implements Comparable> { 12 | public Node node; 13 | public double distance; 14 | public int depth; 15 | 16 | public NodeInfo(Node node, double distance, int depth) { 17 | this.node = node; 18 | this.distance = distance; 19 | this.depth = depth; 20 | } 21 | 22 | @Override 23 | public int compareTo(NodeInfo o) { 24 | int compareTo = Integer.compare(depth, o.depth); 25 | if (compareTo == 0) { 26 | return Double.compare(this.distance, o.distance); 27 | } 28 | return compareTo; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "NodeInfo [node=" + node + ", distance=" + distance + ", depth=" + depth + "]"; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/detection/detection.txt: -------------------------------------------------------------------------------- 1 | 目标检测的图像特征提取: 2 | HOG特征: 3 | https://blog.csdn.net/zouxy09/article/details/7929348 4 | LBP特征: 5 | https://blog.csdn.net/zouxy09/article/details/7929531 6 | Haar特征: 7 | https://blog.csdn.net/zouxy09/article/details/7929570 8 | 9 | SIFT与HOG特征提取: 10 | https://blog.csdn.net/taigw/article/details/42206311 11 | 12 | 图像识别--Java中使用OpenCV提取HOG特征通过SVM训练实现图像识别: 13 | https://blog.csdn.net/m_wbcg/article/details/75092947 14 | 15 | SVM+HOG图片分类 Java版: 16 | https://blog.csdn.net/hanzhangliu_lkzy/article/details/79640901 17 | 18 | Java library to calculate "Histogram of Oriented Gradients" (HOG) descriptors for object detection in images: 19 | https://github.com/danielgimenes/jHOG 20 | 21 | HOG(Histogram of Oriented Gradients): 22 | https://www.jianshu.com/p/c8820deebb50 23 | 24 | Histogram Of Oriented Gradients with modifications: 25 | https://github.com/Visheshj123/WARTHOG 26 | 27 | Histogram of oriented gradients (HOG) Python implementation using NumPy: 28 | https://github.com/ahmedfgad/HOGNumPy 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/color/AbstractPixel.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.color; 2 | 3 | /** 4 | * 抽象像素 5 | * 6 | * @author Birdy 7 | * 8 | */ 9 | public abstract class AbstractPixel implements ColorPixel { 10 | 11 | /** 图像宽度 */ 12 | protected final int width; 13 | 14 | /** 图像高度 */ 15 | protected final int height; 16 | 17 | /** 是否支持透明度 */ 18 | private final boolean transparency; 19 | 20 | public AbstractPixel(int width, int height, boolean transparency) { 21 | this.width = width; 22 | this.height = height; 23 | this.transparency = transparency; 24 | } 25 | 26 | @Override 27 | public int getIndex(int x, int y) { 28 | return (y * width) + x; 29 | } 30 | 31 | @Override 32 | public int getWidth() { 33 | return width; 34 | } 35 | 36 | @Override 37 | public int getHeight() { 38 | return height; 39 | } 40 | 41 | @Override 42 | public boolean hasTransparency() { 43 | return transparency; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/distance/ManhattanDistance.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering.distance; 2 | 3 | import java.util.DoubleSummaryStatistics; 4 | 5 | /** 6 | * @author Kilian 7 | * 8 | */ 9 | public class ManhattanDistance implements DistanceFunction { 10 | 11 | @Override 12 | public double distance(double[] v0, double[] v1) { 13 | double dist = 0; 14 | for (int i = 0; i < v0.length; i++) { 15 | dist += Math.abs(v0[i] - v1[i]); 16 | } 17 | return dist; 18 | } 19 | 20 | @Override 21 | public double distance(DoubleSummaryStatistics[] v0, double[] v1) { 22 | double dist = 0; 23 | for (int i = 0; i < v0.length; i++) { 24 | dist += Math.abs(v0[i].getAverage() - v1[i]); 25 | } 26 | return dist; 27 | } 28 | 29 | @Override 30 | public double distanceSquared(double[] v0, double[] v1) { 31 | return Math.pow(distance(v0, v1), 2); 32 | } 33 | 34 | @Override 35 | public double distanceSquared(DoubleSummaryStatistics[] v0, double[] v1) { 36 | return Math.pow(distance(v0, v1), 2); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/hash/HashUtil.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.hash; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.IOException; 6 | 7 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 8 | 9 | /** 10 | * @author Kilian 11 | * 12 | */ 13 | public class HashUtil { 14 | 15 | public static FuzzyHash toFuzzyHash(Hash... hashes) { 16 | return new FuzzyHash(hashes); 17 | }; 18 | 19 | public static FuzzyHash toFuzzyHash(HashingAlgorithm hasher, File... imageFiles) throws IOException { 20 | FuzzyHash fuzzy = new FuzzyHash(); 21 | for (File imgFile : imageFiles) { 22 | fuzzy.mergeFast(hasher.hash(imgFile)); 23 | } 24 | return fuzzy; 25 | } 26 | 27 | public static FuzzyHash toFuzzyHash(HashingAlgorithm hasher, BufferedImage... images) { 28 | FuzzyHash fuzzy = new FuzzyHash(); 29 | for (BufferedImage image : images) { 30 | fuzzy.mergeFast(hasher.hash(image)); 31 | } 32 | return fuzzy; 33 | } 34 | 35 | public static boolean areCompatible(Hash h0, Hash h1) { 36 | return h0.getAlgorithmId() == h1.getAlgorithmId(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/CryptoUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import javax.crypto.SecretKey; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | /** 12 | * @author Kilian 13 | * 14 | */ 15 | class CryptoUtilTest { 16 | 17 | @Test 18 | protected void deriveKeyIdentity() { 19 | SecretKey key = CryptoUtil.deriveKey(new char[] { 5, 1, 5, 1, 2, 3 }); 20 | SecretKey key1 = CryptoUtil.deriveKey(new char[] { 5, 1, 5, 1, 2, 3 }); 21 | assertTrue(key.equals(key1)); 22 | } 23 | 24 | @Test 25 | protected void encryptDecrypt() { 26 | String original = "MySecret"; 27 | SecretKey key = CryptoUtil.deriveKey(new char[] { 5, 1, 5, 1, 2, 3 }); 28 | 29 | try { 30 | String text = CryptoUtil.encryptAES(key, original); 31 | assertNotEquals(original, text); 32 | assertEquals(original, CryptoUtil.decrypt(key, text)); 33 | } catch (Exception e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/distance/EuclideanDistance.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering.distance; 2 | 3 | import java.util.DoubleSummaryStatistics; 4 | 5 | /** 6 | * @author Kilian 7 | * 8 | */ 9 | public class EuclideanDistance implements DistanceFunction { 10 | 11 | public double distance(DoubleSummaryStatistics[] v0, double v1[]) { 12 | return Math.sqrt(distanceSquared(v0, v1)); 13 | } 14 | 15 | public double distanceSquared(DoubleSummaryStatistics[] v0, double v1[]) { 16 | assert v0.length == v1.length; 17 | double distance = 0; 18 | for (int i = 0; i < v0.length; i++) { 19 | double temp = v0[i].getAverage() - v1[i]; 20 | distance += temp * temp; 21 | } 22 | return distance; 23 | } 24 | 25 | public double distance(double[] v0, double v1[]) { 26 | return Math.sqrt(distanceSquared(v0, v1)); 27 | } 28 | 29 | @Override 30 | public double distanceSquared(double[] v0, double[] v1) { 31 | assert v0.length == v1.length; 32 | double distance = 0; 33 | for (int i = 0; i < v0.length; i++) { 34 | double temp = v0[i] - v1[i]; 35 | distance += temp * temp; 36 | } 37 | return distance; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/distance/JaccardIndex.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering.distance; 2 | 3 | import java.util.Arrays; 4 | import java.util.DoubleSummaryStatistics; 5 | import java.util.HashSet; 6 | 7 | /** 8 | * @author Kilian 9 | * 10 | */ 11 | public class JaccardIndex implements DistanceFunction { 12 | 13 | // TODO jmh performance benchmarking! This can't be optimal 14 | 15 | @Override 16 | public double distance(double[] v0, double[] v1) { 17 | HashSet a = new HashSet(Arrays.asList(v0)); 18 | HashSet b = new HashSet(Arrays.asList(v0)); 19 | HashSet union = new HashSet<>(a); 20 | union.addAll(b); 21 | b.retainAll(a); 22 | return b.size() / (double) union.size(); 23 | } 24 | 25 | @Override 26 | public double distance(DoubleSummaryStatistics[] v0, double[] v1) { 27 | // TODO Auto-generated method stub 28 | return 0; 29 | } 30 | 31 | @Override 32 | public double distanceSquared(double[] v0, double[] v1) { 33 | // TODO Auto-generated method stub 34 | return 0; 35 | } 36 | 37 | @Override 38 | public double distanceSquared(DoubleSummaryStatistics[] v0, double[] v1) { 39 | // TODO Auto-generated method stub 40 | return 0; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/sync/PcgRRTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.sync; 2 | 3 | import java.util.Random; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | 7 | import com.github.kilianB.pcg.Pcg; 8 | import com.github.kilianB.pcg.PcgBaseTest; 9 | import com.github.kilianB.pcg.RandomBurdenTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRRTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRR(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRR(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return false; 39 | } 40 | 41 | @Nested 42 | class RandomBurden extends RandomBurdenTest { 43 | @Override 44 | protected Random getInstance() { 45 | return new PcgRR(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/sync/PcgRSTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.sync; 2 | 3 | import java.util.Random; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | 7 | import com.github.kilianB.pcg.Pcg; 8 | import com.github.kilianB.pcg.PcgBaseTest; 9 | import com.github.kilianB.pcg.RandomBurdenTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRSTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRS(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRS(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return false; 39 | } 40 | 41 | @Nested 42 | class RandomBurden extends RandomBurdenTest { 43 | @Override 44 | protected Random getInstance() { 45 | return new PcgRS(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/cas/PcgRSCasTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.cas; 2 | 3 | import java.util.Random; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | 7 | import com.github.kilianB.pcg.Pcg; 8 | import com.github.kilianB.pcg.PcgBaseTest; 9 | import com.github.kilianB.pcg.RandomBurdenTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRSCasTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRSCas(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRSCas(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return false; 39 | } 40 | 41 | @Nested 42 | class RandomBurden extends RandomBurdenTest { 43 | @Override 44 | protected Random getInstance() { 45 | return new PcgRSCas(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/cas/PcgRRCasTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.cas; 2 | 3 | import java.util.Random; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | 7 | import com.github.kilianB.pcg.Pcg; 8 | import com.github.kilianB.pcg.PcgBaseTest; 9 | import com.github.kilianB.pcg.RandomBurdenTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRRCasTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRRCas(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRRCas(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return false; 39 | } 40 | 41 | @Nested 42 | class RandomBurden extends RandomBurdenTest { 43 | @Override 44 | protected Random getInstance() { 45 | return new PcgRRCas(); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/lock/PcgRRLockedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.lock; 2 | 3 | import java.util.Random; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | 7 | import com.github.kilianB.pcg.Pcg; 8 | import com.github.kilianB.pcg.PcgBaseTest; 9 | import com.github.kilianB.pcg.RandomBurdenTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRRLockedTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRRLocked(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRRLocked(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return false; 39 | } 40 | 41 | @Nested 42 | class RandomBurden extends RandomBurdenTest { 43 | @Override 44 | protected Random getInstance() { 45 | return new PcgRRLocked(); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/lock/PcgRSLockedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.lock; 2 | 3 | import java.util.Random; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | 7 | import com.github.kilianB.pcg.Pcg; 8 | import com.github.kilianB.pcg.PcgBaseTest; 9 | import com.github.kilianB.pcg.RandomBurdenTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRSLockedTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRSLocked(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRSLocked(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return false; 39 | } 40 | 41 | @Nested 42 | class RandomBurden extends RandomBurdenTest { 43 | @Override 44 | protected Random getInstance() { 45 | return new PcgRSLocked(); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/supervised/randomForest/LeafNode.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize.supervised.randomForest; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | import com.github.kilianB.StringUtil; 7 | 8 | /** 9 | * A leaf node is a terminating . Reaching this point will r 10 | * 11 | * .. 12 | * 13 | * @author Kilian 14 | * 15 | */ 16 | class LeafNode extends TreeNode { 17 | 18 | private static AtomicInteger uniqueId = new AtomicInteger(); 19 | 20 | protected int id; 21 | 22 | /** indicate if this node indicates a match or mismatch leaf node */ 23 | protected int category; 24 | 25 | /** 26 | * Create a leaf node. 27 | * 28 | * @param match 29 | */ 30 | public LeafNode(int category) { 31 | this.category = category; 32 | this.id = uniqueId.incrementAndGet(); 33 | } 34 | 35 | @Override 36 | protected void printTree(int depth) { 37 | System.out.println(StringUtil.multiplyChar("\t", depth) + this); 38 | } 39 | 40 | @Override 41 | public int[] predictAgainstAll(BufferedImage bi) { 42 | return new int[]{category,id}; 43 | } 44 | 45 | public String toString() { 46 | return "LeafNode " + this.hashCode() + " [Category:" + category + "] "; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/dip.txt: -------------------------------------------------------------------------------- 1 | 计算机视觉,计算机图形学和数字图像处理,三者之间的联系和区别是什么: 2 | https://www.zhihu.com/question/20672053 3 | 4 | 计算机视觉四大基本任务的应用知识分享: 5 | https://blog.csdn.net/u011707542/article/details/79151978 6 | 7 | C-OCR是携程自研的OCR项目,主要包括身份证、护照、火车票、签证等旅游相关证件、材料的识别: 8 | https://github.com/ctripcorp/C-OCR 9 | 10 | 常见二维码种类(Data Matrix、MaxiCode、Aztec、QR Code、PDF417、Vericode、Ultracode、Code 49、Code 16K): 11 | https://www.cnblogs.com/guorongtao/p/12921855.html 12 | 13 | 二维码/条码识别、身份证识别、银行卡识别、车牌识别、图片文字识别、黄图识别、驾驶证(驾照)识别: 14 | https://github.com/shouzhong/Scanner 15 | 16 | ZXing应用详解: 17 | https://www.jianshu.com/p/6607e69b1121 18 | 19 | Java实现二维码的生成与解析: 20 | https://blog.csdn.net/jam_fanatic/article/details/82818857 21 | 22 | Java生成二维码/Java解析二维码 23 | https://www.cnblogs.com/sonder/p/10007876.html 24 | 25 | Java实现二维码的生成和解析:QRCode、zxing 两种方式 26 | https://www.cnblogs.com/codingcc1/p/11099788.html 27 | 28 | Java 创建/识别条形码、二维码 29 | https://www.cnblogs.com/Yesi/p/11527369.html 30 | 31 | Java生成读取条形码和二维码图片 32 | https://www.open-open.com/code/view/1453520722495 33 | 34 | This repository accompanies Java Image Processing Recipes by Nicolas Modrzyk: 35 | https://github.com/Apress/java-image-processing-recipes 36 | 37 | Thumbnailator - a thumbnail generation library for Java: 38 | https://github.com/coobird/thumbnailator 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/concurrency/RunnableWithCallback.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.concurrency; 2 | 3 | /** 4 | * A runnable wrapper which can be useful to schedule a task with a specific 5 | * delay and invoking a callback once the runnable finished executing. 6 | * 7 | *

e.g. conditional task rescheduling

8 | * 9 | *
10 |  * Callback callback = {
11 |  *      //Check if we want to reschedule a new task
12 |  *      Runnable rescheduledTask = new Task();
13 |  *      Exector.schedule(new RunnableWithCallBack(rescheduledTask,this),newDelay,timeunit); 
14 |  * };
15 |  * 
16 |  * Runnable task = new Task();
17 |  * RunnableWithCallback wrapper = new RunnableWithCallback(task,callback);
18 |  * Executor.shedule(wrapper,initialDelay,TimeUnit.seconds);
19 |  * 
20 | * 21 | * @author Kilian 22 | * @since 1.0.0 23 | */ 24 | public class RunnableWithCallback implements Runnable { 25 | 26 | private Runnable task, callback; 27 | 28 | /** 29 | * @param task The task to execute 30 | * @param callback executed after task finished 31 | */ 32 | public RunnableWithCallback(Runnable task, Runnable callback) { 33 | this.task = task; 34 | this.callback = callback; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | task.run(); 40 | callback.run(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/fast/PcgRSFastTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.fast; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.github.kilianB.pcg.Pcg; 9 | import com.github.kilianB.pcg.PcgBaseTest; 10 | 11 | /** 12 | * JUnit tests are only used to test methods like distance advance/skip/rewind 13 | * states and other ordinary functionality. It is not designed to test 14 | * distributions or statistical properties! 15 | *

16 | * 17 | * Statistical properties are tested with PractRand evaluation 18 | *

19 | * Performance are checked by jmh 20 | * 21 | * @author Kilian 22 | * 23 | */ 24 | class PcgRSFastTest extends PcgBaseTest { 25 | 26 | @Override 27 | public Pcg getInstance() { 28 | return new PcgRSFast(); 29 | } 30 | 31 | @Override 32 | public Pcg getInstance(long seed, long streamNumber) { 33 | return new PcgRSFast(seed, streamNumber); 34 | } 35 | 36 | @Override 37 | public boolean isFast() { 38 | return true; 39 | } 40 | 41 | @Nested 42 | class FastSpecific { 43 | 44 | @Test 45 | void noNext() { 46 | assertThrows(UnsupportedOperationException.class, () -> { 47 | getInstance().next(4); 48 | }); 49 | } 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/datastructures/KMeansTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import static com.github.kilianB.TestResources.createHash; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.jstarcraft.dip.hash.Hash; 9 | 10 | /** 11 | * @author Kilian 12 | * 13 | */ 14 | class KMeansTest { 15 | 16 | @Test 17 | void testMatchingDataSizeAndClusterCount() { 18 | KMeans clusterer = new KMeans(2); 19 | ClusterResult clusterResult = clusterer.cluster(new Hash[] { createHash("0", 0), createHash("1", 0) }); 20 | // -1 for the noise cluster 21 | assertEquals(2, clusterResult.getClusters().keySet().size() - 1); 22 | } 23 | 24 | @Test 25 | void testSmallerDataSizeAndClusterCount() { 26 | KMeans clusterer = new KMeans(1); 27 | ClusterResult clusterResult = clusterer.cluster(new Hash[] { createHash("0", 0), createHash("1", 0) }); 28 | // -1 for the noise cluster 29 | assertEquals(1, clusterResult.getClusters().keySet().size() - 1); 30 | } 31 | 32 | @Test 33 | void testLargerDataSizeAndClusterCount() { 34 | KMeans clusterer = new KMeans(4); 35 | ClusterResult clusterResult = clusterer.cluster(new Hash[] { createHash("0", 0), createHash("1", 0) }); 36 | // -1 for the noise cluster 37 | assertEquals(2, clusterResult.getClusters().keySet().size() - 1); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/datastructures/tree/NodeInfoTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Arrays; 6 | 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | 10 | /** 11 | * @author Kilian 12 | * 13 | */ 14 | class NodeInfoTest { 15 | 16 | @Nested 17 | @SuppressWarnings("rawtypes") 18 | class Sorting { 19 | 20 | @Test 21 | public void distinctDepth() { 22 | 23 | NodeInfo nInfo = new NodeInfo<>(null, 1, 3); 24 | NodeInfo nInfo2 = new NodeInfo<>(null, 1, 2); 25 | 26 | NodeInfo[] n = { nInfo, nInfo2 }; 27 | Arrays.sort(n); 28 | assertEquals(nInfo2, n[0]); 29 | assertEquals(nInfo, n[1]); 30 | } 31 | 32 | @Test 33 | public void identical() { 34 | 35 | NodeInfo nInfo = new NodeInfo<>(null, 1, 3); 36 | NodeInfo nInfo2 = new NodeInfo<>(null, 1, 3); 37 | NodeInfo[] n = { nInfo, nInfo2 }; 38 | Arrays.sort(n); 39 | assertEquals(nInfo, n[0]); 40 | assertEquals(nInfo2, n[1]); 41 | } 42 | 43 | @Test 44 | public void distinctDistanceSameDepth() { 45 | 46 | NodeInfo nInfo = new NodeInfo<>(null, 2, 3); 47 | NodeInfo nInfo2 = new NodeInfo<>(null, 1, 3); 48 | NodeInfo[] n = { nInfo, nInfo2 }; 49 | Arrays.sort(n); 50 | assertEquals(nInfo2, n[0]); 51 | assertEquals(nInfo, n[1]); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/c++/UnsignedMath.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Do some test to ensure the java and c++ version behave similary when 3 | * stepping back and forth 4 | */ 5 | 6 | #include 7 | using namespace std; 8 | 9 | //shall we typedef? 10 | 11 | //Func headers. No reason to use a header file 12 | void performAdvance(uint64_t delta, uint32_t iterations); 13 | uint64_t pcg_advance_lcg_64(uint64_t state, uint64_t delta, uint64_t cur_mult, 14 | uint64_t cur_plus); 15 | 16 | 17 | //State 18 | static uint64_t cur_step = 0; 19 | static uint64_t state = 5; 20 | 21 | int main() { 22 | 23 | cout << "C++" << endl << endl; 24 | 25 | //Single step forwards 26 | performAdvance(1,10); 27 | 28 | performAdvance(-1,10); 29 | 30 | return 0; 31 | } 32 | 33 | void performAdvance(uint64_t delta, uint32_t iterations) { 34 | uint64_t cur_mult = 6364136223846793005; 35 | uint64_t cur_plus = 16; 36 | 37 | for (uint32_t i = 0; i < iterations; i++) { 38 | state = pcg_advance_lcg_64(state, delta, cur_mult, cur_plus); 39 | cur_step += delta; 40 | cout << cur_step << " " << state << endl; 41 | } 42 | } 43 | 44 | //Original step ahead and back function used in c++ 45 | uint64_t pcg_advance_lcg_64(uint64_t state, uint64_t delta, uint64_t cur_mult, 46 | uint64_t cur_plus) { 47 | uint64_t acc_mult = 1u; 48 | uint64_t acc_plus = 0u; 49 | while (delta > 0) { 50 | if (delta & 1) { 51 | acc_mult *= cur_mult; 52 | acc_plus = acc_plus * cur_mult + cur_plus; 53 | } 54 | cur_plus = (cur_mult + 1) * cur_plus; 55 | cur_mult *= cur_mult; 56 | delta /= 2; 57 | } 58 | return acc_mult * state + acc_plus; 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/tree/binaryTree/Leaf.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree.binaryTree; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * A leaf node in the binary tree containing multiple values associated with a 7 | * specific hash value 8 | * 9 | * @author Kilian 10 | * 11 | */ 12 | public class Leaf extends Node { 13 | 14 | /** 15 | * Values saved in this leaf 16 | */ 17 | private ArrayList data = new ArrayList<>(); 18 | 19 | /** 20 | * Append new data to the leaf 21 | * 22 | * @param data Value which will be associated with the hash this leaf represents 23 | */ 24 | public void addData(T data) { 25 | this.data.add(data); 26 | } 27 | 28 | /** 29 | * @return a strong reference to the arraylist backing this leaf 30 | */ 31 | public ArrayList getData() { 32 | return data; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | final int prime = 31; 38 | int result = super.hashCode(); 39 | result = prime * result + ((data == null) ? 0 : data.hashCode()); 40 | return result; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if (this == obj) { 46 | return true; 47 | } 48 | if (!super.equals(obj)) { 49 | return false; 50 | } 51 | if (!(obj instanceof Leaf)) { 52 | return false; 53 | } 54 | Leaf other = (Leaf) obj; 55 | if (data == null) { 56 | if (other.data != null) { 57 | return false; 58 | } 59 | } else if (!data.equals(other.data)) { 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/matcher/categorize/WeightedCategoricalMatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize; 2 | 3 | import static com.github.kilianB.TestResources.ballon; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import java.util.stream.Stream; 7 | 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.MethodSource; 10 | 11 | import com.github.kilianB.matcher.categorize.WeightedCategoricalMatcher.DimReduction; 12 | import com.jstarcraft.dip.lsh.AverageHash; 13 | 14 | /** 15 | * @author Kilian 16 | * 17 | */ 18 | class WeightedCategoricalMatcherTest extends CategorizeBaseTest { 19 | 20 | @ParameterizedTest 21 | @MethodSource(value = "com.github.kilianB.matcher.categorize.WeightedCategoricalMatcherTest#getMatcher") 22 | void sameDistanceAfterClusterRecomputation(WeightedCategoricalMatcher wMatcher) { 23 | wMatcher.addHashingAlgorithm(new AverageHash(32)); 24 | wMatcher.categorizeImageAndAdd(ballon, "ballon"); 25 | wMatcher.recomputeClusters(10); 26 | assertEquals(0, (double) wMatcher.categorizeImage(ballon).getQuality()); 27 | } 28 | 29 | public static Stream getMatcher() { 30 | return Stream.of(new WeightedCategoricalMatcher(.2, DimReduction.NONE), new WeightedCategoricalMatcher(.2, DimReduction.K_MEANS_APPROXIMATION), new WeightedCategoricalMatcher(.2, DimReduction.BINARY_TREE)); 31 | } 32 | 33 | @Override 34 | CategoricalMatcher getInstance() { 35 | CategoricalMatcher matcher = new WeightedCategoricalMatcher(.2, DimReduction.NONE); 36 | matcher.addHashingAlgorithm(new AverageHash(32)); 37 | return matcher; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/tree/binaryTreeFuzzy/FuzzyNode.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree.binaryTreeFuzzy; 2 | 3 | import com.github.kilianB.datastructures.tree.binaryTree.Node; 4 | 5 | /** 6 | * A tree node saving references to it's children. 7 | * 8 | * @author Kilian 9 | * @since 3.0.0 10 | */ 11 | public class FuzzyNode extends Node { 12 | 13 | private static final long serialVersionUID = 4043416513243595121L; 14 | // TODO explain better 15 | /** The lower bound error this node might have */ 16 | public double lowerDistance = Double.MAX_VALUE; 17 | /** The upper bound error this node might have */ 18 | public double uppderDistance = -Double.MAX_VALUE; 19 | 20 | /** 21 | * Create and set a child of the current node 22 | * 23 | * @param left if true create the left child if false create the right child 24 | * @return the created node 25 | */ 26 | public Node createChild(boolean left) { 27 | return setChild(left, new FuzzyNode()); 28 | } 29 | 30 | /** 31 | * Refresh the node's the upper and lower bound error. This method should be 32 | * called when ever a hash was added to this node. The bounds will be adapted 33 | * correctly by this method. 34 | * 35 | * @param distance the distance of the hash added to this node 36 | */ 37 | public void setNodeBounds(double distance) { 38 | 39 | if (distance < this.lowerDistance) { 40 | this.lowerDistance = distance; 41 | } 42 | 43 | if (distance > this.uppderDistance) { 44 | this.uppderDistance = distance; 45 | } 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "FuzzyNode [lowerDistance=" + lowerDistance + ", uppderDistance=" + uppderDistance + "]"; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/StringUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | /** 9 | * @author Kilian 10 | * 11 | */ 12 | class StringUtilTest { 13 | 14 | @Test 15 | void charsNeededZero() { 16 | assertEquals(1, StringUtil.charsNeeded(Integer.valueOf(0))); 17 | } 18 | 19 | @Test 20 | void charsNeededOne() { 21 | assertEquals(1, StringUtil.charsNeeded(Integer.valueOf(1))); 22 | } 23 | 24 | @Test 25 | void charsNeededTen() { 26 | assertEquals(2, StringUtil.charsNeeded(Integer.valueOf(10))); 27 | } 28 | 29 | @Test 30 | void charsNeededNegative() { 31 | assertEquals(2, StringUtil.charsNeeded(Integer.valueOf(-1))); 32 | } 33 | 34 | @Test 35 | void charsNeededFraction() { 36 | assertEquals(2, StringUtil.charsNeeded(Double.valueOf(10.1234))); 37 | } 38 | 39 | @Test 40 | void charsNeededFractionNegative() { 41 | assertEquals(3, StringUtil.charsNeeded(Double.valueOf(-10.1234))); 42 | } 43 | 44 | @Test 45 | void multiplyTextEmpty() { 46 | assertEquals(0, StringUtil.multiplyChar("", 10).length()); 47 | } 48 | 49 | @Test 50 | void multiplyTextSimple() { 51 | assertEquals(10, StringUtil.multiplyChar(" ", 10).length()); 52 | } 53 | 54 | @Test 55 | void multiplyTextTwoSpaces() { 56 | assertEquals(20, StringUtil.multiplyChar(" ", 10).length()); 57 | } 58 | 59 | @Test 60 | void fillStringSimple() { 61 | String content = "Hello"; 62 | String result = StringUtil.fillString("-", 10, content); 63 | assertTrue(result.startsWith(content)); 64 | assertTrue(result.endsWith("-----")); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/clustering/KMeansPlusPlusTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.Test; 13 | 14 | /** 15 | * @author Kilian 16 | * 17 | */ 18 | //@TestInstance(Lifecycle.PER_CLASS) 19 | class KMeansPlusPlusTest { 20 | 21 | private static double[][] clusterTestData; 22 | 23 | @BeforeAll 24 | private static void loadTestset() { 25 | 26 | try (BufferedReader br = new BufferedReader(new InputStreamReader(KMeansPlusPlusTest.class.getClassLoader().getResourceAsStream("clusterTestData.txt")))) { 27 | String line; 28 | List dataPoints = new ArrayList<>(); 29 | while ((line = br.readLine()) != null) { 30 | String[] tokens = line.split("\t"); 31 | dataPoints.add(new double[] { Double.parseDouble(tokens[1]), Double.parseDouble(tokens[2]) }); 32 | } 33 | clusterTestData = dataPoints.toArray(new double[dataPoints.size()][2]); 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } 37 | 38 | } 39 | 40 | @Test 41 | void compareKMeans() { 42 | KMeans kmeans = new KMeans(10); 43 | KMeans kmeansPp = new KMeansPlusPlus(10); 44 | 45 | int kCount = 0; 46 | int kppCount = 0; 47 | for (int i = 0; i < 15; i++) { 48 | kmeans.cluster(clusterTestData); 49 | kmeansPp.cluster(clusterTestData); 50 | 51 | kCount += kmeans.iterations(); 52 | kppCount += kmeansPp.iterations(); 53 | } 54 | assertTrue(kCount > kppCount); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/concurrency/NamedThreadFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.concurrency; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | import static org.junit.jupiter.api.Assertions.fail; 6 | 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | /** 13 | * @author Kilian 14 | * 15 | */ 16 | class NamedThreadFactoryTest { 17 | 18 | @Test 19 | void createsNonDaemonThread() { 20 | NamedThreadFactory factory = new NamedThreadFactory(); 21 | Thread t = factory.newThread(() -> { 22 | }); 23 | assertFalse(t.isDaemon()); 24 | } 25 | 26 | @Test 27 | void createsDaemonThread() { 28 | NamedThreadFactory factory = new NamedThreadFactory(true); 29 | Thread t = factory.newThread(() -> { 30 | }); 31 | assertTrue(t.isDaemon()); 32 | } 33 | 34 | @Test 35 | void defaultExceptionIsCaught() { 36 | NamedThreadFactory factory = new NamedThreadFactory(); 37 | Thread t = factory.newThread(() -> { 38 | throw new IllegalArgumentException(); 39 | }); 40 | t.start(); 41 | // Fails if an exception is thrown 42 | } 43 | 44 | @Test 45 | void exceptionHandler() { 46 | 47 | CountDownLatch latch = new CountDownLatch(1); 48 | NamedThreadFactory factory = new NamedThreadFactory((thread, throwable) -> { 49 | latch.countDown(); 50 | }); 51 | 52 | Thread t = factory.newThread(() -> { 53 | throw new IllegalArgumentException(); 54 | }); 55 | t.start(); 56 | 57 | try { 58 | assertTrue(latch.await(150, TimeUnit.MILLISECONDS)); 59 | } catch (InterruptedException e) { 60 | fail("Interrupted"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/color/ImageUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.color; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.awt.image.BufferedImage; 6 | import java.io.IOException; 7 | 8 | import javax.imageio.ImageIO; 9 | 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | 13 | /** 14 | * @author Kilian 15 | * 16 | */ 17 | class ImageUtilTest { 18 | 19 | // No alpha image 20 | private static BufferedImage lena; 21 | 22 | // 23 | private static BufferedImage bw; 24 | 25 | private static BufferedImage catCustom; 26 | 27 | @BeforeAll 28 | static void loadImage() { 29 | try { 30 | lena = ImageIO.read(ImageUtilTest.class.getClassLoader().getResourceAsStream("Lena.png")); 31 | bw = ImageIO.read(ImageUtilTest.class.getClassLoader().getResourceAsStream("BlackWhite.png")); 32 | catCustom = ImageIO.read(ImageUtilTest.class.getClassLoader().getResourceAsStream("catMono.png")); 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | @Test 39 | void getScaledInstanceSize() { 40 | BufferedImage scaled = ImageUtility.getScaledInstance(lena, 10, 10); 41 | assertEquals(10, scaled.getWidth()); 42 | assertEquals(10, scaled.getHeight()); 43 | } 44 | 45 | @Test 46 | void getScaledInstanceSizeBW() { 47 | BufferedImage scaled = ImageUtility.getScaledInstance(bw, 10, 10); 48 | assertEquals(10, scaled.getWidth()); 49 | assertEquals(10, scaled.getHeight()); 50 | } 51 | 52 | @Test 53 | void getScaledInstanceCustomImage() { 54 | BufferedImage scaled = ImageUtility.getScaledInstance(catCustom, 10, 10); 55 | assertEquals(10, scaled.getWidth()); 56 | assertEquals(10, scaled.getHeight()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/pcg/AdvanceBackup.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg; 2 | 3 | /** 4 | * Test the step back and advance functionality for uniformity. Due to unsigned 5 | * long math first this method wasn't working as expected and the output was 6 | * compared to the original c++ source whose code can be found at 7 | * src/test/c++/UnsignedMath.cpp 8 | * 9 | * @author Kilian 10 | * 11 | */ 12 | public class AdvanceBackup { 13 | 14 | private static long state = 5; 15 | private static long curStep = 0; 16 | 17 | public static void main(String[] args) { 18 | 19 | System.out.println("Java\n"); 20 | 21 | // Single step forwards 22 | performAdvance(1, 10); 23 | 24 | // Single step backwards 25 | performAdvance(-1, 10); 26 | 27 | performAdvance(3, 10); 28 | performAdvance(-3, 10); 29 | 30 | } 31 | 32 | public static void performAdvance(long delta, int iterations) { 33 | // Reset state 34 | long steps = delta; 35 | long curMult = 6364136223846793005L; 36 | long curPlus = 16; 37 | 38 | for (int i = 0; i < iterations; i++) { 39 | state = stepRight(state, steps, curMult, curPlus); 40 | curStep += delta; 41 | System.out.println(curStep + " " + Long.toUnsignedString(state)); 42 | } 43 | 44 | } 45 | 46 | public static long stepRight(long state, long steps, long mult, long inc) { 47 | 48 | long acc_mult = 1; 49 | long acc_plus = 0; 50 | 51 | long cur_plus = inc; 52 | long cur_mult = mult; 53 | 54 | while (Long.compareUnsigned(steps, 0) > 0) { 55 | if ((steps & 1) == 1) { // Last significant bit is 1 56 | acc_mult *= cur_mult; 57 | acc_plus = acc_plus * cur_mult + cur_plus; 58 | } 59 | cur_plus *= (cur_mult + 1); 60 | cur_mult *= cur_mult; 61 | steps = Long.divideUnsigned(steps, 2); 62 | } 63 | state = (acc_mult * state) + acc_plus; 64 | return state; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/mutable/MutableBooleanTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | /** 9 | * @author Kilian 10 | * 11 | */ 12 | class MutableBooleanTest { 13 | 14 | /** 15 | * Test method for {@link com.github.kilianB.mutable.MutableBoolean#getValue()}. 16 | */ 17 | @Test 18 | void testGetValue() { 19 | MutableBoolean i = new MutableBoolean(true); 20 | assertEquals(Boolean.TRUE, i.getValue()); 21 | } 22 | 23 | /** 24 | * Test method for 25 | * {@link com.github.kilianB.mutable.MutableBoolean#setValue(java.lang.Boolean)}. 26 | */ 27 | @Test 28 | void testSetValue() { 29 | MutableBoolean i = new MutableBoolean(true); 30 | i.setValue(Boolean.valueOf(Boolean.FALSE)); 31 | assertEquals(Boolean.FALSE, i.getValue()); 32 | } 33 | 34 | /** 35 | * Test method for 36 | * {@link com.github.kilianB.mutable.MutableBoolean#setValue(java.lang.Boolean)}. 37 | */ 38 | @Test 39 | void testSetValuePrimitive() { 40 | MutableBoolean i = new MutableBoolean(false); 41 | i.setValue(true); 42 | assertEquals(Boolean.TRUE, i.getValue()); 43 | } 44 | 45 | /** 46 | * Test method for 47 | * {@link com.github.kilianB.mutable.MutableBoolean#equals(java.lang.Object)}. 48 | */ 49 | @Test 50 | void testEqualsObject() { 51 | MutableBoolean i = new MutableBoolean(false); 52 | MutableBoolean i2 = new MutableBoolean(false); 53 | assertEquals(i, i2); 54 | 55 | } 56 | 57 | /** 58 | * Test method for 59 | * {@link com.github.kilianB.mutable.MutableBoolean#getAndIncrement()}. 60 | */ 61 | @Test 62 | void testInvertValueToogle() { 63 | MutableBoolean i = new MutableBoolean(false); 64 | i.invertValue(); 65 | assertTrue(i.getValue()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/tree/Result.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree; 2 | 3 | /** 4 | * Search result returned when querying the tree 5 | * 6 | * @author Kilian 7 | * 8 | */ 9 | public class Result implements Comparable> { 10 | /** 11 | * Value saved at the leaf node 12 | */ 13 | public T value; 14 | /** 15 | * The hamming distance to the actual hash supplied during the search 16 | */ 17 | public double distance; 18 | 19 | /** 20 | * The normalized hamming distance to the actual hash supplied during the search 21 | */ 22 | public double normalizedHammingDistance; 23 | 24 | public Result(T value, double distance, double normalizedDistance) { 25 | this.value = value; 26 | this.distance = distance; 27 | this.normalizedHammingDistance = normalizedDistance; 28 | } 29 | 30 | @Override 31 | public int compareTo(Result o) { 32 | return normalizedHammingDistance > o.normalizedHammingDistance ? 1 : normalizedHammingDistance == o.normalizedHammingDistance ? 0 : -1; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | final int prime = 31; 38 | int result = 1; 39 | result = prime * result + ((value == null) ? 0 : value.hashCode()); 40 | return result; 41 | } 42 | 43 | @SuppressWarnings("rawtypes") 44 | @Override 45 | public boolean equals(Object obj) { 46 | if (this == obj) 47 | return true; 48 | if (obj == null) 49 | return false; 50 | if (getClass() != obj.getClass()) 51 | return false; 52 | Result other = (Result) obj; 53 | if (value == null) { 54 | if (other.value != null) 55 | return false; 56 | } else if (!value.equals(other.value)) 57 | return false; 58 | return true; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return String.format("Result Distance:%.3f Normalized Distance %.3f, Value:%s", distance, normalizedHammingDistance, value); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/RotAverageHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 12 | import com.jstarcraft.dip.lsh.RotAverageHash; 13 | 14 | /** 15 | * @author Kilian 16 | * 17 | */ 18 | class RotAverageHashTest { 19 | 20 | /** 21 | * The algorithms id shall stay consistent throughout different instances of the 22 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 23 | * from strings and integers are by contract consistent. 24 | */ 25 | @Test 26 | @DisplayName("Consistent AlgorithmIds") 27 | public void consistency() { 28 | 29 | assertAll(() -> { 30 | assertEquals(1505795238, new RotAverageHash(14).algorithmId()); 31 | }, () -> { 32 | assertEquals(1506471782, new RotAverageHash(25).algorithmId()); 33 | }); 34 | } 35 | 36 | @Test 37 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 38 | public void notVersionTwo() { 39 | assertAll(() -> { 40 | assertNotEquals(-1456716412, new RotAverageHash(14).algorithmId()); 41 | }, () -> { 42 | assertNotEquals(-1456694588, new RotAverageHash(25).algorithmId()); 43 | }); 44 | } 45 | 46 | //Base Hashing algorithm tests 47 | @Nested 48 | class AlgorithmBaseTests extends RotationalTestBase { 49 | @Override 50 | protected HashingAlgorithm getInstance(int bitResolution) { 51 | return new RotAverageHash(bitResolution); 52 | } 53 | 54 | @Override 55 | protected double differenceBallonHqHash() { 56 | return 65; 57 | } 58 | 59 | @Override 60 | protected double normDifferenceBallonHqHash() { 61 | return 65 / 128d; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/RotationalTestBase.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static com.github.kilianB.TestResources.lenna; 4 | import static com.github.kilianB.TestResources.lenna180; 5 | import static com.github.kilianB.TestResources.lenna270; 6 | import static com.github.kilianB.TestResources.lenna90; 7 | import static org.junit.jupiter.api.Assertions.assertAll; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | import java.util.stream.Stream; 11 | 12 | import org.junit.jupiter.params.ParameterizedTest; 13 | import org.junit.jupiter.params.provider.MethodSource; 14 | 15 | import com.github.kilianB.ArrayUtil; 16 | import com.jstarcraft.dip.hash.Hash; 17 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 18 | 19 | /** 20 | * @author Kilian 21 | * 22 | */ 23 | public abstract class RotationalTestBase extends HashTestBase { 24 | 25 | @ParameterizedTest 26 | @MethodSource(value = "com.jstarcraft.dip.lsh.RotationalTestBase#bitResolution") 27 | public void rotatedImages(Integer bitResolution) { 28 | 29 | HashingAlgorithm h = getInstance(bitResolution + this.offsetBitResolution()); 30 | Hash rot0 = h.hash(lenna); 31 | Hash rot90 = h.hash(lenna90); 32 | Hash rot180 = h.hash(lenna180); 33 | Hash rot270 = h.hash(lenna270); 34 | 35 | assertAll(() -> { 36 | assertTrue(rot0.normalizedHammingDistance(rot90) < 0.2); 37 | }, () -> { 38 | assertTrue(rot0.normalizedHammingDistance(rot180) < 0.2); 39 | }, () -> { 40 | assertTrue(rot0.normalizedHammingDistance(rot270) < 0.2); 41 | }, () -> { 42 | assertTrue(rot90.normalizedHammingDistance(rot180) < 0.2); 43 | }, () -> { 44 | assertTrue(rot90.normalizedHammingDistance(rot270) < 0.2); 45 | }, () -> { 46 | assertTrue(rot180.normalizedHammingDistance(rot270) < 0.2); 47 | }); 48 | } 49 | 50 | public static Stream bitResolution() { 51 | Integer[] ints = new Integer[10]; 52 | ArrayUtil.fillArray(ints, (index) -> { 53 | return index * 2 + 20; 54 | }); 55 | return Stream.of(ints); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/MedianHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 12 | import com.jstarcraft.dip.lsh.MedianHash; 13 | 14 | /** 15 | * @author Kilian 16 | * 17 | */ 18 | class MedianHashTest { 19 | 20 | @Nested 21 | @DisplayName("Algorithm Id") 22 | class AlgorithmId { 23 | /** 24 | * The algorithms id shall stay consistent throughout different instances of the 25 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 26 | * from strings and integers are by contract consistent. 27 | */ 28 | @Test 29 | @DisplayName("Consistent AlgorithmIds") 30 | public void consistency() { 31 | 32 | assertAll(() -> { 33 | assertEquals(552703146, new MedianHash(14).algorithmId()); 34 | }, () -> { 35 | assertEquals(552733898, new MedianHash(25).algorithmId()); 36 | }); 37 | } 38 | 39 | @Test 40 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 41 | public void notVersionTwo() { 42 | assertAll(() -> { 43 | assertNotEquals(-442972544, new MedianHash(14).algorithmId()); 44 | }, () -> { 45 | assertNotEquals(-442971552, new MedianHash(25).algorithmId()); 46 | }); 47 | } 48 | } 49 | 50 | // Base Hashing algorithm tests 51 | @Nested 52 | class AlgorithmBaseTests extends HashTestBase { 53 | 54 | @Override 55 | protected HashingAlgorithm getInstance(int bitResolution) { 56 | return new MedianHash(bitResolution); 57 | } 58 | 59 | @Override 60 | protected double differenceBallonHqHash() { 61 | return 78; 62 | } 63 | 64 | @Override 65 | protected double normDifferenceBallonHqHash() { 66 | return 78 / 132d; 67 | } 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/tree/binaryTree/Node.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree.binaryTree; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A tree node saving references to it's children. 7 | * 8 | * @author Kilian 9 | * 10 | */ 11 | public class Node implements Serializable { 12 | 13 | private static final long serialVersionUID = 9168509020498037545L; 14 | 15 | public Node leftChild; // 1 16 | public Node rightChild; // 0 17 | 18 | public Node getChild(boolean left) { 19 | return left ? leftChild : rightChild; 20 | } 21 | 22 | /** 23 | * Create and set a child of the current node 24 | * 25 | * @param left if true create the left child if false create the right child 26 | * @return the created node 27 | */ 28 | public Node createChild(boolean left) { 29 | return setChild(left, new Node()); 30 | } 31 | 32 | public Node setChild(boolean left, Node newNode) { 33 | if (left) { 34 | leftChild = newNode; 35 | } else { 36 | rightChild = newNode; 37 | } 38 | return newNode; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | final int prime = 31; 44 | int result = 1; 45 | result = prime * result + ((leftChild == null) ? 0 : leftChild.hashCode()); 46 | result = prime * result + ((rightChild == null) ? 0 : rightChild.hashCode()); 47 | return result; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object obj) { 52 | if (this == obj) { 53 | return true; 54 | } 55 | if (obj == null) { 56 | return false; 57 | } 58 | if (!(obj instanceof Node)) { 59 | return false; 60 | } 61 | Node other = (Node) obj; 62 | if (leftChild == null) { 63 | if (other.leftChild != null) { 64 | return false; 65 | } 66 | } else if (!leftChild.equals(other.leftChild)) { 67 | return false; 68 | } 69 | if (rightChild == null) { 70 | if (other.rightChild != null) { 71 | return false; 72 | } 73 | } else if (!rightChild.equals(other.rightChild)) { 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/PerceptiveHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 12 | import com.jstarcraft.dip.lsh.PerceptiveHash; 13 | 14 | class PerceptiveHashTest { 15 | 16 | @Nested 17 | @DisplayName("Algorithm Id") 18 | class AlgorithmId { 19 | 20 | /** 21 | * The algorithms id shall stay consistent throughout different instances of the 22 | * jvm. While simple hashcodes do not guarantee this behaviour hash codes 23 | * created from strings and integers are by contract consistent. 24 | */ 25 | @Test 26 | @DisplayName("Consistent AlgorithmIds") 27 | public void consistency() { 28 | 29 | assertAll(() -> { 30 | assertEquals(2038088856, new PerceptiveHash(14).algorithmId()); // Was 748566082 31 | }, () -> { 32 | assertEquals(2041902104, new PerceptiveHash(25).algorithmId()); // Was 748566093 33 | }); 34 | } 35 | 36 | @Test 37 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 38 | public void notVersionTwo() { 39 | assertAll(() -> { 40 | assertNotEquals(1062023020, new PerceptiveHash(14).algorithmId()); 41 | }, () -> { 42 | assertNotEquals(1062146028, new PerceptiveHash(25).algorithmId()); 43 | }); 44 | } 45 | } 46 | 47 | // Base Hashing algorithm tests 48 | @Nested 49 | class AlgorithmBaseTests extends HashTestBase { 50 | 51 | @Override 52 | protected HashingAlgorithm getInstance(int bitResolution) { 53 | return new PerceptiveHash(bitResolution); 54 | } 55 | 56 | @Override 57 | protected double differenceBallonHqHash() { 58 | return 67; 59 | } 60 | 61 | @Override 62 | protected double normDifferenceBallonHqHash() { 63 | return 67 / 132d; 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/MedianHash.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import java.math.BigInteger; 4 | 5 | import com.github.kilianB.ArrayUtil; 6 | import com.jstarcraft.dip.color.ColorPixel; 7 | 8 | /** 9 | * Calculate a hash value based on the median luminosity in an image. 10 | * 11 | *

12 | * Really good performance almost comparable to average hash. So far does a 13 | * better job matching images if watermarks are added but trades this off for a 14 | * little bit worse detection rating if handling rescaled images. 15 | * 16 | *

17 | * - Slower to compute 18 | * 19 | * @author Kilian 20 | * @since 2.0.0 21 | */ 22 | public class MedianHash extends AverageHash { 23 | 24 | /** 25 | * @param bitResolution The bit resolution specifies the final length of the 26 | * generated hash. A higher resolution will increase 27 | * computation time and space requirement while being able 28 | * to track finer detail in the image. Be aware that a high 29 | * key is not always desired. 30 | *

31 | * 32 | * The median hash will produce a hash with at least the 33 | * number of bits defined by this argument. In turn this 34 | * also means that different bit resolutions may be mapped 35 | * to the same final key length. 36 | * 37 | *

38 |      *  64 = 8x8 = 65 bit key
39 |      *  128 = 11.3 -> 12 -> 144 bit key
40 |      *  256 = 16 x 16 = 256 bit key
41 |      *                      
42 | */ 43 | public MedianHash(int bitResolution) { 44 | super(bitResolution); 45 | } 46 | 47 | @Override 48 | protected BigInteger hash(ColorPixel pixel, HashBuilder hash) { 49 | int[] vector = pixel.getLuminanceVector(); 50 | 51 | int[][] luminance = pixel.getLuminanceMatrix(); 52 | 53 | // Create hash 54 | return computeHash(hash, luminance, ArrayUtil.median(vector)); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/supervised/randomForest/InnerNode.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize.supervised.randomForest; 2 | 3 | import java.awt.image.BufferedImage; 4 | 5 | import com.github.kilianB.StringUtil; 6 | import com.jstarcraft.dip.hash.FuzzyHash; 7 | import com.jstarcraft.dip.hash.Hash; 8 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 9 | 10 | class InnerNode extends TreeNode { 11 | 12 | private FuzzyHash internalHash; 13 | private HashingAlgorithm hasher; 14 | private double threshold; 15 | 16 | // Not really entropy in all cases 17 | protected double quality; 18 | protected double qualityLeft; 19 | protected double qualityRight; 20 | 21 | protected TreeNode leftNode; 22 | protected TreeNode rightNode; 23 | 24 | protected InnerNode() { 25 | }; 26 | 27 | /** 28 | * @param hasher 29 | * @param bestCutoff 30 | */ 31 | public InnerNode(FuzzyHash internalHash, HashingAlgorithm hasher, double bestCutoff, double quality, double qualityLeft, 32 | double qualityRight) { 33 | super(); 34 | this.internalHash = internalHash; 35 | this.hasher = hasher; 36 | this.threshold = bestCutoff; 37 | this.quality = quality; 38 | this.qualityLeft = qualityLeft; 39 | this.qualityRight = qualityRight; 40 | } 41 | 42 | 43 | 44 | public int[] predictAgainstAll(BufferedImage bi) { 45 | 46 | Hash targetHash = hasher.hash(bi); 47 | 48 | double distance = internalHash.normalizedHammingDistance(targetHash); 49 | 50 | if (distance < threshold) { 51 | return leftNode.predictAgainstAll(bi); 52 | } else { 53 | return rightNode.predictAgainstAll(bi); 54 | } 55 | } 56 | 57 | 58 | 59 | @Override 60 | public String toString() { 61 | return "InnerNode [internalHash=" + internalHash + ", hasher=" + hasher + ", threshold=" + threshold 62 | + ", quality=" + quality + "]"; 63 | } 64 | 65 | public void printTree() { 66 | printTree(0); 67 | } 68 | 69 | public void printTree(int depth) { 70 | System.out.println(StringUtil.multiplyChar("\t", depth) + this); 71 | leftNode.printTree(++depth); 72 | 73 | if (rightNode != null) 74 | rightNode.printTree(depth); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/MutableBoolean.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Mutable class wrapper for boolean values. Mutable classes are useful in 7 | * lambda expressions or anonymous classes which want to alter the content of a 8 | * variable but are limited to final or effective final variables. 9 | * 10 | * @author Kilian 11 | * @since 1.0.0 12 | */ 13 | public class MutableBoolean implements Mutable, Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = 126801622569995551L; 16 | 17 | private boolean field; 18 | 19 | /** 20 | * Create a mutable Boolean with an initial value of False 21 | */ 22 | public MutableBoolean() { 23 | }; 24 | 25 | /** 26 | * Create a mutable Boolean. 27 | * 28 | * @param initialValue the initial value of the integer 29 | */ 30 | public MutableBoolean(boolean initialValue) { 31 | this.field = initialValue; 32 | } 33 | 34 | @Override 35 | public Boolean getValue() { 36 | return field; 37 | } 38 | 39 | @Override 40 | public void setValue(Boolean newValue) { 41 | this.field = newValue; 42 | } 43 | 44 | /** 45 | * Set the internal field to the new value 46 | * 47 | * @param newValue the new value 48 | * @since 1.2.0 49 | */ 50 | public void setValue(boolean newValue) { 51 | this.field = newValue; 52 | } 53 | 54 | public boolean booleanValue() { 55 | return field; 56 | } 57 | 58 | /** 59 | * Invert the value of the internal field 60 | */ 61 | public void invertValue() { 62 | field = !field; 63 | } 64 | 65 | @Override 66 | public int compareTo(MutableBoolean o) { 67 | return (this.field == o.field) ? 0 : (this.field ? 1 : -1); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return (field ? 1231 : 1237); 73 | } 74 | 75 | @Override 76 | public boolean equals(Object obj) { 77 | if (this == obj) 78 | return true; 79 | if (obj == null) 80 | return false; 81 | if (getClass() != obj.getClass()) 82 | return false; 83 | MutableBoolean other = (MutableBoolean) obj; 84 | return field == other.field; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/datastructures/CircularLinkedHashMapTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import java.util.Map.Entry; 7 | import java.util.Set; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | /** 12 | * @author Kilian 13 | * 14 | */ 15 | class CircularLinkedHashMapTest { 16 | 17 | @Test 18 | void invalidConstructorZero() { 19 | assertThrows(IllegalArgumentException.class, () -> { 20 | new CircularLinkedHashMap<>(0); 21 | }); 22 | } 23 | 24 | @Test 25 | void invalidConstructorNegative() { 26 | assertThrows(IllegalArgumentException.class, () -> { 27 | new CircularLinkedHashMap<>(-1); 28 | }); 29 | } 30 | 31 | @Test 32 | void preservesOrder() { 33 | CircularLinkedHashMap map = new CircularLinkedHashMap<>(4); 34 | 35 | for (int i = 0; i < 4; i++) { 36 | map.put(Integer.valueOf(i), Integer.valueOf(i)); 37 | } 38 | 39 | Set> entries = map.entrySet(); 40 | 41 | int i = 0; 42 | for (Entry entry : entries) { 43 | assertEquals(Integer.valueOf(i), entry.getKey(), "Key not as expected"); 44 | assertEquals(Integer.valueOf(i), entry.getValue(), "Value not as expected"); 45 | i++; 46 | } 47 | } 48 | 49 | @Test 50 | void maxSize() { 51 | CircularLinkedHashMap map = new CircularLinkedHashMap<>(4); 52 | for (int i = 0; i < 5; i++) { 53 | map.put(Integer.valueOf(i), Integer.valueOf(i)); 54 | } 55 | assertEquals(4, map.size()); 56 | } 57 | 58 | @Test 59 | void removeOldestValues() { 60 | CircularLinkedHashMap map = new CircularLinkedHashMap<>(4); 61 | 62 | for (int i = 0; i < 7; i++) { 63 | map.put(Integer.valueOf(i), Integer.valueOf(i)); 64 | } 65 | Set> entries = map.entrySet(); 66 | int i = 3; 67 | for (Entry entry : entries) { 68 | assertEquals(Integer.valueOf(i), entry.getKey(), "Key not as expected"); 69 | assertEquals(Integer.valueOf(i), entry.getValue(), "Value not as expected"); 70 | i++; 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/GrayscaleHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | /** 12 | * @author Kilian 13 | * 14 | */ 15 | class GrayscaleHashTest { 16 | 17 | @Nested 18 | @DisplayName("Algorithm Id") 19 | class AlgorithmId { 20 | /** 21 | * The algorithms id shall stay consistent throughout different instances of the 22 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 23 | * from strings and integers are by contract consistent. 24 | */ 25 | @Test 26 | @DisplayName("Consistent AlgorithmIds") 27 | public void consistency() { 28 | assertAll(() -> { 29 | assertEquals(471804845, new GrayscaleHash(14).algorithmId()); 30 | }, () -> { 31 | assertEquals(471835597, new GrayscaleHash(25).algorithmId()); 32 | }); 33 | } 34 | 35 | @Test 36 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 37 | public void notVersionTwo() { 38 | assertAll(() -> { 39 | assertNotEquals(1308249156, new GrayscaleHash(14).algorithmId()); 40 | }, () -> { 41 | assertNotEquals(1308249156, new GrayscaleHash(14).algorithmId()); 42 | }); 43 | } 44 | } 45 | 46 | // Base Hashing algorithm tests 47 | @Nested 48 | class AlgorithmBaseTests extends HashTestBase { 49 | 50 | @Override 51 | protected HashingAlgorithm getInstance(int bitResolution) { 52 | return new GrayscaleHash(bitResolution); 53 | } 54 | 55 | @Override 56 | protected double differenceBallonHqHash() { 57 | return 76; 58 | } 59 | 60 | @Override 61 | protected double normDifferenceBallonHqHash() { 62 | return 76 / 132d; 63 | } 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/exotic/SingleImageMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.exotic; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.Map.Entry; 7 | 8 | import javax.imageio.ImageIO; 9 | 10 | import com.github.kilianB.matcher.TypedImageMatcher; 11 | import com.jstarcraft.dip.hash.Hash; 12 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 13 | 14 | /** 15 | * Convenience class to chain multiple hashing algorithms to check if two images 16 | * are similar. In order for images to be considered similar all supplied 17 | * hashing algorithms have to agree that the images match. 18 | * 19 | * @author Kilian 20 | * 21 | */ 22 | public class SingleImageMatcher extends TypedImageMatcher { 23 | 24 | /** 25 | * Execute all supplied hashing algorithms in the order they were supplied and 26 | * check if the images are similar 27 | * 28 | * @param image First input image 29 | * @param image1 Second input image 30 | * @return true if images are considered similar 31 | * @throws IOException if an error occurred during image loading 32 | * @see #checkSimilarity(BufferedImage, BufferedImage) 33 | */ 34 | public boolean checkSimilarity(File image, File image1) throws IOException { 35 | return (checkSimilarity(ImageIO.read(image), ImageIO.read(image1))); 36 | } 37 | 38 | /** 39 | * Execute all supplied hashing algorithms in the order they were supplied and 40 | * check if the images are similar 41 | * 42 | * @param image First input image 43 | * @param image1 Second input image 44 | * @return true if images are considered similar 45 | */ 46 | public boolean checkSimilarity(BufferedImage image, BufferedImage image1) { 47 | if (steps.isEmpty()) 48 | throw new IllegalStateException("Please supply at least one hashing algorithm prior to invoking the match method"); 49 | 50 | for (Entry entry : steps.entrySet()) { 51 | Hash hash = entry.getKey().hash(image); 52 | Hash hash1 = entry.getKey().hash(image1); 53 | 54 | // Check if the hashing algo is within the threshold. If it's not return early 55 | if (!entry.getValue().apply(hash, hash1)) { 56 | return false; 57 | } 58 | } 59 | // Everything matched 60 | return true; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/GrayscaleHash.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import java.math.BigInteger; 4 | 5 | import com.github.kilianB.ArrayUtil; 6 | import com.jstarcraft.dip.color.ColorPixel; 7 | 8 | /** 9 | * Calculate a hash value based on the average rgb color in an image. 10 | * 11 | * @author Kilian 12 | * @since 2.0.0 similar to ahash from version 1.0.0 13 | */ 14 | // 参考Kilian的AverageColorHash 15 | public class GrayscaleHash extends AverageHash { 16 | 17 | /** 18 | * @param bitResolution The bit resolution specifies the final length of the 19 | * generated hash. A higher resolution will increase 20 | * computation time and space requirement while being able 21 | * to track finer detail in the image. Be aware that a high 22 | * key is not always desired. 23 | *

24 | * 25 | * The average hash requires to re scale the base image 26 | * according to the required bit resolution. If the square 27 | * root of the bit resolution is not a natural number the 28 | * resolution will be rounded to the next whole number. 29 | *

30 | * 31 | * The average hash will produce a hash with at least the 32 | * number of bits defined by this argument. In turn this 33 | * also means that different bit resolutions may be mapped 34 | * to the same final key length. 35 | * 36 | *
37 |      *  64 = 8x8 = 65 bit key
38 |      *  128 = 11.3 -> 12 -> 144 bit key
39 |      *  256 = 16 x 16 = 256 bit key
40 |      *                      
41 | */ 42 | public GrayscaleHash(int bitResolution) { 43 | super(bitResolution); 44 | } 45 | 46 | @Override 47 | protected BigInteger hash(ColorPixel pixel, HashBuilder hash) { 48 | int[][] grayscale = pixel.getGrayscaleMatrix(); 49 | 50 | // Calculate the average color of the entire image 51 | double average = ArrayUtil.average(grayscale); 52 | 53 | // Create hash 54 | return computeHash(hash, grayscale, average); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/KMeansPlusPlus.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import java.util.Random; 4 | 5 | import com.github.kilianB.ArrayUtil; 6 | import com.github.kilianB.pcg.fast.PcgRSFast; 7 | import com.jstarcraft.dip.hash.FuzzyHash; 8 | import com.jstarcraft.dip.hash.Hash; 9 | 10 | /** 11 | * Kmeans plus plus implementation. Opposed to Kmeans this algorithm 12 | * strategically chooses it's starting clusters to decrease iteration time at 13 | * the later stage 14 | * 15 | * @author Kilian 16 | * 17 | */ 18 | public class KMeansPlusPlus extends KMeans { 19 | 20 | public KMeansPlusPlus(int clusters) { 21 | super(clusters); 22 | } 23 | 24 | @Override 25 | protected FuzzyHash[] computeStartingClusters(Hash[] hashes) { 26 | 27 | // Fast high quality rng 28 | Random rng = new PcgRSFast(); 29 | 30 | FuzzyHash[] clusterMeans = new FuzzyHash[k]; 31 | 32 | ArrayUtil.fillArrayMulti(clusterMeans, () -> { 33 | return new FuzzyHash(); 34 | }); 35 | 36 | // Randomly choose a starting point. Initial vector 37 | clusterMeans[0].mergeFast(hashes[rng.nextInt(hashes.length)]); 38 | 39 | for (int cluster = 1; cluster < k; cluster++) { 40 | 41 | // Choose a random cluster center with probability equal to the squared distance 42 | // of the closest existing center 43 | double[] distance = new double[hashes.length]; 44 | ArrayUtil.fillArray(distance, () -> { 45 | return Double.MAX_VALUE; 46 | }); 47 | 48 | double sum = 0; 49 | 50 | // For each point 51 | for (int i = 0; i < hashes.length; i++) { 52 | 53 | // find the minimum distance to all already existing clusters 54 | for (int j = 0; j < cluster; j++) { 55 | double distTemp = clusterMeans[j].normalizedHammingDistanceFast(hashes[i]); 56 | distTemp *= distTemp; 57 | if (distTemp < distance[i]) { 58 | distance[i] = distTemp; 59 | } 60 | } 61 | sum += distance[i]; 62 | } 63 | int index = 0; 64 | double rand = rng.nextDouble() * sum; 65 | double runningSum = distance[0]; 66 | for (; index < hashes.length; index++) { 67 | if (rand <= runningSum) { 68 | break; 69 | } 70 | runningSum += distance[index + 1]; 71 | } 72 | clusterMeans[cluster].mergeFast(hashes[index]); 73 | } 74 | return clusterMeans; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/CategorizationResult.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize; 2 | 3 | /** 4 | * A categorization result describes the membership to a cluster. It contains 5 | * the identifier of the category this image was matched to as well as a quality 6 | * measurement. 7 | * 8 | *

9 | * The quality metric is depended on the the actual implementation of the 10 | * categorizer. 11 | * 12 | *

13 | * Categorization results can be nested in case that multiple categorizers are 14 | * chained together. The first result will point to the categorization result of 15 | * the main matcher. The next to the first nested categorizer. 16 | * 17 | *

18 | * Categorization results are only valid at the time they are created and may 19 | * represent an invalid state as soon as the categorizer who produces this stage 20 | * was changed. 21 | * 22 | * @author Kilian 23 | * 24 | * @since 3.0.0 25 | * @see com.github.kilianB.matcher.categorize.CategoricalImageMatcher 26 | */ 27 | public class CategorizationResult { 28 | 29 | protected int category; 30 | protected double qualityMeasurement; 31 | 32 | // TODO not yet implemented 33 | // /** In case of nested matchers this will point to the next result */ 34 | protected CategorizationResult subResult = null; 35 | 36 | public CategorizationResult(int category, double qualityMeasurement) { 37 | this.category = category; 38 | this.qualityMeasurement = qualityMeasurement; 39 | } 40 | 41 | /** 42 | * Get the category of this result object. The category uniquely identifies the 43 | * cluster this image was matched to. 44 | * 45 | * @return the category 46 | */ 47 | public int getCategory() { 48 | return category; 49 | } 50 | 51 | // public List getAllCategories() { 52 | // List cRes = new ArrayList<>(); 53 | // CategorizationResult temp = subResult; 54 | // do { 55 | // cRes.add(temp); 56 | // } while ((temp = temp.subResult) != null); 57 | // return cRes; 58 | // } 59 | 60 | public double getQuality() { 61 | return qualityMeasurement; 62 | } 63 | 64 | public void addCategory(CategorizationResult catgeorizationResult) { 65 | this.subResult = catgeorizationResult; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "CategorizationResult [category=" + category + ", qualityMeasurement=" + qualityMeasurement + "]"; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/HogHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Nested; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 13 | import com.jstarcraft.dip.lsh.experimental.HogHash; 14 | 15 | /** 16 | * @author Kilian 17 | * 18 | */ 19 | @SuppressWarnings("deprecation") 20 | class HogHashTest { 21 | 22 | @Nested 23 | @DisplayName("Algorithm Id") 24 | class AlgorithmId { 25 | 26 | /** 27 | * The algorithms id shall stay consistent throughout different instances of the 28 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 29 | * from strings and integers are by contract consistent. 30 | */ 31 | @Test 32 | @DisplayName("Consistent AlgorithmIds") 33 | public void consistency() { 34 | 35 | assertAll(() -> { 36 | assertEquals(-789235684, new HogHash(14).algorithmId()); 37 | }, () -> { 38 | assertEquals(-730130340, new HogHash(25).algorithmId()); 39 | }); 40 | } 41 | 42 | @Test 43 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 44 | public void notVersionTwo() { 45 | assertAll(() -> { 46 | assertNotEquals(769691726, new HogHash(14).algorithmId()); 47 | }, () -> { 48 | assertNotEquals(771598350, new HogHash(25).algorithmId()); 49 | }); 50 | } 51 | } 52 | 53 | @Test 54 | public void illegalConstructor() { 55 | assertThrows(IllegalArgumentException.class, () -> { 56 | new HogHash(2); 57 | }); 58 | } 59 | 60 | // Base Hashing algorithm tests 61 | @Nested 62 | class AlgorithmBaseTests extends HashTestBase { 63 | 64 | @Override 65 | protected HashingAlgorithm getInstance(int bitResolution) { 66 | return new HogHash(bitResolution); 67 | } 68 | 69 | // Hog hash requires higher bit resolution. override default offset 70 | @Override 71 | protected int offsetBitResolution() { 72 | return 10; 73 | } 74 | 75 | @Override 76 | protected double differenceBallonHqHash() { 77 | return 50; 78 | } 79 | 80 | @Override 81 | protected double normDifferenceBallonHqHash() { 82 | return 50 / 144d; 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/kernel/MaximumKernelTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.kernel; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.github.kilianB.ArrayUtil; 9 | import com.jstarcraft.dip.lsh.kernel.Kernel; 10 | import com.jstarcraft.dip.lsh.kernel.MaximumKernel; 11 | 12 | /** 13 | * @author Kilian 14 | * 15 | */ 16 | class MaximumKernelTest { 17 | 18 | @Test 19 | public void identity() { 20 | double[][] arr = new double[10][10]; 21 | ArrayUtil.fillArrayMulti(arr, (index) -> { 22 | return (double) index; 23 | }); 24 | MaximumKernel kernel = new MaximumKernel(1, 1); 25 | double[][] result = kernel.apply(arr); 26 | assertArrayEquals(arr, result); 27 | } 28 | 29 | @Test 30 | public void sameDimension1D() { 31 | double[][] input = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; 32 | Kernel k = new MaximumKernel(3, 1); 33 | double[][] result = k.apply(input); 34 | assertEquals(input.length, result.length); 35 | assertEquals(input[0].length, result[0].length); 36 | } 37 | 38 | @Test 39 | public void sameDimension2DSquared() { 40 | double[][] input = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 } }; 41 | Kernel k = new MaximumKernel(3, 1); 42 | double[][] result = k.apply(input); 43 | assertEquals(input.length, result.length); 44 | assertEquals(input[0].length, result[0].length); 45 | } 46 | 47 | @Test 48 | public void sameDimension2D() { 49 | double[][] input = { { 0, 1, 2 }, { 0, 1, 2 } }; 50 | Kernel k = new MaximumKernel(3, 1); 51 | double[][] result = k.apply(input); 52 | assertEquals(input.length, result.length); 53 | assertEquals(input[0].length, result[0].length); 54 | 55 | } 56 | 57 | @Test 58 | public void noMask1D() { 59 | double[][] arr = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 } }; 60 | double[][] res = { { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13 } }; 61 | MaximumKernel kernel = new MaximumKernel(3, 1); 62 | double[][] result = kernel.apply(arr); 63 | assertArrayEquals(res, result); 64 | } 65 | 66 | @Test 67 | public void mask1D() { 68 | double[][] mask = { { 1, 3, 1 } }; 69 | double[][] arr = { { 0, 1, 4, 2, 3, 5 } }; 70 | double[][] res = { { 1, 4, 4, 2, 3, 5 } }; 71 | MaximumKernel kernel = new MaximumKernel(mask); 72 | double[][] result = kernel.apply(arr); 73 | assertArrayEquals(res, result); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/MutableFloat.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Mutable class wrapper for float values. Mutable classes are useful in lambda 7 | * expressions or anonymous classes which want to alter the content of a 8 | * variable but are limited to final or effective final variables. 9 | * 10 | * @author Kilian 11 | * @since 1.0.0 12 | */ 13 | public class MutableFloat extends Number implements Mutable, Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = 6846548022746719522L; 16 | 17 | private float field; 18 | 19 | /** 20 | * Create a mutable Float with an initial value of 0 21 | */ 22 | public MutableFloat() { 23 | }; 24 | 25 | /** 26 | * Create a mutable Float. 27 | * 28 | * @param initialValue the initial value of the float 29 | */ 30 | public MutableFloat(float initialValue) { 31 | this.field = initialValue; 32 | } 33 | 34 | @Override 35 | public int compareTo(MutableFloat o) { 36 | return Float.compare(field, o.field); 37 | } 38 | 39 | @Override 40 | public Float getValue() { 41 | return Float.valueOf(field); 42 | } 43 | 44 | @Override 45 | public void setValue(Float newValue) { 46 | field = newValue; 47 | } 48 | 49 | /** 50 | * Set the internal field to the new value 51 | * 52 | * @param newValue the new value 53 | * @since 1.2.0 54 | */ 55 | public void setValue(float newValue) { 56 | field = newValue; 57 | } 58 | 59 | /** 60 | * @return the current value as float primitive 61 | */ 62 | public float floatValue() { 63 | return field; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Float.floatToIntBits(field); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object obj) { 73 | if (this == obj) 74 | return true; 75 | if (obj == null) 76 | return false; 77 | if (getClass() != obj.getClass()) 78 | return false; 79 | MutableFloat other = (MutableFloat) obj; 80 | if (Float.floatToIntBits(field) != Float.floatToIntBits(other.field)) 81 | return false; 82 | return true; 83 | } 84 | 85 | @Override 86 | public int intValue() { 87 | return (int) field; 88 | } 89 | 90 | @Override 91 | public long longValue() { 92 | return (long) field; 93 | } 94 | 95 | @Override 96 | public double doubleValue() { 97 | return field; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/matcher/categorize/CategorizeBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize; 2 | 3 | import static com.github.kilianB.TestResources.ballon; 4 | import static com.github.kilianB.TestResources.copyright; 5 | import static com.github.kilianB.TestResources.highQuality; 6 | import static com.github.kilianB.TestResources.lenna; 7 | import static com.github.kilianB.TestResources.lowQuality; 8 | import static com.github.kilianB.TestResources.thumbnail; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 11 | 12 | import org.junit.jupiter.api.Test; 13 | 14 | /** 15 | * @author Kilian 16 | * 17 | */ 18 | public abstract class CategorizeBaseTest { 19 | 20 | @Test 21 | void distanceIdentity() { 22 | CategoricalImageMatcher matcher = getInstance(); 23 | 24 | CategorizationResult pair = matcher.categorizeImageAndAdd(ballon, "ballon"); 25 | // Category 26 | assertEquals(0, (int) pair.getCategory()); 27 | // Dostance 28 | assertEquals(0, (double) pair.getQuality()); 29 | 30 | } 31 | 32 | @Test 33 | public void categorizeMultipleImages() { 34 | 35 | CategoricalImageMatcher matcher = getInstance(); 36 | 37 | matcher.categorizeImageAndAdd(ballon, "ballon"); 38 | matcher.categorizeImageAndAdd(copyright, "copyright"); 39 | matcher.categorizeImageAndAdd(highQuality, "highQuality"); 40 | matcher.categorizeImageAndAdd(thumbnail, "thumbnail"); 41 | matcher.categorizeImageAndAdd(lowQuality, "lowQuality"); 42 | matcher.categorizeImageAndAdd(lenna, "lena"); 43 | matcher.recomputeCategories(); 44 | assertEquals(3, matcher.getCategories().size()); 45 | 46 | int ballonCategory = matcher.getCategory("ballon"); 47 | 48 | int copyRightCategory = matcher.getCategory("copyright"); 49 | 50 | int lenaCategory = matcher.getCategory("lena"); 51 | 52 | assertNotEquals(ballonCategory, copyRightCategory); 53 | // TODO this rarely evaluates as true due to the random factor of kMeans. 54 | // but it should not due to KmeansPlusPlus. did we mess up a great than instead 55 | // of greater sign or 56 | // something? 57 | assertNotEquals(ballonCategory, lenaCategory); 58 | assertNotEquals(copyRightCategory, lenaCategory); 59 | 60 | assertEquals(copyRightCategory, matcher.getCategory("highQuality")); 61 | assertEquals(copyRightCategory, matcher.getCategory("thumbnail")); 62 | assertEquals(copyRightCategory, matcher.getCategory("lowQuality")); 63 | } 64 | 65 | abstract CategoricalImageMatcher getInstance(); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/MutableDouble.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Mutable class wrapper for double values. Mutable classes are useful in lambda 7 | * expressions or anonymous classes which want to alter the content of a 8 | * variable but are limited to final or effective final variables. 9 | * 10 | * @author Kilian 11 | * @since 1.0.0 12 | */ 13 | public class MutableDouble extends Number implements Mutable, Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = 6846548022746719522L; 16 | 17 | private double field; 18 | 19 | /** 20 | * Create a mutable Double with an initial value of 0 21 | */ 22 | public MutableDouble() { 23 | }; 24 | 25 | /** 26 | * Create a mutable Double. 27 | * 28 | * @param initialValue the initial value of the float 29 | */ 30 | public MutableDouble(double initialValue) { 31 | this.field = initialValue; 32 | } 33 | 34 | @Override 35 | public int compareTo(MutableDouble o) { 36 | return Double.compare(field, o.field); 37 | } 38 | 39 | @Override 40 | public Double getValue() { 41 | return Double.valueOf(field); 42 | } 43 | 44 | @Override 45 | public void setValue(Double newValue) { 46 | field = newValue; 47 | } 48 | 49 | /** 50 | * Set the internal field to the new value 51 | * 52 | * @param newValue the new value 53 | * @since 1.2.0 54 | */ 55 | public void setValue(double newValue) { 56 | field = newValue; 57 | } 58 | 59 | /** 60 | * @return the current value as double primitive 61 | */ 62 | public double doubleValue() { 63 | return field; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | long temp; 69 | temp = Double.doubleToLongBits(field); 70 | return (int) (temp ^ (temp >>> 32)); 71 | } 72 | 73 | @Override 74 | public boolean equals(Object obj) { 75 | if (this == obj) 76 | return true; 77 | if (obj == null) 78 | return false; 79 | if (getClass() != obj.getClass()) 80 | return false; 81 | MutableDouble other = (MutableDouble) obj; 82 | if (Double.doubleToLongBits(field) != Double.doubleToLongBits(other.field)) 83 | return false; 84 | return true; 85 | } 86 | 87 | @Override 88 | public int intValue() { 89 | return (int) field; 90 | } 91 | 92 | @Override 93 | public long longValue() { 94 | return (long) field; 95 | } 96 | 97 | @Override 98 | public float floatValue() { 99 | return (float) field; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/AbstractCategoricalMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.github.kilianB.matcher.PlainImageMatcher; 11 | 12 | /** 13 | * An abstract implementation of the categorical image matcher supporting 14 | * classes to keep track of the categories an image was matched to. 15 | *

16 | * It is up to the implementing class to keep the field 17 | * cachedImagesInCategory and reverseImageCategoryMap 18 | * in a valid state. 19 | * 20 | * @author Kilian 21 | * @since 3.0.0 22 | * 23 | */ 24 | public abstract class AbstractCategoricalMatcher extends PlainImageMatcher implements CategoricalImageMatcher { 25 | 26 | protected Map> cachedImagesInCategory = new HashMap<>(); 27 | protected Map reverseImageCategoryMap = new HashMap<>(); 28 | 29 | public abstract CategorizationResult categorizeImageAndAdd(BufferedImage bi, String uniqueId); 30 | 31 | @Override 32 | public int getCategory(String uniqueId) { 33 | int category = reverseImageCategoryMap.get(uniqueId); 34 | return category; 35 | } 36 | 37 | @Override 38 | public CategorizationResult categorizeImage(BufferedImage bi) { 39 | return categorizeImage(null, bi); 40 | } 41 | 42 | protected abstract CategorizationResult categorizeImage(String uniqueId, BufferedImage bi); 43 | 44 | /** 45 | * Check if an image has already been added to the categorizer 46 | * 47 | * @param uniqueId the unique id of the image 48 | * @return true if it has been added, false otherwise 49 | */ 50 | public boolean isCategorized(String uniqueId) { 51 | return reverseImageCategoryMap.containsKey(uniqueId); 52 | } 53 | 54 | @Override 55 | public List getCategories() { 56 | List categoriesAsList = new ArrayList<>(cachedImagesInCategory.keySet()); 57 | categoriesAsList.sort(null); 58 | return Collections.unmodifiableList(categoriesAsList); 59 | } 60 | 61 | @Override 62 | public List getImagesInCategory(int category) { 63 | return cachedImagesInCategory.get(category); 64 | } 65 | 66 | /** 67 | * Get the number of images that are were added in this category. 68 | * 69 | * @param category to retrieve the number of images from. 70 | * @return he number of images that were mapped and added to this category 71 | */ 72 | public int getImageCountInCategory(int category) { 73 | return cachedImagesInCategory.get(category).size(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/datastructures/tree/binaryTreeFuzzy/FuzzyBinaryTreeTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures.tree.binaryTreeFuzzy; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Disabled; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.github.kilianB.TestResources; 9 | import com.jstarcraft.dip.hash.FuzzyHash; 10 | import com.jstarcraft.dip.hash.Hash; 11 | 12 | /** 13 | * @author Kilian 14 | * 15 | */ 16 | class FuzzyBinaryTreeTest { 17 | 18 | @Test 19 | @Disabled 20 | void testSimple() { 21 | FuzzyBinaryTree fuzzyTree = new FuzzyBinaryTree(true); 22 | FuzzyHash fuzzy = new FuzzyHash(TestResources.createHash("0101010111011", 0)); 23 | fuzzyTree.addHash(fuzzy); 24 | assertEquals(fuzzy, fuzzyTree.getNearestNeighbour(fuzzy).get(0).value); 25 | } 26 | 27 | @Disabled 28 | @Test 29 | void testDepth0() { 30 | FuzzyBinaryTree fuzzyTree = new FuzzyBinaryTree(true); 31 | FuzzyHash fuzzy = new FuzzyHash(TestResources.createHash("0", 0), TestResources.createHash("1", 0), TestResources.createHash("1", 0)); 32 | FuzzyHash fuzzy1 = new FuzzyHash(TestResources.createHash("1", 0), TestResources.createHash("1", 0), TestResources.createHash("1", 0)); 33 | 34 | fuzzyTree.addHash(fuzzy); 35 | fuzzyTree.addHash(fuzzy1); 36 | 37 | assertEquals(fuzzy1, fuzzyTree.getNearestNeighbour(TestResources.createHash("1", 0)).get(0).value); 38 | } 39 | 40 | @Test 41 | void testRuntime() { 42 | 43 | FuzzyBinaryTree fuzzyTree = new FuzzyBinaryTree(true); 44 | // FuzzyHash fuzzy = new FuzzyHash(createHash("10000000000001",0)); 45 | // FuzzyHash fuzzy1 = new FuzzyHash(createHash("11111111100001",0)); 46 | // 47 | FuzzyHash fuzzy = new FuzzyHash(TestResources.createHash("10000100", 0)); 48 | fuzzy.merge(TestResources.createHash("11100100", 0)); 49 | fuzzy.merge(TestResources.createHash("10101100", 0)); 50 | 51 | FuzzyHash fuzzy1 = new FuzzyHash(); 52 | fuzzy1.merge(TestResources.createHash("11100100", 0)); 53 | fuzzy1.merge(TestResources.createHash("00101100", 0)); 54 | fuzzy1.merge(TestResources.createHash("00101100", 0)); 55 | 56 | fuzzyTree.addHash(fuzzy); 57 | fuzzyTree.addHash(fuzzy1); 58 | 59 | System.out.println(fuzzyTree.getNearestNeighbour(fuzzy)); 60 | } 61 | 62 | @Test 63 | @Disabled 64 | void test() { 65 | 66 | FuzzyBinaryTree fuzzyTree = new FuzzyBinaryTree(true); 67 | Hash h = TestResources.createHash("0101010111011", 0); 68 | Hash h1 = TestResources.createHash("0101010111111", 0); 69 | FuzzyHash fuzzy = new FuzzyHash(); 70 | fuzzy.merge(h); 71 | fuzzy.merge(h1); 72 | fuzzyTree.addHash(fuzzy); 73 | 74 | System.out.println(fuzzyTree.getNearestNeighbour(fuzzy)); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/experimental/HogHashDualTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.experimental; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Nested; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import com.jstarcraft.dip.lsh.HashTestBase; 13 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 14 | import com.jstarcraft.dip.lsh.experimental.HogHashDual; 15 | 16 | /** 17 | * @author Kilian 18 | * 19 | */ 20 | @SuppressWarnings("deprecation") 21 | class HogHashDualTest { 22 | 23 | @Nested 24 | @DisplayName("Algorithm Id") 25 | class AlgorithmId { 26 | 27 | /** 28 | * The algorithms id shall stay consistent throughout different instances of the 29 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 30 | * from strings and integers are by contract consistent. 31 | */ 32 | @Test 33 | @DisplayName("Consistent AlgorithmIds") 34 | public void consistency() { 35 | 36 | assertAll(() -> { 37 | assertEquals(1521777976, new HogHashDual(14).algorithmId()); 38 | }, () -> { 39 | assertEquals(1580883320, new HogHashDual(25).algorithmId()); 40 | }); 41 | } 42 | 43 | @Test 44 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 45 | public void notVersionTwo() { 46 | assertAll(() -> { 47 | assertNotEquals(234483250, new HogHashDual(14).algorithmId()); 48 | }, () -> { 49 | assertNotEquals(236389874, new HogHashDual(14).algorithmId()); 50 | }); 51 | } 52 | 53 | } 54 | 55 | @SuppressWarnings("deprecation") 56 | @Test 57 | public void illegalConstructor() { 58 | assertThrows(IllegalArgumentException.class, () -> { 59 | new HogHashDual(2); 60 | }); 61 | } 62 | 63 | // Base Hashing algorithm tests 64 | @Nested 65 | class AlgorithmBaseTests extends HashTestBase { 66 | 67 | @Override 68 | protected HashingAlgorithm getInstance(int bitResolution) { 69 | return new HogHashDual(bitResolution); 70 | } 71 | 72 | @Override 73 | protected double differenceBallonHqHash() { 74 | return 66; 75 | } 76 | 77 | @Override 78 | protected double normDifferenceBallonHqHash() { 79 | return 66 / 144d; 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/AverageHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | 7 | import java.awt.image.BufferedImage; 8 | 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Nested; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import com.github.kilianB.TestResources; 14 | import com.jstarcraft.dip.hash.Hash; 15 | import com.jstarcraft.dip.lsh.AverageHash; 16 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 17 | 18 | class AverageHashTest { 19 | 20 | @Nested 21 | @DisplayName("Algorithm Id") 22 | class AlgorithmId { 23 | /** 24 | * The algorithms id shall stay consistent throughout different instances of the 25 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 26 | * from strings and integers are by contract consistent. 27 | */ 28 | @Test 29 | @DisplayName("Consistent AlgorithmIds") 30 | public void consistency() { 31 | assertAll(() -> { 32 | assertEquals(538597315, new AverageHash(14).algorithmId()); 33 | }, () -> { 34 | assertEquals(538628067, new AverageHash(25).algorithmId()); 35 | }); 36 | } 37 | 38 | @Test 39 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 40 | public void notVersionTwo() { 41 | assertAll(() -> { 42 | assertNotEquals(-1105481375, new AverageHash(14).algorithmId()); 43 | }, () -> { 44 | assertNotEquals(-1105480383, new AverageHash(14).algorithmId()); 45 | }); 46 | } 47 | } 48 | 49 | /** 50 | * The average hash has the interesting property that it's hashes image 51 | * representation if hashed is the exact opposite of the first hash. 52 | *

53 | * This only works if the hashes are perfectly aligned. With this test we can 54 | * make sure that bits are not shifted 55 | */ 56 | @Test 57 | void toImageTest() { 58 | AverageHash hasher = new AverageHash(512); 59 | 60 | Hash ballonHash = hasher.hash(TestResources.ballon); 61 | BufferedImage imageOfHash = ballonHash.toImage(10); 62 | Hash hashedImage = hasher.hash(imageOfHash); 63 | assertEquals(1, ballonHash.normalizedHammingDistance(hashedImage)); 64 | } 65 | 66 | // Base Hashing algorithm tests 67 | @Nested 68 | class AlgorithmBaseTests extends HashTestBase { 69 | @Override 70 | protected HashingAlgorithm getInstance(int bitResolution) { 71 | return new AverageHash(bitResolution); 72 | } 73 | 74 | @Override 75 | protected double differenceBallonHqHash() { 76 | return 77; 77 | } 78 | 79 | @Override 80 | protected double normDifferenceBallonHqHash() { 81 | return 77 / 132d; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/experimental/HogHashAngularEncodedTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.experimental; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Nested; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import com.jstarcraft.dip.lsh.HashTestBase; 13 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 14 | import com.jstarcraft.dip.lsh.experimental.HogHashAngularEncoded; 15 | 16 | /** 17 | * @author Kilian 18 | * 19 | */ 20 | @SuppressWarnings("deprecation") 21 | class HogHashAngularEncodedTest { 22 | 23 | @Nested 24 | @DisplayName("Algorithm Id") 25 | class AlgorithmId { 26 | 27 | /** 28 | * The algorithms id shall stay consistent throughout different instances of the 29 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 30 | * from strings and integers are by contract consistent. 31 | */ 32 | @Test 33 | @DisplayName("Consistent AlgorithmIds") 34 | public void consistency() { 35 | assertAll(() -> { 36 | assertEquals(373104456, new HogHashAngularEncoded(14).algorithmId()); 37 | }, () -> { 38 | assertEquals(432209800, new HogHashAngularEncoded(25).algorithmId()); 39 | }); 40 | } 41 | 42 | @Test 43 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 44 | public void notVersionTwo() { 45 | assertAll(() -> { 46 | assertNotEquals(1815042658, new HogHashAngularEncoded(14).algorithmId()); 47 | }, () -> { 48 | assertNotEquals(1816949282, new HogHashAngularEncoded(14).algorithmId()); 49 | }); 50 | } 51 | 52 | } 53 | 54 | @Test 55 | public void illegalConstructor() { 56 | assertThrows(IllegalArgumentException.class, () -> { 57 | new HogHashAngularEncoded(2); 58 | }); 59 | } 60 | 61 | // Base Hashing algorithm tests 62 | @Nested 63 | class AlgorithmBaseTests extends HashTestBase { 64 | 65 | @Override 66 | protected HashingAlgorithm getInstance(int bitResolution) { 67 | return new HogHashAngularEncoded(bitResolution); 68 | } 69 | 70 | @Override 71 | protected double differenceBallonHqHash() { 72 | return 71; 73 | } 74 | 75 | @Override 76 | protected double normDifferenceBallonHqHash() { 77 | return 71 / 144d; 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/lsh/RotPHashTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import static com.github.kilianB.TestResources.lenna; 4 | import static org.junit.jupiter.api.Assertions.assertAll; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Nested; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 13 | import com.jstarcraft.dip.lsh.RotPHash; 14 | 15 | /** 16 | * @author Kilian 17 | * @since 2.0.0 18 | */ 19 | class RotPHashTest { 20 | 21 | @Nested 22 | @DisplayName("Algorithm Id") 23 | class AlgorithmId { 24 | 25 | /** 26 | * The algorithms id shall stay consistent throughout different instances of the 27 | * jvm. While simple hashcodes do not guarantee this behavior hash codes created 28 | * from strings and integers are by contract consistent. 29 | */ 30 | @Test 31 | @DisplayName("Consistent AlgorithmIds") 32 | public void consistency() { 33 | 34 | assertAll(() -> { 35 | assertEquals(1665648520, new RotPHash(14, false).algorithmId()); 36 | }, () -> { 37 | assertEquals(1667549378, new RotPHash(25, true).algorithmId()); 38 | }); 39 | } 40 | 41 | @Test 42 | @DisplayName("Consistent AlgorithmIds v 2.0.0 collision") 43 | public void notVersionTwo() { 44 | assertAll(() -> { 45 | assertNotEquals(-1292976068, new RotPHash(14, false).algorithmId()); 46 | }, () -> { 47 | assertNotEquals(-1292914750, new RotPHash(25, true).algorithmId()); 48 | }); 49 | } 50 | } 51 | 52 | /** 53 | * RotPHash has a setting specifying that the key length is truly the one 54 | * specified 55 | */ 56 | @Test 57 | void keyLengthExact() { 58 | 59 | HashingAlgorithm hasher = new RotPHash(5, true); 60 | assertEquals(5, hasher.hash(lenna).getBitResolution()); 61 | 62 | hasher = new RotPHash(25, true); 63 | assertEquals(25, hasher.hash(lenna).getBitResolution()); 64 | 65 | hasher = new RotPHash(200, true); 66 | assertEquals(200, hasher.hash(lenna).getBitResolution()); 67 | } 68 | 69 | // Base Hashing algorithm tests 70 | @Nested 71 | class AlgorithmBaseTests extends RotationalTestBase { 72 | @Override 73 | protected HashingAlgorithm getInstance(int bitResolution) { 74 | return new RotPHash(bitResolution); 75 | } 76 | 77 | /** 78 | * PHash requires a higher bit resolution. 79 | */ 80 | protected int offsetBitResolution() { 81 | return 10; 82 | } 83 | 84 | @Override 85 | protected double differenceBallonHqHash() { 86 | return 54; 87 | } 88 | 89 | @Override 90 | protected double normDifferenceBallonHqHash() { 91 | return 54 / 137d; 92 | } 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/Pair.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A convenience data structure encapsulating 2 values. This class can be used 7 | * to return two values from a method call and create compound keys/values in 8 | * other collections. 9 | * 10 | * @author Kilian 11 | * 12 | * @param type of the first value 13 | * @param type of the second value 14 | * @since 1.5.4 15 | * @since 1.5.6 implements serializable 16 | */ 17 | public class Pair implements Serializable { 18 | 19 | private static final long serialVersionUID = 8525518254221383644L; 20 | 21 | private final S first; 22 | 23 | private final U second; 24 | 25 | /** 26 | * @param first element of the pair 27 | * @param second element of the pair 28 | */ 29 | public Pair(S first, U second) { 30 | super(); 31 | this.first = first; 32 | this.second = second; 33 | } 34 | 35 | /** 36 | * Copy constructor. 37 | *

38 | * Creates a copy of the supplied entry. This is a soft clone referencing the 39 | * same instances of the original pair. 40 | * 41 | * @param original the original pair to take the entries from. 42 | */ 43 | public Pair(Pair original) { 44 | this.first = original.first; 45 | this.second = original.second; 46 | } 47 | 48 | /** 49 | * @return the first object stored in this pair 50 | */ 51 | public S getFirst() { 52 | return first; 53 | } 54 | 55 | /** 56 | * @return the second object stored in this pair 57 | */ 58 | public U getSecond() { 59 | return second; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "Pair [first=" + first + ", second=" + second + "]"; 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | final int prime = 31; 70 | int result = 1; 71 | result = prime * result + ((first == null) ? 0 : first.hashCode()); 72 | result = prime * result + ((second == null) ? 0 : second.hashCode()); 73 | return result; 74 | } 75 | 76 | @SuppressWarnings("rawtypes") 77 | @Override 78 | public boolean equals(Object obj) { 79 | if (this == obj) { 80 | return true; 81 | } 82 | if (obj == null) { 83 | return false; 84 | } 85 | if (!(obj instanceof Pair)) { 86 | return false; 87 | } 88 | Pair other = (Pair) obj; 89 | if (first == null) { 90 | if (other.first != null) { 91 | return false; 92 | } 93 | } else if (!first.equals(other.first)) { 94 | return false; 95 | } 96 | if (second == null) { 97 | if (other.second != null) { 98 | return false; 99 | } 100 | } else if (!second.equals(other.second)) { 101 | return false; 102 | } 103 | return true; 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/statBenchmark/PractRandInterface.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.statBenchmark; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.DataOutputStream; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | 9 | import com.github.kilianB.pcg.fast.PcgRSFast; 10 | 11 | public class PractRandInterface { 12 | 13 | public static void main(String[] args) throws IOException { 14 | 15 | /* 16 | * Settings 17 | */ 18 | 19 | PcgRSFast rngToTest = new PcgRSFast(0, 0); 20 | 21 | // Pract rand settings 22 | 23 | int bitsPerData = 32; // Integer are 32 bits 24 | int bytesToTest = 36; // 2^n -> 2^36 bytes = 64 gb data 25 | int transformFolding = 2; // Transform the input for additional statistical tests 26 | 27 | File practRandExecutable = new File("benchmark/win_binary/PractRand_094/RNG_test"); 28 | 29 | // Terminal command on windows -> Output | RNG_test [Parameters] 30 | ProcessBuilder p = new ProcessBuilder(practRandExecutable.getAbsolutePath().toString(), "stdin" + bitsPerData, "-a", "-tf", Integer.toString(transformFolding), "-tlmax", Integer.toString(bytesToTest)); 31 | 32 | Process practRandProcess = p.start(); 33 | 34 | // Handle the output returned by the test 35 | Thread t = new Thread(() -> { 36 | try (BufferedReader br = new BufferedReader(new InputStreamReader(practRandProcess.getInputStream()))) { 37 | String line; 38 | while ((line = br.readLine()) != null) { 39 | 40 | // Print it to the console ... and additionally to a file? 41 | System.out.println(line); 42 | } 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | }); 47 | 48 | t.start(); 49 | 50 | // Pipe input data to the process 51 | DataOutputStream out = new DataOutputStream(practRandProcess.getOutputStream()); 52 | 53 | try { 54 | // Shall we trust brench prediction? We should but pull if outside 55 | // of the loop just in case ... 56 | if (bitsPerData == 64) { 57 | while (true) { 58 | out.writeLong(rngToTest.nextLong()); 59 | } 60 | } else if (bitsPerData == 32) { 61 | while (true) { 62 | out.writeInt(rngToTest.nextInt()); 63 | } 64 | } else if (bitsPerData == 16) { 65 | while (true) { 66 | // Widening cast will take place followed by bitshift 67 | out.writeShort(rngToTest.nextShort()); 68 | } 69 | } else if (bitsPerData == 8) { 70 | while (true) { 71 | // Same out.write takes an int... 72 | out.write(rngToTest.nextByte()); 73 | } 74 | } 75 | } catch (IOException io) { 76 | // If practrand collected enough data it will close the pipe on it's side. 77 | // Ignore this error and cease execution 78 | if (!io.getMessage().contains("The pipe has been ended")) { 79 | io.printStackTrace(); 80 | } 81 | } 82 | 83 | // Should run 30 min ... 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/TestResources.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertAll; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.awt.image.BufferedImage; 7 | import java.io.IOException; 8 | import java.math.BigInteger; 9 | 10 | import javax.imageio.ImageIO; 11 | 12 | import org.junit.jupiter.api.Test; 13 | 14 | import com.jstarcraft.dip.hash.Hash; 15 | 16 | /** 17 | * @author Kilian 18 | * 19 | */ 20 | public class TestResources { 21 | 22 | public static BufferedImage ballon; 23 | // Similar images 24 | public static BufferedImage copyright; 25 | public static BufferedImage highQuality; 26 | public static BufferedImage lowQuality; 27 | public static BufferedImage thumbnail; 28 | 29 | public static BufferedImage lenna; 30 | public static BufferedImage lenna90; 31 | public static BufferedImage lenna180; 32 | public static BufferedImage lenna270; 33 | 34 | static { 35 | try { 36 | ballon = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("ballon.jpg")); 37 | copyright = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("copyright.jpg")); 38 | highQuality = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("highQuality.jpg")); 39 | lowQuality = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("lowQuality.jpg")); 40 | thumbnail = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("thumbnail.jpg")); 41 | lenna = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("Lenna.png")); 42 | lenna90 = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("Lenna90.png")); 43 | lenna180 = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("Lenna180.png")); 44 | lenna270 = ImageIO.read(TestResources.class.getClassLoader().getResourceAsStream("Lenna270.png")); 45 | 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | }; 50 | 51 | @Test 52 | public void allResourcesLoaded() { 53 | 54 | assertAll(() -> { 55 | assertTrue(ballon.getWidth() > 0); 56 | }, () -> { 57 | assertTrue(copyright.getWidth() > 0); 58 | }, () -> { 59 | assertTrue(highQuality.getWidth() > 0); 60 | }, () -> { 61 | assertTrue(lowQuality.getWidth() > 0); 62 | }, () -> { 63 | assertTrue(thumbnail.getWidth() > 0); 64 | }, () -> { 65 | assertTrue(lenna.getWidth() > 0); 66 | }, () -> { 67 | assertTrue(lenna90.getWidth() > 0); 68 | }, () -> { 69 | assertTrue(lenna180.getWidth() > 0); 70 | }, () -> { 71 | assertTrue(lenna270.getWidth() > 0); 72 | }); 73 | } 74 | 75 | /** 76 | * Create a dummy hash 77 | * 78 | * @param bits 79 | * @param algoId 80 | * @return 81 | */ 82 | public static Hash createHash(String bits, int algoId) { 83 | return new Hash(new BigInteger(bits, 2), bits.length(), algoId); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/persistent/ConsecutiveMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.persistent; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.util.Map.Entry; 5 | import java.util.PriorityQueue; 6 | 7 | import com.github.kilianB.datastructures.tree.Result; 8 | import com.github.kilianB.datastructures.tree.binaryTree.BinaryTree; 9 | import com.jstarcraft.dip.hash.Hash; 10 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 11 | 12 | /** 13 | * Convenience class allowing to chain multiple hashing algorithms to find 14 | * similar images. The ConsecutiveMatcher keeps the hashes and buffered images 15 | * in cache. 16 | * 17 | * @author Kilian 18 | * 19 | */ 20 | public class ConsecutiveMatcher extends PersitentBinaryTreeMatcher { 21 | 22 | /** 23 | * @param cacheAddedHashes Additionally to the binary tree, hashes of added 24 | * images will be mapped to their uniqueId allowing to 25 | * retrieve matches of added images without loading the 26 | * image file from disk. This setting increases memory 27 | * overhead in exchange for performance. 28 | *

29 | * Use this setting if calls to 30 | * {@link #getMatchingImages(java.io.File)} likely 31 | * contains an image already added to the match prior. 32 | */ 33 | public ConsecutiveMatcher(boolean cacheAddedHashes) { 34 | super(cacheAddedHashes); 35 | } 36 | 37 | private static final long serialVersionUID = 831914616034052308L; 38 | 39 | protected PriorityQueue> getMatchingImagesInternal(BufferedImage image, String uniqueId) { 40 | 41 | if (steps.isEmpty()) 42 | throw new IllegalStateException("Please supply at least one hashing algorithm prior to invoking the match method"); 43 | 44 | PriorityQueue> returnValues = null; 45 | 46 | for (Entry entry : steps.entrySet()) { 47 | HashingAlgorithm algo = entry.getKey(); 48 | 49 | BinaryTree binTree = binTreeMap.get(algo); 50 | AlgoSettings settings = entry.getValue(); 51 | 52 | Hash needleHash = getHash(algo, uniqueId, image); 53 | 54 | int threshold = 0; 55 | if (settings.isNormalized()) { 56 | int hashLength = needleHash.getBitResolution(); 57 | threshold = (int) Math.round(settings.getThreshold() * hashLength); 58 | } else { 59 | threshold = (int) settings.getThreshold(); 60 | } 61 | 62 | PriorityQueue> temp = binTree.getElementsWithinHammingDistance(needleHash, threshold); 63 | 64 | if (returnValues == null) { 65 | returnValues = temp; 66 | } else { 67 | temp.retainAll(returnValues); 68 | returnValues = temp; 69 | } 70 | } 71 | return returnValues; 72 | } 73 | 74 | // Don't keep a reference to the image so the garbage collector can release it 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/concurrency/DelayedConsumerHashMap.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.concurrency; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map.Entry; 5 | import java.util.concurrent.locks.Condition; 6 | import java.util.concurrent.locks.ReentrantLock; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Accepts a consumer which executes all provided tasks in a batch no more often 11 | * than the delay specified. Tasks are labeled by id and will replace each other 12 | * if they arrive within the grace period while ensuring that the task scheduled 13 | * last will eventually be executed. 14 | * 15 | *

    16 | *
  1. Event( key = 1) arrives. Consumer has not executed a task therefore it is 17 | * executed immediately.
  2. 18 | *
  3. Event( key = 2) arrives. Consumer is currently sleeping
  4. 19 | *
  5. Event( key = 1) arrives. Consumer is currently sleeping
  6. 20 | *
  7. Event( key = 2) arrives. Consumer is currently sleeping. The first event 21 | * with key 2 is replaced and will never be executed. 22 | *
  8. Grace period over. Execute Event(key = 2) and Event( key = 1)
  9. 23 | *
  10. Lay dormant until new tasks arrive
  11. 24 | *
25 | * 26 | * Due to the use of internal locks no polling takes place. 27 | * 28 | * @author Kilian 29 | * 30 | * @param Type of the task the consumer will execute 31 | * @since 1.0.0 32 | */ 33 | public class DelayedConsumerHashMap { 34 | 35 | private final HashMap objects = new HashMap<>(); 36 | 37 | /** Main lock guarding all access */ 38 | private final ReentrantLock lock; // Maybe support a seperate sleep timer for each id? 39 | 40 | /** Condition for waiting takes */ 41 | private final Condition notEmpty; 42 | 43 | private Consumer consumer; 44 | private int sleepDuration; 45 | 46 | public DelayedConsumerHashMap(Consumer consumer, int sleep) { 47 | lock = new ReentrantLock(true); 48 | notEmpty = lock.newCondition(); 49 | this.consumer = consumer; 50 | this.sleepDuration = sleep; 51 | Thread internalThread = new Thread(() -> { 52 | handleRequest(); 53 | }); 54 | internalThread.setDaemon(false); 55 | internalThread.start(); 56 | } 57 | 58 | public void put(Integer key, T object) { 59 | lock.lock(); 60 | objects.put(key, object); 61 | notEmpty.signal(); 62 | lock.unlock(); 63 | 64 | } 65 | 66 | private void handleRequest() { 67 | try { 68 | lock.lock(); 69 | if (objects.size() == 0) { 70 | // Spurious wakeups are no problem here 71 | notEmpty.await(); 72 | } 73 | for (Entry entry : objects.entrySet()) { 74 | consumer.accept(entry.getValue()); 75 | } 76 | objects.clear(); 77 | 78 | } catch (InterruptedException e) { 79 | e.printStackTrace(); 80 | } finally { 81 | lock.unlock(); 82 | } 83 | try { 84 | Thread.sleep(sleepDuration); 85 | } catch (InterruptedException e) { 86 | e.printStackTrace(); 87 | } 88 | handleRequest(); 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/KMeansPlusPlus.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering; 2 | 3 | import java.util.DoubleSummaryStatistics; 4 | import java.util.Random; 5 | 6 | import com.github.kilianB.ArrayUtil; 7 | import com.github.kilianB.clustering.distance.DistanceFunction; 8 | import com.github.kilianB.pcg.fast.PcgRSFast; 9 | 10 | /** 11 | * @author Kilian 12 | * 13 | */ 14 | public class KMeansPlusPlus extends KMeans { 15 | 16 | /** 17 | * Create a KMeans clusterer with k clusters and EuclideanDistance. 18 | * 19 | * @param clusters the number of cluster to partition the data into 20 | */ 21 | public KMeansPlusPlus(int clusters) { 22 | super(clusters); 23 | } 24 | 25 | /** 26 | * Create a KMeans clusterer 27 | * 28 | * @param clusters the number of cluster to partition the data into 29 | * @param distanceFunction the distanceFunction used to compute the distance 30 | * between data points 31 | */ 32 | public KMeansPlusPlus(int clusters, DistanceFunction distanceFunction) { 33 | super(clusters, distanceFunction); 34 | } 35 | 36 | @Override 37 | protected DoubleSummaryStatistics[][] computeStartingClusters(double[][] data, int k, int dataDimension) { 38 | 39 | // Fast high quality rng 40 | Random rng = new PcgRSFast(); 41 | 42 | DoubleSummaryStatistics[][] clusterMeans = new DoubleSummaryStatistics[k][dataDimension]; 43 | 44 | ArrayUtil.fillArrayMulti(clusterMeans, () -> { 45 | return new DoubleSummaryStatistics(); 46 | }); 47 | 48 | // Randomly choose a starting point. Initial vector 49 | int clusterStart = rng.nextInt(data.length); 50 | 51 | for (int i = 0; i < dataDimension; i++) { 52 | clusterMeans[0][i].accept(data[clusterStart][i]); 53 | } 54 | 55 | for (int cluster = 1; cluster < k; cluster++) { 56 | 57 | // Choose a random cluster center with probability equal to the squared distance 58 | // of the closest existing center 59 | double[] distance = new double[data.length]; 60 | ArrayUtil.fillArray(distance, () -> { 61 | return Double.MAX_VALUE; 62 | }); 63 | 64 | double sum = 0; 65 | 66 | // For each point 67 | for (int i = 0; i < data.length; i++) { 68 | 69 | // find the minimum distance to all already existing clusters 70 | for (int j = 0; j < cluster; j++) { 71 | double distTemp = distanceFunction.distanceSquared(clusterMeans[j], data[i]); 72 | if (distTemp < distance[i]) { 73 | distance[i] = distTemp; 74 | } 75 | } 76 | sum += distance[i]; 77 | } 78 | 79 | int index = 0; 80 | double rand = rng.nextDouble() * sum; 81 | double runningSum = distance[0]; 82 | for (; index < data.length; index++) { 83 | if (rand <= runningSum) { 84 | break; 85 | } 86 | runningSum += distance[index]; 87 | } 88 | 89 | for (int i = 0; i < dataDimension; i++) { 90 | clusterMeans[cluster][i].accept(data[i][i]); 91 | } 92 | } 93 | return clusterMeans; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/kernel/MaximumKernel.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.kernel; 2 | 3 | import com.github.kilianB.ArrayUtil; 4 | 5 | /** 6 | * A maximum kernel is a non linear filter scanning the image and replacing 7 | * every value with the maximum value found in the neighborhood. 8 | * 9 | * This maximum kernel allows a weight matrix to be supplied. 10 | * 11 | *

12 | * Example 1D kernel width 5 Kernel and no/uniform mask 13 | * 14 | *

15 |  * 	Values: 5 4 1 3 6
16 |  * 
17 | * 18 | * During convolution, the kernel looks at the value 1 and replaces it with the 19 | * value 6 due to it being the maximum. 20 | * 21 | *

22 | * A weight mask {@code [1 2 3 2 1]} can give more emphasis on closer pixel 23 | * values. An intermediary value matrix is calculated: 24 | * 25 | *

26 |  * Values * Mask = [5 8 3 6 6]
27 |  * 
28 | * 29 | * and it is found that the second value is the maximum. Now the unaltered vlaue 30 | * at position 2 is taken. Therefore the 1 is replaced with the value 4. 31 | * 32 | * 33 | * @author Kilian 34 | * @since 2.0.0 35 | * @see MedianKernel 36 | * @see MinimumKernel 37 | */ 38 | public class MaximumKernel extends NonAveragingKernel { 39 | 40 | /** 41 | * Create a maximum kernel with no weight matrix 42 | * 43 | * @param width of the kernel 44 | * @param height height of the kernel 45 | */ 46 | @SuppressWarnings("deprecation") 47 | public MaximumKernel(int width, int height) { 48 | super(EdgeHandlingStrategy.EXPAND); 49 | 50 | if (width <= 0 || width % 2 == 0 || height <= 0 || height % 2 == 0) { 51 | throw new IllegalArgumentException("Currently only odd dimensional kernels are supported. Width & height have to be positive"); 52 | } 53 | // Create mask 54 | double[][] mask = new double[height][width]; 55 | ArrayUtil.fillArrayMulti(mask, () -> { 56 | return 1d; 57 | }); 58 | this.mask = mask; 59 | } 60 | 61 | /** 62 | * Create a kernel with the given masks dimension. The masks acts as weight 63 | * filter increasing or decreasing the weight of the value during convolution. 64 | * For an example see the javadoc of the class. 65 | * 66 | * @param mask weight matrix used to judge which value is the maximum 67 | */ 68 | public MaximumKernel(double[][] mask) { 69 | super(mask); 70 | } 71 | 72 | @Override 73 | protected double calcValue(byte[][] input, int x, int y) { 74 | return resolveMax(computePotentialValues(input, x, y)); 75 | } 76 | 77 | @Override 78 | protected double calcValue(int[][] input, int x, int y) { 79 | return resolveMax(computePotentialValues(input, x, y)); 80 | } 81 | 82 | @Override 83 | protected double calcValue(double[][] input, int x, int y) { 84 | return resolveMax(computePotentialValues(input, x, y)); 85 | } 86 | 87 | protected double resolveMax(double[][] values) { 88 | if (values[1].length == 1 && values[1][0] == Double.MIN_VALUE) { 89 | return values[0][0]; 90 | } 91 | return values[0][ArrayUtil.maximumIndex(values[1])]; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/PlainImageMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedHashSet; 5 | import java.util.Set; 6 | 7 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 8 | 9 | /** 10 | * Image matchers are a collection of classes bundling the hashing operation of 11 | * one or multiple {@link com.jstarcraft.dip.lsh.HashingAlgorithm 12 | * HashingAlgorithms} together and usually exposing functionalities to compare 13 | * multiple images with each other. 14 | *

15 | * PlainImage matchers accept hashing algorithms with no further configuration 16 | * arguments. 17 | * 18 | * @author Kilian 19 | * @since 3.0.0 20 | * @see com.github.kilianB.matcher.TypedImageMatcher 21 | */ 22 | public class PlainImageMatcher { 23 | 24 | TypedImageMatcher t; 25 | 26 | protected LinkedHashSet steps = new LinkedHashSet<>(); 27 | 28 | /** 29 | * Append a new hashing algorithm to be used by this matcher. The same algorithm 30 | * may only be added once. Attempts to add the same algorithm twice is a NOP. 31 | *

32 | * For some matchers the order of added hashing algorithms is crucial. The order 33 | * the hashes are added is preserved. 34 | * 35 | * @param hashingAlgorithm The algorithms to be added 36 | * @return true if the algorithm was added, false if it was already present 37 | */ 38 | public boolean addHashingAlgorithm(HashingAlgorithm hashingAlgorithm) { 39 | return this.steps.add(hashingAlgorithm); 40 | } 41 | 42 | /** 43 | * Remove a hashing algorithm from this matcher. 44 | * 45 | * @param hashingAlgorithm The algorithms to be removed 46 | * @return true if the algorithm was removed, false if it was not present. 47 | */ 48 | public boolean removeHashingAlgorithm(HashingAlgorithm hashingAlgorithm) { 49 | return this.steps.remove(hashingAlgorithm); 50 | } 51 | 52 | /** 53 | * Remove all hashing algorithms used by this image matcher instance. At least 54 | * one algorithm has to be supplied before images can be processed 55 | */ 56 | public void clearHashingAlgorithms() { 57 | steps.clear(); 58 | } 59 | 60 | /** 61 | * @return an unmofifiable collection of the hashing algorithms added to this 62 | * matcher. 63 | */ 64 | public Set getAlgorithms() { 65 | return Collections.unmodifiableSet(steps); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | final int prime = 31; 71 | int result = 1; 72 | result = prime * result + ((steps == null) ? 0 : steps.hashCode()); 73 | return result; 74 | } 75 | 76 | @Override 77 | public boolean equals(Object obj) { 78 | if (this == obj) { 79 | return true; 80 | } 81 | if (obj == null) { 82 | return false; 83 | } 84 | if (!(obj instanceof PlainImageMatcher)) { 85 | return false; 86 | } 87 | PlainImageMatcher other = (PlainImageMatcher) obj; 88 | if (steps == null) { 89 | if (other.steps != null) { 90 | return false; 91 | } 92 | } else if (!steps.equals(other.steps)) { 93 | return false; 94 | } 95 | return true; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/matcher/categorize/supervised/LabeledImage.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher.categorize.supervised; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.IOException; 6 | 7 | import javax.imageio.ImageIO; 8 | 9 | /** 10 | * A labeled image used to benchmark hashing algorithms. 11 | *

12 | * Labeled images bind an image to a category. All images in the same category 13 | * are expected to produce a match if an image matcher is queried. 14 | * 15 | * @author Kilian 16 | * @since 2.0.0 17 | * @since 2.1.1 moved to new file 18 | * @since 2.2.0 renamed to LabeledImage previously TestData 19 | */ 20 | public class LabeledImage implements Comparable { 21 | 22 | /** A character representation of the file for easy feedback */ 23 | protected String name; 24 | 25 | /** The category of the image. Same categories equals similar images */ 26 | protected int category; 27 | 28 | /** The image to test */ 29 | protected BufferedImage bImage; 30 | 31 | /** 32 | * 33 | * @param category The image category. Images with the same category are 34 | * expected to be classified as similar images 35 | * @param f The Fie pointing to the image 36 | */ 37 | public LabeledImage(int category, File f) { 38 | try { 39 | this.bImage = ImageIO.read(f); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | } 43 | this.name = f.getName().substring(0, f.getName().lastIndexOf(".")); 44 | this.category = category; 45 | } 46 | 47 | @Override 48 | public int compareTo(LabeledImage o) { 49 | return Integer.compare(category, o.category); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "LabeledImage [name=" + name + ", category=" + category + "]"; 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | final int prime = 31; 60 | int result = 1; 61 | result = prime * result + ((bImage == null) ? 0 : bImage.hashCode()); 62 | result = prime * result + category; 63 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 64 | return result; 65 | } 66 | 67 | @Override 68 | public boolean equals(Object obj) { 69 | if (this == obj) 70 | return true; 71 | if (obj == null) 72 | return false; 73 | if (getClass() != obj.getClass()) 74 | return false; 75 | LabeledImage other = (LabeledImage) obj; 76 | if (bImage == null) { 77 | if (other.bImage != null) 78 | return false; 79 | } else if (!bImage.equals(other.bImage)) 80 | return false; 81 | if (category != other.category) 82 | return false; 83 | if (name == null) { 84 | if (other.name != null) 85 | return false; 86 | } else if (!name.equals(other.name)) 87 | return false; 88 | return true; 89 | } 90 | 91 | /** 92 | * @return the name 93 | */ 94 | public String getName() { 95 | return name; 96 | } 97 | 98 | /** 99 | * @return the category 100 | */ 101 | public int getCategory() { 102 | return category; 103 | } 104 | 105 | /** 106 | * @return the bImage 107 | */ 108 | public BufferedImage getbImage() { 109 | return bImage; 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/kernel/SobelFilter.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.kernel; 2 | 3 | import java.awt.image.BufferedImage; 4 | 5 | import com.github.kilianB.Require; 6 | import com.jstarcraft.dip.color.ColorPixel; 7 | 8 | /** 9 | * 10 | * Edge detection filter 11 | * 12 | *

 13 |  * 
 14 |  * 		 1     0   -1
 15 |  * Gx :	 2     0   -2
 16 |  *  	 1     0   -1
 17 |  * 		
 18 |  * 		 1    2    1
 19 |  * Gy:	 0    0    0
 20 |  *		-1   -2   -1
 21 |  *
 22 |  * G : sqrt(Gx^2+ Gy^2)
 23 |  *
 24 |  * 
25 | * 26 | * @author Kilian 27 | * @since 2.0.0 28 | * @see Sobel 29 | * Operator 30 | */ 31 | public class SobelFilter implements ImageConverter { 32 | 33 | /** Separated Gx Kernel */ 34 | private MultiKernel xKernel; 35 | /** Separated Gy Kernel */ 36 | private MultiKernel yKernel; 37 | 38 | /** Gray cutoff value */ 39 | private double threshold; 40 | 41 | /** 42 | * Create a sobel filter 43 | * 44 | * @param threshold the cutoff beneath which gray values will be set to 0. [0 - 45 | * 1]. 46 | */ 47 | public SobelFilter(double threshold) { 48 | 49 | this.threshold = (double) Require.inRange(threshold, 0, 1, "Threshold must be in range of [0-1]"); 50 | 51 | // x Kernel 52 | /* 53 | * @formatter:off 54 | * 55 | * xKernel Separated 1 0 -1 1 2 0 -2 => 2 1 0 -1 1 0 -1 1 56 | * 57 | * @formatter:on 58 | */ 59 | 60 | double[][] x0Mask = { { 1 }, { 2 }, { 1 } }; 61 | double[][] x1Mask = { { 1, 0, -1 } }; 62 | 63 | /* 64 | * @formatter:off 65 | * 66 | * yKernel Separated 1 2 1 1 0 0 0 => 0 x 1 2 1 -1 -2 -1 -1 67 | * 68 | * @formatter:on 69 | */ 70 | 71 | double[][] y0Mask = { { 1 }, { 0 }, { -1 } }; 72 | double[][] y1Mask = { { 1, 2, 1 } }; 73 | 74 | xKernel = new MultiKernel(x1Mask, x0Mask); 75 | yKernel = new MultiKernel(y0Mask, y1Mask); 76 | } 77 | 78 | @Override 79 | public BufferedImage convert(BufferedImage bi) { 80 | 81 | ColorPixel fp = ColorPixel.convert(bi); 82 | 83 | int[][] grayscale = fp.getGrayscaleMatrix(); 84 | 85 | int[][] xGradient = xKernel.applyInt(grayscale); 86 | int[][] yGradient = yKernel.applyInt(grayscale); 87 | 88 | int[][] result = new int[xGradient.length][xGradient[0].length]; 89 | 90 | int cutOffValue = (int) (threshold * 255); 91 | 92 | for (int x = 0; x < xGradient.length; x++) { 93 | for (int y = 0; y < xGradient[x].length; y++) { 94 | result[x][y] = (int) Math.sqrt(xGradient[x][y] * xGradient[x][y] + yGradient[x][y] * yGradient[x][y]); 95 | 96 | if (result[x][y] < 0) { 97 | result[x][y] = -result[x][y]; 98 | } 99 | 100 | if (result[x][y] < cutOffValue) { 101 | result[x][y] = 0; 102 | } 103 | } 104 | } 105 | 106 | BufferedImage returnBi = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType()); 107 | ColorPixel fpSet = ColorPixel.convert(returnBi); 108 | 109 | fpSet.setGrayscaleMatrix(result); 110 | 111 | if (fpSet.hasTransparency()) { 112 | fpSet.setTransparencyMatrix(fp.getTransparencyMatrix()); 113 | } 114 | 115 | return returnBi; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/kernel/ScharrFilter.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.kernel; 2 | 3 | import java.awt.image.BufferedImage; 4 | 5 | import com.github.kilianB.Require; 6 | import com.jstarcraft.dip.color.ColorPixel; 7 | 8 | /** 9 | * 10 | * Edge detection filter similar to the sobel operator. 11 | * 12 | *
 13 |  * 
 14 |  * 		 3     0   -3
 15 |  * Gx :	10     0   -10
 16 |  *  	 3     0   -3
 17 |  * 		
 18 |  * 		 3    10   3
 19 |  * Gy:	 0     0   0
 20 |  *		-3   -10  -3
 21 |  *
 22 |  * G : sqrt(Gx^2+ Gy^2)
 23 |  *
 24 |  * 
25 | * 26 | * @author Kilian 27 | * @since 2.0.0 28 | * @see Sobel 29 | * Operator 30 | */ 31 | public class ScharrFilter implements ImageConverter { 32 | 33 | /** Separated Gx Kernel */ 34 | private MultiKernel xKernel; 35 | /** Separated Gy Kernel */ 36 | private MultiKernel yKernel; 37 | 38 | /** Gray cutoff value */ 39 | private double threshold; 40 | 41 | /** 42 | * Create a scharr filter 43 | * 44 | * @param threshold the cutoff beneath which gray values will be set to 0. [0 - 45 | * 1]. 46 | */ 47 | public ScharrFilter(double threshold) { 48 | 49 | this.threshold = (double) Require.inRange(threshold, 0, 1, "Threshold must be in range of [0-1]"); 50 | 51 | // x Kernel 52 | /* 53 | * @formatter:off 54 | * 55 | * xKernel Separated 1 0 -1 1 2 0 -2 => 2 1 0 -1 1 0 -1 1 56 | * 57 | * @formatter:on 58 | */ 59 | 60 | double[][] x0Mask = { { 3 }, { 10 }, { 3 } }; 61 | double[][] x1Mask = { { 1, 0, -1 } }; 62 | 63 | /* 64 | * @formatter:off 65 | * 66 | * yKernel Separated 1 2 1 1 0 0 0 => 0 x 1 2 1 -1 -2 -1 -1 67 | * 68 | * @formatter:on 69 | */ 70 | 71 | double[][] y0Mask = { { 1 }, { 0 }, { -1 } }; 72 | double[][] y1Mask = { { 3, 10, 3 } }; 73 | 74 | xKernel = new MultiKernel(x1Mask, x0Mask); 75 | yKernel = new MultiKernel(y0Mask, y1Mask); 76 | 77 | } 78 | 79 | @Override 80 | public BufferedImage convert(BufferedImage bi) { 81 | 82 | ColorPixel fp = ColorPixel.convert(bi); 83 | 84 | int[][] grayscale = fp.getRedMatrix(); 85 | 86 | int[][] xGradient = xKernel.applyInt(grayscale); 87 | int[][] yGradient = yKernel.applyInt(grayscale); 88 | 89 | int[][] result = new int[xGradient.length][xGradient[0].length]; 90 | 91 | int cutOffValue = (int) (threshold * 255); 92 | 93 | for (int x = 0; x < xGradient.length; x++) { 94 | for (int y = 0; y < xGradient[x].length; y++) { 95 | result[x][y] = (int) Math.sqrt(xGradient[x][y] * xGradient[x][y] + yGradient[x][y] * yGradient[x][y]); 96 | 97 | if (result[x][y] < 0) { 98 | result[x][y] -= result[x][y]; 99 | } 100 | if (result[x][y] < cutOffValue) { 101 | result[x][y] = 0; 102 | } 103 | } 104 | } 105 | 106 | BufferedImage returnBi = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType()); 107 | ColorPixel fpSet = ColorPixel.convert(returnBi); 108 | 109 | fpSet.setGrayscaleMatrix(result); 110 | 111 | if (fpSet.hasTransparency()) { 112 | fpSet.setTransparencyMatrix(fp.getTransparencyMatrix()); 113 | } 114 | 115 | return returnBi; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/kernel/MinimumKernel.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.kernel; 2 | 3 | import com.github.kilianB.ArrayUtil; 4 | 5 | /** 6 | * A maximum kernel is a non linear filter scanning the image and replacing 7 | * every value with the minimum value found in the neighborhood. 8 | * 9 | * This minimum kernel allows a weight matrix to be supplied. 10 | * 11 | *

12 | * Example 1D kernel width 5 Kernel and no/uniform mask 13 | * 14 | *

 15 |  * 	Values: 2 3 2 1 6
 16 |  * 
17 | * 18 | * During convolution, the kernel looks at the value 2 and replaces it with the 19 | * value 1 due to it being the minimum. 20 | * 21 | * 22 | * ------------------- 23 | * 24 | *

25 | * A weight mask {@code [1 2 3 2 1]} can give more emphasis on closer pixel 26 | * values. An intermediary value matrix is calculated: 27 | * 28 | *

 29 |  * Values * Mask = [2 6 6 2 6]
 30 |  * 
31 | * 32 | * and it is found that the first value is the minimum. (If two values are equal 33 | * the first value will be chosen). Now the unaltered value at position 1 is 34 | * taken. Therefore the 2 would be replaced with the value 2. 35 | * 36 | * 37 | * @author Kilian 38 | * @since 2.0.0 39 | * @see MedianKernel 40 | * @see MaximumKernel 41 | */ 42 | public class MinimumKernel extends NonAveragingKernel { 43 | 44 | /** 45 | * Create a minimum kernel with a uniform weight mask (no weighting takes place) 46 | * 47 | * @param width of the kernel. has to be odd 48 | * @param height of the kernel. has to be odd 49 | * 50 | */ 51 | @SuppressWarnings("deprecation") 52 | public MinimumKernel(int width, int height) { 53 | super(EdgeHandlingStrategy.EXPAND); 54 | 55 | if (width <= 0 || width % 2 == 0 || height <= 0 || height % 2 == 0) { 56 | throw new IllegalArgumentException("Currently only odd dimensional kernels are supported. Width & height have to be positive"); 57 | } 58 | // Create mask 59 | double[][] mask = new double[width][height]; 60 | ArrayUtil.fillArrayMulti(mask, () -> { 61 | return 1d; 62 | }); 63 | this.mask = mask; 64 | } 65 | 66 | /** 67 | * Create a kernel with the given masks dimension. The masks acts as weight 68 | * filter increasing or decreasing the weight of the value during convolution. 69 | * For an example see the javadoc of the class. 70 | * 71 | * @param mask weight matrix used to judge which value is the maximum 72 | */ 73 | public MinimumKernel(double[][] mask) { 74 | super(mask); 75 | } 76 | 77 | @Override 78 | protected double calcValue(byte[][] input, int x, int y) { 79 | return resolveMax(computePotentialValues(input, x, y)); 80 | } 81 | 82 | @Override 83 | protected double calcValue(int[][] input, int x, int y) { 84 | return resolveMax(computePotentialValues(input, x, y)); 85 | } 86 | 87 | @Override 88 | protected double calcValue(double[][] input, int x, int y) { 89 | return resolveMax(computePotentialValues(input, x, y)); 90 | } 91 | 92 | protected double resolveMax(double[][] values) { 93 | if (values[1].length == 1 && values[1][0] == Double.MIN_VALUE) { 94 | return values[0][0]; 95 | } 96 | return values[0][ArrayUtil.minimumIndex(values[1])]; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/experimental/HogHashAngularEncoded.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh.experimental; 2 | 3 | import java.math.BigInteger; 4 | 5 | import com.jstarcraft.dip.color.ColorPixel; 6 | import com.jstarcraft.dip.lsh.HashBuilder; 7 | 8 | /** 9 | * 10 | * 11 | * @author Kilian 12 | * @deprecated not ready to use yet 13 | * @since 2.0.0 14 | */ 15 | public class HogHashAngularEncoded extends HogHash { 16 | 17 | /** 18 | * * Create a hog hasher with the target bit resolution. 19 | * 20 | * Default values of 4 bins per cell (0°,45°,90°,135°) and a cell width of 2 21 | * pixels per cell are assumed. 22 | * 23 | * @param bitResolution the bit resolution of the final hash. The hash will be 24 | * at least the specified bits but may be bigger due to 25 | * algorithmic constraints. The best attempt is made to 26 | * return a hash with the given number of bits. 27 | */ 28 | public HogHashAngularEncoded(int bitResolution) { 29 | super(bitResolution); 30 | } 31 | 32 | /** 33 | * Create a hog hasher with parameters specific to the hog feature detection 34 | * algorithm. 35 | * 36 | * The actual hash will have a key length of 37 | * (width / cellWidth) * (height / cellWidth) * numBins 38 | * 39 | * @param width of the rescaled image 40 | * @param height of the rescaled image 41 | * @param cellWidth the width and height of sub cell. For each cell a gradient 42 | * will be computed. The cell width has to be a divisor of 43 | * width AND height! 44 | * @param numBins the number of bins per cell. The number of bins represent 45 | * the angular granularity the gradients will be sorted into. 46 | * The gradients will be sorted into buckets equivalent of the 47 | * size of 180°/numBins 48 | * @throws IllegalArgumentException if width or height can't be divided by 49 | * cellWidth or if any of the arguments is 50 | * smaller or equal 0 51 | */ 52 | public HogHashAngularEncoded(int width, int height, int cellWidth, int numBins) { 53 | super(width, height, cellWidth, numBins); 54 | } 55 | 56 | @Override 57 | protected BigInteger hash(ColorPixel fp, HashBuilder hash) { 58 | int[][] lum = fp.getLuminanceMatrix(); 59 | 60 | // 1 Compute hisogramm 61 | 62 | int[][][] hog = computeHogFeatures(lum); 63 | 64 | double binAverage[] = new double[numBins]; 65 | // Compute average of each bin 66 | 67 | int cells = xCells * yCells; 68 | 69 | for (int xCell = 0; xCell < xCells; xCell++) { 70 | // Construct intermediary vector 71 | for (int yCell = 0; yCell < yCells; yCell++) { 72 | for (int bin = 0; bin < numBins; bin++) { 73 | binAverage[bin] += hog[xCell][yCell][bin] / cells; 74 | } 75 | } 76 | } 77 | 78 | for (int xCell = 0; xCell < xCells; xCell++) { 79 | // Construct intermediary vector 80 | for (int yCell = 0; yCell < yCells; yCell++) { 81 | for (int bin = 0; bin < numBins; bin++) { 82 | if (hog[xCell][yCell][bin] > binAverage[bin]) { 83 | hash.prependZero(); 84 | } else { 85 | hash.prependOne(); 86 | } 87 | } 88 | } 89 | } 90 | 91 | return hash.toBigInteger(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/datastructures/Triple.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.datastructures; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A convenience data structure encapsulating 3 values. This class can be used 7 | * to return multiple values from a method call and create compound keys/values 8 | * in other collections. 9 | * 10 | * @author Kilian 11 | * 12 | * @param type of the first value 13 | * @param type of the second value 14 | * @param type of the third value 15 | * @since 1.5.6 16 | */ 17 | public class Triple implements Serializable { 18 | 19 | private static final long serialVersionUID = -6739318752459774954L; 20 | 21 | private final S first; 22 | 23 | private final U second; 24 | 25 | private final V third; 26 | 27 | /** 28 | * @param first element of the pair 29 | * @param second element of the pair 30 | * @param third tlement of the pair 31 | */ 32 | public Triple(S first, U second, V third) { 33 | this.first = first; 34 | this.second = second; 35 | this.third = third; 36 | } 37 | 38 | /** 39 | * Copy constructor. 40 | *

41 | * Creates a copy of the supplied entry. This is a soft clone referencing the 42 | * same instances of the original pair. 43 | * 44 | * @param original the original pair to take the entries from. 45 | */ 46 | public Triple(Triple original) { 47 | this.first = original.first; 48 | this.second = original.second; 49 | this.third = original.third; 50 | } 51 | 52 | /** 53 | * @return the first object stored in this triplet 54 | */ 55 | public S getFirst() { 56 | return first; 57 | } 58 | 59 | /** 60 | * @return the second object stored in this triplet 61 | */ 62 | public U getSecond() { 63 | return second; 64 | } 65 | 66 | /** 67 | * @return the second object stored in this triplet 68 | */ 69 | public V getThird() { 70 | return third; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "Triple [first=" + first + ", second=" + second + ", third=" + third + "]"; 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | final int prime = 31; 81 | int result = 1; 82 | result = prime * result + ((first == null) ? 0 : first.hashCode()); 83 | result = prime * result + ((second == null) ? 0 : second.hashCode()); 84 | result = prime * result + ((third == null) ? 0 : third.hashCode()); 85 | return result; 86 | } 87 | 88 | @SuppressWarnings("rawtypes") 89 | @Override 90 | public boolean equals(Object obj) { 91 | if (this == obj) { 92 | return true; 93 | } 94 | if (obj == null) { 95 | return false; 96 | } 97 | if (!(obj instanceof Triple)) { 98 | return false; 99 | } 100 | Triple other = (Triple) obj; 101 | if (first == null) { 102 | if (other.first != null) { 103 | return false; 104 | } 105 | } else if (!first.equals(other.first)) { 106 | return false; 107 | } 108 | if (second == null) { 109 | if (other.second != null) { 110 | return false; 111 | } 112 | } else if (!second.equals(other.second)) { 113 | return false; 114 | } 115 | if (third == null) { 116 | if (other.third != null) { 117 | return false; 118 | } 119 | } else if (!third.equals(other.third)) { 120 | return false; 121 | } 122 | return true; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/pcg/cas/PcgRSCas.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.cas; 2 | 3 | /** 4 | * A 64 bit State PcgRNG with 32 bit output. PCG-XSH-RR 5 | *

6 | * 7 | * The pcg family combines a linear congruential generators with a permutation 8 | * output function resulting in high quality pseudo random numbers. 9 | *

10 | * 11 | * The original concept was introduced by Melissa O’Neill please refer to 12 | * pcg-random for more information. 13 | *

14 | * Opposed to RR this version performs a random shift rather than a random 15 | * rotation. 16 | * 17 | * The RS instance permutates the output using the following function: 18 | * 19 | *

20 |  * {@code
21 |  * ((state >>> 22) ^ state) >>> ((state >>> 61) + 22)
22 |  * }
23 |  * 
24 | * 25 | * This implementation is thread safe utilizing CAS instructions similar to the 26 | * {@link java.util.Random} class. Performance wise CAS never achieves the same 27 | * throughput as the {@link com.github.kilianB.pcg.sync.PcgRS synchronized} 28 | * version. 29 | * 30 | * @author Kilian 31 | * @see www.pcg-random.org 32 | */ 33 | public class PcgRSCas extends RandomBaseCAS { 34 | 35 | private static final long serialVersionUID = -6682896154178640281L; 36 | 37 | /** 38 | * Create a cas synchronized PcgRS instance seeded with with 2 longs generated 39 | * by xorshift*. The values chosen are very likely not used as seeds in any 40 | * other non argument constructor of any of the classes provided in this 41 | * library. 42 | */ 43 | public PcgRSCas() { 44 | super(); 45 | } 46 | 47 | /** 48 | * Create a random number generator with the given seed and stream number. The 49 | * seed defines the current state in which the rng is in and corresponds to 50 | * seeds usually found in other RNG implementations. RNGs with different seeds 51 | * are able to catch up after they exhaust their period and produce the same 52 | * numbers. 53 | *

54 | * 55 | * Different stream numbers alter the increment of the rng and ensure distinct 56 | * state sequences 57 | *

58 | * 59 | * Only generators with the same seed AND stream numbers will produce identical 60 | * values 61 | *

62 | * 63 | * @param seed used to compute the starting state of the RNG 64 | * @param streamNumber used to compute the increment for the lcg. 65 | */ 66 | public PcgRSCas(long seed, long streamNumber) { 67 | super(seed, streamNumber); 68 | } 69 | 70 | /** 71 | * Copy constructor. Has to be implemented in all inheriting instances. 72 | * This will be invoked through reflection! when calling {@link #split()} or 73 | * {@link #splitDistinct()} If no special behavior is desired simply pass though 74 | * the values. 75 | * 76 | * This constructor should usually not be called manually as the seed and 77 | * increment will just be set without performing any randomization. 78 | * 79 | * @param seed of the lcg. The value will be set and not altered. 80 | * @param streamNumber used in the lcg. has to be odd 81 | * @param dummy unused. Resolve signature disambiguate 82 | */ 83 | @Deprecated 84 | public PcgRSCas(long seed, long streamNumber, boolean dummy) { 85 | super(seed, streamNumber, true); 86 | 87 | } 88 | 89 | @Override 90 | protected int getInt(long state) { 91 | // No rotation 92 | return (int) (((state >>> 22) ^ state) >>> ((state >>> 61) + 22)); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/matcher/SingleImageMatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.matcher; 2 | 3 | import static com.github.kilianB.TestResources.ballon; 4 | import static com.github.kilianB.TestResources.copyright; 5 | import static com.github.kilianB.TestResources.highQuality; 6 | import static com.github.kilianB.TestResources.lowQuality; 7 | import static com.github.kilianB.TestResources.thumbnail; 8 | import static org.junit.jupiter.api.Assertions.assertAll; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | import java.awt.image.BufferedImage; 15 | 16 | import org.junit.jupiter.api.DisplayName; 17 | import org.junit.jupiter.api.Nested; 18 | import org.junit.jupiter.api.Test; 19 | 20 | import com.github.kilianB.matcher.exotic.SingleImageMatcher; 21 | import com.jstarcraft.dip.lsh.AverageHash; 22 | 23 | class SingleImageMatcherTest { 24 | 25 | @Nested 26 | class CheckDefaultMatcher { 27 | 28 | private void assertMatches(SingleImageMatcher matcher) { 29 | // Identical 30 | assertAll("Identical images", () -> { 31 | assertTrue(matcher.checkSimilarity(ballon, ballon)); 32 | }, () -> { 33 | assertTrue(matcher.checkSimilarity(copyright, copyright)); 34 | }, () -> { 35 | assertTrue(matcher.checkSimilarity(highQuality, highQuality)); 36 | }, () -> { 37 | assertTrue(matcher.checkSimilarity(lowQuality, lowQuality)); 38 | }, () -> { 39 | assertTrue(matcher.checkSimilarity(thumbnail, thumbnail)); 40 | }); 41 | 42 | // Similar images 43 | assertAll("Similar images", () -> { 44 | assertTrue(matcher.checkSimilarity(highQuality, copyright)); 45 | }, () -> { 46 | assertTrue(matcher.checkSimilarity(highQuality, lowQuality)); 47 | }, () -> { 48 | assertTrue(matcher.checkSimilarity(highQuality, thumbnail)); 49 | }, () -> { 50 | assertTrue(matcher.checkSimilarity(lowQuality, copyright)); 51 | }, () -> { 52 | assertTrue(matcher.checkSimilarity(lowQuality, thumbnail)); 53 | }, () -> { 54 | assertTrue(matcher.checkSimilarity(copyright, thumbnail)); 55 | }); 56 | 57 | // Mismatches 58 | assertAll("Mismtaches", () -> { 59 | assertFalse(matcher.checkSimilarity(highQuality, ballon)); 60 | }, () -> { 61 | assertFalse(matcher.checkSimilarity(lowQuality, ballon)); 62 | }, () -> { 63 | assertFalse(matcher.checkSimilarity(copyright, ballon)); 64 | }, () -> { 65 | assertFalse(matcher.checkSimilarity(thumbnail, ballon)); 66 | }); 67 | } 68 | 69 | @Test 70 | @DisplayName("Check Similarity Default") 71 | public void imageMatches() { 72 | SingleImageMatcher matcher = new SingleImageMatcher(); 73 | matcher.addHashingAlgorithm(new AverageHash(64), .4); 74 | assertMatches(matcher); 75 | } 76 | } 77 | 78 | @Test 79 | @DisplayName("Empty Matcher") 80 | public void noAlgorithm() { 81 | SingleImageMatcher matcher = new SingleImageMatcher(); 82 | BufferedImage dummyImage = new BufferedImage(1, 1, 0x1); 83 | assertThrows(IllegalStateException.class, () -> { 84 | matcher.checkSimilarity(dummyImage, dummyImage); 85 | }); 86 | } 87 | 88 | @Test 89 | public void addAndClearAlgorithms() { 90 | SingleImageMatcher matcher = new SingleImageMatcher(); 91 | assertEquals(0, matcher.getAlgorithms().size()); 92 | matcher.addHashingAlgorithm(new AverageHash(14), 0.5, true); 93 | assertEquals(1, matcher.getAlgorithms().size()); 94 | matcher.clearHashingAlgorithms(); 95 | assertEquals(0, matcher.getAlgorithms().size()); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/pcg/lock/PcgRSLocked.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.pcg.lock; 2 | 3 | /** 4 | * A 64 bit State PcgRNG with 32 bit output. PCG-XSH-RR 5 | *

6 | * 7 | * The pcg family combines a linear congruential generators with a permutation 8 | * output function resulting in high quality pseudo random numbers. 9 | *

10 | * 11 | * The original concept was introduced by Melissa O’Neill please refer to 12 | * pcg-random for more information. 13 | *

14 | * Opposed to RR this version performs a random shift rather than a random 15 | * rotation. 16 | * 17 | * The RS instance permutates the output using the following function: 18 | * 19 | *

20 |  * {@code
21 |  * ((state >>> 22) ^ state) >>> ((state >>> 61) + 22)
22 |  * }
23 |  * 
24 | * 25 | * This implementation is thread safe utilizing read write locks. During non 26 | * contested periods this version performs considerably slower and only gains 27 | * the upper hand if the synchronized parts are hammered consistently. 28 | * 29 | * @author Kilian 30 | * @see www.pcg-random.org 31 | */ 32 | public class PcgRSLocked extends RandomBaseLocked { 33 | 34 | private static final long serialVersionUID = 6405237437417614399L; 35 | 36 | /** 37 | * Create a synchronized PcgRS instance seeded with with 2 longs generated by 38 | * xorshift*. The values chosen are very likely not used as seeds in any other 39 | * non argument constructor of any of the classes provided in this library. 40 | */ 41 | public PcgRSLocked() { 42 | super(); 43 | } 44 | 45 | /** 46 | * Create a random number generator with the given seed and stream number. The 47 | * seed defines the current state in which the rng is in and corresponds to 48 | * seeds usually found in other RNG implementations. RNGs with different seeds 49 | * are able to catch up after they exhaust their period and produce the same 50 | * numbers. 51 | *

52 | * 53 | * Different stream numbers alter the increment of the rng and ensure distinct 54 | * state sequences 55 | *

56 | * 57 | * Only generators with the same seed AND stream numbers will produce identical 58 | * values 59 | *

60 | * 61 | * @param seed used to compute the starting state of the RNG 62 | * @param streamNumber used to compute the increment for the lcg. 63 | */ 64 | public PcgRSLocked(long seed, long streamNumber) { 65 | super(seed, streamNumber); 66 | } 67 | 68 | /** 69 | * Copy constructor. Has to be implemented in all inheriting instances. 70 | * This will be invoked through reflection! when calling {@link #split()} or 71 | * {@link #splitDistinct()} If no special behavior is desired simply pass though 72 | * the values. 73 | * 74 | * This constructor should usually not be called manually as the seed and 75 | * increment will just be set without performing any randomization. 76 | * 77 | * @param seed of the lcg. The value will be set and not altered. 78 | * @param streamNumber used in the lcg as increment constant. 79 | * @param dummy unused. Resolve signature disambiguate 80 | */ 81 | @Deprecated 82 | public PcgRSLocked(long seed, long streamNumber, boolean dummy) { 83 | super(seed, streamNumber, true); 84 | } 85 | 86 | @Override 87 | protected int getInt(long state) { 88 | return (int) (((state >>> 22) ^ state) >>> ((state >>> 61) + 22)); 89 | } 90 | 91 | // protected int rotateRightUnsigned(int value, int rot) { 92 | // return (value >>> rot) | (value << ((- rot) & 31)); 93 | // } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/MutableLong.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Mutable class wrapper for long values. Mutable classes are useful in lambda 7 | * expressions or anonymous classes which want to alter the content of a 8 | * variable but are limited to final or effective final variables. 9 | * 10 | * @author Kilian 11 | * @since 1.0.0 12 | */ 13 | public class MutableLong extends Number implements Mutable, Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = 6846548022746719522L; 16 | 17 | private long field; 18 | 19 | /** 20 | * Create a mutable Long with an initial value of 0L 21 | */ 22 | public MutableLong() { 23 | }; 24 | 25 | /** 26 | * Create a mutable Long. 27 | * 28 | * @param initialValue the initial value of the long 29 | */ 30 | public MutableLong(long initialValue) { 31 | this.field = initialValue; 32 | } 33 | 34 | @Override 35 | public int compareTo(MutableLong o) { 36 | return Long.compare(field, o.field); 37 | } 38 | 39 | @Override 40 | public Long getValue() { 41 | return Long.valueOf(field); 42 | } 43 | 44 | @Override 45 | public void setValue(Long newValue) { 46 | field = newValue; 47 | } 48 | 49 | /** 50 | * Set the internal field to the new value 51 | * 52 | * @param newValue the new value 53 | * @since 1.2.0 54 | */ 55 | public void setValue(long newValue) { 56 | field = newValue; 57 | } 58 | 59 | @Override 60 | public long longValue() { 61 | return field; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return (int) (field ^ (field >>> 32)); 67 | } 68 | 69 | @Override 70 | public boolean equals(Object obj) { 71 | if (this == obj) 72 | return true; 73 | if (obj == null) 74 | return false; 75 | if (getClass() != obj.getClass()) 76 | return false; 77 | MutableLong other = (MutableLong) obj; 78 | if (field != other.field) 79 | return false; 80 | return true; 81 | } 82 | 83 | @Override 84 | public int intValue() { 85 | return (int) field; 86 | } 87 | 88 | @Override 89 | public float floatValue() { 90 | return field; 91 | } 92 | 93 | @Override 94 | public double doubleValue() { 95 | return field; 96 | } 97 | 98 | /** 99 | * Return the internal value and increment it afterwards. 100 | * 101 | * @return the value of the internal field before performing the increment 102 | * operation. 103 | * @since 1.2.0 104 | */ 105 | public Long getAndIncrement() { 106 | return Long.valueOf(field++); 107 | } 108 | 109 | /** 110 | * Increment the internal value and return the result. 111 | * 112 | * @return the new value after after performing the increment operation. 113 | * @since 1.2.0 114 | */ 115 | public Long incrementAndGet() { 116 | return Long.valueOf(++field); 117 | } 118 | 119 | /** 120 | * Return the internal value and decrement it afterwards. 121 | * 122 | * @return the value of the internal field before performing the decrement 123 | * operation. 124 | * @since 1.2.0 125 | */ 126 | public Long getAndDecrement() { 127 | return Long.valueOf(field--); 128 | } 129 | 130 | /** 131 | * Decrement the internal value and return the result. 132 | * 133 | * @return the new value after after performing the decrement operation. 134 | * @since 1.2.0 135 | */ 136 | public Long decrementAndGet() { 137 | return Long.valueOf(--field); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/MutableInteger.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Mutable class wrapper for integer values. Mutable classes are useful in 7 | * lambda expressions or anonymous classes which want to alter the content of a 8 | * variable but are limited to final or effective final variables. 9 | * 10 | * @author Kilian 11 | * @since 1.0.0 12 | */ 13 | public class MutableInteger extends Number implements Mutable, Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = 6846548022746719522L; 16 | 17 | private int field; 18 | 19 | /** 20 | * Create a mutable Integer with an initial value of 0 21 | */ 22 | public MutableInteger() { 23 | }; 24 | 25 | /** 26 | * Create a mutable Integer. 27 | * 28 | * @param initialValue the initial value of the integer 29 | */ 30 | public MutableInteger(int initialValue) { 31 | this.field = initialValue; 32 | } 33 | 34 | @Override 35 | public int compareTo(MutableInteger o) { 36 | return Integer.compare(field, o.field); 37 | } 38 | 39 | @Override 40 | public Integer getValue() { 41 | return Integer.valueOf(field); 42 | } 43 | 44 | @Override 45 | public void setValue(Integer newValue) { 46 | field = newValue; 47 | } 48 | 49 | /** 50 | * Set the internal field to the new value 51 | * 52 | * @param newValue the new value 53 | * @since 1.2.0 54 | */ 55 | public void setValue(int newValue) { 56 | field = newValue; 57 | } 58 | 59 | @Override 60 | public int intValue() { 61 | return field; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return field; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object obj) { 71 | if (this == obj) 72 | return true; 73 | if (obj == null) 74 | return false; 75 | if (getClass() != obj.getClass()) 76 | return false; 77 | MutableInteger other = (MutableInteger) obj; 78 | if (field != other.field) 79 | return false; 80 | return true; 81 | } 82 | 83 | @Override 84 | public long longValue() { 85 | return field; 86 | } 87 | 88 | @Override 89 | public float floatValue() { 90 | return field; 91 | } 92 | 93 | @Override 94 | public double doubleValue() { 95 | return field; 96 | } 97 | 98 | /** 99 | * Return the internal value and increment it afterwards. 100 | * 101 | * @return the value of the internal field before performing the increment 102 | * operation. 103 | * @since 1.2.0 104 | */ 105 | public Integer getAndIncrement() { 106 | return Integer.valueOf(field++); 107 | } 108 | 109 | /** 110 | * Increment the internal value and return the result. 111 | * 112 | * @return the new value after after performing the increment operation. 113 | * @since 1.2.0 114 | */ 115 | public Integer incrementAndGet() { 116 | return Integer.valueOf(++field); 117 | } 118 | 119 | /** 120 | * Return the internal value and decrement it afterwards. 121 | * 122 | * @return the value of the internal field before performing the decrement 123 | * operation. 124 | * @since 1.2.0 125 | */ 126 | public Integer getAndDecrement() { 127 | return Integer.valueOf(field--); 128 | } 129 | 130 | /** 131 | * Decrement the internal value and return the result. 132 | * 133 | * @return the new value after after performing the decrement operation. 134 | * @since 1.2.0 135 | */ 136 | public Integer decrementAndGet() { 137 | return Integer.valueOf(--field); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/mutable/MutableByte.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Mutable class wrapper for byte values. Mutable classes are useful in lambda 7 | * expressions or anonymous classes which want to alter the content of a 8 | * variable but are limited to final or effective final variables. 9 | * 10 | * @author Kilian 11 | * @since 1.0.0 12 | */ 13 | public class MutableByte extends Number implements Mutable, Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = -1973847474259823325L; 16 | 17 | private byte field; 18 | 19 | /** 20 | * Create a mutable Boolean with an initial value of 0 21 | */ 22 | public MutableByte() { 23 | }; 24 | 25 | /** 26 | * Create a mutable Boolean 27 | * 28 | * @param initialValue the intial value of the byte 29 | */ 30 | public MutableByte(byte initialValue) { 31 | this.field = initialValue; 32 | } 33 | 34 | @Override 35 | public int compareTo(MutableByte o) { 36 | return Byte.compare(field, o.field); 37 | } 38 | 39 | @Override 40 | public Byte getValue() { 41 | return Byte.valueOf(field); 42 | } 43 | 44 | @Override 45 | public void setValue(Byte newValue) { 46 | this.field = newValue; 47 | } 48 | 49 | /** 50 | * Set the internal field to the new value 51 | * 52 | * @param newValue the new value 53 | * @since 1.2.0 54 | */ 55 | public void setValue(byte newValue) { 56 | this.field = newValue; 57 | } 58 | 59 | /** 60 | * @return the current value as byte primitive 61 | */ 62 | public byte byteValue() { 63 | return field; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return field; 69 | } 70 | 71 | @Override 72 | public boolean equals(Object obj) { 73 | if (this == obj) 74 | return true; 75 | if (obj == null) 76 | return false; 77 | if (getClass() != obj.getClass()) 78 | return false; 79 | MutableByte other = (MutableByte) obj; 80 | if (field != other.field) 81 | return false; 82 | return true; 83 | } 84 | 85 | @Override 86 | public int intValue() { 87 | return field; 88 | } 89 | 90 | @Override 91 | public long longValue() { 92 | return field; 93 | } 94 | 95 | @Override 96 | public float floatValue() { 97 | return field; 98 | } 99 | 100 | @Override 101 | public double doubleValue() { 102 | return field; 103 | } 104 | 105 | /** 106 | * Return the internal value and increment it afterwards. 107 | * 108 | * @return the value of the internal field before performing the increment 109 | * operation. 110 | * @since 1.2.0 111 | */ 112 | public Byte getAndIncrement() { 113 | return Byte.valueOf(field++); 114 | } 115 | 116 | /** 117 | * Increment the internal value and return the result. 118 | * 119 | * @return the new value after after performing the increment operation. 120 | * @since 1.2.0 121 | */ 122 | public Byte incrementAndGet() { 123 | return Byte.valueOf(++field); 124 | } 125 | 126 | /** 127 | * Return the internal value and decrement it afterwards. 128 | * 129 | * @return the value of the internal field before performing the decrement 130 | * operation. 131 | * @since 1.2.0 132 | */ 133 | public Byte getAndDecrement() { 134 | return Byte.valueOf(field--); 135 | } 136 | 137 | /** 138 | * Decrement the internal value and return the result. 139 | * 140 | * @return the new value after after performing the decrement operation. 141 | * @since 1.2.0 142 | */ 143 | public Byte decrementAndGet() { 144 | return Byte.valueOf(--field); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/mutable/MutableLongTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * @author Kilian 9 | * 10 | */ 11 | class MutableLongTest { 12 | 13 | /** 14 | * Test method for {@link com.github.kilianB.mutable.MutableLong#intValue()}. 15 | */ 16 | @Test 17 | void testIntValue() { 18 | MutableLong i = new MutableLong(1); 19 | assertEquals(1, i.intValue()); 20 | } 21 | 22 | /** 23 | * Test method for {@link com.github.kilianB.mutable.MutableLong#longValue()}. 24 | */ 25 | @Test 26 | void testLongValue() { 27 | MutableLong i = new MutableLong(1); 28 | assertEquals(1l, i.longValue()); 29 | } 30 | 31 | /** 32 | * Test method for {@link com.github.kilianB.mutable.MutableLong#floatValue()}. 33 | */ 34 | @Test 35 | void testFloatValue() { 36 | MutableLong i = new MutableLong(1); 37 | assertEquals(1f, i.floatValue()); 38 | } 39 | 40 | /** 41 | * Test method for {@link com.github.kilianB.mutable.MutableLong#doubleValue()}. 42 | */ 43 | @Test 44 | void testDoubleValue() { 45 | MutableLong i = new MutableLong(1); 46 | assertEquals(1d, i.doubleValue()); 47 | } 48 | 49 | /** 50 | * Test method for {@link com.github.kilianB.mutable.MutableLong#getValue()}. 51 | */ 52 | @Test 53 | void testGetValue() { 54 | MutableLong i = new MutableLong(1); 55 | assertEquals(Long.valueOf(1), i.getValue()); 56 | } 57 | 58 | /** 59 | * Test method for 60 | * {@link com.github.kilianB.mutable.MutableLong#setValue(java.lang.Long)}. 61 | */ 62 | @Test 63 | void testSetValue() { 64 | MutableLong i = new MutableLong(1); 65 | i.setValue(Long.valueOf(3)); 66 | assertEquals(Long.valueOf(3), i.getValue()); 67 | } 68 | 69 | /** 70 | * Test method for 71 | * {@link com.github.kilianB.mutable.MutableLong#setValue(java.lang.Long)}. 72 | */ 73 | @Test 74 | void testSetValuePrimitive() { 75 | MutableLong i = new MutableLong(1); 76 | i.setValue(3l); 77 | assertEquals(Long.valueOf(3), i.getValue()); 78 | } 79 | 80 | /** 81 | * Test method for 82 | * {@link com.github.kilianB.mutable.MutableLong#equals(java.lang.Object)}. 83 | */ 84 | @Test 85 | void testEqualsObject() { 86 | MutableLong i = new MutableLong(4); 87 | MutableLong i2 = new MutableLong(4); 88 | assertEquals(i, i2); 89 | 90 | } 91 | 92 | /** 93 | * Test method for 94 | * {@link com.github.kilianB.mutable.MutableLong#getAndIncrement()}. 95 | */ 96 | @Test 97 | void testGetAndIncrement() { 98 | MutableLong i = new MutableLong(4); 99 | assertEquals(Long.valueOf(4), i.getAndIncrement()); 100 | assertEquals(Long.valueOf(5), i.getValue()); 101 | } 102 | 103 | /** 104 | * Test method for 105 | * {@link com.github.kilianB.mutable.MutableLong#incrementAndGet()}. 106 | */ 107 | @Test 108 | void testIncrementAndGet() { 109 | MutableLong i = new MutableLong(4); 110 | assertEquals(Long.valueOf(5), i.incrementAndGet()); 111 | } 112 | 113 | /** 114 | * Test method for 115 | * {@link com.github.kilianB.mutable.MutableLong#getAndDecrement()}. 116 | */ 117 | @Test 118 | void testGetAndDecrement() { 119 | MutableLong i = new MutableLong(4); 120 | assertEquals(Long.valueOf(4), i.getAndDecrement()); 121 | assertEquals(Long.valueOf(3), i.getValue()); 122 | } 123 | 124 | /** 125 | * Test method for 126 | * {@link com.github.kilianB.mutable.MutableLong#decrementAndGet()}. 127 | */ 128 | @Test 129 | void testDecrementAndGet() { 130 | MutableLong i = new MutableLong(4); 131 | assertEquals(Long.valueOf(3), i.decrementAndGet()); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/clustering/DBScan.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.clustering; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.ListIterator; 6 | 7 | import com.github.kilianB.ArrayUtil; 8 | import com.github.kilianB.clustering.distance.DistanceFunction; 9 | import com.github.kilianB.clustering.distance.EuclideanDistance; 10 | 11 | /** 12 | * @author Kilian 13 | * 14 | */ 15 | public class DBScan implements ClusterAlgorithm { 16 | 17 | /** 18 | * number of neighboors a points has to have in order to be considered a core 19 | * point 20 | */ 21 | private int minPoints; 22 | 23 | /** max distance to look for neightboors */ 24 | private double eps; 25 | 26 | private DistanceFunction dist; 27 | 28 | public DBScan(int minPoints, double eps) { 29 | this(minPoints, eps, new EuclideanDistance()); 30 | } 31 | 32 | public DBScan(int minPoints, double eps, DistanceFunction dist) { 33 | 34 | this.minPoints = minPoints; 35 | this.eps = eps; 36 | this.dist = dist; 37 | } 38 | 39 | // Possible optimizations 40 | 41 | // 0. 42 | // Avoid duplicate neighbor computation; Space bs memory? 43 | // HashMap> neightboorMap = new HashMap<>(); 44 | 45 | // 1. 46 | // We also could create copies of the data array one dimension at a time and 47 | // sort it by distance. Cutoff if distance is alreay > than x 48 | 49 | // 2. 50 | // Sort the datapoints into buckets (evently?) spaced over the domain. 51 | // Now only the distance extremes have to be computed to the current point. 52 | // if it is already furhter away discard all points else do indepth calculation 53 | // for all points. 54 | 55 | @Override 56 | public ClusterResult cluster(double[][] data) { 57 | 58 | // corepoints if minPts are within distance eps 59 | // direct reachable 60 | // reachable 61 | // noise 62 | 63 | int clusters = -1; 64 | 65 | // -2 not worked on. -1 noise 66 | int cluster[] = new int[data.length]; 67 | ArrayUtil.fillArray(cluster, () -> { 68 | return -2; 69 | }); 70 | 71 | for (int i = 0; i < data.length; i++) { 72 | 73 | // Point already processed 74 | if (cluster[i] != -2) { 75 | continue; 76 | } 77 | 78 | List neighboor = new ArrayList<>(); 79 | 80 | for (int j = 0; j < data.length; j++) { 81 | if (i != j && dist.distance(data[i], data[j]) <= eps) { 82 | neighboor.add(j); 83 | } 84 | } 85 | 86 | if (neighboor.size() < minPoints) { 87 | // Noise 88 | cluster[i] = -1; 89 | continue; 90 | } 91 | 92 | clusters++; 93 | // Cluster 94 | cluster[i] = clusters; 95 | 96 | ListIterator liter = neighboor.listIterator(); 97 | 98 | while (liter.hasNext()) { 99 | 100 | int j = liter.next(); 101 | 102 | // if point is noise add it to the cluster 103 | if (cluster[j] == -1) { 104 | cluster[j] = clusters; 105 | continue; 106 | // If it already belongs to a cluster skip it 107 | } else if (cluster[j] != -2) { 108 | continue; 109 | } 110 | cluster[j] = clusters; 111 | 112 | List newNeighboots = new ArrayList<>(); 113 | 114 | for (int m = 0; m < data.length; m++) { 115 | if (m != j) { 116 | if (dist.distance(data[m], data[j]) < eps) { 117 | newNeighboots.add(m); 118 | } 119 | } 120 | } 121 | 122 | if (newNeighboots.size() >= eps) { 123 | // liter.add 124 | for (int n : newNeighboots) { 125 | liter.add(n); 126 | // Reset cursor 127 | liter.previous(); 128 | } 129 | } 130 | } 131 | } 132 | 133 | return new ClusterResult(cluster, data); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/github/kilianB/examples/CompareImages.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.examples; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.HashMap; 7 | 8 | import javax.imageio.ImageIO; 9 | 10 | import com.jstarcraft.dip.hash.Hash; 11 | import com.jstarcraft.dip.lsh.AverageHash; 12 | import com.jstarcraft.dip.lsh.HashingAlgorithm; 13 | 14 | /** 15 | * An example demonstrating how two images can be compared at a time using a 16 | * single algorithm 17 | * 18 | * @author Kilian 19 | * 20 | */ 21 | public class CompareImages { 22 | 23 | // Key bit resolution 24 | private int keyLength = 64; 25 | 26 | // Pick an algorithm 27 | private HashingAlgorithm hasher = new AverageHash(keyLength); 28 | 29 | // Images used for testing 30 | private HashMap images = new HashMap<>(); 31 | 32 | public CompareImages() { 33 | 34 | loadImages(); 35 | 36 | // Compare each picture to each other 37 | images.forEach((imageName, image) -> { 38 | images.forEach((imageName2, image2) -> { 39 | formatOutput(imageName, imageName2, compareTwoImages(image, image2)); 40 | }); 41 | }); 42 | } 43 | 44 | /** 45 | * Compares the similarity of two images. 46 | * 47 | * @param image1 First image to be matched against 2nd image 48 | * @param image2 The second image 49 | * @return true if the algorithm defines the images to be similar. 50 | */ 51 | public boolean compareTwoImages(BufferedImage image1, BufferedImage image2) { 52 | 53 | // Generate the hash for each image 54 | Hash hash1 = hasher.hash(image1); 55 | Hash hash2 = hasher.hash(image2); 56 | 57 | // Compute a similarity score 58 | // Ranges between 0 - 1. The lower the more similar the images are. 59 | double similarityScore = hash1.normalizedHammingDistance(hash2); 60 | 61 | return similarityScore < 0.4d; 62 | } 63 | 64 | /** 65 | * Compares the similarity of two images. 66 | * 67 | * @param image1 First image to be matched against 2nd image 68 | * @param image2 The second image 69 | * @return true if the algorithm defines the images to be similar. 70 | * @throws IOException IOerror occurred during image loading 71 | */ 72 | public boolean compareTwoImages2(File image1, File image2) throws IOException { 73 | 74 | // Generate the hash for each image 75 | Hash hash1 = hasher.hash(image1); 76 | Hash hash2 = hasher.hash(image2); 77 | 78 | // Ranges between [0 - keyLength]. The lower the more similar the images are. 79 | int similarityScore = hash1.hammingDistance(hash2); 80 | 81 | return similarityScore < 41; 82 | } 83 | 84 | // Utility function 85 | private void formatOutput(String image1, String image2, boolean similar) { 86 | String format = "| %-11s | %-11s | %-8b |%n"; 87 | System.out.printf(format, image1, image2, similar); 88 | } 89 | 90 | private void loadImages() { 91 | // Load images 92 | try { 93 | images.put("ballon", ImageIO.read(getClass().getResourceAsStream("images/ballon.jpg"))); 94 | images.put("copyright", ImageIO.read(getClass().getResourceAsStream("images/copyright.jpg"))); 95 | images.put("highQuality", ImageIO.read(getClass().getResourceAsStream("images/highQuality.jpg"))); 96 | images.put("lowQuality", ImageIO.read(getClass().getResourceAsStream("images/lowQuality.jpg"))); 97 | images.put("thumbnail", ImageIO.read(getClass().getResourceAsStream("images/thumbnail.jpg"))); 98 | } catch (IOException e) { 99 | e.printStackTrace(); 100 | } 101 | 102 | // Print header 103 | System.out.println("| Image 1 | Image 2 | Similar |"); 104 | } 105 | 106 | public static void main(String[] args) { 107 | new CompareImages(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/jstarcraft/dip/lsh/HashBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.lsh; 2 | 3 | import java.math.BigInteger; 4 | 5 | /** 6 | * Helper class to quickly create a bitwise byte array representation which can 7 | * be converted to a big integer object. 8 | * 9 | *

10 | * To maintain the capability to decode the created hash value back to an image 11 | * the order of the byte array is of utmost importance. Due to hashes ability to 12 | * contain non 8 compliment bit values and {@link java.math.BigInteger} 13 | * stripping leading zero bits the partial bit value has to be present at the 0 14 | * slot. 15 | *

16 | * The hashbuilder systematically grows the base byte[] array as needed but 17 | * performs the best if the correct amount of bits are known beforehand. 18 | * 19 | *

20 | * In other terms this class performs the same operation as 21 | * 22 | *

 23 |  * 
 24 |  * 	StringBuilder sb = new StringBuilder();
 25 |  * 	sb.append("100011);
 26 |  * 	BigInteger b = new BigInteger(sb.toString(),2);
 27 |  * 
 28 |  * 
29 | * 30 | * But scales much much better for higher hash values. The order of the bits are 31 | * flipped using the hashbuilder approach. 32 | * 33 | * @author Kilian 34 | * 35 | * TODO we don't really need a Big Integer at all. We can use a byte 36 | * array. 37 | * @since 3.0.0 38 | */ 39 | public class HashBuilder { 40 | 41 | /** 42 | * Mask used to add 43 | */ 44 | private static final byte[] MASK = { 1, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, (byte) (1 << 7) }; 45 | 46 | private byte[] bytes; 47 | private int arrayIndex = 0; 48 | private int bitIndex = 0; 49 | protected int length; 50 | 51 | /** 52 | * Create a hashbuilder. 53 | * 54 | * @param bits the number of bits the hash will have [8 - Integer.MAX_VALUE]. If 55 | * the builder requires more space than specified copy operations 56 | * will take place to grow the builder automatically. 57 | *

58 | * Allocating to much space also results in the correct hash value 59 | * being created but might also result in a slight performance 60 | * penalty 61 | */ 62 | public HashBuilder(int bits) { 63 | bytes = new byte[(int) Math.ceil(bits / 8d)]; 64 | arrayIndex = bytes.length - 1; 65 | } 66 | 67 | /** 68 | * Add a zero bit to the hash 69 | */ 70 | public void prependZero() { 71 | if (bitIndex == 8) { 72 | bitIndex = 0; 73 | arrayIndex--; 74 | if (arrayIndex == -1) { 75 | byte[] temp = new byte[bytes.length + 1]; 76 | System.arraycopy(bytes, 0, temp, 1, bytes.length); 77 | bytes = temp; 78 | arrayIndex = 0; 79 | } 80 | } 81 | bitIndex++; 82 | length++; 83 | } 84 | 85 | /** 86 | * Add a one bit to the hash 87 | */ 88 | public void prependOne() { 89 | if (bitIndex == 8) { 90 | bitIndex = 0; 91 | arrayIndex--; 92 | if (arrayIndex == -1) { 93 | byte[] temp = new byte[bytes.length + 1]; 94 | System.arraycopy(bytes, 0, temp, 1, bytes.length); 95 | bytes = temp; 96 | arrayIndex = 0; 97 | } 98 | } 99 | bytes[arrayIndex] |= MASK[bitIndex++]; 100 | length++; 101 | } 102 | 103 | /** 104 | * Convert the internal state of the hashbuilder to a big integer object 105 | * 106 | * @return a big integer object 107 | */ 108 | public BigInteger toBigInteger() { 109 | return new BigInteger(1, bytes); 110 | } 111 | } -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/NetworkUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | import java.net.InetAddress; 11 | import java.net.ServerSocket; 12 | import java.net.Socket; 13 | import java.util.concurrent.CountDownLatch; 14 | 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | 19 | /** 20 | * @author Kilian 21 | * 22 | */ 23 | class NetworkUtilTest { 24 | 25 | private Socket inSocket; 26 | private Socket outSocket; 27 | 28 | @BeforeEach 29 | public void setupSocket() { 30 | try { 31 | ServerSocket ss = new ServerSocket(0); 32 | 33 | CountDownLatch latch = new CountDownLatch(1); 34 | CountDownLatch latch1 = new CountDownLatch(1); 35 | 36 | InetAddress localHost = InetAddress.getLocalHost(); 37 | int localPort = ss.getLocalPort(); 38 | new Thread(() -> { 39 | latch.countDown(); 40 | try { 41 | inSocket = ss.accept(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | latch1.countDown(); 46 | }).start(); 47 | 48 | latch.await(); 49 | outSocket = new Socket(localHost, localPort); 50 | latch1.await(); 51 | ss.close(); 52 | } catch (IOException | InterruptedException io) { 53 | io.printStackTrace(); 54 | } 55 | } 56 | 57 | @Test 58 | public void collectSocket() { 59 | // pipied output stream 60 | try { 61 | String testMessage = "HelloWorld1234"; 62 | OutputStream os = outSocket.getOutputStream(); 63 | os.write(testMessage.getBytes()); 64 | os.flush(); 65 | String readData = NetworkUtil.collectSocket(inSocket); 66 | assertEquals(testMessage, readData); 67 | assertFalse(inSocket.isClosed()); 68 | } catch (IOException io) { 69 | fail("IO Exception occured. " + io); 70 | } 71 | } 72 | 73 | @Test 74 | public void collectSocketTimeout() { 75 | // pipied output stream 76 | try { 77 | String testMessage = "HelloWorld1234"; 78 | OutputStream os = outSocket.getOutputStream(); 79 | os.write(testMessage.getBytes()); 80 | os.flush(); 81 | int originalTimeout = 400; 82 | inSocket.setSoTimeout(originalTimeout); 83 | String readData = NetworkUtil.collectSocket(inSocket, 40); 84 | assertEquals(testMessage, readData); 85 | assertFalse(inSocket.isClosed()); 86 | assertEquals(originalTimeout, inSocket.getSoTimeout()); 87 | } catch (IOException io) { 88 | fail("IO Exception occured. " + io); 89 | } 90 | } 91 | 92 | @Test 93 | public void collectSocketAndClose() { 94 | // pipied output stream 95 | try { 96 | String testMessage = "HelloWorld1234"; 97 | OutputStream os = outSocket.getOutputStream(); 98 | os.write(testMessage.getBytes()); 99 | os.flush(); 100 | int originalTimeout = 400; 101 | inSocket.setSoTimeout(originalTimeout); 102 | String readData = NetworkUtil.collectSocketAndClose(inSocket); 103 | assertEquals(testMessage, readData); 104 | assertTrue(inSocket.isClosed()); 105 | } catch (IOException io) { 106 | fail("IO Exception occured. " + io); 107 | } 108 | } 109 | 110 | @AfterEach 111 | public void closeSockets() { 112 | try { 113 | inSocket.close(); 114 | outSocket.close(); 115 | } catch (IOException e) { 116 | e.printStackTrace(); 117 | } 118 | } 119 | 120 | @Test 121 | public void publicIpAddress() { 122 | try { 123 | NetworkUtil.resolvePublicAddress(); 124 | } catch (IOException io) { 125 | fail(io); 126 | } 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/com/github/kilianB/mutable/MutableByteTest.java: -------------------------------------------------------------------------------- 1 | package com.github.kilianB.mutable; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * @author Kilian 9 | * 10 | */ 11 | public class MutableByteTest { 12 | /** 13 | * Test method for {@link com.github.kilianB.mutable.MutableByte#intValue()}. 14 | */ 15 | @Test 16 | void testIntValue() { 17 | MutableByte i = new MutableByte((byte) 1); 18 | assertEquals(1, i.intValue()); 19 | } 20 | 21 | /** 22 | * Test method for {@link com.github.kilianB.mutable.MutableByte#longValue()}. 23 | */ 24 | @Test 25 | void testByteValue() { 26 | MutableByte i = new MutableByte((byte) 1); 27 | assertEquals(1l, i.longValue()); 28 | } 29 | 30 | /** 31 | * Test method for {@link com.github.kilianB.mutable.MutableByte#floatValue()}. 32 | */ 33 | @Test 34 | void testFloatValue() { 35 | MutableByte i = new MutableByte((byte) 1); 36 | assertEquals(1f, i.floatValue()); 37 | } 38 | 39 | /** 40 | * Test method for {@link com.github.kilianB.mutable.MutableByte#doubleValue()}. 41 | */ 42 | @Test 43 | void testDoubleValue() { 44 | MutableByte i = new MutableByte((byte) 1); 45 | assertEquals(1d, i.doubleValue()); 46 | } 47 | 48 | /** 49 | * Test method for {@link com.github.kilianB.mutable.MutableByte#getValue()}. 50 | */ 51 | @Test 52 | void testGetValue() { 53 | MutableByte i = new MutableByte((byte) 1); 54 | assertEquals(Byte.valueOf((byte) 1), i.getValue()); 55 | } 56 | 57 | /** 58 | * Test method for 59 | * {@link com.github.kilianB.mutable.MutableByte#setValue(java.lang.Byte)}. 60 | */ 61 | @Test 62 | void testSetValue() { 63 | MutableByte i = new MutableByte((byte) 1); 64 | i.setValue(Byte.valueOf((byte) 3)); 65 | assertEquals(Byte.valueOf((byte) 3), i.getValue()); 66 | } 67 | 68 | /** 69 | * Test method for 70 | * {@link com.github.kilianB.mutable.MutableByte#setValue(java.lang.Byte)}. 71 | */ 72 | @Test 73 | void testSetValuePrimitive() { 74 | MutableByte i = new MutableByte((byte) 1); 75 | i.setValue((byte) 3); 76 | assertEquals(Byte.valueOf((byte) 3), i.getValue()); 77 | } 78 | 79 | /** 80 | * Test method for 81 | * {@link com.github.kilianB.mutable.MutableByte#equals(java.lang.Object)}. 82 | */ 83 | @Test 84 | void testEqualsObject() { 85 | MutableByte i = new MutableByte((byte) 4); 86 | MutableByte i2 = new MutableByte((byte) 4); 87 | assertEquals(i, i2); 88 | 89 | } 90 | 91 | /** 92 | * Test method for 93 | * {@link com.github.kilianB.mutable.MutableByte#getAndIncrement()}. 94 | */ 95 | @Test 96 | void testGetAndIncrement() { 97 | MutableByte i = new MutableByte((byte) 4); 98 | assertEquals(Byte.valueOf((byte) 4), i.getAndIncrement()); 99 | assertEquals(Byte.valueOf((byte) 5), i.getValue()); 100 | } 101 | 102 | /** 103 | * Test method for 104 | * {@link com.github.kilianB.mutable.MutableByte#incrementAndGet()}. 105 | */ 106 | @Test 107 | void testIncrementAndGet() { 108 | MutableByte i = new MutableByte((byte) 4); 109 | assertEquals(Byte.valueOf((byte) 5), i.incrementAndGet()); 110 | } 111 | 112 | /** 113 | * Test method for 114 | * {@link com.github.kilianB.mutable.MutableByte#getAndDecrement()}. 115 | */ 116 | @Test 117 | void testGetAndDecrement() { 118 | MutableByte i = new MutableByte((byte) 4); 119 | assertEquals(Byte.valueOf((byte) 4), i.getAndDecrement()); 120 | assertEquals(Byte.valueOf((byte) 3), i.getValue()); 121 | } 122 | 123 | /** 124 | * Test method for 125 | * {@link com.github.kilianB.mutable.MutableByte#decrementAndGet()}. 126 | */ 127 | @Test 128 | void testDecrementAndGet() { 129 | MutableByte i = new MutableByte((byte) 4); 130 | assertEquals(Byte.valueOf((byte) 3), i.decrementAndGet()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/com/jstarcraft/dip/color/ColorUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.jstarcraft.dip.color; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import org.junit.jupiter.api.Disabled; 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import javafx.scene.paint.Color; 11 | 12 | class ColorUtilTest { 13 | 14 | @Nested 15 | class AwtToFX { 16 | @Test 17 | void black() { 18 | assertEquals(Color.BLACK, ColorUtility.awtToFxColor(java.awt.Color.BLACK)); 19 | } 20 | 21 | @Test 22 | void white() { 23 | assertEquals(Color.WHITE, ColorUtility.awtToFxColor(java.awt.Color.WHITE)); 24 | } 25 | 26 | @Test 27 | void gray() { 28 | assertEquals(Color.GRAY, ColorUtility.awtToFxColor(java.awt.Color.GRAY)); 29 | } 30 | 31 | @Test 32 | void test() { 33 | Color fxColor = Color.web("#961e1e"); 34 | 35 | java.awt.Color awtColor = java.awt.Color.decode("#961e1e"); 36 | 37 | Color genfxColor = ColorUtility.awtToFxColor(awtColor); 38 | assertEquals(fxColor, genfxColor); 39 | } 40 | 41 | @Test 42 | @Disabled 43 | void alpha() { 44 | // Color.web(colorString, opacity) 45 | // java.awt.Color.decode(nm) what is the awt equivalent? 46 | // DON'T compare float and doubles! 47 | assertEquals(new Color(0.5d, 0.5d, 0.5d, 0.3d), ColorUtility.awtToFxColor(new java.awt.Color(0.5f, 0.5f, 0.5f, 0.3f))); 48 | } 49 | 50 | } 51 | 52 | @Nested 53 | class FXToAwt { 54 | @Test 55 | void black() { 56 | assertEquals(java.awt.Color.BLACK, ColorUtility.fxToAwtColor(Color.BLACK)); 57 | } 58 | 59 | @Test 60 | void white() { 61 | assertEquals(java.awt.Color.WHITE, ColorUtility.fxToAwtColor(Color.WHITE)); 62 | } 63 | 64 | @Test 65 | void gray() { 66 | assertEquals(java.awt.Color.GRAY, ColorUtility.fxToAwtColor(Color.GRAY)); 67 | } 68 | 69 | @Test 70 | void test() { 71 | Color fxColor = Color.web("#961e1e"); 72 | 73 | java.awt.Color awtColor = java.awt.Color.decode("#961e1e"); 74 | 75 | java.awt.Color genAwtColor = ColorUtility.fxToAwtColor(fxColor); 76 | assertEquals(awtColor, genAwtColor); 77 | } 78 | 79 | @Test 80 | @Disabled 81 | void alpha() { 82 | // Color.web(colorString, opacity) 83 | // java.awt.Color.decode(nm) what is the awt equivalent? 84 | 85 | // DON'T compare float and doubles! 86 | assertEquals(new Color(0.5d, 0.5d, 0.5d, 0.3d), ColorUtility.awtToFxColor(new java.awt.Color(0.5f, 0.5f, 0.5f, 0.3f))); 87 | } 88 | } 89 | 90 | @Nested 91 | class ColorDistance { 92 | 93 | @Test 94 | void symmetry() { 95 | Color c1 = Color.GREENYELLOW; 96 | Color c2 = Color.DARKORANGE; 97 | assertEquals(ColorUtility.distance(c1, c2), ColorUtility.distance(c2, c1)); 98 | } 99 | 100 | @Test 101 | void identity() { 102 | Color c1 = Color.DARKORANGE; 103 | Color c2 = Color.DARKORANGE; 104 | assertEquals(0, ColorUtility.distance(c2, c1)); 105 | } 106 | 107 | @Test 108 | void distance() { 109 | Color c1 = Color.WHITE; 110 | Color c2 = Color.DARKORANGE; 111 | Color c3 = Color.ORANGE; 112 | assertTrue(ColorUtility.distance(c1, c2) > ColorUtility.distance(c2, c3)); 113 | } 114 | } 115 | 116 | @Nested 117 | class ContrastColor { 118 | @Test 119 | void white() { 120 | assertEquals(Color.BLACK, ColorUtility.getContrastColor(Color.WHITE)); 121 | } 122 | 123 | @Test 124 | void black() { 125 | assertEquals(Color.WHITE, ColorUtility.getContrastColor(Color.BLACK)); 126 | } 127 | 128 | @Test 129 | void yellow() { 130 | assertEquals(Color.BLACK, ColorUtility.getContrastColor(Color.YELLOW)); 131 | } 132 | 133 | @Test 134 | void blue() { 135 | assertEquals(Color.WHITE, ColorUtility.getContrastColor(Color.BLUE)); 136 | } 137 | } 138 | } --------------------------------------------------------------------------------