Skip to content
David Ray edited this page May 5, 2017 · 28 revisions

The term Sensor means to "go out and get information, or a conduit through which information is received".

Sensors in HTM.java are "Network reception or retrieval objects". They come in 4 flavors; only 3 of which are of any concern to a user, the fourth (HTMSensor) is a wrapper around the other three - which is HTM Network aware. These are:

Sensors are general data retrieval mechanisms and could potentially be used outside of the NAPI framework. A Decorator-Pattern is used with them to wrap them in an HTMSensor which grants it HTM Network awareness and transforms the Stream into an HTM specific source of data (i.e. The Header retrievable from the HTMSensor must be 3 lines long to support HTM Network specific headers).


Sensor Creation

Here is an example of Sensor creation for all 3 types:

// ---  File
Sensor.create(FileSensor::create, SensorParams.create(
                Keys::path, "", ResourceLocator.path("rec-center-hourly.csv")))

// --- URL
Sensor.create(URISensor::create, SensorParams.create(
                Keys::uri, "", "http://www.metaware.us/nupic/rec-center-hourly.csv"))

// --- Manual / Programmatic
// NOTE: This must be done differently when using the PersistenceAPI - see below
PublisherSupplier manual = PublisherSupplier.builder()
    .addHeader("timestamp,consumption")
    .addHeader("datetime,float")
    .addHeader("T,B") //see SensorFlags.java for more info
    .build();

Sensor.create(ObservableSensor::create, SensorParams.create(
                Keys::obs, "", manual))

// From there, for "manual" input call something like: 
// (When the Sensor has been added to a Network, of course)

manual.onNext("7/2/10 0:00,21.2");
manual.onNext("7/2/10 1:00,34.0");
manual.onNext("7/2/10 2:00,40.4");
manual.onNext("7/2/10 3:00,123.4");

For more info on programmatic manual entry see ObservableTest


ObservableSensor with PersistenceAPI

With the introduction of the PersistenceAPI the setup of ObservableSensors has changed. The old way will still work if you don't use the PersistenceAPI, but if you are using it to save and load your networks (and why wouldn't you be?), you'll need to make use of a PublisherSupplier.

Here's an example:

//Create the PublisherSupplier - we'll need this to create our Sensor (instead of the Publisher)
PublisherSupplier supplier = PublisherSupplier.builder()
    .addHeader("timestamp,consumption")
    .addHeader("datetime,float")
    .addHeader("T,B") //see SensorFlags.java for more info
    .build();

//Get the Publisher from the PublisherSupplier - we'll use this to feed in data just as before
Publisher manual = supplier.get();

//Create the ObservableSensor; note the use of the PublisherSupplier instead of the Publisher
Sensor.create(ObservableSensor::create, SensorParams.create(
                Keys::obs, new Object[] {"", supplier}))

// From there, for "manual" input call something like: 
// (When the Sensor has been added to a Network, of course)

manual.onNext("7/2/10 0:00,21.2");
manual.onNext("7/2/10 1:00,34.0");
manual.onNext("7/2/10 2:00,40.4");
manual.onNext("7/2/10 3:00,123.4");

See here for another example using PublisherSuppliers.


Method References...

Here we will go into more of the gritty details underlying the Sensor architecture.

Ok so what kind of magic incantation is this!?!?

Sensor.create(URISensor::create, SensorParams.create(
                Keys::uri, "", "http://www.metaware.us/nupic/rec-center-hourly.csv"))

It's not so bad, trust me... :-)

The create() method of Sensor, has the following signature:

Sensor<URI> sensor = Sensor.create(SensorFactory factory, SensorParams params);

The first parameter (SensorFactory) is a Functional Interface (Meaning that it is an interface with exactly one abstract method) Inside of which (inside Sensor.create()), factory.create(params); is called. This means we can "substitute" a specific type (URISensor) which has the create(params) method and returns a specific type. This results in the factory method of URISensor (URISensor.create()) being called which returns a Sensor of type Sensor.

This magical incantation allows us to keep the Sensor creation code very concise while still allowing minimal code to create and return specific types of Sensors!

The other half of the Sensor.create() formal parameters is the SensorParams parameter. This class has a static create method also:

SensorParams.create(Supplier<Keys.Args> keySet, Object... values)

SensorParams inherits from NamedTuple which takes an array of String keys and an array of Object values as its construction parameters. Here, we use the functional interface Supplier which contains one method (get()) that takes no arguments and returns an object (Keys.Args).

Keys.Args is class which returns an array of keys for the type indicated. This means Keys.obs() (or Keys::obs) returns an array of String keys used for retrieving ObservableSensor params from a NamedTuple. Likewise, Keys.path() (or Keys::path) returns keys for a FileSensor, and Keys.uri() (or Keys::uri) returns keys used to retrieve URI information of a URISensor. see the Args enum in the SensorParams java file.

Of course, you don't have to know all of this to use the different Sensor creation factory methods, but it might prove interesting.


Underlying Java 8 Streams

All of the Sensors (FileSensor, URISensor, even ObservableSensor), create java.util.Stream(s) internally upon creation. The Stream created is a very specialized type called a BatchedCsvStream which has a few extra beneficial properties:

  • It incorporates batching when in parallel mode which gives it up to 30% performance increase.
  • It inserts a sequence number as a header in each row so that the stream can be reassembled in order farther down the line. This allows the parallelization to extend into client (consumer) code much deeper without worrying about how to reassemble later - and thus avoids a performance penalty for reassembly or at least allows us to make the trade-off in our favor. (i.e. when we look at parallelization of the "inhibition phase" of the SpatialPooler later on).
  • It separates the header information so that we can optimally process and create a header for the consuming encoder creation code.

Each of the 3 Sensors is immediately wrapped in an HTMSensor class which is the first class in the above construction chain which "knows" anything about the "HTM world". It is responsible for 3 things:

  1. Creation of the Header class which parses the header information into FieldMetaTypes (which translates type information into the appropriate Encoder type); and also processes the control columns for turning "learning" on and off and TemporalMemory resets.

  2. It knows how to "split" a stream for multiple consumers and do "multiplexing" for each consumer to keep the updating in synch.

  3. It creates the Encoders for each field type.

When the Sensor is finished being constructed it is ready to be added to a Layer but is not yet "hot" (consuming endpoint information) until "start" is called by one of the parent Network components.

Next: Layer