diff --git a/Makefile b/Makefile
index 3003f98..149f5b2 100644
--- a/Makefile
+++ b/Makefile
@@ -4,21 +4,21 @@ BIN_DIR = ./bin
MKDIR = mkdir -p
CPP = g++
-CPPFLAGS = -Wall -pthread -std=c++1y -I./include -I${CONDA_PREFIX}/include
+CPPFLAGS = -Wall -fPIC -pthread -std=c++1y -I./include -I${CONDA_PREFIX}/include
LDLIBS = -L/usr/lib64 -L${CONDA_PREFIX}/lib -lzmq -lhdf5 -lhdf5_hl -lhdf5_cpp -lhdf5_hl_cpp -lboost_system -lboost_regex -lboost_thread -lpthread
-LDFLAGS = -g
+LDFLAGS = -shared
HEADERS = $(wildcard $(SRC_DIR)/*.hpp)
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRCS))
-all: build_dirs h5_zmq_writer
+all: build_dirs libCppH5Writer
debug: CPPFLAGS += -DDEBUG_OUTPUT -g
debug: all
-h5_zmq_writer: $(OBJS)
- $(CPP) $(LDFLAGS) -o $(BIN_DIR)/h5_zmq_writer $(OBJS) $(LDLIBS)
+libCppH5Writer: $(OBJS)
+ $(CPP) $(LDFLAGS) -o $(BIN_DIR)/libCppH5Writer.so $(OBJS) $(LDLIBS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CPP) $(CPPFLAGS) $(LDLIBS) -c -o $@ $<
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2373f59
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1049 @@
+conda config --add channels conda-forge
+
+
+
+[](https://travis-ci.org/datastreaming/cam_server) [](https://ci.appveyor.com/project/Babicaa/cam-server)
+
+# Camera and Pipeline server
+Cam server is an epics - bsread interface that converts epics enabled camera into a bs_read stream. In addition it
+also provides a processing pipeline and a REST interface to control both the cameras and the pipeline.
+
+**WARNING**: Please note that for normal users, only **PipelineClient** should be used. CamClient is used by the
+underlying infrastructure to provide camera images to the pipeline server.
+
+# Table of content
+1. [Quick start](#quick_start)
+2. [Build](#build)
+ 1. [Conda setup](#conda_setup)
+ 2. [Local build](#local_build)
+ 3. [Docker build](#docker_build)
+3. [Basic concepts](#basic_concepts)
+ 1. [Requesting a stream and instance management](#reqeust_a_stream)
+ 2. [Shared and private pipeline instances](#shared_and_private)
+ 3. [Configuration versioning and camera background in the pipeline server](#configuration_versioning)
+4. [Configuration](#configuration)
+ 1. [Camera configuration](#camera_configuration)
+ 2. [Pipeline configuration](#pipeline_configuration)
+5. [Web interface](#web_interface)
+ 1. [Python client](#python_client)
+ 2. [REST API](#rest_api)
+6. [Running the servers](#running_the_servers)
+ 1. [Camera_server](#run_camera_server)
+ 2. [Pipeline server](#run_pipeline_server)
+ 3. [Docker Container](#run_docker_container)
+7. [Production configuration](#production_configuration)
+8. [Examples](#examples)
+ 1. [Get the simulation camera stream](#get_simulation_camera_stream)
+ 2. [Get a basic pipeline with a simulated camera](#basic_pipeline)
+ 3. [Create a pipeline instance with background](#private_pipeline)
+ 4. [Read the stream for a given camera name](#read_camera_stream)
+ 5. [Modifying camera config](#modify_camera_config)
+ 6. [Modifying pipeline config](#modify_pipeline_config)
+ 7. [Create a new camera](#create_camera_config)
+ 8. [Get single message from screen_panel stream](#single_message_screen_panel)
+ 9. [Save camera stream to H5 file](#stream_to_h5_file)
+9. [Deploy in production](#deploy_in_production)
+
+
+
+## Quick start
+
+
+
+## Build
+
+
+### Conda setup
+If you use conda, you can create an environment with the cam_server library by running:
+
+```bash
+conda create -c paulscherrerinstitute --name cam_server
+```
+
+After that you can just source you newly created environment and start using the server.
+
+
+### Local build
+You can build the library by running the setup script in the root folder of the project:
+
+```bash
+python setup.py install
+```
+
+or by using the conda also from the root folder of the project:
+
+```bash
+conda build conda-recipe
+conda install --use-local mflow_node_processors
+```
+
+#### Requirements
+The library relies on the following packages:
+
+- requests
+- bsread >=0.9.3
+- bottle
+- numpy
+- scipy
+- pyepics
+- matplotlib
+- pillow
+
+In case you are using conda to install the packages, you might need to add the **conda-forge** channel to
+your conda config:
+
+```
+conda config --add channels conda-forge
+```
+
+
+### Docker build
+**Warning**: When you build the docker image with **build.sh**, your built will be pushed to the PSI repo as the
+latest cam_server version. Please use the **build.sh** script only if you are sure that this is what you want.
+
+To build the docker image, run the build from the **docker/** folder:
+```bash
+./build.sh
+```
+
+Before building the docker image, make sure the latest version of the library is available in Anaconda.
+
+**Please note**: There is no need to build the image if you just want to run the docker container.
+Please see the [Docker Container](#run_docker_container) chapter.
+
+
+
+## Basic concepts
+
+
+### Requesting a stream and instance management
+
+Instance management is done automatically. Once you request a stream, you have a fixed amount of time
+(no clients timeout) to connect to the stream. If you do not connect in the given time, the stream will automatically
+be stopped. In this case, you will need to request the stream again.
+
+The same is true when disconnecting from the stream. You do not need to take any actions to close the pipeline or
+camera stream, as they will close themselves automatically after the configured time has elapsed.
+
+There are however 2 methods available for manual instance management:
+- stop_instance
+- stop_all_instances
+
+They are not meant to be used during normal operations, but only as administrative methods if something does not work
+as expected.
+
+
+### Shared and private pipeline instances
+
+The difference between shared and private pipeline instances in mainly in the fact that shared instances are
+read only (the pipeline config cannot be changed). This is done to prevent the interaction between different users
+- if 2 people are independently viewing the same pipeline at the same time, we need to prevent to any of them to change
+what the other receives. If you need to change the pipeline parameters, you need to create a private pipeline instance.
+
+Shared instances are named after the pipeline config they are created from. For example, if you have a saved pipeline
+with the name 'simulation_pipeline', the shared instance instance_id will be 'simulation_pipeline'. Private instances
+can have a custom instance_id you provide, or an automatically generated one.
+In any case, the given instance_id must be unique.
+
+You can share the private pipeline as well - all you need to share is the instance_id. But you need to be
+aware that anyone having your instance_id can change any parameter on the pipeline.
+
+
+### Configuration versioning and camera background in the pipeline server
+We have a requirement to be always able to access the original configuration with which the image was processed.
+
+The configuration used by the pipeline for image processing is explicitly added to each bs_read message. The
+configuration is added as a JSON object (string representation) in the **processing\_parameters** field.
+Since this config is included with every message, there is no need to version the
+processing parameters on the pipeline server. You can retrieve the processing parameters for each frame individually
+(the processing parameters can change from frame to frame).
+
+Backgrounds, on the other hand, are not included into this field - just the background id is (since the background is
+a large image, it does not make sense to include it in every message). As a consequence, backgrounds can never be
+deleted and need to be versioned on the pipeline server. All backgrounds need to be backed up regularly.
+
+
+## Configuration
+The camera and pipeline instances have their own configuration which can be read and set via the rest interface.
+We currently store the configurations in files, but REDIS is planned to be used in the near future.
+
+The expected folder structure for the configuration (which can be changed by passing parameters to the executables):
+
+- configuration/camera_config : Folder where JSON files with camera configurations are located.
+- configuration/pipeline_config : Folder where JSON files with pipeline configurations are located.
+- configuration/background_config : Folder where NPY files with camera backgrounds are located.
+
+
+### Camera configuration
+For camera configuration, all fields must be specified, and there is no defaulting in case some are missing.
+
+#### Configuration parameters
+
+- **name**: Name of the camera.
+- **source**: Source of the camera (PV prefix, bsread stream)
+- **source_type**: Type of the source (available: epics, bsread, simulation)
+- **mirror\_x**: Mirror camera image over X axis.
+- **mirror\_y**: Mirror camera image over Y axis.
+- **rotate**: how many times to rotate the camera image by 90 degrees.
+- **camera\_calibration** (Default _None_): Info on how to convert the camera pixels into engineering units.
+ - reference_marker (Default _[0, 0, 100, 100]_): Reference markers placement.
+ - reference_marker_width (Default _100.0_): Width of reference markers.
+ - reference_marker_height (Default _100.0_): Height of reference markers.
+ - angle_horizontal (Default _0.0_): Horizontal angle.
+ - angle_vertical (Default _0.0_): Vertical angle.
+
+##### Source type
+cam_server can connect to different type of sources. The type of source you select defines the meaning of the
+**source** field in the configuration:
+
+- source_type = "epics" : Connect to an Epics camera. The 'source' field is the camera prefix.
+- source_type = "bsread" : Connect to a bsread stream. The 'source' field is the stream address.
+- source_type = "simulation": Generate simulated images. The 'source' can be anything, but it must NOT be None.
+
+#### Example
+```json
+{
+ "name": "example_4",
+ "source": "EPICS_example_4",
+ "source_type": "epics",
+ "mirror_x": true,
+ "mirror_y": false,
+ "rotate": 4,
+
+ "camera_calibration": {
+ "reference_marker": [ 0, 0, 100, 100 ],
+ "reference_marker_width": 100.0,
+ "reference_marker_height": 100.0,
+ "angle_horizontal": 0.0,
+ "angle_vertical": 0.0
+ }
+}
+```
+
+
+### Pipeline configuration
+
+Configuration changes can for the pipeline can be incremental - you need to specify only the fields that you want
+to change. A valid configuration must have only the **camera_name** specified, all other fields will be
+defaulted to **None** (or False, in the case of "image_background_enable"). The complete configuration used for the
+pipeline is added to the output bsread stream in the
+**processing\_parameters** field.
+
+#### Configuration parameters
+
+- **camera\_name** : Name of the camera to use as a pipeline source.
+- **image\_background** (Default _None_): Background to subtract from the original image.
+- **image\_background_enable** (Default _False_): Enable or disale the image_background subtraction.
+- **image\_threshold** (Default _None_): Minimum value of each pixel. Pixels below the threshold are converted to 0.
+- **image\_region\_of\_interest** (Default _None_): Crop the image before processing.
+- **image\_good\_region** (Default _None_): Good region to use for fits and slices.
+ - threshold (Default _0.3_): Threshold to apply on each pixel.
+ - gfscale (Default _1.8_): Scale to extend the good region.
+- **image\_slices** (Default _None_):
+ - number_of_slices (Default _1_): Desired number of slices.
+ - scale (Default _2.0_): Good region scale in for slicing purposes.
+ - orientation (Default _vertical_): Orientation of the slices. Can be 'vertical' or 'horizontal'.
+
+#### Example
+```json
+{
+ "camera_name": "simulation",
+
+ "image_background": null,
+ "image_background_enable": false,
+ "image_threshold": 0.5,
+ "image_region_of_interest": [0, 100, 0, 100],
+
+ "image_good_region": {
+ "threshold": 0.9,
+ "gfscale": 3
+ },
+
+ "image_slices": {
+ "number_of_slices": 1,
+ "scale": 1.0,
+ "orientation": "vertical"
+ }
+}
+```
+
+
+## Web interface
+The cam_server is divided into 2 parts:
+
+- Camera server (default: localhost:8888)
+- Pipeline server (default: localhost:8889)
+
+Operations on both server are accessible via the REST api and also via a python client class (that calls the REST api).
+
+As an end user you should interact mostly with the pipeline, with the exception of cases where you need the raw
+image coming directly from the camera - even in this case, it is advisable to create a new pipeline without processing
+and use the stream from the pipeline. In this way, we lower the network load on the system (only one instance of
+camera stream, that is shared by many pipelines).
+
+All request (with the exception of **get\_camera\_image**) return a JSON with the following fields:
+- **state** - \["ok", "error"\]
+- **status** - What happened on the server or error message, depending on the state.
+- Optional request specific field - \["cameras", "geometry", "info", "stream", "config", "pipelines",
+"instance_id", "background_id"\]
+
+For more information on what each command does check the **API** section in this document.
+
+
+### Python client
+
+There are 2 classes available to communicate with the camera and pipeline server. They are basically just wrappers
+around REST API calls (see next chapters).
+
+#### CamClient
+
+**WARNING**: Please note that for normal users, only **PipelineClient** should be used. CamClient is used by the
+underlying infrastructure to provide camera images to the pipeline server.
+
+Import and create a cam client instance:
+```python
+from cam_server import CamClient
+client = CamClient()
+```
+
+Class definition:
+```
+class CamClient()
+
+ __init__(self, address='http://sf-daqsync-01:8888/')
+ :param address: Address of the cam API, e.g. http://localhost:10000
+
+ delete_camera_config(self, camera_name)
+ Delete config of camera.
+ :param camera_name: Camera to set the config to.
+ :return: Actual applied config.
+
+ get_address(self)
+ Return the REST api endpoint address.
+
+ get_camera_config(self, camera_name)
+ Return the cam configuration.
+ :param camera_name: Name of the cam.
+ :return: Camera configuration.
+
+ get_camera_geometry(self, camera_name)
+ Get cam geometry.
+ :param camera_name: Name of the cam.
+ :return: Camera geometry.
+
+ get_camera_image(self, camera_name)
+ Return the cam image in PNG format.
+ :param camera_name: Camera name.
+ :return: server_response content (PNG).
+
+ def get_camera_image_bytes(self, camera_name):
+ Return the cam image bytes.
+ :param camera_name: Camera name.
+ :return: JSON with bytes and metadata.
+
+ get_camera_stream(self, camera_name)
+ Get the camera stream address.
+ :param camera_name: Name of the camera to get the address for.
+ :return: Stream address.
+
+ get_cameras(self)
+ List existing cameras.
+ :return: Currently existing cameras.
+
+ get_server_info(self)
+ Return the info of the cam server instance.
+ For administrative purposes only.
+ :return: Status of the server
+
+ set_camera_config(self, camera_name, configuration)
+ Set config on camera.
+ :param camera_name: Camera to set the config to.
+ :param configuration: Config to set, in dictionary format.
+ :return: Actual applied config.
+
+ stop_all_cameras(self)
+ Stop all the cameras on the server.
+ :return: Response.
+
+ stop_camera(self, camera_name)
+ Stop the camera.
+ :param camera_name: Name of the camera to stop.
+ :return: Response.
+
+```
+
+#### PipelineClient
+Import and create a pipeline client instance:
+```python
+from cam_server import PipelineClient
+client = PipelineClient()
+```
+
+Class definition:
+```
+class PipelineClient(builtins.object)
+
+ __init__(self, address='http://sf-daqsync-01:8889/')
+ :param address: Address of the pipeline API, e.g. http://localhost:10000
+
+ collect_background(self, camera_name, n_images=None)
+ Collect the background image on the selected camera.
+ :param camera_name: Name of the camera to collect the background on.
+ :param n_images: Number of images to collect the background on.
+ :return: Background id.
+
+ create_instance_from_config(self, configuration, instance_id=None)
+ Create a pipeline from the provided config. Pipeline config can be changed.
+ :param configuration: Config to use with the pipeline.
+ :param instance_id: User specified instance id. GUID used if not specified.
+ :return: Pipeline instance stream.
+
+ create_instance_from_name(self, pipeline_name, instance_id=None)
+ Create a pipeline from a config file. Pipeline config can be changed.
+ :param pipeline_name: Name of the pipeline to create.
+ :param instance_id: User specified instance id. GUID used if not specified.
+ :return: Pipeline instance stream.
+
+ delete_pipeline_config(self, pipeline_name)
+ Delete a pipeline config.
+ :param pipeline_name: Name of pipeline config to delete.
+
+ get_cameras(self)
+ List available cameras.
+ :return: Currently available cameras.
+
+ get_instance_config(self, instance_id)
+ Return the instance configuration.
+ :param instance_id: Id of the instance.
+ :return: Pipeline configuration.
+
+ get_instance_info(self, instance_id)
+ Return the instance info.
+ :param instance_id: Id of the instance.
+ :return: Pipeline instance info.
+
+ get_instance_stream(self, instance_id)
+ Return the instance stream. If the instance does not exist, it will be created.
+ Instance will be read only - no config changes will be allowed.
+ :param instance_id: Id of the instance.
+ :return: Pipeline instance stream.
+
+ get_latest_background(self, camera_name)
+ Return the latest collected background for a camera.
+ :param camera_name: Name of the camera to return the background.
+ :return: Background id.
+
+ get_pipeline_config(self, pipeline_name)
+ Return the pipeline configuration.
+ :param pipeline_name: Name of the pipeline.
+ :return: Pipeline configuration.
+
+ get_pipelines(self)
+ List existing pipelines.
+ :return: Currently existing cameras.
+
+ get_server_info(self)
+ Return the info of the cam server instance.
+ For administrative purposes only.
+ :return: Status of the server
+
+ save_pipeline_config(self, pipeline_name, configuration)
+ Set config of the pipeline.
+ :param pipeline_name: Pipeline to save the config for.
+ :param configuration: Config to save, in dictionary format.
+ :return: Actual applied config.
+
+ set_instance_config(self, instance_id, configuration)
+ Set config of the instance.
+ :param instance_id: Instance to apply the config for.
+ :param configuration: Config to apply, in dictionary format.
+ :return: Actual applied config.
+
+ stop_all_instances(self)
+ Stop all the pipelines on the server.
+
+ stop_instance(self, instance_id)
+ Stop the pipeline.
+ :param instance_id: Name of the pipeline to stop.
+
+ get_instance_message(self, instance_id):
+
+ Get a single message from a stream instance.
+ :param instance_id: Instance id of the stream.
+ :return: Message from the stream.
+
+```
+
+
+### REST API
+
+#### Camera server API
+
+**WARNING**: Please note that for normal users, only the **Pipeline server API** should be used. The Camera server API
+is used by the underlying infrastructure to provide camera images to the pipeline server.
+
+In the API description, localhost and port 8888 are assumed. Please change this for your specific case.
+
+* `GET localhost:8888/api/v1/cam` - get the list of available cameras.
+ - Response specific field: "cameras" - List of cameras.
+
+* `GET localhost:8888/api/v1/cam/` - get the camera stream.
+ - Response specific field: "stream" - Stream address.
+
+* `GET localhost:8888/api/v1/cam//config` - get camera config.
+ - Response specific field: "config" - configuration JSON.
+
+* `POST localhost:8888/api/v1/cam//config` - set camera config.
+ - Response specific field: "config" configuration JSON.
+
+* `DELETE localhost:8888/api/v1/cam//config` - delete the camera config.
+ - Response specific field: None
+
+* `GET localhost:8888/api/v1/cam//geometry` - get the geometry of the camera.
+ - Response specific field: "geometry" - \[width, height\] of image
+
+* `GET localhost:8888/api/v1/cam//image` - get one PNG image of the camera.
+ - Returns a PNG image
+
+* `GET localhost:8888/api/v1/cam//image_bytes` - get one PNG image of the camera.
+ - Returns JSON with Base64, UTF-8 image bytes and metadata.
+
+* `GET localhost:8888/api/v1/cam/info` - return info on the camera manager.
+ - Response specific field: "info" - JSON with instance info.
+
+* `DELETE localhost:8888/api/v1/cam` - stop all camera instances.
+ - Response specific field: None
+
+* `DELETE localhost:8888/api/v1/cam/` - stop the camera instance.
+ - Response specific field: None
+
+#### Pipeline server API
+
+In the API description, localhost and port 8889 are assumed. Please change this for your specific case.
+
+* `GET localhost:8889/api/v1/pipeline` - get the list of available pipelines.
+ - Response specific field: "pipelines" - List of pipelines.
+
+* `POST localhost:8889/api/v1/pipeline?instance_id=id` - create a pipeline by passing its config as a JSON payload.
+ - Query parameter: instance_id (optional) - request a specific instance id. Must be unique.
+ - Response specific field: "instance_id", "stream", "config"
+
+* `POST localhost:8889/api/v1/pipeline/?instance_id=id` - create a pipeline from a named config.
+ - Query parameter: instance_id (optional) - request a specific instance id. Must be unique.
+ - Response specific field: "instance_id", "stream", "config"
+
+* `GET localhost:8889/api/v1/pipeline/instance/` - get pipeline instance stream address.
+ - Response specific field: "stream" - Stream address of the pipeline instance.
+
+* `GET localhost:8889/api/v1/pipeline/instance//info` - get pipeline instance info.
+ - Response specific field: "info" - JSON with instance info.
+
+* `GET localhost:8889/api/v1/pipeline/instance//config` - get pipeline instance config.
+ - Response specific field: "config" - JSON instance config.
+
+* `POST localhost:8889/api/v1/pipeline/instance//config` - set pipeline instance config - JSON payload.
+ - Response specific field: "config" - JSON instance config.
+
+* `GET localhost:8889/api/v1/pipeline//config` - get named pipeline config.
+ - Response specific field: "config" - JSON named pipeline config.
+
+* `POST localhost:8889/api/v1/pipeline//config` - set named pipeline config - JSON payload.
+ - Response specific field: "config" - JSON named pipeline config.
+
+* `GET localhost:8889/api/v1/pipeline/camera` - return the list of available cameras.
+ - Response specific field: "cameras" - List of cameras.
+
+* `POST localhost:8889/api/v1/pipeline/camera//background?n_images=10` - collect background for the camera.
+ - Query parameter: n_images (optional) = how many images to average for the background.
+ - Response specific field: "background_id" - ID of the acquired background.
+
+* `GET localhost:8889/api/v1/pipeline/camera//background` - return latest background for camera.
+ - Response specific field: "background_id" - ID of the latest background for the camera.
+
+* `GET localhost:8889/api/v1/pipeline/info` - return info on the pipeline manager.
+ - Response specific field: "info" - JSON with instance info.
+
+* `DELETE localhost:8889/api/v1/pipeline` - stop all pipeline instances.
+ - Response specific field: None
+
+* `DELETE localhost:8889/api/v1/pipeline/` - stop the pipeline instance.
+ - Response specific field: None
+
+
+## Running the servers
+
+The scripts for running the existing server are located under the **cam\_server/** folder.
+
+The two servers are:
+
+- **Camera server** (start_camera_server.py): Converts epics cameras into bsread cameras.
+- **Pipeline server** (start_pipeline_server.py): Processes cameras in bsread format.
+
+You can also use the docker container directly - it setups and starts both servers.
+
+Before you can run the servers, you need to have (and specify where you have it) the cameras, pipelines and background
+configurations. In this repo, you can find the test configurations inside the **tests/** folder. To use the
+production configuration, see **Production configuration** chapter below.
+
+
+### Camera server
+
+```bash
+usage: start_camera_server.py [-h] [-p PORT] [-i INTERFACE] [-b BASE]
+ [-n HOSTNAME]
+ [--log_level {CRITICAL,ERROR,WARNING,INFO,DEBUG}]
+
+Camera acquisition server
+
+optional arguments:
+ -h, --help show this help message and exit
+ -p PORT, --port PORT Server cam_port
+ -i INTERFACE, --interface INTERFACE
+ Hostname interface to bind to
+ -b BASE, --base BASE (Camera) Configuration base directory
+ -n HOSTNAME, --hostname HOSTNAME
+ Hostname to use when returning the stream address.
+ --log_level {CRITICAL,ERROR,WARNING,INFO,DEBUG}
+ Log level to use.
+```
+
+
+### Pipeline server
+
+```bash
+usage: start_pipeline_server.py [-h] [-c CAM_SERVER] [-p PORT] [-i INTERFACE]
+ [-b BASE] [-g BACKGROUND_BASE] [-n HOSTNAME]
+ [--log_level {CRITICAL,ERROR,WARNING,INFO,DEBUG}]
+
+Pipeline processing server
+
+optional arguments:
+ -h, --help show this help message and exit
+ -c CAM_SERVER, --cam_server CAM_SERVER
+ Cam server rest api address.
+ -p PORT, --port PORT Server port
+ -i INTERFACE, --interface INTERFACE
+ Hostname interface to bind to
+ -b BASE, --base BASE (Pipeline) Configuration base directory
+ -g BACKGROUND_BASE, --background_base BACKGROUND_BASE
+ -n HOSTNAME, --hostname HOSTNAME
+ Hostname to use when returning the stream address.
+ --log_level {CRITICAL,ERROR,WARNING,INFO,DEBUG}
+ Log level to use.
+
+```
+
+
+### Docker container
+To execute the application inside a docker container, you must first start it (from the project root folder):
+```bash
+docker run --net=host -it -v /CURRENT_DIR/tests:/configuration docker.psi.ch:5000/cam_server
+```
+
+**WARNING**: Docker needs (at least on OSX) a full path for the -v option. Replace the **CURRENT\_DIR** with your
+actual path.
+
+This will map the test configuration (-v option) to the /configuration folder inside the container.
+If you need to have the production configuration, see the next chapter.
+
+Once in the container bash, you can start the two servers:
+```bash
+camera_server & pipeline_server &
+```
+
+
+## Production configuration
+
+The production configurations are not part of this repository but are available on:
+- https://git.psi.ch/controls_highlevel_applications/cam_server_configuration
+
+You can download it using git:
+```bash
+git clone https://git.psi.ch/controls_highlevel_applications/cam_server_configuration.git
+```
+
+And later, when you start the docker container, map the configuration using the **-v** parameter:
+```bash
+docker run --net=host -it -v /CURRENT_DIR/cam_server_configuration/configuration:/configuration docker.psi.ch:5000/cam_server
+```
+
+**WARNING**: Docker needs (at least on OSX) a full path for the -v option. Replace the **CURRENT\_DIR** with your
+actual path.
+
+
+## Examples
+
+
+### Get the simulation camera stream
+
+**WARNING**: This example should not be used by normal users. CamClient is used by the
+underlying infrastructure to provide camera images to the pipeline server. See PipelineClient for a
+user oriented client.
+
+This is just an example on how you can retrieve the raw image from the camera. You **should not** do this for
+the normal use case. See next example for a more common use.
+
+```python
+from cam_server import CamClient
+from cam_server.utils import get_host_port_from_stream_address
+from bsread import source, SUB
+
+# Initialize the client.
+camera_client = CamClient()
+
+# Get stream address of simulation camera. Stream address in format tcp://hostname:port.
+camera_stream_address = camera_client.get_camera_stream("simulation")
+
+# Extract the stream hostname and port from the stream address.
+camera_host, camera_port = get_host_port_from_stream_address(camera_stream_address)
+
+# Subscribe to the stream.
+with source(host=camera_host, port=camera_port, mode=SUB) as stream:
+ # Receive next message.
+ data = stream.receive()
+
+image_width = data.data.data["width"].value
+image_height = data.data.data["height"].value
+image_bytes = data.data.data["image"].value
+
+print("Image size: %d x %d" % (image_width, image_height))
+print("Image data: %s" % image_bytes)
+```
+
+
+### Get a basic pipeline with a simulated camera
+
+In contrast with the example above, where we just request a camera stream, we have to create a pipeline instance
+in this example. We create a pipeline instance by specifying which camera - simulation in our example - to use as
+the pipeline source.
+
+By not giving any additional pipeline parameters, the image will in fact be exactly the same as the one in the
+previous example. Even if requesting a raw camera image, it is still advisable to use the PipelineClient (and create
+an empty pipeline as in the example below) because the CamClient might change and be moved to a different server out
+of your reach.
+
+```python
+from cam_server import PipelineClient
+from cam_server.utils import get_host_port_from_stream_address
+from bsread import source, SUB
+
+# Initialize the client.
+pipeline_client = PipelineClient()
+
+# Setup the pipeline config. Use the simulation camera as the pipeline source.
+pipeline_config = {"camera_name": "simulation"}
+
+# Create a new pipeline with the provided configuration. Stream address in format tcp://hostname:port.
+instance_id, pipeline_stream_address = pipeline_client.create_instance_from_config(pipeline_config)
+
+# Extract the stream hostname and port from the stream address.
+pipeline_host, pipeline_port = get_host_port_from_stream_address(pipeline_stream_address)
+
+# Subscribe to the stream.
+with source(host=pipeline_host, port=pipeline_port, mode=SUB) as stream:
+ # Receive next message.
+ data = stream.receive()
+
+image_width = data.data.data["width"].value
+image_height = data.data.data["height"].value
+image_bytes = data.data.data["image"].value
+
+print("Image size: %d x %d" % (image_width, image_height))
+print("Image data: %s" % image_bytes)
+```
+
+
+### Create a pipeline instance with background
+
+This example is the continuation of the previous example. Before creating our own pipeline, we collect the
+background for the simulation camera and apply it to the pipeline.
+
+```python
+from cam_server import PipelineClient
+
+camera_name = "simulation"
+
+# Initialize the client.
+pipeline_client = PipelineClient()
+
+# Collect the background for the given camera.
+background_id = pipeline_client.collect_background(camera_name)
+
+# Setup the pipeline config. Use the simulation camera as the pipeline source, and the collected background.
+pipeline_config = {"camera_name": camera_name,
+ "background_id": background_id}
+
+# Create a new pipeline with the provided configuration. Stream address in format tcp://hostname:port.
+instance_id, pipeline_stream_address = pipeline_client.create_instance_from_config(pipeline_config)
+
+# TODO: Continue as in the example above.
+```
+
+
+### Read the stream for a given camera name
+
+```python
+from cam_server import PipelineClient
+from cam_server.utils import get_host_port_from_stream_address
+from bsread import source, SUB
+
+# Define the camera name you want to read.
+camera_name = "SAROP21-PPRM102"
+
+# First create the pipeline for the selected camera.
+client = PipelineClient()
+instance_id, stream_address = client.create_instance_from_config({"camera_name": camera_name})
+
+# Extract the stream host and port from the stream_address.
+stream_host, stream_port = get_host_port_from_stream_address(stream_address)
+
+# Check if your instance is running on the server.
+if instance_id not in client.get_server_info()["active_instances"]:
+ raise ValueError("Requested pipeline is not running.")
+
+# Open connection to the stream. When exiting the 'with' section, the source disconnects by itself.
+with source(host=stream_host, port=stream_port, mode=SUB) as input_stream:
+ input_stream.connect()
+
+ # Read one message.
+ message = input_stream.receive()
+
+ # Print out the received stream data - dictionary.
+ print("Dictionary with data:\n", message.data.data)
+
+ # Print out the X center of mass.
+ print("X center of mass: ", message.data.data["x_center_of_mass"].value)
+
+```
+
+
+### Modifying camera config
+When modifying the camera config, the changes are applied immediately. As soon as you call **set\_camera\_config** the
+changes will be reflected in the camera stream within a couple of frames.
+
+```python
+from cam_server import CamClient
+
+# Initialize the camera client.
+cam_client = CamClient()
+
+# Print the list of available cameras.
+print(cam_client.get_cameras())
+
+# TODO: Put the name of the camera you want to modify.
+camera_to_modify = "test_camera"
+
+# Retrieve the camera config.
+camera_config = cam_client.get_camera_config(camera_to_modify)
+
+# Change the mirror_x setting.
+camera_config["mirror_x"] = False
+# Change the camera_calibration setting.
+camera_config["camera_calibration"] = {
+ "reference_marker": [ 0, 0, 100, 100 ],
+ "reference_marker_width": 100.0,
+ "reference_marker_height": 100.0,
+ "angle_horizontal": 0.0,
+ "angle_vertical": 0.0
+}
+
+# Save the camera configuration.
+cam_client.set_camera_config(camera_to_modify, camera_config)
+
+# You can also save the same (or another) config under a different camera name.
+cam_client.set_camera_config("camera_to_delete", camera_config)
+
+# And also delete camera configs.
+cam_client.delete_camera_config("camera_to_delete")
+```
+
+
+### Modifying pipeline config
+Please note that modifying the pipeline config works differently than modifying the camera config.
+When using **save\_pipeline\_config**, the changes do not affect existing pipelines. You need to restart or recreate
+the pipeline for the changes to be applied.
+
+You can however modify an existing instance config, by calling **set\_instance\_config**. The changes will be
+reflected in the pipeline stream within a couple of frames.
+
+```python
+from cam_server import PipelineClient
+
+# Initialize the pipeline client.
+pipeline_client = PipelineClient()
+
+# Print the list of available pipelines.
+print(pipeline_client.get_pipelines())
+
+# TODO: Put the name of the pipeline you want to modify.
+pipeline_to_modify = "test_pipeline"
+
+# Retrieve the camera config.
+pipeline_config = pipeline_client.get_pipeline_config(pipeline_to_modify)
+
+# Change the image threshold.
+pipeline_config["image_threshold"] = 0.5
+# Change the image region of interest.
+pipeline_config["image_region_of_interest"] = [0, 100, 0, 100]
+
+# Save the camera configuration.
+pipeline_client.save_pipeline_config(pipeline_to_modify, pipeline_config)
+
+# You can also save the same (or another) config under a different camera name.
+pipeline_client.save_pipeline_config("pipeline_to_delete", pipeline_config)
+
+# And also delete camera configs.
+pipeline_client.delete_pipeline_config("pipeline_to_delete")
+```
+
+
+### Create a new camera
+This example shows how to create a new camera config.
+
+```python
+from cam_server import CamClient
+
+# Initialize the camera client.
+cam_client = CamClient()
+
+# Specify the desired camera config.
+camera_config = {
+ "name": "camera_example_3",
+ "source": "EPICS:CAM1:EXAMPLE",
+ "source_type": "epics",
+ "mirror_x": False,
+ "mirror_y": False,
+ "rotate": 0,
+
+ "camera_calibration": {
+ "reference_marker": [ 0, 0, 100, 100 ],
+ "reference_marker_width": 100.0,
+ "reference_marker_height": 100.0,
+ "angle_horizontal": 0.0,
+ "angle_vertical": 0.0
+ }
+}
+
+# Specify the new camera name.
+new_camera_name = "new_camera_name"
+
+# Save the camera configuration.
+cam_client.set_camera_config(new_camera_name, camera_config)
+
+# In case you need to, delete the camera config you just added.
+# cam_client.cam_client.delete_camera_config(new_camera_name)
+```
+
+
+### Get single message from screen_panel stream
+You should have the screen_panel open, with the camera you want to acquire running. We will connect to the same
+stream instance as the screen_panel uses, which means that all the changes done in the screen panel will also be
+reflected in our acquisition (you can configure what you want to acquire in the screen panel).
+
+```python
+from cam_server import PipelineClient
+
+# Instantiate the pipeline client.
+pipeline_client = PipelineClient()
+
+# Name of the camera we want to get a message from.
+camera_name = "simulation"
+# Screen panel defines the instance name as: [CAMERA_NAME]_sp1
+instance_name = camera_name + "_sp1"
+
+# Get the data.
+data = pipeline_client.get_instance_message(instance_name)
+```
+
+
+### Save camera stream to H5 file
+```python
+from bsread import h5, SUB
+from cam_server import PipelineClient
+
+camera_name = "simulation"
+file_name = "output.h5"
+n_messages = 10
+
+client = PipelineClient()
+
+instance_id, stream_address = client.create_instance_from_config({"camera_name": camera_name})
+
+# The output file 'output.h5' has 10 images from the simulation camera stream.
+h5.receive(source=stream_address, file_name=file_name, mode=SUB, n_messages=n_messages)
+```
+
+#### Commandline
+
+Following script can be used to dump the camera stream to an hdf5 file using the `bs h5` tool. The process to use this script is to
+
+1) Open ScreenPanel and select desired camera
+2) (Optional) Configure all the calculations you are interested in in the ScreenPanel
+3) Use the script as follows (if you name the script `record_camera`): `record_camera `
+
+```bash
+#!/bin/bash
+
+if (( $# != 2 )); then
+ echo "usage: $0 "
+ exit -1
+fi
+
+CAMERA_NAME=$1
+FILENAME=$2
+
+STREAM=$(curl -s http://sf-daqsync-01:8889/api/v1/pipeline/instance/${CAMERA_NAME}_sp1 | sed -e 's/.*"stream": "\([^"]*\)".*/\1/')
+echo $STREAM
+bs h5 -s $STREAM -m sub $FILENAME
+```
+
+
+## Deploy in production
+
+Before deploying in production, make sure the latest version was tagged in git (this triggers the Travis build) and
+that the Travis build completed successfully (the new cam_server package in available in anaconda). After this 2 steps,
+you need to build the new version of the docker image (the docker image checks out the latest version of cam_server
+from Anaconda). The docker image version and the cam_server version should always match - If they don't, something went
+wrong.
+
+### Production configuration
+Login to the target system, where cam_server will be running. Checkout the production configuration into the root
+of the target system filesystem.
+
+```bash
+cd /
+git clone https://git.psi.ch/controls_highlevel_applications/cam_server_configuration.git
+```
+
+### Setup the cam_server as a service
+On the target system, copy **docker/camera_server.service** and **docker/pipeline_server.service** into
+**/etc/systemd/system**.
+
+Then need to reload the systemctl daemon:
+```bash
+systemctl daemon-reload
+```
+
+### Verifying the configuration
+On the target system, copy **docker/validate_configs.sh** into your home folder.
+Run it to verify if the deployed configurations are valid for the current version of the cam_server.
+
+```bash
+./validate_configs.sh
+```
+
+### Run the servers
+Using systemctl you then run both servers:
+```bash
+systemctl start camera_server.service
+systemctl start pipeline_server.service
+```
+
+### Inspecting server logs
+To inspect the logs for each server, use journalctl:
+```bash
+journalctl -u camera_server.service
+journalctl -u pipeline_server.service
+```
+
+Note: The '-f' flag will make you follow the log file.