GH-7: Add checkstyle and javaformat plugins

Fixes: #7

* Run `./gradlew format`
* Updates from PR review suggestions
This commit is contained in:
Chris Bono
2023-12-29 21:08:58 -06:00
committed by Artem Bilan
parent 84e732da08
commit 836708f0f2
357 changed files with 4344 additions and 4519 deletions

View File

@@ -63,17 +63,11 @@ public class AggregatorFunctionConfiguration {
private BeanFactory beanFactory;
@Bean
public Function<Flux<Message<?>>, Flux<Message<?>>> aggregatorFunction(
FluxMessageChannel inputChannel,
FluxMessageChannel outputChannel
) {
public Function<Flux<Message<?>>, Flux<Message<?>>> aggregatorFunction(FluxMessageChannel inputChannel,
FluxMessageChannel outputChannel) {
return input -> Flux.from(outputChannel)
.doOnRequest((request) ->
inputChannel.subscribeTo(
input.map((inputMessage) ->
MessageBuilder.fromMessage(inputMessage)
.removeHeader("kafka_consumer")
.build())));
.doOnRequest((request) -> inputChannel.subscribeTo(input.map((
inputMessage) -> MessageBuilder.fromMessage(inputMessage).removeHeader("kafka_consumer").build())));
}
@Bean
@@ -88,13 +82,10 @@ public class AggregatorFunctionConfiguration {
@Bean
@ServiceActivator(inputChannel = "inputChannel")
public AggregatorFactoryBean aggregator(
@Nullable CorrelationStrategy correlationStrategy,
@Nullable ReleaseStrategy releaseStrategy,
@Nullable MessageGroupProcessor messageGroupProcessor,
@Nullable MessageGroupStore messageStore,
@Qualifier("outputChannel") MessageChannel outputChannel,
@Nullable ComponentCustomizer<AggregatorFactoryBean> aggregatorCustomizer) {
public AggregatorFactoryBean aggregator(@Nullable CorrelationStrategy correlationStrategy,
@Nullable ReleaseStrategy releaseStrategy, @Nullable MessageGroupProcessor messageGroupProcessor,
@Nullable MessageGroupStore messageStore, @Qualifier("outputChannel") MessageChannel outputChannel,
@Nullable ComponentCustomizer<AggregatorFactoryBean> aggregatorCustomizer) {
AggregatorFactoryBean aggregator = new AggregatorFactoryBean();
aggregator.setExpireGroupsUponCompletion(true);
@@ -149,14 +140,10 @@ public class AggregatorFunctionConfiguration {
return new ExpressionEvaluatingMessageGroupProcessor(this.properties.getAggregation().getExpressionString());
}
@Configuration
@ConditionalOnMissingBean(MessageGroupStore.class)
@Import({
MessageStoreConfiguration.Mongo.class,
MessageStoreConfiguration.Redis.class,
MessageStoreConfiguration.Jdbc.class
})
@Import({ MessageStoreConfiguration.Mongo.class, MessageStoreConfiguration.Redis.class,
MessageStoreConfiguration.Jdbc.class })
protected static class MessageStoreAutoConfiguration {
}

View File

@@ -55,7 +55,8 @@ public class AggregatorFunctionProperties {
private String messageStoreType = MessageStoreType.SIMPLE;
/**
* Persistence message store entity: table prefix in RDBMS, collection name in MongoDb, etc.
* Persistence message store entity: table prefix in RDBMS, collection name in
* MongoDb, etc.
*/
private String messageStoreEntity;

View File

@@ -32,8 +32,9 @@ import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
/**
* An {@link EnvironmentPostProcessor} to add {@code spring.autoconfigure.exclude} property
* since we can't use {@code application.properties} from the library perspective.
* An {@link EnvironmentPostProcessor} to add {@code spring.autoconfigure.exclude}
* property since we can't use {@code application.properties} from the library
* perspective.
*
* @author Artem Bilan
* @author Corneil du Plessis
@@ -45,16 +46,14 @@ public class ExcludeStoresAutoConfigurationEnvironmentPostProcessor implements E
MutablePropertySources propertySources = environment.getPropertySources();
Properties properties = new Properties();
properties.setProperty("spring.autoconfigure.exclude",
DataSourceAutoConfiguration.class.getName() + ", " +
DataSourceTransactionManagerAutoConfiguration.class.getName() + ", " +
MongoAutoConfiguration.class.getName() + ", " +
MongoDataAutoConfiguration.class.getName() + ", " +
MongoRepositoriesAutoConfiguration.class.getName() + ", " +
RedisAutoConfiguration.class.getName() + ", " +
RedisRepositoriesAutoConfiguration.class.getName());
properties.setProperty("spring.autoconfigure.exclude", DataSourceAutoConfiguration.class.getName() + ", "
+ DataSourceTransactionManagerAutoConfiguration.class.getName() + ", "
+ MongoAutoConfiguration.class.getName() + ", " + MongoDataAutoConfiguration.class.getName() + ", "
+ MongoRepositoriesAutoConfiguration.class.getName() + ", " + RedisAutoConfiguration.class.getName()
+ ", " + RedisRepositoriesAutoConfiguration.class.getName());
propertySources.addLast(new PropertiesPropertySource("aggregator.exclude.stores.auto-configuration", properties));
propertySources
.addLast(new PropertiesPropertySource("aggregator.exclude.stores.auto-configuration", properties));
}
}

View File

@@ -40,11 +40,10 @@ import org.springframework.integration.store.MessageGroupStore;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.StringUtils;
/**
* A helper class containing configuration classes for particular technologies
* to expose an appropriate {@link org.springframework.integration.store.MessageStore} bean
* via matched configuration properties.
* A helper class containing configuration classes for particular technologies to expose
* an appropriate {@link org.springframework.integration.store.MessageStore} bean via
* matched configuration properties.
*
* @author Artem Bilan
* @author Corneil du Plessis
@@ -52,11 +51,9 @@ import org.springframework.util.StringUtils;
class MessageStoreConfiguration {
@ConditionalOnClass(ConfigurableMongoDbMessageStore.class)
@ConditionalOnProperty(prefix = AggregatorFunctionProperties.PREFIX,
name = "message-store-type",
@ConditionalOnProperty(prefix = AggregatorFunctionProperties.PREFIX, name = "message-store-type",
havingValue = AggregatorFunctionProperties.MessageStoreType.MONGODB)
@Import({ MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class })
@Import({ MongoAutoConfiguration.class, MongoDataAutoConfiguration.class })
static class Mongo {
@Bean
@@ -72,15 +69,14 @@ class MessageStoreConfiguration {
@Bean
@Primary
public MongoCustomConversions mongoDbCustomConversions() {
return new MongoCustomConversions(Arrays.asList(
new MessageToBinaryConverter(), new BinaryToMessageConverter()));
return new MongoCustomConversions(
Arrays.asList(new MessageToBinaryConverter(), new BinaryToMessageConverter()));
}
}
@ConditionalOnClass(RedisMessageStore.class)
@ConditionalOnProperty(prefix = AggregatorFunctionProperties.PREFIX,
name = "message-store-type",
@ConditionalOnProperty(prefix = AggregatorFunctionProperties.PREFIX, name = "message-store-type",
havingValue = AggregatorFunctionProperties.MessageStoreType.REDIS)
@Import(RedisAutoConfiguration.class)
static class Redis {
@@ -93,12 +89,9 @@ class MessageStoreConfiguration {
}
@ConditionalOnClass(JdbcMessageStore.class)
@ConditionalOnProperty(prefix = AggregatorFunctionProperties.PREFIX,
name = "message-store-type",
@ConditionalOnProperty(prefix = AggregatorFunctionProperties.PREFIX, name = "message-store-type",
havingValue = AggregatorFunctionProperties.MessageStoreType.JDBC)
@Import({
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
@Import({ DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
static class Jdbc {
@Bean

View File

@@ -48,9 +48,11 @@ public abstract class AbstractAggregatorFunctionTests {
@SpringBootApplication
public static class AggregatorFunctionTestApplication {
public static void main(String[] args) {
SpringApplication.run(AggregatorFunctionTestApplication.class, args);
}
}
}

View File

@@ -38,13 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Artem Bilan
*/
@TestPropertySource(properties = {
"aggregator.correlation=T(Thread).currentThread().id",
@TestPropertySource(properties = { "aggregator.correlation=T(Thread).currentThread().id",
"aggregator.release=!messages.?[payload == 'bar'].empty",
"aggregator.aggregation=#this.?[payload == 'foo'].![payload]",
"aggregator.messageStoreType=mongodb",
"aggregator.message-store-entity=aggregatorTest"
})
"aggregator.aggregation=#this.?[payload == 'foo'].![payload]", "aggregator.messageStoreType=mongodb",
"aggregator.message-store-entity=aggregatorTest" })
@AutoConfigureDataMongo
public class CustomPropsAndMongoMessageStoreAggregatorTests extends AbstractAggregatorFunctionTests
implements MongoDbTestContainerSupport {
@@ -57,22 +54,19 @@ public class CustomPropsAndMongoMessageStoreAggregatorTests extends AbstractAggr
@Test
public void test() {
Flux<Message<?>> input =
Flux.just("foo", "bar")
.map(GenericMessage::new);
Flux<Message<?>> input = Flux.just("foo", "bar").map(GenericMessage::new);
Flux<Message<?>> output = this.aggregatorFunction.apply(input);
output.as(StepVerifier::create)
.assertNext((message) ->
assertThat(message)
.extracting(Message::getPayload)
.isInstanceOf(List.class)
.asList()
.hasSize(1)
.element(0).isEqualTo("foo"))
.thenCancel()
.verify(Duration.ofSeconds(10));
.assertNext((message) -> assertThat(message).extracting(Message::getPayload)
.isInstanceOf(List.class)
.asList()
.hasSize(1)
.element(0)
.isEqualTo("foo"))
.thenCancel()
.verify(Duration.ofSeconds(10));
assertThat(this.messageGroupStore).isInstanceOf(ConfigurableMongoDbMessageStore.class);
assertThat(TestUtils.getPropertyValue(this.messageGroupStore, "collectionName")).isEqualTo("aggregatorTest");

View File

@@ -40,12 +40,13 @@ import static org.assertj.core.api.Assertions.assertThat;
@Disabled("Fails on CI sporadically")
@TestPropertySource(properties = "aggregator.message-store-type=simple")
public class DefaultAggregatorTests extends AbstractAggregatorFunctionTests {
private static final Logger logger = LoggerFactory.getLogger(DefaultAggregatorTests.class);
@Test
public void test() {
Flux<Message<?>> input =
Flux.just(MessageBuilder.withPayload("2")
Flux<Message<?>> input = Flux.just(
MessageBuilder.withPayload("2")
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "my_correlation")
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 2)
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 2)
@@ -57,20 +58,13 @@ public class DefaultAggregatorTests extends AbstractAggregatorFunctionTests {
.build());
Flux<Message<?>> output = this.aggregatorFunction.apply(input.log("DefaultAggregatorTests:input"));
output.log("DefaultAggregatorTests:output")
.as(StepVerifier::create)
.assertNext((message) -> {
assertThat(message)
.extracting(Message::getPayload)
.asList()
.hasSize(2)
.contains("1", "2");
})
.thenCancel()
.verify(Duration.ofSeconds(30));
output.log("DefaultAggregatorTests:output").as(StepVerifier::create).assertNext((message) -> {
assertThat(message).extracting(Message::getPayload).asList().hasSize(2).contains("1", "2");
}).thenCancel().verify(Duration.ofSeconds(30));
assertThat(this.messageGroupStore).isNull();
assertThat(this.aggregatingMessageHandler.getMessageStore()).isInstanceOf(SimpleMessageStore.class);
}
}

View File

@@ -40,30 +40,28 @@ public class JdbcMessageStoreAggregatorTests extends AbstractAggregatorFunctionT
@Test
public void test() {
Flux<Message<?>> input =
Flux.just(MessageBuilder.withPayload("2")
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "my_correlation")
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 2)
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 2)
.build(),
MessageBuilder.withPayload("1")
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "my_correlation")
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 1)
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 2)
.build());
Flux<Message<?>> input = Flux.just(
MessageBuilder.withPayload("2")
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "my_correlation")
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 2)
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 2)
.build(),
MessageBuilder.withPayload("1")
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "my_correlation")
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 1)
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 2)
.build());
Flux<Message<?>> output = this.aggregatorFunction.apply(input);
output.as(StepVerifier::create)
.assertNext((message) ->
assertThat(message)
.extracting(Message::getPayload)
.isInstanceOf(List.class)
.asList()
.hasSize(2)
.contains("1", "2"))
.thenCancel()
.verify(Duration.ofSeconds(10));
.assertNext((message) -> assertThat(message).extracting(Message::getPayload)
.isInstanceOf(List.class)
.asList()
.hasSize(2)
.contains("1", "2"))
.thenCancel()
.verify(Duration.ofSeconds(10));
assertThat(this.messageGroupStore).isInstanceOf(JdbcMessageStore.class);

View File

@@ -41,7 +41,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Artem Bilan
*/
@TestPropertySource(properties = "aggregator.message-store-type=redis")
public class RedisMessageStoreAggregatorTests extends AbstractAggregatorFunctionTests implements RedisTestContainerSupport {
public class RedisMessageStoreAggregatorTests extends AbstractAggregatorFunctionTests
implements RedisTestContainerSupport {
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
@@ -52,8 +53,8 @@ public class RedisMessageStoreAggregatorTests extends AbstractAggregatorFunction
public void test() {
InputStream fakeNonSerializableKafkaConsumer = new ByteArrayInputStream(new byte[0]);
Flux<Message<?>> input =
Flux.just(MessageBuilder.withPayload("2")
Flux<Message<?>> input = Flux.just(
MessageBuilder.withPayload("2")
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "my_correlation")
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 2)
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 2)
@@ -68,13 +69,11 @@ public class RedisMessageStoreAggregatorTests extends AbstractAggregatorFunction
Flux<Message<?>> output = this.aggregatorFunction.apply(input);
output.as(StepVerifier::create)
.assertNext((message) ->
assertThat(message)
.extracting(Message::getPayload)
.isInstanceOf(List.class)
.asList()
.hasSize(2)
.contains("1", "2"))
.assertNext((message) -> assertThat(message).extracting(Message::getPayload)
.isInstanceOf(List.class)
.asList()
.hasSize(2)
.contains("1", "2"))
.thenCancel()
.verify(Duration.ofSeconds(10));

View File

@@ -34,7 +34,7 @@ public class FilterFunctionConfiguration {
@Bean
public Function<Message<?>, Message<?>> filterFunction(
ExpressionEvaluatingTransformer filterExpressionEvaluatingTransformer) {
ExpressionEvaluatingTransformer filterExpressionEvaluatingTransformer) {
return message -> {
if ((Boolean) filterExpressionEvaluatingTransformer.transform(message).getPayload()) {
@@ -48,7 +48,7 @@ public class FilterFunctionConfiguration {
@Bean
public ExpressionEvaluatingTransformer filterExpressionEvaluatingTransformer(
FilterFunctionProperties filterFunctionProperties) {
FilterFunctionProperties filterFunctionProperties) {
return new ExpressionEvaluatingTransformer(filterFunctionProperties.getExpression());
}

View File

@@ -21,11 +21,8 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -50,10 +47,9 @@ public class FilterFunctionApplicationTests {
@Test
public void testFilter() {
Stream<Message<?>> messages = List.of("hello", "hello world")
.stream()
.map(GenericMessage::new);
List<Message<?>> result = messages.filter(message -> this.filter.apply(message) != null).collect(Collectors.toList());
Stream<Message<?>> messages = List.of("hello", "hello world").stream().map(GenericMessage::new);
List<Message<?>> result = messages.filter(message -> this.filter.apply(message) != null)
.collect(Collectors.toList());
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0).getPayload()).isNotNull();
assertThat(result.get(0).getPayload()).isEqualTo("hello world");

View File

@@ -58,8 +58,8 @@ public class HeaderEnricherFunctionConfiguration {
Enumeration<?> enumeration = props.propertyNames();
while (enumeration.hasMoreElements()) {
String propertyName = (String) enumeration.nextElement();
ExpressionEvaluatingHeaderValueMessageProcessor<?> headerValueMessageProcessor =
new ExpressionEvaluatingHeaderValueMessageProcessor<>(props.getProperty(propertyName), null);
ExpressionEvaluatingHeaderValueMessageProcessor<?> headerValueMessageProcessor = new ExpressionEvaluatingHeaderValueMessageProcessor<>(
props.getProperty(propertyName), null);
headerValueMessageProcessor.setBeanFactory(beanFactory);
headersToAdd.put(propertyName, headerValueMessageProcessor);
}

View File

@@ -35,8 +35,8 @@ import org.springframework.validation.annotation.Validated;
public class HeaderEnricherFunctionProperties {
/**
* \n separated properties representing headers in which values are SpEL expressions, e.g
* foo='bar' \n baz=payload.baz.
* \n separated properties representing headers in which values are SpEL expressions,
* e.g foo='bar' \n baz=payload.baz.
*/
private Properties headers;

View File

@@ -33,12 +33,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
*
* @author Gary Russell
* @author Soby Chacko
*/
@SpringBootTest(properties = {
"header.enricher.headers=foo='bar' \\n baz='fiz' \\n buz=payload \\n jaz=@value",
@SpringBootTest(properties = { "header.enricher.headers=foo='bar' \\n baz='fiz' \\n buz=payload \\n jaz=@value",
"header.enricher.overwrite = true" })
@DirtiesContext
public class HeaderEnricherFunctionApplicationTests {
@@ -48,8 +46,7 @@ public class HeaderEnricherFunctionApplicationTests {
@Test
public void testDefault() {
final Message<?> message = MessageBuilder.withPayload("hello")
.setHeader("baz", "qux").build();
final Message<?> message = MessageBuilder.withPayload("hello").setHeader("baz", "qux").build();
final Message<?> enriched = headerEnricherFunction.apply(message);
assertThat(enriched, HeaderMatcher.hasHeader("foo", equalTo("bar")));

View File

@@ -37,7 +37,9 @@ import org.springframework.util.StringUtils;
@EnableConfigurationProperties(HeaderFilterFunctionProperties.class)
@ConditionalOnExpression("'${header.filter.remove}'!='' or '${header.filter.delete-all}' != ''")
public class HeaderFilterFunctionConfiguration {
private final HeaderFilterFunctionProperties properties;
public HeaderFilterFunctionConfiguration(HeaderFilterFunctionProperties properties) {
this.properties = properties;
}
@@ -46,7 +48,7 @@ public class HeaderFilterFunctionConfiguration {
public Function<Message<?>, Message<?>> headerFilterFunction() {
if (properties.isDeleteAll()) {
return (message) -> {
var accessor = new IntegrationMessageHeaderAccessor(message);
var accessor = new IntegrationMessageHeaderAccessor(message);
var headers = new HashSet<>(message.getHeaders().keySet());
headers.removeIf(accessor::isReadOnly);
HeaderFilter filter = new HeaderFilter(headers.toArray(new String[0]));

View File

@@ -20,18 +20,20 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties for configuration of header-filter-function.
*
* @author Corneil du Plessis
*/
@ConfigurationProperties("header.filter")
public class HeaderFilterFunctionProperties {
/**
* Indicates the need to remove all headers.
*/
private boolean deleteAll = false;
/**
* Remove all headers named. A comma, space separated list of header names.
* The names may contain patterns.
* Remove all headers named. A comma, space separated list of header names. The names
* may contain patterns.
*/
private String remove;
@@ -50,4 +52,5 @@ public class HeaderFilterFunctionProperties {
public void setRemove(String remove) {
this.remove = remove;
}
}

View File

@@ -30,13 +30,10 @@ import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = {
HeaderFilterFunctionApplicationDeleteAllTests.HeaderFilterFunctionTestApplication.class,
HeaderFilterFunctionConfiguration.class
},
properties = {"header.filter.delete-all=true"}
)
@SpringBootTest(classes = { HeaderFilterFunctionApplicationDeleteAllTests.HeaderFilterFunctionTestApplication.class,
HeaderFilterFunctionConfiguration.class }, properties = { "header.filter.delete-all=true" })
public class HeaderFilterFunctionApplicationDeleteAllTests {
@Autowired
protected Function<Message<?>, Message<?>> headerFilter;
@@ -54,9 +51,11 @@ public class HeaderFilterFunctionApplicationDeleteAllTests {
@SpringBootApplication
static class HeaderFilterFunctionTestApplication {
public static void main(String[] args) throws Exception {
SpringApplication.main(args);
}
}
}

View File

@@ -29,13 +29,10 @@ import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = {
HeaderFilterFunctionApplicationTests.HeaderFilterFunctionTestApplication.class,
HeaderFilterFunctionConfiguration.class
},
properties = {"header.filter.remove=foo,bar,pf-*"}
)
@SpringBootTest(classes = { HeaderFilterFunctionApplicationTests.HeaderFilterFunctionTestApplication.class,
HeaderFilterFunctionConfiguration.class }, properties = { "header.filter.remove=foo,bar,pf-*" })
public class HeaderFilterFunctionApplicationTests {
@Autowired
protected Function<Message<?>, Message<?>> headerFilter;
@@ -93,9 +90,11 @@ public class HeaderFilterFunctionApplicationTests {
@SpringBootApplication
static class HeaderFilterFunctionTestApplication {
public static void main(String[] main) {
}
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.messaging.Message;
final public class HeaderUtils {
private HeaderUtils() {
}
@@ -35,4 +36,5 @@ final public class HeaderUtils {
headers.removeIf(accessor::isReadOnly);
return headers;
}
}

View File

@@ -33,7 +33,6 @@ import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
/**
* Configuration for a {@link Function} that makes HTTP requests to a resource and for
* each request, returns a {@link ResponseEntity}.
@@ -47,7 +46,8 @@ import org.springframework.web.util.UriBuilderFactory;
public class HttpRequestFunctionConfiguration {
@Bean
public HttpRequestFunction httpRequestFunction(WebClient.Builder webClientBuilder, HttpRequestFunctionProperties properties) {
public HttpRequestFunction httpRequestFunction(WebClient.Builder webClientBuilder,
HttpRequestFunctionProperties properties) {
return new HttpRequestFunction(webClientBuilder.build(), properties);
}
@@ -76,8 +76,7 @@ public class HttpRequestFunctionConfiguration {
@Override
public Object apply(Message<?> message) {
return this.webClient
.method(resolveHttpMethod(message))
return this.webClient.method(resolveHttpMethod(message))
.uri(uriBuilderFactory.uriString(resolveUrl(message)).build())
.bodyValue(resolveBody(message))
.headers(httpHeaders -> httpHeaders.addAll(resolveHeaders(message)))
@@ -98,7 +97,7 @@ public class HttpRequestFunctionConfiguration {
private Object resolveBody(Message<?> message) {
return properties.getBodyExpression() != null ? properties.getBodyExpression().getValue(message)
: message.getPayload();
: message.getPayload();
}
private HttpHeaders resolveHeaders(Message<?> message) {
@@ -107,8 +106,7 @@ public class HttpRequestFunctionConfiguration {
Map<?, ?> headersMap = properties.getHeadersExpression().getValue(message, Map.class);
for (Map.Entry<?, ?> header : headersMap.entrySet()) {
if (header.getKey() != null && header.getValue() != null) {
headers.add(header.getKey().toString(),
header.getValue().toString());
headers.add(header.getKey().toString(), header.getValue().toString());
}
}
}
@@ -123,5 +121,7 @@ public class HttpRequestFunctionConfiguration {
public HttpMethod convert(String source) {
return HttpMethod.valueOf(source);
}
}
}

View File

@@ -43,25 +43,21 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
public class HttpRequestFunctionTestApplicationTests {
private MockWebServer server = new MockWebServer();
private ApplicationContextRunner runner;
@BeforeEach
void setup() {
this.runner = new ApplicationContextRunner()
.withUserConfiguration(HttpRequestFunctionTestApplication.class)
.withPropertyValues(
"http.request.reply-expression=#root",
"http.request.url-expression='" + url() + "'");
this.runner = new ApplicationContextRunner().withUserConfiguration(HttpRequestFunctionTestApplication.class)
.withPropertyValues("http.request.reply-expression=#root", "http.request.url-expression='" + url() + "'");
}
@Test
void shouldReturnString() {
server.enqueue(new MockResponse()
.setResponseCode(HttpStatus.OK.value())
.setBody("hello"));
server.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value()).setBody("hello"));
runner.withPropertyValues("http.request.http-method-expression='POST'").run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
@@ -78,26 +74,27 @@ public class HttpRequestFunctionTestApplicationTests {
server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE,
recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.CREATED.value());
}
});
runner.withPropertyValues("http.request.http-method-expression='POST'",
"http.request.headers-expression={'Content-Type':'application/json'}").run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
String json = "{\"hello\":\"world\"}";
Message<?> message = MessageBuilder.withPayload(json)
.build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo(json);
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(r.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
assertThat(request.getMethod()).isEqualTo("POST");
});
runner
.withPropertyValues("http.request.http-method-expression='POST'",
"http.request.headers-expression={'Content-Type':'application/json'}")
.run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
String json = "{\"hello\":\"world\"}";
Message<?> message = MessageBuilder.withPayload(json).build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo(json);
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(r.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
assertThat(request.getMethod()).isEqualTo("POST");
});
}
@Test
@@ -106,26 +103,27 @@ public class HttpRequestFunctionTestApplicationTests {
server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE,
recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.CREATED.value());
}
});
runner.withPropertyValues("http.request.http-method-expression='POST'",
"http.request.expected-response-type=" + Map.class.getName()).run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Map<String, String> json = Collections.singletonMap("hello", "world");
Message<?> message = MessageBuilder.withPayload(json)
.build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo(json);
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(r.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
assertThat(request.getMethod()).isEqualTo("POST");
});
runner
.withPropertyValues("http.request.http-method-expression='POST'",
"http.request.expected-response-type=" + Map.class.getName())
.run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Map<String, String> json = Collections.singletonMap("hello", "world");
Message<?> message = MessageBuilder.withPayload(json).build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(r.getBody()).isEqualTo(json);
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(r.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
assertThat(request.getMethod()).isEqualTo("POST");
});
}
@Test
@@ -133,24 +131,25 @@ public class HttpRequestFunctionTestApplicationTests {
server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE,
recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.ACCEPTED.value());
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.ACCEPTED.value());
}
});
runner.withPropertyValues("http.request.http-method-expression='DELETE'",
"http.request.expected-response-type=" + Void.class.getName()).run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Message<?> message = MessageBuilder.withPayload("")
.build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(r.getBody()).isNull();
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
assertThat(request.getMethod()).isEqualTo("DELETE");
});
runner
.withPropertyValues("http.request.http-method-expression='DELETE'",
"http.request.expected-response-type=" + Void.class.getName())
.run(context -> {
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
Message<?> message = MessageBuilder.withPayload("").build();
ResponseEntity r = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(r.getBody()).isNull();
assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
RecordedRequest request = server.takeRequest(100, TimeUnit.MILLISECONDS);
assertThat(request.getMethod()).isEqualTo("DELETE");
});
}
@Test
@@ -173,17 +172,15 @@ public class HttpRequestFunctionTestApplicationTests {
server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE,
recordedRequest.getHeader(HttpHeaders.ACCEPT))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.OK.value());
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, recordedRequest.getHeader(HttpHeaders.ACCEPT))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.OK.value());
}
});
runner.withPropertyValues(
"http.request.url-expression=headers['url']",
"http.request.http-method-expression=headers['method']",
"http.request.body-expression=headers['body']",
runner.withPropertyValues("http.request.url-expression=headers['url']",
"http.request.http-method-expression=headers['method']", "http.request.body-expression=headers['body']",
"http.request.headers-expression={Accept:'application/json'}")
.run(context -> {
Message<?> message = MessageBuilder.withPayload("")
@@ -196,8 +193,7 @@ public class HttpRequestFunctionTestApplicationTests {
ResponseEntity responseEntity = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isEqualTo(message.getHeaders().get("body"));
assertThat(responseEntity.getHeaders().getContentType())
.isEqualTo(MediaType.APPLICATION_JSON);
assertThat(responseEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
});
}
@@ -206,25 +202,23 @@ public class HttpRequestFunctionTestApplicationTests {
server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE,
recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.OK.value());
return new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
.setBody(recordedRequest.getBody())
.setResponseCode(HttpStatus.OK.value());
}
});
runner.withPropertyValues(
"http.request..http-method-expression='POST'",
"http.request.headers-expression={'Content-Type':'application/octet-stream'}",
"http.request.expected-response-type=byte[]")
runner
.withPropertyValues("http.request..http-method-expression='POST'",
"http.request.headers-expression={'Content-Type':'application/octet-stream'}",
"http.request.expected-response-type=byte[]")
.run(context -> {
Message<?> message = MessageBuilder.withPayload("hello")
.build();
Message<?> message = MessageBuilder.withPayload("hello").build();
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
ResponseEntity responseEntity = (ResponseEntity) httpRequestFunction.apply(message);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isEqualTo("hello".getBytes());
assertThat(responseEntity.getHeaders().getContentType())
.isEqualTo(MediaType.APPLICATION_OCTET_STREAM);
assertThat(responseEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_OCTET_STREAM);
});
}
@@ -233,17 +227,14 @@ public class HttpRequestFunctionTestApplicationTests {
server.setDispatcher(new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
return new MockResponse()
.setBody(recordedRequest.getBody())
.setHeader("method", recordedRequest.getMethod())
.setResponseCode(HttpStatus.OK.value());
return new MockResponse().setBody(recordedRequest.getBody())
.setHeader("method", recordedRequest.getMethod())
.setResponseCode(HttpStatus.OK.value());
}
});
runner.withPropertyValues(
"http.request.http-method-expression=#jsonPath(payload,'$.myMethod')")
runner.withPropertyValues("http.request.http-method-expression=#jsonPath(payload,'$.myMethod')")
.run(context -> {
Message<?> message = MessageBuilder
.withPayload("{\"name\":\"Fred\",\"age\":41, \"myMethod\":\"POST\"}")
Message<?> message = MessageBuilder.withPayload("{\"name\":\"Fred\",\"age\":41, \"myMethod\":\"POST\"}")
.build();
HttpRequestFunction httpRequestFunction = context.getBean(HttpRequestFunction.class);
ResponseEntity responseEntity = (ResponseEntity) httpRequestFunction.apply(message);
@@ -258,5 +249,7 @@ public class HttpRequestFunctionTestApplicationTests {
@SpringBootApplication
static class HttpRequestFunctionTestApplication {
}
}

View File

@@ -46,23 +46,29 @@ import org.springframework.util.StreamUtils;
public class ImageRecognition implements AutoCloseable {
private final List<String> labels;
private final GraphRunner imageNormalization;
private final GraphRunner imageRecognition;
private final GraphRunner maxProbability;
private final GraphRunner topKProbabilities;
/**
* Instead of creating the {@link ImageRecognition} service explicitly via the constructor,
* you should consider the convenience factory methods below. E.g.
*
* {@link #inception(String, int, int, boolean)}
* {@link #mobileNetV1(String, int, int, boolean)}
* {@link #mobileNetV2(String, int, int, boolean)}
* Instead of creating the {@link ImageRecognition} service explicitly via the
* constructor, you should consider the convenience factory methods below. E.g.
*
* {@link #inception(String, int, int, boolean)}
* {@link #mobileNetV1(String, int, int, boolean)}
* {@link #mobileNetV2(String, int, int, boolean)}
* @param modelUri location of the pre-trained model to use.
* @param labelsUri location of the list fromMemory pre-trained categories used by the model.
* @param imageRecognitionGraphInputName name of the Model's input node to send the input image to.
* @param imageRecognitionGraphOutputName name of the Model's output node to retrieve the predictions from.
* @param labelsUri location of the list fromMemory pre-trained categories used by the
* model.
* @param imageRecognitionGraphInputName name of the Model's input node to send the
* input image to.
* @param imageRecognitionGraphOutputName name of the Model's output node to retrieve
* the predictions from.
* @param imageHeight normalized image height.
* @param imageWidth normalized image width.
* @param mean mean value to normalize the input image.
@@ -71,62 +77,66 @@ public class ImageRecognition implements AutoCloseable {
* @param cacheModel if true the pre-trained model is cached on the local file system.
*/
public ImageRecognition(String modelUri, String labelsUri, int imageHeight, int imageWidth, float mean, float scale,
String imageRecognitionGraphInputName, String imageRecognitionGraphOutputName, int responseSize, boolean cacheModel) {
String imageRecognitionGraphInputName, String imageRecognitionGraphOutputName, int responseSize,
boolean cacheModel) {
this.labels = labels(labelsUri);
/**
* Normalizes the raw input image into format expected by the pre-trained Inception/MobileNetV1/MobileNetV2 models.
* Typically the model is trained fromMemory images scaled to certain size. Usually it is 224x224 pixels, but can be
* also 192x192, 160x160, 128128, 92x92. Use the (imageHeight, imageWidth) to set the desired size.
* The colors, represented as R, G, B in 1-byte each were converted to float using (Value - Mean)/Scale.
* Normalizes the raw input image into format expected by the pre-trained
* Inception/MobileNetV1/MobileNetV2 models. Typically the model is trained
* fromMemory images scaled to certain size. Usually it is 224x224 pixels, but can
* be also 192x192, 160x160, 128128, 92x92. Use the (imageHeight, imageWidth) to
* set the desired size. The colors, represented as R, G, B in 1-byte each were
* converted to float using (Value - Mean)/Scale.
*
* imageHeight normalized image height.
* imageWidth normalized image width.
* mean mean value to normalize the input image.
* scale scale to normalize the input image.
* imageHeight normalized image height. imageWidth normalized image width. mean
* mean value to normalize the input image. scale scale to normalize the input
* image.
*/
this.imageNormalization = new GraphRunner("raw_image", "normalized_image")
.withGraphDefinition(tf -> {
Placeholder<String> input = tf.withName("raw_image").placeholder(String.class);
final Operand<Float> decodedImage =
tf.dtypes.cast(tf.image.decodeJpeg(input, DecodeJpeg.channels(3L)), Float.class);
final Operand<Float> resizedImage = tf.image.resizeBilinear(
tf.expandDims(decodedImage, tf.constant(0)),
tf.constant(new int[] { imageHeight, imageWidth }));
tf.withName("normalized_image").math.div(tf.math.sub(resizedImage, tf.constant(mean)), tf.constant(scale));
});
this.imageNormalization = new GraphRunner("raw_image", "normalized_image").withGraphDefinition(tf -> {
Placeholder<String> input = tf.withName("raw_image").placeholder(String.class);
final Operand<Float> decodedImage = tf.dtypes.cast(tf.image.decodeJpeg(input, DecodeJpeg.channels(3L)),
Float.class);
final Operand<Float> resizedImage = tf.image.resizeBilinear(tf.expandDims(decodedImage, tf.constant(0)),
tf.constant(new int[] { imageHeight, imageWidth }));
tf.withName("normalized_image").math.div(tf.math.sub(resizedImage, tf.constant(mean)), tf.constant(scale));
});
this.imageRecognition = new GraphRunner(imageRecognitionGraphInputName, imageRecognitionGraphOutputName)
.withGraphDefinition(new ProtoBufGraphDefinition(toResource(modelUri), cacheModel));
.withGraphDefinition(new ProtoBufGraphDefinition(toResource(modelUri), cacheModel));
this.maxProbability = new GraphRunner(Arrays.asList("recognition_result"), Arrays.asList("category", "probability"))
.withGraphDefinition(tf -> {
Placeholder<Float> input = tf.withName("recognition_result").placeholder(Float.class);
tf.withName("category").math.argMax(input, tf.constant(1));
tf.withName("probability").max(input, tf.constant(1));
});
this.maxProbability = new GraphRunner(Arrays.asList("recognition_result"),
Arrays.asList("category", "probability"))
.withGraphDefinition(tf -> {
Placeholder<Float> input = tf.withName("recognition_result").placeholder(Float.class);
tf.withName("category").math.argMax(input, tf.constant(1));
tf.withName("probability").max(input, tf.constant(1));
});
this.topKProbabilities = new GraphRunner("recognition_result", "topK")
.withGraphDefinition(tf -> {
Placeholder<Float> input = tf.withName("recognition_result").placeholder(Float.class);
tf.withName("topK").nn.topK(input, tf.constant(responseSize), TopK.sorted(true));
});
this.topKProbabilities = new GraphRunner("recognition_result", "topK").withGraphDefinition(tf -> {
Placeholder<Float> input = tf.withName("recognition_result").placeholder(Float.class);
tf.withName("topK").nn.topK(input, tf.constant(responseSize), TopK.sorted(true));
});
}
/**
* Takes an byte encoded image and returns the most probable category recognized in the image along fromMemory its probability.
* Takes an byte encoded image and returns the most probable category recognized in
* the image along fromMemory its probability.
* @param inputImage Byte array encoded image to recognize.
* @return Returns a single map entry containing the names of the recognized categories as key and the confidence as value.
* @return Returns a single map entry containing the names of the recognized
* categories as key and the confidence as value.
*/
public Map<String, Double> recognizeMax(byte[] inputImage) {
try (Tensor inputTensor = Tensor.create(inputImage); GraphRunnerMemory memorize = new GraphRunnerMemory()) {
Map<String, Tensor<?>> max = this.imageNormalization.andThen(memorize)
.andThen(this.imageRecognition).andThen(memorize)
.andThen(this.maxProbability).andThen(memorize)
.apply(Collections.singletonMap("raw_image", inputTensor));
.andThen(this.imageRecognition)
.andThen(memorize)
.andThen(this.maxProbability)
.andThen(memorize)
.apply(Collections.singletonMap("raw_image", inputTensor));
long[] category = new long[1];
max.get("category").copyTo(category);
@@ -138,26 +148,28 @@ public class ImageRecognition implements AutoCloseable {
}
/**
* Takes an byte encoded input image and returns the top K most probable categories recognized in the image
* along fromMemory their probabilities.
*
* Takes an byte encoded input image and returns the top K most probable categories
* recognized in the image along fromMemory their probabilities.
* @param inputImage Byte array encoded image to recognize.
* @return Returns a list of key-value pairs. Every key-value pair represents a single category recognized.
* The key stands for the name(s) of the category while the value states the confidence that there is an
* object of this category. The entries in the Map are ordered from the higher to the lower confidences.
* @return Returns a list of key-value pairs. Every key-value pair represents a single
* category recognized. The key stands for the name(s) of the category while the value
* states the confidence that there is an object of this category. The entries in the
* Map are ordered from the higher to the lower confidences.
*/
public Map<String, Double> recognizeTopK(byte[] inputImage) {
try (Tensor inputTensor = Tensor.create(inputImage); GraphRunnerMemory memorize = new GraphRunnerMemory()) {
Map<String, Tensor<?>> topKResults =
this.imageNormalization.andThen(memorize)
.andThen(this.imageRecognition).andThen(memorize)
.andThen(this.topKProbabilities).andThen(memorize)
.apply(Collections.singletonMap("raw_image", inputTensor));
Map<String, Tensor<?>> topKResults = this.imageNormalization.andThen(memorize)
.andThen(this.imageRecognition)
.andThen(memorize)
.andThen(this.topKProbabilities)
.andThen(memorize)
.apply(Collections.singletonMap("raw_image", inputTensor));
Tensor recognizedImagesTensor = memorize.getTensorMap().get(this.imageRecognition.getSingleFetchName());
float[][] results = new float[(int) recognizedImagesTensor.shape()[0]][(int) recognizedImagesTensor.shape()[1]];
float[][] results = new float[(int) recognizedImagesTensor.shape()[0]][(int) recognizedImagesTensor
.shape()[1]];
recognizedImagesTensor.copyTo(results);
Tensor<Float> topKTensor = topKResults.get("topK").expect(Float.class);
@@ -204,12 +216,10 @@ public class ImageRecognition implements AutoCloseable {
* The Inception graph uses "input" as input and "output" as output.
*
*/
public static ImageRecognition inception(String inceptionModelUri,
int normalizedImageSize, int responseSize, boolean cacheModel) {
return new ImageRecognition(inceptionModelUri, "classpath:/labels/inception_labels.txt",
normalizedImageSize, normalizedImageSize, 117f, 1f,
"input", "output",
responseSize, cacheModel);
public static ImageRecognition inception(String inceptionModelUri, int normalizedImageSize, int responseSize,
boolean cacheModel) {
return new ImageRecognition(inceptionModelUri, "classpath:/labels/inception_labels.txt", normalizedImageSize,
normalizedImageSize, 117f, 1f, "input", "output", responseSize, cacheModel);
}
/**
@@ -218,48 +228,50 @@ public class ImageRecognition implements AutoCloseable {
*
* The normalized image size is always square (e.g. H=W)
*
* The MobileNetV2 graph uses "input" as input and "MobilenetV2/Predictions/Reshape_1" as output.
*
* The MobileNetV2 graph uses "input" as input and "MobilenetV2/Predictions/Reshape_1"
* as output.
* @param mobileNetV2ModelUri model uri
* @param normalizedImageSize Depends on the pre-trained model used. Usually 224px is used.
* @param normalizedImageSize Depends on the pre-trained model used. Usually 224px is
* used.
* @param responseSize Number of responses fot topK requests.
* @param cacheModel cache model
* @return ImageRecognition instance configured fromMemory a MobileNetV2 pre-trained model.
* @return ImageRecognition instance configured fromMemory a MobileNetV2 pre-trained
* model.
*/
public static ImageRecognition mobileNetV2(String mobileNetV2ModelUri,
int normalizedImageSize, int responseSize, boolean cacheModel) {
return new ImageRecognition(mobileNetV2ModelUri, "classpath:/labels/mobilenet_labels.txt",
normalizedImageSize, normalizedImageSize, 0f, 127f,
"input", "MobilenetV2/Predictions/Reshape_1",
responseSize, cacheModel);
public static ImageRecognition mobileNetV2(String mobileNetV2ModelUri, int normalizedImageSize, int responseSize,
boolean cacheModel) {
return new ImageRecognition(mobileNetV2ModelUri, "classpath:/labels/mobilenet_labels.txt", normalizedImageSize,
normalizedImageSize, 0f, 127f, "input", "MobilenetV2/Predictions/Reshape_1", responseSize, cacheModel);
}
/**
* Convenience for MobileNetV1 pre-trained models:
* https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md#pre-trained-models
*
* The MobileNetV1 graph uses "input" as input and "MobilenetV1/Predictions/Reshape_1" as output.
* The MobileNetV1 graph uses "input" as input and "MobilenetV1/Predictions/Reshape_1"
* as output.
*
*/
public static ImageRecognition mobileNetV1(String mobileNetV1ModelUri,
int normalizedImageSize, int responseSize, boolean cacheModel) {
return new ImageRecognition(mobileNetV1ModelUri, "classpath:/labels/mobilenet_labels.txt",
normalizedImageSize, normalizedImageSize,
0f, 127f,
"input", "MobilenetV1/Predictions/Reshape_1",
responseSize, cacheModel);
public static ImageRecognition mobileNetV1(String mobileNetV1ModelUri, int normalizedImageSize, int responseSize,
boolean cacheModel) {
return new ImageRecognition(mobileNetV1ModelUri, "classpath:/labels/mobilenet_labels.txt", normalizedImageSize,
normalizedImageSize, 0f, 127f, "input", "MobilenetV1/Predictions/Reshape_1", responseSize, cacheModel);
}
/**
* Convert image recognition results into {@link RecognitionResponse} domain list.
* @param recognitionMap map containing the category mames and its probability. Returned by the
* {@link ImageRecognition#recognizeMax(byte[])} and the ImageRecognition{@link #recognizeTopK(byte[])} methods
* @return List of {@link RecognitionResponse} objects representing the name-to-probability pairs in the input map.
* @param recognitionMap map containing the category mames and its probability.
* Returned by the {@link ImageRecognition#recognizeMax(byte[])} and the
* ImageRecognition{@link #recognizeTopK(byte[])} methods
* @return List of {@link RecognitionResponse} objects representing the
* name-to-probability pairs in the input map.
*/
public static List<RecognitionResponse> toRecognitionResponse(Map<String, Double> recognitionMap) {
return recognitionMap.entrySet().stream()
.map(nameProbabilityPair -> new RecognitionResponse(nameProbabilityPair.getKey(), nameProbabilityPair.getValue()))
.collect(Collectors.toList());
return recognitionMap.entrySet()
.stream()
.map(nameProbabilityPair -> new RecognitionResponse(nameProbabilityPair.getKey(),
nameProbabilityPair.getValue()))
.collect(Collectors.toList());
}
@Override
@@ -269,4 +281,5 @@ public class ImageRecognition implements AutoCloseable {
this.maxProbability.close();
this.topKProbabilities.close();
}
}

View File

@@ -33,7 +33,6 @@ import javax.imageio.ImageIO;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Ability to to augment the input image fromMemory the recognized labels.
*
@@ -47,6 +46,7 @@ public class ImageRecognitionAugmenter implements BiFunction<byte[], List<Recogn
public static final String IMAGE_FORMAT = "jpg";
private final Color textColor = Color.BLACK;
private final Color bgColor = new Color(167, 252, 0);
public ImageRecognitionAugmenter() {
@@ -54,7 +54,6 @@ public class ImageRecognitionAugmenter implements BiFunction<byte[], List<Recogn
/**
* Augment the input image by adding the recognized classes.
*
* @param imageBytes input image as byte array
* @param result computed recognition labels
* @return the image augmented fromMemory recognized labels.

View File

@@ -20,7 +20,9 @@ package org.springframework.cloud.fn.image.recognition;
* @author Christian Tzolov
*/
public class RecognitionResponse {
private String label;
private Double probability;
public RecognitionResponse() {
@@ -51,4 +53,5 @@ public class RecognitionResponse {
public String toString() {
return "{label='" + label + ", probability=" + probability + '}';
}
}

View File

@@ -36,22 +36,20 @@ import org.springframework.util.StreamUtils;
/**
* Create a text file mapping label id to human readable string.
*
* Produces a text file where every line represents single category. The line number represents the category id, while
* the line text is human-readable names for the categories fromMemory this imagenet id.
* Produces a text file where every line represents single category. The line number
* represents the category id, while the line text is human-readable names for the
* categories fromMemory this imagenet id.
*
* Based on https://github.com/tensorflow/models/blob/master/research/slim/datasets/imagenet.py#L66
* Based on
* https://github.com/tensorflow/models/blob/master/research/slim/datasets/imagenet.py#L66
*
* We retrieve a synset file, which contains a list of valid synset labels used
* by ILSVRC competition. There is one synset one per line, eg.
* # n01440764
* # n01443537
* We also retrieve a synset_to_human_file, which contains a mapping from synsets
* to human-readable names for every synset in Imagenet. These are stored in a
* tsv format, as follows:
* # n02119247 black fox
* # n02119359 silver fox
* We assign each synset (in alphabetical order) an integer, starting from 1
* (since 0 is reserved for the background class)
* We retrieve a synset file, which contains a list of valid synset labels used by ILSVRC
* competition. There is one synset one per line, eg. # n01440764 # n01443537 We also
* retrieve a synset_to_human_file, which contains a mapping from synsets to
* human-readable names for every synset in Imagenet. These are stored in a tsv format, as
* follows: # n02119247 black fox # n02119359 silver fox We assign each synset (in
* alphabetical order) an integer, starting from 1 (since 0 is reserved for the background
* class)
*
* @author Christian Tzolov
*/
@@ -62,8 +60,10 @@ public final class ImageNetReadableNamesWriter {
/** BASE_URL. */
public final static String BASE_URL = "https://raw.githubusercontent.com/tensorflow/models/master/research/inception/inception/data/";
/** SYNSET_URI. */
public final static String SYNSET_URI = BASE_URL + "imagenet_lsvrc_2015_synsets.txt";
/** SYNSET_TO_HUMAN_URI. */
public final static String SYNSET_TO_HUMAN_URI = BASE_URL + "imagenet_metadata.txt";
@@ -71,19 +71,24 @@ public final class ImageNetReadableNamesWriter {
Charset utf8 = Charset.forName("UTF-8");
try (InputStream synsetIs = toResource(SYNSET_URI).getInputStream();
InputStream synsetToHumanIs = toResource(SYNSET_TO_HUMAN_URI).getInputStream()) {
InputStream synsetToHumanIs = toResource(SYNSET_TO_HUMAN_URI).getInputStream()) {
List<String> synsetList = Arrays.asList(StreamUtils.copyToString(synsetIs, utf8)
.split("\n")).stream().map(l -> l.trim()).collect(Collectors.toList());
List<String> synsetList = Arrays.asList(StreamUtils.copyToString(synsetIs, utf8).split("\n"))
.stream()
.map(l -> l.trim())
.collect(Collectors.toList());
Assert.notNull(synsetList, "Failed to initialize the labels list");
Assert.isTrue(synsetList.size() == 1000, "Labels list is expected to be of " +
"size 1000 but was:" + synsetList.size());
Assert.isTrue(synsetList.size() == 1000,
"Labels list is expected to be of " + "size 1000 but was:" + synsetList.size());
Map<String, String> synsetToHuman = Arrays.asList(StreamUtils.copyToString(synsetToHumanIs, utf8)
.split("\n")).stream().map(s2h -> s2h.split("\t")).collect(Collectors.toMap(s -> s[0], s -> s[1]));
Map<String, String> synsetToHuman = Arrays
.asList(StreamUtils.copyToString(synsetToHumanIs, utf8).split("\n"))
.stream()
.map(s2h -> s2h.split("\t"))
.collect(Collectors.toMap(s -> s[0], s -> s[1]));
Assert.notNull(synsetToHuman, "Failed to initialize the synsetToHuman");
Assert.isTrue(synsetToHuman.size() == 21842, "synsetToHuman is expected to be of " +
"size 21842 but was:" + synsetToHuman.size());
Assert.isTrue(synsetToHuman.size() == 21842,
"synsetToHuman is expected to be of " + "size 21842 but was:" + synsetToHuman.size());
List<String> l = synsetList.stream().map(id -> synsetToHuman.get(id)).collect(Collectors.toList());
@@ -102,4 +107,5 @@ public final class ImageNetReadableNamesWriter {
public static Resource toResource(String uri) {
return new DefaultResourceLoader().getResource(uri);
}
}

View File

@@ -42,62 +42,51 @@ public final class ImageRecognitionExample {
// MmobileNetV2 models
// https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet#pretrained-models
String mobilenet_v2_modelUri = "https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz#mobilenet_v2_1.4_224_frozen.pb";
//String mobilenet_v2_modelUri = "https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_96.tgz#mobilenet_v2_0.35_96_frozen.pb";
try (ImageRecognition imageRecognition = ImageRecognition.mobileNetV2(
mobilenet_v2_modelUri,
224,
5,
true)) {
// String mobilenet_v2_modelUri =
// "https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_96.tgz#mobilenet_v2_0.35_96_frozen.pb";
try (ImageRecognition imageRecognition = ImageRecognition.mobileNetV2(mobilenet_v2_modelUri, 224, 5, true)) {
List<RecognitionResponse> recognizedObjects =
ImageRecognition.toRecognitionResponse(imageRecognition.recognizeTopK(inputImage));
List<RecognitionResponse> recognizedObjects = ImageRecognition
.toRecognitionResponse(imageRecognition.recognizeTopK(inputImage));
// Draw the predicted labels on top of the input image.
byte[] augmentedImage = new ImageRecognitionAugmenter().apply(inputImage, recognizedObjects);
IOUtils.write(augmentedImage, new FileOutputStream("./image-recognition/target/image-augmented-mobilnetV2.jpg"));
IOUtils.write(augmentedImage,
new FileOutputStream("./image-recognition/target/image-augmented-mobilnetV2.jpg"));
String jsonRecognizedObjects = new JsonMapperFunction().apply(recognizedObjects);
System.out.println("mobilnetV2 result:" + jsonRecognizedObjects);
}
String mobilenet_v1_modelUri = "https://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224.tgz#mobilenet_v1_1.0_224_frozen.pb";
try (ImageRecognition recognitionService = ImageRecognition.mobileNetV1(
mobilenet_v1_modelUri,
224,
5,
true)) {
try (ImageRecognition recognitionService = ImageRecognition.mobileNetV1(mobilenet_v1_modelUri, 224, 5, true)) {
List<RecognitionResponse> recognizedObjects =
ImageRecognition.toRecognitionResponse(recognitionService.recognizeTopK(inputImage));
List<RecognitionResponse> recognizedObjects = ImageRecognition
.toRecognitionResponse(recognitionService.recognizeTopK(inputImage));
// Draw the predicted labels on top of the input image.
byte[] augmentedImage = new ImageRecognitionAugmenter().apply(inputImage, recognizedObjects);
IOUtils.write(augmentedImage, new FileOutputStream("./image-recognition/target/image-augmented-mobilnetV1.jpg"));
IOUtils.write(augmentedImage,
new FileOutputStream("./image-recognition/target/image-augmented-mobilnetV1.jpg"));
String jsonRecognizedObjects = new JsonMapperFunction().apply(recognizedObjects);
System.out.println("mobilnetV1 result:" + jsonRecognizedObjects);
}
String inception_modelUri = "https://storage.googleapis.com/scdf-tensorflow-models/image-recognition/tensorflow_inception_graph.pb";
try (ImageRecognition recognitionService = ImageRecognition.inception(
inception_modelUri,
224,
5,
true)) {
try (ImageRecognition recognitionService = ImageRecognition.inception(inception_modelUri, 224, 5, true)) {
List<RecognitionResponse> recognizedObjects =
ImageRecognition.toRecognitionResponse(recognitionService.recognizeTopK(inputImage));
List<RecognitionResponse> recognizedObjects = ImageRecognition
.toRecognitionResponse(recognitionService.recognizeTopK(inputImage));
// Draw the predicted labels on top of the input image.
byte[] augmentedImage = new ImageRecognitionAugmenter().apply(inputImage, recognizedObjects);
IOUtils.write(augmentedImage, new FileOutputStream("./image-recognition/target/image-augmented-inception.jpg"));
IOUtils.write(augmentedImage,
new FileOutputStream("./image-recognition/target/image-augmented-inception.jpg"));
String jsonRecognizedObjects = new JsonMapperFunction().apply(recognizedObjects);
System.out.println("inception result:" + jsonRecognizedObjects);
}
}
}

View File

@@ -45,8 +45,11 @@ public final class ImageRecognitionExample2 {
System.out.println(inceptions.recognizeTopK(inputImage));
System.out.println(ImageRecognition.toRecognitionResponse(inceptions.recognizeTopK(inputImage)));
IOUtils.write(augmenter.apply(inputImage, ImageRecognition.toRecognitionResponse(inceptions.recognizeTopK(inputImage))),
new FileOutputStream("./functions/function/image-recognition-function/target/image-augmented-inceptions.jpg"));
IOUtils.write(
augmenter.apply(inputImage,
ImageRecognition.toRecognitionResponse(inceptions.recognizeTopK(inputImage))),
new FileOutputStream(
"./functions/function/image-recognition-function/target/image-augmented-inceptions.jpg"));
inceptions.close();
ImageRecognition mobileNetV2 = ImageRecognition.mobileNetV2(
@@ -54,8 +57,11 @@ public final class ImageRecognitionExample2 {
224, 10, true);
System.out.println(mobileNetV2.recognizeMax(inputImage));
System.out.println(mobileNetV2.recognizeTopK(inputImage));
IOUtils.write(augmenter.apply(inputImage, ImageRecognition.toRecognitionResponse(mobileNetV2.recognizeTopK(inputImage))),
new FileOutputStream("./functions/function/image-recognition-function/target/image-augmented-mobilnetV2.jpg"));
IOUtils.write(
augmenter.apply(inputImage,
ImageRecognition.toRecognitionResponse(mobileNetV2.recognizeTopK(inputImage))),
new FileOutputStream(
"./functions/function/image-recognition-function/target/image-augmented-mobilnetV2.jpg"));
mobileNetV2.close();
ImageRecognition mobileNetV1 = ImageRecognition.mobileNetV1(
@@ -63,8 +69,12 @@ public final class ImageRecognitionExample2 {
224, 10, true);
System.out.println(mobileNetV1.recognizeMax(inputImage));
System.out.println(mobileNetV1.recognizeTopK(inputImage));
IOUtils.write(augmenter.apply(inputImage, ImageRecognition.toRecognitionResponse(mobileNetV1.recognizeTopK(inputImage))),
new FileOutputStream("./functions/function/image-recognition-function/target/image-augmented-mobilnetV1.jpg"));
IOUtils.write(
augmenter.apply(inputImage,
ImageRecognition.toRecognitionResponse(mobileNetV1.recognizeTopK(inputImage))),
new FileOutputStream(
"./functions/function/image-recognition-function/target/image-augmented-mobilnetV1.jpg"));
mobileNetV1.close();
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.fn.image.recognition;
import java.util.Map;
import com.google.protobuf.InvalidProtocolBufferException;
import org.tensorflow.SavedModelBundle;
import org.tensorflow.framework.MetaGraphDef;
@@ -40,11 +39,13 @@ public final class SavedModelTest {
*
*/
public static void main(String[] args) throws InvalidProtocolBufferException {
SavedModelBundle savedModelBundle =
SavedModelBundle.load("/Users/ctzolov/Downloads/ssd_mobilenet_v1_coco_2017_11_17/saved_model", "serve");
//SavedModelBundle.load("/Users/ctzolov/Downloads/aiy_vision_classifier_plants_V1_1/", "serve");
//SavedModelBundle savedModelBundle =
// SavedModelBundle.load("/Users/ctzolov/Downloads/mnasnet-a1/saved_model", "serve");
SavedModelBundle savedModelBundle = SavedModelBundle
.load("/Users/ctzolov/Downloads/ssd_mobilenet_v1_coco_2017_11_17/saved_model", "serve");
// SavedModelBundle.load("/Users/ctzolov/Downloads/aiy_vision_classifier_plants_V1_1/",
// "serve");
// SavedModelBundle savedModelBundle =
// SavedModelBundle.load("/Users/ctzolov/Downloads/mnasnet-a1/saved_model",
// "serve");
MetaGraphDef meta = MetaGraphDef.parseFrom(savedModelBundle.metaGraphDef());
@@ -54,10 +55,11 @@ public final class SavedModelTest {
savedModelBundle.session();
//Iterator<Operation> itr = savedModelBundle.graph().operations();
// Iterator<Operation> itr = savedModelBundle.graph().operations();
//
//while (itr.hasNext()) {
// System.out.println("Operation: " + itr.next());
//}
// while (itr.hasNext()) {
// System.out.println("Operation: " + itr.next());
// }
}
}

View File

@@ -33,8 +33,8 @@ import org.springframework.cloud.fn.object.detection.domain.ObjectDetection;
import org.springframework.util.CollectionUtils;
/**
* Augment the input image fromMemory detected object bounding boxes and categories.
* For mask models and withMask set to true it draws the instance segmentation image as well.
* Augment the input image fromMemory detected object bounding boxes and categories. For
* mask models and withMask set to true it draws the instance segmentation image as well.
*
* @author Christian Tzolov
*/
@@ -48,6 +48,7 @@ public class ObjectDetectionImageAugmenter implements BiFunction<byte[], List<Ob
private String imageFormat = DEFAULT_IMAGE_FORMAT;
private final boolean withMask;
private boolean agnosticColors = false;
public ObjectDetectionImageAugmenter() {
@@ -99,8 +100,7 @@ public class ObjectDetectionImageAugmenter implements BiFunction<byte[], List<Ob
float[][] mask = od.getMask();
if (mask != null) {
Color maskColor = this.agnosticColors ? null : GraphicsUtils.getClassColor(cid);
BufferedImage maskImage = GraphicsUtils.createMaskImage(
mask, x2 - x1, y2 - y1, maskColor);
BufferedImage maskImage = GraphicsUtils.createMaskImage(mask, x2 - x1, y2 - y1, maskColor);
GraphicsUtils.overlayImages(bufferedImage, maskImage, x1, y1);
}
}
@@ -116,4 +116,5 @@ public class ObjectDetectionImageAugmenter implements BiFunction<byte[], List<Ob
// Null mend that QR image is found and not output message will be send.
return imageBytes;
}
}

View File

@@ -41,8 +41,10 @@ public class ObjectDetectionInputAdapter implements Function<byte[], Map<String,
/** Make checkstyle happy. **/
public static final String RAW_IMAGE = "raw_image";
/** Make checkstyle happy. **/
public static final String NORMALIZED_IMAGE = "normalized_image";
/** Make checkstyle happy. **/
public static final long CHANNELS = 3;
@@ -50,14 +52,14 @@ public class ObjectDetectionInputAdapter implements Function<byte[], Map<String,
public ObjectDetectionInputAdapter() {
this.imageLoaderGraph = new GraphRunner(RAW_IMAGE, NORMALIZED_IMAGE)
.withGraphDefinition(tf -> {
Placeholder<String> rawImage = tf.withName(RAW_IMAGE).placeholder(String.class);
Operand<UInt8> decodedImage = tf.dtypes.cast(
tf.image.decodeJpeg(rawImage, DecodeJpeg.channels(CHANNELS)), UInt8.class);
// Expand dimensions since the model expects images to have shape: [1, H, W, 3]
tf.withName(NORMALIZED_IMAGE).expandDims(decodedImage, tf.constant(0));
});
this.imageLoaderGraph = new GraphRunner(RAW_IMAGE, NORMALIZED_IMAGE).withGraphDefinition(tf -> {
Placeholder<String> rawImage = tf.withName(RAW_IMAGE).placeholder(String.class);
Operand<UInt8> decodedImage = tf.dtypes.cast(tf.image.decodeJpeg(rawImage, DecodeJpeg.channels(CHANNELS)),
UInt8.class);
// Expand dimensions since the model expects images to have shape: [1, H, W,
// 3]
tf.withName(NORMALIZED_IMAGE).expandDims(decodedImage, tf.constant(0));
});
}
@Override
@@ -73,4 +75,5 @@ public class ObjectDetectionInputAdapter implements Function<byte[], Map<String,
this.imageLoaderGraph.close();
}
}
}

View File

@@ -35,8 +35,8 @@ import org.tensorflow.types.UInt8;
import org.springframework.cloud.fn.common.tensorflow.deprecated.GraphicsUtils;
/**
* Converts byte array image into a input Tensor for the Object Detection API. The computed image tensors uses the
* 'image_tensor' model placeholder.
* Converts byte array image into a input Tensor for the Object Detection API. The
* computed image tensors uses the 'image_tensor' model placeholder.
*
* @author Christian Tzolov
*/
@@ -95,4 +95,5 @@ public class ObjectDetectionInputConverter implements Function<byte[][], Map<Str
data[i + 2] = tmp;
}
}
}

View File

@@ -37,21 +37,22 @@ import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* Converts the Tensorflow Object Detection result into {@link ObjectDetection} list.
* The pre-trained Object Detection models (http://bit.ly/2osxMAY) produce 3 tensor outputs:
* (1) detection_classes - containing the ids of detected objects, (2) detection_scores - confidence probabilities of the
* detected object and (3) detection_boxes - the object bounding boxes withing the images.
* Converts the Tensorflow Object Detection result into {@link ObjectDetection} list. The
* pre-trained Object Detection models (http://bit.ly/2osxMAY) produce 3 tensor outputs:
* (1) detection_classes - containing the ids of detected objects, (2) detection_scores -
* confidence probabilities of the detected object and (3) detection_boxes - the object
* bounding boxes withing the images.
*
* The MASK based models provide to 2 additional tensors: (4) num_detections and (5) detection_masks.
* The MASK based models provide to 2 additional tensors: (4) num_detections and (5)
* detection_masks.
*
* All outputs tensors are float arrays, having:
* - 1 as the first dimension
* - maxObjects as the second dimension
* While boxesT will have 4 as the third dimension (2 sets of (x, y) coordinates).
* This can be verified by looking at scoresT.shape() etc.
* All outputs tensors are float arrays, having: - 1 as the first dimension - maxObjects
* as the second dimension While boxesT will have 4 as the third dimension (2 sets of (x,
* y) coordinates). This can be verified by looking at scoresT.shape() etc.
*
* The format detected classes (e.g. labels) names is defined by the 'string_int_labels_map.proto'. The input list
* is available at: https://github.com/tensorflow/models/tree/master/research/object_detection/data
* The format detected classes (e.g. labels) names is defined by the
* 'string_int_labels_map.proto'. The input list is available at:
* https://github.com/tensorflow/models/tree/master/research/object_detection/data
*
* @author Christian Tzolov
*/
@@ -61,17 +62,23 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
/** DETECTION_CLASSES. */
public static final String DETECTION_CLASSES = "detection_classes";
/** DETECTION_SCORES. */
public static final String DETECTION_SCORES = "detection_scores";
/** DETECTION_BOXES. */
public static final String DETECTION_BOXES = "detection_boxes";
/** DETECTION_MASKS. */
public static final String DETECTION_MASKS = "detection_masks";
/** NUM_DETECTIONS. */
public static final String NUM_DETECTIONS = "num_detections";
private final String[] labels;
private float confidence;
private List<String> modelFetch;
public ObjectDetectionOutputConverter(Resource labelsResource, float confidence, List<String> modelFetch) {
@@ -96,15 +103,16 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
private static String[] loadLabels(Resource labelsResource) throws Exception {
try (InputStream is = labelsResource.getInputStream()) {
String text = StreamUtils.copyToString(is, Charset.forName("UTF-8"));
StringIntLabelMapOuterClass.StringIntLabelMap.Builder builder =
StringIntLabelMapOuterClass.StringIntLabelMap.newBuilder();
StringIntLabelMapOuterClass.StringIntLabelMap.Builder builder = StringIntLabelMapOuterClass.StringIntLabelMap
.newBuilder();
TextFormat.merge(text, builder);
StringIntLabelMapOuterClass.StringIntLabelMap proto = builder.build();
int maxLabelId = proto.getItemList().stream()
.map(StringIntLabelMapOuterClass.StringIntLabelMapItem::getId)
.max(Comparator.comparing(i -> i))
.orElse(-1);
int maxLabelId = proto.getItemList()
.stream()
.map(StringIntLabelMapOuterClass.StringIntLabelMapItem::getId)
.max(Comparator.comparing(i -> i))
.orElse(-1);
String[] labelIdToNameMap = new String[maxLabelId + 1];
for (StringIntLabelMapOuterClass.StringIntLabelMapItem item : proto.getItemList()) {
@@ -112,7 +120,8 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
labelIdToNameMap[item.getId()] = item.getDisplayName();
}
else {
// Common practice is to set the name to a MID or Synsets Id. Synset is a set of synonyms that
// Common practice is to set the name to a MID or Synsets Id. Synset
// is a set of synonyms that
// share a common meaning: https://en.wikipedia.org/wiki/WordNet
labelIdToNameMap[item.getId()] = item.getName();
}
@@ -125,13 +134,13 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
public List<List<ObjectDetection>> apply(Map<String, Tensor<?>> tensorMap) {
try (Tensor<Float> scoresTensor = tensorMap.get(DETECTION_SCORES).expect(Float.class);
Tensor<Float> classesTensor = tensorMap.get(DETECTION_CLASSES).expect(Float.class);
Tensor<Float> boxesTensor = tensorMap.get(DETECTION_BOXES).expect(Float.class)
) {
Tensor<Float> classesTensor = tensorMap.get(DETECTION_CLASSES).expect(Float.class);
Tensor<Float> boxesTensor = tensorMap.get(DETECTION_BOXES).expect(Float.class)) {
// All these tensors have:
// - 1 as the first dimension
// - maxObjects as the second dimension
// While boxesT will have 4 as the third dimension (2 sets of (x, y) coordinates).
// While boxesT will have 4 as the third dimension (2 sets of (x, y)
// coordinates).
// This can be verified by looking at scoresT.shape() etc.
int batchSize = (int) scoresTensor.shape()[0];
int maxObjects = (int) scoresTensor.shape()[1];
@@ -143,10 +152,10 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
for (int batchIndex = 0; batchIndex < batchSize; batchIndex++) {
List<ObjectDetection> objectDetections = new ArrayList<>();
// Collect only the objects whose scores are at above the configured confidence threshold.
// Collect only the objects whose scores are at above the configured
// confidence threshold.
for (int i = 0; i < scores[batchIndex].length; ++i) {
if (scores[batchIndex][i] >= confidence) {
String category = labels[(int) classes[batchIndex][i]];
@@ -169,7 +178,8 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
if (masksTensor != null) {
long[] shape = masksTensor.shape();
float[][][][] masks = masksTensor.copyTo(new float[(int) shape[0]][(int) shape[1]][(int) shape[2]][(int) shape[3]]);
float[][][][] masks = masksTensor
.copyTo(new float[(int) shape[0]][(int) shape[1]][(int) shape[2]][(int) shape[3]]);
od.setMask(masks[batchIndex][i]);
logger.debug(String.format("Num detections: %s, Masks: %s", nd, masks));
}
@@ -184,4 +194,5 @@ public class ObjectDetectionOutputConverter implements Function<Map<String, Tens
return batchObjectDetections;
}
}
}

View File

@@ -29,66 +29,79 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.StreamUtils;
/**
* Convenience class that leverages the the {@link ObjectDetectionInputConverter}, {@link ObjectDetectionOutputConverter} and {@link TensorFlowService}
* in combination fromMemory the Tensorflow Object Detection API (https://github.com/tensorflow/models/tree/master/research/object_detection)
* models for detection objects in input images.
* Convenience class that leverages the the {@link ObjectDetectionInputConverter},
* {@link ObjectDetectionOutputConverter} and {@link TensorFlowService} in combination
* fromMemory the Tensorflow Object Detection API
* (https://github.com/tensorflow/models/tree/master/research/object_detection) models for
* detection objects in input images.
*
* All pre-trained models (https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) and labels are supported.
* All pre-trained models
* (https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)
* and labels are supported.
*
* You can download pre-trained models directly from the zoo: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
* Just use the URI notation: (zoo model tar.gz url)#(name of the frozen model file name). To speedup the bootstrap
* performance you should consider downloading the models locally and use the file:/"path to my model" URI instead!
* You can download pre-trained models directly from the zoo:
* https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
* Just use the URI notation: (zoo model tar.gz url)#(name of the frozen model file name).
* To speedup the bootstrap performance you should consider downloading the models locally
* and use the file:/"path to my model" URI instead!
*
* The object category labels for the pre-trained models are available at: https://github.com/tensorflow/models/tree/master/research/object_detection/data
* Use the labels applicable for the model. Also, for performance reasons you may consider to download the labels
* and load them from file: instead.
* The object category labels for the pre-trained models are available at:
* https://github.com/tensorflow/models/tree/master/research/object_detection/data Use the
* labels applicable for the model. Also, for performance reasons you may consider to
* download the labels and load them from file: instead.
*
* @author Christian Tzolov
*/
public class ObjectDetectionService {
/** Default list of fetch names for Box models. */
public static List<String> FETCH_NAMES = Arrays.asList(
ObjectDetectionOutputConverter.DETECTION_SCORES, ObjectDetectionOutputConverter.DETECTION_CLASSES,
ObjectDetectionOutputConverter.DETECTION_BOXES, ObjectDetectionOutputConverter.NUM_DETECTIONS);
/** Default list of fetch names for mask supporting models. */
public static List<String> FETCH_NAMES_WITH_MASKS = Arrays.asList(
ObjectDetectionOutputConverter.DETECTION_SCORES, ObjectDetectionOutputConverter.DETECTION_CLASSES,
ObjectDetectionOutputConverter.DETECTION_BOXES, ObjectDetectionOutputConverter.DETECTION_MASKS,
public static List<String> FETCH_NAMES = Arrays.asList(ObjectDetectionOutputConverter.DETECTION_SCORES,
ObjectDetectionOutputConverter.DETECTION_CLASSES, ObjectDetectionOutputConverter.DETECTION_BOXES,
ObjectDetectionOutputConverter.NUM_DETECTIONS);
/** Default list of fetch names for mask supporting models. */
public static List<String> FETCH_NAMES_WITH_MASKS = Arrays.asList(ObjectDetectionOutputConverter.DETECTION_SCORES,
ObjectDetectionOutputConverter.DETECTION_CLASSES, ObjectDetectionOutputConverter.DETECTION_BOXES,
ObjectDetectionOutputConverter.DETECTION_MASKS, ObjectDetectionOutputConverter.NUM_DETECTIONS);
private final ObjectDetectionInputConverter inputConverter;
private final ObjectDetectionOutputConverter outputConverter;
private final TensorFlowService tensorFlowService;
public ObjectDetectionService() {
this("https://download.tensorflow.org/models/object_detection/ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz#frozen_inference_graph.pb",
"https://storage.googleapis.com/scdf-tensorflow-models/object-detection/mscoco_label_map.pbtxt",
0.4f, false, true);
"https://storage.googleapis.com/scdf-tensorflow-models/object-detection/mscoco_label_map.pbtxt", 0.4f,
false, true);
}
/**
* Convenience constructor that would initialize all necessary internal components.
* @param modelUri URI of the pre-trained, frozen Tensorflow model.
* @param labelsUri URI of the pre-trained category labels.
* @param confidence Confidence threshold. Only objects detected wth confidence above this threshold will be returned.
* @param withMasks If a Mask model is selected then you can use this flag to extract the instance segmentation masks as well.
* @param confidence Confidence threshold. Only objects detected wth confidence above
* this threshold will be returned.
* @param withMasks If a Mask model is selected then you can use this flag to extract
* the instance segmentation masks as well.
*/
public ObjectDetectionService(String modelUri, String labelsUri,
float confidence, boolean withMasks, boolean cacheModel) {
public ObjectDetectionService(String modelUri, String labelsUri, float confidence, boolean withMasks,
boolean cacheModel) {
this.inputConverter = new ObjectDetectionInputConverter();
List<String> fetchNames = withMasks ? FETCH_NAMES_WITH_MASKS : FETCH_NAMES;
this.outputConverter = new ObjectDetectionOutputConverter(
new DefaultResourceLoader().getResource(labelsUri), confidence, fetchNames);
this.tensorFlowService = new TensorFlowService(
new DefaultResourceLoader().getResource(modelUri), fetchNames, cacheModel);
this.outputConverter = new ObjectDetectionOutputConverter(new DefaultResourceLoader().getResource(labelsUri),
confidence, fetchNames);
this.tensorFlowService = new TensorFlowService(new DefaultResourceLoader().getResource(modelUri), fetchNames,
cacheModel);
}
/**
* Generic constructor thea allow the converter to be pre-configured before passed to the service.
* @param inputConverter Converter from byte array to object detection input image tensor
* @param outputConverter Covets the object detection output tensors into {@link ObjectDetection } list
* Generic constructor thea allow the converter to be pre-configured before passed to
* the service.
* @param inputConverter Converter from byte array to object detection input image
* tensor
* @param outputConverter Covets the object detection output tensors into
* {@link ObjectDetection } list
* @param tensorFlowService Java tensorflow runner instance
*/
public ObjectDetectionService(ObjectDetectionInputConverter inputConverter,
@@ -100,9 +113,9 @@ public class ObjectDetectionService {
/**
* Detects objects in a single input image identified by its URI.
*
* @param imageUri input image's URI
* @return Returns a list of {@link ObjectDetection} domain objects representing detected objects
* @return Returns a list of {@link ObjectDetection} domain objects representing
* detected objects
*/
public List<ObjectDetection> detect(String imageUri) {
try (InputStream is = new DefaultResourceLoader().getResource(imageUri).getInputStream()) {
@@ -116,10 +129,11 @@ public class ObjectDetectionService {
/**
* Detects objects in a single {@link BufferedImage}.
*
* @param image Input image to detect objects from.
* @param format Image format (e.g. jpg, png ...) to use when converting the buffer into byte array.
* @return Returns a list of {@link ObjectDetection} domain objects representing detected objects in the input image
* @param format Image format (e.g. jpg, png ...) to use when converting the buffer
* into byte array.
* @return Returns a list of {@link ObjectDetection} domain objects representing
* detected objects in the input image
*/
public List<ObjectDetection> detect(BufferedImage image, String format) {
return this.detect(GraphicsUtils.toImageByteArray(image, format));
@@ -127,21 +141,27 @@ public class ObjectDetectionService {
/**
* Detects objects from a single input image encoded as byte array.
*
* @param image Input image encoded as byte array
* @return Returns a list of {@link ObjectDetection} domain objects representing detected objects in the input image
* @return Returns a list of {@link ObjectDetection} domain objects representing
* detected objects in the input image
*/
public List<ObjectDetection> detect(byte[] image) {
return this.inputConverter.andThen(this.tensorFlowService).andThen(this.outputConverter).apply(new byte[][] { image }).get(0);
return this.inputConverter.andThen(this.tensorFlowService)
.andThen(this.outputConverter)
.apply(new byte[][] { image })
.get(0);
}
/**
* Uses detects objects from a batch of input images encoded as byte array.
*
* @param batchedImages Batch of input images encoded as byte arrays. First dimension is the batch size and second the image bytes.
* @return Returns list of lists. For every input image in the batch a list of {@link ObjectDetection} domain objects representing detected objects in the input image.
* @param batchedImages Batch of input images encoded as byte arrays. First dimension
* is the batch size and second the image bytes.
* @return Returns list of lists. For every input image in the batch a list of
* {@link ObjectDetection} domain objects representing detected objects in the input
* image.
*/
public List<List<ObjectDetection>> detect(byte[][] batchedImages) {
return this.inputConverter.andThen(this.tensorFlowService).andThen(this.outputConverter).apply(batchedImages);
}
}

View File

@@ -40,35 +40,34 @@ import org.springframework.core.io.DefaultResourceLoader;
public class ObjectDetectionService2 implements AutoCloseable {
/** Default Box models fetch names. */
public static List<String> FETCH_NAMES = Arrays.asList(
ObjectDetectionOutputConverter.DETECTION_SCORES, ObjectDetectionOutputConverter.DETECTION_CLASSES,
ObjectDetectionOutputConverter.DETECTION_BOXES, ObjectDetectionOutputConverter.NUM_DETECTIONS);
/** Default Models models fetch names. */
public static List<String> FETCH_NAMES_WITH_MASKS = Arrays.asList(
ObjectDetectionOutputConverter.DETECTION_SCORES, ObjectDetectionOutputConverter.DETECTION_CLASSES,
ObjectDetectionOutputConverter.DETECTION_BOXES, ObjectDetectionOutputConverter.DETECTION_MASKS,
public static List<String> FETCH_NAMES = Arrays.asList(ObjectDetectionOutputConverter.DETECTION_SCORES,
ObjectDetectionOutputConverter.DETECTION_CLASSES, ObjectDetectionOutputConverter.DETECTION_BOXES,
ObjectDetectionOutputConverter.NUM_DETECTIONS);
private final GraphRunner imageNormalization;
private final GraphRunner objectDetection;
private final ObjectDetectionOutputConverter outputConverter;
/** Default Models models fetch names. */
public static List<String> FETCH_NAMES_WITH_MASKS = Arrays.asList(ObjectDetectionOutputConverter.DETECTION_SCORES,
ObjectDetectionOutputConverter.DETECTION_CLASSES, ObjectDetectionOutputConverter.DETECTION_BOXES,
ObjectDetectionOutputConverter.DETECTION_MASKS, ObjectDetectionOutputConverter.NUM_DETECTIONS);
private final GraphRunner imageNormalization;
private final GraphRunner objectDetection;
private final ObjectDetectionOutputConverter outputConverter;
public ObjectDetectionService2(String modelUri, ObjectDetectionOutputConverter outputConverter) {
this.imageNormalization = new GraphRunner("raw_image", "normalized_image")
.withGraphDefinition(tf -> {
Placeholder<String> rawImage = tf.withName("raw_image").placeholder(String.class);
Operand<UInt8> decodedImage = tf.dtypes.cast(
tf.image.decodeJpeg(rawImage, DecodeJpeg.channels(3L)), UInt8.class);
// Expand dimensions since the model expects images to have shape: [1, H, W, 3]
tf.withName("normalized_image").expandDims(decodedImage, tf.constant(0));
});
this.imageNormalization = new GraphRunner("raw_image", "normalized_image").withGraphDefinition(tf -> {
Placeholder<String> rawImage = tf.withName("raw_image").placeholder(String.class);
Operand<UInt8> decodedImage = tf.dtypes.cast(tf.image.decodeJpeg(rawImage, DecodeJpeg.channels(3L)),
UInt8.class);
// Expand dimensions since the model expects images to have shape: [1, H, W,
// 3]
tf.withName("normalized_image").expandDims(decodedImage, tf.constant(0));
});
this.objectDetection = new GraphRunner(Arrays.asList("image_tensor"), FETCH_NAMES)
.withGraphDefinition(new ProtoBufGraphDefinition(
new DefaultResourceLoader().getResource(modelUri), true));
.withGraphDefinition(new ProtoBufGraphDefinition(new DefaultResourceLoader().getResource(modelUri), true));
this.outputConverter = outputConverter;
}
@@ -77,9 +76,10 @@ public class ObjectDetectionService2 implements AutoCloseable {
try (Tensor inputTensor = Tensor.create(image); GraphRunnerMemory memorize = new GraphRunnerMemory()) {
List<List<ObjectDetection>> out = this.imageNormalization.andThen(memorize)
.andThen(this.objectDetection).andThen(memorize)
.andThen(outputConverter)
.apply(Collections.singletonMap("raw_image", inputTensor));
.andThen(this.objectDetection)
.andThen(memorize)
.andThen(outputConverter)
.apply(Collections.singletonMap("raw_image", inputTensor));
return out.get(0);
@@ -90,7 +90,7 @@ public class ObjectDetectionService2 implements AutoCloseable {
public void close() {
this.imageNormalization.close();
this.objectDetection.close();
//this.outputConverter.close();
// this.outputConverter.close();
}
public static void main(String[] args) throws IOException {
@@ -100,7 +100,8 @@ public class ObjectDetectionService2 implements AutoCloseable {
ObjectDetectionOutputConverter outputAdapter = new ObjectDetectionOutputConverter(
new DefaultResourceLoader().getResource(labelUri), 0.4f, FETCH_NAMES);
//byte[] inputImage = GraphicsUtils.loadAsByteArray("classpath:/images/object-detection.jpg");
// byte[] inputImage =
// GraphicsUtils.loadAsByteArray("classpath:/images/object-detection.jpg");
byte[] inputImage = GraphicsUtils.loadAsByteArray("classpath:/images/wild-animals-15.jpg");
try (ObjectDetectionService2 objectDetectionService2 = new ObjectDetectionService2(modelUri, outputAdapter)) {
@@ -110,4 +111,5 @@ public class ObjectDetectionService2 implements AutoCloseable {
System.out.println(boza);
}
}
}

View File

@@ -28,12 +28,19 @@ import com.fasterxml.jackson.annotation.JsonInclude;
public class ObjectDetection {
private String name;
private float confidence;
private float x1;
private float y1;
private float x2;
private float y2;
private float[][] mask;
private int cid;
public String getName() {
@@ -102,15 +109,8 @@ public class ObjectDetection {
@Override
public String toString() {
return "ObjectDetection{" +
"name='" + name + '\'' +
", confidence=" + confidence +
", x1=" + x1 +
", y1=" + y1 +
", x2=" + x2 +
", y2=" + y2 +
", mask=" + Arrays.toString(mask) +
", cid=" + cid +
'}';
return "ObjectDetection{" + "name='" + name + '\'' + ", confidence=" + confidence + ", x1=" + x1 + ", y1=" + y1
+ ", x2=" + x2 + ", y2=" + y2 + ", mask=" + Arrays.toString(mask) + ", cid=" + cid + '}';
}
}

View File

@@ -32,15 +32,15 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
/**
* 4 of the pre-trained model in the model zoo (https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)
* 4 of the pre-trained model in the model zoo
* (https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)
* can also compute the masks of the detected objects, providing instance segmentation.
* <p>
* Here are the models that can be used for instance segmentation.
* <p>
* mask_rcnn_inception_resnet_v2_atrous_coco 771 36 Masks
* mask_rcnn_inception_v2_coco 79 25 Masks
* mask_rcnn_resnet101_atrous_coco 470 33 Masks
* mask_rcnn_resnet50_atrous_coco 343 29 Masks
* mask_rcnn_inception_resnet_v2_atrous_coco 771 36 Masks mask_rcnn_inception_v2_coco 79
* 25 Masks mask_rcnn_resnet101_atrous_coco 470 33 Masks mask_rcnn_resnet50_atrous_coco
* 343 29 Masks
*
* @author Christian Tzolov
*/
@@ -50,44 +50,53 @@ public class ExampleInstanceSegmentation {
ResourceLoader resourceLoader = new DefaultResourceLoader();
// You can download pre-trained models directly from the zoo: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
// Just use the notation <zoo model tar.gz url>#<name of the frozen model file name>
// For performance reasons you may consider downloading the model locally and use the file:/<path to my model> URI instead!
// You can download pre-trained models directly from the zoo:
// https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
// Just use the notation <zoo model tar.gz url>#<name of the frozen model file
// name>
// For performance reasons you may consider downloading the model locally and use
// the file:/<path to my model> URI instead!
String model = "https://download.tensorflow.org/models/object_detection/mask_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz#frozen_inference_graph.pb";
// All labels for the pre-trained models are available at:
// https://github.com/tensorflow/models/tree/master/research/object_detection/data
// Use the labels applicable for the model.
// Also, for performance reasons you may consider to download the labels and load them from file: instead.
// Also, for performance reasons you may consider to download the labels and load
// them from file: instead.
String labels = "https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_label_map.pbtxt";
// You can cache the TF model on the local file system to improve the bootstrap performance on consecutive runs!
// You can cache the TF model on the local file system to improve the bootstrap
// performance on consecutive runs!
boolean CACHE_TF_MODEL = true;
// For the pre-trained models fromMemory mask you can set the INSTANCE_SEGMENTATION to enable object instance segmentation as well
// For the pre-trained models fromMemory mask you can set the
// INSTANCE_SEGMENTATION to enable object instance segmentation as well
boolean INSTANCE_SEGMENTATION = true;
// Only object fromMemory confidence above the threshold are returned
float CONFIDENCE_THRESHOLD = 0.4f;
ObjectDetectionService detectionService =
new ObjectDetectionService(model, labels, CONFIDENCE_THRESHOLD, INSTANCE_SEGMENTATION, CACHE_TF_MODEL);
ObjectDetectionService detectionService = new ObjectDetectionService(model, labels, CONFIDENCE_THRESHOLD,
INSTANCE_SEGMENTATION, CACHE_TF_MODEL);
// You can use file:, http: or classpath: to provide the path to the input image.
byte[] image = GraphicsUtils.loadAsByteArray("classpath:/images/object-detection.jpg");
// Returns a list ObjectDetection domain classes to allow programmatic accesses to the detected objects's metadata
// Returns a list ObjectDetection domain classes to allow programmatic accesses to
// the detected objects's metadata
List<ObjectDetection> detectedObjects = detectionService.detect(image);
// Get JSON representation of the detected objects
String jsonObjectDetections = new JsonMapperFunction().apply(detectedObjects);
System.out.println(jsonObjectDetections);
// Draw the detected object metadata on top of the original image and store the result
// Draw the detected object metadata on top of the original image and store the
// result
byte[] annotatedImage = new ObjectDetectionImageAugmenter(INSTANCE_SEGMENTATION).apply(image, detectedObjects);
File projectDir = new File("functions/function/object-detection-function");
File output = new File(projectDir, "target/object-detection-segmentation-augmented.jpg");
IOUtils.write(annotatedImage, new FileOutputStream(output));
System.out.println("Created:" + output.getPath());
}
}

View File

@@ -37,31 +37,40 @@ public class ExampleObjectDetection {
public static void main(String[] args) throws IOException {
// You can download pre-trained models directly from the zoo: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
// Just use the notation <zoo model tar.gz url>#<name of the frozen model file name>
// For performance reasons you may consider downloading the model locally and use the file:/<path to my model> URI instead!
// You can download pre-trained models directly from the zoo:
// https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
// Just use the notation <zoo model tar.gz url>#<name of the frozen model file
// name>
// For performance reasons you may consider downloading the model locally and use
// the file:/<path to my model> URI instead!
String model = "https://download.tensorflow.org/models/object_detection/faster_rcnn_nas_coco_2018_01_28.tar.gz#frozen_inference_graph.pb";
//Resource model = resourceLoader.getResource("https://download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_fgvc_2018_07_19.tar.gz#frozen_inference_graph.pb");
//Resource model = resourceLoader.getResource("https://download.tensorflow.org/models/object_detection/faster_rcnn_resnet50_fgvc_2018_07_19.tar.gz#frozen_inference_graph.pb");
// Resource model =
// resourceLoader.getResource("https://download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_fgvc_2018_07_19.tar.gz#frozen_inference_graph.pb");
// Resource model =
// resourceLoader.getResource("https://download.tensorflow.org/models/object_detection/faster_rcnn_resnet50_fgvc_2018_07_19.tar.gz#frozen_inference_graph.pb");
// All labels for the pre-trained models are available at:
// https://github.com/tensorflow/models/tree/master/research/object_detection/data
// Use the labels applicable for the model.
// Also, for performance reasons you may consider to download the labels and load them from file: instead.
// Also, for performance reasons you may consider to download the labels and load
// them from file: instead.
String labels = "https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_label_map.pbtxt";
//Resource labels = resourceLoader.getResource("https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/fgvc_2854_classes_label_map.pbtxt");
// Resource labels =
// resourceLoader.getResource("https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/fgvc_2854_classes_label_map.pbtxt");
// You can cache the TF model on the local file system to improve the bootstrap performance on consecutive runs!
// You can cache the TF model on the local file system to improve the bootstrap
// performance on consecutive runs!
boolean CACHE_TF_MODEL = true;
// For the pre-trained models fromMemory mask you can set the INSTANCE_SEGMENTATION to enable object instance segmentation as well
// For the pre-trained models fromMemory mask you can set the
// INSTANCE_SEGMENTATION to enable object instance segmentation as well
boolean NO_INSTANCE_SEGMENTATION = false;
// Only object fromMemory confidence above the threshold are returned
float CONFIDENCE_THRESHOLD = 0.4f;
ObjectDetectionService detectionService =
new ObjectDetectionService(model, labels, CONFIDENCE_THRESHOLD, NO_INSTANCE_SEGMENTATION, CACHE_TF_MODEL);
ObjectDetectionService detectionService = new ObjectDetectionService(model, labels, CONFIDENCE_THRESHOLD,
NO_INSTANCE_SEGMENTATION, CACHE_TF_MODEL);
// You can use file:, http: or classpath: to provide the path to the input image.
String inputImageUri = "classpath:/images/object-detection.jpg";
@@ -69,16 +78,21 @@ public class ExampleObjectDetection {
byte[] image = StreamUtils.copyToByteArray(is);
// Returns a list ObjectDetection domain classes to allow programmatic accesses to the detected objects's metadata
// Returns a list ObjectDetection domain classes to allow programmatic
// accesses to the detected objects's metadata
List<ObjectDetection> detectedObjects = detectionService.detect(image);
// Get JSON representation of the detected objects
String jsonObjectDetections = new JsonMapperFunction().apply(detectedObjects);
System.out.println(jsonObjectDetections);
// Draw the detected object metadata on top of the original image and store the result
byte[] annotatedImage = new ObjectDetectionImageAugmenter(NO_INSTANCE_SEGMENTATION).apply(image, detectedObjects);
IOUtils.write(annotatedImage, new FileOutputStream("./object-detection-function/target/object-detection-augmented.jpg"));
// Draw the detected object metadata on top of the original image and store
// the result
byte[] annotatedImage = new ObjectDetectionImageAugmenter(NO_INSTANCE_SEGMENTATION).apply(image,
detectedObjects);
IOUtils.write(annotatedImage,
new FileOutputStream("./object-detection-function/target/object-detection-augmented.jpg"));
}
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.cloud.fn.object.detection.examples;
import java.util.List;
import org.springframework.cloud.fn.object.detection.ObjectDetectionService;
@@ -28,15 +27,30 @@ import org.springframework.cloud.fn.object.detection.domain.ObjectDetection;
public class SimpleExample {
public static void main(String[] args) {
// Select a pre-trained model from the model zoo: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
// Just use the notation <model zoo url>#<name of the frozen model file in the zoo's tar.gz>
// Select a pre-trained model from the model zoo:
// https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
// Just use the notation <model zoo url>#<name of the frozen model file in the
// zoo's tar.gz>
String model = "https://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_ppn_shared_box_predictor_300x300_coco14_sync_2018_07_03.tar.gz#frozen_inference_graph.pb";
// All labels for the pre-trained models are available at: https://github.com/tensorflow/models/tree/master/research/object_detection/data
// All labels for the pre-trained models are available at:
// https://github.com/tensorflow/models/tree/master/research/object_detection/data
String labels = "https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_label_map.pbtxt";
ObjectDetectionService detectionService = new ObjectDetectionService(model, labels,
0.4f, // Only object fromMemory confidence above the threshold are returned. Confidence range is [0, 1].
ObjectDetectionService detectionService = new ObjectDetectionService(model, labels, 0.4f, // Only
// object
// fromMemory
// confidence
// above
// the
// threshold
// are
// returned.
// Confidence
// range
// is
// [0,
// 1].
false, // No instance segmentation
true); // cache the TF model locally
@@ -44,4 +58,5 @@ public class SimpleExample {
List<ObjectDetection> detectedObjects = detectionService.detect("classpath:/images/object-detection.jpg");
detectedObjects.stream().map(o -> o.toString()).forEach(System.out::println);
}
}

View File

@@ -24,7 +24,6 @@ import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.MimeTypeUtils;
/**
*
* @author Christian Tzolov
*/
public class ByteArrayTextToString implements Function<Message<?>, Message<?>> {
@@ -35,17 +34,17 @@ public class ByteArrayTextToString implements Function<Message<?>, Message<?>> {
if (message.getPayload() instanceof byte[]) {
final MessageHeaders headers = message.getHeaders();
String contentType = headers.containsKey(MessageHeaders.CONTENT_TYPE)
? headers.get(MessageHeaders.CONTENT_TYPE).toString()
: MimeTypeUtils.APPLICATION_JSON_VALUE;
? headers.get(MessageHeaders.CONTENT_TYPE).toString() : MimeTypeUtils.APPLICATION_JSON_VALUE;
if (contentType.contains("text") || contentType.contains("json") || contentType.contains("x-spring-tuple")) {
if (contentType.contains("text") || contentType.contains("json")
|| contentType.contains("x-spring-tuple")) {
message = MessageBuilder.withPayload(new String(((byte[]) message.getPayload())))
.copyHeaders(message.getHeaders())
.build();
.copyHeaders(message.getHeaders())
.build();
}
}
return message;
}
}
}

View File

@@ -29,12 +29,11 @@ import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.MimeTypeUtils;
/**
* The {@link Function} to deserialize {@code byte[]} payload into a Map
* if {@link MessageHeaders#CONTENT_TYPE} header is JSON.
* Otherwise, the message is returned as is.
* The {@link Function} to deserialize {@code byte[]} payload into a Map if
* {@link MessageHeaders#CONTENT_TYPE} header is JSON. Otherwise, the message is returned
* as is.
*
* @author Artem Bilan
*
* @since 4.0
*/
public class JsonBytesToMap implements Function<Message<?>, Message<?>> {
@@ -51,15 +50,13 @@ public class JsonBytesToMap implements Function<Message<?>, Message<?>> {
public Message<?> apply(Message<?> message) {
if (message.getPayload() instanceof byte[] payload) {
MessageHeaders headers = message.getHeaders();
String contentType =
headers.containsKey(MessageHeaders.CONTENT_TYPE)
? headers.get(MessageHeaders.CONTENT_TYPE).toString()
: MimeTypeUtils.APPLICATION_JSON_VALUE;
String contentType = headers.containsKey(MessageHeaders.CONTENT_TYPE)
? headers.get(MessageHeaders.CONTENT_TYPE).toString() : MimeTypeUtils.APPLICATION_JSON_VALUE;
if (contentType.contains("json")) {
message = MessageBuilder.withPayload(payloadToMapIfCan(payload))
.copyHeaders(message.getHeaders())
.build();
.copyHeaders(message.getHeaders())
.build();
}
}

View File

@@ -32,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ByteArrayTextToStringTests {
private static final String MESSAGE = "hello world";
private static Function<Message<?>, Message<?>> converter;
@BeforeAll

View File

@@ -43,11 +43,10 @@ public final class NativeImageUtils {
* https://github.com/tensorflow/tensorflow/blob/r1.13/tensorflow/python/ops/image_ops_impl.py#L1536
*/
public static <T> Operand<T> grayscaleToRgb(Ops tf, Operand<T> images) {
ExpandDims<Integer> rank_1 = tf.expandDims(
tf.math.sub(tf.rank(images), tf.constant(1)),
tf.constant(0));
ExpandDims<Integer> rank_1 = tf.expandDims(tf.math.sub(tf.rank(images), tf.constant(1)), tf.constant(0));
// Create once 1D vector of the shape defined by the rank_1.
// E.g. for rank [2] will produce matrix [1, 1]. For [3] rank will produce a cube [1, 1, 1]
// E.g. for rank [2] will produce matrix [1, 1]. For [3] rank will produce a cube
// [1, 1, 1]
Add<Integer> ones = tf.math.add(tf.zeros(rank_1, Integer.class), tf.constant(1));
// Convert scalar 3 into 1D array [3]
ExpandDims<Integer> channelsAs1D = tf.expandDims(tf.constant(3), tf.constant(0));
@@ -59,49 +58,57 @@ public final class NativeImageUtils {
public static Operand<Float> normalizeMask(Ops tf, Operand<Float> mask, float newValue) {
// generate array representing the axis indexes.
// For example of tensor of rank K the axisRange is {0, 1, 2 ...K}
Range<Integer> axisRange = tf.range(tf.constant(0), // from
Range<Integer> axisRange = tf.range(tf.constant(0), // from
tf.dtypes.cast(tf.rank(mask), Integer.class), // to
tf.constant(1)); // step
ReduceMax<Float> max = tf.reduceMax(mask, axisRange);
//Mul<Float> input2Float1 = tf.math.mul(tf.math.div(input2Float, max), tf.constant(1f));
// Mul<Float> input2Float1 = tf.math.mul(tf.math.div(input2Float, max),
// tf.constant(1f));
Mul<Float> normalizedMask = tf.math.mul(tf.math.div(mask, max), tf.constant(newValue));
return normalizedMask;
}
/**
* Alpha Blending .
* https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
* Alpha Blending . https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
*/
public static Operand<Float> alphaBlending(Ops tf, Operand<Float> srcRgb, Operand<Float> dstRgb, Operand<Float> srcAlpha) {
public static Operand<Float> alphaBlending(Ops tf, Operand<Float> srcRgb, Operand<Float> dstRgb,
Operand<Float> srcAlpha) {
Sub<Float> alpha = tf.math.sub(tf.onesLike(srcRgb), srcAlpha);
Mul<Float> src = tf.math.mul(srcRgb, alpha);
Mul<Float> dst = tf.math.mul(dstRgb, tf.math.sub(tf.constant(1.0f), alpha));
Add<Float> out = tf.math.add(dst, src);
//Mul<Float> out = tf.math.mul(srcRgbNormalized, dstRgb);
//Squeeze<Float> squeeze = tf.withName("squeeze").squeeze(out, Squeeze.axis(Arrays.asList(0L)));
// Mul<Float> out = tf.math.mul(srcRgbNormalized, dstRgb);
// Squeeze<Float> squeeze = tf.withName("squeeze").squeeze(out,
// Squeeze.axis(Arrays.asList(0L)));
return out;
}
/**
* The mask can contain label values larger than the list of colors provided in the color map.
* To avoid out-of-index errors we will "normalize" the label values in the mask to MOD max-color-table-value.
* The mask can contain label values larger than the list of colors provided in the
* color map. To avoid out-of-index errors we will "normalize" the label values in the
* mask to MOD max-color-table-value.
* @param tf - tensorflow
* @param colorTable Color map of shape [n, 3]. n is the count of label entries and 3 is the RGB color assigned
* to that label.
* @param colorTable Color map of shape [n, 3]. n is the count of label entries and 3
* is the RGB color assigned to that label.
* @param mask Mask of shape [h, w] containing label vales.
* @return Mask of shape [h, w] fromMemory values normalized between [0, n]
*/
public static Operand<Long> normalizeMaskLabels(Ops tf, Operand<Integer> colorTable, Operand<Long> mask) {
// The mask can contain label values larger than the list of colors provided in the color map.
// To avoid out-of-index errors we will "normalize" the label values in the mask to MOD max-color-table-value.
// The mask can contain label values larger than the list of colors provided in
// the color map.
// To avoid out-of-index errors we will "normalize" the label values in the mask
// to MOD max-color-table-value.
Sub<Long> colorTableShape = tf.math.sub(tf.shape(colorTable, Long.class), tf.constant(1L));
// Color tables have shape [N, 3], where N is the count of label entries. Therefore the max label id is (N - 1).
// Color tables have shape [N, 3], where N is the count of label entries.
// Therefore the max label id is (N - 1).
Gather<Long> colorTableSize = tf.gather(colorTableShape, tf.constant(new int[] { 0 }), tf.constant(0));
// Normalize the label values in the mask so they don't exceed the max value in the color map.
// Normalize the label values in the mask so they don't exceed the max value in
// the color map.
return tf.math.mod(mask, colorTableSize);
}
}

View File

@@ -25,16 +25,17 @@ import org.springframework.core.io.DefaultResourceLoader;
/**
*
* Visualizes the segmentation results via specified color map.
* Color maps helping to visualize the semantic segmentation results for the different datasets.
* Visualizes the segmentation results via specified color map. Color maps helping to
* visualize the semantic segmentation results for the different datasets.
*
* Supported colormaps are:
* - ADE20K (http://groups.csail.mit.edu/vision/datasets/ADE20K/).
* - Cityscapes dataset (https://www.cityscapes-dataset.com).
* - Mapillary Vistas (https://research.mapillary.com).
* - PASCAL VOC 2012 (http://host.robots.ox.ac.uk/pascal/VOC/).
* Supported colormaps are: - ADE20K
* (http://groups.csail.mit.edu/vision/datasets/ADE20K/). - Cityscapes dataset
* (https://www.cityscapes-dataset.com). - Mapillary Vistas
* (https://research.mapillary.com). - PASCAL VOC 2012
* (http://host.robots.ox.ac.uk/pascal/VOC/).
*
* Based on: https://github.com/tensorflow/models/blob/master/research/deeplab/utils/get_dataset_colormap.py
* Based on:
* https://github.com/tensorflow/models/blob/master/research/deeplab/utils/get_dataset_colormap.py
*
* @author Christian Tzolov
*/
@@ -45,238 +46,51 @@ public final class SegmentationColorMap {
}
/** MAPILLARY_COLORMAP . */
public static final int[][] MAPILLARY_COLORMAP = new int[][] {
{ 165, 42, 42 },
{ 0, 192, 0 },
{ 196, 196, 196 },
{ 190, 153, 153 },
{ 180, 165, 180 },
{ 102, 102, 156 },
{ 102, 102, 156 },
{ 128, 64, 255 },
{ 140, 140, 200 },
{ 170, 170, 170 },
{ 250, 170, 160 },
{ 96, 96, 96 },
{ 230, 150, 140 },
{ 128, 64, 128 },
{ 110, 110, 110 },
{ 244, 35, 232 },
{ 150, 100, 100 },
{ 70, 70, 70 },
{ 150, 120, 90 },
{ 220, 20, 60 },
{ 255, 0, 0 },
{ 255, 0, 0 },
{ 255, 0, 0 },
{ 200, 128, 128 },
{ 255, 255, 255 },
{ 64, 170, 64 },
{ 128, 64, 64 },
{ 70, 130, 180 },
{ 255, 255, 255 },
{ 152, 251, 152 },
{ 107, 142, 35 },
{ 0, 170, 30 },
{ 255, 255, 128 },
{ 250, 0, 30 },
{ 0, 0, 0 },
{ 220, 220, 220 },
{ 170, 170, 170 },
{ 222, 40, 40 },
{ 100, 170, 30 },
{ 40, 40, 40 },
{ 33, 33, 33 },
{ 170, 170, 170 },
{ 0, 0, 142 },
{ 170, 170, 170 },
{ 210, 170, 100 },
{ 153, 153, 153 },
{ 128, 128, 128 },
{ 0, 0, 142 },
{ 250, 170, 30 },
{ 192, 192, 192 },
{ 220, 220, 0 },
{ 180, 165, 180 },
{ 119, 11, 32 },
{ 0, 0, 142 },
{ 0, 60, 100 },
{ 0, 0, 142 },
{ 0, 0, 90 },
{ 0, 0, 230 },
{ 0, 80, 100 },
{ 128, 64, 64 },
{ 0, 0, 110 },
{ 0, 0, 70 },
{ 0, 0, 192 },
{ 32, 32, 32 },
{ 0, 0, 0 },
{ 0, 0, 0 },
};
public static final int[][] MAPILLARY_COLORMAP = new int[][] { { 165, 42, 42 }, { 0, 192, 0 }, { 196, 196, 196 },
{ 190, 153, 153 }, { 180, 165, 180 }, { 102, 102, 156 }, { 102, 102, 156 }, { 128, 64, 255 },
{ 140, 140, 200 }, { 170, 170, 170 }, { 250, 170, 160 }, { 96, 96, 96 }, { 230, 150, 140 },
{ 128, 64, 128 }, { 110, 110, 110 }, { 244, 35, 232 }, { 150, 100, 100 }, { 70, 70, 70 }, { 150, 120, 90 },
{ 220, 20, 60 }, { 255, 0, 0 }, { 255, 0, 0 }, { 255, 0, 0 }, { 200, 128, 128 }, { 255, 255, 255 },
{ 64, 170, 64 }, { 128, 64, 64 }, { 70, 130, 180 }, { 255, 255, 255 }, { 152, 251, 152 }, { 107, 142, 35 },
{ 0, 170, 30 }, { 255, 255, 128 }, { 250, 0, 30 }, { 0, 0, 0 }, { 220, 220, 220 }, { 170, 170, 170 },
{ 222, 40, 40 }, { 100, 170, 30 }, { 40, 40, 40 }, { 33, 33, 33 }, { 170, 170, 170 }, { 0, 0, 142 },
{ 170, 170, 170 }, { 210, 170, 100 }, { 153, 153, 153 }, { 128, 128, 128 }, { 0, 0, 142 }, { 250, 170, 30 },
{ 192, 192, 192 }, { 220, 220, 0 }, { 180, 165, 180 }, { 119, 11, 32 }, { 0, 0, 142 }, { 0, 60, 100 },
{ 0, 0, 142 }, { 0, 0, 90 }, { 0, 0, 230 }, { 0, 80, 100 }, { 128, 64, 64 }, { 0, 0, 110 }, { 0, 0, 70 },
{ 0, 0, 192 }, { 32, 32, 32 }, { 0, 0, 0 }, { 0, 0, 0 }, };
/**
* Label colormap used in ADE20K segmentation benchmark.
*/
public static final int[][] ADE20K_COLORMAP = new int[][] {
{ 0, 0, 0 },
{ 120, 120, 120 },
{ 180, 120, 120 },
{ 6, 230, 230 },
{ 80, 50, 50 },
{ 4, 200, 3 },
{ 120, 120, 80 },
{ 140, 140, 140 },
{ 204, 5, 255 },
{ 230, 230, 230 },
{ 4, 250, 7 },
{ 224, 5, 255 },
{ 235, 255, 7 },
{ 150, 5, 61 },
{ 120, 120, 70 },
{ 8, 255, 51 },
{ 255, 6, 82 },
{ 143, 255, 140 },
{ 204, 255, 4 },
{ 255, 51, 7 },
{ 204, 70, 3 },
{ 0, 102, 200 },
{ 61, 230, 250 },
{ 255, 6, 51 },
{ 11, 102, 255 },
{ 255, 7, 71 },
{ 255, 9, 224 },
{ 9, 7, 230 },
{ 220, 220, 220 },
{ 255, 9, 92 },
{ 112, 9, 255 },
{ 8, 255, 214 },
{ 7, 255, 224 },
{ 255, 184, 6 },
{ 10, 255, 71 },
{ 255, 41, 10 },
{ 7, 255, 255 },
{ 224, 255, 8 },
{ 102, 8, 255 },
{ 255, 61, 6 },
{ 255, 194, 7 },
{ 255, 122, 8 },
{ 0, 255, 20 },
{ 255, 8, 41 },
{ 255, 5, 153 },
{ 6, 51, 255 },
{ 235, 12, 255 },
{ 160, 150, 20 },
{ 0, 163, 255 },
{ 140, 140, 140 },
{ 250, 10, 15 },
{ 20, 255, 0 },
{ 31, 255, 0 },
{ 255, 31, 0 },
{ 255, 224, 0 },
{ 153, 255, 0 },
{ 0, 0, 255 },
{ 255, 71, 0 },
{ 0, 235, 255 },
{ 0, 173, 255 },
{ 31, 0, 255 },
{ 11, 200, 200 },
{ 255, 82, 0 },
{ 0, 255, 245 },
{ 0, 61, 255 },
{ 0, 255, 112 },
{ 0, 255, 133 },
{ 255, 0, 0 },
{ 255, 163, 0 },
{ 255, 102, 0 },
{ 194, 255, 0 },
{ 0, 143, 255 },
{ 51, 255, 0 },
{ 0, 82, 255 },
{ 0, 255, 41 },
{ 0, 255, 173 },
{ 10, 0, 255 },
{ 173, 255, 0 },
{ 0, 255, 153 },
{ 255, 92, 0 },
{ 255, 0, 255 },
{ 255, 0, 245 },
{ 255, 0, 102 },
{ 255, 173, 0 },
{ 255, 0, 20 },
{ 255, 184, 184 },
{ 0, 31, 255 },
{ 0, 255, 61 },
{ 0, 71, 255 },
{ 255, 0, 204 },
{ 0, 255, 194 },
{ 0, 255, 82 },
{ 0, 10, 255 },
{ 0, 112, 255 },
{ 51, 0, 255 },
{ 0, 194, 255 },
{ 0, 122, 255 },
{ 0, 255, 163 },
{ 255, 153, 0 },
{ 0, 255, 10 },
{ 255, 112, 0 },
{ 143, 255, 0 },
{ 82, 0, 255 },
{ 163, 255, 0 },
{ 255, 235, 0 },
{ 8, 184, 170 },
{ 133, 0, 255 },
{ 0, 255, 92 },
{ 184, 0, 255 },
{ 255, 0, 31 },
{ 0, 184, 255 },
{ 0, 214, 255 },
{ 255, 0, 112 },
{ 92, 255, 0 },
{ 0, 224, 255 },
{ 112, 224, 255 },
{ 70, 184, 160 },
{ 163, 0, 255 },
{ 153, 0, 255 },
{ 71, 255, 0 },
{ 255, 0, 163 },
{ 255, 204, 0 },
{ 255, 0, 143 },
{ 0, 255, 235 },
{ 133, 255, 0 },
{ 255, 0, 235 },
{ 245, 0, 255 },
{ 255, 0, 122 },
{ 255, 245, 0 },
{ 10, 190, 212 },
{ 214, 255, 0 },
{ 0, 204, 255 },
{ 20, 0, 255 },
{ 255, 255, 0 },
{ 0, 153, 255 },
{ 0, 41, 255 },
{ 0, 255, 204 },
{ 41, 0, 255 },
{ 41, 255, 0 },
{ 173, 0, 255 },
{ 0, 245, 255 },
{ 71, 0, 255 },
{ 122, 0, 255 },
{ 0, 255, 184 },
{ 0, 92, 255 },
{ 184, 255, 0 },
{ 0, 133, 255 },
{ 255, 214, 0 },
{ 25, 194, 194 },
{ 102, 255, 0 },
{ 92, 0, 255 },
};
public static final int[][] ADE20K_COLORMAP = new int[][] { { 0, 0, 0 }, { 120, 120, 120 }, { 180, 120, 120 },
{ 6, 230, 230 }, { 80, 50, 50 }, { 4, 200, 3 }, { 120, 120, 80 }, { 140, 140, 140 }, { 204, 5, 255 },
{ 230, 230, 230 }, { 4, 250, 7 }, { 224, 5, 255 }, { 235, 255, 7 }, { 150, 5, 61 }, { 120, 120, 70 },
{ 8, 255, 51 }, { 255, 6, 82 }, { 143, 255, 140 }, { 204, 255, 4 }, { 255, 51, 7 }, { 204, 70, 3 },
{ 0, 102, 200 }, { 61, 230, 250 }, { 255, 6, 51 }, { 11, 102, 255 }, { 255, 7, 71 }, { 255, 9, 224 },
{ 9, 7, 230 }, { 220, 220, 220 }, { 255, 9, 92 }, { 112, 9, 255 }, { 8, 255, 214 }, { 7, 255, 224 },
{ 255, 184, 6 }, { 10, 255, 71 }, { 255, 41, 10 }, { 7, 255, 255 }, { 224, 255, 8 }, { 102, 8, 255 },
{ 255, 61, 6 }, { 255, 194, 7 }, { 255, 122, 8 }, { 0, 255, 20 }, { 255, 8, 41 }, { 255, 5, 153 },
{ 6, 51, 255 }, { 235, 12, 255 }, { 160, 150, 20 }, { 0, 163, 255 }, { 140, 140, 140 }, { 250, 10, 15 },
{ 20, 255, 0 }, { 31, 255, 0 }, { 255, 31, 0 }, { 255, 224, 0 }, { 153, 255, 0 }, { 0, 0, 255 },
{ 255, 71, 0 }, { 0, 235, 255 }, { 0, 173, 255 }, { 31, 0, 255 }, { 11, 200, 200 }, { 255, 82, 0 },
{ 0, 255, 245 }, { 0, 61, 255 }, { 0, 255, 112 }, { 0, 255, 133 }, { 255, 0, 0 }, { 255, 163, 0 },
{ 255, 102, 0 }, { 194, 255, 0 }, { 0, 143, 255 }, { 51, 255, 0 }, { 0, 82, 255 }, { 0, 255, 41 },
{ 0, 255, 173 }, { 10, 0, 255 }, { 173, 255, 0 }, { 0, 255, 153 }, { 255, 92, 0 }, { 255, 0, 255 },
{ 255, 0, 245 }, { 255, 0, 102 }, { 255, 173, 0 }, { 255, 0, 20 }, { 255, 184, 184 }, { 0, 31, 255 },
{ 0, 255, 61 }, { 0, 71, 255 }, { 255, 0, 204 }, { 0, 255, 194 }, { 0, 255, 82 }, { 0, 10, 255 },
{ 0, 112, 255 }, { 51, 0, 255 }, { 0, 194, 255 }, { 0, 122, 255 }, { 0, 255, 163 }, { 255, 153, 0 },
{ 0, 255, 10 }, { 255, 112, 0 }, { 143, 255, 0 }, { 82, 0, 255 }, { 163, 255, 0 }, { 255, 235, 0 },
{ 8, 184, 170 }, { 133, 0, 255 }, { 0, 255, 92 }, { 184, 0, 255 }, { 255, 0, 31 }, { 0, 184, 255 },
{ 0, 214, 255 }, { 255, 0, 112 }, { 92, 255, 0 }, { 0, 224, 255 }, { 112, 224, 255 }, { 70, 184, 160 },
{ 163, 0, 255 }, { 153, 0, 255 }, { 71, 255, 0 }, { 255, 0, 163 }, { 255, 204, 0 }, { 255, 0, 143 },
{ 0, 255, 235 }, { 133, 255, 0 }, { 255, 0, 235 }, { 245, 0, 255 }, { 255, 0, 122 }, { 255, 245, 0 },
{ 10, 190, 212 }, { 214, 255, 0 }, { 0, 204, 255 }, { 20, 0, 255 }, { 255, 255, 0 }, { 0, 153, 255 },
{ 0, 41, 255 }, { 0, 255, 204 }, { 41, 0, 255 }, { 41, 255, 0 }, { 173, 0, 255 }, { 0, 245, 255 },
{ 71, 0, 255 }, { 122, 0, 255 }, { 0, 255, 184 }, { 0, 92, 255 }, { 184, 255, 0 }, { 0, 133, 255 },
{ 255, 214, 0 }, { 25, 194, 194 }, { 102, 255, 0 }, { 92, 0, 255 }, };
/** BLACK_WHITE_COLORMAP . */
public static int[][] BLACK_WHITE_COLORMAP = new int[][] {
{ 0, 0, 0 },
{ 127, 127, 127 },
{ 255, 255, 255 },
};
public static int[][] BLACK_WHITE_COLORMAP = new int[][] { { 0, 0, 0 }, { 127, 127, 127 }, { 255, 255, 255 }, };
/** CITYMAP_COLORMAP . */
public static final int[][] CITYMAP_COLORMAP = new int[255][3];
@@ -284,27 +98,10 @@ public final class SegmentationColorMap {
static {
// Initialize citymap
int[][] _CITYMAP_COLORMAP = new int[][] {
{ 128, 64, 128 },
{ 244, 35, 232 },
{ 70, 70, 70 },
{ 102, 102, 156 },
{ 190, 153, 153 },
{ 153, 153, 153 },
{ 250, 170, 30 },
{ 220, 220, 0 },
{ 107, 142, 35 },
{ 152, 251, 152 },
{ 70, 130, 180 },
{ 220, 20, 60 },
{ 255, 0, 0 },
{ 0, 0, 142 },
{ 0, 0, 70 },
{ 0, 60, 100 },
{ 0, 80, 100 },
{ 0, 0, 230 },
{ 119, 11, 32 }
};
int[][] _CITYMAP_COLORMAP = new int[][] { { 128, 64, 128 }, { 244, 35, 232 }, { 70, 70, 70 }, { 102, 102, 156 },
{ 190, 153, 153 }, { 153, 153, 153 }, { 250, 170, 30 }, { 220, 220, 0 }, { 107, 142, 35 },
{ 152, 251, 152 }, { 70, 130, 180 }, { 220, 20, 60 }, { 255, 0, 0 }, { 0, 0, 142 }, { 0, 0, 70 },
{ 0, 60, 100 }, { 0, 80, 100 }, { 0, 0, 230 }, { 119, 11, 32 } };
for (int i = 0; i < _CITYMAP_COLORMAP.length; i++) {
System.arraycopy(_CITYMAP_COLORMAP[i], 0, CITYMAP_COLORMAP[i], 0, _CITYMAP_COLORMAP[i].length);
@@ -323,8 +120,11 @@ public final class SegmentationColorMap {
}
public static class ColorMap {
private String name;
private String info;
private int[][] colormap;
public String getName() {
@@ -353,11 +153,10 @@ public final class SegmentationColorMap {
@Override
public String toString() {
return "ColorMap{" +
"name='" + name + '\'' +
"info='" + info + '\'' +
", colormap=" + Arrays.deepToString(colormap) +
'}';
return "ColorMap{" + "name='" + name + '\'' + "info='" + info + '\'' + ", colormap="
+ Arrays.deepToString(colormap) + '}';
}
}
}

View File

@@ -54,12 +54,19 @@ import org.springframework.core.io.DefaultResourceLoader;
public class SemanticSegmentation implements AutoCloseable {
private static final long CHANNELS = 3;
private static final float REQUIRED_INPUT_IMAGE_SIZE = 513f;
private final GraphRunner imageNormalization;
private final GraphRunner semanticSegmentation;
private final GraphRunner maskImageEncoding;
private final GraphRunner alphaBlending;
private final Tensor<Integer> colorMapTensor;
private final Tensor<Float> maskTransparencyTensor;
@Override
@@ -75,95 +82,134 @@ public class SemanticSegmentation implements AutoCloseable {
public SemanticSegmentation(String modelUrl, int[][] colorMap, long[] labelFilter, float maskTransparency) {
this.imageNormalization = new GraphRunner("input_image", "resized_image")
.withGraphDefinition(tf -> {
Placeholder<String> input = tf.withName("input_image").placeholder(String.class);
ExtractJpegShape<Integer> imageShapeAndChannel = tf.image.extractJpegShape(input);
Gather<Integer> imageShape = tf.gather(imageShapeAndChannel, tf.constant(new int[] { 0, 1 }), tf.constant(0));
this.imageNormalization = new GraphRunner("input_image", "resized_image").withGraphDefinition(tf -> {
Placeholder<String> input = tf.withName("input_image").placeholder(String.class);
ExtractJpegShape<Integer> imageShapeAndChannel = tf.image.extractJpegShape(input);
Gather<Integer> imageShape = tf.gather(imageShapeAndChannel, tf.constant(new int[] { 0, 1 }),
tf.constant(0));
Cast<Float> maxSize = tf.dtypes.cast(tf.max(imageShape, tf.constant(0)), Float.class);
Div<Float> scale = tf.math.div(tf.constant(REQUIRED_INPUT_IMAGE_SIZE), maxSize);
Cast<Integer> newSize = tf.dtypes.cast(tf.math.mul(scale, tf.dtypes.cast(imageShape, Float.class)), Integer.class);
Cast<Float> maxSize = tf.dtypes.cast(tf.max(imageShape, tf.constant(0)), Float.class);
Div<Float> scale = tf.math.div(tf.constant(REQUIRED_INPUT_IMAGE_SIZE), maxSize);
Cast<Integer> newSize = tf.dtypes.cast(tf.math.mul(scale, tf.dtypes.cast(imageShape, Float.class)),
Integer.class);
final Operand<Float> decodedImage =
tf.dtypes.cast(tf.image.decodeJpeg(input, DecodeJpeg.channels(CHANNELS)), Float.class);
final Operand<Float> decodedImage = tf.dtypes
.cast(tf.image.decodeJpeg(input, DecodeJpeg.channels(CHANNELS)), Float.class);
final Operand<Float> resizedImageFloat =
tf.image.resizeBilinear(tf.expandDims(decodedImage, tf.constant(0)), newSize);
final Operand<Float> resizedImageFloat = tf.image
.resizeBilinear(tf.expandDims(decodedImage, tf.constant(0)), newSize);
tf.withName("resized_image").dtypes.cast(resizedImageFloat, UInt8.class);
});
tf.withName("resized_image").dtypes.cast(resizedImageFloat, UInt8.class);
});
this.semanticSegmentation = new GraphRunner("ImageTensor:0", "SemanticPredictions:0")
.withGraphDefinition(new ProtoBufGraphDefinition(new DefaultResourceLoader().getResource(modelUrl), true));
.withGraphDefinition(new ProtoBufGraphDefinition(new DefaultResourceLoader().getResource(modelUrl), true));
this.colorMapTensor = Tensor.create(colorMap).expect(Integer.class);
this.maskImageEncoding = new GraphRunner(Arrays.asList("color_map", "mask_pixels"), Arrays.asList("mask_png", "mask_rgb"))
.withGraphDefinition(tf -> {
Placeholder<Integer> colorTable = tf.withName("color_map").placeholder(Integer.class);
this.maskImageEncoding = new GraphRunner(Arrays.asList("color_map", "mask_pixels"),
Arrays.asList("mask_png", "mask_rgb"))
.withGraphDefinition(tf -> {
Placeholder<Integer> colorTable = tf.withName("color_map").placeholder(Integer.class);
Placeholder<Long> batchedMask = tf.withName("mask_pixels").placeholder(Long.class);
// Remove batch dimension
Squeeze<Long> mask = tf.squeeze(batchedMask, Squeeze.axis(Arrays.asList(0L)));
Placeholder<Long> batchedMask = tf.withName("mask_pixels").placeholder(Long.class);
// Remove batch dimension
Squeeze<Long> mask = tf.squeeze(batchedMask, Squeeze.axis(Arrays.asList(0L)));
Operand<Long> filteredMask = labelFilter(tf, mask, labelFilter);
Operand<Long> filteredMask = labelFilter(tf, mask, labelFilter);
// The mask can contain label values larger than the list of colors provided in the color map.
// To avoid out-of-index errors we will "normalize" the label values in the mask to MOD max-color-table-value.
Operand<Long> mask3 = NativeImageUtils.normalizeMaskLabels(tf, colorTable, filteredMask);
// The mask can contain label values larger than the list of colors
// provided in the color map.
// To avoid out-of-index errors we will "normalize" the label values in
// the mask to MOD max-color-table-value.
Operand<Long> mask3 = NativeImageUtils.normalizeMaskLabels(tf, colorTable, filteredMask);
Gather<Integer> maskRgb = tf.withName("mask_rgb").gather(colorTable, mask3, tf.constant(0));
Gather<Integer> maskRgb = tf.withName("mask_rgb").gather(colorTable, mask3, tf.constant(0));
Operand<String> png = tf.withName("mask_png").image.encodePng(tf.dtypes.cast(maskRgb, UInt8.class));
Operand<String> png = tf.withName("mask_png").image.encodePng(tf.dtypes.cast(maskRgb, UInt8.class));
});
});
this.maskTransparencyTensor = Tensor.create(maskTransparency).expect(Float.class);
this.alphaBlending = new GraphRunner(
Arrays.asList("input_image", "mask_image", "mask_transparency"), Arrays.asList("blended_png"))
.withGraphDefinition(tf -> {
// Input image [B, H, W, 3]
Cast<Float> inputImageRgb = tf.dtypes.cast(tf.withName("input_image").placeholder(UInt8.class), Float.class);
this.alphaBlending = new GraphRunner(Arrays.asList("input_image", "mask_image", "mask_transparency"),
Arrays.asList("blended_png"))
.withGraphDefinition(tf -> {
// Input image [B, H, W, 3]
Cast<Float> inputImageRgb = tf.dtypes.cast(tf.withName("input_image").placeholder(UInt8.class),
Float.class);
Placeholder<Integer> a = tf.withName("mask_image").placeholder(Integer.class);
Cast<Float> maskRgb = tf.dtypes.cast(a, Float.class);
Placeholder<Integer> a = tf.withName("mask_image").placeholder(Integer.class);
Cast<Float> maskRgb = tf.dtypes.cast(a, Float.class);
Squeeze<Float> inputImageRgb2 = tf.squeeze(inputImageRgb, Squeeze.axis(Arrays.asList(0L)));
Squeeze<Float> inputImageRgb2 = tf.squeeze(inputImageRgb, Squeeze.axis(Arrays.asList(0L)));
Placeholder<Float> maskTransparencyHolder = tf.withName("mask_transparency").placeholder(Float.class);
Placeholder<Float> maskTransparencyHolder = tf.withName("mask_transparency").placeholder(Float.class);
// Blend the transparent maskImage on top of the input image.
Operand<Float> blended = NativeImageUtils.alphaBlending(tf, maskRgb, inputImageRgb2, maskTransparencyHolder);
// Blend the transparent maskImage on top of the input image.
Operand<Float> blended = NativeImageUtils.alphaBlending(tf, maskRgb, inputImageRgb2,
maskTransparencyHolder);
// Cut
//Operand<Boolean> condition = tf.math.equal(a, tf.zerosLike(a));
//Operand<Float> blended = tf.where3(condition, tf.zerosLike(maskRgb), inputImageRgb2);
// Cut
// Operand<Boolean> condition = tf.math.equal(a, tf.zerosLike(a));
// Operand<Float> blended = tf.where3(condition, tf.zerosLike(maskRgb),
// inputImageRgb2);
// Encode PNG
tf.withName("blended_png").image.encodePng(tf.dtypes.cast(blended, UInt8.class));
// Encode PNG
tf.withName("blended_png").image.encodePng(tf.dtypes.cast(blended, UInt8.class));
});
});
}
public byte[] blendMask(byte[] image) {
try (Tensor inputTensor = Tensor.create(image); GraphRunnerMemory memory = new GraphRunnerMemory()) {
Map<String, Tensor<?>> blendedTensors =
this.imageNormalization.andThen(memory) // (input_image) -> (resized_image) and memorize (resized_image)
.andThen(this.semanticSegmentation).andThen(memory) // (ImageTensor:0) -> (SemanticPredictions:0) and memorize (SemanticPredictions:0)
.andThen(Functions.rename("SemanticPredictions:0", "mask_pixels")) // (SemanticPredictions:0) -> (mask_pixels)
.andThen(Functions.enrichWith("color_map", this.colorMapTensor)) // (mask_pixels) -> (mask_pixels, color_map)
.andThen(this.maskImageEncoding).andThen(memory) // (color_map, mask_pixels) -> (mask_png, mask_rgb) and memorize (mask_png, mask_rgb)
.andThen(Functions.enrichFromMemory(
memory, "resized_image")) // (mask_png, mask_rgb) -> (mask_png, mask_rgb, resized_image), e.g. join the normalizedImageTensor
.andThen(Functions.rename(
"resized_image", "input_image",
"mask_rgb", "mask_image")) // (mask_png, mask_rgb, resized_image) -> (mask_image, input_image)
.andThen(Functions.enrichWith("mask_transparency", this.maskTransparencyTensor)) // (mask_image, input_image) -> (mask_image, input_image, mask_transparency)
.andThen(this.alphaBlending).andThen(memory) // (mask_image, input_image, mask_transparency) -> (blended_png)
.apply(Collections.singletonMap("input_image", inputTensor)); // () -> (input_image)
Map<String, Tensor<?>> blendedTensors = this.imageNormalization.andThen(memory) // (input_image)
// ->
// (resized_image)
// and
// memorize
// (resized_image)
.andThen(this.semanticSegmentation)
.andThen(memory) // (ImageTensor:0) -> (SemanticPredictions:0) and
// memorize (SemanticPredictions:0)
.andThen(Functions.rename("SemanticPredictions:0", "mask_pixels")) // (SemanticPredictions:0)
// ->
// (mask_pixels)
.andThen(Functions.enrichWith("color_map", this.colorMapTensor)) // (mask_pixels)
// ->
// (mask_pixels,
// color_map)
.andThen(this.maskImageEncoding)
.andThen(memory) // (color_map, mask_pixels) -> (mask_png, mask_rgb) and
// memorize (mask_png, mask_rgb)
.andThen(Functions.enrichFromMemory(memory, "resized_image")) // (mask_png,
// mask_rgb)
// ->
// (mask_png,
// mask_rgb,
// resized_image),
// e.g.
// join
// the
// normalizedImageTensor
.andThen(Functions.rename("resized_image", "input_image", "mask_rgb", "mask_image")) // (mask_png,
// mask_rgb,
// resized_image)
// ->
// (mask_image,
// input_image)
.andThen(Functions.enrichWith("mask_transparency", this.maskTransparencyTensor)) // (mask_image,
// input_image)
// ->
// (mask_image,
// input_image,
// mask_transparency)
.andThen(this.alphaBlending)
.andThen(memory) // (mask_image, input_image, mask_transparency) ->
// (blended_png)
.apply(Collections.singletonMap("input_image", inputTensor)); // () ->
// (input_image)
byte[] blendedImage = blendedTensors.get("blended_png").bytesValue();
@@ -176,15 +222,21 @@ public class SemanticSegmentation implements AutoCloseable {
public long[][] maskPixels(byte[] image) {
try (Tensor inputTensor = Tensor.create(image); GraphRunnerMemory memory = new GraphRunnerMemory()) {
return this.imageNormalization.andThen(memory) // (input_image) -> (resized_image) and memorize (resized_image)
.andThen(this.semanticSegmentation).andThen(memory) // (ImageTensor:0) -> (SemanticPredictions:0) and memorize (SemanticPredictions:0)
.andThen(tensorMap -> {
Tensor<?> maskTensor = tensorMap.get("SemanticPredictions:0");
int width = (int) maskTensor.shape()[1];
int height = (int) maskTensor.shape()[2];
return maskTensor.copyTo(new long[1][width][height])[0]; // 1 == batch size
})
.apply(Collections.singletonMap("input_image", inputTensor)); // () -> (input_image)
return this.imageNormalization.andThen(memory) // (input_image) ->
// (resized_image) and
// memorize (resized_image)
.andThen(this.semanticSegmentation)
.andThen(memory) // (ImageTensor:0) -> (SemanticPredictions:0) and
// memorize (SemanticPredictions:0)
.andThen(tensorMap -> {
Tensor<?> maskTensor = tensorMap.get("SemanticPredictions:0");
int width = (int) maskTensor.shape()[1];
int height = (int) maskTensor.shape()[2];
return maskTensor.copyTo(new long[1][width][height])[0]; // 1 == batch
// size
})
.apply(Collections.singletonMap("input_image", inputTensor)); // () ->
// (input_image)
}
}
@@ -192,13 +244,25 @@ public class SemanticSegmentation implements AutoCloseable {
try (Tensor inputTensor = Tensor.create(image); GraphRunnerMemory memory = new GraphRunnerMemory()) {
return this.imageNormalization.andThen(memory) // (input_image) -> (resized_image) and memorize (resized_image)
.andThen(this.semanticSegmentation).andThen(memory) // (ImageTensor:0) -> (SemanticPredictions:0) and memorize (SemanticPredictions:0)
.andThen(Functions.rename("SemanticPredictions:0", "mask_pixels")) // (SemanticPredictions:0) -> (mask_pixels)
.andThen(Functions.enrichWith("color_map", this.colorMapTensor)) // (mask_pixels) -> (mask_pixels, color_map)
.andThen(this.maskImageEncoding).andThen(memory) // (color_map, mask_pixels) -> (mask_png, mask_rgb) and memorize (mask_png, mask_rgb)
.andThen(tensorMap -> tensorMap.get("mask_png").bytesValue())
.apply(Collections.singletonMap("input_image", inputTensor)); // () -> (input_image)
return this.imageNormalization.andThen(memory) // (input_image) ->
// (resized_image) and
// memorize (resized_image)
.andThen(this.semanticSegmentation)
.andThen(memory) // (ImageTensor:0) -> (SemanticPredictions:0) and
// memorize (SemanticPredictions:0)
.andThen(Functions.rename("SemanticPredictions:0", "mask_pixels")) // (SemanticPredictions:0)
// ->
// (mask_pixels)
.andThen(Functions.enrichWith("color_map", this.colorMapTensor)) // (mask_pixels)
// ->
// (mask_pixels,
// color_map)
.andThen(this.maskImageEncoding)
.andThen(memory) // (color_map, mask_pixels) -> (mask_png, mask_rgb) and
// memorize (mask_png, mask_rgb)
.andThen(tensorMap -> tensorMap.get("mask_png").bytesValue())
.apply(Collections.singletonMap("input_image", inputTensor)); // () ->
// (input_image)
}
}
@@ -222,8 +286,7 @@ public class SemanticSegmentation implements AutoCloseable {
try (SemanticSegmentation segmentationService = new SemanticSegmentation(
"https://download.tensorflow.org/models/deeplabv3_mnv2_cityscapes_train_2018_02_05.tar.gz#frozen_inference_graph.pb",
SegmentationColorMap.loadColorMap("classpath:/colormap/citymap_colormap.json"), null, 0.45f)
) {
SegmentationColorMap.loadColorMap("classpath:/colormap/citymap_colormap.json"), null, 0.45f)) {
byte[] inputImage = GraphicsUtils.loadAsByteArray("classpath:/images/amsterdam-cityscape1.jpg");
// 1. Mask pixels
@@ -244,8 +307,7 @@ public class SemanticSegmentation implements AutoCloseable {
try (SemanticSegmentation segmentationService = new SemanticSegmentation(
"https://download.tensorflow.org/models/deeplabv3_xception_ade20k_train_2018_05_29.tar.gz#frozen_inference_graph.pb",
SegmentationColorMap.loadColorMap("classpath:/colormap/ade20k_colormap.json"), null, 0.45f)
) {
SegmentationColorMap.loadColorMap("classpath:/colormap/ade20k_colormap.json"), null, 0.45f)) {
byte[] inputImage = GraphicsUtils.loadAsByteArray("classpath:/images/interior.jpg");
// 1. Mask pixels
@@ -264,8 +326,7 @@ public class SemanticSegmentation implements AutoCloseable {
try (SemanticSegmentation segmentationService = new SemanticSegmentation(
"https://download.tensorflow.org/models/deeplabv3_mnv2_pascal_trainval_2018_01_29.tar.gz#frozen_inference_graph.pb",
SegmentationColorMap.loadColorMap("classpath:/colormap/black_white_colormap.json"), null, 0.45f)
) {
SegmentationColorMap.loadColorMap("classpath:/colormap/black_white_colormap.json"), null, 0.45f)) {
byte[] inputImage = GraphicsUtils.loadAsByteArray("classpath:/images/VikiMaxiAdi.jpg");
// 1. Mask pixels
@@ -283,4 +344,5 @@ public class SemanticSegmentation implements AutoCloseable {
}
}
}

View File

@@ -44,8 +44,8 @@ import static java.awt.image.BufferedImage.TYPE_3BYTE_BGR;
/**
*
* Semantic image segmentation - the task of assigning a semantic label, such as "road", "sky", "person", "dog", to
* every pixel in an image.
* Semantic image segmentation - the task of assigning a semantic label, such as "road",
* "sky", "person", "dog", to every pixel in an image.
*
* https://ai.googleblog.com/2018/03/semantic-image-segmentation-with.html
* https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/model_zoo.md
@@ -65,11 +65,14 @@ public class SemanticSegmentationUtils {
/** INPUT_TENSOR_NAME . */
public static final String INPUT_TENSOR_NAME = "ImageTensor:0";
/** OUTPUT_TENSOR_NAME . */
public static final String OUTPUT_TENSOR_NAME = "SemanticPredictions:0";
private static final int BATCH_SIZE = 1;
private static final long CHANNELS = 3;
private static final int REQUIRED_INPUT_IMAGE_SIZE = 513;
public static BufferedImage scaledImage(String imagePath) {
@@ -100,9 +103,11 @@ public class SemanticSegmentationUtils {
int newHeight = (int) (originalImage.getHeight() * scale);
Image tmpImage = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_DEFAULT);
//BufferedImage resizedImage = new BufferedImage(newWidth, newHeight, TYPE_INT_BGR);
// BufferedImage resizedImage = new BufferedImage(newWidth, newHeight,
// TYPE_INT_BGR);
BufferedImage resizedImage = new BufferedImage(newWidth, newHeight, TYPE_3BYTE_BGR);
//BufferedImage resizedImage = new BufferedImage(newWidth, newHeight, originalImage.getType());
// BufferedImage resizedImage = new BufferedImage(newWidth, newHeight,
// originalImage.getType());
Graphics2D g2d = resizedImage.createGraphics();
g2d.drawImage(tmpImage, 0, 0, null);
@@ -125,7 +130,8 @@ public class SemanticSegmentationUtils {
// ImageIO.read produces BGR-encoded images, while the model expects RGB.
byte[] data = bgrToRgb(toBytes(scaledImage));
// Expand dimensions since the model expects images to have shape: [1, None, None, 3]
// Expand dimensions since the model expects images to have shape: [1, None, None,
// 3]
long[] shape = new long[] { BATCH_SIZE, scaledImage.getHeight(), scaledImage.getWidth(), CHANNELS };
return Tensor.create(UInt8.class, shape, ByteBuffer.wrap(data));
@@ -201,7 +207,8 @@ public class SemanticSegmentationUtils {
public String serializeToJson(int[][] pixels) {
String masksBase64 = Base64.getEncoder().encodeToString(toBytes(pixels));
return String.format("{ \"columns\":%d, \"rows\":%d, \"masks\":\"%s\"}", pixels.length, pixels[0].length, masksBase64);
return String.format("{ \"columns\":%d, \"rows\":%d, \"masks\":\"%s\"}", pixels.length, pixels[0].length,
masksBase64);
}
public int[][] deserializeToMasks(String json) throws IOException {
@@ -221,7 +228,7 @@ public class SemanticSegmentationUtils {
b[bi + 0] = (byte) (i >> 24);
b[bi + 1] = (byte) (i >> 16);
b[bi + 2] = (byte) (i >> 8);
b[bi + 3] = (byte) (i /*>> 0*/);
b[bi + 3] = (byte) (i /* >> 0 */);
bi = bi + 4;
}
}
@@ -233,10 +240,7 @@ public class SemanticSegmentationUtils {
int bi = 0;
for (int i = 0; i < ic; i++) {
for (int j = 0; j < jc; j++) {
intResult[i][j] = (b[bi] << 24) +
(b[bi + 1] << 16) +
(b[bi + 2] << 8) +
b[bi + 3];
intResult[i][j] = (b[bi] << 24) + (b[bi + 1] << 16) + (b[bi + 2] << 8) + b[bi + 3];
bi = bi + 4;
}
}
@@ -246,14 +250,16 @@ public class SemanticSegmentationUtils {
public static void main(String[] args) throws IOException {
// PASCAL VOC 2012
//String tensorflowModelLocation = "file:/Users/ctzolov/Downloads/deeplabv3_mnv2_pascal_train_aug/frozen_inference_graph.pb";
//String imagePath = "classpath:/images/VikiMaxiAdi.jpg";
// String tensorflowModelLocation =
// "file:/Users/ctzolov/Downloads/deeplabv3_mnv2_pascal_train_aug/frozen_inference_graph.pb";
// String imagePath = "classpath:/images/VikiMaxiAdi.jpg";
// CITYSCAPE
//String tensorflowModelLocation = "file:/Users/ctzolov/Downloads/deeplabv3_mnv2_cityscapes_train/frozen_inference_graph.pb";
//String imagePath = "classpath:/images/amsterdam-cityscape1.jpg";
//String imagePath = "classpath:/images/amsterdam-channel.jpg";
//String imagePath = "classpath:/images/landsmeer.png";
// String tensorflowModelLocation =
// "file:/Users/ctzolov/Downloads/deeplabv3_mnv2_cityscapes_train/frozen_inference_graph.pb";
// String imagePath = "classpath:/images/amsterdam-cityscape1.jpg";
// String imagePath = "classpath:/images/amsterdam-channel.jpg";
// String imagePath = "classpath:/images/landsmeer.png";
// ADE20K
String tensorflowModelLocation = "file:/Users/ctzolov/Downloads/deeplabv3_xception_ade20k_train/frozen_inference_graph.pb";
@@ -261,7 +267,8 @@ public class SemanticSegmentationUtils {
BufferedImage inputImage = ImageIO.read(new DefaultResourceLoader().getResource(imagePath).getInputStream());
TensorFlowService tf = new TensorFlowService(new DefaultResourceLoader().getResource(tensorflowModelLocation), Arrays.asList(OUTPUT_TENSOR_NAME));
TensorFlowService tf = new TensorFlowService(new DefaultResourceLoader().getResource(tensorflowModelLocation),
Arrays.asList(OUTPUT_TENSOR_NAME));
SemanticSegmentationUtils segmentationService = new SemanticSegmentationUtils();
@@ -275,15 +282,24 @@ public class SemanticSegmentationUtils {
int height = (int) maskPixelsTensor.shape()[1];
int width = (int) maskPixelsTensor.shape()[2];
long[][] maskPixels = maskPixelsTensor.copyTo(new long[BATCH_SIZE][height][width])[0]; // take 0 because the batch size is 1.
long[][] maskPixels = maskPixelsTensor.copyTo(new long[BATCH_SIZE][height][width])[0]; // take
// 0
// because
// the
// batch
// size
// is
// 1.
int[][] maskPixelsInt = segmentationService.toIntArray(maskPixels);
BufferedImage maskImage = segmentationService.createMaskImage(maskPixelsInt, scaledImage.getWidth(), scaledImage.getHeight(), 0.35);
BufferedImage maskImage = segmentationService.createMaskImage(maskPixelsInt, scaledImage.getWidth(),
scaledImage.getHeight(), 0.35);
BufferedImage blended = segmentationService.blendMask(maskImage, scaledImage);
ImageIO.write(maskImage, "png", new File("./semantic-segmentation/target/java2Dmask.jpg"));
ImageIO.write(blended, "png", new File("./semantic-segmentation/target/java2Dblended.jpg"));
}
}

View File

@@ -40,8 +40,8 @@ public class SpelFunctionConfiguration {
public ExpressionEvaluatingTransformer expressionEvaluatingTransformer(
SpelFunctionProperties spelFunctionProperties) {
return new ExpressionEvaluatingTransformer(new SpelExpressionParser()
.parseExpression(spelFunctionProperties.getExpression()));
return new ExpressionEvaluatingTransformer(
new SpelExpressionParser().parseExpression(spelFunctionProperties.getExpression()));
}
}

View File

@@ -48,7 +48,8 @@ public class SpelFunctionApplicationTests {
@Test
public void testJson() {
Message<?> message = MessageBuilder.withPayload("{\"foo\":\"bar\"}")
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build();
final Message<?> transformed = this.transformer.apply(message);
assertThat(transformed.getPayload()).isEqualTo("{\"FOO\":\"BAR\"}");
}
@@ -57,4 +58,5 @@ public class SpelFunctionApplicationTests {
static class SpelFunctionTestApplication {
}
}

View File

@@ -60,8 +60,7 @@ public class SplitterFunctionConfiguration {
@ConditionalOnProperty(prefix = "splitter", name = "expression")
public AbstractMessageSplitter expressionSplitter(SplitterFunctionProperties splitterFunctionProperties) {
return new ExpressionEvaluatingSplitter(
new SpelExpressionParser()
.parseExpression(splitterFunctionProperties.getExpression()));
new SpelExpressionParser().parseExpression(splitterFunctionProperties.getExpression()));
}
@Bean
@@ -96,10 +95,12 @@ public class SplitterFunctionConfiguration {
@ConditionalOnProperty(prefix = "splitter", name = "charset")
static class Charset {
}
@ConditionalOnProperty(prefix = "splitter", name = "fileMarkers")
static class FileMarkers {
}
}

View File

@@ -37,33 +37,29 @@ public class SplitterFunctionProperties {
private String expression;
/**
* When expression is null, delimiters to use when tokenizing
* {@link String} payloads.
* When expression is null, delimiters to use when tokenizing {@link String} payloads.
*/
private String delimiters;
/**
* Set to true or false to use a {@code FileSplitter} (to split
* text-based files by line) that includes
* (or not) beginning/end of file markers.
* Set to true or false to use a {@code FileSplitter} (to split text-based files by
* line) that includes (or not) beginning/end of file markers.
*/
private Boolean fileMarkers;
/**
* When 'fileMarkers == true', specify if they should be produced
* as FileSplitter.FileMarker objects or JSON.
* When 'fileMarkers == true', specify if they should be produced as
* FileSplitter.FileMarker objects or JSON.
*/
private boolean markersJson = true;
/**
* The charset to use when converting bytes in text-based files
* to String.
* The charset to use when converting bytes in text-based files to String.
*/
private String charset;
/**
* Add correlation/sequence information in headers to facilitate later
* aggregation.
* Add correlation/sequence information in headers to facilitate later aggregation.
*/
private boolean applySequence = true;
@@ -122,7 +118,8 @@ public class SplitterFunctionProperties {
@AssertTrue(message = "File properties are not allowed when an 'expression' or 'delimiters' property is provided")
public boolean isFilePropsAllowed() {
return !(this.expression != null || this.delimiters != null) || this.fileMarkers == null && this.charset == null;
return !(this.expression != null || this.delimiters != null)
|| this.fileMarkers == null && this.charset == null;
}
}

View File

@@ -45,5 +45,7 @@ public class SplitterFunctionApplicationTests {
@SpringBootApplication
static class SplitterFunctionTestApplication {
}
}

View File

@@ -21,4 +21,5 @@ import java.util.Collection;
import org.springframework.integration.handler.MessageProcessor;
public interface CommandLineArgumentsMessageMapper extends MessageProcessor<Collection<String>> {
}

View File

@@ -23,7 +23,8 @@ import java.util.Map;
import org.springframework.util.StringUtils;
/**
* Parses a comma delimited list of key value pairs in which the values can contain commas as well.
* Parses a comma delimited list of key value pairs in which the values can contain commas
* as well.
*
* @author Chris Schaeffer
* @author David Turanski
@@ -63,4 +64,5 @@ abstract class KeyValueListParser {
properties.put(pair.substring(0, firstEquals).trim(), pair.substring(firstEquals + 1).trim());
}
}
}

View File

@@ -25,6 +25,7 @@ import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TaskLaunchRequest {
@JsonProperty("args")
private List<String> commandlineArguments = new ArrayList<>();
@@ -62,4 +63,5 @@ public class TaskLaunchRequest {
this.commandlineArguments.addAll(args);
return this;
}
}

View File

@@ -36,13 +36,17 @@ import org.springframework.messaging.Message;
import org.springframework.util.StringUtils;
/**
* Configuration for a {@link TaskLaunchRequestFunction}, provided as a common function that can be composed with other Suppliers or
* Functions to transform any {@link Message} to a {@link TaskLaunchRequest} which may be used as input to the {@code TaskLauncherFunction} to launch a task.
* Configuration for a {@link TaskLaunchRequestFunction}, provided as a common function
* that can be composed with other Suppliers or Functions to transform any {@link Message}
* to a {@link TaskLaunchRequest} which may be used as input to the
* {@code TaskLauncherFunction} to launch a task.
*
* Command line arguments used by the task, as well as the task name itself may be statically configured or extracted from
* the message contents, using SpEL. See {@link TaskLaunchRequestFunctionProperties} for details.
* Command line arguments used by the task, as well as the task name itself may be
* statically configured or extracted from the message contents, using SpEL. See
* {@link TaskLaunchRequestFunctionProperties} for details.
*
* It is also possible to provide your own implementations of {@link CommandLineArgumentsMessageMapper} and {@link TaskNameMessageMapper}.
* It is also possible to provide your own implementations of
* {@link CommandLineArgumentsMessageMapper} and {@link TaskNameMessageMapper}.
*
* @author David Turanski
**/
@@ -58,9 +62,8 @@ public class TaskLaunchRequestFunctionConfiguration {
/**
* A {@link java.util.function.Function} to transform a {@link Message} payload to a
* {@link TaskLaunchRequest}.
*
* @param taskLaunchRequestMessageProcessor a {@link TaskLaunchRequestMessageProcessor}.
*
* @param taskLaunchRequestMessageProcessor a
* {@link TaskLaunchRequestMessageProcessor}.
* @return a {@code TaskLaunchRequest} Message.
*/
@Bean(name = TASK_LAUNCH_REQUEST_FUNCTION_NAME)
@@ -78,10 +81,8 @@ public class TaskLaunchRequestFunctionConfiguration {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Bean
public TaskLaunchRequestMessageProcessor taskLaunchRequestMessageProcessor(
TaskLaunchRequestSupplier taskLaunchRequestInitializer,
TaskLaunchRequestFunctionProperties properties,
EvaluationContext evaluationContext,
@Nullable TaskNameMessageMapper taskNameMessageMapper,
TaskLaunchRequestSupplier taskLaunchRequestInitializer, TaskLaunchRequestFunctionProperties properties,
EvaluationContext evaluationContext, @Nullable TaskNameMessageMapper taskNameMessageMapper,
@Nullable CommandLineArgumentsMessageMapper commandLineArgumentsMessageMapper) {
if (taskNameMessageMapper == null) {
@@ -92,8 +93,7 @@ public class TaskLaunchRequestFunctionConfiguration {
commandLineArgumentsMessageMapper = commandLineArgumentsMessageMapper(properties, evaluationContext);
}
return new TaskLaunchRequestMessageProcessor(taskLaunchRequestInitializer,
taskNameMessageMapper,
return new TaskLaunchRequestMessageProcessor(taskLaunchRequestInitializer, taskNameMessageMapper,
commandLineArgumentsMessageMapper);
}
@@ -103,11 +103,11 @@ public class TaskLaunchRequestFunctionConfiguration {
}
private TaskNameMessageMapper taskNameMessageMapper(TaskLaunchRequestFunctionProperties taskLaunchRequestProperties,
EvaluationContext evaluationContext) {
EvaluationContext evaluationContext) {
if (StringUtils.hasText(taskLaunchRequestProperties.getTaskNameExpression())) {
SpelExpressionParser expressionParser = new SpelExpressionParser();
Expression taskNameExpression = expressionParser
.parseExpression(taskLaunchRequestProperties.getTaskNameExpression());
.parseExpression(taskLaunchRequestProperties.getTaskNameExpression());
return new ExpressionEvaluatingTaskNameMessageMapper(taskNameExpression, evaluationContext);
}
@@ -122,23 +122,23 @@ public class TaskLaunchRequestFunctionConfiguration {
}
private static class TaskLaunchRequestPropertiesInitializer extends TaskLaunchRequestSupplier {
TaskLaunchRequestPropertiesInitializer(
TaskLaunchRequestFunctionProperties taskLaunchRequestProperties) {
this.commandLineArgumentSupplier(
() -> new ArrayList<>(taskLaunchRequestProperties.getArgs()));
TaskLaunchRequestPropertiesInitializer(TaskLaunchRequestFunctionProperties taskLaunchRequestProperties) {
this.deploymentPropertiesSupplier(
() -> KeyValueListParser.parseCommaDelimitedKeyValuePairs(
taskLaunchRequestProperties.getDeploymentProperties()));
this.commandLineArgumentSupplier(() -> new ArrayList<>(taskLaunchRequestProperties.getArgs()));
this.deploymentPropertiesSupplier(() -> KeyValueListParser
.parseCommaDelimitedKeyValuePairs(taskLaunchRequestProperties.getDeploymentProperties()));
this.taskNameSupplier(() -> taskLaunchRequestProperties.getTaskName());
}
}
private static class ExpressionEvaluatingTaskNameMessageMapper implements TaskNameMessageMapper {
private final Expression expression;
private final EvaluationContext evaluationContext;
ExpressionEvaluatingTaskNameMessageMapper(Expression expression, EvaluationContext evaluationContext) {
@@ -150,9 +150,11 @@ public class TaskLaunchRequestFunctionConfiguration {
public String processMessage(Message<?> message) {
return expression.getValue(evaluationContext, message).toString();
}
}
private static class ExpressionEvaluatingCommandLineArgsMapper implements CommandLineArgumentsMessageMapper {
private final Map<String, Expression> argExpressionsMap;
private final EvaluationContext evaluationContext;
@@ -163,8 +165,8 @@ public class TaskLaunchRequestFunctionConfiguration {
if (StringUtils.hasText(argExpressions)) {
SpelExpressionParser expressionParser = new SpelExpressionParser();
KeyValueListParser.parseCommaDelimitedKeyValuePairs(argExpressions).forEach(
(k, v) -> argExpressionsMap.put(k, expressionParser.parseExpression(v)));
KeyValueListParser.parseCommaDelimitedKeyValuePairs(argExpressions)
.forEach((k, v) -> argExpressionsMap.put(k, expressionParser.parseExpression(v)));
}
}
@@ -176,9 +178,10 @@ public class TaskLaunchRequestFunctionConfiguration {
private Collection<String> evaluateArgExpressions(Message<?> message) {
List<String> results = new LinkedList<>();
this.argExpressionsMap.forEach((k, expression) -> results
.add(String.format("%s=%s", k, expression.getValue(this.evaluationContext, message))));
.add(String.format("%s=%s", k, expression.getValue(this.evaluationContext, message))));
return results;
}
}
}

View File

@@ -57,9 +57,9 @@ public class TaskLaunchRequestFunctionProperties {
*/
private String taskName;
/**
* A SpEL expression to extract the task name from each Message, using the Message as the evaluation context.
* A SpEL expression to extract the task name from each Message, using the Message as
* the evaluation context.
*/
private String taskNameExpression;

View File

@@ -57,7 +57,7 @@ class TaskLaunchRequestMessageProcessor implements MessagePostProcessor {
taskLaunchRequest.addCommmandLineArguments(commandLineArgumentsMessageMapper.processMessage(message));
MessageBuilder<TaskLaunchRequest> builder = MessageBuilder.withPayload(taskLaunchRequest)
.copyHeaders(message.getHeaders());
.copyHeaders(message.getHeaders());
return adjustHeaders(builder).build();
}
@@ -65,4 +65,5 @@ class TaskLaunchRequestMessageProcessor implements MessagePostProcessor {
builder.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
return builder;
}
}

View File

@@ -64,4 +64,5 @@ class TaskLaunchRequestSupplier implements Supplier<TaskLaunchRequest> {
return taskLaunchRequest;
}
}

View File

@@ -20,4 +20,5 @@ import org.springframework.integration.handler.MessageProcessor;
@FunctionalInterface
public interface TaskNameMessageMapper extends MessageProcessor<String> {
}

View File

@@ -31,8 +31,8 @@ public class KeyValueListParserTests {
@Test
public void testParseSimpleDeploymentProperty() {
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value");
Map<String, String> deploymentProperties = KeyValueListParser
.parseCommaDelimitedKeyValuePairs("app.sftp.param=value");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 1);
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.param"));
@@ -41,8 +41,8 @@ public class KeyValueListParserTests {
@Test
public void testParseSimpleDeploymentPropertyMultipleValues() {
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value1,value2,value3");
Map<String, String> deploymentProperties = KeyValueListParser
.parseCommaDelimitedKeyValuePairs("app.sftp.param=value1,value2,value3");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 1);
@@ -55,8 +55,7 @@ public class KeyValueListParserTests {
Map<String, String> argExpressions = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"arg1=payload.substr(0,2),arg2=headers['foo'],arg3=headers['bar']==false");
assertTrue("Invalid number of deployment properties: " + argExpressions.size(),
argExpressions.size() == 3);
assertTrue("Invalid number of deployment properties: " + argExpressions.size(), argExpressions.size() == 3);
assertTrue("Expected deployment key not found", argExpressions.containsKey("arg1"));
assertEquals("Invalid deployment value", "payload.substr(0,2)", argExpressions.get("arg1"));
@@ -69,8 +68,8 @@ public class KeyValueListParserTests {
@Test
public void testParseMultipleDeploymentPropertiesSingleValue() {
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value1,app.sftp.other.param=value2");
Map<String, String> deploymentProperties = KeyValueListParser
.parseCommaDelimitedKeyValuePairs("app.sftp.param=value1,app.sftp.other.param=value2");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 2);
@@ -84,8 +83,8 @@ public class KeyValueListParserTests {
public void testParseMultipleDeploymentPropertiesMultipleValues() {
TaskLaunchRequestFunctionProperties taskLaunchRequestProperties = new TaskLaunchRequestFunctionProperties();
Map<String, String> deploymentProperties = KeyValueListParser.parseCommaDelimitedKeyValuePairs(
"app.sftp.param=value1,value2,app.sftp.other.param=other1,other2");
Map<String, String> deploymentProperties = KeyValueListParser
.parseCommaDelimitedKeyValuePairs("app.sftp.param=value1,value2,app.sftp.other.param=other1,other2");
assertTrue("Invalid number of deployment properties: " + deploymentProperties.size(),
deploymentProperties.size() == 2);
@@ -94,4 +93,5 @@ public class KeyValueListParserTests {
assertTrue("Expected deployment key not found", deploymentProperties.containsKey("app.sftp.other.param"));
assertEquals("Invalid deployment value", "other1,other2", deploymentProperties.get("app.sftp.other.param"));
}
}

View File

@@ -45,18 +45,17 @@ public class TaskLaunchRequestFunctionApplicationTests {
@BeforeEach
public void setUp() {
springApplicationBuilder = new SpringApplicationBuilder(TaskLaunchRequestFunctionTestApplication.class)
.web(WebApplicationType.NONE);
.web(WebApplicationType.NONE);
}
@Test
@DirtiesContext
public void simpleDataflowTaskLaunchRequest() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false",
"spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo")
.run();
TaskLaunchRequest taskLaunchRequest = verifyAndreceiveTaskLaunchRequest(context);
@@ -69,16 +68,15 @@ public class TaskLaunchRequestFunctionApplicationTests {
@DirtiesContext
public void dataflowTaskLaunchRequestWithArgsAndDeploymentProperties() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo", "task.launch.request.args=foo=bar,baz=boo",
"task.launch.request.deploymentProperties=count=3")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo", "task.launch.request.args=foo=bar,baz=boo",
"task.launch.request.deploymentProperties=count=3")
.run();
TaskLaunchRequest taskLaunchRequest = verifyAndreceiveTaskLaunchRequest(context);
assertThat(taskLaunchRequest.getTaskName()).isEqualTo("foo");
assertThat(taskLaunchRequest.getCommandlineArguments()).containsExactlyInAnyOrder("foo=bar",
"baz=boo");
assertThat(taskLaunchRequest.getCommandlineArguments()).containsExactlyInAnyOrder("foo=bar", "baz=boo");
assertThat(taskLaunchRequest.getDeploymentProperties()).containsOnly(entry("count", "3"));
}
@@ -86,10 +84,10 @@ public class TaskLaunchRequestFunctionApplicationTests {
@DirtiesContext
public void taskLaunchRequestWithCommandLineArgsMessageMapper() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo", "enhanceTLRArgs=true")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo", "enhanceTLRArgs=true")
.run();
TaskLaunchRequest taskLaunchRequest = verifyAndreceiveTaskLaunchRequest(context);
@@ -102,12 +100,11 @@ public class TaskLaunchRequestFunctionApplicationTests {
@Test
@DirtiesContext
public void taskLaunchRequestWithArgExpressions() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false",
"spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo",
"task.launch.request.arg-expressions=foo=payload.toUpperCase(),bar=payload.substring(0,2)")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo",
"task.launch.request.arg-expressions=foo=payload.toUpperCase(),bar=payload.substring(0,2)")
.run();
Message<String> message = MessageBuilder.withPayload("hello").build();
@@ -123,11 +120,10 @@ public class TaskLaunchRequestFunctionApplicationTests {
@Test
@DirtiesContext
public void taskLaunchRequestWithIntPayload() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo",
"task.launch.request.arg-expressions=i=payload")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name=foo", "task.launch.request.arg-expressions=i=payload")
.run();
TaskLaunchRequestFunction taskLaunchRequestFunction = context.getBean(TaskLaunchRequestFunction.class);
@@ -144,10 +140,10 @@ public class TaskLaunchRequestFunctionApplicationTests {
@Test
@DirtiesContext
public void taskNameExpression() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name-expression=payload+'_task'")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"task.launch.request.task-name-expression=payload+'_task'")
.run();
TaskLaunchRequestFunction taskLaunchRequestFunction = context.getBean(TaskLaunchRequestFunction.class);
@@ -163,10 +159,10 @@ public class TaskLaunchRequestFunctionApplicationTests {
@Test
@DirtiesContext
public void customTaskNameExtractor() throws IOException {
ApplicationContext context = springApplicationBuilder.properties(
"spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"customTaskNameExtractor=true")
.run();
ApplicationContext context = springApplicationBuilder
.properties("spring.jmx.enabled=false", "spring.cloud.function.definition=taskLaunchRequestFunction",
"customTaskNameExtractor=true")
.run();
TaskLaunchRequestFunction taskLaunchRequestFunction = context.getBean(TaskLaunchRequestFunction.class);
Message<String> message = MessageBuilder.withPayload("foo").build();
@@ -184,11 +180,10 @@ public class TaskLaunchRequestFunctionApplicationTests {
assertThat(request.getTaskName()).isEqualTo("defaultTask");
}
private TaskLaunchRequest verifyAndreceiveTaskLaunchRequest(ApplicationContext context)
throws IOException {
private TaskLaunchRequest verifyAndreceiveTaskLaunchRequest(ApplicationContext context) throws IOException {
TaskLaunchRequestFunction taskLaunchRequestFunction = context.getBean(TaskLaunchRequestFunction.class);
Message<TaskLaunchRequest> message = taskLaunchRequestFunction
.apply(MessageBuilder.withPayload(new byte[] {}).build());
.apply(MessageBuilder.withPayload(new byte[] {}).build());
assertThat(message).isNotNull();
return message.getPayload();
}
@@ -207,5 +202,7 @@ public class TaskLaunchRequestFunctionApplicationTests {
CommandLineArgumentsMessageMapper commandLineArgumentsProvider() {
return message -> Collections.singletonList("runtimeArg");
}
}
}

View File

@@ -73,4 +73,5 @@ public class TaskLaunchRequestFunctionPropertiesTests {
static class Conf {
}
}

View File

@@ -36,7 +36,6 @@ import org.springframework.context.annotation.Import;
import org.springframework.messaging.Message;
/**
*
* @author Christian Tzolov
*/
@Configuration
@@ -103,11 +102,10 @@ public class TwitterGeoFunctionConfiguration {
}
@Bean
public Function<Message<?>, Message<byte[]>> twitterGeoFunction(
Function<Message<?>, GeoQuery> toGeoQuery,
Function<GeoQuery, List<Place>> places,
Function<Object, Message<byte[]>> managedJson) {
public Function<Message<?>, Message<byte[]>> twitterGeoFunction(Function<Message<?>, GeoQuery> toGeoQuery,
Function<GeoQuery, List<Place>> places, Function<Object, Message<byte[]>> managedJson) {
return toGeoQuery.andThen(places).andThen(managedJson)::apply;
}
}

View File

@@ -24,7 +24,6 @@ import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
/**
* @author Christian Tzolov
*/
@@ -35,8 +34,10 @@ public class TwitterGeoFunctionProperties {
private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload");
public enum GeoType {
/** Geo retrieval type. */
reverse, search
}
/**
@@ -56,22 +57,24 @@ public class TwitterGeoFunctionProperties {
private Location location = new Location();
/**
* Hints for the number of results to return. This does not guarantee that the number of results
* returned will equal max_results, but instead informs how many 'nearby' results to return.
* Hints for the number of results to return. This does not guarantee that the number
* of results returned will equal max_results, but instead informs how many 'nearby'
* results to return.
*/
private int maxResults = -1;
/**
* Sets a hint on the 'region' in which to search. If a number, then this is a radius in meters, but it
* can also take a string that is suffixed with ft to specify feet. If this is not passed in, then it is
* assumed to be 0m. If coming from a device, in practice, this value is whatever accuracy the device
* has measuring its location (whether it be coming from a GPS, WiFi triangulation, etc.).
* Sets a hint on the 'region' in which to search. If a number, then this is a radius
* in meters, but it can also take a string that is suffixed with ft to specify feet.
* If this is not passed in, then it is assumed to be 0m. If coming from a device, in
* practice, this value is whatever accuracy the device has measuring its location
* (whether it be coming from a GPS, WiFi triangulation, etc.).
*/
private String accuracy = null;
/**
* Minimal granularity of data to return. If this is not passed in, then neighborhood is assumed.
* City can also be passed.
* Minimal granularity of data to return. If this is not passed in, then neighborhood
* is assumed. City can also be passed.
*/
private String granularity = null;
@@ -125,7 +128,8 @@ public class TwitterGeoFunctionProperties {
@AssertTrue(message = "Either the IP or the Location must be set")
public boolean isAtLeastOne() {
return this.getSearch().getIp() == null ^ (this.getLocation().getLat() == null && this.getLocation().getLon() == null);
return this.getSearch().getIp() == null
^ (this.getLocation().getLat() == null && this.getLocation().getLon() == null);
}
@AssertTrue(message = "The IP parameter is applicable only for 'Search' GeoType")
@@ -137,9 +141,10 @@ public class TwitterGeoFunctionProperties {
}
public static class Search {
/**
* An IP address. Used when attempting to fix geolocation based off of the user's IP address.
* Applicable only for 'search' geo type.
* An IP address. Used when attempting to fix geolocation based off of the user's
* IP address. Applicable only for 'search' geo type.
*/
private Expression ip = null;
@@ -163,6 +168,7 @@ public class TwitterGeoFunctionProperties {
public void setQuery(Expression query) {
this.query = query;
}
}
public static class Location {
@@ -177,7 +183,6 @@ public class TwitterGeoFunctionProperties {
*/
private Expression lon;
public Expression getLat() {
return lat;
}
@@ -193,5 +198,7 @@ public class TwitterGeoFunctionProperties {
public void setLon(Expression lon) {
this.lon = lon;
}
}
}

View File

@@ -59,8 +59,8 @@ public class TwitterTrendFunctionConfiguration {
}
@Bean
public Function<Message<?>, List<Location>> closestOrAvailableTrends(
TwitterTrendFunctionProperties properties, Twitter twitter) {
public Function<Message<?>, List<Location>> closestOrAvailableTrends(TwitterTrendFunctionProperties properties,
Twitter twitter) {
return message -> {
try {
if (properties.getClosest().getLat() != null && properties.getClosest().getLon() != null) {
@@ -80,12 +80,12 @@ public class TwitterTrendFunctionConfiguration {
}
@Bean
public Function<Message<?>, Message<byte[]>> twitterTrendFunction(
Function<Object, Message<byte[]>> managedJson, Function<Message<?>, Trends> trend,
TwitterTrendFunctionProperties properties, Function<Message<?>,
List<Location>> closestOrAvailableTrends) {
public Function<Message<?>, Message<byte[]>> twitterTrendFunction(Function<Object, Message<byte[]>> managedJson,
Function<Message<?>, Trends> trend, TwitterTrendFunctionProperties properties,
Function<Message<?>, List<Location>> closestOrAvailableTrends) {
return (properties.getTrendQueryType() == TwitterTrendFunctionProperties.TrendQueryType.trend) ?
trend.andThen(managedJson) : closestOrAvailableTrends.andThen(managedJson);
return (properties.getTrendQueryType() == TwitterTrendFunctionProperties.TrendQueryType.trend)
? trend.andThen(managedJson) : closestOrAvailableTrends.andThen(managedJson);
}
}

View File

@@ -33,10 +33,12 @@ public class TwitterTrendFunctionProperties {
private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload");
enum TrendQueryType {
/** Retrieve trending places. */
trend,
/** Retrieve the Locations of trending places. */
trendLocation
}
private TrendQueryType trendQueryType = TrendQueryType.trend;
@@ -74,17 +76,18 @@ public class TwitterTrendFunctionProperties {
}
public static class Closest {
/**
* If provided with a long parameter the available trend locations will be sorted by distance, nearest
* to furthest, to the co-ordinate pair.
* The valid ranges for longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive.
* If provided with a long parameter the available trend locations will be sorted
* by distance, nearest to furthest, to the co-ordinate pair. The valid ranges for
* longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive.
*/
private Expression lat;
/**
* If provided with a lat parameter the available trend locations will be sorted by distance, nearest to
* furthest, to the co-ordinate pair. The valid ranges for longitude is -180.0 to +180.0 (West is negative,
* East is positive) inclusive.
* If provided with a lat parameter the available trend locations will be sorted
* by distance, nearest to furthest, to the co-ordinate pair. The valid ranges for
* longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive.
*/
private Expression lon;
@@ -103,5 +106,7 @@ public class TwitterTrendFunctionProperties {
public void setLon(Expression lon) {
this.lon = lon;
}
}
}

View File

@@ -35,7 +35,6 @@ import org.springframework.context.annotation.Import;
import org.springframework.messaging.Message;
/**
*
* @author Christian Tzolov
*/
@Configuration
@@ -88,11 +87,12 @@ public class TwitterUsersFunctionConfiguration {
@Bean
/**
* queryUsers - depends on the `twitter.users.type` property is either userSearch or userLookup.
* managedJson - converts Users into JSON message payload.
* queryUsers - depends on the `twitter.users.type` property is either userSearch or
* userLookup. managedJson - converts Users into JSON message payload.
*/
public Function<Message<?>, Message<byte[]>> twitterUsersFunction(Function<Message<?>, List<User>> queryUsers,
Function<Object, Message<byte[]>> managedJson) {
return queryUsers.andThen(managedJson);
}
}

View File

@@ -34,8 +34,10 @@ public class TwitterUsersFunctionProperties {
private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload");
public enum UserQueryType {
/** User retrieval types. */
search, lookup
}
/**
@@ -45,14 +47,14 @@ public class TwitterUsersFunctionProperties {
private UserQueryType type = UserQueryType.search;
/**
* Returns fully-hydrated user objects for specified by comma-separated values passed to the user_id and/or
* screen_name parameters.
* Returns fully-hydrated user objects for specified by comma-separated values passed
* to the user_id and/or screen_name parameters.
*/
private final Lookup lookup = new Lookup();
/**
* relevance-based search interface for querying by topical interest, full name, company name, location,
* or other criteria.
* relevance-based search interface for querying by topical interest, full name,
* company name, location, or other criteria.
*/
private final Search search = new Search();
@@ -85,6 +87,7 @@ public class TwitterUsersFunctionProperties {
}
public static class Lookup {
/**
* A comma separated list of user IDs, up to 100 are allowed in a single request.
* You are strongly encouraged to use a POST for larger requests.
@@ -92,8 +95,9 @@ public class TwitterUsersFunctionProperties {
private Expression userId;
/**
* A comma separated list of screen names, up to 100 are allowed in a single request.
* You are strongly encouraged to use a POST for larger (up to 100 screen names) requests.
* A comma separated list of screen names, up to 100 are allowed in a single
* request. You are strongly encouraged to use a POST for larger (up to 100 screen
* names) requests.
*/
private Expression screenName;
@@ -112,9 +116,11 @@ public class TwitterUsersFunctionProperties {
public void setScreenName(Expression screenName) {
this.screenName = screenName;
}
}
public static class Search {
/**
* The search query to run against people search.
*/
@@ -140,5 +146,7 @@ public class TwitterUsersFunctionProperties {
public void setPage(int page) {
this.page = page;
}
}
}

View File

@@ -58,14 +58,10 @@ import static org.mockserver.verify.VerificationTimes.once;
/**
* @author Christian Tzolov
*/
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = {
"twitter.connection.consumerKey=consumerKey666",
"twitter.connection.consumerSecret=consumerSecret666",
"twitter.connection.accessToken=accessToken666",
"twitter.connection.accessTokenSecret=accessTokenSecret666"
})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = { "twitter.connection.consumerKey=consumerKey666",
"twitter.connection.consumerSecret=consumerSecret666", "twitter.connection.accessToken=accessToken666",
"twitter.connection.accessTokenSecret=accessTokenSecret666" })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public abstract class TwitterGeoFunctionTest {
@@ -83,18 +79,12 @@ public abstract class TwitterGeoFunctionTest {
public static void recordRequestExpectation(Map<String, List<String>> parameters) {
mockClient
.when(
request()
.withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameters(parameters),
unlimited())
.respond(
response()
.withStatusCode(200)
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBody(TwitterTestUtils.asString("classpath:/response/search_places_amsterdam.json"))
.withDelay(TimeUnit.SECONDS, 1));
.when(request().withMethod("GET").withPath("/geo/search.json").withQueryStringParameters(parameters),
unlimited())
.respond(response().withStatusCode(200)
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBody(TwitterTestUtils.asString("classpath:/response/search_places_amsterdam.json"))
.withDelay(TimeUnit.SECONDS, 1));
}
@@ -109,10 +99,8 @@ public abstract class TwitterGeoFunctionTest {
mockServer.stop();
}
@TestPropertySource(properties = {
"twitter.geo.search.ip='127.0.0.1'",
"twitter.geo.search.query=payload.toUpperCase()"
})
@TestPropertySource(
properties = { "twitter.geo.search.ip='127.0.0.1'", "twitter.geo.search.query=payload.toUpperCase()" })
public static class TwitterGeoSearchByIPAndQueryTests extends TwitterGeoFunctionTest {
@Test
@@ -128,12 +116,10 @@ public abstract class TwitterGeoFunctionTest {
Message<?> received = twitterUsersFunction.apply(MessageBuilder.withPayload(inPayload).build());
mockClient.verify(request()
.withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("ip", "127.0.0.1")
.withQueryStringParameter("query", "AMSTERDAM"),
once());
mockClient.verify(request().withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("ip", "127.0.0.1")
.withQueryStringParameter("query", "AMSTERDAM"), once());
String outPayload = new String((byte[]) received.getPayload());
@@ -142,13 +128,11 @@ public abstract class TwitterGeoFunctionTest {
List places = new ObjectMapper().readValue(outPayload, List.class);
assertThat(places).hasSize(12);
}
}
@TestPropertySource(properties = {
"twitter.geo.location.lat='52.378'",
"twitter.geo.location.lon='4.9'",
"twitter.geo.search.query=payload.toUpperCase()"
})
@TestPropertySource(properties = { "twitter.geo.location.lat='52.378'", "twitter.geo.location.lon='4.9'",
"twitter.geo.search.query=payload.toUpperCase()" })
public static class TwitterGeoSearchByLocationTests extends TwitterGeoFunctionTest {
@Test
@@ -165,13 +149,11 @@ public abstract class TwitterGeoFunctionTest {
Message<?> received = twitterUsersFunction.apply(MessageBuilder.withPayload(inPayload).build());
mockClient.verify(request()
.withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("lat", "52.378")
.withQueryStringParameter("long", "4.9")
.withQueryStringParameter("query", "Amsterdam"),
once());
mockClient.verify(request().withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("lat", "52.378")
.withQueryStringParameter("long", "4.9")
.withQueryStringParameter("query", "Amsterdam"), once());
String outPayload = new String((byte[]) received.getPayload());
@@ -180,13 +162,11 @@ public abstract class TwitterGeoFunctionTest {
List places = new ObjectMapper().readValue(outPayload, List.class);
assertThat(places).hasSize(12);
}
}
@TestPropertySource(properties = {
"twitter.geo.type=reverse",
"twitter.geo.location.lat='52.378'",
"twitter.geo.location.lon='4.9'"
})
@TestPropertySource(properties = { "twitter.geo.type=reverse", "twitter.geo.location.lat='52.378'",
"twitter.geo.location.lon='4.9'" })
public static class TwitterGeoSearchByLocation2Tests extends TwitterGeoFunctionTest {
@Test
@@ -202,12 +182,10 @@ public abstract class TwitterGeoFunctionTest {
Message<?> received = twitterUsersFunction.apply(MessageBuilder.withPayload(inPayload).build());
mockClient.verify(request()
.withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("lat", "52.378")
.withQueryStringParameter("long", "4.9"),
once());
mockClient.verify(request().withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("lat", "52.378")
.withQueryStringParameter("long", "4.9"), once());
String outPayload = new String((byte[]) received.getPayload());
@@ -216,13 +194,12 @@ public abstract class TwitterGeoFunctionTest {
List places = new ObjectMapper().readValue(outPayload, List.class);
assertThat(places).hasSize(12);
}
}
@TestPropertySource(properties = {
"twitter.geo.location.lat=#jsonPath(new String(payload),'$.location.lat')",
@TestPropertySource(properties = { "twitter.geo.location.lat=#jsonPath(new String(payload),'$.location.lat')",
"twitter.geo.location.lon=#jsonPath(new String(payload),'$.location.lon')",
"twitter.geo.search.query=#jsonPath(new String(payload),'$.country')"
})
"twitter.geo.search.query=#jsonPath(new String(payload),'$.country')" })
public static class TwitterGeoSearchJsonPathTests extends TwitterGeoFunctionTest {
@Test
@@ -237,18 +214,15 @@ public abstract class TwitterGeoFunctionTest {
String inPayload = "{ \"country\" : \"Netherlands\", \"location\" : { \"lat\" : 52.00 , \"lon\" : 5.0 } }";
Message<?> received = twitterUsersFunction.apply(MessageBuilder
.withPayload(inPayload)
.setHeader("contentType", MimeTypeUtils.APPLICATION_JSON_VALUE)
.build());
Message<?> received = twitterUsersFunction.apply(MessageBuilder.withPayload(inPayload)
.setHeader("contentType", MimeTypeUtils.APPLICATION_JSON_VALUE)
.build());
mockClient.verify(request()
.withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("lat", "52.0")
.withQueryStringParameter("long", "5.0")
.withQueryStringParameter("query", "Netherlands"),
once());
mockClient.verify(request().withMethod("GET")
.withPath("/geo/search.json")
.withQueryStringParameter("lat", "52.0")
.withQueryStringParameter("long", "5.0")
.withQueryStringParameter("query", "Netherlands"), once());
String outPayload = new String((byte[]) received.getPayload());
@@ -257,23 +231,26 @@ public abstract class TwitterGeoFunctionTest {
List places = new ObjectMapper().readValue(outPayload, List.class);
assertThat(places).hasSize(12);
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@Import(TwitterGeoFunctionConfiguration.class)
public static class TwitterGeoFunctionTestApplication {
@Bean
@Primary
public twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionProperties properties,
Function<TwitterConnectionProperties, ConfigurationBuilder> toConfigurationBuilder) {
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration =
toConfigurationBuilder.andThen(
new TwitterTestUtils().mockTwitterUrls(
String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration = toConfigurationBuilder
.andThen(new TwitterTestUtils()
.mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
return mockedConfiguration.apply(properties).build();
}
}
}

View File

@@ -52,14 +52,10 @@ import static org.mockserver.verify.VerificationTimes.once;
/**
* @author Christian Tzolov
*/
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = {
"twitter.connection.consumerKey=consumerKey666",
"twitter.connection.consumerSecret=consumerSecret666",
"twitter.connection.accessToken=accessToken666",
"twitter.connection.accessTokenSecret=accessTokenSecret666"
})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = { "twitter.connection.consumerKey=consumerKey666",
"twitter.connection.consumerSecret=consumerSecret666", "twitter.connection.accessToken=accessToken666",
"twitter.connection.accessTokenSecret=accessTokenSecret666" })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public abstract class TwitterTrendFunctionTests {
@@ -70,6 +66,7 @@ public abstract class TwitterTrendFunctionTests {
private static ClientAndServer mockServer;
private static MockServerClient mockClient;
private static HttpRequest trendsRequest;
@Autowired
@@ -80,10 +77,8 @@ public abstract class TwitterTrendFunctionTests {
mockServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT);
mockClient = new MockServerClient(MOCK_SERVER_IP, MOCK_SERVER_PORT);
trendsRequest = setExpectation(request()
.withMethod("GET")
.withPath("/trends/place.json")
.withQueryStringParameter("id", "2972"));
trendsRequest = setExpectation(
request().withMethod("GET").withPath("/trends/place.json").withQueryStringParameter("id", "2972"));
}
@AfterAll
@@ -92,23 +87,16 @@ public abstract class TwitterTrendFunctionTests {
}
public static HttpRequest setExpectation(HttpRequest request) {
mockClient
.when(request, exactly(1))
.respond(response()
.withStatusCode(200)
.withHeaders(
new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400"))
.withBody(TwitterTestUtils.asString("classpath:/response/trends.json"))
.withDelay(TimeUnit.SECONDS, 1)
);
mockClient.when(request, exactly(1))
.respond(response().withStatusCode(200)
.withHeaders(new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400"))
.withBody(TwitterTestUtils.asString("classpath:/response/trends.json"))
.withDelay(TimeUnit.SECONDS, 1));
return request;
}
@TestPropertySource(properties = {
"twitter.trend.locationId='2972'",
"twitter.connection.rawJson=true"
})
@TestPropertySource(properties = { "twitter.trend.locationId='2972'", "twitter.connection.rawJson=true" })
public static class TwitterTrendPayloadTests extends TwitterTrendFunctionTests {
@Test
@@ -117,24 +105,26 @@ public abstract class TwitterTrendFunctionTests {
mockClient.verify(trendsRequest, once());
assertThat(received).isNotNull();
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@Import(TwitterTrendFunctionConfiguration.class)
public static class TwitterTrendFunctionTestApplication {
@Bean
@Primary
public twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionProperties properties,
Function<TwitterConnectionProperties, ConfigurationBuilder> toConfigurationBuilder) {
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration =
toConfigurationBuilder.andThen(
new TwitterTestUtils().mockTwitterUrls(
String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration = toConfigurationBuilder
.andThen(new TwitterTestUtils()
.mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
return mockedConfiguration.apply(properties).build();
}
}
}

View File

@@ -55,14 +55,10 @@ import static org.mockserver.verify.VerificationTimes.once;
/**
* @author Christian Tzolov
*/
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = {
"twitter.connection.consumerKey=consumerKey666",
"twitter.connection.consumerSecret=consumerSecret666",
"twitter.connection.accessToken=accessToken666",
"twitter.connection.accessTokenSecret=accessTokenSecret666"
})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = { "twitter.connection.consumerKey=consumerKey666",
"twitter.connection.consumerSecret=consumerSecret666", "twitter.connection.accessToken=accessToken666",
"twitter.connection.accessTokenSecret=accessTokenSecret666" })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public abstract class TwitterUsersFunctionTests {
@@ -73,8 +69,11 @@ public abstract class TwitterUsersFunctionTests {
private static ClientAndServer mockServer;
private static MockServerClient mockClient;
private static HttpRequest searchUsersRequest;
private static HttpRequest lookupUsersRequest;
private static HttpRequest lookupUsersRequest2;
@Autowired
@@ -84,16 +83,12 @@ public abstract class TwitterUsersFunctionTests {
Function<Message<?>, Message<byte[]>> twitterUsersFunction;
public static HttpRequest setExpectation(HttpRequest request, String responseUri) {
mockClient
.when(request, exactly(1))
.respond(response()
.withStatusCode(200)
.withHeaders(
new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400"))
.withBody(TwitterTestUtils.asString(responseUri))
.withDelay(TimeUnit.SECONDS, 1)
);
mockClient.when(request, exactly(1))
.respond(response().withStatusCode(200)
.withHeaders(new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400"))
.withBody(TwitterTestUtils.asString(responseUri))
.withDelay(TimeUnit.SECONDS, 1));
return request;
}
@@ -102,25 +97,20 @@ public abstract class TwitterUsersFunctionTests {
mockServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT);
mockClient = new MockServerClient(MOCK_SERVER_IP, MOCK_SERVER_PORT);
searchUsersRequest = setExpectation(request()
.withMethod("GET")
.withPath("/users/search.json")
.withQueryStringParameter("q", "tzolov")
.withQueryStringParameter("page", "3"),
"classpath:/response/search_users.json");
searchUsersRequest = setExpectation(request().withMethod("GET")
.withPath("/users/search.json")
.withQueryStringParameter("q", "tzolov")
.withQueryStringParameter("page", "3"), "classpath:/response/search_users.json");
lookupUsersRequest = setExpectation(request()
.withMethod("GET")
.withPath("/users/lookup.json")
.withQueryStringParameter("user_id",
"710705860343963648,326896547,267603736,781497571629989888,838754923"),
lookupUsersRequest = setExpectation(request().withMethod("GET")
.withPath("/users/lookup.json")
.withQueryStringParameter("user_id", "710705860343963648,326896547,267603736,781497571629989888,838754923"),
"classpath:/response/lookup_users_id.json");
lookupUsersRequest2 = setExpectation(request()
.withMethod("GET")
.withPath("/users/lookup.json")
.withQueryStringParameter("screen_name",
"TzolovMarto,Rabotnik57,antzolov,peyo_tzolov,ivantzolov"),
lookupUsersRequest2 = setExpectation(
request().withMethod("GET")
.withPath("/users/lookup.json")
.withQueryStringParameter("screen_name", "TzolovMarto,Rabotnik57,antzolov,peyo_tzolov,ivantzolov"),
"classpath:/response/lookup_users_id.json");
}
@@ -129,10 +119,7 @@ public abstract class TwitterUsersFunctionTests {
mockServer.stop();
}
@TestPropertySource(properties = {
"twitter.users.type=search",
"twitter.users.search.query=payload"
})
@TestPropertySource(properties = { "twitter.users.type=search", "twitter.users.search.query=payload" })
public static class TwitterSearchUsersTests extends TwitterUsersFunctionTests {
@Test
@@ -143,12 +130,11 @@ public abstract class TwitterUsersFunctionTests {
List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class);
assertThat(list).hasSize(20);
}
}
@TestPropertySource(properties = {
"twitter.users.type=lookup",
"twitter.users.lookup.userId='710705860343963648,326896547,267603736,781497571629989888,838754923'"
})
@TestPropertySource(properties = { "twitter.users.type=lookup",
"twitter.users.lookup.userId='710705860343963648,326896547,267603736,781497571629989888,838754923'" })
public static class TwitterLookupUserIdLiteralTests extends TwitterUsersFunctionTests {
@Test
@@ -161,12 +147,11 @@ public abstract class TwitterUsersFunctionTests {
List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class);
assertThat(list).hasSize(5);
}
}
@TestPropertySource(properties = {
"twitter.users.type=lookup",
"twitter.users.lookup.screenName=#jsonPath(payload,'$[*].code')"
})
@TestPropertySource(properties = { "twitter.users.type=lookup",
"twitter.users.lookup.screenName=#jsonPath(payload,'$[*].code')" })
public static class TwitterLookupScreenNamePayloadTests extends TwitterUsersFunctionTests {
@Test
@@ -182,23 +167,26 @@ public abstract class TwitterUsersFunctionTests {
List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class);
assertThat(list).hasSize(5);
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@Import(TwitterUsersFunctionConfiguration.class)
static class TwitterUsersFunctionTestApplication {
@Bean
@Primary
public twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionProperties properties,
Function<TwitterConnectionProperties, ConfigurationBuilder> toConfigurationBuilder) {
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration =
toConfigurationBuilder.andThen(
new TwitterTestUtils().mockTwitterUrls(
String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration = toConfigurationBuilder
.andThen(new TwitterTestUtils()
.mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
return mockedConfiguration.apply(properties).build();
}
}
}