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; } }