|
|
To write data to an existing memory server you have two different options: You can use an instantiation of the armarx::armem::client::Writer-class or use a specific Writer for the core segment you want to write.
The first option is general and does not need any additional implementation outside of the core segment definition, but offers less convenience as the user needs to do everything themselves (including conversions to data transfer objects).
The second option requieres someone to have already encapsulated all of those steps into a convenience method.
An example of this can be found in this tutorial in the section on how to commit data.
Be aware that you need access to the MemoryNameSystem (MNS) and include dependencies of the aron conversions, the armem::EntityUpdate class and the armarx::armem::client::Writer class.
If specific data needs to be written to a specific core segment form more than one component it makes sense to encapsulate this into its own Writer class. An example of such a class can be found in the Object Memory - Class Writer.
To write such a Writer class one needs to inherit from armem::client::util::SimpleWriterBase and implement at least the methods defaultProperties() and propertyPrefix(). Additionally one needs to set the provider name and can then use the memoryWriter of the parent class to commit data once the armarx::armem::Commit (which consists of one or more armem::EntityUpdates) has been constructed.
In general one should add functions to allow using this Writer with Business Objects and without having to take care of updates or commits.
Reading data from a memory server is very similar to writing to one.
There are again two different options: You can use an instantiation of the armarx::armem::client::Reader-class or use a specific Reader for the core segment you want to read.
The first option is general and does not need any additional implementation outside of the core segment definition, but offers less convenience as the user needs to do everything themselves (including conversions to data transfer objects).
The second option requieres someone to have already encapsulated all of those steps into a convenience method.
An example of this can be found in this tutorial in the section on how to read data.
Be aware that you need access to the MemoryNameSystem (MNS) and include dependencies of the aron conversions, the armem::client::QueryBuilder class and the armarx::armem::client::Reader class.
If specific data needs to be read from a specific core segment form more than one component it makes sense to encapsulate this into its own Reader class. An example of such a class can be found in the Object Memory - Class Reader.
To write such a Reader class one needs to inherit from armem::client::util::SimpleReaderBase and implement at least the methods defaultProperties() and propertyPrefix(). One can use the memoryReader() function of the parent class to access the general Reader implementation and use it's query() method and a armem::client::query::Builder to query data from the memory server.
In general one should add functions to encapsulate the query building and the error handeling if something is going wrong with the underlying Reader.
Every memory server can consolidate the data of its working memory (WM) into its long-term memory (LTM), so that it is persisted to disk and can be loaded again later.
Recording is controlled through scenario properties of the memory server (prefixed with the component name, e.g. ArmarX.RobotStateMemory.):
mem.ltm.enabled — whether recording starts on startup (true/false).mem.ltm.recordingMode — what is consolidated. CONSOLIDATE_REMOVED (default) stores only the snapshots that are evicted from the WM, CONSOLIDATE_ALL stores every committed snapshot.mem.ltm.exportPath / mem.ltm.exportName — where the data is written on disk.mem.ltm.configuration — a JSON string that configures which segments are stored and which snapshot filters ("forgetting" filters) are applied. This is described below.configuration JSON propertyThe value of mem.ltm.configuration is a single-line JSON object. Its top-level keys configure the global snapshot filters that are applied to every stored segment. The default is a frequency filter that limits recording to roughly 20 fps:
The available snapshot filters ("forgetting" filters — a snapshot that is rejected is not stored) are:
SnapshotFrequencyFilter — rate-limits recording. Parameter WaitingTimeInMsForFilter (minimum time in ms between two stored snapshots of an entity).SnapshotSimilarityFilter — drops snapshots that are too similar to recent ones. Parameters Threshold, SimilarityMeasure (MSE, MAE, Chernoff or Cosine) and NumberOfObjectsToCompare.SnapshotImportanceFilter — stores a snapshot only if it is "important" enough. Parameters Threshold and Type (Confidence or Accesses).By default all core and provider segments of the memory are stored. Two optional keys allow restricting and fine-tuning this per segment:
storeSegments — a whitelist of segments to store. Each entry is either a core-segment name ("Core", which admits all of its provider segments) or a provider-segment path ("Core/Provider", which admits only that provider segment). If the key is absent, all segments are stored (the default).segments — per-segment filter overrides, keyed by the same "Core" or "Core/Provider" paths. A segment listed here uses its own filters; a segment not listed falls back to the global (top-level) filters.Store only the Proprioception core segment, using the default 20 fps frequency filter; all other segments are dropped:
Store Localization at ~20 fps and Proprioception at ~1 fps, each with its own frequency filter:
Because there are no top-level filters here, only the listed segments are stored and each uses its own filter. Filters of different types can be mixed, e.g. a similarity filter for one segment and a frequency filter for another.
Store all segments (no storeSegments whitelist), but apply a global similarity filter that forgets snapshots which are nearly identical to the previously stored ones:
This is useful to keep the LTM small by forgetting redundant data while still covering every segment. Swap in SnapshotImportanceFilter to instead forget snapshots whose confidence (or access count) is below a threshold.
Similar to writing data directly into a memory server one can also load data from the long-term memory into the current working memory of any memory server. To do so one can use a Memory Loader.
Currently there is only the general implementation of a Loader class, providing methods to load data from a specified location on the disk into a specified working memory server. It is possible to specify how many snapshots one wants to load (per entity) and provide a list of core segment names to load from.
An usage example can be found at this component or as part of the Example Memory.