package org.elasticsearch.plugin.image.test;
import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.ImageWriteException;
import org.apache.sanselan.Sanselan;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.mapper.image.FeatureEnum;
import org.elasticsearch.index.mapper.image.HashEnum;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.image.ImageQueryBuilder;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Before;
import org.junit.Test;
import java.awt.image.BufferedImage;
import java.io.IOException;
import static org.elasticsearch.client.Requests.putMappingRequest;
import static org.elasticsearch.common.io.Streams.copyToStringFromClasspath;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.*;
@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE)
public class ImageIntegrationTests extends ElasticsearchIntegrationTest {
private final static String INDEX_NAME = "test";
private final static String DOC_TYPE_NAME = "test";
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
.build();
}
@Before
public void createEmptyIndex() throws Exception {
logger.info("creating index [{}]", INDEX_NAME);
createIndex(INDEX_NAME);
ensureGreen();
}
@Override
public Settings indexSettings() {
return settingsBuilder()
.put("index.number_of_replicas", 0)
.put("index.number_of_shards", 5)
.put("index.image.use_thread_pool", randomBoolean())
.build();
}
@Test
public void test_index_search_image() throws Exception {
String mapping = copyToStringFromClasspath("/mapping/test-mapping.json");
client().admin().indices().putMapping(putMappingRequest(INDEX_NAME).type(DOC_TYPE_NAME).source(mapping)).actionGet();
int totalImages = randomIntBetween(10, 50);
// generate random images and index
String nameToSearch = null;
byte[] imgToSearch = null;
String idToSearch = null;
for (int i = 0; i < totalImages; i ++) {
byte[] imageByte = getRandomImage();
String name = randomAsciiOfLength(5);
IndexResponse response = index(INDEX_NAME, DOC_TYPE_NAME, jsonBuilder().startObject().field("img", imageByte).field("name", name).endObject());
if (nameToSearch == null || imgToSearch == null || idToSearch == null) {
nameToSearch = name;
imgToSearch = imageByte;
idToSearch = response.getId();
}
}
refresh();
// test search with hash
ImageQueryBuilder imageQueryBuilder = new ImageQueryBuilder("img").feature(FeatureEnum.CEDD.name()).image(imgToSearch).hash(HashEnum.BIT_SAMPLING.name());
SearchResponse searchResponse = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(imageQueryBuilder).addFields("img.metadata.exif_ifd0.x_resolution", "name").setSize(totalImages).get();
assertNoFailures(searchResponse);
SearchHits hits = searchResponse.getHits();
assertThat("Should match at least one image", hits.getTotalHits(), greaterThanOrEqualTo(1l)); // if using hash, total result maybe different than number of images
SearchHit hit = hits.getHits()[0];
assertThat("First should be exact match and has score 1", hit.getScore(), equalTo(2.0f));
assertImageScore(hits, nameToSearch, 2.0f);
assertThat("Should have metadata", hit.getFields().get("img.metadata.exif_ifd0.x_resolution").getValues(), hasSize(1));
// test search without hash and with boost
ImageQueryBuilder imageQueryBuilder2 = new ImageQueryBuilder("img").feature(FeatureEnum.CEDD.name()).image(imgToSearch).boost(2.0f);
SearchResponse searchResponse2 = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(imageQueryBuilder2).setSize(totalImages).get();
assertNoFailures(searchResponse2);
SearchHits hits2 = searchResponse2.getHits();
assertThat("Should get all images", hits2.getTotalHits(), equalTo((long)totalImages)); // no hash used, total result should be same as number of images
assertThat("First should be exact match and has score 2", searchResponse2.getHits().getMaxScore(), equalTo(4.0f));
assertImageScore(hits2, nameToSearch, 4.0f);
// test search for name as well
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.termQuery("name", nameToSearch));
boolQueryBuilder.must(new ImageQueryBuilder("img").feature(FeatureEnum.CEDD.name()).image(imgToSearch));
SearchResponse searchResponse3 = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(boolQueryBuilder).setSize(totalImages).get();
assertNoFailures(searchResponse3);
SearchHits hits3 = searchResponse3.getHits();
assertThat("Should match one document only", hits3.getTotalHits(), equalTo(1l)); // added filename to query, should have only one result
SearchHit hit3 = hits3.getHits()[0];
assertThat((String)hit3.getSource().get("name"), equalTo(nameToSearch));
// test search with hash and limit
ImageQueryBuilder imageQueryBuilder4 = new ImageQueryBuilder("img").feature(FeatureEnum.CEDD.name()).image(imgToSearch).hash(HashEnum.BIT_SAMPLING.name()).limit(10);
SearchResponse searchResponse4 = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(imageQueryBuilder4).setSize(totalImages).get();
assertNoFailures(searchResponse4);
SearchHits hits4 = searchResponse4.getHits();
assertThat("Should match at least one image", hits4.getTotalHits(), greaterThanOrEqualTo(1l)); // if using hash, total result maybe different than number of images
SearchHit hit4 = hits4.getHits()[0];
assertThat("First should be exact match and has score 1", hit4.getScore(), equalTo(2.0f));
assertImageScore(hits4, nameToSearch, 2.0f);
// test search metadata
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("img.metadata.exif_ifd0.x_resolution", "72 dots per inch");
SearchResponse searchResponse5 = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(termQueryBuilder).setSize(totalImages).get();
assertNoFailures(searchResponse5);
SearchHits hits5 = searchResponse5.getHits();
assertThat("Should match at least one record", hits5.getTotalHits(), greaterThanOrEqualTo(1l)); // if using hash, total result maybe different than number of images
// test search with exist image
ImageQueryBuilder imageQueryBuilder6 = new ImageQueryBuilder("img").feature(FeatureEnum.CEDD.name()).lookupIndex(INDEX_NAME).lookupType(DOC_TYPE_NAME).lookupId(idToSearch).lookupPath("img");
SearchResponse searchResponse6 = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(imageQueryBuilder6).setSize(totalImages).get();
assertNoFailures(searchResponse6);
SearchHits hits6 = searchResponse6.getHits();
assertThat("Should match at least one image", hits6.getTotalHits(), equalTo((long) totalImages));
SearchHit hit6 = hits6.getHits()[0];
assertThat("First should be exact match and has score 1", hit6.getScore(), equalTo(2.0f));
assertImageScore(hits6, nameToSearch, 2.0f);
// test search with exist image using hash
ImageQueryBuilder imageQueryBuilder7 = new ImageQueryBuilder("img").feature(FeatureEnum.CEDD.name()).lookupIndex(INDEX_NAME).lookupType(DOC_TYPE_NAME).lookupId(idToSearch).lookupPath("img").hash(HashEnum.BIT_SAMPLING.name());
SearchResponse searchResponse7 = client().prepareSearch(INDEX_NAME).setTypes(DOC_TYPE_NAME).setQuery(imageQueryBuilder7).setSize(totalImages).get();
assertNoFailures(searchResponse7);
SearchHits hits7 = searchResponse7.getHits();
assertThat("Should match at least one image", hits7.getTotalHits(), equalTo((long) totalImages));
SearchHit hit7 = hits7.getHits()[0];
assertThat("First should be exact match and has score 1", hit7.getScore(), equalTo(2.0f));
assertImageScore(hits7, nameToSearch, 2.0f);
}
private void assertImageScore(SearchHits hits, String name, float score) {
for (SearchHit hit : hits) {
if ((hit.getSource() != null && hit.getSource().get("name").equals(name))
|| (hit.getFields() != null && !hit.getFields().isEmpty() && hit.getFields().get("name").getValue().equals(name))){
assertThat(hit.getScore(), equalTo(score));
return;
}
}
throw new AssertionError("Image " + name + " not found");
}
private byte[] getRandomImage() throws IOException, ImageWriteException {
int width = randomIntBetween(100, 1000);
int height = randomIntBetween(100, 1000);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int j = 0; j < width; j ++) {
for (int k = 0; k < height; k ++) {
image.setRGB(j, k, randomInt(512));
}
}
ImageFormat format = ImageFormat.IMAGE_FORMAT_TIFF;
byte[] bytes = Sanselan.writeImageToBytes(image, format, Maps.newHashMap());
return bytes;
}
}