diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css
new file mode 100644
index 0000000..89a87df
--- /dev/null
+++ b/docs/source/_static/custom.css
@@ -0,0 +1,5 @@
+/* custom.css */
+.red-text {
+ color: red;
+ font-weight: bold;
+}
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..a2357e4
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,50 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('.'))
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'CARTA ICD Tests'
+copyright = '2025, Cheng-Chin Chiang'
+author = 'Cheng-Chin Chiang'
+release = '0.0'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+ 'sphinxcontrib.plantuml', # Support PlantUML
+ 'sphinx.ext.autodoc', # Auto-documentation from docstrings
+ 'sphinx.ext.autosummary', # Generate autodoc summaries
+ 'sphinx.ext.viewcode', # Add links to highlighted source code
+ 'sphinx.ext.napoleon', # Support for NumPy and Google style docstrings
+ 'sphinx.ext.intersphinx', # Link to other project's documentation
+ 'sphinx.ext.todo', # Support for todo items
+ 'sphinx.ext.coverage', # Coverage checker for documentation
+ 'sphinx.ext.ifconfig', # Include content based on configuration
+]
+
+plantuml = 'plantuml'
+plantuml_output_format = 'svg'
+
+# templates_path = ['_templates']
+exclude_patterns = []
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'sphinx_rtd_theme'
+html_static_path = ['_static']
+html_css_files = ["custom.css"]
+
+rst_prolog = """
+.. role:: red-text(emphasis)
+ :class: red-text
+"""
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..5c92d92
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,15 @@
+.. CARTA ICD Tests documentation master file, created by
+ sphinx-quickstart on Mon Sep 8 10:15:50 2025.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+CARTA ICD Tests Documentation
+=============================
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents
+
+ introduction
+ test_cases
+
diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst
new file mode 100644
index 0000000..e0a005f
--- /dev/null
+++ b/docs/source/introduction.rst
@@ -0,0 +1,125 @@
+Introduction
+============
+
+The Interface Control Document (ICD) Test for the CARTA backend with WebSocket connection is designed
+to validate the correctness, stability, and compliance of the backend implementation against the
+agreed communication protocols defined in the ICD.
+The ICD specifies the structure, format, and sequence of messages exchanged between the backend server
+and the client applications, ensuring interoperability and consistency across the system.
+
+In this test framework, the WebSocket protocol is used as the primary communication channel between
+the backend and the client. WebSocket provides a full-duplex, low-latency communication mechanism that
+is essential for real-time data exchange, event-driven interactions, and responsive user experiences.
+The test environment replicates production-like scenarios by opening persistent connections,
+exchanging request and response messages, and monitoring the message flows in accordance with the
+ICD specifications.
+
+To implement and manage the ICD tests, we use RxJS (Reactive Extensions for JavaScript) as the core
+testing tool. RxJS provides a powerful and expressive abstraction for handling asynchronous event
+streams, making it particularly well-suited for testing WebSocket-based interactions.
+By leveraging observables, operators, and stream composition, RxJS allows us to define reproducible
+test cases, capture message sequences, and verify protocol compliance with high precision.
+This approach simplifies the validation of complex asynchronous workflows and ensures that the
+backend behaves as expected under a wide range of conditions.
+
+The primary objectives of these tests are:
+
+1. Protocol Compliance – To confirm that the backend strictly adheres to the message definitions,
+ sequencing rules, and error-handling requirements described in the ICD.
+2. Functional Validation – To verify that backend services provide the expected responses to client
+ requests under normal and edge-case conditions.
+3. Robustness and Stability – To assess the backend’s ability to maintain reliable connections, handle
+ concurrent sessions, and recover gracefully from failures.
+4. Performance Evaluation – To measure latency, throughput, and resource usage during message exchanges,
+ ensuring scalability for operational workloads.
+
+By systematically exercising the WebSocket-based backend through these ICD tests, we ensure alignment
+with the defined interface contract, minimize integration risks, and provide confidence in the backend’s
+readiness for deployment in production environments.
+
+Build Tests
+===========
+
+The ICD test project relies heavily on **Node.js** and **npm**, so ensure that both are properly
+installed and accessible in your environment before proceeding.
+
+0. **Clone the source code from the ICD-RxJS GitHub repository**
+
+ ::
+
+ git clone https://github.com/CARTAvis/ICD-RxJS.git
+
+1. **Initialize submodules and install dependencies**
+
+ ::
+
+ cd ICD-RxJS
+ git submodule update --init --recursive
+ npm install
+
+2. **Build static Protocol Buffer code**
+
+ The script ``build_proto.sh`` located in the ``protobuf`` folder generates the static JavaScript code
+ and the corresponding TypeScript definitions. It also creates symbolic links to the
+ ``node_modules/carta-protobuf`` directory for seamless integration.
+
+ ::
+
+ cd ICD-rxjs/protobuf
+ ./build_proto.sh
+
+This process ensures that all dependencies are properly set up and that the protocol buffer definitions
+are correctly compiled before running the ICD tests.
+
+Run Tests
+=========
+
+For local testing, configure the ``src/test/config.json`` file by setting:
+
+.. code-block:: json
+
+ "serverURL": "ws://127.0.0.1:3002"
+
+Here, ``3002`` corresponds to the backend port number in this example.
+
+For server-side testing, update the same configuration file with:
+
+.. code-block:: json
+
+ "serverURL": "wss://carta.asiaa.sinica.edu.tw/socketdev"
+
+Run One Test at a Time
+----------------------
+
+To minimize side effects such as concurrency issues or heavy I/O traffic,
+it is recommended to run tests individually. A basic connectivity test is always
+available at the beginning of the test suite,
+and the target server address can be adjusted in ``src/test/config.json``.
+
+Example Test Runs
+-----------------
+
+- **Verify backend connectivity**:
+
+ .. code-block:: bash
+
+ npm test src/test/ACCESS_WEBSOCKET.test.ts
+
+ or
+
+ .. code-block:: bash
+
+ npm test src/test/ACCESS_CARTA_DEFAULT.test.ts
+
+ If these tests fail, review and adjust the parameters in ``config.json`` to
+ match your environment.
+
+- **Validate supported file formats**:
+
+ .. code-block:: bash
+
+ npm test src/test/FILEINFO.test.ts
+
+ If this test fails, you may need to increase the timeout values in
+ ``config.json``, such as ``timeout.readFile`` or ``timeout.openFile``.
+
diff --git a/docs/source/region_stats.rst b/docs/source/region_stats.rst
new file mode 100644
index 0000000..19fae63
--- /dev/null
+++ b/docs/source/region_stats.rst
@@ -0,0 +1,873 @@
+Region Statistics
+-----------------
+
+.. uml::
+
+ skinparam style strictuml
+ hide footbox
+ title Creating a region and set stats requirements
+
+ actor User
+
+ box "Client-side" #EDEDED
+ participant Frontend
+ end box
+
+ box "Server-side" #lightblue
+ participant Backend
+ end box
+
+ User -> Frontend: Open a new image
+ activate Frontend
+ Frontend -> Backend : 1. CLOSE_FILE
+ activate Backend
+ Frontend -> Backend : 2. OPEN_FILE
+ Frontend <-- Backend : 3. OPEN_FILE_ACK
+ Frontend -> Backend : 4. ADD_REQUIRED_TILES
+ Frontend -> Backend : 5. SET_CURSOR
+ Frontend <-- Backend : 6. REGION_HISTOGRAM_DATA
+ Frontend <-- Backend : 6. SPATIAL_PROFILE_DATA
+ Frontend <-- Backend : 6. RASTER_TILE_SYNC
+ Frontend <-- Backend : 6. RASTER_TILE_DATA
+ Frontend <-- Backend : 6. RASTER_TILE_SYNC
+ deactivate Backend
+ User <-- Frontend: Displays image, histogram, \nand spatial profile
+ deactivate Frontend
+
+ User -> Frontend: Draws new region
+ activate Frontend
+ Frontend -> Backend : 7. SET_REGION
+ activate Backend
+ Frontend <--[#red] Backend : 8. SET_REGION_ACK [Check 1]
+ deactivate Backend
+ User <-- Frontend: Displays region overlay
+ deactivate Frontend
+
+ User -> Frontend: Set region stats requirements
+ activate Frontend
+ Frontend -> Backend : 9. SET_STATS_REQUIREMENTS
+ activate Backend
+ Frontend <--[#red] Backend : 10. REGION_STATS_DATA [Check 2]
+ deactivate Backend
+ User <-- Frontend: Displays region stats data
+ deactivate Frontend
+
+REGION_STATISTICS_RECTANGLE
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **CLOSE_FILE** (``CloseFile``)
+
+ .. code-block:: protobuf
+
+ file_id = -1
+
+2. Frontend sends: **OPEN_FILE** (``OpenFile``)
+
+ .. code-block:: protobuf
+
+ directory = "set_QA"
+ file = "M17_SWex.image"
+ file_id = 0
+ hdu = ""
+ render_mode = 0
+
+3. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``)
+
+4. Frontend sends: **ADD_REQUIRED_TILES** (``AddRequiredTiles``)
+
+ .. code-block:: protobuf
+
+ tiles = [0]
+ file_id = 0
+ compression_quality = 11
+ compression_type = 1
+
+5. Frontend sends: **SET_CURSOR** (``SetCursor``)
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ point = {x: 1, y: 1}
+
+6. Backend returns:
+
+ **REGION_HISTOGRAM_DATA**
+
+ **SPATIAL_PROFILE_DATA**
+
+ **RASTER_TILE_SYNC**
+
+ **RASTER_TILE_DATA**
+
+ **RASTER_TILE_SYNC**
+
+7. Frontend sends: **SET_REGION** (``SetRegion``)
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "rectangle_1"
+ region_type = 3
+ control_points = [{x: 212, y: 464}, {x: 10, y: 10}]
+ rotation = 0
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "rectangle_2"
+ region_type = 3
+ control_points = [{x: 103, y: 549}, {x: 5, y: 7}]
+ rotation = 0
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "rectangle_3"
+ region_type = 3
+ control_points = [{x: 115, y: 544}, {x: 5, y: 7}]
+ rotation = 300
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "rectangle_4"
+ region_type = 3
+ control_points = [{x: 0, y: 544}, {x: 5, y: 7}]
+ rotation = 300
+
+ Case 5: (empty)
+
+8. Backend returns: **SET_REGION_ACK** (``SetRegionAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - SET_REGION_ACK should arrives within 100 ms
+
+ - SET_REGION_ACK should contains:
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 1
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 2
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 3
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 4
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 5
+
+9. Frontend sends: **SET_STATS_REQUIREMENTS** (``SetRegionRequirements``)
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 1
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 2
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 3
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 4
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+10. Backend returns: **REGION_STATS_DATA** (``RegionStatsData``)
+
+:red-text:`Check 2:` the backend message shoduld satisfies:
+
+ - REGION_STATS_DATA should arrives within 5000 ms
+
+ - REGION_STATS_DATA should contains (precision < %.4E):
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ region_id = 1
+ statistics: [
+ CARTA.StatsType.NumPixels = 121,
+ CARTA.StatsType.Sum = 0.28389804,
+ CARTA.StatsType.FluxDensity = 0.01304297,
+ CARTA.StatsType.Mean = 0.00234626,
+ CARTA.StatsType.RMS = 0.00388394,
+ CARTA.StatsType.Sigma = 0.00310803,
+ CARTA.StatsType.SumSq = 0.00182528,
+ CARTA.StatsType.Min = -0.00358113,
+ CARTA.StatsType.Max = 0.00793927,
+ CARTA.StatsType.Extrema = 0.00793926
+ ]
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ region_id = 2
+ statistics: [
+ CARTA.StatsType.NumPixels = 14,
+ CARTA.StatsType.Sum = -0.03493149,
+ CARTA.StatsType.FluxDensity = -0.00160484,
+ CARTA.StatsType.Mean = -0.00249511,
+ CARTA.StatsType.RMS = 0.00586684,
+ CARTA.StatsType.Sigma = 0.00551027,
+ CARTA.StatsType.SumSq = 0.00048188,
+ CARTA.StatsType.Min = -0.0095625,
+ CARTA.StatsType.Max = 0.00694707,
+ CARTA.StatsType.Extrema = -0.00956249
+ ]
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ region_id = 3
+ statistics: [
+ CARTA.StatsType.NumPixels = 35,
+ CARTA.StatsType.Sum = 0.16957074,
+ CARTA.StatsType.FluxDensity = 0.0077905,
+ CARTA.StatsType.Mean = 0.00484488,
+ CARTA.StatsType.RMS = 0.01209958,
+ CARTA.StatsType.Sigma = 0.01124911,
+ CARTA.StatsType.SumSq = 0.00512399,
+ CARTA.StatsType.Min = -0.01768329,
+ CARTA.StatsType.Max = 0.02505673,
+ CARTA.StatsType.Extrema = 0.02505672
+ ]
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ region_id = 4
+ statistics: [
+ CARTA.StatsType.NumPixels = 0,
+ CARTA.StatsType.Sum = NaN,
+ CARTA.StatsType.FluxDensity = NaN,
+ CARTA.StatsType.Mean = NaN,
+ CARTA.StatsType.RMS = NaN,
+ CARTA.StatsType.Sigma = NaN,
+ CARTA.StatsType.SumSq = NaN,
+ CARTA.StatsType.Min = NaN,
+ CARTA.StatsType.Max = NaN,
+ CARTA.StatsType.Extrema = NaN,
+ ]
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ region_id = -1
+ statistics: [
+ CARTA.StatsType.NumPixels = 216248,
+ CARTA.StatsType.Sum = -7.6253559,
+ CARTA.StatsType.FluxDensity = -0.35032758,
+ CARTA.StatsType.Mean = -3.52620875e-05,
+ CARTA.StatsType.RMS = 0.00473442,
+ CARTA.StatsType.Sigma = 0.0047343,
+ CARTA.StatsType.SumSq = 4.84713562,
+ CARTA.StatsType.Min = -0.03958673,
+ CARTA.StatsType.Max = 0.04523611,
+ CARTA.StatsType.Extrema = 0.04523611,
+ ]
+
+REGION_STATISTICS_ELLIPSE
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **CLOSE_FILE** (``CloseFile``)
+
+ .. code-block:: protobuf
+
+ file_id = -1
+
+2. Frontend sends: **OPEN_FILE** (``OpenFile``)
+
+ .. code-block:: protobuf
+
+ directory = "set_QA"
+ file = "M17_SWex.image"
+ file_id = 0
+ hdu = ""
+ render_mode = 0
+
+3. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``)
+
+4. Frontend sends: **ADD_REQUIRED_TILES** (``AddRequiredTiles``)
+
+ .. code-block:: protobuf
+
+ tiles = [0]
+ file_id = 0
+ compression_quality = 11
+ compression_type = 1
+
+5. Frontend sends: **SET_CURSOR** (``SetCursor``)
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ point = {x: 1, y: 1}
+
+6. Backend returns:
+
+ **REGION_HISTOGRAM_DATA**
+
+ **SPATIAL_PROFILE_DATA**
+
+ **RASTER_TILE_SYNC**
+
+ **RASTER_TILE_DATA**
+
+ **RASTER_TILE_SYNC**
+
+7. Frontend sends: **SET_REGION** (``SetRegion``)
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "ellipse_1"
+ region_type = 4
+ control_points = [{x: 114, y: 545}, {x: 4, y: 2}]
+ rotation = 0
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "ellipse_2"
+ region_type = 4
+ control_points = [{x: 83, y: 489}, {x: 4, y: 3}]
+ rotation = 30
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "ellipse_3"
+ region_type = 4
+ control_points = [{x: 0, y: 486}, {x: 4, y: 3}]
+ rotation = 30
+
+8. Backend returns: **SET_REGION_ACK** (``SetRegionAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - SET_REGION_ACK should arrives within 100 ms
+
+ - SET_REGION_ACK should contains:
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 1
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 2
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 3
+
+9. Frontend sends: **SET_STATS_REQUIREMENTS** (``SetRegionRequirements``)
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 1
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 2
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 3
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+10. Backend returns: **REGION_STATS_DATA** (``RegionStatsData``)
+
+:red-text:`Check 2:` the backend message shoduld satisfies:
+
+ - REGION_STATS_DATA should arrives within 5000 ms
+
+ - REGION_STATS_DATA should contains (precision < %.4E):
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ region_id = 1
+ statistics: [
+ CARTA.StatsType.NumPixels = 24,
+ CARTA.StatsType.Sum = 0.18536625,
+ CARTA.StatsType.FluxDensity = 0.00851618,
+ CARTA.StatsType.Mean = 0.00772359,
+ CARTA.StatsType.RMS = 0.01397174,
+ CARTA.StatsType.Sigma = 0.01189324,
+ CARTA.StatsType.SumSq = 0.00468503,
+ CARTA.StatsType.Min = -0.01768329,
+ CARTA.StatsType.Max = 0.02505673,
+ CARTA.StatsType.Extrema = 0.02505672
+ ]
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ region_id = 2
+ statistics: [
+ CARTA.StatsType.NumPixels = 18,
+ CARTA.StatsType.Sum = 0.00485459,
+ CARTA.StatsType.FluxDensity = 0.00022303,
+ CARTA.StatsType.Mean = 0.0002697,
+ CARTA.StatsType.RMS = 0.00307906,
+ CARTA.StatsType.Sigma = 0.00315614,
+ CARTA.StatsType.SumSq = 0.00017065,
+ CARTA.StatsType.Min = -0.00590614,
+ CARTA.StatsType.Max = 0.00654556,
+ CARTA.StatsType.Extrema = 0.00654555
+ ]
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ region_id = 3
+ statistics: [
+ CARTA.StatsType.NumPixels = 0,
+ CARTA.StatsType.Sum = NaN,
+ CARTA.StatsType.FluxDensity = NaN,
+ CARTA.StatsType.Mean = NaN,
+ CARTA.StatsType.RMS = NaN,
+ CARTA.StatsType.Sigma = NaN,
+ CARTA.StatsType.SumSq = NaN,
+ CARTA.StatsType.Min = NaN,
+ CARTA.StatsType.Max = NaN,
+ CARTA.StatsType.Extrema = NaN
+ ]
+
+REGION_STATISTICS_POLYGON
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **CLOSE_FILE** (``CloseFile``)
+
+ .. code-block:: protobuf
+
+ file_id = -1
+
+2. Frontend sends: **OPEN_FILE** (``OpenFile``)
+
+ .. code-block:: protobuf
+
+ directory = "set_QA"
+ file = "M17_SWex.fits"
+ file_id = 0
+ hdu = "0"
+ render_mode = 0
+
+3. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``)
+
+4. Frontend sends: **ADD_REQUIRED_TILES** (``AddRequiredTiles``)
+
+ .. code-block:: protobuf
+
+ tiles = [0]
+ file_id = 0
+ compression_quality = 11
+ compression_type = 1
+
+5. Frontend sends: **SET_CURSOR** (``SetCursor``)
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ point = {x: 1, y: 1}
+
+6. Backend returns:
+
+ **REGION_HISTOGRAM_DATA**
+
+ **SPATIAL_PROFILE_DATA**
+
+ **RASTER_TILE_SYNC**
+
+ **RASTER_TILE_DATA**
+
+ **RASTER_TILE_SYNC**
+
+7. Frontend sends: **SET_REGION** (``SetRegion``)
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "polygon_1"
+ region_type = 6
+ control_points = [{x: 155, y: 552}, {x: 134, y: 498}, {x: 185, y: 509}]
+ rotation = 0
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "polygon_2"
+ region_type = 6
+ control_points = [{x: 116, y: 604}, {x: 106, y: 574}, {x: 137, y: 577}]
+ rotation = 0
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "polygon_3"
+ region_type = 6
+ control_points = [{x: 556, y: 167}, {x: 547, y: 130}, {x: 577, y: 139}]
+ rotation = 0
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "polygon_4"
+ region_type = 6
+ control_points = [{x: 65, y: 688}, {x: 69, y: 36}, {x: 602, y: 77}, {x: 562, y: 735}]
+ rotation = 0
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "polygon_5"
+ region_type = 6
+ control_points = [{x: 300.2, y: 300.2}, {x: 300.2, y: 301.0}, {x: 300.7, y: 300.2}]
+ rotation = 0
+
+ Case 6:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = -1
+ region_name = "polygon_5"
+ region_type = 6
+ control_points = [{x: 299.5, y: 300.5}, {x: 299.5, y: 299.5}, {x: 300.5, y: 299.5}, {x: 300.5, y: 300.5}]
+ rotation = 0
+
+8. Backend returns: **SET_REGION_ACK** (``SetRegionAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - SET_REGION_ACK should arrives within 100 ms
+
+ - SET_REGION_ACK should contains:
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 1
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 2
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 3
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 4
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 5
+
+ Case 6:
+
+ .. code-block:: protobuf
+
+ success = True
+ region_id = 6
+
+9. Frontend sends: **SET_STATS_REQUIREMENTS** (``SetRegionRequirements``)
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 1
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 2
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 3
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 4
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 5
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+ Case 6:
+
+ .. code-block:: protobuf
+
+ file_id = 0
+ region_id = 6
+ stats = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+10. Backend returns: **REGION_STATS_DATA** (``RegionStatsData``)
+
+:red-text:`Check 2:` the backend message shoduld satisfies:
+
+ - REGION_STATS_DATA should arrives within 5000 ms
+
+ - REGION_STATS_DATA should contains (precision < %.4E):
+
+ Case 1:
+
+ .. code-block:: protobuf
+
+ region_id = 1
+ statistics: [
+ CARTA.StatsType.NumPixels = 1265,
+ CARTA.StatsType.Sum = 1.2024647,
+ CARTA.StatsType.FluxDensity = 0.05524418,
+ CARTA.StatsType.Mean = 0.00095056,
+ CARTA.StatsType.RMS = 0.00372206,
+ CARTA.StatsType.Sigma = 0.00360005,
+ CARTA.StatsType.SumSq = 0.01752493,
+ CARTA.StatsType.Min = -0.01051447,
+ CARTA.StatsType.Max = 0.01217441,
+ CARTA.StatsType.Extrema = 0.01217440
+ ]
+
+ Case 2:
+
+ .. code-block:: protobuf
+
+ region_id = 2
+ statistics: [
+ CARTA.StatsType.NumPixels = 132,
+ CARTA.StatsType.Sum = -0.09657376,
+ CARTA.StatsType.FluxDensity = -0.00443684,
+ CARTA.StatsType.Mean = -0.00073162,
+ CARTA.StatsType.RMS = 0.00945348,
+ CARTA.StatsType.Sigma = 0.00946103,
+ CARTA.StatsType.SumSq = 0.01179662,
+ CARTA.StatsType.Min = -0.01994896,
+ CARTA.StatsType.Max = 0.0235076,
+ CARTA.StatsType.Extrema = 0.02350760
+ ]
+
+ Case 3:
+
+ .. code-block:: protobuf
+
+ region_id = 3
+ statistics: [
+ CARTA.StatsType.NumPixels = 0,
+ CARTA.StatsType.Sum = NaN,
+ CARTA.StatsType.FluxDensity = NaN,
+ CARTA.StatsType.Mean = NaN,
+ CARTA.StatsType.RMS = NaN,
+ CARTA.StatsType.Sigma = NaN,
+ CARTA.StatsType.SumSq = NaN,
+ CARTA.StatsType.Min = NaN,
+ CARTA.StatsType.Max = NaN,
+ CARTA.StatsType.Extrema = NaN
+ ]
+
+ Case 4:
+
+ .. code-block:: protobuf
+
+ region_id = 4
+ statistics: [
+ CARTA.StatsType.NumPixels = 216248,
+ CARTA.StatsType.Sum = -7.6253559,
+ CARTA.StatsType.FluxDensity = -0.35032758,
+ CARTA.StatsType.Mean = -3.52620875e-05,
+ CARTA.StatsType.RMS = 0.00473442,
+ CARTA.StatsType.Sigma = 0.0047343,
+ CARTA.StatsType.SumSq = 4.84713562,
+ CARTA.StatsType.Min = -0.03958673,
+ CARTA.StatsType.Max = 0.04523611,
+ CARTA.StatsType.Extrema = 0.04523611,
+ ]
+
+ Case 5:
+
+ .. code-block:: protobuf
+
+ region_id = 5
+ statistics: [
+ CARTA.StatsType.NumPixels = 0,
+ CARTA.StatsType.Sum = NaN,
+ CARTA.StatsType.FluxDensity = NaN,
+ CARTA.StatsType.Mean = NaN,
+ CARTA.StatsType.RMS = NaN,
+ CARTA.StatsType.Sigma = NaN,
+ CARTA.StatsType.SumSq = NaN,
+ CARTA.StatsType.Min = NaN,
+ CARTA.StatsType.Max = NaN,
+ CARTA.StatsType.Extrema = NaN,
+ ]
+
+ Case 6:
+
+ .. code-block:: protobuf
+
+ region_id = 6
+ statistics: [
+ CARTA.StatsType.NumPixels = 1,
+ CARTA.StatsType.Sum = -0.00115214,
+ CARTA.StatsType.FluxDensity = -5.29322955e-05,
+ CARTA.StatsType.Mean = -0.00115214,
+ CARTA.StatsType.RMS = 0.00115214,
+ CARTA.StatsType.Sigma = 0,
+ CARTA.StatsType.SumSq = 1.32743435e-06,
+ CARTA.StatsType.Min = -0.00115214,
+ CARTA.StatsType.Max = -0.00115214,
+ CARTA.StatsType.Extrema = -0.00115214,
+ ]
diff --git a/docs/source/session.rst b/docs/source/session.rst
new file mode 100644
index 0000000..afbba28
--- /dev/null
+++ b/docs/source/session.rst
@@ -0,0 +1,161 @@
+Session
+-------
+
+.. uml::
+
+ skinparam style strictuml
+ hide footbox
+ title Initial connection
+
+ actor User
+ box "Client-side"
+ participant Frontend
+ end box
+
+ box "Server-side" #lightblue
+ participant Backend
+ end box
+
+ User -> Frontend : Loads app/page
+ activate Frontend
+ Frontend -> Backend : Connects to backend (WS)
+ activate Backend
+ Frontend <-- Backend : Connection response (WS)
+ Frontend -> Backend : 1. REGISTER_VIEWER
+ Frontend <--[#red] Backend : 2. REGISTER_VIEWER_ACK [Check 1]
+ deactivate Backend
+ User <-- Frontend : Connection info updated
+ deactivate Frontend
+
+ACCESS_CARTA_DEFAULT
+~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **REGISTER_VIEWER** (``RegisterViewer``)
+
+ .. code-block:: protobuf
+
+ session_id = "0"
+ api_key = ""
+ client_feature_flags = 5
+
+2. Backend returns: **REGISTER_VIEWER_ACK** (``RegisterViewerAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - REGISTER_VIEWER_ACK should arrives within 100 ms
+
+ - REGISTER_VIEWER_ACK should contains:
+
+ .. code-block:: protobuf
+
+ success = True
+ session_id =
+ session_type = 0
+ server_feature_flags = 8
+ user_preferences = {}
+ user_layouts = {}
+
+ACCESS_CARTA_KNOWN_SESSION
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **REGISTER_VIEWER** (``RegisterViewer``)
+
+ .. code-block:: protobuf
+
+ session_id = "9999"
+ api_key = ""
+ client_feature_flags = 5
+
+2. Backend returns: **REGISTER_VIEWER_ACK** (``RegisterViewerAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - REGISTER_VIEWER_ACK should arrives within 100 ms
+
+ - REGISTER_VIEWER_ACK should contains:
+
+ .. code-block:: protobuf
+
+ success = True
+ session_id = "9999"
+ session_type = 1
+ server_feature_flags = 8
+ user_preferences = {}
+ user_layouts = {}
+ message =
+
+ACCESS_CARTA_NO_CLIENT_FEATURE
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **REGISTER_VIEWER** (``RegisterViewer``)
+
+ .. code-block:: protobuf
+
+ session_id = "0"
+ api_key = ""
+ client_feature_flags = 0
+
+2. Backend returns: **REGISTER_VIEWER_ACK** (``RegisterViewerAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - REGISTER_VIEWER_ACK should arrives within 100 ms
+
+ - REGISTER_VIEWER_ACK should contains:
+
+ .. code-block:: protobuf
+
+ success = True
+ session_id =
+ session_type = 0
+ server_feature_flags = 8
+ user_preferences = {}
+ user_layouts = {}
+ message =
+
+ACCESS_CARTA_SAME_ID_TWICE
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the `source code `__.
+
+1. Frontend sends: **REGISTER_VIEWER** (``RegisterViewer``)
+
+ .. code-block:: protobuf
+
+ session_id = "12345"
+ api_key = ""
+ client_feature_flags = 5
+
+2. Backend returns: **REGISTER_VIEWER_ACK** (``RegisterViewerAck``)
+
+3. Frontend sends: **REGISTER_VIEWER** (``RegisterViewer``)
+
+ .. code-block:: protobuf
+
+ session_id = "12345"
+ api_key = ""
+ client_feature_flags = 5
+
+4. Backend returns: **REGISTER_VIEWER_ACK** (``RegisterViewerAck``)
+
+:red-text:`Check 1:` the backend message shoduld satisfies:
+
+ - REGISTER_VIEWER_ACK should arrives within 100 ms
+
+ - REGISTER_VIEWER_ACK should contains:
+
+ .. code-block:: protobuf
+
+ success = True
+ session_id = "12345"
+ session_type = 1
+ server_feature_flags = 8
+ user_preferences = {}
+ user_layouts = {}
+
diff --git a/docs/source/test_cases.rst b/docs/source/test_cases.rst
new file mode 100644
index 0000000..9e45280
--- /dev/null
+++ b/docs/source/test_cases.rst
@@ -0,0 +1,8 @@
+ICD Test Cases
+==============
+
+.. toctree::
+ :maxdepth: 2
+
+ session
+ region_stats