From 4ce4c01b0120f0ee442a9d157eac05303a055650 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 16:59:35 +0000 Subject: [PATCH 1/8] Changed SensorNet and SensorNetValue so that they may hold multiple sensor data types --- .../scala/com/eigengo/lift/exercise/SensorData.scala | 12 ++++++------ .../lift/exercise/UserExercisesClassifier.scala | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala index bae85814..852c87e7 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala @@ -51,8 +51,8 @@ object Sensor { * Used to model a full sensor network of locations that may transmit data to us. Instances of the case class represent * sensor signals at a given point in time. */ -case class SensorNet(wrist: SensorData, waist: SensorData, foot: SensorData, chest: SensorData, unknown: SensorData) { - val toMap = Map[SensorDataSourceLocation, SensorData]( +case class SensorNet(wrist: Vector[SensorData], waist: Vector[SensorData], foot: Vector[SensorData], chest: Vector[SensorData], unknown: Vector[SensorData]) { + val toMap = Map[SensorDataSourceLocation, Vector[SensorData]]( SensorDataSourceLocationWrist -> wrist, SensorDataSourceLocationWaist -> waist, SensorDataSourceLocationFoot -> foot, @@ -62,7 +62,7 @@ case class SensorNet(wrist: SensorData, waist: SensorData, foot: SensorData, che } object SensorNet { - def apply(sensorMap: Map[SensorDataSourceLocation, SensorData]) = + def apply(sensorMap: Map[SensorDataSourceLocation, Vector[SensorData]]) = new SensorNet( sensorMap(SensorDataSourceLocationWrist), sensorMap(SensorDataSourceLocationWaist), @@ -75,8 +75,8 @@ object SensorNet { /** * Location or column slice through a sensor network. */ -case class SensorNetValue(wrist: SensorValue, waist: SensorValue, foot: SensorValue, chest: SensorValue, unknown: SensorValue) { - val toMap = Map[SensorDataSourceLocation, SensorValue]( +case class SensorNetValue(wrist: Vector[SensorValue], waist: Vector[SensorValue], foot: Vector[SensorValue], chest: Vector[SensorValue], unknown: Vector[SensorValue]) { + val toMap = Map[SensorDataSourceLocation, Vector[SensorValue]]( SensorDataSourceLocationWrist -> wrist, SensorDataSourceLocationWaist -> waist, SensorDataSourceLocationFoot -> foot, @@ -86,7 +86,7 @@ case class SensorNetValue(wrist: SensorValue, waist: SensorValue, foot: SensorVa } object SensorNetValue { - def apply(sensorMap: Map[SensorDataSourceLocation, SensorValue]) = + def apply(sensorMap: Map[SensorDataSourceLocation, Vector[SensorValue]]) = new SensorNetValue( sensorMap(SensorDataSourceLocationWrist), sensorMap(SensorDataSourceLocationWaist), diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala index 539e8097..4362a061 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala @@ -76,8 +76,11 @@ class UserExercisesClassifier(sessionProps: SessionProperties, modelProps: Props val model = context.actorOf(modelProps) override def receive: Receive = { + // FIXME: locations may be the source of multiple signal data types!! + case ClassifyExerciseEvt(_, _) => + // TODO: refactor code so that the following assumptions may be weakened further! - case sdwls: ClassifyExerciseEvt => + case sdwls: ClassifyExerciseEvt if false => require( sdwls.sensorData.map(_.location).toSet == Sensor.sourceLocations && sdwls.sensorData.map(_.location).size == Sensor.sourceLocations.size, "for each sensor location, there is a unique and corresponding member in the sensor data for the `ClassifyExerciseEvt` instance" From 20412368bfc54aad468dd64a760e91db2fe7f5b4 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 18:00:02 +0000 Subject: [PATCH 2/8] Parked changes for later! --- .../com/eigengo/lift/exercise/classifiers/ExerciseModel.scala | 1 + .../exercise/classifiers/model/StandardExerciseModel.scala | 4 +++- .../scala/com/eigengo/lift/exercise/ExerciseGenerators.scala | 4 ++-- .../eigengo/lift/exercise/UserExercisesClassifierTest.scala | 4 ++-- .../lift/exercise/classifiers/model/ExerciseModelTest.scala | 3 ++- .../classifiers/model/StandardExerciseModelTest.scala | 3 ++- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala index 8c3273e2..12f662ce 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala @@ -381,6 +381,7 @@ abstract class ExerciseModel(name: String, sessionProps: SessionProperties, toWa model.run() } + // FIXME: def receive = LoggingReceive { // TODO: refactor code so that the following assumptions may be weakened further! case event: SensorNet => diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala index f8865ae9..2cd12c83 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala @@ -43,7 +43,9 @@ abstract class StandardExerciseModel(sessionProps: SessionProperties, toWatch: S in ~> split - split ~> Flow[SensorNetValue].map(_.toMap(tapSensor).asInstanceOf[AccelerometerValue]).via(classifier.map(_.toSet)) ~> merge.left + split ~> Flow[SensorNetValue] + .mapConcat(_.toMap(tapSensor).find(_.isInstanceOf[AccelerometerValue]).asInstanceOf[Option[AccelerometerValue]].toList) + .via(classifier.map(_.toSet)) ~> merge.left split ~> merge.right diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala index 5c74d6d5..49f39d1a 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala @@ -49,12 +49,12 @@ trait ExerciseGenerators { def SensorNetGen(size: Int): Gen[SensorNet] = for { - sensorMap <- listOfN(Sensor.sourceLocations.size, SensorDataGen(size)).map(_.zipWithIndex.map { case (sv, n) => (Sensor.sourceLocations.toList(n), sv) }.toMap[SensorDataSourceLocation, SensorData]) + sensorMap <- listOfN(Sensor.sourceLocations.size, SensorDataGen(size)).map(_.zipWithIndex.map { case (sv, n) => (Sensor.sourceLocations.toList(n), Vector(sv)) }.toMap[SensorDataSourceLocation, Vector[SensorData]]) } yield SensorNet(sensorMap) val SensorNetValueGen: Gen[SensorNetValue] = for { - sensorMap <- listOfN(Sensor.sourceLocations.size, SensorValueGen).map(_.zipWithIndex.map { case (sv, n) => (Sensor.sourceLocations.toList(n), sv) }.toMap[SensorDataSourceLocation, SensorValue]) + sensorMap <- listOfN(Sensor.sourceLocations.size, SensorValueGen).map(_.zipWithIndex.map { case (sv, n) => (Sensor.sourceLocations.toList(n), Vector(sv)) }.toMap[SensorDataSourceLocation, Vector[SensorValue]]) } yield SensorNetValue(sensorMap) } diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala index b21e4a8b..11e3d985 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala @@ -28,8 +28,8 @@ class UserExercisesClassifierTest } property("UserExercisesClassifier should correctly 'slice up' ClassifyExerciseEvt into SensorNet events") { - val width = 2//0 - val height = 3//0 + val width = 20 + val height = 30 forAll(ClassifyExerciseEvtGen(width, height)) { (event: ClassifyExerciseEvt) => val modelProbe = TestProbe() diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala index f68d9c3a..e8a17dc2 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala @@ -170,8 +170,9 @@ class ExerciseModelTest } }) + // FIXME: forAll(SensorNetGen(30)) { (rawEvent: SensorNet) => - val event = SensorNet(rawEvent.toMap.mapValues(evt => new SensorData { val samplingRate = rate; val values = evt.values })) + val event = SensorNet(rawEvent.toMap.mapValues(_.map(evt => new SensorData { val samplingRate = rate; val values = evt.values }))) model ! event diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala index 229e8808..2056a46e 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala @@ -46,7 +46,8 @@ class StandardExerciseModelTest extends AkkaSpec(ConfigFactory.load("classificat } "correctly detect wrist sensor taps" in { - val msgs: List[SensorNetValue] = accelerometerData.map(d => SensorNetValue(d, dummyValue, dummyValue, dummyValue, dummyValue)) + // FIXME: is this correct? + val msgs: List[SensorNetValue] = accelerometerData.map(d => SensorNetValue(Vector(d), Vector(dummyValue), Vector(dummyValue), Vector(dummyValue), Vector(dummyValue))) val tapIndex = List(256 until 290, 341 until 344, 379 until 408, 546 until 577).flatten.toList // Simulate source that outputs messages and then blocks val in = PublisherProbe[SensorNetValue]() From a8edc6b5f7a5434ab79d3e3d692843d949ade248 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 20:48:40 +0000 Subject: [PATCH 3/8] UserExercisesClassifier and ExerciseModel now correctly process SensorNet and SensorNetValue events --- .../com/eigengo/lift/exercise/SensorData.scala | 4 ++++ .../exercise/UserExercisesClassifier.scala | 18 ++++++++---------- .../exercise/classifiers/ExerciseModel.scala | 17 ++++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala index 852c87e7..7d3259e7 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/SensorData.scala @@ -50,6 +50,8 @@ object Sensor { /** * Used to model a full sensor network of locations that may transmit data to us. Instances of the case class represent * sensor signals at a given point in time. + * + * Sensor networks relate a location and a point (an instance or position index) to the `SensorData` they produce. */ case class SensorNet(wrist: Vector[SensorData], waist: Vector[SensorData], foot: Vector[SensorData], chest: Vector[SensorData], unknown: Vector[SensorData]) { val toMap = Map[SensorDataSourceLocation, Vector[SensorData]]( @@ -74,6 +76,8 @@ object SensorNet { /** * Location or column slice through a sensor network. + * + * Sensor points produce `SensorNetValue`. They have a location and an instance or position index (the point). */ case class SensorNetValue(wrist: Vector[SensorValue], waist: Vector[SensorValue], foot: Vector[SensorValue], chest: Vector[SensorValue], unknown: Vector[SensorValue]) { val toMap = Map[SensorDataSourceLocation, Vector[SensorValue]]( diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala index 4362a061..c964893b 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala @@ -76,24 +76,22 @@ class UserExercisesClassifier(sessionProps: SessionProperties, modelProps: Props val model = context.actorOf(modelProps) override def receive: Receive = { - // FIXME: locations may be the source of multiple signal data types!! - case ClassifyExerciseEvt(_, _) => - // TODO: refactor code so that the following assumptions may be weakened further! case sdwls: ClassifyExerciseEvt if false => require( - sdwls.sensorData.map(_.location).toSet == Sensor.sourceLocations && sdwls.sensorData.map(_.location).size == Sensor.sourceLocations.size, - "for each sensor location, there is a unique and corresponding member in the sensor data for the `ClassifyExerciseEvt` instance" + sdwls.sensorData.map(_.location).toSet == Sensor.sourceLocations && sdwls.sensorData.forall(_.data.nonEmpty), + "all sensor locations are present in the `ClassifyExerciseEvt` instance and have data" ) - val sensorMap = sdwls.sensorData.groupBy(_.location).mapValues(_.flatMap(_.data)) - val blockSize = sensorMap(SensorDataSourceLocationWrist).length + // (SensorDataSourceLocation, Int) -> List[SensorData] + val sensorMap: Map[SensorDataSourceLocation, List[List[SensorData]]] = sdwls.sensorData.groupBy(_.location).mapValues(_.map(_.data)) + val blockSize = sensorMap(SensorDataSourceLocationWrist).head.length require( - sensorMap.values.forall(_.length == blockSize), - "all sensor data locations have a common data length" + sensorMap.values.forall(_.forall(_.length == blockSize)), + "all sensor data location points have a common data length" ) (0 until blockSize).foreach { block => - val sensorEvent = sensorMap.map { case (loc, _) => (loc, sensorMap(loc)(block)) }.toMap + val sensorEvent = sensorMap.map { case (loc, data) => (loc, (0 to data.size).map(point => sensorMap(loc)(point)(block)).toVector) }.toMap model.tell(SensorNet(sensorEvent), sender()) } diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala index 12f662ce..381be875 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala @@ -381,25 +381,24 @@ abstract class ExerciseModel(name: String, sessionProps: SessionProperties, toWa model.run() } - // FIXME: def receive = LoggingReceive { // TODO: refactor code so that the following assumptions may be weakened further! case event: SensorNet => require( - event.toMap.values.forall(_.values.nonEmpty), - "all sensors in a network should produce some sensor value" + event.toMap.values.forall(_.forall(_.values.nonEmpty)), + "all sensor points in a network should produce some sensor value" ) - val blockSize = event.toMap.values.head.values.length + val blockSize = event.toMap.values.head.head.values.length require( - event.toMap.values.forall(_.values.length == blockSize), - "all sensors in a network produce the same number of sensor values" + event.toMap.values.forall(_.forall(_.values.length == blockSize)), + "all sensor points in a network produce the same number of sensor values" ) require( - event.toMap.values.forall(_.samplingRate == samplingRate), - "all sensors have a fixed known sample rate" + event.toMap.values.forall(_.forall(_.samplingRate == samplingRate)), + "all sensor points have a fixed known sample rate" ) - val sensorEvents = (0 until blockSize).map(block => SensorNetValue(event.toMap.mapValues(_.values(block)))) + val sensorEvents = (0 until blockSize).map(block => SensorNetValue(event.toMap.mapValues(data => (0 to data.size).map(point => data(point).values(block)).toVector))) for (evt <- sensorEvents) { self.tell(evt, sender()) From 06ba5dfa4eea0d823a26758ee8f25d7bb6961a86 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 22:02:18 +0000 Subject: [PATCH 4/8] Updated tests with changes --- .../model/RandomExerciseModel.scala | 24 ++++++++++--------- .../UserExercisesClassifierTest.scala | 8 +++++-- .../classifiers/model/ExerciseModelTest.scala | 9 ++++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/RandomExerciseModel.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/RandomExerciseModel.scala index f0eabf41..9368cca7 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/RandomExerciseModel.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/RandomExerciseModel.scala @@ -83,17 +83,19 @@ class RandomExerciseModel(sessionProps: SessionProperties) override def aroundReceive(receive: Receive, msg: Any) = msg match { case event: SensorNet => event.toMap.foreach { x => (x: @unchecked) match { - case (location, AccelerometerData(sr, values)) => - val xs = values.map(_.x) - val ys = values.map(_.y) - val zs = values.map(_.z) - println(s"****** Acceleration $location | X: (${xs.min}, ${xs.max}), Y: (${ys.min}, ${ys.max}), Z: (${zs.min}, ${zs.max})") - - case (location, RotationData(_, values)) => - val xs = values.map(_.x) - val ys = values.map(_.y) - val zs = values.map(_.z) - println(s"****** Rotation $location | X: (${xs.min}, ${xs.max}), Y: (${ys.min}, ${ys.max}), Z: (${zs.min}, ${zs.max})") + case (location, data: Vector[_]) => + for ((AccelerometerData(_, values), point) <- data.zipWithIndex) { + val xs = values.map(_.x) + val ys = values.map(_.y) + val zs = values.map(_.z) + println(s"****** Acceleration $location@$point | X: (${xs.min}, ${xs.max}), Y: (${ys.min}, ${ys.max}), Z: (${zs.min}, ${zs.max})") + } + for ((RotationData(_, values), point) <- data.zipWithIndex) { + val xs = values.map(_.x) + val ys = values.map(_.y) + val zs = values.map(_.z) + println(s"****** Rotation $location@$point | X: (${xs.min}, ${xs.max}), Y: (${ys.min}, ${ys.max}), Z: (${zs.min}, ${zs.max})") + } }} super.aroundReceive(receive, msg) diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala index 11e3d985..d033df71 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala @@ -39,10 +39,14 @@ class UserExercisesClassifierTest val msgs = modelProbe.receiveN(width).asInstanceOf[Vector[SensorNet]].toList for (result <- msgs) { - assert(result.toMap.values.forall(_.values.length == height)) + assert(result.toMap.values.forall(_.forall(_.values.length == height))) } for (sensor <- Sensor.sourceLocations) { - assert(msgs.flatMap(_.toMap(sensor).values) == event.sensorData.find(_.location == sensor).get.data.flatMap(_.values)) + val numberOfPoints = event.sensorData.count(_.location == sensor) + + for (point <- 0 to numberOfPoints) { + assert(msgs.flatMap(_.toMap(sensor)(point).values) == event.sensorData.filter(_.location == sensor).map(_.data.flatMap(_.values)).toVector(point)) + } } } } diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala index e8a17dc2..688f9a97 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala @@ -170,15 +170,18 @@ class ExerciseModelTest } }) - // FIXME: forAll(SensorNetGen(30)) { (rawEvent: SensorNet) => val event = SensorNet(rawEvent.toMap.mapValues(_.map(evt => new SensorData { val samplingRate = rate; val values = evt.values }))) model ! event - val msgs = modelProbe.receiveN(event.wrist.values.length).asInstanceOf[Vector[SensorNetValue]].toList + val msgs = modelProbe.receiveN(event.wrist.head.values.length).asInstanceOf[Vector[SensorNetValue]].toList for (sensor <- Sensor.sourceLocations) { - assert(msgs.map(_.toMap(sensor)) == event.toMap(sensor).values) + val numberOfPoints = rawEvent.wrist.length + + for (point <- 0 to numberOfPoints) { + assert(msgs.map(_.toMap(sensor)(point)) == event.toMap(sensor)(point).values) + } } } } From 9140bbfbd2ec6df6ba2ba59a434c791895f7e135 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 22:17:56 +0000 Subject: [PATCH 5/8] Changes to ensure tests pass --- .../com/eigengo/lift/exercise/UserExercisesClassifier.scala | 4 ++-- .../eigengo/lift/exercise/classifiers/ExerciseModel.scala | 2 +- .../eigengo/lift/exercise/UserExercisesClassifierTest.scala | 6 +++--- .../lift/exercise/classifiers/model/ExerciseModelTest.scala | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala index c964893b..3fa923db 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/UserExercisesClassifier.scala @@ -77,7 +77,7 @@ class UserExercisesClassifier(sessionProps: SessionProperties, modelProps: Props override def receive: Receive = { // TODO: refactor code so that the following assumptions may be weakened further! - case sdwls: ClassifyExerciseEvt if false => + case sdwls: ClassifyExerciseEvt => require( sdwls.sensorData.map(_.location).toSet == Sensor.sourceLocations && sdwls.sensorData.forall(_.data.nonEmpty), "all sensor locations are present in the `ClassifyExerciseEvt` instance and have data" @@ -91,7 +91,7 @@ class UserExercisesClassifier(sessionProps: SessionProperties, modelProps: Props ) (0 until blockSize).foreach { block => - val sensorEvent = sensorMap.map { case (loc, data) => (loc, (0 to data.size).map(point => sensorMap(loc)(point)(block)).toVector) }.toMap + val sensorEvent = sensorMap.map { case (loc, data) => (loc, (0 until data.size).map(point => sensorMap(loc)(point)(block)).toVector) }.toMap model.tell(SensorNet(sensorEvent), sender()) } diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala index 381be875..c8191987 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/ExerciseModel.scala @@ -398,7 +398,7 @@ abstract class ExerciseModel(name: String, sessionProps: SessionProperties, toWa "all sensor points have a fixed known sample rate" ) - val sensorEvents = (0 until blockSize).map(block => SensorNetValue(event.toMap.mapValues(data => (0 to data.size).map(point => data(point).values(block)).toVector))) + val sensorEvents = (0 until blockSize).map(block => SensorNetValue(event.toMap.mapValues(data => (0 until data.size).map(point => data(point).values(block)).toVector))) for (evt <- sensorEvents) { self.tell(evt, sender()) diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala index d033df71..b4209cee 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala @@ -28,8 +28,8 @@ class UserExercisesClassifierTest } property("UserExercisesClassifier should correctly 'slice up' ClassifyExerciseEvt into SensorNet events") { - val width = 20 - val height = 30 + val width = 2//0 + val height = 3//0 forAll(ClassifyExerciseEvtGen(width, height)) { (event: ClassifyExerciseEvt) => val modelProbe = TestProbe() @@ -44,7 +44,7 @@ class UserExercisesClassifierTest for (sensor <- Sensor.sourceLocations) { val numberOfPoints = event.sensorData.count(_.location == sensor) - for (point <- 0 to numberOfPoints) { + for (point <- 0 until numberOfPoints) { assert(msgs.flatMap(_.toMap(sensor)(point).values) == event.sensorData.filter(_.location == sensor).map(_.data.flatMap(_.values)).toVector(point)) } } diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala index 688f9a97..4590229c 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala @@ -179,7 +179,7 @@ class ExerciseModelTest for (sensor <- Sensor.sourceLocations) { val numberOfPoints = rawEvent.wrist.length - for (point <- 0 to numberOfPoints) { + for (point <- 0 until numberOfPoints) { assert(msgs.map(_.toMap(sensor)(point)) == event.toMap(sensor)(point).values) } } From adf1c471c4eb411ac7aa2cd635b4850fab8c73d7 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 22:28:32 +0000 Subject: [PATCH 6/8] Removed debug commenting --- .../eigengo/lift/exercise/UserExercisesClassifierTest.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala index b4209cee..61444090 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/UserExercisesClassifierTest.scala @@ -28,8 +28,8 @@ class UserExercisesClassifierTest } property("UserExercisesClassifier should correctly 'slice up' ClassifyExerciseEvt into SensorNet events") { - val width = 2//0 - val height = 3//0 + val width = 20 + val height = 30 forAll(ClassifyExerciseEvtGen(width, height)) { (event: ClassifyExerciseEvt) => val modelProbe = TestProbe() From 8df3a624c9eeb112a0e104cdd3185a43aace69cf Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Tue, 17 Feb 2015 22:46:00 +0000 Subject: [PATCH 7/8] Added in multisensor variants of UserExercisesClassifier and ExerciseModel testing --- .../com/eigengo/lift/exercise/ExerciseGenerators.scala | 10 +++++++++- .../exercise/classifiers/model/ExerciseModelTest.scala | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala index 49f39d1a..caa87711 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/ExerciseGenerators.scala @@ -45,13 +45,21 @@ trait ExerciseGenerators { for { sessionProps <- SessionPropertiesGen events <- listOfN(Sensor.sourceLocations.size, SensorDataWithLocationGen(width, height)).map(_.zipWithIndex.map { case (sdwl, n) => sdwl.copy(location = Sensor.sourceLocations.toList(n)) }) - } yield ClassifyExerciseEvt(sessionProps, events) + data <- SensorDataWithLocationGen(width, height) + } yield ClassifyExerciseEvt(sessionProps, events :+ data) def SensorNetGen(size: Int): Gen[SensorNet] = for { sensorMap <- listOfN(Sensor.sourceLocations.size, SensorDataGen(size)).map(_.zipWithIndex.map { case (sv, n) => (Sensor.sourceLocations.toList(n), Vector(sv)) }.toMap[SensorDataSourceLocation, Vector[SensorData]]) } yield SensorNet(sensorMap) + def MultiSensorNetGen(size: Int): Gen[SensorNet] = + for { + sensorNet <- SensorNetGen(size) + location <- SensorDataSourceLocationGen + value <- SensorDataGen(size) + } yield SensorNet(sensorNet.toMap + (location -> (sensorNet.toMap(location) :+ value))) + val SensorNetValueGen: Gen[SensorNetValue] = for { sensorMap <- listOfN(Sensor.sourceLocations.size, SensorValueGen).map(_.zipWithIndex.map { case (sv, n) => (Sensor.sourceLocations.toList(n), Vector(sv)) }.toMap[SensorDataSourceLocation, Vector[SensorValue]]) diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala index 4590229c..be67955a 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala @@ -170,7 +170,7 @@ class ExerciseModelTest } }) - forAll(SensorNetGen(30)) { (rawEvent: SensorNet) => + forAll(MultiSensorNetGen(30)) { (rawEvent: SensorNet) => val event = SensorNet(rawEvent.toMap.mapValues(_.map(evt => new SensorData { val samplingRate = rate; val values = evt.values }))) model ! event From 898de214f6ce5a67530a57007286ba0f78528287 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Wed, 18 Feb 2015 14:03:18 +0000 Subject: [PATCH 8/8] Fix to ensure tests pass Refcatored gesture classifier so that it listens on a specified sensor --- .../exercise/classifiers/model/StandardExerciseModel.scala | 6 ++---- .../lift/exercise/classifiers/model/ExerciseModelTest.scala | 2 +- .../classifiers/model/StandardExerciseModelTest.scala | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala index 2cd12c83..8d67f070 100644 --- a/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala +++ b/server/exercise/src/main/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModel.scala @@ -14,7 +14,7 @@ import com.eigengo.lift.exercise.classifiers.ExerciseModel * Essentially, we view our model traces as being streams here. As a result, all queries are evaluated (on the actual * stream) from the time point they are received by the model. */ -abstract class StandardExerciseModel(sessionProps: SessionProperties, toWatch: Set[Query] = Set.empty) +abstract class StandardExerciseModel(sessionProps: SessionProperties, tapSensor: SensorDataSourceLocation, toWatch: Set[Query] = Set.empty) extends ExerciseModel("tap", sessionProps, toWatch) with StandardEvaluation with ActorLogging { @@ -24,10 +24,8 @@ abstract class StandardExerciseModel(sessionProps: SessionProperties, toWatch: S import ClassificationAssertions._ import FlowGraphImplicits._ - // Workflow for recognising 'tap' gestures.. + // Workflow for recognising 'tap' gestures that are detected via `tapSensor` object Tap extends GestureWorkflows("tap", context.system.settings.config) - // ..that are detected via wrist sensors - val tapSensor = SensorDataSourceLocationWrist /** * Monitor wrist sensor and add in tap gesture detection. diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala index be67955a..d959def5 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/ExerciseModelTest.scala @@ -177,7 +177,7 @@ class ExerciseModelTest val msgs = modelProbe.receiveN(event.wrist.head.values.length).asInstanceOf[Vector[SensorNetValue]].toList for (sensor <- Sensor.sourceLocations) { - val numberOfPoints = rawEvent.wrist.length + val numberOfPoints = rawEvent.toMap(sensor).length for (point <- 0 until numberOfPoints) { assert(msgs.map(_.toMap(sensor)(point)) == event.toMap(sensor)(point).values) diff --git a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala index 2056a46e..c7939109 100644 --- a/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala +++ b/server/exercise/src/test/scala/com/eigengo/lift/exercise/classifiers/model/StandardExerciseModelTest.scala @@ -7,7 +7,7 @@ import akka.testkit.TestActorRef import com.eigengo.lift.exercise.UserExercisesClassifier.{Tap => TapEvent} import com.eigengo.lift.exercise.classifiers.ExerciseModel import com.eigengo.lift.exercise.classifiers.workflows.ClassificationAssertions.{NegGesture, Gesture, BindToSensors} -import com.eigengo.lift.exercise.{AccelerometerValue, SensorNetValue, SessionProperties} +import com.eigengo.lift.exercise.{SensorDataSourceLocationWrist, AccelerometerValue, SensorNetValue, SessionProperties} import com.typesafe.config.ConfigFactory import java.text.SimpleDateFormat import scala.concurrent.{ExecutionContext, Future} @@ -37,7 +37,7 @@ class StandardExerciseModelTest extends AkkaSpec(ConfigFactory.load("classificat "StandardExerciseModel workflow" must { def component(in: Source[SensorNetValue], out: Sink[BindToSensors]) = { - val workflow = TestActorRef(new StandardExerciseModel(sessionProps) with SMTInterface { + val workflow = TestActorRef(new StandardExerciseModel(sessionProps, SensorDataSourceLocationWrist) with SMTInterface { def makeDecision(query: Query, value: QueryValue, result: Boolean) = TapEvent def simplify(query: Query)(implicit ec: ExecutionContext) = Future(query) def satisfiable(query: Query)(implicit ec: ExecutionContext) = Future(true)