/*
* This file is part of the LIRE project: http://lire-project.net
* LIRE is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* LIRE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LIRE; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* We kindly ask you to refer the any or one of the following publications in
* any publication mentioning or employing Lire:
*
* Lux Mathias, Savvas A. Chatzichristofis. Lire: Lucene Image Retrieval –
* An Extensible Java CBIR Library. In proceedings of the 16th ACM International
* Conference on Multimedia, pp. 1085-1088, Vancouver, Canada, 2008
* URL: http://doi.acm.org/10.1145/1459359.1459577
*
* Lux Mathias. Content Based Image Retrieval with LIRE. In proceedings of the
* 19th ACM International Conference on Multimedia, pp. 735-738, Scottsdale,
* Arizona, USA, 2011
* URL: http://dl.acm.org/citation.cfm?id=2072432
*
* Mathias Lux, Oge Marques. Visual Information Retrieval using Java and LIRE
* Morgan & Claypool, 2013
* URL: http://www.morganclaypool.com/doi/abs/10.2200/S00468ED1V01Y201301ICR025
*
* Copyright statement:
* ====================
* (c) 2002-2013 by Mathias Lux (mathias@juggle.at)
* http://www.semanticmetadata.net/lire, http://www.lire-project.net
*
* Updated: 11.07.13 10:07
*/
/*
* Ported from C#, performance is probably poor
* (c) 2009 Arthur Pitman. Contributed to LIRE and put under GPL in 2009.
*/
package net.semanticmetadata.lire.imageanalysis.features.global;
import net.semanticmetadata.lire.builders.DocumentBuilder;
import net.semanticmetadata.lire.imageanalysis.features.GlobalFeature;
import net.semanticmetadata.lire.imageanalysis.features.LireFeature;
import net.semanticmetadata.lire.utils.SerializationUtils;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Arrays;
import java.util.Comparator;
public class JpegCoefficientHistogram implements GlobalFeature {
protected int[] descriptorValues;
protected final int BLOCK_SIZE = 8;
protected double[][][][] transform;
double[][][] dctScaler2 = new double[][][]
{
{{0, 0, 0}, {1, 1, 1}, {1.9734043721689, 1.94899230863397, 1.94946285758305}, {2.94332698437659, 2.98796119092502, 2.96024565199338}, {3.80255904577673, 4.37008158805412, 4.35141415401432}, {4.68161529944488, 5.72204262950584, 5.66166566827365}, {5.87064634019722, 7.54306903541829, 7.3857474119367}, {7.78994087483808, 10.2359236535064, 9.95778205927185}},
{{1.19274531042455, 1.2152969832815, 1.20171324933339}, {2.2943803290662, 2.14415384022472, 2.15039994134979}, {3.25623650819256, 3.01385037727374, 3.03249686547772}, {4.27045252457434, 4.21446933397605, 4.23815367511332}, {5.17998028000972, 5.78600261610464, 5.81037357341976}, {6.19785417964287, 7.2822799946799, 7.26373154837891}, {7.69684274092057, 9.28633217515052, 9.21658162859184}, {10.0353932177135, 12.1802615431261, 11.9831585607954}},
{{2.4646732594343, 2.46387210793402, 2.43507278648256}, {3.49836053443852, 3.27953398208675, 3.26284773577962}, {4.28065963174638, 4.13119763592951, 4.1308271078573}, {5.14409465284924, 5.70765094689331, 5.74219324470954}, {5.98518749895616, 6.93385341523623, 6.96708272307546}, {7.0299847606958, 8.42771919562544, 8.40220952430735}, {8.57907078388286, 10.4160723222439, 10.3679017528836}, {11.0685666474499, 13.4531303115576, 13.2927862368604}},
{{3.70846881386602, 3.86908673744653, 3.83210828276994}, {4.80559125796947, 4.88056952563021, 4.82595757558342}, {5.42778400768362, 6.14578620000214, 6.17040735624603}, {6.08929033878163, 7.16832687031443, 7.20751420464431}, {6.86850046640731, 8.26420707459324, 8.29853895971336}, {7.84984618688477, 9.71743532538622, 9.7147262415522}, {9.51903135622904, 11.8234824848058, 11.7845246577222}, {12.0049646114253, 14.8400917638334, 14.7424927473365}},
{{4.90318525184508, 5.99581694008571, 5.95615698669246}, {5.97444669974084, 7.12987554548257, 7.1095906114534}, {6.53741678314503, 7.89774782069033, 7.85927030323856}, {7.13754772163163, 8.79910033955154, 8.77810164915207}, {7.7562396411917, 9.81614830137095, 9.77351931847552}, {8.76689841135206, 11.1452991551185, 11.0811843459497}, {10.513368171242, 13.2459275832513, 13.1009567613693}, {13.1979037369898, 16.3323036754675, 16.1491682798314}},
{{6.02160192386879, 8.0427560221024, 7.93223007591373}, {7.45458506605638, 9.27703833686395, 9.17127434342562}, {7.94204962999446, 9.98039068148056, 9.90039605113603}, {8.66079232688593, 10.8162595699569, 10.653988942289}, {9.47351300957499, 11.7292236951962, 11.5907955087553}, {10.2689869608636, 13.110178084127, 12.8952102613367}, {12.2305610572211, 15.2364340660148, 14.9924012867386}, {14.3930003174177, 18.3138604479156, 17.9595357309848}},
{{7.61123713909278, 10.5887158283599, 10.4192188429678}, {9.20731703451098, 12.0628893168648, 11.9550573473496}, {9.85709132996928, 12.8701941652904, 12.6276025472299}, {10.3165183194159, 13.605842264484, 13.4316611205688}, {11.2692286351072, 14.4761780529619, 14.1929497377358}, {12.4358837402085, 15.9177322372191, 15.568967449971}, {14.5577475321854, 18.1287696351735, 17.5958497696997}, {16.874317177599, 21.112705389718, 20.5780027036999}},
{{10.114830426421, 14.4286223409782, 14.081749212272}, {11.6031746911885, 16.1683668751011, 15.6551122883117}, {12.0837641414121, 16.7824928835092, 16.2757903018179}, {12.7704455546218, 17.5347046049121, 17.0202232421845}, {13.5794854546779, 18.3596755686211, 17.7964343726763}, {14.9521289593159, 19.7235148101556, 19.1020295141703}, {17.3688525982791, 21.8409326336491, 21.1185396395873}, {20.69640161027, 24.8034535963609, 23.8084412646294}}
};
@Override
public void extract(BufferedImage bimg) {
if (bimg.getColorModel().getColorSpace().getType() != ColorSpace.TYPE_RGB) {
BufferedImage img = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_INT_RGB);
img.getGraphics().drawImage(bimg, 0, 0, null);
bimg = img;
}
transform = createTransformArray();
int newWidth = bimg.getWidth() - bimg.getWidth() % BLOCK_SIZE;
int newHeight = bimg.getHeight() - bimg.getHeight() % BLOCK_SIZE;
int[][][] yuvImage = getYUVImage(bimg.getRaster(), newWidth, newHeight, -128);
descriptorValues = new int[BLOCK_SIZE * BLOCK_SIZE * 3];
getComponentHistogram(yuvImage, newWidth, newHeight, 0, descriptorValues);
getComponentHistogram(yuvImage, newWidth, newHeight, 1, descriptorValues);
getComponentHistogram(yuvImage, newWidth, newHeight, 2, descriptorValues);
}
@Override
public byte[] getByteArrayRepresentation() {
return SerializationUtils.toByteArray(descriptorValues);
}
@Override
public void setByteArrayRepresentation(byte[] in) {
descriptorValues = SerializationUtils.toIntArray(in);
}
@Override
public void setByteArrayRepresentation(byte[] in, int offset, int length) {
descriptorValues = SerializationUtils.toIntArray(in, offset, length);
}
@Override
public double[] getFeatureVector() {
double[] result = new double[descriptorValues.length];
for (int i = 0; i < descriptorValues.length; i++) {
result[i] = (double) descriptorValues[i];
}
return result;
}
protected class DctPoint {
public int i;
public int j;
public double v;
}
public class DctComparator implements Comparator<DctPoint> {
public int compare(DctPoint o1, DctPoint o2) {
// reverse sort
return Double.compare(o1.v, o2.v) * -1;
}
}
protected void getComponentHistogram(int[][][] yuvImage, int width, int height, int component, int[] descriptorBytes) {
int hBlockCount = width / BLOCK_SIZE;
int vBlockCount = height / BLOCK_SIZE;
double[][] tempHistogram = new double[BLOCK_SIZE][BLOCK_SIZE];
for (int by = 0; by < vBlockCount; by++) {
for (int bx = 0; bx < hBlockCount; bx++) {
double[][] dctValues = new double[BLOCK_SIZE][BLOCK_SIZE];
for (int v = 0; v < BLOCK_SIZE; v++) {
for (int u = 0; u < BLOCK_SIZE; u++) {
double t = 0;
for (int j = 0; j < BLOCK_SIZE; j++) {
for (int i = 0; i < BLOCK_SIZE; i++) {
t += yuvImage[bx * BLOCK_SIZE + i][by * BLOCK_SIZE + j][component] * transform[i][j][u][v];
// Info Dump
// System.out.println(i + ", " + j +", " + u +", " + v + ", " +t + ", " + yuvImage[bx * BLOCK_SIZE + i][by * BLOCK_SIZE + j][component] + ", " + transform[i][j][u][v]);
}
}
double cU = 1;
double cV = 1;
if (u == 0)
cU = 0.707106781;
if (v == 0)
cV = 0.707106781;
dctValues[u][v] = cU * cV * t / 4;
}
}
// filtering for noise (ensures that c# and java implementations give the same output)
dctValues[0][0] = 0;
for (int j = 0; j < BLOCK_SIZE; j++) {
for (int i = 0; i < BLOCK_SIZE; i++) {
if (Math.abs(dctValues[i][j]) < 0.001)
dctValues[i][j] = 0;
}
}
DctPoint[] dctPoints = new DctPoint[BLOCK_SIZE * BLOCK_SIZE];
int p = 0;
for (int j = 0; j < BLOCK_SIZE; j++) {
for (int i = 0; i < BLOCK_SIZE; i++) {
dctPoints[p] = new DctPoint();
dctPoints[p].i = i;
dctPoints[p].j = j;
dctPoints[p].v = Math.abs(dctScaler2[i][j][component] * dctValues[i][j]);
p++;
}
}
Arrays.sort(dctPoints, new DctComparator());
int n = 0;
for (int cc = 0; cc < (BLOCK_SIZE * BLOCK_SIZE); cc++) {
tempHistogram[dctPoints[cc].i][dctPoints[cc].j] += 1.0 / (cc + 1);
n++;
if (n == 8)
break;
}
}
}
double maxPoint = 0;
for (int j = 0; j < BLOCK_SIZE; j++) {
for (int i = 0; i < BLOCK_SIZE; i++) {
if (tempHistogram[i][j] > maxPoint)
maxPoint = tempHistogram[i][j];
}
}
int p = BLOCK_SIZE * BLOCK_SIZE * component;
for (int j = 0; j < BLOCK_SIZE; j++) {
for (int i = 0; i < BLOCK_SIZE; i++) {
descriptorBytes[p] = (int) (tempHistogram[i][j] / maxPoint * 255);
p++;
}
}
}
@Override
public double getDistance(LireFeature vd) {
if (!(vd instanceof JpegCoefficientHistogram))
throw new UnsupportedOperationException("Wrong descriptor.");
JpegCoefficientHistogram target = (JpegCoefficientHistogram) vd;
if (descriptorValues == null)
throw new UnsupportedOperationException("source descriptor bytes are null");
if (target.descriptorValues == null)
throw new UnsupportedOperationException("target descriptor bytes are null");
int size2 = BLOCK_SIZE * BLOCK_SIZE * 3;
double distance = 0;
for (int i = 0; i < size2; i++)
distance += ((descriptorValues[i] - target.descriptorValues[i]) * (descriptorValues[i] - target.descriptorValues[i]));
return Math.sqrt(distance / size2);
}
// public String getStringRepresentation() { // added by mlux
// StringBuilder sb = new StringBuilder(descriptorValues.length * 2 + 25);
// sb.append("jpegcoeffhist");
// sb.append(' ');
// sb.append(descriptorValues.length);
// sb.append(' ');
// for (double aData : descriptorValues) {
// sb.append((int) aData);
// sb.append(' ');
// }
// return sb.toString().trim();
// }
//
// public void setStringRepresentation(String s) { // added by mlux
// StringTokenizer st = new StringTokenizer(s);
// if (!st.nextToken().equals("jpegcoeffhist"))
// throw new UnsupportedOperationException("This is not a jpegcoeffhist descriptor.");
// descriptorValues = new int[Integer.parseInt(st.nextToken())];
// for (int i = 0; i < descriptorValues.length; i++) {
// if (!st.hasMoreTokens())
// throw new IndexOutOfBoundsException("Too few numbers in string representation.");
// descriptorValues[i] = (int) Integer.parseInt(st.nextToken());
// }
//
// }
protected int[][][] getYUVImage(WritableRaster raster, int newWidth, int newHeight, int shift) { //TODO: rgb2yuv Conversion
int[][][] yuvImage = new int[newWidth][newHeight][3];
int[] rgbPixel = new int[3];
for (int j = 0; j < newHeight; j++) {
for (int i = 0; i < newWidth; i++) {
raster.getPixel(i, j, rgbPixel);
int r = rgbPixel[0];
int g = rgbPixel[1];
int b = rgbPixel[2];
// order: Y, U, V
yuvImage[i][j][0] = (int) (0.299 * r + 0.587 * g + 0.114 * b) + shift;
yuvImage[i][j][1] = (int) (128 - 0.1687 * r - 0.3313 * g + 0.5 * b) + shift;
yuvImage[i][j][2] = (int) (128 + 0.5 * r - 0.4187 * g - 0.0813 * b) + shift;
}
}
return yuvImage;
}
protected double[][][][] createTransformArray() {
double[][][][] t = new double[BLOCK_SIZE][BLOCK_SIZE][BLOCK_SIZE][BLOCK_SIZE];
for (int v = 0; v < BLOCK_SIZE; v++) {
for (int u = 0; u < BLOCK_SIZE; u++) {
for (int j = 0; j < BLOCK_SIZE; j++) {
for (int i = 0; i < BLOCK_SIZE; i++) {
t[i][j][u][v] = Math.cos(((2 * i + 1) * u * Math.PI) / 2 / BLOCK_SIZE)
* Math.cos(((2 * j + 1) * v * Math.PI) / 2 / BLOCK_SIZE);
}
}
}
}
return t;
}
@Override
public String getFeatureName() {
return "Histogram of JPEG Coefficients";
}
@Override
public String getFieldName() {
return DocumentBuilder.FIELD_NAME_JPEGCOEFFS;
}
}