├── project ├── build.properties ├── sbt-updates.sbt ├── sbt-lagom.sbt ├── sbt-native-packager.sbt ├── sbt-protobuf.sbt ├── scalabp.sbt ├── sbt-dependency-graph.sbt ├── sbt-conductr.sbt └── Dependencies.scala ├── version.sbt ├── it ├── src │ └── main │ │ ├── resources │ │ ├── images │ │ │ ├── beer.jpg │ │ │ ├── cake.jpg │ │ │ ├── laptop.png │ │ │ └── salad.jpg │ │ ├── it.conf │ │ ├── log4j.properties │ │ └── simplelogger.properties │ │ └── scala │ │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── it │ │ └── v100 │ │ └── Main.scala └── build.sbt ├── dashboard ├── src │ ├── main │ │ ├── protobuf │ │ │ └── org │ │ │ │ └── eigengo │ │ │ │ └── rsa │ │ │ │ └── dashboard │ │ │ │ └── internal.proto │ │ ├── scala │ │ │ └── org │ │ │ │ └── eigengo │ │ │ │ └── rsa │ │ │ │ ├── dashboard │ │ │ │ └── v100 │ │ │ │ │ ├── StaticFilesService.scala │ │ │ │ │ ├── DashboardService.scala │ │ │ │ │ ├── Main.scala │ │ │ │ │ ├── SummaryActor.scala │ │ │ │ │ ├── DashboardSinkActor.scala │ │ │ │ │ └── HandleSummaryItemsBuilder.scala │ │ │ │ └── ScalaPBMarshalling.scala │ │ └── resources │ │ │ ├── log4j.properties │ │ │ ├── logback.xml │ │ │ ├── simplelogger.properties │ │ │ └── dashboard.conf │ └── test │ │ ├── resources │ │ └── dashboard-test.conf │ │ └── scala │ │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── dashboard │ │ └── v100 │ │ ├── SummaryActorTest.scala │ │ └── HandleSummaryItemsBuilderTest.scala ├── build.sbt └── webapp │ ├── css │ └── base.css │ └── index.html ├── vision-identity ├── src │ ├── test │ │ ├── resources │ │ │ ├── salad.jpg │ │ │ ├── dogface.jpg │ │ │ ├── impostor.jpg │ │ │ ├── verified.jpg │ │ │ └── jonas boner.jpg │ │ └── scala │ │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── identity │ │ │ └── v100 │ │ │ ├── IdentityMatcherTest.scala │ │ │ └── FaceExtractorTest.scala │ ├── main │ │ ├── resources │ │ │ ├── log4j.properties │ │ │ ├── logback.xml │ │ │ ├── simplelogger.properties │ │ │ └── vision-identity.conf │ │ ├── protobuf │ │ │ └── org │ │ │ │ └── eigengo │ │ │ │ └── rsa │ │ │ │ └── identity │ │ │ │ └── internal.proto │ │ └── scala │ │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── identity │ │ │ └── v100 │ │ │ ├── Main.scala │ │ │ ├── IdentityMatcher.scala │ │ │ ├── FaceExtractor.scala │ │ │ ├── IdentityMatcherActorSupervisor.scala │ │ │ └── IdentityMatcherActor.scala │ └── train │ │ ├── scala │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── identity │ │ │ └── v100 │ │ │ ├── FacePreprocessor.scala │ │ │ ├── PreprocessingPipeline.scala │ │ │ ├── Preprocessor.scala │ │ │ └── FaceTrainer.scala │ │ └── java │ │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── identity │ │ └── v100 │ │ ├── DeepFaceVariant.java │ │ └── AlexNet.java └── build.sbt ├── vision-scene-classification ├── src │ ├── test │ │ ├── resources │ │ │ └── scene │ │ │ │ ├── beer.jpg │ │ │ │ ├── cake.jpg │ │ │ │ ├── salad.jpg │ │ │ │ └── laptop.jpg │ │ └── scala │ │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── scene │ │ │ └── v100 │ │ │ └── SceneClassifierTest.scala │ └── main │ │ ├── resources │ │ ├── log4j.properties │ │ ├── vision-scene.conf │ │ ├── logback.xml │ │ └── simplelogger.properties │ │ └── scala │ │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── scene │ │ └── v100 │ │ ├── Main.scala │ │ ├── SceneClassifier.scala │ │ └── SceneClassifierActor.scala └── build.sbt ├── linter-plugin └── src │ └── main │ ├── resources │ └── scalac-plugin.xml │ └── scala │ └── org │ └── eigengo │ └── rsa │ └── linter │ └── MarshallerLinterPlugin.scala ├── protocol └── src │ └── main │ └── resources │ └── org │ └── eigengo │ └── rsa │ ├── text │ └── v100.proto │ ├── scene │ └── v100.proto │ ├── envelope.proto │ ├── identity │ └── v100.proto │ └── dashboard │ └── v100.proto ├── protobuf-testkit └── src │ ├── test │ └── resources │ │ └── org │ │ └── eigengo │ │ └── protobufcheck │ │ ├── v100.proto │ │ ├── v101.proto │ │ └── v200.proto │ └── main │ └── scala │ └── org │ └── eigengo │ └── protobufcheck │ ├── ProtobufMatchers.scala │ └── ProtobufGen.scala ├── scalapb-akka-serializer └── src │ ├── main │ ├── protobuf │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── serialization │ │ │ └── internal.proto │ └── scala │ │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── serialization │ │ └── ScalaPBSerializer.scala │ └── test │ └── scala │ └── org │ └── eigengo │ └── rsa │ └── serialization │ └── ScalaPBSerializerTest.scala ├── ingest └── src │ ├── main │ ├── protobuf │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── ingest │ │ │ └── v100 │ │ │ └── internal.proto │ ├── scala │ │ └── org │ │ │ └── eigengo │ │ │ └── rsa │ │ │ └── ingest │ │ │ └── v100 │ │ │ ├── SimplifiedTweetFormat.scala │ │ │ ├── SimplifiedTweetProcessorActor.scala │ │ │ └── Main.scala │ └── resources │ │ └── ingest.conf │ └── test │ ├── scala │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── ingest │ │ └── v100 │ │ └── SimplifiedTweetParserTest.scala │ └── resources │ └── testing.json ├── run.sh ├── fat-it └── src │ └── main │ ├── resources │ ├── log4j.properties │ └── simplelogger.properties │ └── scala │ └── org │ └── eigengo │ └── rsa │ └── v100 │ └── LocalMain.scala ├── provisioning ├── all.template ├── ck.yaml └── it.yaml ├── .gitignore ├── deeplearning4j-common └── src │ ├── test │ └── scala │ │ └── org │ │ └── eigengo │ │ └── rsa │ │ └── deeplearning4j │ │ └── NetworkLoaderTest.scala │ └── main │ └── scala │ └── org │ └── eigengo │ └── rsa │ └── deeplearning4j │ └── NetworkLoader.scala └── vision-text └── src └── main ├── java └── org │ └── eigengo │ └── rsa │ └── text │ └── v100 │ ├── TweetImageEvent.java │ ├── server │ └── Module.java │ ├── TextService.java │ ├── TweetImageService.java │ ├── TweetImageServiceImpl.java │ ├── TextServiceImpl.java │ └── TextEntity.java ├── resources └── application.conf └── scala └── org └── eigengo └── rsa └── text └── v100 └── ScalaPBMessageSerializer.scala /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.11 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "1.0-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /project/sbt-updates.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") 2 | -------------------------------------------------------------------------------- /project/sbt-lagom.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.2.0-RC1") 2 | -------------------------------------------------------------------------------- /project/sbt-native-packager.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.4") 2 | -------------------------------------------------------------------------------- /project/sbt-protobuf.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.trueaccord.scalapb" % "sbt-scalapb" % "0.5.38") 2 | 3 | -------------------------------------------------------------------------------- /project/scalabp.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies ++= Seq( 2 | "com.github.os72" % "protoc-jar" % "3.0.0" 3 | ) -------------------------------------------------------------------------------- /project/sbt-dependency-graph.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2") 2 | -------------------------------------------------------------------------------- /it/src/main/resources/images/beer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/it/src/main/resources/images/beer.jpg -------------------------------------------------------------------------------- /it/src/main/resources/images/cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/it/src/main/resources/images/cake.jpg -------------------------------------------------------------------------------- /it/src/main/resources/images/laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/it/src/main/resources/images/laptop.png -------------------------------------------------------------------------------- /it/src/main/resources/images/salad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/it/src/main/resources/images/salad.jpg -------------------------------------------------------------------------------- /dashboard/src/main/protobuf/org/eigengo/rsa/dashboard/internal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.dashboard.v100; 4 | 5 | -------------------------------------------------------------------------------- /vision-identity/src/test/resources/salad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-identity/src/test/resources/salad.jpg -------------------------------------------------------------------------------- /vision-identity/src/test/resources/dogface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-identity/src/test/resources/dogface.jpg -------------------------------------------------------------------------------- /vision-identity/src/test/resources/impostor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-identity/src/test/resources/impostor.jpg -------------------------------------------------------------------------------- /vision-identity/src/test/resources/verified.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-identity/src/test/resources/verified.jpg -------------------------------------------------------------------------------- /vision-identity/src/test/resources/jonas boner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-identity/src/test/resources/jonas boner.jpg -------------------------------------------------------------------------------- /project/sbt-conductr.sbt: -------------------------------------------------------------------------------- 1 | // addSbtPlugin("com.lightbend.conductr" % "sbt-conductr" % "2.1.7") 2 | addSbtPlugin("com.typesafe.conductr" % "sbt-conductr-sandbox" % "1.4.3") 3 | -------------------------------------------------------------------------------- /vision-scene-classification/src/test/resources/scene/beer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-scene-classification/src/test/resources/scene/beer.jpg -------------------------------------------------------------------------------- /vision-scene-classification/src/test/resources/scene/cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-scene-classification/src/test/resources/scene/cake.jpg -------------------------------------------------------------------------------- /vision-scene-classification/src/test/resources/scene/salad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-scene-classification/src/test/resources/scene/salad.jpg -------------------------------------------------------------------------------- /it/src/main/resources/it.conf: -------------------------------------------------------------------------------- 1 | tweet-image-producer { 2 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 3 | topic = "tweet-image" 4 | } 5 | 6 | akka { 7 | loglevel = "INFO" 8 | } 9 | -------------------------------------------------------------------------------- /linter-plugin/src/main/resources/scalac-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | marshallerLinter 3 | org.eigengo.rsa.linter.MarshallerLinterPlugin 4 | 5 | -------------------------------------------------------------------------------- /protocol/src/main/resources/org/eigengo/rsa/text/v100.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.text.v100; 4 | 5 | message Text { 6 | repeated string areas = 1; 7 | } 8 | -------------------------------------------------------------------------------- /vision-scene-classification/src/test/resources/scene/laptop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigengo/reactive-summit-2016/HEAD/vision-scene-classification/src/test/resources/scene/laptop.jpg -------------------------------------------------------------------------------- /protobuf-testkit/src/test/resources/org/eigengo/protobufcheck/v100.proto: -------------------------------------------------------------------------------- 1 | package org.eigengo.protobufcheck.v100; 2 | 3 | message Caption { 4 | required string text = 1; 5 | required double accuracy = 2; 6 | } 7 | -------------------------------------------------------------------------------- /scalapb-akka-serializer/src/main/protobuf/org/eigengo/rsa/serialization/internal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.serialization; 4 | 5 | message SomeMessage { 6 | int32 version = 1; 7 | string text = 2; 8 | } 9 | -------------------------------------------------------------------------------- /protobuf-testkit/src/test/resources/org/eigengo/protobufcheck/v101.proto: -------------------------------------------------------------------------------- 1 | package org.eigengo.protobufcheck.v101; 2 | 3 | message Caption { 4 | required string text = 1; 5 | required double accuracy = 2; 6 | optional bool newField = 3; 7 | } 8 | -------------------------------------------------------------------------------- /protocol/src/main/resources/org/eigengo/rsa/scene/v100.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.scene.v100; 4 | 5 | message Scene { 6 | message Label { 7 | string label = 1; 8 | double score = 2; 9 | } 10 | repeated Label labels = 3; 11 | } 12 | -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/dashboard/v100/StaticFilesService.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.dashboard.v100 2 | 3 | import akka.http.scaladsl.server.{Directives, Route} 4 | 5 | trait StaticFilesService extends Directives { 6 | 7 | def staticFilesRoute: Route = getFromResourceDirectory("/src/main/webapp") 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ingest/src/main/protobuf/org/eigengo/rsa/ingest/v100/internal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.ingest.v100; 4 | 5 | message SimplifiedTweet { 6 | string handle = 1; 7 | repeated string mediaUrls = 2; 8 | } 9 | 10 | message TweetImage { 11 | string handle = 1; 12 | bytes content = 2; 13 | } 14 | -------------------------------------------------------------------------------- /it/build.sbt: -------------------------------------------------------------------------------- 1 | mainClass in Compile := Some("org.eigengo.rsa.it.v100.Main") 2 | 3 | mappings in Universal <++= (packageBin in Compile, sourceDirectory) map { (_, src) => 4 | packageMapping( 5 | (src / "main" / "resources") -> "conf" 6 | ).withContents().mappings.toSeq 7 | } 8 | 9 | enablePlugins(JavaServerAppPackaging, DockerPlugin) 10 | -------------------------------------------------------------------------------- /it/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # prepare fresh virtual environment 4 | ENV=.conductr 5 | rm -rf $ENV 6 | 7 | # Ensure that venv is ready 8 | python3 -m venv $ENV 9 | 10 | # switch to this venv 11 | . $ENV/bin/activate 12 | 13 | # Install the conductor-cli tooling 14 | pip3 install conductr-cli 15 | 16 | # Load the cassandra bundle 17 | # conduct load -v cassandra --api-version 1 18 | -------------------------------------------------------------------------------- /dashboard/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /fat-it/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /vision-identity/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /protocol/src/main/resources/org/eigengo/rsa/envelope.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa; 4 | 5 | message Envelope { 6 | int32 version = 1; 7 | int64 processingTimestamp = 2; 8 | int64 ingestionTimestamp = 3; 9 | string handle = 5; 10 | string correlationId = 6; 11 | string messageId = 7; 12 | string messageType = 8; 13 | bytes payload = 9; 14 | } 15 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /dashboard/build.sbt: -------------------------------------------------------------------------------- 1 | mainClass in Compile := Some("org.eigengo.rsa.dashboard.v100.Main") 2 | 3 | mappings in Universal <++= (packageBin in Compile, sourceDirectory) map { (_, src) => 4 | packageMapping( 5 | (src / "main" / "resources") -> "conf" 6 | ).withContents().mappings.toSeq 7 | } 8 | 9 | enablePlugins(JavaServerAppPackaging, DockerPlugin) 10 | 11 | unmanagedResourceDirectories in Compile += baseDirectory.value / "webapp" 12 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/resources/vision-scene.conf: -------------------------------------------------------------------------------- 1 | app { 2 | kafka { 3 | consumer-config { 4 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 5 | group.id = "vision-scene-v100" 6 | auto.offset.reset = "earliest" 7 | } 8 | 9 | scene-producer { 10 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 11 | topic = "scene" 12 | } 13 | 14 | } 15 | } 16 | 17 | akka.loglevel = "INFO" 18 | -------------------------------------------------------------------------------- /protocol/src/main/resources/org/eigengo/rsa/identity/v100.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.identity.v100; 4 | 5 | message Identity { 6 | 7 | oneof face { 8 | IdentifiedFace identifiedFace = 1; 9 | UnknownFace unknownFace = 2; 10 | } 11 | 12 | message IdentifiedFace { 13 | string name = 1; 14 | double score = 2; 15 | } 16 | 17 | message UnknownFace { 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /protobuf-testkit/src/test/resources/org/eigengo/protobufcheck/v200.proto: -------------------------------------------------------------------------------- 1 | package org.eigengo.protobufcheck.v200; 2 | 3 | message Caption { 4 | required string text = 1; 5 | repeated int32 ints = 2; 6 | repeated Item items = 3; 7 | 8 | message Item { 9 | required double accuracy = 10; 10 | // oneof kind { 11 | // string namedPerson = 100; 12 | // string category = 101; 13 | // } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /provisioning/all.template: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | cassandra: 5 | image: cassandra:3 6 | 7 | zookeeper: 8 | image: wurstmeister/zookeeper 9 | 10 | kafka: 11 | image: wurstmeister/kafka 12 | 13 | it: 14 | image: eigengo/rsa-it:latest 15 | 16 | dashboard: 17 | image: eigengo/rsa-dashboard:latest 18 | 19 | vision-identity: 20 | image: eigengo/rsa-vision-identity:latest 21 | 22 | vision-scene-classification: 23 | image: eigengo/rsa-vision-scene-classification:latest 24 | -------------------------------------------------------------------------------- /dashboard/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # We don't commit *any* of IntelliJ's files 2 | .idea/ 3 | .conductr 4 | 5 | .DS_Store 6 | 7 | 8 | # We don't want generated stuff 9 | generated 10 | 11 | # Created by https://www.gitignore.io/api/scalaj 12 | 13 | ### Scala ### 14 | *.class 15 | *.log 16 | 17 | # sbt specific 18 | .cache 19 | .history 20 | .lib/ 21 | dist/* 22 | target/ 23 | lib_managed/ 24 | src_managed/ 25 | project/boot/ 26 | project/plugins/project/ 27 | 28 | # Scala-IDE specific 29 | .scala_dependencies 30 | .worksheet 31 | 32 | ### Python ### 33 | *.pyc 34 | -------------------------------------------------------------------------------- /vision-identity/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /vision-identity/src/main/protobuf/org/eigengo/rsa/identity/internal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.identity.v100; 4 | 5 | message IdentifyFace { 6 | int64 ingestionTimestamp = 2; 7 | string correlationId = 3; 8 | string handle = 4; 9 | bytes image = 5; 10 | } 11 | 12 | message IdentifyFaces { 13 | repeated IdentifyFace identifyFaces = 1; 14 | } 15 | 16 | message FaceImage { 17 | double confidence = 1; 18 | int32 x = 2; 19 | int32 y = 3; 20 | int32 w = 4; 21 | int32 h = 5; 22 | bytes rgbBitmap = 6; 23 | } 24 | -------------------------------------------------------------------------------- /protocol/src/main/resources/org/eigengo/rsa/dashboard/v100.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.eigengo.rsa.dashboard.v100; 4 | 5 | message Summary { 6 | int32 totalHandles = 1; 7 | repeated HandleSummary topHandleSummaries = 2; 8 | } 9 | 10 | message HandleSummary { 11 | string handle = 1; 12 | repeated Item items = 2; 13 | 14 | message Item { 15 | int32 windowSize = 1; 16 | string description = 2; 17 | repeated Tweet tweets = 3; 18 | 19 | message Tweet { 20 | repeated bytes images = 1; 21 | string text = 2; 22 | // more stuff here 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dashboard/src/test/resources/dashboard-test.conf: -------------------------------------------------------------------------------- 1 | app { 2 | kafka { 3 | consumer-config { 4 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 5 | group.id = "dashboard" 6 | auto.offset.reset = "earliest" 7 | } 8 | } 9 | } 10 | 11 | akka.loglevel = "DEBUG" 12 | akka.persistence.journal.plugin = "inmemory-journal" 13 | akka.persistence.snapshot-store.plugin = "inmemory-snapshot-store" 14 | 15 | akka.actor { 16 | 17 | debug { 18 | # enable DEBUG logging of unhandled messages 19 | unhandled = on 20 | event-stream = on 21 | } 22 | 23 | serializers { 24 | spb = "org.eigengo.rsa.serialization.ScalaPBSerializer" 25 | } 26 | 27 | serialization-bindings { 28 | "com.trueaccord.scalapb.GeneratedMessage" = spb 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /ingest/src/main/scala/org/eigengo/rsa/ingest/v100/SimplifiedTweetFormat.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.ingest.v100 2 | 3 | import org.json4s.JsonAST.JString 4 | import org.json4s.JsonInput 5 | import org.json4s.jackson.JsonMethods 6 | 7 | import scala.util.Try 8 | 9 | trait SimplifiedTweetFormat { 10 | 11 | def parse(json: JsonInput): Try[SimplifiedTweet] = { 12 | Try { 13 | val jTweet = JsonMethods.parse(json) 14 | val JString(handle) = jTweet \ "user" \ "screen_name" 15 | val mediaUrls = (jTweet \ "entities" \ "media").filterField { case (f, _) ⇒ f == "media_url" }.map { case (_, JString(url)) ⇒ url } 16 | 17 | SimplifiedTweet(handle = handle, mediaUrls = mediaUrls) 18 | } 19 | } 20 | 21 | } 22 | 23 | object SimplifiedTweetFormat extends SimplifiedTweetFormat 24 | -------------------------------------------------------------------------------- /vision-identity/build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.packager.docker._ 2 | 3 | mainClass in Compile := Some("org.eigengo.rsa.identity.v100.Main") 4 | 5 | mappings in Universal <++= (packageBin in Compile, sourceDirectory) map { (_, src) => 6 | packageMapping( 7 | (src / "main" / "resources") -> "conf" 8 | ).withContents().mappings.toSeq 9 | } 10 | 11 | dockerCommands += Cmd("ADD", "http://rsa16-models.s3-website-eu-west-1.amazonaws.com/scene/labels /opt/models/identity/labels") 12 | dockerCommands += Cmd("ADD", "http://rsa16-models.s3-website-eu-west-1.amazonaws.com/scene/config /opt/models/identity/config") 13 | dockerCommands += Cmd("ADD", "http://rsa16-models.s3-website-eu-west-1.amazonaws.com/scene/params /opt/models/identity/params") 14 | 15 | enablePlugins(JavaServerAppPackaging, DockerPlugin) 16 | -------------------------------------------------------------------------------- /vision-scene-classification/build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.packager.docker._ 2 | 3 | mainClass in Compile := Some("org.eigengo.rsa.scene.v100.Main") 4 | 5 | mappings in Universal <++= (packageBin in Compile, sourceDirectory) map { (_, src) => 6 | packageMapping( 7 | (src / "main" / "resources") -> "conf" 8 | ).withContents().mappings.toSeq 9 | } 10 | 11 | dockerCommands += Cmd("ADD", "http://rsa16-models.s3-website-eu-west-1.amazonaws.com/scene/labels /opt/models/scene/labels") 12 | dockerCommands += Cmd("ADD", "http://rsa16-models.s3-website-eu-west-1.amazonaws.com/scene/config /opt/models/scene/config") 13 | dockerCommands += Cmd("ADD", "http://rsa16-models.s3-website-eu-west-1.amazonaws.com/scene/params /opt/models/scene/params") 14 | 15 | enablePlugins(JavaServerAppPackaging, DockerPlugin) 16 | -------------------------------------------------------------------------------- /dashboard/webapp/css/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | font-size: 15px; 5 | line-height: 1.7; 6 | margin: 0; 7 | padding: 30px; 8 | } 9 | 10 | #topHandleSummaries { 11 | width: 100%; 12 | padding: 0; 13 | margin: 0; 14 | overflow-x: scroll; 15 | white-space: nowrap; 16 | } 17 | 18 | #topHandleSummaries li { 19 | display: inline-block; 20 | width: 200pt; 21 | vertical-align: top; 22 | } 23 | 24 | ul.handleSummaryItems { 25 | background: #ccc; 26 | overflow-x: scroll; 27 | white-space: normal; 28 | padding: 0; 29 | } 30 | 31 | ul.handleSummaryItems li { 32 | padding-bottom: 2pt; 33 | border-bottom: 1px black solid; 34 | } 35 | 36 | div.description { 37 | font-style: italic; 38 | } 39 | -------------------------------------------------------------------------------- /ingest/src/test/scala/org/eigengo/rsa/ingest/v100/SimplifiedTweetParserTest.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.ingest.v100 2 | 3 | import org.scalatest.prop.PropertyChecks 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | import scala.io.Source 7 | import scala.util.Success 8 | 9 | class SimplifiedTweetParserTest extends FlatSpec with PropertyChecks with Matchers { 10 | 11 | it should "parse simplified JSON" in { 12 | val json = Source.fromInputStream(getClass.getResourceAsStream("/testing.json")).getLines().mkString 13 | val Success(simplifiedTweet) = SimplifiedTweetFormat.parse(json) 14 | simplifiedTweet.handle shouldBe "honzam399" 15 | simplifiedTweet.mediaUrls.length shouldBe 2 16 | simplifiedTweet.mediaUrls should contain ("http://pbs.twimg.com/media/Cso-f3PWgAApURT.jpg") 17 | simplifiedTweet.mediaUrls should contain ("http://pbs.twimg.com/media/Dso-f3PWgAApURT.jpg") 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /deeplearning4j-common/src/test/scala/org/eigengo/rsa/deeplearning4j/NetworkLoaderTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.deeplearning4j 20 | 21 | import java.io.FileNotFoundException 22 | 23 | import org.scalatest.prop.PropertyChecks 24 | import org.scalatest.{FlatSpec, Matchers} 25 | 26 | import scala.util.Failure 27 | 28 | class NetworkLoaderTest extends FlatSpec with PropertyChecks with Matchers { 29 | import NetworkLoader._ 30 | 31 | it should "report missing multilayer network files" in { 32 | val Failure(ex) = loadMultiLayerNetwork(_ ⇒ Failure(new FileNotFoundException("not there"))) 33 | ex should be (a[FileNotFoundException]) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/TweetImageEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100; 20 | 21 | import com.lightbend.lagom.javadsl.persistence.AggregateEvent; 22 | import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; 23 | import org.eigengo.rsa.Envelope; 24 | 25 | interface TweetImageEvent extends AggregateEvent { 26 | Envelope getEnvelope(); 27 | 28 | @Override 29 | default AggregateEventTag aggregateTag() { 30 | return TweetImageEventTag.INSTANCE; 31 | } 32 | } 33 | 34 | class TweetImageEventTag { 35 | static AggregateEventTag INSTANCE = AggregateEventTag.of(TweetImageEvent.class, "in"); 36 | } 37 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/scala/org/eigengo/rsa/scene/v100/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.scene.v100 20 | 21 | import akka.actor.ActorSystem 22 | import com.typesafe.config.{ConfigFactory, ConfigResolveOptions} 23 | 24 | object Main { 25 | 26 | def main(args: Array[String]): Unit = { 27 | Option(System.getenv("START_DELAY")).foreach(d ⇒ Thread.sleep(d.toInt)) 28 | 29 | val config = ConfigFactory.load("vision-scene.conf").resolve(ConfigResolveOptions.defaults()) 30 | val system = ActorSystem(name = "scene-classification-100", config = config) 31 | system.log.info("Scene 100 starting...") 32 | 33 | system.actorOf(SceneClassifierActor.props(config.getConfig("app"))) 34 | system.log.info("Scene 100 running.") 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /vision-identity/src/main/scala/org/eigengo/rsa/identity/v100/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import akka.actor.ActorSystem 22 | import com.typesafe.config.{ConfigFactory, ConfigResolveOptions} 23 | 24 | object Main { 25 | 26 | def main(args: Array[String]): Unit = { 27 | Option(System.getenv("START_DELAY")).foreach(d ⇒ Thread.sleep(d.toInt)) 28 | 29 | val config = ConfigFactory.load("vision-identity.conf").resolve(ConfigResolveOptions.defaults()) 30 | val system = ActorSystem(name = "identity-100", config = config) 31 | 32 | system.log.info(s"Identity 100 starting...") 33 | system.actorOf(IdentityMatcherActorSupervisor.props(config.getConfig("app"))) 34 | system.log.info(s"Identity 100 running.") 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/server/Module.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100.server; 20 | 21 | import com.google.inject.AbstractModule; 22 | import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; 23 | import org.eigengo.rsa.text.v100.TextService; 24 | import org.eigengo.rsa.text.v100.TextServiceImpl; 25 | import org.eigengo.rsa.text.v100.TweetImageService; 26 | import org.eigengo.rsa.text.v100.TweetImageServiceImpl; 27 | 28 | public class Module extends AbstractModule implements ServiceGuiceSupport { 29 | 30 | @Override 31 | protected void configure() { 32 | bindServices(serviceBinding(TweetImageService.class, TweetImageServiceImpl.class), serviceBinding(TextService.class, TextServiceImpl.class)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/dashboard/v100/DashboardService.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import akka.http.scaladsl.server.{Directives, PathMatchers, Route} 22 | import akka.stream.scaladsl.{Flow, Sink, Source} 23 | import org.eigengo.rsa.ScalaPBMarshalling 24 | 25 | import scala.concurrent.ExecutionContext 26 | 27 | trait DashboardService extends Directives with PathMatchers with ScalaPBMarshalling { 28 | 29 | def summarySource: Source[Summary, _] 30 | 31 | def dashboardRoute(implicit executionContext: ExecutionContext): Route = { 32 | path("dashboard") { 33 | get { 34 | handleWebSocketMessages(Flow.fromSinkAndSource(Sink.ignore, summarySource.map(x ⇒ marshalTextMessage(x)))) 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/TextService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100; 20 | 21 | import com.lightbend.lagom.javadsl.api.Descriptor; 22 | import com.lightbend.lagom.javadsl.api.Service; 23 | import com.lightbend.lagom.javadsl.api.broker.Topic; 24 | import org.eigengo.rsa.Envelope; 25 | 26 | import static com.lightbend.lagom.javadsl.api.Service.*; 27 | import static com.lightbend.lagom.javadsl.api.Service.topic; 28 | 29 | public interface TextService extends Service { 30 | 31 | Topic textTopic(); 32 | 33 | @Override 34 | default Descriptor descriptor() { 35 | return named("text") 36 | .publishing( 37 | topic("text", this::textTopic).withMessageSerializer(ScalaPBMessageSerializer.of(Envelope.messageCompanion())) 38 | ); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /provisioning/ck.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | zookeeper: 5 | extends: 6 | file: all.template 7 | service: zookeeper 8 | expose: 9 | - "2181" 10 | 11 | kafka: 12 | extends: 13 | file: all.template 14 | service: kafka 15 | ports: 16 | - "9092:9092" 17 | - "2181:2181" 18 | expose: 19 | - "9092" 20 | environment: 21 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 22 | KAFKA_ADVERTISED_HOST_NAME: "localhost" 23 | KAFKA_ADVERTISED_PORT: "9092" 24 | # 30 partitions, one replica; documentation is at 25 | # https://github.com/wurstmeister/kafka-docker#automatically-create-topics 26 | KAFKA_CREATE_TOPICS: "tweet-image:10:1,scene:10:1,identity:10:1,text:10:1" 27 | links: 28 | - zookeeper 29 | 30 | cassandra: 31 | extends: 32 | file: all.template 33 | service: cassandra 34 | ports: 35 | - "9042:9042" 36 | expose: 37 | - "9042" 38 | links: 39 | - kafka 40 | 41 | # 42 | # 43 | #version: '2' 44 | # 45 | #services: 46 | # kafka: 47 | # extends: 48 | # file: all.template 49 | # service: kafka 50 | # ports: 51 | # - "9092:9092" 52 | # - "2181:2181" 53 | # expose: 54 | # - "9092" 55 | # - "2181" 56 | # environment: 57 | # ADVERTISED_HOST: "localhost" 58 | # ADVERTISED_PORT: "9092" 59 | # NUM_PARTITIONS: 3 60 | # AUTO_CREATE_TOPICS: "true" 61 | # TOPICS: "tweet-image,scene,identity" 62 | # 63 | # cassandra: 64 | # extends: 65 | # file: all.template 66 | # service: cassandra 67 | # ports: 68 | # - "9042:9042" 69 | # expose: 70 | # - "9042" 71 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/TweetImageService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100; 20 | 21 | import com.lightbend.lagom.javadsl.api.Descriptor; 22 | import com.lightbend.lagom.javadsl.api.Service; 23 | import com.lightbend.lagom.javadsl.api.broker.Topic; 24 | import org.eigengo.rsa.Envelope; 25 | 26 | import static com.lightbend.lagom.javadsl.api.Service.*; 27 | import static com.lightbend.lagom.javadsl.api.Service.topic; 28 | 29 | public interface TweetImageService extends Service { 30 | 31 | Topic tweetImageTopic(); 32 | 33 | @Override 34 | default Descriptor descriptor() { 35 | return named("tweet-image") 36 | .publishing( 37 | topic("tweet-image", this::tweetImageTopic).withMessageSerializer(ScalaPBMessageSerializer.of(Envelope.messageCompanion())) 38 | ); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /it/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # SLF4J's SimpleLogger configuration file 2 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 3 | 4 | # Default logging detail level for all instances of SimpleLogger. 5 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 6 | # If not specified, defaults to "info". 7 | org.slf4j.simpleLogger.defaultLogLevel=info 8 | 9 | # Logging detail level for a SimpleLogger instance named "xxxxx". 10 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 11 | # If not specified, the default logging detail level is used. 12 | #org.slf4j.simpleLogger.log.xxxxx= 13 | 14 | # Set to true if you want the current date and time to be included in output messages. 15 | # Default is false, and will output the number of milliseconds elapsed since startup. 16 | org.slf4j.simpleLogger.showDateTime=true 17 | 18 | # The date and time format to be used in the output messages. 19 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 20 | # If the format is not specified or is invalid, the default format is used. 21 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 22 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z 23 | 24 | # Set to true if you want to output the current thread name. 25 | # Defaults to true. 26 | org.slf4j.simpleLogger.showThreadName=true 27 | 28 | # Set to true if you want the Logger instance name to be included in output messages. 29 | # Defaults to true. 30 | org.slf4j.simpleLogger.showLogName=true 31 | 32 | # Set to true if you want the last component of the name to be included in output messages. 33 | # Defaults to false. 34 | org.slf4j.simpleLogger.showShortLogName=false 35 | -------------------------------------------------------------------------------- /dashboard/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # SLF4J's SimpleLogger configuration file 2 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 3 | 4 | # Default logging detail level for all instances of SimpleLogger. 5 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 6 | # If not specified, defaults to "info". 7 | org.slf4j.simpleLogger.defaultLogLevel=info 8 | 9 | # Logging detail level for a SimpleLogger instance named "xxxxx". 10 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 11 | # If not specified, the default logging detail level is used. 12 | #org.slf4j.simpleLogger.log.xxxxx= 13 | 14 | # Set to true if you want the current date and time to be included in output messages. 15 | # Default is false, and will output the number of milliseconds elapsed since startup. 16 | org.slf4j.simpleLogger.showDateTime=true 17 | 18 | # The date and time format to be used in the output messages. 19 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 20 | # If the format is not specified or is invalid, the default format is used. 21 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 22 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z 23 | 24 | # Set to true if you want to output the current thread name. 25 | # Defaults to true. 26 | org.slf4j.simpleLogger.showThreadName=true 27 | 28 | # Set to true if you want the Logger instance name to be included in output messages. 29 | # Defaults to true. 30 | org.slf4j.simpleLogger.showLogName=true 31 | 32 | # Set to true if you want the last component of the name to be included in output messages. 33 | # Defaults to false. 34 | org.slf4j.simpleLogger.showShortLogName=false 35 | -------------------------------------------------------------------------------- /fat-it/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # SLF4J's SimpleLogger configuration file 2 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 3 | 4 | # Default logging detail level for all instances of SimpleLogger. 5 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 6 | # If not specified, defaults to "info". 7 | org.slf4j.simpleLogger.defaultLogLevel=info 8 | 9 | # Logging detail level for a SimpleLogger instance named "xxxxx". 10 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 11 | # If not specified, the default logging detail level is used. 12 | #org.slf4j.simpleLogger.log.xxxxx= 13 | 14 | # Set to true if you want the current date and time to be included in output messages. 15 | # Default is false, and will output the number of milliseconds elapsed since startup. 16 | org.slf4j.simpleLogger.showDateTime=true 17 | 18 | # The date and time format to be used in the output messages. 19 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 20 | # If the format is not specified or is invalid, the default format is used. 21 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 22 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z 23 | 24 | # Set to true if you want to output the current thread name. 25 | # Defaults to true. 26 | org.slf4j.simpleLogger.showThreadName=true 27 | 28 | # Set to true if you want the Logger instance name to be included in output messages. 29 | # Defaults to true. 30 | org.slf4j.simpleLogger.showLogName=true 31 | 32 | # Set to true if you want the last component of the name to be included in output messages. 33 | # Defaults to false. 34 | org.slf4j.simpleLogger.showShortLogName=false 35 | -------------------------------------------------------------------------------- /vision-identity/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # SLF4J's SimpleLogger configuration file 2 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 3 | 4 | # Default logging detail level for all instances of SimpleLogger. 5 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 6 | # If not specified, defaults to "info". 7 | org.slf4j.simpleLogger.defaultLogLevel=info 8 | 9 | # Logging detail level for a SimpleLogger instance named "xxxxx". 10 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 11 | # If not specified, the default logging detail level is used. 12 | #org.slf4j.simpleLogger.log.xxxxx= 13 | 14 | # Set to true if you want the current date and time to be included in output messages. 15 | # Default is false, and will output the number of milliseconds elapsed since startup. 16 | org.slf4j.simpleLogger.showDateTime=true 17 | 18 | # The date and time format to be used in the output messages. 19 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 20 | # If the format is not specified or is invalid, the default format is used. 21 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 22 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z 23 | 24 | # Set to true if you want to output the current thread name. 25 | # Defaults to true. 26 | org.slf4j.simpleLogger.showThreadName=true 27 | 28 | # Set to true if you want the Logger instance name to be included in output messages. 29 | # Defaults to true. 30 | org.slf4j.simpleLogger.showLogName=true 31 | 32 | # Set to true if you want the last component of the name to be included in output messages. 33 | # Defaults to false. 34 | org.slf4j.simpleLogger.showShortLogName=false 35 | -------------------------------------------------------------------------------- /vision-identity/src/test/scala/org/eigengo/rsa/identity/v100/IdentityMatcherTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import org.eigengo.rsa.deeplearning4j.NetworkLoader 22 | import org.scalatest.prop.PropertyChecks 23 | import org.scalatest.{FlatSpec, Matchers} 24 | 25 | import scala.util.Success 26 | 27 | class IdentityMatcherTest extends FlatSpec with PropertyChecks with Matchers { 28 | 29 | it should "should match known faces" in { 30 | // Expected to fail 31 | val Success(identityMatcher) = IdentityMatcher( 32 | NetworkLoader.fallbackResourceAccessor( 33 | NetworkLoader.filesystemResourceAccessor("/opt/models/identity"), 34 | NetworkLoader.filesystemResourceAccessor("/Users/janmachacek/Dropbox/Models/identity") 35 | ) 36 | ) 37 | 38 | val Some(jonas) = identityMatcher.identify(getClass.getResourceAsStream("/jonas boner.jpg")) 39 | jonas.name shouldBe "jonas boner" 40 | jonas.score should be > 0.3 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /fat-it/src/main/scala/org/eigengo/rsa/v100/LocalMain.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.v100 20 | 21 | import java.io.File 22 | 23 | import scala.io.Source 24 | 25 | object LocalMain { 26 | 27 | def main(args: Array[String]): Unit = { 28 | System.setProperty("KAFKA_BOOTSTRAP_SERVERS", "localhost:9092") 29 | System.setProperty("CASSANDRA_JOURNAL_CPS", "localhost:9042") 30 | System.setProperty("CASSANDRA_SNAPSHOT_CPS", "localhost:9042") 31 | Source.fromFile(new File(System.getProperty("user.home"), ".env/twitter-rsa")) 32 | .getLines() 33 | .foreach { line ⇒ 34 | val Array(k, v) = line.split("=") 35 | System.setProperty(k, v) 36 | } 37 | 38 | org.eigengo.rsa.ingest.v100.Main.main(args) 39 | org.eigengo.rsa.dashboard.v100.Main.main(args) 40 | org.eigengo.rsa.scene.v100.Main.main(args) 41 | org.eigengo.rsa.identity.v100.Main.main(args) 42 | 43 | Thread.sleep(30000) 44 | org.eigengo.rsa.it.v100.Main.main(args) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # SLF4J's SimpleLogger configuration file 2 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 3 | 4 | # Default logging detail level for all instances of SimpleLogger. 5 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 6 | # If not specified, defaults to "info". 7 | org.slf4j.simpleLogger.defaultLogLevel=info 8 | 9 | # Logging detail level for a SimpleLogger instance named "xxxxx". 10 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 11 | # If not specified, the default logging detail level is used. 12 | #org.slf4j.simpleLogger.log.xxxxx= 13 | 14 | # Set to true if you want the current date and time to be included in output messages. 15 | # Default is false, and will output the number of milliseconds elapsed since startup. 16 | org.slf4j.simpleLogger.showDateTime=true 17 | 18 | # The date and time format to be used in the output messages. 19 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 20 | # If the format is not specified or is invalid, the default format is used. 21 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 22 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z 23 | 24 | # Set to true if you want to output the current thread name. 25 | # Defaults to true. 26 | org.slf4j.simpleLogger.showThreadName=true 27 | 28 | # Set to true if you want the Logger instance name to be included in output messages. 29 | # Defaults to true. 30 | org.slf4j.simpleLogger.showLogName=true 31 | 32 | # Set to true if you want the last component of the name to be included in output messages. 33 | # Defaults to false. 34 | org.slf4j.simpleLogger.showShortLogName=false 35 | -------------------------------------------------------------------------------- /scalapb-akka-serializer/src/main/scala/org/eigengo/rsa/serialization/ScalaPBSerializer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.serialization 20 | 21 | import akka.actor.ExtendedActorSystem 22 | import akka.serialization.SerializerWithStringManifest 23 | import com.trueaccord.scalapb.{GeneratedMessage, GeneratedMessageCompanion, Message} 24 | 25 | class ScalaPBSerializer(system: ExtendedActorSystem) extends SerializerWithStringManifest { 26 | 27 | override val identifier: Int = 0xface0fb0 28 | 29 | override def manifest(o: AnyRef): String = o.getClass.getCanonicalName 30 | 31 | override def toBinary(o: AnyRef): Array[Byte] = o match { 32 | case g: GeneratedMessage ⇒ g.toByteArray 33 | case _ ⇒ throw new IllegalArgumentException(s"$o is not an instance of ScalaPB-generated class.") 34 | } 35 | 36 | override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = 37 | system.dynamicAccess.getObjectFor[GeneratedMessageCompanion[_ <: GeneratedMessage with Message[_]]](manifest) 38 | .flatMap(_.validate(bytes)) 39 | .get 40 | 41 | } 42 | -------------------------------------------------------------------------------- /vision-identity/src/train/scala/org/eigengo/rsa/identity/v100/FacePreprocessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import java.io.File 22 | 23 | import scala.util.Random 24 | 25 | object FacePreprocessor { 26 | 27 | def main(args: Array[String]): Unit = { 28 | val brightnessPreprocessors = (-10 to 10).map(new Preprocessors.Brightness(_)) 29 | val blurPreprocessors = (1 to 4).filterNot(_ % 2 == 0).map(x ⇒ new Preprocessors.Blur(x * 3)) 30 | val rotatePreprocessors = List.fill(10)(new Preprocessors.Rotate((Random.nextDouble() - 0.5) * 30)) 31 | 32 | val preprocessors = 33 | List(new Preprocessors.ExtractFaces("/haarcascade_frontalface_default.xml")) ++ 34 | rotatePreprocessors ++ 35 | brightnessPreprocessors ++ 36 | blurPreprocessors 37 | 38 | val result = new PreprocessingPipeline( 39 | new File("/Users/janmachacek/Eigengo/reactive-summit-2016-data/faces-raw"), 40 | new File("/Users/janmachacek/Eigengo/reactive-summit-2016-data/faces"), 41 | preprocessors 42 | ).preprocess() 43 | 44 | println(result) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /scalapb-akka-serializer/src/test/scala/org/eigengo/rsa/serialization/ScalaPBSerializerTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.serialization 20 | 21 | import akka.actor.{ActorSystem, ExtendedActorSystem} 22 | import org.scalatest.{FlatSpec, Matchers} 23 | import org.scalatest.prop.PropertyChecks 24 | 25 | class ScalaPBSerializerTest extends FlatSpec with PropertyChecks with Matchers { 26 | val system = ActorSystem().asInstanceOf[ExtendedActorSystem] 27 | val serializer = new ScalaPBSerializer(system) 28 | 29 | it should "handle ScalaPB instance" in { 30 | val in = SomeMessage(version = 1, text = "foo") 31 | 32 | val manifest = serializer.manifest(in) 33 | val binary = serializer.toBinary(in) 34 | val out = serializer.fromBinary(binary, manifest) 35 | 36 | in shouldBe out 37 | } 38 | 39 | it should "reject non-ScalaPB instances" in { 40 | an[IllegalArgumentException] should be thrownBy serializer.toBinary("Hello") 41 | } 42 | 43 | it should "reject missing objects" in { 44 | a[ClassNotFoundException] should be thrownBy serializer.fromBinary(Array.emptyByteArray, "Foo") 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /vision-text/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | lagom.broker.kafka { 2 | # The URLs of the Kafka brokers. Separate each URL with a comma. 3 | brokers = ${lagom.broker.defaults.kafka.brokers} 4 | 5 | client { 6 | default { 7 | # Exponential backoff for failures 8 | failure-exponential-backoff { 9 | # minimum (initial) duration until processor is started again 10 | # after failure 11 | min = 3s 12 | 13 | # the exponential back-off is capped to this duration 14 | max = 30s 15 | 16 | # additional random delay is based on this factor 17 | random-factor = 0.2 18 | } 19 | } 20 | 21 | # configuration used by the Lagom Kafka producer 22 | producer = ${lagom.broker.kafka.client.default} 23 | producer.role = "" 24 | 25 | # configuration used by the Lagom Kafka consumer 26 | consumer { 27 | failure-exponential-backoff = ${lagom.broker.kafka.client.default.failure-exponential-backoff} 28 | 29 | # Number of messages batched together by the consumer before the related messages' 30 | # offsets are committed to Kafka. 31 | # By increasing the batching-size you are trading speed with the risk of having 32 | # to re-process a larger number of messages if a failure occurs. 33 | # The value provided must be strictly greater than zero. 34 | batching-size = 5 35 | 36 | # Interval of time waited by the consumer before the currently batched messages' 37 | # offsets are committed to Kafka. 38 | # This parameter is useful to ensure that messages' offsets are always committed 39 | # within a fixed amount of time. 40 | # The value provided must be strictly greater than zero. 41 | batching-interval = 1 second 42 | } 43 | } 44 | } 45 | 46 | play.modules.enabled += org.eigengo.rsa.text.v100.server.Module 47 | 48 | cassandra-journal.keyspace=vision_text 49 | cassandra-snapshot-store.keyspace=vision_text 50 | lagom.persistence.read-side.cassandra.keyspace=vision_text 51 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/TweetImageServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100; 20 | 21 | import akka.japi.Pair; 22 | import com.google.inject.Inject; 23 | import com.lightbend.lagom.javadsl.api.broker.Topic; 24 | import com.lightbend.lagom.javadsl.broker.TopicProducer; 25 | import com.lightbend.lagom.javadsl.persistence.Offset; 26 | import com.lightbend.lagom.javadsl.persistence.PersistentEntityRegistry; 27 | import org.eigengo.rsa.Envelope; 28 | 29 | public class TweetImageServiceImpl implements TweetImageService { 30 | private final PersistentEntityRegistry persistentEntityRegistry; 31 | 32 | @Inject 33 | public TweetImageServiceImpl(PersistentEntityRegistry persistentEntityRegistry) { 34 | this.persistentEntityRegistry = persistentEntityRegistry; 35 | 36 | } 37 | 38 | private Pair convertEvent(Pair pair) { 39 | return new Pair<>(pair.first().getEnvelope(), pair.second()); 40 | } 41 | 42 | public Topic tweetImageTopic() { 43 | return TopicProducer.singleStreamWithOffset(offset -> 44 | persistentEntityRegistry.eventStream(TweetImageEventTag.INSTANCE, offset) 45 | .map(this::convertEvent)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /provisioning/it.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | zookeeper: 5 | extends: 6 | file: all.template 7 | service: zookeeper 8 | expose: 9 | - "2181" 10 | 11 | kafka: 12 | extends: 13 | file: all.template 14 | service: kafka 15 | expose: 16 | - "9092" 17 | extra_hosts: 18 | - "kafka:127.0.0.1" 19 | environment: 20 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 21 | KAFKA_ADVERTISED_HOST_NAME: kafka 22 | KAFKA_ADVERTISED_PORT: 9092 23 | # 30 partitions, one replica; documentation is at 24 | # https://github.com/wurstmeister/kafka-docker#automatically-create-topics 25 | KAFKA_CREATE_TOPICS: "tweet-image:30:1" 26 | links: 27 | - zookeeper 28 | 29 | cassandra: 30 | extends: 31 | file: all.template 32 | service: cassandra 33 | links: 34 | - kafka 35 | expose: 36 | - "9042" 37 | 38 | it: 39 | extends: 40 | file: all.template 41 | service: it 42 | environment: 43 | KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" 44 | START_DELAY: "120000" 45 | links: 46 | - kafka 47 | - cassandra 48 | 49 | vision-scene-classification: 50 | extends: 51 | file: all.template 52 | service: vision-scene-classification 53 | environment: 54 | KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" 55 | START_DELAY: "120000" 56 | links: 57 | - kafka 58 | 59 | # vision-identity: 60 | # extends: 61 | # file: all.template 62 | # service: vision-identity 63 | # environment: 64 | # KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" 65 | # CASSANDRA_JOURNAL_CPS: "cassandra" 66 | # CASSANDRA_SNAPSHOT_CPS: "cassandra" 67 | # START_DELAY: "120000" 68 | # links: 69 | # - kafka 70 | # - cassandra 71 | 72 | dashboard: 73 | extends: 74 | file: all.template 75 | service: dashboard 76 | ports: 77 | - "8080:8080" 78 | environment: 79 | KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" 80 | START_DELAY: "120000" 81 | links: 82 | - kafka 83 | -------------------------------------------------------------------------------- /dashboard/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dashboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Dashboard

13 |
Loading...
14 | 15 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /ingest/src/main/scala/org/eigengo/rsa/ingest/v100/SimplifiedTweetProcessorActor.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.ingest.v100 2 | 3 | import java.util.UUID 4 | 5 | import akka.actor.{Actor, OneForOneStrategy, Props, SupervisorStrategy} 6 | import akka.http.scaladsl.Http 7 | import akka.http.scaladsl.model.{HttpMethods, HttpRequest, Uri} 8 | import akka.stream.ActorMaterializer 9 | import cakesolutions.kafka.{KafkaProducer, KafkaProducerRecord, KafkaSerializer} 10 | import com.google.protobuf.ByteString 11 | import com.typesafe.config.Config 12 | import org.apache.kafka.common.serialization.StringSerializer 13 | import org.eigengo.rsa.Envelope 14 | 15 | object SimplifiedTweetProcessorActor { 16 | 17 | def props(config: Config): Props = { 18 | val producerConf = KafkaProducer.Conf( 19 | config.getConfig("tweet-image-producer"), 20 | new StringSerializer, 21 | KafkaSerializer[Envelope](_.toByteArray) 22 | ) 23 | Props(classOf[SimplifiedTweetProcessorActor], producerConf) 24 | } 25 | } 26 | 27 | class SimplifiedTweetProcessorActor(producerConf: KafkaProducer.Conf[String, Envelope]) extends Actor { 28 | private[this] val producer = KafkaProducer(conf = producerConf) 29 | implicit val _ = ActorMaterializer() 30 | 31 | import scala.concurrent.duration._ 32 | override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10.seconds) { 33 | case _ ⇒ SupervisorStrategy.Restart 34 | } 35 | 36 | override def receive: Receive = { 37 | case TweetImage(handle, content) ⇒ 38 | producer.send(KafkaProducerRecord("tweet-image", handle, 39 | Envelope(version = 100, 40 | handle = handle, 41 | ingestionTimestamp = System.nanoTime(), 42 | processingTimestamp = System.nanoTime(), 43 | messageId = UUID.randomUUID().toString, 44 | correlationId = UUID.randomUUID().toString, 45 | payload = content))) 46 | case SimplifiedTweet(handle, mediaUrls) ⇒ 47 | mediaUrls.foreach { mediaUrl ⇒ 48 | import context.dispatcher 49 | val request = HttpRequest(method = HttpMethods.GET, uri = Uri(mediaUrl)) 50 | val timeout = 1000.millis 51 | Http(context.system).singleRequest(request).flatMap(_.entity.toStrict(timeout)).foreach { entity ⇒ 52 | self ! TweetImage(handle, ByteString.copyFrom(entity.data.toArray)) 53 | } 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /vision-identity/src/test/scala/org/eigengo/rsa/identity/v100/FaceExtractorTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import org.scalatest.prop.PropertyChecks 22 | import org.scalatest.{FlatSpec, Inside, Matchers} 23 | 24 | import scala.util.Success 25 | 26 | class FaceExtractorTest extends FlatSpec with PropertyChecks with Matchers with Inside { 27 | lazy val Success(faceExtractor) = FaceExtractor() 28 | 29 | def getResourceBytes(resourceName: String): Array[Byte] = { 30 | val is = getClass.getResourceAsStream(resourceName) 31 | Stream.continually(is.read).takeWhile(_ != -1).map(_.toByte).toArray 32 | } 33 | 34 | it should "indicate failures" in { 35 | faceExtractor.extract(Array(1: Byte)).isFailure shouldBe true 36 | faceExtractor.extract(null).isFailure shouldBe true 37 | } 38 | 39 | it should "not find Helena in salad" in { 40 | faceExtractor.extract(getResourceBytes("/salad.jpg")).get shouldBe empty 41 | } 42 | 43 | it should "find faces in image" in { 44 | faceExtractor.extract(getResourceBytes("/dogface.jpg")).get shouldBe empty 45 | 46 | inside(faceExtractor.extract(getResourceBytes("/verified.jpg")).get) { 47 | case FaceImage(_, x, y, w, h, _)::Nil ⇒ 48 | x should be > 60 49 | y should be > 30 50 | w should be > 100 51 | h should be > 100 52 | case _ ⇒ fail() 53 | } 54 | 55 | inside(faceExtractor.extract(getResourceBytes("/impostor.jpg")).get) { 56 | case FaceImage(_, x, y, w, h, _)::Nil ⇒ 57 | x should be > 65 58 | y should be > 45 59 | w should be > 160 60 | h should be > 160 61 | case _ ⇒ fail() 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /vision-identity/src/main/scala/org/eigengo/rsa/identity/v100/IdentityMatcher.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import java.io.InputStream 22 | 23 | import org.datavec.image.loader.ImageLoader 24 | import org.deeplearning4j.nn.multilayer.MultiLayerNetwork 25 | import org.eigengo.rsa.deeplearning4j.NetworkLoader 26 | 27 | import scala.io.Source 28 | import scala.util.Try 29 | 30 | class IdentityMatcher private(network: MultiLayerNetwork, labels: List[String]) { 31 | private val loader = new ImageLoader(50, 50, 3) 32 | private val threshold = 0.34 33 | 34 | def identify(imageStream: InputStream): Option[Identity.IdentifiedFace] = { 35 | Try(loader.asRowVector(imageStream)).toOption.flatMap { imageRowVector ⇒ 36 | val predictions = network.output(imageRowVector) 37 | val (i, s) = (0 until predictions.columns()).foldLeft((0, 0.0)) { 38 | case (x@(bi, bs), idx) ⇒ 39 | val s = predictions.getDouble(0, idx) 40 | if (s > bs) (idx, s) else x 41 | } 42 | if (s > threshold) Some(Identity.IdentifiedFace(labels(i), s)) else None 43 | } 44 | } 45 | 46 | } 47 | 48 | object IdentityMatcher { 49 | /** 50 | * The network's prediction for a single row vector is not a row vector 51 | * (This is never expected to happen) 52 | */ 53 | case object BadPredictionsShape extends Exception("Predictions are not row vector.") 54 | 55 | def apply(resourceAccessor: NetworkLoader.ResourceAccessor): Try[IdentityMatcher] = { 56 | for { 57 | network ← NetworkLoader.loadMultiLayerNetwork(resourceAccessor) 58 | labels ← resourceAccessor("labels").map(is ⇒ Source.fromInputStream(is).getLines().toList) 59 | } yield new IdentityMatcher(network, labels) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /vision-scene-classification/src/test/scala/org/eigengo/rsa/scene/v100/SceneClassifierTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.scene.v100 20 | 21 | import java.io.{File, FileInputStream, InputStream} 22 | 23 | import org.eigengo.rsa.deeplearning4j.NetworkLoader 24 | import org.scalatest.prop.PropertyChecks 25 | import org.scalatest.{FlatSpec, Matchers} 26 | 27 | import scala.util.Success 28 | 29 | class SceneClassifierTest extends FlatSpec with PropertyChecks with Matchers { 30 | 31 | /** 32 | * Finds all images in classpath resource ``/scene``, and applies 33 | * ``block`` to each 34 | * 35 | * @param block the block to be applied to each resource under ``/scene`` 36 | */ 37 | private def forAllScenes[U](block: (InputStream, String) ⇒ U): Unit = { 38 | val scene = new File(getClass.getResource("/scene").toURI) 39 | val labelFileNamePattern = "(\\w+).*".r 40 | scene.listFiles().foreach { file ⇒ 41 | labelFileNamePattern.findFirstMatchIn(file.getName).foreach { label ⇒ 42 | val is = new FileInputStream(file) 43 | block(is, label.group(1)) 44 | is.close() 45 | } 46 | } 47 | } 48 | 49 | it should "predict correct labels" in { 50 | val Success(classifier) = SceneClassifier( 51 | NetworkLoader.fallbackResourceAccessor( 52 | NetworkLoader.filesystemResourceAccessor("/opt/models/scene"), 53 | NetworkLoader.filesystemResourceAccessor("/Users/janmachacek/Dropbox/Models/scene") 54 | ) 55 | ) 56 | 57 | forAllScenes { (stream, label) ⇒ 58 | val Success(scene) = classifier.classify(stream) 59 | 60 | scene.labels.length should be (1) 61 | val firstLabel = scene.labels.head 62 | firstLabel.label should be (label) 63 | firstLabel.score should be > 0.8 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/TextServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100; 20 | 21 | import akka.Done; 22 | import akka.japi.Pair; 23 | import akka.stream.javadsl.Flow; 24 | import com.google.inject.Inject; 25 | import com.lightbend.lagom.javadsl.api.broker.Topic; 26 | import com.lightbend.lagom.javadsl.broker.TopicProducer; 27 | import com.lightbend.lagom.javadsl.persistence.PersistentEntityRef; 28 | import com.lightbend.lagom.javadsl.persistence.PersistentEntityRegistry; 29 | import org.eigengo.rsa.Envelope; 30 | 31 | public class TextServiceImpl implements TextService { 32 | private final PersistentEntityRegistry persistentEntityRegistry; 33 | 34 | @Override 35 | public Topic textTopic() { 36 | return TopicProducer.singleStreamWithOffset(offset -> persistentEntityRegistry 37 | .eventStream(TextEntityEvent.OcredTag.INSTANCE, offset) 38 | .map(p -> new Pair<>(p.first().envelope(), offset)) 39 | ); 40 | } 41 | 42 | @Inject 43 | public TextServiceImpl(PersistentEntityRegistry persistentEntityRegistry, TweetImageService tweetImageService) { 44 | this.persistentEntityRegistry = persistentEntityRegistry; 45 | 46 | persistentEntityRegistry.register(TextEntity.class); 47 | tweetImageService.tweetImageTopic().subscribe().withGroupId("text").atLeastOnce(Flow.fromFunction(this::extractText)); 48 | } 49 | 50 | private Done extractText(Envelope envelope) { 51 | TextEntityCommand.Ocr command = new TextEntityCommand.Ocr(envelope.correlationId(), envelope.ingestionTimestamp(), envelope.payload().toByteArray()); 52 | PersistentEntityRef ref = persistentEntityRegistry.refFor(TextEntity.class, envelope.handle()); 53 | ref.ask(command); 54 | 55 | return Done.getInstance(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /vision-identity/src/train/scala/org/eigengo/rsa/identity/v100/PreprocessingPipeline.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import java.io.File 22 | import java.util.UUID 23 | 24 | import scala.util.{Failure, Try} 25 | 26 | class PreprocessingPipeline(sourceDirectory: File, targetDirectory: File, 27 | preprocessors: List[Preprocessor]) { 28 | import org.bytedeco.javacpp.opencv_imgcodecs._ 29 | 30 | def preprocess(): Try[Seq[File]] = { 31 | if (!sourceDirectory.exists()) Failure(new Exception(s"$sourceDirectory does not exist.")) 32 | if (!targetDirectory.mkdirs()) Failure(new Exception(s"Cannot create $targetDirectory.")) 33 | 34 | def preprocessSourceFile(preprocessors: List[Preprocessor], sourceFile: File, targetDirectory: File): List[File] = preprocessors match { 35 | case h::t ⇒ 36 | val mat = imread(sourceFile.getAbsolutePath) 37 | h.preprocess(mat).flatMap { result ⇒ 38 | val targetFile = new File(targetDirectory, UUID.randomUUID().toString + ".jpg") 39 | imwrite(targetFile.getAbsolutePath, result) 40 | targetFile :: preprocessSourceFile(t, targetFile, targetDirectory) 41 | } 42 | case Nil ⇒ Nil 43 | } 44 | 45 | def preprocess(source: File, targetDirectory: File): Seq[File] = { 46 | if (source.isDirectory) { 47 | val target = new File(targetDirectory, source.getName) 48 | if (target.exists() || target.mkdirs()) { 49 | source.listFiles().flatMap(x ⇒ preprocess(x, new File(targetDirectory, source.getName))) 50 | } else { 51 | Nil 52 | } 53 | } else { 54 | preprocessSourceFile(preprocessors, source, targetDirectory) 55 | } 56 | } 57 | 58 | Try(sourceDirectory.listFiles().flatMap { file ⇒ 59 | if (file.isDirectory) preprocess(file, targetDirectory) else Nil 60 | }) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /vision-text/src/main/scala/org/eigengo/rsa/text/v100/ScalaPBMessageSerializer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100 20 | 21 | import java.util 22 | 23 | import akka.util.ByteString 24 | import com.lightbend.lagom.javadsl.api.deser.MessageSerializer 25 | import com.lightbend.lagom.javadsl.api.deser.MessageSerializer.{NegotiatedDeserializer, NegotiatedSerializer} 26 | import com.lightbend.lagom.javadsl.api.transport.MessageProtocol 27 | import com.trueaccord.scalapb.{GeneratedMessage, GeneratedMessageCompanion, Message} 28 | 29 | class ScalaPBMessageSerializer[M <: GeneratedMessage] private (companion: GeneratedMessageCompanion[_ <: M with Message[_]]) extends MessageSerializer[M, ByteString] { 30 | import org.eigengo.rsa.text.v100.ScalaPBMessageSerializer._ 31 | 32 | override def deserializer(protocol: MessageProtocol): NegotiatedDeserializer[M, ByteString] = 33 | new ScalaPBNegotiatedDeserializer[M](companion) 34 | 35 | override def serializerForResponse(acceptedMessageProtocols: util.List[MessageProtocol]): NegotiatedSerializer[M, ByteString] = 36 | new ScalaPBNegotiatedSerializer[M] 37 | 38 | override def serializerForRequest(): NegotiatedSerializer[M, ByteString] = 39 | new ScalaPBNegotiatedSerializer[M] 40 | 41 | } 42 | 43 | object ScalaPBMessageSerializer { 44 | def of[M <: GeneratedMessage](companion: GeneratedMessageCompanion[_ <: M with Message[_]]): ScalaPBMessageSerializer[M] = new ScalaPBMessageSerializer[M](companion) 45 | 46 | class ScalaPBNegotiatedSerializer[M <: GeneratedMessage] extends NegotiatedSerializer[M, ByteString] { 47 | override def serialize(messageEntity: M): ByteString = ByteString(messageEntity.toByteArray) 48 | } 49 | 50 | class ScalaPBNegotiatedDeserializer[M <: GeneratedMessage](companion: GeneratedMessageCompanion[_ <: M with Message[_]]) extends NegotiatedDeserializer[M, ByteString] { 51 | override def deserialize(wire: ByteString): M = companion.parseFrom(wire.toArray) 52 | } 53 | } -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/dashboard/v100/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import akka.actor.{ActorRef, ActorSystem, Props} 22 | import akka.http.scaladsl.Http 23 | import akka.http.scaladsl.server.Route 24 | import akka.stream.ActorMaterializer 25 | import akka.stream.actor.ActorPublisher 26 | import akka.stream.actor.ActorPublisherMessage.Request 27 | import akka.stream.scaladsl.Source 28 | import com.typesafe.config.{ConfigFactory, ConfigResolveOptions} 29 | 30 | object Main extends App with DashboardService { 31 | Option(System.getenv("START_DELAY")).foreach(d ⇒ Thread.sleep(d.toInt)) 32 | val config = ConfigFactory.load("dashboard.conf").resolve(ConfigResolveOptions.defaults()) 33 | implicit val system = ActorSystem(name = "dashboard-100", config = config) 34 | implicit val materializer = ActorMaterializer() 35 | import system.dispatcher 36 | 37 | system.log.info("Dashboard 100 starting...") 38 | 39 | val route: Route = { 40 | if ("FALSE".equalsIgnoreCase(System.getenv("EMBEDDED_SERVER"))) { 41 | dashboardRoute 42 | } else { 43 | dashboardRoute ~ getFromResourceDirectory("") 44 | } 45 | } 46 | 47 | private class SummarySourceActor(summaryActor: ActorRef) extends ActorPublisher[Summary] { 48 | override def preStart(): Unit = { 49 | context.system.eventStream.subscribe(self, classOf[Summary]) 50 | } 51 | 52 | override def postStop(): Unit = { 53 | context.system.eventStream.unsubscribe(self) 54 | } 55 | 56 | override def receive: Receive = { 57 | case Request(_) ⇒ summaryActor ! SummaryActor.Peek 58 | case s: Summary if totalDemand > 0 ⇒ onNext(s) 59 | } 60 | } 61 | 62 | system.actorOf(DashboardSinkActor.props(config.getConfig("app"))) 63 | 64 | lazy val summaryActor = system.actorOf(SummaryActor.props) 65 | lazy val summarySource: Source[Summary, _] = Source.actorPublisher(Props(classOf[SummarySourceActor], summaryActor)) 66 | 67 | 68 | Http(system).bindAndHandle(route, "0.0.0.0", 8080) 69 | system.log.info(s"Dashboard 100 running.") 70 | 71 | } 72 | -------------------------------------------------------------------------------- /dashboard/src/test/scala/org/eigengo/rsa/dashboard/v100/SummaryActorTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import java.util.UUID 22 | 23 | import akka.actor.{Actor, ActorSystem} 24 | import akka.testkit.{TestActorRef, TestKitBase} 25 | import com.google.protobuf.ByteString 26 | import com.trueaccord.scalapb.GeneratedMessage 27 | import com.typesafe.config.ConfigFactory 28 | import org.eigengo.rsa.Envelope 29 | import org.eigengo.rsa.identity.v100.Identity 30 | import org.eigengo.rsa.scene.v100.Scene 31 | import org.scalatest.prop.PropertyChecks 32 | import org.scalatest.{FlatSpec, Matchers} 33 | 34 | class SummaryActorTest extends FlatSpec with TestKitBase with PropertyChecks with Matchers { 35 | implicit lazy val system = ActorSystem("test", ConfigFactory.load("dashboard-test.conf").resolve()) 36 | 37 | it must "handle" in { 38 | val lsa = TestActorRef[LastSummaryActor] 39 | system.eventStream.subscribe(lsa, classOf[Summary]) 40 | 41 | val scene = ("scene", Scene(labels = Seq(Scene.Label(label = "salad", score = 1)))) 42 | val identity = ("identity", Identity(face = Identity.Face.IdentifiedFace(Identity.IdentifiedFace(name = "Jan", score = 1)))) 43 | 44 | def envelopeForHandle(ingestionTimestamp: Long, handle: String, m: (String, GeneratedMessage)): Envelope = { 45 | val (messageType, message) = m 46 | Envelope(version = 100, 47 | ingestionTimestamp = ingestionTimestamp, 48 | handle = handle, 49 | messageType = messageType, 50 | messageId = UUID.randomUUID().toString, 51 | payload = ByteString.copyFrom(message.toByteArray) 52 | ) 53 | } 54 | 55 | val sa = system.actorOf(SummaryActor.props, "summary") 56 | (0 until 1) 57 | .flatMap(x ⇒ List(envelopeForHandle(x, "@" + x, scene), envelopeForHandle(x, "@" + x, identity))) 58 | .foreach(sa.!) 59 | 60 | Thread.sleep(10000) 61 | 62 | println(lsa.underlyingActor.lastSummary) 63 | () 64 | } 65 | 66 | } 67 | 68 | class LastSummaryActor extends Actor { 69 | var lastSummary: Summary = _ 70 | override def receive: Receive = { 71 | case s: Summary ⇒ lastSummary = s 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ingest/src/main/scala/org/eigengo/rsa/ingest/v100/Main.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.ingest.v100 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model.HttpHeader.ParsingResult 6 | import akka.http.scaladsl.model._ 7 | import akka.http.scaladsl.model.headers.Accept 8 | import akka.stream.ActorMaterializer 9 | import com.hunorkovacs.koauth.domain.KoauthRequest 10 | import com.hunorkovacs.koauth.service.consumer.DefaultConsumerService 11 | import com.typesafe.config.ConfigFactory 12 | 13 | import scala.util.{Failure, Success, Try} 14 | 15 | object Main extends App { 16 | val config = ConfigFactory.load("ingest.conf").resolve() 17 | 18 | val consumerKey = config.getString("app.twitter.consumerKey") 19 | val consumerSecret = config.getString("app.twitter.consumerSecret") 20 | val accessToken = config.getString("app.twitter.accessToken") 21 | val accessTokenSecret = config.getString("app.twitter.accessTokenSecret") 22 | val url = "https://stream.twitter.com/1.1/statuses/filter.json" 23 | 24 | implicit val system = ActorSystem("Ingest", config) 25 | implicit val materializer = ActorMaterializer() 26 | import system.dispatcher 27 | 28 | val simplifiedTweetProcessorActor = system.actorOf(SimplifiedTweetProcessorActor.props(config.getConfig("app"))) 29 | 30 | def ingest(source: Uri, body: String)(authorizationHeader: HttpHeader): Unit = { 31 | val httpRequest = HttpRequest( 32 | method = HttpMethods.POST, 33 | uri = source, 34 | headers = List(Accept(MediaRanges.`*/*`), authorizationHeader), 35 | entity = HttpEntity(contentType = ContentType(MediaTypes.`application/x-www-form-urlencoded`, HttpCharsets.`UTF-8`), string = body) 36 | ) 37 | val request = Http().singleRequest(httpRequest) 38 | request.foreach { response ⇒ 39 | if (response.status.intValue() == 200) { 40 | response.entity.dataBytes 41 | .scan("")((acc, curr) => if (acc.contains("\n")) curr.utf8String else acc + curr.utf8String) 42 | .filter(x ⇒ x.length > 2 && x.contains("\n")) 43 | .map(x ⇒ SimplifiedTweetFormat.parse(x)) 44 | .runForeach { 45 | case Success(tweet) ⇒ 46 | simplifiedTweetProcessorActor ! tweet 47 | case Failure(ex) ⇒ 48 | system.log.warning("Could not process tweet: {}.", ex) 49 | } 50 | } 51 | } 52 | } 53 | 54 | val consumer = new DefaultConsumerService(system.dispatcher) 55 | 56 | val body = "track=%23ReactiveSummi" 57 | val source = Uri(url) 58 | 59 | val koauthRequest = KoauthRequest(method = "POST", url = url, authorizationHeader = None, body = Some(body)) 60 | consumer.createOauthenticatedRequest(koauthRequest, consumerKey, consumerSecret, accessToken, accessTokenSecret) 61 | .map { x ⇒ 62 | val ParsingResult.Ok(h, _) = HttpHeader.parse("Authorization", x.header) 63 | h 64 | } 65 | .foreach(ingest(source, body)) 66 | 67 | } 68 | -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/dashboard/v100/SummaryActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import akka.actor.Props 22 | import akka.persistence.PersistentActor 23 | import org.eigengo.rsa.Envelope 24 | 25 | object SummaryActor { 26 | lazy val props: Props = Props[SummaryActor] 27 | 28 | case object Peek 29 | 30 | implicit object HandleSummaryOrdering extends Ordering[HandleSummary] { 31 | override def compare(x: HandleSummary, y: HandleSummary): Int = x.handle.compare(y.handle) 32 | } 33 | 34 | } 35 | 36 | class SummaryActor extends PersistentActor { 37 | import SummaryActor._ 38 | private val maximumTopHandles = 100 39 | private val topHandleHSIBuilders: collection.mutable.Map[String, HandleSummaryItemsBuilder] = collection.mutable.Map() 40 | 41 | override def preStart(): Unit = { 42 | context.system.eventStream.subscribe(self, classOf[Envelope]) 43 | } 44 | 45 | override def postStop(): Unit = { 46 | context.system.eventStream.unsubscribe(self) 47 | } 48 | 49 | override val persistenceId: String = "summary" 50 | 51 | override def receiveRecover: Receive = { 52 | case m: Envelope ⇒ handleMessage(m) 53 | } 54 | 55 | def buildSummary(): Summary = { 56 | val topHandleSummaries = topHandleHSIBuilders.map { 57 | case (k, v) ⇒ HandleSummary(handle = k, v.build()) 58 | }.toList.sorted 59 | 60 | Summary(topHandleSummaries = topHandleSummaries) 61 | } 62 | 63 | override def receiveCommand: Receive = { 64 | case m: Envelope ⇒ persist(m)(handleMessage) 65 | case Peek ⇒ context.system.eventStream.publish(buildSummary()) 66 | } 67 | 68 | private def handleMessage(message: Envelope): Unit = { 69 | if (topHandleHSIBuilders.size > maximumTopHandles) { 70 | topHandleHSIBuilders 71 | .find { case (h, b) ⇒ h != message.handle && !b.isActive(message) } 72 | .foreach { case (h, _) ⇒ topHandleHSIBuilders.remove(h) } 73 | } 74 | 75 | val builder = topHandleHSIBuilders.getOrElse(message.handle, new HandleSummaryItemsBuilder()) 76 | builder.append(message) 77 | topHandleHSIBuilders.put(message.handle, builder) 78 | 79 | context.system.eventStream.publish(buildSummary()) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /vision-identity/src/train/scala/org/eigengo/rsa/identity/v100/Preprocessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import org.bytedeco.javacpp.opencv_core._ 22 | import org.bytedeco.javacpp.opencv_objdetect.CascadeClassifier 23 | 24 | trait Preprocessor { 25 | 26 | def preprocess(mat: Mat): List[Mat] 27 | 28 | } 29 | 30 | object Preprocessors { 31 | import org.bytedeco.javacpp.opencv_imgproc._ 32 | 33 | object EqualizeHistogram extends Preprocessor { 34 | override def preprocess(mat: Mat): List[Mat] = { 35 | equalizeHist(mat, mat) 36 | List(mat) 37 | } 38 | } 39 | 40 | class ExtractFaces(cascadeResource: String) extends Preprocessor { 41 | val cascadeClassifier = { 42 | val file = FaceExtractor.getClass.getResource(cascadeResource).getFile 43 | new CascadeClassifier(file) 44 | } 45 | 46 | override def preprocess(mat: Mat): List[Mat] = { 47 | val faces = new RectVector() 48 | val grayMat = new Mat() 49 | cvtColor(mat, grayMat, COLOR_BGRA2GRAY) 50 | equalizeHist(grayMat, grayMat) 51 | cascadeClassifier.detectMultiScale(grayMat, faces) 52 | 53 | (0 until faces.size().toInt).map { x ⇒ 54 | val rect = faces.get(x) 55 | val submat = new Mat(mat, rect) 56 | submat 57 | }.toList 58 | } 59 | } 60 | 61 | object Grayscale extends Preprocessor { 62 | override def preprocess(mat: Mat): List[Mat] = { 63 | cvtColor(mat, mat, COLOR_BGRA2GRAY) 64 | List(mat) 65 | } 66 | } 67 | 68 | class Brightness(delta: Double) extends Preprocessor { 69 | override def preprocess(mat: Mat): List[Mat] = { 70 | mat.convertTo(mat, -1, 1, delta) 71 | List(mat) 72 | } 73 | } 74 | 75 | class Blur(ksize: Int) extends Preprocessor { 76 | override def preprocess(mat: Mat): List[Mat] = { 77 | medianBlur(mat, mat, ksize) 78 | List(mat) 79 | } 80 | } 81 | 82 | class Rotate(degrees: Double) extends Preprocessor { 83 | override def preprocess(mat: Mat): List[Mat] = { 84 | val rm = getRotationMatrix2D(new Point2f(mat.cols() / 2, mat.rows() / 2), degrees - 180, 1) 85 | warpAffine(mat, mat, rm, mat.size()) 86 | List(mat) 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/dashboard/v100/DashboardSinkActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import akka.actor.{Actor, OneForOneStrategy, Props, SupervisorStrategy} 22 | import akka.routing.RandomPool 23 | import cakesolutions.kafka._ 24 | import cakesolutions.kafka.akka.KafkaConsumerActor.{Confirm, Subscribe, Unsubscribe} 25 | import cakesolutions.kafka.akka.{ConsumerRecords, KafkaConsumerActor} 26 | import com.typesafe.config.Config 27 | import org.apache.kafka.common.serialization.StringDeserializer 28 | import org.eigengo.rsa._ 29 | 30 | object DashboardSinkActor { 31 | 32 | private val extractor = ConsumerRecords.extractor[String, Envelope] 33 | 34 | def props(config: Config): Props = { 35 | val consumerConf = KafkaConsumer.Conf( 36 | config.getConfig("kafka.consumer-config"), 37 | keyDeserializer = new StringDeserializer, 38 | valueDeserializer = KafkaDeserializer(Envelope.parseFrom) 39 | ) 40 | val consumerActorConf = KafkaConsumerActor.Conf() 41 | 42 | Props(classOf[DashboardSinkActor], consumerConf, consumerActorConf) 43 | } 44 | 45 | } 46 | 47 | class DashboardSinkActor(consumerConf: KafkaConsumer.Conf[String, Envelope], consumerActorConf: KafkaConsumerActor.Conf) extends Actor { 48 | import DashboardSinkActor._ 49 | 50 | private[this] val kafkaConsumerActor = context.actorOf( 51 | KafkaConsumerActor.props(consumerConf = consumerConf, actorConf = consumerActorConf, downstreamActor = self) 52 | ) 53 | 54 | import scala.concurrent.duration._ 55 | override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10.seconds) { 56 | case _ ⇒ SupervisorStrategy.Restart 57 | } 58 | 59 | override def preStart(): Unit = { 60 | kafkaConsumerActor ! Subscribe.AutoPartition(Seq("identity", "scene", "text")) 61 | } 62 | 63 | override def postStop(): Unit = { 64 | kafkaConsumerActor ! Unsubscribe 65 | } 66 | 67 | override def receive: Receive = { 68 | case extractor(consumerRecords) ⇒ 69 | consumerRecords.pairs.foreach { 70 | case (_, envelope) ⇒ context.system.eventStream.publish(envelope) 71 | } 72 | kafkaConsumerActor ! Confirm(consumerRecords.offsets, commit = true) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /protobuf-testkit/src/main/scala/org/eigengo/protobufcheck/ProtobufMatchers.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.protobufcheck 2 | 3 | import java.io.{ByteArrayInputStream, ByteArrayOutputStream} 4 | 5 | import com.google.protobuf.Descriptors.FieldDescriptor 6 | import com.trueaccord.scalapb.{GeneratedMessage, GeneratedMessageCompanion, Message} 7 | import org.scalatest.Matchers 8 | import org.scalatest.matchers.{BeMatcher, MatchResult} 9 | 10 | /** 11 | * Defines matchers to be used with the ``be`` verb and ScalaPB-generated code. Its matchers 12 | * provide means to verify that the messages are compatible with each other. 13 | */ 14 | trait ProtobufMatchers extends Matchers { 15 | 16 | private type Right = GeneratedMessageCompanion[_ <: GeneratedMessage with Message[_]] 17 | 18 | /** 19 | * Common matcher that matches ``GeneratedMessage`` on the left with the ``GeneratedMessage`` 20 | * constructed using the ``GeneratedMessageCompanion`` on the right 21 | * 22 | * @param right the right companion 23 | */ 24 | private class CompatibleMatcher(right: Right) extends BeMatcher[GeneratedMessage] { 25 | 26 | import collection.JavaConversions._ 27 | 28 | /** 29 | * Returns ``true`` if the two ``FieldDescriptor``s are equal 30 | * @param a the first FD 31 | * @param b the second FD 32 | * @return a == b 33 | */ 34 | protected final def fdEquals(a: FieldDescriptor)(b: FieldDescriptor): Boolean = { 35 | a.getName == b.getName && a.getType.name() == b.getType.name() 36 | } 37 | 38 | override def apply(left: GeneratedMessage): MatchResult = { 39 | val os = new ByteArrayOutputStream() 40 | left.writeTo(os) 41 | 42 | val leftFields = left.companion.descriptor.getFields.toList 43 | val rightFields = right.descriptor.getFields.toList 44 | val intersectedFields = leftFields.flatMap(l => rightFields.find(fdEquals(l)).map(r => (l, r))) 45 | 46 | val parsedRight = right.parseFrom(new ByteArrayInputStream(os.toByteArray)) 47 | 48 | val failures = intersectedFields.foldLeft(List.empty[(FieldDescriptor, FieldDescriptor, Any, Any)]) { 49 | case (r, (leftDescriptor, rightDescriptor)) => 50 | val leftValue = left.getField(leftDescriptor) 51 | val rightValue = parsedRight.getField(rightDescriptor) 52 | if (!leftValue.equals(rightValue)) { 53 | (leftDescriptor, rightDescriptor, leftValue, rightValue) :: r 54 | } else r 55 | } 56 | 57 | if (failures.isEmpty) { 58 | MatchResult(matches = true, "", "") 59 | } else { 60 | MatchResult(matches = false, "", "") 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Returns a matcher that verifies that the given ``GeneratedMessage`` is compatible with 67 | * the value unmarshalled using the ``GeneratedMessageCompanion`` on the right. 68 | * 69 | * @param right the companion that can unmarshal from the wire format 70 | * @return compatiblity matcher 71 | */ 72 | def compatibleWith(right: Right): BeMatcher[GeneratedMessage] = new CompatibleMatcher(right) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /vision-identity/src/main/scala/org/eigengo/rsa/identity/v100/FaceExtractor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import com.google.protobuf.ByteString 22 | import org.bytedeco.javacpp.BytePointer 23 | 24 | import scala.util.Try 25 | import org.bytedeco.javacpp.opencv_objdetect._ 26 | 27 | /** 28 | * Performs the face extraction from a given image using a cascade classifier 29 | * @param faceCascade the cascade classifier 30 | */ 31 | class FaceExtractor private (faceCascade: CascadeClassifier) { 32 | import org.bytedeco.javacpp.opencv_core._ 33 | import org.bytedeco.javacpp.opencv_imgproc._ 34 | import org.bytedeco.javacpp.opencv_imgcodecs._ 35 | 36 | /** 37 | * Extracts the faces in an encoding of an image `image`. Returns a Try of a 38 | * decoding or image manipulation error or a list of detected faces 39 | * 40 | * @param image the image 41 | * @return the error or detected faces, each with its coordinates in the original image and the extracted B&W image 42 | */ 43 | def extract(image: Array[Byte]): Try[List[FaceImage]] = Try { 44 | val mat = imdecode(new Mat(image, false), CV_LOAD_IMAGE_COLOR) 45 | if (mat.cols() == 0 || mat.rows() == 0) { 46 | throw new IllegalArgumentException("The image is 0 width or 0 height") 47 | } 48 | val grayMat = new Mat() 49 | cvtColor(mat, grayMat, COLOR_BGRA2GRAY) 50 | equalizeHist(grayMat, grayMat) 51 | val faces = new RectVector() 52 | faceCascade.detectMultiScale(grayMat, faces) 53 | 54 | (0 until faces.size().toInt).map { x ⇒ 55 | val rect = faces.get(x) 56 | val submat = new Mat(grayMat, rect) 57 | val bp = new BytePointer() 58 | imencode(".jpg", submat, bp) 59 | val bs = ByteString.copyFrom(bp.getStringBytes) 60 | 61 | FaceImage(confidence = 1.0, x = rect.x(), y = rect.y(), w = rect.width(), h = rect.height(), rgbBitmap = bs) 62 | }.toList 63 | } 64 | 65 | } 66 | 67 | /** 68 | * Constructs the face extractor 69 | */ 70 | object FaceExtractor { 71 | 72 | /** 73 | * Construct the classifier and return a valid instance 74 | * @return the FaceExtractor instance 75 | */ 76 | def apply(): Try[FaceExtractor] = Try { 77 | val file = FaceExtractor.getClass.getResource("/haarcascade_frontalface_alt.xml").getFile 78 | new FaceExtractor(new CascadeClassifier(file)) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | 5 | val scalaTest = "org.scalatest" %% "scalatest" % "2.2.6" 6 | // ScalaTest 2.2.6 is not compatible with ScalaCheck > 1.12.5 7 | val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.12.5" 8 | 9 | val protobuf = "com.google.protobuf" % "protobuf-java" % "3.0.0" 10 | 11 | val cats = "org.typelevel" %% "cats" % "0.6.1" 12 | 13 | val troy = "io.github.cassandra-scala" %% "troy" % "0.0.2" 14 | 15 | object cakesolutions { 16 | val akkaKafkaClient = "net.cakesolutions" %% "scala-kafka-client-akka" % "0.10.0.1-SNAPSHOT" 17 | } 18 | 19 | object scalapb { 20 | val runtime = "com.trueaccord.scalapb" %% "scalapb-runtime" % "0.5.38" exclude("com.google.protobuf", "protobuf-java") 21 | val json4s = "com.trueaccord.scalapb" %% "scalapb-json4s" % "0.1.1" 22 | } 23 | 24 | object conductr { 25 | private val version = "1.4.10" 26 | 27 | val akka = "com.typesafe.conductr" %% "akka24-conductr-bundle-lib" % version 28 | } 29 | 30 | object akka { 31 | private val version = "2.4.10" 32 | 33 | val actor = "com.typesafe.akka" %% "akka-actor" % version 34 | val persistence = "com.typesafe.akka" %% "akka-persistence" % version 35 | val persistenceCassandra = "com.github.krasserm" %% "akka-persistence-cassandra-3x" % "0.6" intransitive()// excludeAll ExclusionRule() 36 | 37 | val testKit = "com.typesafe.akka" %% "akka-testkit" % version 38 | val persistenceInMemory = "com.github.dnvriend" %% "akka-persistence-inmemory" % "1.3.8" 39 | 40 | object http { 41 | val core = "com.typesafe.akka" %% "akka-http-core" % version 42 | val experimental = "com.typesafe.akka" %% "akka-http-experimental" % version 43 | } 44 | } 45 | 46 | object nd4j { 47 | 48 | private val version = "0.5.0" 49 | private lazy val osArchClassifier = { 50 | val rawOsName = System.getProperty("os.name").toLowerCase 51 | val rawArch = System.getProperty("os.arch").toLowerCase 52 | if (rawOsName.startsWith("windows")) s"windows-$rawArch" 53 | else if (rawOsName.startsWith("linux")) s"linux-$rawArch" 54 | else if (rawOsName.startsWith("mac os x")) s"macosx-$rawArch" 55 | else "" 56 | } 57 | 58 | val exclusionRules = Seq( 59 | ExclusionRule(organization = "com.google.code.findbugs") 60 | ) 61 | 62 | val api = "org.nd4j" % "nd4j-api" % version 63 | 64 | // Even though ``native`` includes all native backends, SBT must be explicitly told 65 | // the classifier in order to pull in the native shared object 66 | def native(arch: String = osArchClassifier) = "org.nd4j" % "nd4j-native" % version classifier "" classifier arch 67 | } 68 | 69 | object bytedeco { 70 | val javacpp = "org.bytedeco" % "javacpp" % "1.2.2" 71 | val javacv = "org.bytedeco" % "javacv" % "1.2" 72 | } 73 | 74 | object deeplearning4j { 75 | private val version = "0.5.0" 76 | 77 | val core = "org.deeplearning4j" % "deeplearning4j-core" % version 78 | } 79 | 80 | object imageio { 81 | private val version = "3.1.1" 82 | 83 | val core = "com.twelvemonkeys.imageio" % "imageio-core" % version 84 | } 85 | 86 | val koauth = "com.hunorkovacs" %% "koauth" % "1.1.0" 87 | 88 | } 89 | -------------------------------------------------------------------------------- /it/src/main/scala/org/eigengo/rsa/it/v100/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.it.v100 20 | 21 | import java.util.UUID 22 | 23 | import cakesolutions.kafka.{KafkaProducer, KafkaProducerRecord, KafkaSerializer} 24 | import com.google.protobuf.ByteString 25 | import com.typesafe.config.{ConfigFactory, ConfigResolveOptions} 26 | import org.apache.kafka.clients.producer.RecordMetadata 27 | import org.apache.kafka.common.serialization.StringSerializer 28 | import org.eigengo.rsa.Envelope 29 | import org.slf4j.LoggerFactory 30 | 31 | import scala.concurrent.{Await, Future} 32 | import scala.util.{Random, Try} 33 | 34 | object Main { 35 | private val logger = LoggerFactory.getLogger(Main.getClass) 36 | 37 | def main(args: Array[String]): Unit = { 38 | val handles = (0 until 50).map(x ⇒ s"@$x") 39 | 40 | Option(System.getenv("START_DELAY")).foreach(d ⇒ Thread.sleep(d.toInt)) 41 | 42 | import scala.concurrent.ExecutionContext.Implicits.global 43 | val config = ConfigFactory.load("it.conf").resolve(ConfigResolveOptions.defaults()) 44 | 45 | val producer = KafkaProducer(KafkaProducer.Conf( 46 | config.getConfig("tweet-image-producer"), 47 | new StringSerializer, 48 | KafkaSerializer[Envelope](_.toByteArray) 49 | )) 50 | 51 | val resources = 52 | List("/images/beer.jpg", "/images/cake.jpg", "/images/laptop.png", "/images/salad.jpg") 53 | .map { resource ⇒ 54 | val is = getClass.getResourceAsStream(resource) 55 | Stream.continually(is.read).takeWhile(_ != -1).map(_.toByte).toArray 56 | } 57 | 58 | 59 | while (true) { 60 | val futures: Seq[Future[RecordMetadata]] = 61 | Random.shuffle(handles).take(1 + Random.nextInt(4)).flatMap { handle ⇒ 62 | val payload = ByteString.copyFrom(resources(Random.nextInt(resources.length))) 63 | val ret = Try(producer.send(KafkaProducerRecord("tweet-image", handle, 64 | Envelope(version = 100, 65 | handle = handle, 66 | ingestionTimestamp = System.nanoTime(), 67 | processingTimestamp = System.nanoTime(), 68 | messageId = UUID.randomUUID().toString, 69 | correlationId = UUID.randomUUID().toString, 70 | payload = payload)))) 71 | ret.toOption 72 | } 73 | val future = Future.sequence(futures) 74 | 75 | import scala.concurrent.duration._ 76 | logger.info(Await.result(future, 1.minute).toString()) 77 | 78 | Thread.sleep(1000) 79 | } 80 | producer.close() 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /vision-identity/src/main/scala/org/eigengo/rsa/identity/v100/IdentityMatcherActorSupervisor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import akka.actor.{Actor, OneForOneStrategy, Props, SupervisorStrategy} 22 | import akka.routing.RandomPool 23 | import cakesolutions.kafka._ 24 | import cakesolutions.kafka.akka.KafkaConsumerActor 25 | import cakesolutions.kafka.akka.KafkaConsumerActor.{Subscribe, Unsubscribe} 26 | import com.typesafe.config.Config 27 | import org.apache.kafka.common.serialization.{StringDeserializer, StringSerializer} 28 | import org.eigengo.rsa.Envelope 29 | import org.eigengo.rsa.deeplearning4j.NetworkLoader 30 | 31 | import scala.util.Success 32 | 33 | object IdentityMatcherActorSupervisor { 34 | 35 | def props(config: Config): Props = { 36 | val Success(identityMatcher) = IdentityMatcher( 37 | NetworkLoader.fallbackResourceAccessor( 38 | NetworkLoader.filesystemResourceAccessor("/opt/models/identity"), 39 | NetworkLoader.filesystemResourceAccessor("/Users/janmachacek/Dropbox/Models/identity") 40 | ) 41 | ) 42 | val consumerConf = KafkaConsumer.Conf( 43 | config.getConfig("kafka.consumer-config"), 44 | keyDeserializer = new StringDeserializer, 45 | valueDeserializer = KafkaDeserializer(Envelope.parseFrom) 46 | ) 47 | val consumerActorConf = KafkaConsumerActor.Conf() 48 | val producerConf = KafkaProducer.Conf( 49 | config.getConfig("kafka.identity-producer"), 50 | new StringSerializer, 51 | KafkaSerializer[Envelope](_.toByteArray) 52 | ) 53 | 54 | Props(classOf[IdentityMatcherActorSupervisor], consumerConf, consumerActorConf, producerConf, identityMatcher) 55 | } 56 | 57 | } 58 | 59 | class IdentityMatcherActorSupervisor(consumerConf: KafkaConsumer.Conf[String, Envelope], consumerActorConf: KafkaConsumerActor.Conf, 60 | producerConf: KafkaProducer.Conf[String, Envelope], 61 | identityMatcher: IdentityMatcher) extends Actor { 62 | 63 | private[this] val identityMatcherActor = context.actorOf( 64 | IdentityMatcherActor.props(producerConf, identityMatcher).withRouter(RandomPool(nrOfInstances = 10)) 65 | ) 66 | private[this] val kafkaConsumerActor = context.actorOf( 67 | KafkaConsumerActor.props(consumerConf = consumerConf, actorConf = consumerActorConf, downstreamActor = identityMatcherActor) 68 | ) 69 | 70 | override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { 71 | case _ ⇒ SupervisorStrategy.Escalate 72 | } 73 | 74 | override def preStart(): Unit = { 75 | kafkaConsumerActor ! Subscribe.AutoPartition(Seq("tweet-image")) 76 | } 77 | 78 | override def postStop(): Unit = { 79 | kafkaConsumerActor ! Unsubscribe 80 | } 81 | 82 | override def receive: Receive = Actor.ignoringBehavior 83 | } 84 | -------------------------------------------------------------------------------- /dashboard/src/test/scala/org/eigengo/rsa/dashboard/v100/HandleSummaryItemsBuilderTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import com.google.protobuf.ByteString 22 | import com.trueaccord.scalapb.GeneratedMessage 23 | import org.eigengo.rsa.Envelope 24 | import org.eigengo.rsa.identity.v100.Identity 25 | import org.eigengo.rsa.scene.v100.Scene 26 | import org.scalatest.{FlatSpec, Matchers} 27 | import org.scalatest.prop.PropertyChecks 28 | 29 | class HandleSummaryItemsBuilderTest extends FlatSpec with PropertyChecks with Matchers { 30 | import scala.concurrent.duration._ 31 | 32 | private def pue(ingestionTimestamp: Long, messageId: String, messageType: String, message: GeneratedMessage): Envelope = { 33 | val payload = ByteString.copyFrom(message.toByteArray) 34 | Envelope(version = 100, ingestionTimestamp = ingestionTimestamp, handle = "@honzam399", messageType = messageType, messageId = messageId, payload = payload) 35 | } 36 | 37 | it should "handle single item" in { 38 | val builder = new HandleSummaryItemsBuilder() 39 | builder.append(pue(ingestionTimestamp = 0, messageId = "a", messageType = "scene", Scene(labels = Seq(Scene.Label("beer", 1.0))))) 40 | val items = builder.build() 41 | 42 | items should have size 1 43 | items.head.windowSize shouldBe 0 44 | items.head.description shouldBe "beer" 45 | } 46 | 47 | it should "handle multiple items in a single window" in { 48 | val builder = new HandleSummaryItemsBuilder() 49 | builder.append(pue(ingestionTimestamp = 10.second.toNanos, messageId = "a", messageType = "scene", Scene(labels = Seq(Scene.Label("beer", 1.0))))) 50 | builder.append(pue(ingestionTimestamp = 20.second.toNanos, messageId = "b", messageType = "scene", Scene(labels = Seq(Scene.Label("cake", 1.0))))) 51 | builder.append(pue(ingestionTimestamp = 30.second.toNanos, messageId = "c", messageType = "scene", Scene(labels = Seq(Scene.Label("beer", 1.0))))) 52 | builder.append(pue(ingestionTimestamp = 40.second.toNanos, messageId = "d", messageType = "identity", Identity(face = Identity.Face.IdentifiedFace(Identity.IdentifiedFace("Jamie Allen"))))) 53 | val items = builder.build() 54 | 55 | items should have size 1 56 | items.head.windowSize shouldBe 30.second.toMillis 57 | items.head.description shouldBe "with the famous Jamie Allen and beer, cake" 58 | } 59 | 60 | it should "window tweets properly" in { 61 | val builder = new HandleSummaryItemsBuilder() 62 | builder.append(pue(ingestionTimestamp = 1.minute.toNanos, messageId = "a", messageType = "scene", Scene(labels = Seq(Scene.Label("beer", 1.0))))) 63 | builder.append(pue(ingestionTimestamp = 2.minute.toNanos, messageId = "b", messageType = "scene", Scene(labels = Seq(Scene.Label("beer", 1.0))))) 64 | val items = builder.build() 65 | 66 | items should have size 2 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/scala/org/eigengo/rsa/scene/v100/SceneClassifier.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.scene.v100 20 | 21 | import java.io._ 22 | 23 | import org.datavec.image.loader.ImageLoader 24 | import org.deeplearning4j.nn.multilayer.MultiLayerNetwork 25 | import org.eigengo.rsa.deeplearning4j.NetworkLoader 26 | 27 | import scala.io.Source 28 | import scala.util.{Failure, Success, Try} 29 | 30 | /** 31 | * Performs classification using the loaded network and matching labels. The number 32 | * of elements in ``labels`` has to match the number of outputs in the ``network``. 33 | * 34 | * @param network the (trained and initialized) network 35 | * @param labels the human-readable names in order of network outputs 36 | */ 37 | class SceneClassifier private(network: MultiLayerNetwork, labels: List[String]) { 38 | private val loader = new ImageLoader(100, 100, 3) 39 | private val threshold = 0.7 40 | 41 | /** 42 | * Classifies the content of the image in the ``imageStream``. 43 | * 44 | * @param imageStream the stream containing a loadable image (i.e. png, jpeg, ...) 45 | * @return error or scene with labels 46 | */ 47 | def classify(imageStream: InputStream): Try[Scene] = { 48 | Try(loader.asRowVector(imageStream)).flatMap { imageRowVector ⇒ 49 | val predictions = network.output(imageRowVector) 50 | if (predictions.isRowVector) { 51 | val predictedLabels = (0 until predictions.columns()).flatMap { column ⇒ 52 | val prediction = predictions.getDouble(0, column) 53 | if (prediction > threshold) { 54 | Some(Scene.Label(labels(column), prediction)) 55 | } else None 56 | } 57 | Success(Scene(predictedLabels)) 58 | } else Failure(SceneClassifier.BadPredictionsShape) 59 | } 60 | } 61 | 62 | } 63 | 64 | /** 65 | * Contains function to construct the ``SceneClassifier`` instance from a base path and 66 | * common error types. 67 | */ 68 | object SceneClassifier { 69 | 70 | /** 71 | * The network's prediction for a single row vector is not a row vector 72 | * (This is never expected to happen) 73 | */ 74 | case object BadPredictionsShape extends Exception("Predictions are not row vector.") 75 | 76 | /** 77 | * Constructs the SceneClassifier by loading the ``MultiLayerNetwork`` from three files 78 | * at the given ``basePath``. The three files are 79 | * 80 | * - the network configuration in ``basePath.json`` 81 | * - the network parameters in ``basePath.bin`` 82 | * - the labels in ``basePath.labels`` 83 | * 84 | * @param resourceAccessor the resource accessor 85 | * @return error or constructed classifier 86 | */ 87 | def apply(resourceAccessor: NetworkLoader.ResourceAccessor): Try[SceneClassifier] = { 88 | for { 89 | network ← NetworkLoader.loadMultiLayerNetwork(resourceAccessor) 90 | labels ← resourceAccessor("labels").map(is ⇒ Source.fromInputStream(is).getLines().toList) 91 | } yield new SceneClassifier(network, labels) 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /vision-identity/src/main/scala/org/eigengo/rsa/identity/v100/IdentityMatcherActor.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.identity.v100 2 | 3 | import java.util.UUID 4 | 5 | import akka.actor.{Kill, OneForOneStrategy, Props, SupervisorStrategy} 6 | import akka.persistence.{AtLeastOnceDelivery, PersistentActor} 7 | import cakesolutions.kafka.akka.{ConsumerRecords, KafkaConsumerActor} 8 | import cakesolutions.kafka.akka.KafkaConsumerActor.Confirm 9 | import cakesolutions.kafka.{KafkaProducer, KafkaProducerRecord} 10 | import com.google.protobuf.ByteString 11 | import org.eigengo.rsa.Envelope 12 | 13 | import scala.concurrent.{ExecutionContext, Future} 14 | import scala.util.Success 15 | 16 | object IdentityMatcherActor { 17 | private val extractor = ConsumerRecords.extractor[String, Envelope] 18 | 19 | def props(producerConf: KafkaProducer.Conf[String, Envelope], 20 | identityMatcher: IdentityMatcher): Props = 21 | Props(classOf[IdentityMatcherActor], producerConf, identityMatcher) 22 | 23 | } 24 | 25 | class IdentityMatcherActor(producerConf: KafkaProducer.Conf[String, Envelope], 26 | identityMatcher: IdentityMatcher) 27 | extends PersistentActor with AtLeastOnceDelivery { 28 | import IdentityMatcherActor._ 29 | lazy val Success(faceExtractor) = FaceExtractor() 30 | 31 | private[this] val producer = KafkaProducer(conf = producerConf) 32 | 33 | import scala.concurrent.duration._ 34 | 35 | override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10.seconds) { 36 | case _ ⇒ SupervisorStrategy.Restart 37 | } 38 | 39 | override val persistenceId: String = "identity-matcher-actor" 40 | 41 | def identifyFacesAndSend(identifyFaces: Seq[IdentifyFace])(implicit executor: ExecutionContext): Future[Unit] = { 42 | val sentFutures = identifyFaces.flatMap { identifyFace ⇒ 43 | faceExtractor.extract(identifyFace.image.toByteArray).map(_.map { faceImage ⇒ 44 | val face = identityMatcher.identify(faceImage.rgbBitmap.newInput()) match { 45 | case Some(identifiedFace) ⇒ Identity.Face.IdentifiedFace(identifiedFace) 46 | case None ⇒ Identity.Face.UnknownFace(Identity.UnknownFace()) 47 | } 48 | val identity = Identity(face = face) 49 | val out = Envelope(version = 100, 50 | handle = identifyFace.handle, 51 | processingTimestamp = System.nanoTime(), 52 | ingestionTimestamp = identifyFace.ingestionTimestamp, 53 | correlationId = identifyFace.correlationId, 54 | messageId = UUID.randomUUID().toString, 55 | messageType = "identity", 56 | payload = ByteString.copyFrom(identity.toByteArray)) 57 | producer.send(KafkaProducerRecord("identity", identifyFace.handle, out)).map(_ ⇒ Unit) 58 | }).getOrElse(Nil) 59 | } 60 | 61 | Future.sequence(sentFutures).map(_ ⇒ Unit) 62 | } 63 | 64 | def handleIdentifyFace: Receive = { 65 | case (deliveryId: Long, identifyFaces: IdentifyFaces) ⇒ 66 | import context.dispatcher 67 | identifyFacesAndSend(identifyFaces.identifyFaces).onSuccess { case _ ⇒ confirmDelivery(deliveryId) } 68 | case IdentifyFaces(faces) ⇒ 69 | import context.dispatcher 70 | identifyFacesAndSend(faces).onFailure { case _ ⇒ self ! Kill } 71 | } 72 | 73 | override def receiveRecover: Receive = handleIdentifyFace 74 | 75 | override def receiveCommand: Receive = handleIdentifyFace orElse { 76 | case extractor(consumerRecords) ⇒ 77 | val identifyFaces = consumerRecords.pairs.map { 78 | case (_, envelope) ⇒ IdentifyFace(envelope.ingestionTimestamp, envelope.correlationId, envelope.handle, envelope.payload) 79 | } 80 | 81 | persist(IdentifyFaces(identifyFaces = identifyFaces)) { result ⇒ 82 | deliver(self.path)(deliveryId ⇒ (deliveryId, result)) 83 | sender() ! Confirm(consumerRecords.offsets, commit = true) 84 | } 85 | case KafkaConsumerActor.BackingOff(_) ⇒ 86 | context.system.log.warning("Backing off!") 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /linter-plugin/src/main/scala/org/eigengo/rsa/linter/MarshallerLinterPlugin.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.rsa.linter 2 | 3 | import scala.tools.nsc.plugins.{Plugin, PluginComponent} 4 | import scala.tools.nsc.{Global, Phase} 5 | 6 | /** 7 | * Reports compile errors in code that attempts to use hand-rolled marshallers 8 | * @param global the NSC global 9 | */ 10 | class MarshallerLinterPlugin(val global: Global) extends Plugin { 11 | plugin ⇒ 12 | 13 | override val name: String = "Marshaller Linter" 14 | override val description: String = "Verifies the coding standards of marshalling code" 15 | override val components: List[PluginComponent] = List(component) 16 | 17 | /** 18 | * Introduces the ``marshaller-linter`` phase after ``typer``, before ``patmat``. 19 | */ 20 | private object component extends PluginComponent { 21 | override val global: Global = plugin.global 22 | override val phaseName: String = "marshaller-linter" 23 | override val runsBefore = List("patmat") 24 | override val runsAfter: List[String] = List("typer") 25 | 26 | import global._ 27 | 28 | override def newPhase(prev: Phase): Phase = new StdPhase(prev) { 29 | 30 | override def apply(unit: CompilationUnit): Unit = { 31 | // the permit 32 | val permitAnnotationType = rootMirror.getClassIfDefined("org.eigengo.rsa.ScalaPBMarshalling.permit").tpe 33 | // the rejected types; note that they are the erased types, because we don't really want to 34 | // or need to mess around with variance; checking the erased types is sufficient in this plugin 35 | val rejectedRhsTypes = List("akka.http.scaladsl.marshalling.Marshaller", "akka.http.scaladsl.unmarshalling.Unmarshaller") 36 | .map(name ⇒ rootMirror.getClassIfDefined(name).tpe.erasure) 37 | 38 | // Expands all child trees of ``tree``, returning flattened iterator of trees. 39 | def allTrees(tree: Tree): Iterator[Tree] = 40 | Iterator(tree, analyzer.macroExpandee(tree)).filter(_ != EmptyTree) 41 | .flatMap(t ⇒ Iterator(t) ++ t.children.iterator.flatMap(allTrees)) 42 | 43 | // checks that the permit annotation is present on the given ``symbol``. 44 | def hasPermitAnnotation(symbol: global.Symbol): Boolean = 45 | Option(symbol).forall(_.annotations.exists(_.tpe <:< permitAnnotationType)) 46 | 47 | // for now, rejection is the type name that we reject 48 | type Rejection = String 49 | // checks the tree for disallowed type 50 | def rejectHandRolled(tree: Tree): Option[Rejection] = { 51 | if (tree.tpe <:< definitions.NullTpe) None 52 | else rejectedRhsTypes.find(rejectedType ⇒ tree.tpe.dealiasWiden.erasure <:< rejectedType).map(_.toString()) 53 | // ^ ^ ^ 54 | // note the checks are done using the de-aliased, widened and erased types 55 | } 56 | 57 | // check all expanded trees of each compilation unit; find 58 | // 59 | // val x = `rhs` 60 | // val x: Tpt = `rhs` 61 | // implicit def x = `rhs` 62 | // implicit def x: Tpt = `rhs` 63 | // 64 | // Check the explicit types or the type of the RHS and reject those that are listed in 65 | // ``rejectedRhsTypes``. 66 | // 67 | allTrees(unit.body).foreach { 68 | case d@ValDef(mods, _, tpt, rhs) if !hasPermitAnnotation(rhs.symbol) ⇒ 69 | rejectHandRolled(rhs).orElse(rejectHandRolled(tpt)).foreach { rejection ⇒ 70 | global.globalError(d.pos, s"Cannot hand-roll val of type $rejection.") 71 | } 72 | case d@DefDef(mods, _, _, _, tpt, rhs) if mods.isImplicit && !hasPermitAnnotation(d.symbol) ⇒ 73 | rejectHandRolled(rhs).orElse(rejectHandRolled(tpt)).foreach { rejection ⇒ 74 | global.globalError(d.pos, s"Cannot hand-roll implicit def returning $rejection.") 75 | } 76 | case _ ⇒ // noop 77 | } 78 | } 79 | } 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /deeplearning4j-common/src/main/scala/org/eigengo/rsa/deeplearning4j/NetworkLoader.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.deeplearning4j 20 | 21 | import java.io.InputStream 22 | 23 | import scala.util.{Failure, Success, Try} 24 | 25 | /** 26 | * Contains convenience functions to load DL4J networks using a common naming 27 | * schemes. 28 | */ 29 | object NetworkLoader { 30 | 31 | type ResourceAccessor = String ⇒ Try[InputStream] 32 | import java.io._ 33 | 34 | import org.deeplearning4j.nn.conf.MultiLayerConfiguration 35 | import org.deeplearning4j.nn.multilayer.MultiLayerNetwork 36 | import org.nd4j.linalg.api.ndarray.INDArray 37 | import org.nd4j.linalg.factory.Nd4j 38 | 39 | import scala.io.Source 40 | 41 | /** 42 | * Function that can be used as the ResourceAccessor to delegate amongst many resourceAccessors, 43 | * where it is expected that one of them might succeed 44 | * 45 | * @param resourceAccessor the first resource accessor 46 | * @param resourceAccessors the remaining resource accessors 47 | * @param name the resource name 48 | * @return the resource stream 49 | */ 50 | def fallbackResourceAccessor(resourceAccessor: ResourceAccessor, resourceAccessors: ResourceAccessor*)(name: String): Try[InputStream] = { 51 | resourceAccessors.foldLeft(resourceAccessor(name))((result, ra) ⇒ result.orElse(ra(name))) 52 | } 53 | 54 | /** 55 | * Function that can be used as the ResourceAccessor for filesystem resources 56 | * @param prefix the prefix, e.g. "/opt/models/scene"; most likely a good idea to start with "/" 57 | * @param name the resource name 58 | * @return the resource stream 59 | */ 60 | def filesystemResourceAccessor(prefix: String)(name: String): Try[InputStream] = { 61 | Try(new FileInputStream(new File(prefix, name))) 62 | } 63 | 64 | /** 65 | * Function that can be used as the ResourceAccessor for classpath resources 66 | * @param clazz the class to load the resources from 67 | * @param prefix the prefix, e.g. "/models"; remember to start with "/" 68 | * @param name the resource name 69 | * @return the resource stream 70 | */ 71 | def classpathResourceAccessor(clazz: Class[_], prefix: String = "/")(name: String): Try[InputStream] = { 72 | val resourceName = prefix + name 73 | val is = getClass.getResourceAsStream(resourceName) 74 | if (is == null) { 75 | Failure(new FileNotFoundException(resourceName)) 76 | } else { 77 | Success(is) 78 | } 79 | } 80 | 81 | /** 82 | * Constructs the ``MultiLayerNetwork`` from two files 83 | * at the given ``basePath``. The three files are 84 | * 85 | * - the network configuration in resource named ``config`` 86 | * - the network parameters in resource named ``params`` 87 | * 88 | * @param resourceAccessor the accessor for the given resource 89 | * @return error or loaded & initialized ``MultiLayerNetwork`` 90 | */ 91 | def loadMultiLayerNetwork(resourceAccessor: ResourceAccessor): Try[MultiLayerNetwork] = { 92 | 93 | def loadNetworkConfiguration(): Try[MultiLayerConfiguration] = 94 | resourceAccessor("config").flatMap { config ⇒ Try { 95 | val configJson = Source.fromInputStream(config).mkString 96 | MultiLayerConfiguration.fromJson(configJson) 97 | } 98 | } 99 | 100 | def loadParams(): Try[INDArray] = 101 | resourceAccessor("params").flatMap { params ⇒ Try { 102 | val is = new DataInputStream(new BufferedInputStream(params)) 103 | val result = Nd4j.read(is) 104 | is.close() 105 | result 106 | } 107 | } 108 | 109 | def initializeNetwork(configuration: MultiLayerConfiguration, params: INDArray): MultiLayerNetwork = { 110 | val network = new MultiLayerNetwork(configuration) 111 | network.init() 112 | network.setParams(params) 113 | network 114 | } 115 | 116 | for { 117 | configuration ← loadNetworkConfiguration() 118 | params ← loadParams() 119 | } yield initializeNetwork(configuration, params) 120 | 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/ScalaPBMarshalling.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa 20 | 21 | import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller} 22 | import akka.http.scaladsl.model.MediaType.Compressible 23 | import akka.http.scaladsl.model.ws.TextMessage 24 | import akka.http.scaladsl.model.{ContentType, ContentTypes, HttpEntity, MediaType} 25 | import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException 26 | import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller} 27 | import akka.http.scaladsl.util.FastFuture 28 | import com.google.protobuf.CodedInputStream 29 | import com.trueaccord.scalapb.json.JsonFormat 30 | import com.trueaccord.scalapb.{GeneratedMessage, GeneratedMessageCompanion, Message} 31 | 32 | import scala.annotation.StaticAnnotation 33 | import scala.concurrent.Future 34 | 35 | trait ScalaPBMarshalling { 36 | private val protobufContentType = ContentType(MediaType.applicationBinary("octet-stream", Compressible, "proto")) 37 | private val applicationJsonContentType = ContentTypes.`application/json` 38 | 39 | trait ToTextMessageMarshaller[-A] { 40 | def apply(value: A): TextMessage 41 | } 42 | 43 | def marshalTextMessage[A](value: A)(implicit m: ToTextMessageMarshaller[A]): TextMessage = m(value) 44 | 45 | implicit object StringToTextMessageMarshaller extends ToTextMessageMarshaller[String] { 46 | override def apply(value: String): TextMessage = TextMessage('"' + value + '"') 47 | } 48 | 49 | implicit def scalaPBToTextMessageMarshaller[A <: GeneratedMessage]: ToTextMessageMarshaller[A] = new ToTextMessageMarshaller[A] { 50 | override def apply(value: A): TextMessage = TextMessage(JsonFormat.toJsonString(value)) 51 | } 52 | 53 | implicit def listToEntityMarshaller[A : ToTextMessageMarshaller]: ToTextMessageMarshaller[Seq[A]] = new ToTextMessageMarshaller[Seq[A]] { 54 | val m = implicitly[ToTextMessageMarshaller[A]] 55 | 56 | override def apply(value: Seq[A]): TextMessage = { 57 | val result = StringBuilder.newBuilder 58 | result.append("[") 59 | value.foreach { x ⇒ 60 | if (result.length > 1) result.append(",") 61 | result.append(m(x) match { 62 | case TextMessage.Strict(text) ⇒ text 63 | case _ ⇒ "?" 64 | }) 65 | } 66 | result.append("]") 67 | TextMessage(result.toString()) 68 | } 69 | } 70 | 71 | @ScalaPBMarshalling.permit 72 | def scalaPBFromRequestUnmarshaller[O <: GeneratedMessage with Message[O]](companion: GeneratedMessageCompanion[O]): FromEntityUnmarshaller[O] = { 73 | Unmarshaller.withMaterializer[HttpEntity, O](_ ⇒ implicit mat ⇒ { 74 | case entity@HttpEntity.Strict(`applicationJsonContentType`, data) ⇒ 75 | val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(entity) 76 | FastFuture.successful(JsonFormat.fromJsonString(data.decodeString(charBuffer.nioCharset().name()))(companion)) 77 | case entity@HttpEntity.Strict(`protobufContentType`, data) ⇒ 78 | FastFuture.successful(companion.parseFrom(CodedInputStream.newInstance(data.asByteBuffer))) 79 | case entity ⇒ 80 | Future.failed(UnsupportedContentTypeException(applicationJsonContentType, protobufContentType)) 81 | }) 82 | } 83 | 84 | @ScalaPBMarshalling.permit 85 | implicit def scalaPBToEntityMarshaller[U <: GeneratedMessage]: ToEntityMarshaller[U] = { 86 | def jsonMarshaller(): ToEntityMarshaller[U] = { 87 | val contentType = applicationJsonContentType 88 | Marshaller.withFixedContentType(contentType) { value ⇒ 89 | HttpEntity(contentType, JsonFormat.toJsonString(value)) 90 | } 91 | } 92 | 93 | def protobufMarshaller(): ToEntityMarshaller[U] = { 94 | Marshaller.withFixedContentType(protobufContentType) { value ⇒ 95 | HttpEntity(protobufContentType, value.toByteArray) 96 | } 97 | } 98 | 99 | Marshaller.oneOf(jsonMarshaller(), protobufMarshaller()) 100 | } 101 | 102 | } 103 | 104 | object ScalaPBMarshalling extends ScalaPBMarshalling { 105 | private class permit extends StaticAnnotation 106 | } 107 | -------------------------------------------------------------------------------- /vision-scene-classification/src/main/scala/org/eigengo/rsa/scene/v100/SceneClassifierActor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.scene.v100 20 | 21 | import java.io.ByteArrayInputStream 22 | import java.util.UUID 23 | 24 | import akka.actor.{Actor, OneForOneStrategy, Props, SupervisorStrategy} 25 | import cakesolutions.kafka._ 26 | import cakesolutions.kafka.akka.KafkaConsumerActor.{Confirm, Subscribe, Unsubscribe} 27 | import cakesolutions.kafka.akka.{ConsumerRecords, KafkaConsumerActor} 28 | import com.google.protobuf.ByteString 29 | import com.typesafe.config.Config 30 | import org.apache.kafka.common.serialization.{StringDeserializer, StringSerializer} 31 | import org.eigengo.rsa.Envelope 32 | import org.eigengo.rsa.deeplearning4j.NetworkLoader 33 | 34 | import scala.concurrent.Future 35 | import scala.util.Success 36 | 37 | object SceneClassifierActor { 38 | private val extractor = ConsumerRecords.extractor[String, Envelope] 39 | 40 | def props(config: Config): Props = { 41 | val Success(sceneClassifier) = SceneClassifier( 42 | NetworkLoader.fallbackResourceAccessor( 43 | NetworkLoader.filesystemResourceAccessor("/opt/models/scene"), 44 | NetworkLoader.filesystemResourceAccessor("/Users/janmachacek/Dropbox/Models/scene") 45 | ) 46 | ) 47 | val consumerConf = KafkaConsumer.Conf( 48 | config.getConfig("kafka.consumer-config"), 49 | keyDeserializer = new StringDeserializer, 50 | valueDeserializer = KafkaDeserializer(Envelope.parseFrom) 51 | ) 52 | val consumerActorConf = KafkaConsumerActor.Conf() 53 | val producerConf = KafkaProducer.Conf( 54 | config.getConfig("kafka.scene-producer"), 55 | new StringSerializer, 56 | KafkaSerializer[Envelope](_.toByteArray) 57 | ) 58 | 59 | Props(classOf[SceneClassifierActor], consumerConf, consumerActorConf, producerConf, sceneClassifier) 60 | } 61 | 62 | } 63 | 64 | class SceneClassifierActor(consumerConf: KafkaConsumer.Conf[String, Envelope], consumerActorConf: KafkaConsumerActor.Conf, 65 | producerConf: KafkaProducer.Conf[String, Envelope], 66 | sceneClassifier: SceneClassifier) extends Actor { 67 | import SceneClassifierActor._ 68 | 69 | private[this] val kafkaConsumerActor = context.actorOf( 70 | KafkaConsumerActor.props(consumerConf = consumerConf, actorConf = consumerActorConf, downstreamActor = self) 71 | ) 72 | private[this] val producer = KafkaProducer(conf = producerConf) 73 | 74 | import scala.concurrent.duration._ 75 | override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10.seconds) { 76 | case _ ⇒ SupervisorStrategy.Restart 77 | } 78 | 79 | @scala.throws(classOf[Exception]) 80 | override def preStart(): Unit = { 81 | kafkaConsumerActor ! Subscribe.AutoPartition(Seq("tweet-image")) 82 | } 83 | 84 | @scala.throws(classOf[Exception]) 85 | override def postStop(): Unit = { 86 | kafkaConsumerActor ! Unsubscribe 87 | } 88 | 89 | override def receive: Receive = { 90 | case extractor(consumerRecords) ⇒ 91 | val futures = consumerRecords.pairs.flatMap { 92 | case (_, envelope) ⇒ 93 | val is = new ByteArrayInputStream(envelope.payload.toByteArray) 94 | sceneClassifier.classify(is).map { scene ⇒ 95 | val out = Envelope(version = 100, 96 | handle = envelope.handle, 97 | processingTimestamp = System.nanoTime(), 98 | ingestionTimestamp = envelope.ingestionTimestamp, 99 | correlationId = envelope.correlationId, 100 | messageId = UUID.randomUUID().toString, 101 | messageType = "scene", 102 | payload = ByteString.copyFrom(scene.toByteArray)) 103 | 104 | producer.send(KafkaProducerRecord("scene", envelope.handle, out)) 105 | }.toOption 106 | } 107 | import context.dispatcher 108 | Future.sequence(futures).onSuccess { 109 | case _ ⇒ kafkaConsumerActor ! Confirm(consumerRecords.offsets, commit = true) 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /vision-identity/src/train/java/org/eigengo/rsa/identity/v100/DeepFaceVariant.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100; 20 | 21 | import org.deeplearning4j.nn.api.OptimizationAlgorithm; 22 | import org.deeplearning4j.nn.conf.GradientNormalization; 23 | import org.deeplearning4j.nn.conf.MultiLayerConfiguration; 24 | import org.deeplearning4j.nn.conf.NeuralNetConfiguration; 25 | import org.deeplearning4j.nn.conf.Updater; 26 | import org.deeplearning4j.nn.conf.layers.ConvolutionLayer; 27 | import org.deeplearning4j.nn.conf.layers.DenseLayer; 28 | import org.deeplearning4j.nn.conf.layers.OutputLayer; 29 | import org.deeplearning4j.nn.conf.layers.SubsamplingLayer; 30 | import org.deeplearning4j.nn.multilayer.MultiLayerNetwork; 31 | import org.deeplearning4j.nn.weights.WeightInit; 32 | import org.nd4j.linalg.lossfunctions.LossFunctions; 33 | 34 | /** 35 | * Architecture partially based on DeepFace: http://mmlab.ie.cuhk.edu.hk/pdf/YiSun_CVPR14.pdf 36 | * 37 | * Used for problems like LFW 38 | */ 39 | public class DeepFaceVariant { 40 | 41 | private int height; 42 | private int width; 43 | private int channels; 44 | private int numLabels; 45 | private long seed; 46 | private int iterations; 47 | 48 | public DeepFaceVariant(int height, int width, int channels, int numLabels, long seed, int iterations) { 49 | this.height = height; 50 | this.width = width; 51 | this.channels = channels; 52 | this.numLabels = numLabels; 53 | this.seed = seed; 54 | this.iterations = iterations; 55 | } 56 | 57 | public MultiLayerNetwork init() { 58 | MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder() 59 | .seed(seed) 60 | .iterations(iterations) 61 | .activation("relu") 62 | .weightInit(WeightInit.XAVIER) 63 | .gradientNormalization(GradientNormalization.RenormalizeL2PerLayer) 64 | .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT) 65 | .learningRate(0.01) 66 | .momentum(0.9) 67 | .regularization(true) 68 | .l2(1e-3) 69 | .updater(Updater.ADAGRAD) 70 | .useDropConnect(true) 71 | .list() 72 | .layer(0, new ConvolutionLayer.Builder(4, 4) 73 | .name("cnn1") 74 | .nIn(channels) 75 | .stride(1, 1) 76 | .nOut(20) 77 | .build()) 78 | .layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2}) 79 | .name("pool1") 80 | .build()) 81 | .layer(2, new ConvolutionLayer.Builder(3, 3) 82 | .name("cnn2") 83 | .stride(1,1) 84 | .nOut(40) 85 | .build()) 86 | .layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2}) 87 | .name("pool2") 88 | .build()) 89 | .layer(4, new ConvolutionLayer.Builder(3, 3) 90 | .name("cnn3") 91 | .stride(1,1) 92 | .nOut(60) 93 | .build()) 94 | .layer(5, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2}) 95 | .name("pool3") 96 | .build()) 97 | .layer(6, new ConvolutionLayer.Builder(2, 2) 98 | .name("cnn3") 99 | .stride(1,1) 100 | .nOut(80) 101 | .build()) 102 | .layer(7, new DenseLayer.Builder() 103 | .name("ffn1") 104 | .nOut(160) 105 | .dropOut(0.5) 106 | .build()) 107 | .layer(8, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) 108 | .nOut(numLabels) 109 | .activation("softmax") 110 | .build()) 111 | .backprop(true).pretrain(false) 112 | .cnnInputSize(height, width, channels) 113 | .build(); 114 | 115 | MultiLayerNetwork network = new MultiLayerNetwork(conf); 116 | network.init(); 117 | return network; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /protobuf-testkit/src/main/scala/org/eigengo/protobufcheck/ProtobufGen.scala: -------------------------------------------------------------------------------- 1 | package org.eigengo.protobufcheck 2 | 3 | import com.google.protobuf.Descriptors 4 | import com.google.protobuf.Descriptors.FieldDescriptor 5 | import com.trueaccord.scalapb.{GeneratedMessage, GeneratedMessageCompanion, Message} 6 | import org.scalacheck.{Arbitrary, Gen} 7 | 8 | object ProtobufGen { 9 | 10 | /** 11 | * Returns a generator for the ScalaPB-based message defined by its ``companion``. The companion is typically 12 | * generated during the ``protobuf:protobuf-generate`` sbt task, which also typically runs during the ``package`` 13 | * task. 14 | * 15 | * @param companion the companion for the message type ``M`` 16 | * @tparam M the message type 17 | * @return generator for arbitrary messages of type ``M`` 18 | */ 19 | def message[M <: GeneratedMessage with Message[M]](companion: GeneratedMessageCompanion[M]): Gen[M] = { 20 | import collection.JavaConversions._ 21 | 22 | /** 23 | * Maps the list of ``FieldDescriptor``s into a list of pairs of the descriptor with the matching 24 | * ``Gen[_]`` for that field. 25 | * @param fields the field descriptors 26 | * @return the fd and matching generator 27 | */ 28 | def generatorsFromFields(fields: List[FieldDescriptor]): List[(FieldDescriptor, Gen[Any])] = { 29 | fields.map { field ⇒ 30 | import Descriptors.FieldDescriptor._ 31 | 32 | val fieldGen: Gen[Any] = field.getType match { 33 | case Type.BOOL ⇒ Arbitrary.arbBool.arbitrary 34 | case Type.BYTES ⇒ Gen.containerOf(Arbitrary.arbByte.arbitrary) 35 | case Type.DOUBLE ⇒ Arbitrary.arbDouble.arbitrary 36 | case Type.ENUM ⇒ Gen.oneOf(companion.enumCompanionForField(field).descriptor.getValues.toList) 37 | case Type.FIXED32 ⇒ ??? 38 | case Type.FIXED64 ⇒ 39 | case Type.FLOAT ⇒ Arbitrary.arbFloat.arbitrary 40 | case Type.GROUP ⇒ ??? 41 | case Type.INT32 ⇒ Arbitrary.arbInt.arbitrary //.withFilter(_ >= 0) 42 | case Type.INT64 ⇒ Arbitrary.arbBigInt.arbitrary //.withFilter(_ >= 0) 43 | case Type.MESSAGE ⇒ existentialMessage(companion.messageCompanionForField(field)) 44 | case Type.SFIXED32 ⇒ ??? 45 | case Type.SFIXED64 ⇒ ??? 46 | case Type.SINT32 ⇒ Arbitrary.arbInt.arbitrary 47 | case Type.SINT64 ⇒ Arbitrary.arbInt.arbitrary 48 | case Type.STRING ⇒ Arbitrary.arbString.arbitrary 49 | case Type.UINT32 ⇒ Arbitrary.arbInt.arbitrary //.withFilter(_ >= 0) 50 | case Type.UINT64 ⇒ Arbitrary.arbBigInt.arbitrary //.withFilter(_ >= 0) 51 | } 52 | 53 | if (field.isRepeated) { 54 | (field, Gen.listOf(fieldGen)) 55 | } else { 56 | (field, fieldGen) 57 | } 58 | } 59 | } 60 | 61 | def flatten(generators: List[(FieldDescriptor, Gen[Any])]): Gen[Map[FieldDescriptor, Any]] = { 62 | assert(generators.nonEmpty, "The list of generators must not be empty.") 63 | 64 | val (ffd, ffg) = generators.head 65 | val firstFieldGenerator = ffg.map(x ⇒ Map(ffd → x)) 66 | val remainingFieldGenerators = generators.tail 67 | 68 | remainingFieldGenerators.foldLeft(firstFieldGenerator) { 69 | case (result, (fd, fg)) ⇒ result.flatMap(m ⇒ fg.map(x ⇒ Map(fd → x) ++ m)) 70 | } 71 | } 72 | 73 | /** 74 | * Constructs a generator for instances generated from generated fields from the ``companion``. 75 | * @param companion the companion 76 | * @return the generator of messages constructed using the companion 77 | */ 78 | def existentialMessage(companion: GeneratedMessageCompanion[_]): Gen[_] = { 79 | val oneOfFields = companion.descriptor.getOneofs.toList 80 | val fields = companion.descriptor.getFields.toList 81 | 82 | // first, construct generators for plain fields: i.e. those that are not algebraic 83 | val plainGenerator = flatten(generatorsFromFields(fields.filterNot(oneOfFields.flatMap(_.getFields.toList).contains))) 84 | // next, construct generators for algebraic fields by constructing a generator that selects one field from each one-of group, 85 | // then constructing a generator for that field and folding the generators 86 | val oneOfGenerators = oneOfFields.map { oneOf ⇒ 87 | Gen.oneOf(oneOf.getFields.toList).flatMap { fd ⇒ 88 | val (_, generator) = generatorsFromFields(List(fd)).head 89 | generator.map(x ⇒ Map(fd → x)) 90 | } 91 | } 92 | 93 | // finally, combine the generators for the one-of fields with the plain fields 94 | val combinedGenerators = oneOfGenerators match { 95 | case Nil ⇒ plainGenerator 96 | case (h::t) ⇒ 97 | val combinedOneOfGenerator = t.foldLeft(h)((result, gen) ⇒ result.flatMap(m ⇒ gen.map(x ⇒ m ++ x))) 98 | combinedOneOfGenerator.flatMap(o ⇒ plainGenerator.map(x ⇒ o ++ x)) 99 | } 100 | 101 | // use the companion to construct an instance from the field map 102 | combinedGenerators.map(companion.fromFieldsMap) 103 | } 104 | 105 | // the innards of ScalaPB do not allow to express properly compile-time type checking: it would be only 106 | // possible to check the top-level type, but possible inner messages would be left "untyped", so for the 107 | // sake of commonality, we use ``existentialMessage(...).map(_.asInstanceOf[M])`` here. 108 | existentialMessage(companion).map(_.asInstanceOf[M]) 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /dashboard/src/main/scala/org/eigengo/rsa/dashboard/v100/HandleSummaryItemsBuilder.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.dashboard.v100 20 | 21 | import com.trueaccord.scalapb.GeneratedMessage 22 | import org.eigengo.rsa.identity.v100.Identity 23 | import org.eigengo.rsa.identity.v100.Identity.{IdentifiedFace, UnknownFace} 24 | import org.eigengo.rsa.scene.v100.Scene 25 | import org.eigengo.rsa.text.v100.Text 26 | import org.eigengo.rsa.{Envelope, identity, scene} 27 | 28 | import scala.collection.SortedSet 29 | 30 | object HandleSummaryItemsBuilder { 31 | implicit object EnvelopeOrdering extends Ordering[Envelope] { 32 | override def compare(x: Envelope, y: Envelope): Int = x.ingestionTimestamp.compare(y.ingestionTimestamp) 33 | } 34 | 35 | implicit object IdentifiedFaceOrdering extends Ordering[IdentifiedFace] { 36 | override def compare(x: IdentifiedFace, y: IdentifiedFace): Int = x.name.compareTo(y.name) 37 | } 38 | 39 | implicit object UnknownFaceOrdering extends Ordering[UnknownFace] { 40 | override def compare(x: UnknownFace, y: UnknownFace): Int = 1 41 | } 42 | 43 | } 44 | 45 | class HandleSummaryItemsBuilder(maximumMessages: Int = 500) { 46 | import HandleSummaryItemsBuilder._ 47 | 48 | import scala.concurrent.duration._ 49 | 50 | private var messages = List.empty[Envelope] 51 | 52 | private def acceptableIngestionTimestampDiff(m1: Envelope)(m2: Envelope): Boolean = 53 | math.abs(m1.ingestionTimestamp - m2.ingestionTimestamp) < 30.seconds.toNanos 54 | 55 | def isActive(lastIngestedMessage: Envelope): Boolean = 56 | messages.lastOption.forall(acceptableIngestionTimestampDiff(lastIngestedMessage)) 57 | 58 | def build(): List[HandleSummary.Item] = { 59 | def messageFromEnvelope(envelope: Envelope): Option[GeneratedMessage] = { 60 | (envelope.version, envelope.messageType) match { 61 | case (100, "identity") ⇒ identity.v100.Identity.validate(envelope.payload.toByteArray).toOption 62 | case (100, "scene") ⇒ scene.v100.Scene.validate(envelope.payload.toByteArray).toOption 63 | case (100, "text") ⇒ org.eigengo.rsa.text.v100.Text.validate(envelope.payload.toByteArray).toOption 64 | case _ ⇒ None 65 | } 66 | } 67 | 68 | def itemFromWindow(window: List[Envelope]): Option[HandleSummary.Item] = { 69 | val windowSize = (window.last.ingestionTimestamp - window.head.ingestionTimestamp).nanos.toMillis.toInt 70 | val groups = window.flatMap(msg ⇒ messageFromEnvelope(msg)).groupBy(_.getClass) 71 | 72 | val identities = groups 73 | .get(classOf[Identity]) 74 | .map(_.asInstanceOf[List[Identity]]) 75 | .map(_.foldLeft((SortedSet.empty[Identity.IdentifiedFace], SortedSet.empty[Identity.UnknownFace])) { 76 | case ((i, u), f) ⇒ f.face match { 77 | case Identity.Face.IdentifiedFace(value) ⇒ (i + value, u) 78 | case Identity.Face.UnknownFace(value) ⇒ (i, u + value) 79 | case _ ⇒ (i, u) 80 | } 81 | }) 82 | 83 | val sceneLabels = groups 84 | .get(classOf[Scene]) 85 | .map(_.asInstanceOf[List[Scene]]) 86 | .map(_.foldLeft(List.empty[String]) { (result, scene) ⇒ result ++ scene.labels.map(_.label) }.distinct) 87 | 88 | val areasOfText = groups 89 | .get(classOf[Text]) 90 | .map(_.asInstanceOf[List[Text]]) 91 | .map(_.foldLeft(List.empty[String]) { (result, text) ⇒ result :+ text.areas.mkString(", ") }) 92 | 93 | val sb = new StringBuilder() 94 | 95 | identities.foreach { case (identifiedFaces, unknownFaces) ⇒ 96 | if (identifiedFaces.nonEmpty || unknownFaces.nonEmpty) sb.append("with") 97 | if (identifiedFaces.nonEmpty) sb.append(s" the famous ${identifiedFaces.map(_.name).mkString(", ")}") 98 | if (unknownFaces.nonEmpty) sb.appendAll(s" ${unknownFaces.size} other people") 99 | } 100 | 101 | sceneLabels.foreach { labels ⇒ 102 | if (sb.nonEmpty) sb.append(" and ") 103 | if (labels.nonEmpty) sb.append(s"${labels.mkString(", ")}") 104 | } 105 | 106 | areasOfText.foreach { text ⇒ 107 | if (sb.nonEmpty) sb.append(" reading ") 108 | if (text.nonEmpty) sb.append(s"${text.mkString(", ")}") 109 | } 110 | 111 | if (sb.nonEmpty) Some(HandleSummary.Item(windowSize, sb.toString(), Nil)) else None 112 | } 113 | 114 | def transformMessages(): List[HandleSummary.Item] = { 115 | val windows = messages.foldLeft(List.empty[List[Envelope]]) { 116 | case (Nil, msg) ⇒ List(List(msg)) 117 | case (nel, msg) if acceptableIngestionTimestampDiff(nel.last.last)(msg) ⇒ nel.init :+ (nel.last :+ msg) 118 | case (nel, msg) ⇒ nel :+ List(msg) 119 | } 120 | 121 | windows.flatMap(itemFromWindow) 122 | } 123 | 124 | transformMessages() 125 | } 126 | 127 | def append(message: Envelope): Unit = { 128 | if (!messages.exists(_.messageId == message.messageId)) { 129 | messages = (message :: messages).takeRight(maximumMessages).sorted 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /vision-text/src/main/java/org/eigengo/rsa/text/v100/TextEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.text.v100; 20 | 21 | import akka.NotUsed; 22 | import com.fasterxml.jackson.annotation.JsonCreator; 23 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 24 | import com.google.common.base.Objects; 25 | import com.google.protobuf.ByteString; 26 | import com.lightbend.lagom.javadsl.persistence.AggregateEvent; 27 | import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; 28 | import com.lightbend.lagom.javadsl.persistence.PersistentEntity; 29 | import com.lightbend.lagom.serialization.CompressedJsonable; 30 | import com.lightbend.lagom.serialization.Jsonable; 31 | import org.eigengo.rsa.Envelope; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | import scala.collection.JavaConversions; 35 | import scala.collection.Seq; 36 | 37 | import javax.annotation.concurrent.Immutable; 38 | import java.util.Arrays; 39 | import java.util.Optional; 40 | import java.util.UUID; 41 | 42 | class TextEntity extends PersistentEntity { 43 | private static final Logger log = LoggerFactory.getLogger(TextEntity.class); 44 | 45 | private TextEntityEvent.Ocred ocr(TextEntityCommand.Ocr ocr) { 46 | log.info("Performing OCR for {}", ocr.correlationId); 47 | return new TextEntityEvent.Ocred(entityId(), ocr.correlationId, ocr.ingestionTimestamp, System.nanoTime(), new String[]{"text"}); 48 | } 49 | 50 | public Behavior initialBehavior(Optional snapshotState) { 51 | BehaviorBuilder b = newBehaviorBuilder(null); 52 | b.setCommandHandler(TextEntityCommand.Ocr.class, (cmd, ctx) -> { 53 | TextEntityEvent.Ocred event = ocr(cmd); 54 | return ctx.thenPersist(event, e -> ctx.reply(NotUsed.getInstance())); 55 | } 56 | ); 57 | b.setEventHandler(TextEntityEvent.Ocred.class, e -> NotUsed.getInstance()); 58 | return b.build(); 59 | } 60 | } 61 | 62 | interface TextEntityEvent extends Jsonable { 63 | class OcredTag { 64 | static AggregateEventTag INSTANCE = AggregateEventTag.of(Ocred.class, "text"); 65 | } 66 | 67 | @Immutable 68 | @JsonDeserialize 69 | class Ocred implements TextEntityEvent, CompressedJsonable, AggregateEvent { 70 | final String handle; 71 | final String correlationId; 72 | final long ingestionTimestamp; 73 | final long processingTimestamp; 74 | final String[] areas; 75 | 76 | @JsonCreator 77 | Ocred(String handle, String correlationId, long ingestionTimestamp, long processingTimestamp, String[] areas) { 78 | this.handle = handle; 79 | this.correlationId = correlationId; 80 | this.ingestionTimestamp = ingestionTimestamp; 81 | this.processingTimestamp = processingTimestamp; 82 | this.areas = areas; 83 | } 84 | 85 | Envelope envelope() { 86 | Seq areas = JavaConversions.asScalaBuffer(Arrays.asList(this.areas)); 87 | Text payload = Text.apply(areas); 88 | 89 | return Envelope.apply(100, this.processingTimestamp, this.ingestionTimestamp, this.handle, this.correlationId, 90 | UUID.randomUUID().toString(), "text", ByteString.copyFrom(payload.toByteArray())); 91 | } 92 | 93 | @Override 94 | public boolean equals(Object o) { 95 | if (this == o) return true; 96 | if (o == null || getClass() != o.getClass()) return false; 97 | Ocred ocred = (Ocred) o; 98 | return ingestionTimestamp == ocred.ingestionTimestamp && 99 | processingTimestamp == ocred.processingTimestamp && 100 | Objects.equal(correlationId, ocred.correlationId) && 101 | Objects.equal(handle, ocred.handle) && 102 | Objects.equal(areas, ocred.areas); 103 | } 104 | 105 | @Override 106 | public int hashCode() { 107 | return Objects.hashCode(correlationId, handle, ingestionTimestamp, processingTimestamp, areas); 108 | } 109 | 110 | @Override 111 | public AggregateEventTag aggregateTag() { 112 | return OcredTag.INSTANCE; 113 | } 114 | } 115 | 116 | } 117 | 118 | interface TextEntityCommand extends Jsonable { 119 | 120 | @Immutable 121 | @JsonDeserialize 122 | class Ocr implements TextEntityCommand, CompressedJsonable, PersistentEntity.ReplyType { 123 | final String correlationId; 124 | final long ingestionTimestamp; 125 | final byte[] image; 126 | 127 | @JsonCreator 128 | public Ocr(String correlationId, long ingestionTimestamp, byte[] image) { 129 | this.correlationId = correlationId; 130 | this.ingestionTimestamp = ingestionTimestamp; 131 | this.image = image; 132 | } 133 | 134 | @Override 135 | public boolean equals(Object o) { 136 | if (this == o) return true; 137 | if (o == null || getClass() != o.getClass()) return false; 138 | Ocr ocr = (Ocr) o; 139 | return ingestionTimestamp == ocr.ingestionTimestamp && 140 | Objects.equal(correlationId, ocr.correlationId) && 141 | Objects.equal(image, ocr.image); 142 | } 143 | 144 | @Override 145 | public int hashCode() { 146 | return Objects.hashCode(correlationId, ingestionTimestamp, image); 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /dashboard/src/main/resources/dashboard.conf: -------------------------------------------------------------------------------- 1 | app { 2 | kafka { 3 | consumer-config { 4 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 5 | group.id = "dashboard" 6 | auto.offset.reset = "earliest" 7 | } 8 | } 9 | } 10 | 11 | akka.loglevel = "INFO" 12 | akka.persistence.journal.plugin = "cassandra-journal" 13 | akka.persistence.snapshot-store.plugin = "cassandra-snapshot-store" 14 | 15 | akka.actor { 16 | 17 | serializers { 18 | spb = "org.eigengo.rsa.serialization.ScalaPBSerializer" 19 | } 20 | 21 | serialization-bindings { 22 | "com.trueaccord.scalapb.GeneratedMessage" = spb 23 | } 24 | 25 | } 26 | 27 | cassandra-journal { 28 | 29 | # FQCN of the cassandra journal plugin 30 | class = "akka.persistence.cassandra.journal.CassandraJournal" 31 | 32 | # Comma-separated list of contact points in the cluster 33 | contact-points = [${?CASSANDRA_JOURNAL_CPS}] 34 | 35 | # Port of contact points in the cluster 36 | port = 9042 37 | 38 | # Name of the keyspace to be created/used by the journal 39 | keyspace = "akka_dashboard" 40 | 41 | # Parameter indicating whether the journal keyspace should be auto created 42 | keyspace-autocreate = true 43 | 44 | # In case that schema creation failed you can define a number of retries before giving up. 45 | keyspace-autocreate-retries = 1 46 | 47 | # The number of retries when a write request returns a TimeoutException or an UnavailableException. 48 | write-retries = 3 49 | 50 | # Deletes are achieved using a metadata entry and then the actual messages are deleted asynchronously 51 | # Number of retries before giving up 52 | delete-retries = 3 53 | 54 | # Number of retries before giving up connecting to the cluster 55 | connect-retries = 3 56 | 57 | # Delay between connection retries 58 | connect-retry-delay = 5s 59 | 60 | # Name of the table to be created/used by the journal 61 | table = "messages" 62 | 63 | # Compaction strategy for the journal table 64 | table-compaction-strategy { 65 | class = "SizeTieredCompactionStrategy" 66 | } 67 | 68 | # Name of the table to be created/used for storing metadata 69 | metadata-table = "metadata" 70 | 71 | # Name of the table to be created/used for journal config 72 | config-table = "config" 73 | 74 | # replication strategy to use. SimpleStrategy or NetworkTopologyStrategy 75 | replication-strategy = "SimpleStrategy" 76 | 77 | # Replication factor to use when creating a keyspace. Is only used when replication-strategy is SimpleStrategy. 78 | replication-factor = 1 79 | 80 | # Replication factor list for data centers, e.g. ["dc1:3", "dc2:2"]. Is only used when replication-strategy is NetworkTopologyStrategy. 81 | data-center-replication-factors = [] 82 | 83 | # Write consistency level 84 | write-consistency = "QUORUM" 85 | 86 | # Read consistency level 87 | read-consistency = "QUORUM" 88 | 89 | max-message-batch-size = 200 90 | 91 | # Target number of entries per partition (= columns per row). 92 | # Must not be changed after table creation (currently not checked). 93 | # This is "target" as AtomicWrites that span parition boundaries will result in bigger partitions to ensure atomicity. 94 | target-partition-size = 500000 95 | 96 | # Maximum size of result set 97 | max-result-size = 50001 98 | 99 | # Dispatcher for the plugin actor. 100 | plugin-dispatcher = "cassandra-journal.default-dispatcher" 101 | 102 | # Dispatcher for fetching and replaying messages 103 | replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" 104 | 105 | # Default dispatcher for plugin actor. 106 | default-dispatcher { 107 | type = Dispatcher 108 | executor = "fork-join-executor" 109 | fork-join-executor { 110 | parallelism-min = 2 111 | parallelism-max = 8 112 | } 113 | } 114 | 115 | # The time to wait before cassandra will remove the thombstones created for deleted entries. 116 | # cfr. gc_grace_seconds table property documentation on http://www.datastax.com/documentation/cql/3.1/cql/cql_reference/tabProp.html 117 | gc-grace-seconds = 864000 118 | } 119 | 120 | cassandra-snapshot-store { 121 | 122 | # FQCN of the cassandra snapshot store plugin 123 | class = "akka.persistence.cassandra.snapshot.CassandraSnapshotStore" 124 | 125 | # Comma-separated list of contact points in the cluster 126 | contact-points = [${?CASSANDRA_SNAPSHOT_CPS}] 127 | 128 | # Port of contact points in the cluster 129 | port = 9042 130 | 131 | # Name of the keyspace to be created/used by the snapshot store 132 | keyspace = "akka_snapshot_dashboard" 133 | 134 | # Parameter indicating whether the snapshot keyspace should be auto created 135 | keyspace-autocreate = true 136 | 137 | # In case that schema creation failed you can define a number of retries before giving up. 138 | keyspace-autocreate-retries = 1 139 | 140 | # Number of retries before giving up connecting to the cluster 141 | connect-retries = 3 142 | 143 | # Delay between connection retries 144 | connect-retry-delay = 5s 145 | 146 | # Name of the table to be created/used by the snapshot store 147 | table = "snapshots" 148 | 149 | # Compaction strategy for the snapshot table 150 | table-compaction-strategy { 151 | class = "SizeTieredCompactionStrategy" 152 | } 153 | 154 | # Name of the table to be created/used for journal config 155 | config-table = "config" 156 | 157 | # Name of the table to be created/used for storing metadata 158 | metadata-table = "metadata" 159 | 160 | # replication strategy to use. SimpleStrategy or NetworkTopologyStrategy 161 | replication-strategy = "SimpleStrategy" 162 | 163 | # Replication factor to use when creating a keyspace. Is only used when replication-strategy is SimpleStrategy. 164 | replication-factor = 1 165 | 166 | # Replication factor list for data centers, e.g. ["dc1:3", "dc2:2"]. Is only used when replication-strategy is NetworkTopologyStrategy. 167 | data-center-replication-factors = [] 168 | 169 | # Write consistency level 170 | write-consistency = "ONE" 171 | 172 | # Read consistency level 173 | read-consistency = "ONE" 174 | 175 | # Maximum number of snapshot metadata to load per recursion (when trying to 176 | # find a snapshot that matches specified selection criteria). Only increase 177 | # this value when selection criteria frequently select snapshots that are 178 | # much older than the most recent snapshot i.e. if there are much more than 179 | # 10 snapshots between the most recent one and selected one. This setting is 180 | # only for increasing load efficiency of snapshots. 181 | max-metadata-result-size = 10 182 | 183 | # Maximum size of result set 184 | max-result-size = 50001 185 | 186 | # Dispatcher for the plugin actor. 187 | plugin-dispatcher = "cassandra-snapshot-store.default-dispatcher" 188 | 189 | # Default dispatcher for plugin actor. 190 | default-dispatcher { 191 | type = Dispatcher 192 | executor = "fork-join-executor" 193 | fork-join-executor { 194 | parallelism-min = 2 195 | parallelism-max = 8 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /ingest/src/main/resources/ingest.conf: -------------------------------------------------------------------------------- 1 | app { 2 | twitter { 3 | consumerKey: ${?CONSUMER_KEY} 4 | consumerSecret: ${?CONSUMER_SECRET} 5 | accessToken: ${?ACCESS_TOKEN} 6 | accessTokenSecret: ${?ACCESS_TOKEN_SECRET} 7 | } 8 | 9 | tweet-image-producer { 10 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 11 | topic = "tweet-image" 12 | } 13 | } 14 | 15 | akka.loglevel = "INFO" 16 | akka.persistence.journal.plugin = "cassandra-journal" 17 | akka.persistence.snapshot-store.plugin = "cassandra-snapshot-store" 18 | 19 | akka.actor { 20 | 21 | serializers { 22 | spb = "org.eigengo.rsa.serialization.ScalaPBSerializer" 23 | } 24 | 25 | serialization-bindings { 26 | "com.trueaccord.scalapb.GeneratedMessage" = spb 27 | } 28 | 29 | } 30 | 31 | cassandra-journal { 32 | 33 | # FQCN of the cassandra journal plugin 34 | class = "akka.persistence.cassandra.journal.CassandraJournal" 35 | 36 | # Comma-separated list of contact points in the cluster 37 | contact-points = [${?CASSANDRA_JOURNAL_CPS}] 38 | 39 | # Port of contact points in the cluster 40 | port = 9042 41 | 42 | # Name of the keyspace to be created/used by the journal 43 | keyspace = "akka_ingest" 44 | 45 | # Parameter indicating whether the journal keyspace should be auto created 46 | keyspace-autocreate = true 47 | 48 | # In case that schema creation failed you can define a number of retries before giving up. 49 | keyspace-autocreate-retries = 1 50 | 51 | # The number of retries when a write request returns a TimeoutException or an UnavailableException. 52 | write-retries = 3 53 | 54 | # Deletes are achieved using a metadata entry and then the actual messages are deleted asynchronously 55 | # Number of retries before giving up 56 | delete-retries = 3 57 | 58 | # Number of retries before giving up connecting to the cluster 59 | connect-retries = 3 60 | 61 | # Delay between connection retries 62 | connect-retry-delay = 5s 63 | 64 | # Name of the table to be created/used by the journal 65 | table = "messages" 66 | 67 | # Compaction strategy for the journal table 68 | table-compaction-strategy { 69 | class = "SizeTieredCompactionStrategy" 70 | } 71 | 72 | # Name of the table to be created/used for storing metadata 73 | metadata-table = "metadata" 74 | 75 | # Name of the table to be created/used for journal config 76 | config-table = "config" 77 | 78 | # replication strategy to use. SimpleStrategy or NetworkTopologyStrategy 79 | replication-strategy = "SimpleStrategy" 80 | 81 | # Replication factor to use when creating a keyspace. Is only used when replication-strategy is SimpleStrategy. 82 | replication-factor = 1 83 | 84 | # Replication factor list for data centers, e.g. ["dc1:3", "dc2:2"]. Is only used when replication-strategy is NetworkTopologyStrategy. 85 | data-center-replication-factors = [] 86 | 87 | # Write consistency level 88 | write-consistency = "QUORUM" 89 | 90 | # Read consistency level 91 | read-consistency = "QUORUM" 92 | 93 | max-message-batch-size = 200 94 | 95 | # Target number of entries per partition (= columns per row). 96 | # Must not be changed after table creation (currently not checked). 97 | # This is "target" as AtomicWrites that span parition boundaries will result in bigger partitions to ensure atomicity. 98 | target-partition-size = 500000 99 | 100 | # Maximum size of result set 101 | max-result-size = 50001 102 | 103 | # Dispatcher for the plugin actor. 104 | plugin-dispatcher = "cassandra-journal.default-dispatcher" 105 | 106 | # Dispatcher for fetching and replaying messages 107 | replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" 108 | 109 | # Default dispatcher for plugin actor. 110 | default-dispatcher { 111 | type = Dispatcher 112 | executor = "fork-join-executor" 113 | fork-join-executor { 114 | parallelism-min = 2 115 | parallelism-max = 8 116 | } 117 | } 118 | 119 | # The time to wait before cassandra will remove the thombstones created for deleted entries. 120 | # cfr. gc_grace_seconds table property documentation on http://www.datastax.com/documentation/cql/3.1/cql/cql_reference/tabProp.html 121 | gc-grace-seconds = 864000 122 | } 123 | 124 | cassandra-snapshot-store { 125 | 126 | # FQCN of the cassandra snapshot store plugin 127 | class = "akka.persistence.cassandra.snapshot.CassandraSnapshotStore" 128 | 129 | # Comma-separated list of contact points in the cluster 130 | contact-points = [${?CASSANDRA_SNAPSHOT_CPS}] 131 | 132 | # Port of contact points in the cluster 133 | port = 9042 134 | 135 | # Name of the keyspace to be created/used by the snapshot store 136 | keyspace = "akka_snapshot_ingest" 137 | 138 | # Parameter indicating whether the snapshot keyspace should be auto created 139 | keyspace-autocreate = true 140 | 141 | # In case that schema creation failed you can define a number of retries before giving up. 142 | keyspace-autocreate-retries = 1 143 | 144 | # Number of retries before giving up connecting to the cluster 145 | connect-retries = 3 146 | 147 | # Delay between connection retries 148 | connect-retry-delay = 5s 149 | 150 | # Name of the table to be created/used by the snapshot store 151 | table = "snapshots" 152 | 153 | # Compaction strategy for the snapshot table 154 | table-compaction-strategy { 155 | class = "SizeTieredCompactionStrategy" 156 | } 157 | 158 | # Name of the table to be created/used for journal config 159 | config-table = "config" 160 | 161 | # Name of the table to be created/used for storing metadata 162 | metadata-table = "metadata" 163 | 164 | # replication strategy to use. SimpleStrategy or NetworkTopologyStrategy 165 | replication-strategy = "SimpleStrategy" 166 | 167 | # Replication factor to use when creating a keyspace. Is only used when replication-strategy is SimpleStrategy. 168 | replication-factor = 1 169 | 170 | # Replication factor list for data centers, e.g. ["dc1:3", "dc2:2"]. Is only used when replication-strategy is NetworkTopologyStrategy. 171 | data-center-replication-factors = [] 172 | 173 | # Write consistency level 174 | write-consistency = "ONE" 175 | 176 | # Read consistency level 177 | read-consistency = "ONE" 178 | 179 | # Maximum number of snapshot metadata to load per recursion (when trying to 180 | # find a snapshot that matches specified selection criteria). Only increase 181 | # this value when selection criteria frequently select snapshots that are 182 | # much older than the most recent snapshot i.e. if there are much more than 183 | # 10 snapshots between the most recent one and selected one. This setting is 184 | # only for increasing load efficiency of snapshots. 185 | max-metadata-result-size = 10 186 | 187 | # Maximum size of result set 188 | max-result-size = 50001 189 | 190 | # Dispatcher for the plugin actor. 191 | plugin-dispatcher = "cassandra-snapshot-store.default-dispatcher" 192 | 193 | # Default dispatcher for plugin actor. 194 | default-dispatcher { 195 | type = Dispatcher 196 | executor = "fork-join-executor" 197 | fork-join-executor { 198 | parallelism-min = 2 199 | parallelism-max = 8 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /vision-identity/src/train/scala/org/eigengo/rsa/identity/v100/FaceTrainer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100 20 | 21 | import java.io.{File, FileOutputStream} 22 | import java.util.Random 23 | 24 | import org.datavec.api.io.filters.BalancedPathFilter 25 | import org.datavec.api.io.labels.ParentPathLabelGenerator 26 | import org.datavec.api.split.{FileSplit, InputSplit} 27 | import org.datavec.image.loader.BaseImageLoader 28 | import org.datavec.image.recordreader.ImageRecordReader 29 | import org.datavec.image.transform.{FlipImageTransform, ImageTransform, WarpImageTransform} 30 | import org.deeplearning4j.datasets.datavec.RecordReaderDataSetIterator 31 | import org.deeplearning4j.datasets.iterator.MultipleEpochsIterator 32 | import org.deeplearning4j.nn.api.OptimizationAlgorithm 33 | import org.deeplearning4j.nn.conf.layers._ 34 | import org.deeplearning4j.nn.conf.{GradientNormalization, MultiLayerConfiguration, NeuralNetConfiguration, Updater} 35 | import org.deeplearning4j.nn.multilayer.MultiLayerNetwork 36 | import org.deeplearning4j.nn.weights.WeightInit 37 | import org.deeplearning4j.optimize.listeners.ScoreIterationListener 38 | import org.deeplearning4j.util.NetSaverLoaderUtils 39 | import org.nd4j.linalg.dataset.api.iterator.DataSetIterator 40 | import org.nd4j.linalg.lossfunctions.LossFunctions 41 | 42 | import scala.util.Try 43 | 44 | object FaceTrainer { 45 | val height = 50 46 | val width = 50 47 | val channels = 3 48 | val numExamples = 200 49 | val numLabels = 5 50 | val batchSize = 20 51 | val listenerFreq = 1 52 | val iterations = 10 53 | val epochs = 7 54 | val splitTrainTest = 0.8 55 | val nCores = 8 56 | // num of cores on machine to paralize data load 57 | val rng = new Random() 58 | 59 | def main(args: Array[String]) { 60 | System.setProperty("OMP_NUM_THREADS", nCores.toString) 61 | val mainPath = new File("/Users/janmachacek/Eigengo/reactive-summit-2016-data/faces") 62 | 63 | // Define how to filter and load data into batches 64 | val fileSplit: FileSplit = new FileSplit(mainPath, BaseImageLoader.ALLOWED_FORMATS, rng) 65 | val pathFilter: BalancedPathFilter = new BalancedPathFilter(rng, BaseImageLoader.ALLOWED_FORMATS, new ParentPathLabelGenerator(), numExamples, numLabels, 0, batchSize) 66 | 67 | // Define train and test split 68 | val inputSplit: Array[InputSplit] = fileSplit.sample(pathFilter, 80, 20) 69 | val trainData: InputSplit = inputSplit(0) 70 | val testData: InputSplit = inputSplit(1) 71 | 72 | // Define transformation 73 | val flipTransform: ImageTransform = new FlipImageTransform(rng) 74 | val warpTransform: ImageTransform = new WarpImageTransform(rng, 42) 75 | val transforms: List[ImageTransform] = List(null, flipTransform, warpTransform) 76 | 77 | // Build model based on tiny model configuration paper 78 | val confTiny: MultiLayerConfiguration = new NeuralNetConfiguration.Builder() 79 | //.seed(seed) 80 | .iterations(iterations) 81 | .activation("relu") 82 | .weightInit(WeightInit.XAVIER) 83 | .gradientNormalization(GradientNormalization.RenormalizeL2PerLayer) 84 | .updater(Updater.NESTEROVS) 85 | .learningRate(0.01) 86 | .momentum(0.9) 87 | .regularization(true) 88 | .l2(0.04) 89 | .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT) 90 | .useDropConnect(true) 91 | .list() 92 | .layer(0, new ConvolutionLayer.Builder(5, 5) 93 | .name("cnn1") 94 | .nIn(channels) 95 | .stride(1, 1) 96 | .padding(2, 2) 97 | .nOut(32) 98 | .build()) 99 | .layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX) 100 | .kernelSize(3, 3) 101 | .name("pool1") 102 | .build()) 103 | .layer(2, new LocalResponseNormalization.Builder(3, 5e-05, 0.75).build()) 104 | .layer(3, new ConvolutionLayer.Builder(5, 5) 105 | .name("cnn2") 106 | .stride(1, 1) 107 | .padding(2, 2) 108 | .nOut(32) 109 | .build()) 110 | .layer(4, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX) 111 | .kernelSize(3, 3) 112 | .name("pool2") 113 | .build()) 114 | .layer(5, new LocalResponseNormalization.Builder(3, 5e-05, 0.75).build()) 115 | .layer(6, new ConvolutionLayer.Builder(5, 5) 116 | .name("cnn3") 117 | .stride(1, 1) 118 | .padding(2, 2) 119 | .nOut(64) 120 | .build()) 121 | .layer(7, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX) 122 | .kernelSize(3, 3) 123 | .name("pool3") 124 | .build()) 125 | .layer(8, new DenseLayer.Builder() 126 | .name("ffn1") 127 | .nOut(250) 128 | .dropOut(0.5) 129 | .build()) 130 | .layer(9, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) 131 | .nOut(numLabels) 132 | .activation("softmax") 133 | .build()) 134 | .backprop(true).pretrain(false) 135 | .cnnInputSize(height, width, channels).build() 136 | 137 | val network = new MultiLayerNetwork(confTiny) 138 | network.init() 139 | network.setListeners(new ScoreIterationListener(listenerFreq)) 140 | 141 | // Define how to load data into network 142 | val recordReader: ImageRecordReader = new ImageRecordReader(height, width, channels, new ParentPathLabelGenerator()) 143 | var dataIter: DataSetIterator = null 144 | var trainIter: MultipleEpochsIterator = null 145 | 146 | // Train 147 | for (transform <- transforms) Try { 148 | recordReader.initialize(trainData, transform) 149 | dataIter = new RecordReaderDataSetIterator(recordReader, batchSize, 1, numLabels) 150 | trainIter = new MultipleEpochsIterator(epochs, dataIter, nCores) 151 | network.fit(trainIter) 152 | } 153 | 154 | // Evaluate 155 | recordReader.initialize(testData) 156 | dataIter = new RecordReaderDataSetIterator(recordReader, 20, 1, numLabels) 157 | val eval = network.evaluate(dataIter) 158 | print(eval.stats(true)) 159 | 160 | // Save model and parameters 161 | import scala.collection.JavaConversions._ 162 | new FileOutputStream("/Users/janmachacek/Tmp/labels").write(recordReader.getLabels.mkString("\n").getBytes) 163 | NetSaverLoaderUtils.saveNetworkAndParameters(network, "/Users/janmachacek/Tmp") 164 | NetSaverLoaderUtils.saveUpdators(network, "/Users/janmachacek/Tmp") 165 | 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /vision-identity/src/main/resources/vision-identity.conf: -------------------------------------------------------------------------------- 1 | app { 2 | kafka { 3 | consumer-config { 4 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 5 | group.id = "vision-identity-v100" 6 | auto.offset.reset = "earliest" 7 | unconfirmed.timeout = "2s" 8 | } 9 | 10 | identity-producer { 11 | bootstrap.servers = ${?KAFKA_BOOTSTRAP_SERVERS} 12 | topic = "identity" 13 | } 14 | } 15 | } 16 | 17 | akka.loglevel = "INFO" 18 | akka.persistence.journal.plugin = "cassandra-journal" 19 | akka.persistence.snapshot-store.plugin = "cassandra-snapshot-store" 20 | 21 | akka.actor { 22 | 23 | serializers { 24 | spb = "org.eigengo.rsa.serialization.ScalaPBSerializer" 25 | } 26 | 27 | serialization-bindings { 28 | "com.trueaccord.scalapb.GeneratedMessage" = spb 29 | } 30 | 31 | } 32 | 33 | cassandra-journal { 34 | 35 | # FQCN of the cassandra journal plugin 36 | class = "akka.persistence.cassandra.journal.CassandraJournal" 37 | 38 | # Comma-separated list of contact points in the cluster 39 | contact-points = [${?CASSANDRA_JOURNAL_CPS}] 40 | 41 | # Port of contact points in the cluster 42 | port = 9042 43 | 44 | # Name of the keyspace to be created/used by the journal 45 | keyspace = "akka_identity" 46 | 47 | # Parameter indicating whether the journal keyspace should be auto created 48 | keyspace-autocreate = true 49 | 50 | # In case that schema creation failed you can define a number of retries before giving up. 51 | keyspace-autocreate-retries = 1 52 | 53 | # The number of retries when a write request returns a TimeoutException or an UnavailableException. 54 | write-retries = 3 55 | 56 | # Deletes are achieved using a metadata entry and then the actual messages are deleted asynchronously 57 | # Number of retries before giving up 58 | delete-retries = 3 59 | 60 | # Number of retries before giving up connecting to the cluster 61 | connect-retries = 3 62 | 63 | # Delay between connection retries 64 | connect-retry-delay = 5s 65 | 66 | # Name of the table to be created/used by the journal 67 | table = "messages" 68 | 69 | # Compaction strategy for the journal table 70 | table-compaction-strategy { 71 | class = "SizeTieredCompactionStrategy" 72 | } 73 | 74 | # Name of the table to be created/used for storing metadata 75 | metadata-table = "metadata" 76 | 77 | # Name of the table to be created/used for journal config 78 | config-table = "config" 79 | 80 | # replication strategy to use. SimpleStrategy or NetworkTopologyStrategy 81 | replication-strategy = "SimpleStrategy" 82 | 83 | # Replication factor to use when creating a keyspace. Is only used when replication-strategy is SimpleStrategy. 84 | replication-factor = 1 85 | 86 | # Replication factor list for data centers, e.g. ["dc1:3", "dc2:2"]. Is only used when replication-strategy is NetworkTopologyStrategy. 87 | data-center-replication-factors = [] 88 | 89 | # Write consistency level 90 | write-consistency = "QUORUM" 91 | 92 | # Read consistency level 93 | read-consistency = "QUORUM" 94 | 95 | max-message-batch-size = 200 96 | 97 | # Target number of entries per partition (= columns per row). 98 | # Must not be changed after table creation (currently not checked). 99 | # This is "target" as AtomicWrites that span parition boundaries will result in bigger partitions to ensure atomicity. 100 | target-partition-size = 500000 101 | 102 | # Maximum size of result set 103 | max-result-size = 50001 104 | 105 | # Dispatcher for the plugin actor. 106 | plugin-dispatcher = "cassandra-journal.default-dispatcher" 107 | 108 | # Dispatcher for fetching and replaying messages 109 | replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" 110 | 111 | # Default dispatcher for plugin actor. 112 | default-dispatcher { 113 | type = Dispatcher 114 | executor = "fork-join-executor" 115 | fork-join-executor { 116 | parallelism-min = 2 117 | parallelism-max = 8 118 | } 119 | } 120 | 121 | # The time to wait before cassandra will remove the thombstones created for deleted entries. 122 | # cfr. gc_grace_seconds table property documentation on http://www.datastax.com/documentation/cql/3.1/cql/cql_reference/tabProp.html 123 | gc-grace-seconds = 864000 124 | } 125 | 126 | cassandra-snapshot-store { 127 | 128 | # FQCN of the cassandra snapshot store plugin 129 | class = "akka.persistence.cassandra.snapshot.CassandraSnapshotStore" 130 | 131 | # Comma-separated list of contact points in the cluster 132 | contact-points = [${?CASSANDRA_SNAPSHOT_CPS}] 133 | 134 | # Port of contact points in the cluster 135 | port = 9042 136 | 137 | # Name of the keyspace to be created/used by the snapshot store 138 | keyspace = "akka_snapshot_identity" 139 | 140 | # Parameter indicating whether the snapshot keyspace should be auto created 141 | keyspace-autocreate = true 142 | 143 | # In case that schema creation failed you can define a number of retries before giving up. 144 | keyspace-autocreate-retries = 1 145 | 146 | # Number of retries before giving up connecting to the cluster 147 | connect-retries = 3 148 | 149 | # Delay between connection retries 150 | connect-retry-delay = 5s 151 | 152 | # Name of the table to be created/used by the snapshot store 153 | table = "snapshots" 154 | 155 | # Compaction strategy for the snapshot table 156 | table-compaction-strategy { 157 | class = "SizeTieredCompactionStrategy" 158 | } 159 | 160 | # Name of the table to be created/used for journal config 161 | config-table = "config" 162 | 163 | # Name of the table to be created/used for storing metadata 164 | metadata-table = "metadata" 165 | 166 | # replication strategy to use. SimpleStrategy or NetworkTopologyStrategy 167 | replication-strategy = "SimpleStrategy" 168 | 169 | # Replication factor to use when creating a keyspace. Is only used when replication-strategy is SimpleStrategy. 170 | replication-factor = 1 171 | 172 | # Replication factor list for data centers, e.g. ["dc1:3", "dc2:2"]. Is only used when replication-strategy is NetworkTopologyStrategy. 173 | data-center-replication-factors = [] 174 | 175 | # Write consistency level 176 | write-consistency = "ONE" 177 | 178 | # Read consistency level 179 | read-consistency = "ONE" 180 | 181 | # Maximum number of snapshot metadata to load per recursion (when trying to 182 | # find a snapshot that matches specified selection criteria). Only increase 183 | # this value when selection criteria frequently select snapshots that are 184 | # much older than the most recent snapshot i.e. if there are much more than 185 | # 10 snapshots between the most recent one and selected one. This setting is 186 | # only for increasing load efficiency of snapshots. 187 | max-metadata-result-size = 10 188 | 189 | # Maximum size of result set 190 | max-result-size = 50001 191 | 192 | # Dispatcher for the plugin actor. 193 | plugin-dispatcher = "cassandra-snapshot-store.default-dispatcher" 194 | 195 | # Default dispatcher for plugin actor. 196 | default-dispatcher { 197 | type = Dispatcher 198 | executor = "fork-join-executor" 199 | fork-join-executor { 200 | parallelism-min = 2 201 | parallelism-max = 8 202 | } 203 | } 204 | } -------------------------------------------------------------------------------- /ingest/src/test/resources/testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at": "Sun Sep 18 13:40:45 +0000 2016", 3 | "id": 777502622274166784, 4 | "id_str": "777502622274166784", 5 | "text": "Testing... see the real deal with live code at #ReactiveSummit. https:\/\/t.co\/pzQaEurQ6a", 6 | "source": "\u003ca href=\"http:\/\/twitter.com\/download\/iphone\" rel=\"nofollow\"\u003eTwitter for iPhone\u003c\/a\u003e", 7 | "truncated": false, 8 | "in_reply_to_status_id": null, 9 | "in_reply_to_status_id_str": null, 10 | "in_reply_to_user_id": null, 11 | "in_reply_to_user_id_str": null, 12 | "in_reply_to_screen_name": null, 13 | "user": { 14 | "id": 29976216, 15 | "id_str": "29976216", 16 | "name": "Jan Mach\u00e1\u010dek", 17 | "screen_name": "honzam399", 18 | "location": "Oxford | Manchester, UK", 19 | "url": null, 20 | "description": "Pointy-haired engineer & competitive cyclist.", 21 | "protected": false, 22 | "verified": false, 23 | "followers_count": 2078, 24 | "friends_count": 156, 25 | "listed_count": 188, 26 | "favourites_count": 752, 27 | "statuses_count": 7037, 28 | "created_at": "Thu Apr 09 12:47:53 +0000 2009", 29 | "utc_offset": 3600, 30 | "time_zone": "London", 31 | "geo_enabled": true, 32 | "lang": "en", 33 | "contributors_enabled": false, 34 | "is_translator": false, 35 | "profile_background_color": "C0DEED", 36 | "profile_background_image_url": "http:\/\/pbs.twimg.com\/profile_background_images\/59990568\/P4140010.JPG", 37 | "profile_background_image_url_https": "https:\/\/pbs.twimg.com\/profile_background_images\/59990568\/P4140010.JPG", 38 | "profile_background_tile": false, 39 | "profile_link_color": "0084B4", 40 | "profile_sidebar_border_color": "C0DEED", 41 | "profile_sidebar_fill_color": "DDEEF6", 42 | "profile_text_color": "333333", 43 | "profile_use_background_image": true, 44 | "profile_image_url": "http:\/\/pbs.twimg.com\/profile_images\/747039823618252800\/D6dYKhKO_normal.jpg", 45 | "profile_image_url_https": "https:\/\/pbs.twimg.com\/profile_images\/747039823618252800\/D6dYKhKO_normal.jpg", 46 | "profile_banner_url": "https:\/\/pbs.twimg.com\/profile_banners\/29976216\/1370081381", 47 | "default_profile": false, 48 | "default_profile_image": false, 49 | "following": null, 50 | "follow_request_sent": null, 51 | "notifications": null 52 | }, 53 | "geo": null, 54 | "coordinates": null, 55 | "place": { 56 | "id": "315b740b108481f6", 57 | "url": "https:\/\/api.twitter.com\/1.1\/geo\/id\/315b740b108481f6.json", 58 | "place_type": "city", 59 | "name": "Manchester", 60 | "full_name": "Manchester, England", 61 | "country_code": "GB", 62 | "country": "United Kingdom", 63 | "bounding_box": { 64 | "type": "Polygon", 65 | "coordinates": [ 66 | [ 67 | [ 68 | -2.319934, 69 | 53.343623 70 | ], 71 | [ 72 | -2.319934, 73 | 53.570282 74 | ], 75 | [ 76 | -2.147026, 77 | 53.570282 78 | ], 79 | [ 80 | -2.147026, 81 | 53.343623 82 | ] 83 | ] 84 | ] 85 | }, 86 | "attributes": {} 87 | }, 88 | "contributors": null, 89 | "is_quote_status": false, 90 | "retweet_count": 0, 91 | "favorite_count": 0, 92 | "entities": { 93 | "hashtags": [ 94 | { 95 | "text": "ReactiveSummit", 96 | "indices": [ 97 | 47, 98 | 62 99 | ] 100 | } 101 | ], 102 | "urls": [], 103 | "user_mentions": [], 104 | "symbols": [], 105 | "media": [ 106 | { 107 | "id": 777502602800037888, 108 | "id_str": "777502602800037888", 109 | "indices": [ 110 | 64, 111 | 87 112 | ], 113 | "media_url": "http:\/\/pbs.twimg.com\/media\/Cso-f3PWgAApURT.jpg", 114 | "media_url_https": "https:\/\/pbs.twimg.com\/media\/Cso-f3PWgAApURT.jpg", 115 | "url": "https:\/\/t.co\/pzQaEurQ6a", 116 | "display_url": "pic.twitter.com\/pzQaEurQ6a", 117 | "expanded_url": "https:\/\/twitter.com\/honzam399\/status\/777502622274166784\/photo\/1", 118 | "type": "photo", 119 | "sizes": { 120 | "medium": { 121 | "w": 900, 122 | "h": 1200, 123 | "resize": "fit" 124 | }, 125 | "thumb": { 126 | "w": 150, 127 | "h": 150, 128 | "resize": "crop" 129 | }, 130 | "small": { 131 | "w": 510, 132 | "h": 680, 133 | "resize": "fit" 134 | }, 135 | "large": { 136 | "w": 1536, 137 | "h": 2048, 138 | "resize": "fit" 139 | } 140 | } 141 | }, 142 | { 143 | "id": 777502602800037888, 144 | "id_str": "777502602800037888", 145 | "indices": [ 146 | 64, 147 | 87 148 | ], 149 | "media_url": "http:\/\/pbs.twimg.com\/media\/Dso-f3PWgAApURT.jpg", 150 | "media_url_https": "https:\/\/pbs.twimg.com\/media\/Dso-f3PWgAApURT.jpg", 151 | "url": "https:\/\/t.co\/pzQaEurQ6a", 152 | "display_url": "pic.twitter.com\/azQaEurQ6a", 153 | "expanded_url": "https:\/\/twitter.com\/honzam399\/status\/777502622274166784\/photo\/2", 154 | "type": "photo", 155 | "sizes": { 156 | "medium": { 157 | "w": 900, 158 | "h": 1200, 159 | "resize": "fit" 160 | }, 161 | "thumb": { 162 | "w": 150, 163 | "h": 150, 164 | "resize": "crop" 165 | }, 166 | "small": { 167 | "w": 510, 168 | "h": 680, 169 | "resize": "fit" 170 | }, 171 | "large": { 172 | "w": 1536, 173 | "h": 2048, 174 | "resize": "fit" 175 | } 176 | } 177 | } 178 | ] 179 | }, 180 | "extended_entities": { 181 | "media": [ 182 | { 183 | "id": 777502602800037888, 184 | "id_str": "777502602800037888", 185 | "indices": [ 186 | 64, 187 | 87 188 | ], 189 | "media_url": "http:\/\/pbs.twimg.com\/media\/Cso-f3PWgAApURT.jpg", 190 | "media_url_https": "https:\/\/pbs.twimg.com\/media\/Cso-f3PWgAApURT.jpg", 191 | "url": "https:\/\/t.co\/pzQaEurQ6a", 192 | "display_url": "pic.twitter.com\/pzQaEurQ6a", 193 | "expanded_url": "https:\/\/twitter.com\/honzam399\/status\/777502622274166784\/photo\/1", 194 | "type": "photo", 195 | "sizes": { 196 | "medium": { 197 | "w": 900, 198 | "h": 1200, 199 | "resize": "fit" 200 | }, 201 | "thumb": { 202 | "w": 150, 203 | "h": 150, 204 | "resize": "crop" 205 | }, 206 | "small": { 207 | "w": 510, 208 | "h": 680, 209 | "resize": "fit" 210 | }, 211 | "large": { 212 | "w": 1536, 213 | "h": 2048, 214 | "resize": "fit" 215 | } 216 | } 217 | } 218 | ] 219 | }, 220 | "favorited": false, 221 | "retweeted": false, 222 | "possibly_sensitive": false, 223 | "filter_level": "low", 224 | "lang": "en", 225 | "timestamp_ms": "1474206045107" 226 | } 227 | -------------------------------------------------------------------------------- /vision-identity/src/train/java/org/eigengo/rsa/identity/v100/AlexNet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The Reactive Summit Austin talk 3 | * Copyright (C) 2016 Jan Machacek 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | package org.eigengo.rsa.identity.v100; 20 | 21 | import org.deeplearning4j.nn.api.OptimizationAlgorithm; 22 | import org.deeplearning4j.nn.conf.*; 23 | import org.deeplearning4j.nn.conf.distribution.GaussianDistribution; 24 | import org.deeplearning4j.nn.conf.distribution.NormalDistribution; 25 | import org.deeplearning4j.nn.conf.layers.*; 26 | import org.deeplearning4j.nn.multilayer.MultiLayerNetwork; 27 | import org.deeplearning4j.nn.weights.WeightInit; 28 | import org.nd4j.linalg.lossfunctions.LossFunctions; 29 | 30 | 31 | /** 32 | * AlexNet 33 | *

34 | * Dl4j's AlexNet model interpretation based on the original paper ImageNet Classification with Deep Convolutional Neural Networks 35 | * and the imagenetExample code referenced. 36 | *

37 | * References: 38 | * http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf 39 | * https://github.com/BVLC/caffe/blob/master/models/bvlc_alexnet/train_val.prototxt 40 | *

41 | * Model is built in dl4j based on available functionality and notes indicate where there are gaps waiting for enhancements. 42 | *

43 | * Bias initialization in the paper is 1 in certain layers but 0.1 in the imagenetExample code 44 | * Weight distribution uses 0.1 std for all layers in the paper but 0.005 in the dense layers in the imagenetExample code 45 | */ 46 | public class AlexNet { 47 | 48 | private int height; 49 | private int width; 50 | private int channels; 51 | private int numLabels = 1000; 52 | private long seed = 42; 53 | private int iterations = 90; 54 | 55 | public AlexNet(int height, int width, int channels, int numLabels, long seed, int iterations) { 56 | this.height = height; 57 | this.width = width; 58 | this.channels = channels; 59 | this.numLabels = numLabels; 60 | this.seed = seed; 61 | this.iterations = iterations; 62 | } 63 | 64 | public MultiLayerConfiguration conf() { 65 | double nonZeroBias = 1; 66 | double dropOut = 0.5; 67 | SubsamplingLayer.PoolingType poolingType = SubsamplingLayer.PoolingType.MAX; 68 | 69 | // TODO split and link kernel maps on GPUs - 2nd, 4th, 5th convolution should only connect maps on the same gpu, 3rd connects to all in 2nd 70 | MultiLayerConfiguration.Builder conf = new NeuralNetConfiguration.Builder() 71 | .seed(seed) 72 | .weightInit(WeightInit.DISTRIBUTION) 73 | .dist(new NormalDistribution(0.0, 0.01)) 74 | .activation("relu") 75 | .updater(Updater.NESTEROVS) 76 | .iterations(iterations) 77 | .gradientNormalization(GradientNormalization.RenormalizeL2PerLayer) // normalize to prevent vanishing or exploding gradients 78 | .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT) 79 | .learningRate(1e-2) 80 | .biasLearningRate(1e-2 * 2) 81 | .learningRateDecayPolicy(LearningRatePolicy.Step) 82 | .lrPolicyDecayRate(0.1) 83 | .lrPolicySteps(100000) 84 | .regularization(true) 85 | .l2(5 * 1e-4) 86 | .momentum(0.9) 87 | .miniBatch(false) 88 | .list() 89 | .layer(0, new ConvolutionLayer.Builder(new int[]{11, 11}, new int[]{4, 4}, new int[]{3, 3}) 90 | .name("cnn1") 91 | .nIn(channels) 92 | .nOut(96) 93 | .build()) 94 | .layer(1, new LocalResponseNormalization.Builder() 95 | .name("lrn1") 96 | .build()) 97 | .layer(2, new SubsamplingLayer.Builder(poolingType, new int[]{3, 3}, new int[]{2, 2}) 98 | .name("maxpool1") 99 | .build()) 100 | .layer(3, new ConvolutionLayer.Builder(new int[]{5, 5}, new int[]{1, 1}, new int[]{2, 2}) 101 | .name("cnn2") 102 | .nOut(256) 103 | .biasInit(nonZeroBias) 104 | .build()) 105 | .layer(4, new LocalResponseNormalization.Builder() 106 | .name("lrn2") 107 | .k(2).n(5).alpha(1e-4).beta(0.75) 108 | .build()) 109 | .layer(5, new SubsamplingLayer.Builder(poolingType, new int[]{3, 3}, new int[]{2, 2}) 110 | .name("maxpool2") 111 | .build()) 112 | .layer(6, new ConvolutionLayer.Builder(new int[]{3, 3}, new int[]{1, 1}, new int[]{1, 1}) 113 | .name("cnn3") 114 | .nOut(384) 115 | .build()) 116 | .layer(7, new ConvolutionLayer.Builder(new int[]{3, 3}, new int[]{1, 1}, new int[]{1, 1}) 117 | .name("cnn4") 118 | .nOut(384) 119 | .biasInit(nonZeroBias) 120 | .build()) 121 | .layer(8, new ConvolutionLayer.Builder(new int[]{3, 3}, new int[]{1, 1}, new int[]{1, 1}) 122 | .name("cnn5") 123 | .nOut(256) 124 | .biasInit(nonZeroBias) 125 | .build()) 126 | .layer(9, new SubsamplingLayer.Builder(poolingType, new int[]{3, 3}, new int[]{2, 2}) 127 | .name("maxpool3") 128 | .build()) 129 | .layer(10, new DenseLayer.Builder() 130 | .name("ffn1") 131 | .nOut(4096) 132 | .dist(new GaussianDistribution(0, 0.005)) 133 | .biasInit(nonZeroBias) 134 | .dropOut(dropOut) 135 | .build()) 136 | .layer(11, new DenseLayer.Builder() 137 | .name("ffn2") 138 | .nOut(4096) 139 | .dist(new GaussianDistribution(0, 0.005)) 140 | .biasInit(nonZeroBias) 141 | .dropOut(dropOut) 142 | .build()) 143 | .layer(12, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) 144 | .name("output") 145 | .nOut(numLabels) 146 | .activation("softmax") 147 | .build()) 148 | .backprop(true) 149 | .pretrain(false) 150 | .cnnInputSize(height, width, channels); 151 | 152 | return conf.build(); 153 | } 154 | 155 | public MultiLayerNetwork init() { 156 | MultiLayerNetwork network = new MultiLayerNetwork(conf()); 157 | network.init(); 158 | return network; 159 | 160 | } 161 | 162 | } 163 | --------------------------------------------------------------------------------