From 3582fce74f871c666f9305c7fd787dd372570069 Mon Sep 17 00:00:00 2001
From: swittl <simon.wittl@th-deg.de>
Date: Thu, 11 Jul 2024 18:26:42 +0200
Subject: [PATCH] added first test and some more doc strings

---
 .vscode/settings.json                         |   7 +-
 .../__pycache__/__init__.cpython-38.pyc       | Bin 305 -> 305 bytes
 .../__pycache__/projection.cpython-38.pyc     | Bin 4308 -> 6548 bytes
 .../projection_geometry.cpython-38.pyc        | Bin 2113 -> 3846 bytes
 rq_controller/common/projection.py            |  58 +++++++-
 rq_controller/common/projection_geometry.py   |  43 ++++++
 rq_controller/common/region_of_intrest.py     |  78 ++++++++++
 rq_controller/common/volume.py                |  77 +++++++++-
 ...test_copyright.cpython-38-pytest-8.2.2.pyc | Bin 0 -> 1030 bytes
 .../test_flake8.cpython-38-pytest-8.2.2.pyc   | Bin 0 -> 989 bytes
 .../test_pep257.cpython-38-pytest-8.2.2.pyc   | Bin 0 -> 941 bytes
 ...est_projection.cpython-38-pytest-8.2.2.pyc | Bin 0 -> 20692 bytes
 ...ction_geometry.cpython-38-pytest-8.2.2.pyc | Bin 0 -> 11871 bytes
 ...ion_of_intrest.cpython-38-pytest-8.2.2.pyc | Bin 0 -> 12566 bytes
 .../test_volume.cpython-38-pytest-8.2.2.pyc   | Bin 0 -> 9180 bytes
 test/test_copyright.py                        |  25 ----
 test/test_flake8.py                           |  25 ----
 test/test_pep257.py                           |  23 ---
 test/test_projection.py                       | 134 ++++++++++++++++++
 test/test_projection_geometry.py              |  78 ++++++++++
 test/test_region_of_intrest.py                | 100 +++++++++++++
 test/test_volume.py                           |  98 +++++++++++++
 22 files changed, 669 insertions(+), 77 deletions(-)
 create mode 100644 test/__pycache__/test_copyright.cpython-38-pytest-8.2.2.pyc
 create mode 100644 test/__pycache__/test_flake8.cpython-38-pytest-8.2.2.pyc
 create mode 100644 test/__pycache__/test_pep257.cpython-38-pytest-8.2.2.pyc
 create mode 100644 test/__pycache__/test_projection.cpython-38-pytest-8.2.2.pyc
 create mode 100644 test/__pycache__/test_projection_geometry.cpython-38-pytest-8.2.2.pyc
 create mode 100644 test/__pycache__/test_region_of_intrest.cpython-38-pytest-8.2.2.pyc
 create mode 100644 test/__pycache__/test_volume.cpython-38-pytest-8.2.2.pyc
 delete mode 100644 test/test_copyright.py
 delete mode 100644 test/test_flake8.py
 delete mode 100644 test/test_pep257.py
 create mode 100644 test/test_projection.py
 create mode 100644 test/test_projection_geometry.py
 create mode 100644 test/test_region_of_intrest.py
 create mode 100644 test/test_volume.py

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5626c31..9a75dc7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -12,5 +12,10 @@
         "C:\\ci\\ws\\install\\lib\\python3.8\\dist-packages",
         "C:\\dev\\ros2_humble\\Lib\\site-packages",
         ""
-    ]
+    ],
+    "python.testing.pytestArgs": [
+        "test"
+    ],
+    "python.testing.unittestEnabled": false,
+    "python.testing.pytestEnabled": true
 }
\ No newline at end of file
diff --git a/rq_controller/common/__pycache__/__init__.cpython-38.pyc b/rq_controller/common/__pycache__/__init__.cpython-38.pyc
index b5a7ce552844d6e4a6c1828b88db4a6f0d8786a2..c4ac8808716f0af57d37032d44cd890bef1dd36e 100644
GIT binary patch
delta 165
zcmdnUw2_H7l$V!_0SF9JYtphO^2#!*Ow^WT$z_dVo#<gw&vc6;pfW5!r!+V9mS8|-
zP-=Q+ex84tXI@EBYH^95CfhBpf};Ga)Z`MNco7rOoFZl*af_`Ctfz<tB*F?LRx%WE
wfY>17mus?BOiF55Oi^Kcd45rLT26j>OmR^%h@YGfG&(;gC$$K~oA}=d0CzDn!~g&Q

delta 160
zcmdnUw2_H7l$V!_0SF$n7p3J)<dtPqn5Zo~(Z{5o@s?mfWl(B*W`3T3nrB`~QEG9C
zpC)S*S3yyJR%&tyP`ro<sIiC{NJI%1fmO!mr^RPN)E2P-#aA*Ev4Yfqh+n?WRxv55
vWidsC@#XnN*=afX<uRFg#U+V3IWaz&NioHlC8@dviOJcC>8ZsNf13dS9P2Z$

diff --git a/rq_controller/common/__pycache__/projection.cpython-38.pyc b/rq_controller/common/__pycache__/projection.cpython-38.pyc
index 84787faff2867b84f7673e065471b45fd6f6c070..292a7253e286283d2a9f7b7e8b5c8f92156b3bb6 100644
GIT binary patch
literal 6548
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

delta 1195
zcmZ{i$#2s@6o);Io!xF6vKL5Gl9sR(C_4yI76GRS1VzFEtdMcqpg1vOS2TcB5E12y
zhGW^pi6iQb3n%U%aq<BqPVh${@t#wvDguxEdEWB#yqS5=_kS&^vx*WG;JNngezR71
zuZGFe4=_zZCsfcyO3c7(iHa}+RGO3C5Gun6Hf0*}N0>%nMCsrgp)nd?i7yLHLKUj^
z6<s!ibc81Q!egR`AWvH%G)Xu0B|Qw06itJjq1nC!@d()3$`YYDnumlaE%@o9bTe2n
zGfubo_EuW-)0K747%ll86+96?XPj>HJtOO$GOhTY#Ihg>x^`KpPI&Q)y^E}S+ibY4
zZpRCDZ{c)Rk{lunmxXtP+mV!_2kVa8vrSLz*gAg}Ss-V4D0+UR2*;$t>mUS)--tdU
zHQtL=$OL~CE9}wbdnW5TRpJF2ZHJX1X@CmMJee+7R<8ilfnMZ4V;@0(LHRjd0<Rvi
zS*zn3j?0FnAp^aJF^=LSKp?BP)FVV?TVP&IvoZLvN&Yrr5uIyVF}NKayZ9mP#^g{L
z@h0n9hGjZVea@VMH0LZp5LZ8FFG!}w2r4DOFA)&{=scFnPh&3IgV>AMhrn9cCd4!%
zhuDoc0MPl>RH?Qf%{&6P&PD;%0K*M?p|vUolA$`Ib?0xDmmt}}1q8xBrJl(Cu6ZrJ
zErGw;cv$>VdNWzzFVhbb$A&{wnJjt0&t-1zI)S0#IQTq`nlAK1ml=0k)SWl%#fSWJ
zX7;du$|B34ZsvdDgxy*++lJk88}r7ZVOf2CJ$t3JrcI;l<iE81OEyiOLT)A4164K2
zlezNJ+LfXJ+s=zM+I7dVOn1IZSry$o5eE@R5yt^u*k)bZWNvS;)C>krAx>jdF%17N
z4YS=gj1A|8eZ{azSp=ZKTXc2+!=gwML=<5B-Vql`G^7F(<7%0I&uu@d%bf+w?hQ)C
wMj2eB3^QG0cMRxelQruN)0wiIITpl>5C-{7K2yLJ@z>i>h~EPhZs*H?011Er`Tzg`

diff --git a/rq_controller/common/__pycache__/projection_geometry.cpython-38.pyc b/rq_controller/common/__pycache__/projection_geometry.cpython-38.pyc
index e7efc4a86b91246f1a0538ae704fa85f48a9b64b..a849f8bd44ad63633ea5991038e17486b43fa1a9 100644
GIT binary patch
literal 3846
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
T>*8qNVAL|~A4^(GTh;ynLJc49

delta 652
zcmZ9Izi-qq6vu5}el?f4X?vxmlu~{PPN-7Fz(5C(5P|`rN)=KSMM}fjDMv4tVkcA?
z&<&gz*pSx_Fm^`#0f>>AB`ip6{0R(*=d`pHdCJG{*<ZgOezWvBpig|?Meu!@FVob2
zM-N5}B921LP)Hc&t@j2aU@c~B8y|2=nAw;Gw3)MSu?gmm&Cui)Ycp?*UgOXP-)D3m
zvq=_=4cKuYo`QIq&5Vt3f^{}fced`VSL-5w&U-~aA3orD#tVTfwblA*q78I_Rbt(}
zIRpLt-n@4^V*F(!Uc`I3*hvTZUeq7TA{h*#NByUf>=%4xH|gyp+gz%z)~&V!*JLxA
zp**|7kJYNPvaFMd1|9Hfla*OkO>3#vBcfh8Kd6`jx9f>Bu!uSJ*1d1dYj;6?bMIXH
zFNK$iJc~0fC6v}Oc^!br$F=rD3{}_si=%V;_M*mljU^3FV^ZUShOa?2mH}NNbW^o`
zl=raZlfK<V{wui(I(Z3zutg{aJIy9i-~AidRW`jcI+_X8Vir)hdxJzuc=u;H6KD1I
zWeq*TshY=yg$V~X{4xMIErNnS3ogN~NpLXNoOYesjpMW|O2Ok;5YW};u*`NVsI5NH
OtHH?w9_CfQ=;B{Ku8Cm)

diff --git a/rq_controller/common/projection.py b/rq_controller/common/projection.py
index ea5c1ee..813f573 100644
--- a/rq_controller/common/projection.py
+++ b/rq_controller/common/projection.py
@@ -11,10 +11,39 @@ import ros2_numpy
 
 
 class PyProjection(PyProjectionGeometry):
+    """
+    Represents a projection including image data and associated geometry.
+
+    Attributes:
+        image (np.ndarray): The projection image data.
+        detector_heigth_mm (float): The height of the detector in millimeters.
+        detector_width_mm (float): The width of the detector in millimeters.
+        voltage_kv (float): The voltage in kilovolts.
+        current_ua (float): The current in microamperes.
+        exposure_time_ms (float): The exposure time in milliseconds.
+    """
+
     def __init__(self, focal_spot_mm: ndarray, detector_postion_mm: ndarray, detector_orientation_quad: ndarray, image: np.ndarray,
                  detector_heigth_mm: float, detector_width_mm: float, frame_id: str = 'object', 
                  focal_spot_orientation_quad: np.ndarray = np.array([0., 0., 0, 1.]),
                  voltage_kv: float = 100., current_ua: float = 100., exposure_time_ms: float = 1000.) -> None:
+        """
+        Initializes a PyProjection instance.
+
+        Args:
+            focal_spot_mm (ndarray): Position of the focal spot in millimeters.
+            detector_postion_mm (ndarray): Position of the detector in millimeters.
+            detector_orientation_quad (ndarray): Orientation of the detector as a quaternion.
+            image (np.ndarray): The projection image data.
+            detector_heigth_mm (float): The height of the detector in millimeters.
+            detector_width_mm (float): The width of the detector in millimeters.
+            frame_id (str): Frame ID for the projection geometry. Default is 'object'.
+            focal_spot_orientation_quad (np.ndarray): Orientation of the focal spot as a quaternion. Default is [0., 0., 0, 1.].
+            voltage_kv (float): The voltage in kilovolts. Default is 100.
+            current_ua (float): The current in microamperes. Default is 100.
+            exposure_time_ms (float): The exposure time in milliseconds. Default is 1000.
+        """
+        
         super().__init__(focal_spot_mm, detector_postion_mm, detector_orientation_quad, frame_id, focal_spot_orientation_quad)
         self.image = image.astype(np.uint16)
         self.detector_heigth_mm = detector_heigth_mm
@@ -25,6 +54,13 @@ class PyProjection(PyProjectionGeometry):
 
     @classmethod
     def dummy(cls):
+        """
+        Creates a dummy instance of PyProjection for testing.
+
+        Returns:
+            PyProjection: A dummy instance with default values.
+        """
+
         return cls(np.array([0., 100., 0]), 
                    np.array([0., -100., 0]),
                    np.array([1., 0., 0, 1.]),
@@ -32,6 +68,12 @@ class PyProjection(PyProjectionGeometry):
                    10., 10.)
     
     def __str__(self) -> str:
+        """
+        Returns a string representation of the PyProjection instance.
+
+        Returns:
+            str: The string representation of the projection.
+        """
         print_str = f'---\n'
         print_str += f'PyProjection\n'
         print_str += f'--- Projection Geometry:\n'
@@ -42,9 +84,17 @@ class PyProjection(PyProjectionGeometry):
         print_str += f'---\n\n'
         return print_str
 
-
     @classmethod
     def from_message(cls, msg: Projection):
+        """
+        Creates an instance of PyProjection from a ROS message.
+
+        Args:
+            msg (Projection): The ROS message containing projection data.
+
+        Returns:
+            PyProjection: An instance initialized from the ROS message.
+        """
         focal_spot_mm = np.array([msg.projection_geometry.focal_spot_postion_mm.x,
                                   msg.projection_geometry.focal_spot_postion_mm.y,
                                   msg.projection_geometry.focal_spot_postion_mm.z,])
@@ -79,6 +129,12 @@ class PyProjection(PyProjectionGeometry):
                    voltage_kv, current_ua, exposure_time_ms)
     
     def as_message(self) -> Projection:
+        """
+        Converts the PyProjection instance to a ROS message.
+
+        Returns:
+            Projection: The ROS message representing the projection.
+        """
         message = Projection()
         projection_geometry = ProjectionGeometry()
 
diff --git a/rq_controller/common/projection_geometry.py b/rq_controller/common/projection_geometry.py
index 8548dcd..61bc500 100644
--- a/rq_controller/common/projection_geometry.py
+++ b/rq_controller/common/projection_geometry.py
@@ -5,10 +5,32 @@ import numpy as np
 from rq_interfaces.msg import ProjectionGeometry, Projection
 
 class PyProjectionGeometry():
+    """
+    Represents the geometry of a projection including the focal spot and detector position and orientation.
+
+    Attributes:
+        focal_spot_mm (np.ndarray): Position of the focal spot in millimeters.
+        detector_postion_mm (np.ndarray): Position of the detector in millimeters.
+        detector_orientation_quad (np.ndarray): Orientation of the detector as a quaternion.
+        focal_spot_orientation_quad (np.ndarray): Orientation of the focal spot as a quaternion.
+        frame_id (str): Frame ID for the projection geometry.
+    """
+
     def __init__(self, focal_spot_mm: np.ndarray, detector_postion_mm: np.ndarray, 
                  detector_orientation_quad: np.ndarray,
                  frame_id: str = 'object', focal_spot_orientation_quad: np.ndarray = np.array([0., 0., 0, 1.])
                  ) -> None:
+        """
+        Initializes a PyProjectionGeometry instance.
+
+        Args:
+            focal_spot_mm (np.ndarray): Position of the focal spot in millimeters.
+            detector_postion_mm (np.ndarray): Position of the detector in millimeters.
+            detector_orientation_quad (np.ndarray): Orientation of the detector as a quaternion.
+            frame_id (str): Frame ID for the projection geometry. Default is 'object'.
+            focal_spot_orientation_quad (np.ndarray): Orientation of the focal spot as a quaternion.
+        """
+
         self.focal_spot_mm = focal_spot_mm
         self.detector_postion_mm = detector_postion_mm
         self.focal_spot_orientation_quad = focal_spot_orientation_quad
@@ -17,12 +39,27 @@ class PyProjectionGeometry():
 
     @classmethod
     def dummy(cls):
+        """
+        Creates a dummy instance of PyProjectionGeometry for testing.
+
+        Returns:
+            PyProjectionGeometry: A dummy instance with default values.
+        """
         return cls(np.array([1., 0., 0]), 
                    np.array([-1., 0., 0]),
                    np.array([0., 0., 0, 1.]))
 
     @classmethod
     def from_message(cls, msg: ProjectionGeometry):
+        """
+        Creates an instance of PyProjectionGeometry from a ROS message.
+
+        Args:
+            msg (ProjectionGeometry): The ROS message containing projection geometry data.
+
+        Returns:
+            PyProjectionGeometry: An instance initialized from the ROS message.
+        """
         focal_spot_mm = np.array([msg.focal_spot_postion_mm.x,
                                   msg.focal_spot_postion_mm.y,
                                   msg.focal_spot_postion_mm.z,])
@@ -46,6 +83,12 @@ class PyProjectionGeometry():
         return cls(focal_spot_mm, detector_center_mm, detector_orientation_quad, frame_id, focal_spot_orientation)
     
     def as_message(self) -> ProjectionGeometry:
+        """
+        Converts the PyProjectionGeometry instance to a ROS message.
+
+        Returns:
+            ProjectionGeometry: The ROS message representing the projection geometry.
+        """
         message = ProjectionGeometry()
 
         message.focal_spot_postion_mm.x = self.focal_spot_mm[0]
diff --git a/rq_controller/common/region_of_intrest.py b/rq_controller/common/region_of_intrest.py
index f067df7..4a71fda 100644
--- a/rq_controller/common/region_of_intrest.py
+++ b/rq_controller/common/region_of_intrest.py
@@ -7,8 +7,28 @@ from visualization_msgs.msg import Marker
 
 
 class PyRegionOfIntrest():
+    """
+    Represents a region of interest (ROI) with center points, dimensions, and resolution.
+
+    Attributes:
+        center_points_mm (np.ndarray): Center points of the ROIs in millimeters.
+        dimensions_mm (np.ndarray): Dimensions of the ROIs in millimeters.
+        frame_id (str): Frame ID for the ROI.
+        resolution_mm (np.ndarray): Resolution of the ROIs in millimeters.
+    """
+
     def __init__(self, center_points_mm: np.ndarray, dimensions_mm: np.ndarray, frame_id: str = 'object', 
                  resolution_mm: np.ndarray = np.array([0.1, 0.1, 0.1])):
+        """
+        Initializes a PyRegionOfIntrest instance.
+
+        Args:
+            center_points_mm (np.ndarray): Center points of the ROIs in millimeters.
+            dimensions_mm (np.ndarray): Dimensions of the ROIs in millimeters.
+            frame_id (str): Frame ID for the ROI. Default is 'object'.
+            resolution_mm (np.ndarray): Resolution of the ROIs in millimeters. Default is [0.1, 0.1, 0.1].
+        """
+
         self.center_points_mm = center_points_mm.reshape((-1, 3))
         self.dimensions_mm = dimensions_mm.reshape((-1, 3))
         self.frame_id = frame_id
@@ -16,11 +36,28 @@ class PyRegionOfIntrest():
 
     @classmethod
     def dummy(cls):
+        """
+        Creates a dummy instance of PyRegionOfIntrest for testing.
+
+        Returns:
+            PyRegionOfIntrest: A dummy instance with random values.
+        """
+
         return cls((np.random.random((3, )) - 0.5) * 20., 
                    np.random.random((3, )) * 10.)
 
     @classmethod
     def from_message(cls, msg: RegionOfIntrest):
+        """
+        Creates an instance of PyRegionOfIntrest from a ROS message.
+
+        Args:
+            msg (RegionOfIntrest): The ROS message containing ROI data.
+
+        Returns:
+            PyRegionOfIntrest: An instance initialized from the ROS message.
+        """
+
         center_points_mm = list()
         dimensions_mm = list()
 
@@ -45,14 +82,35 @@ class PyRegionOfIntrest():
     
     @property
     def number_of_rois(self) -> int:
+        """
+        Returns the number of regions of interest.
+
+        Returns:
+            int: The number of ROIs.
+        """
+
         return self.center_points_mm.shape[0]
     
     @property
     def shape(self) -> tuple:
+        """
+        Returns the shape of the ROI grid based on the dimensions and resolution.
+
+        Returns:
+            tuple: The shape of the ROI grid.
+        """
+
         shape = self.dimensions_mm[0] // self.resolution_mm[0]
         return (int(shape[0]), int(shape[1]), int(shape[2]))
       
     def as_message(self) -> RegionOfIntrest:
+        """
+        Converts the PyRegionOfIntrest instance to a ROS message.
+
+        Returns:
+            RegionOfIntrest: The ROS message representing the ROI.
+        """
+
         message = RegionOfIntrest()
         roi_list = list()
 
@@ -80,6 +138,16 @@ class PyRegionOfIntrest():
         return message
     
     def get_grid(self, indice: int = 0) -> np.ndarray:
+        """
+        Generates a grid of points within the ROI.
+
+        Args:
+            indice (int): Index of the ROI. Default is 0.
+
+        Returns:
+            np.ndarray: A grid of points within the ROI.
+        """
+
         start = self.center_points_mm[indice] - (self.dimensions_mm[indice] / 2.)
         end = self.center_points_mm[indice] + (self.dimensions_mm[indice] / 2.)
         
@@ -97,6 +165,16 @@ class PyRegionOfIntrest():
     
     @staticmethod
     def next_neighbor(grid_mm: np.ndarray, point_mm: np.ndarray) -> np.ndarray:
+        """
+        Finds the nearest neighbor in the grid to a given point.
+
+        Args:
+            grid_mm (np.ndarray): The grid of points.
+            point_mm (np.ndarray): The point to find the nearest neighbor for.
+
+        Returns:
+            np.ndarray: The indices of the nearest neighbor in the grid.
+        """
         
         x = grid_mm[:, 0, 0, 0]
         y = grid_mm[0, :, 0, 1]
diff --git a/rq_controller/common/volume.py b/rq_controller/common/volume.py
index 3ac025e..ac1b645 100644
--- a/rq_controller/common/volume.py
+++ b/rq_controller/common/volume.py
@@ -11,18 +11,50 @@ import ros2_numpy
 
 
 class VOLUME_TYPES(IntEnum):
+    """
+    Enum representing volume data types.
+    """
+
     UINT_16 = 0
     UINT_8 = 1
 
 
 class PyVolume():
+    """
+    Represents a volumetric dataset with an associated region of interest.
+
+    Attributes:
+        roi (PyRegionOfIntrest): The region of interest associated with the volume.
+        array (ndarray): The volumetric data array.
+        data_typ (VOLUME_TYPES): The data type of the volume.
+    """
+
     def __init__(self, array: ndarray, roi: PyRegionOfIntrest, data_type: VOLUME_TYPES = ...):
+        """
+        Initializes a PyVolume instance.
+
+        Args:
+            array (ndarray): The volumetric data array.
+            roi (PyRegionOfIntrest): The region of interest associated with the volume.
+            data_type (VOLUME_TYPES): The data type of the volume. Default is VOLUME_TYPES.UINT_8.
+        """
+
         self.roi = roi
         self.array = array
         self.data_typ = data_type
 
     @staticmethod
     def get_data_type(volume_type: VOLUME_TYPES) -> np.dtype:
+        """
+        Gets the numpy data type corresponding to the given volume type.
+
+        Args:
+            volume_type (VOLUME_TYPES): The volume data type.
+
+        Returns:
+            np.dtype: The corresponding numpy data type.
+        """
+
         if volume_type == VOLUME_TYPES.UINT_16:
             return np.uint16
         elif volume_type == VOLUME_TYPES.UINT_8:
@@ -32,6 +64,16 @@ class PyVolume():
         
     @staticmethod
     def enum_to_numpify(volume_type: VOLUME_TYPES) -> str:
+        """
+        Converts the volume type enum to a string for use in ROS2 numpy conversion.
+
+        Args:
+            volume_type (VOLUME_TYPES): The volume data type.
+
+        Returns:
+            str: The corresponding string for ROS2 numpy conversion.
+        """
+
         if volume_type == VOLUME_TYPES.UINT_16:
             return 'mono16'
         elif volume_type == VOLUME_TYPES.UINT_8:
@@ -40,14 +82,31 @@ class PyVolume():
             raise ValueError('Datatype is not implemented')
         
     @classmethod
-    def dummy(cls):
+    def dummy(cls) -> 'PyVolume':
+        """
+        Creates a dummy instance of PyVolume for testing.
+
+        Returns:
+            PyVolume: A dummy instance with random data.
+        """
+
         roi = PyRegionOfIntrest.dummy()
         array = np.random.randint(0, 255, size=roi.shape)
         data_type = VOLUME_TYPES.UINT_8
         return cls(array, roi, data_type)
 
     @classmethod
-    def from_message(cls, msg: Volume):
+    def from_message(cls, msg: Volume) -> 'PyVolume':
+        """
+        Creates an instance of PyVolume from a ROS message.
+
+        Args:
+            msg (Volume): The ROS message containing volume data.
+
+        Returns:
+            PyVolume: An instance initialized from the ROS message.
+        """
+
         roi: Marker = msg.grid.region_of_intrest_stack.markers[0]
         center_points_mm = np.array([
             roi.pose.position.x,
@@ -77,6 +136,13 @@ class PyVolume():
         return cls(array, py_roi, data_typ)
     
     def as_message(self) -> Volume:
+        """
+        Converts the PyVolume instance to a ROS message.
+
+        Returns:
+            Volume: The ROS message representing the volume.
+        """
+
         message = Volume()
 
         message.datatype = self.data_typ
@@ -93,6 +159,13 @@ class PyVolume():
     
     @property
     def shape(self):
+        """
+        Returns the shape of the volumetric data array.
+
+        Returns:
+            tuple: The shape of the volume.
+        """
+        
         return self.array.shape
                                                          
         
diff --git a/test/__pycache__/test_copyright.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_copyright.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c0a8d69338629d501cf0136fa81e7f066901c8a9
GIT binary patch
literal 1030
zcmYjQ&u`N(6t<l-O}nm)G401K7X-Ofz!(Rh(<a2kX$KGzswPEQ?6z5*IN0t+tM&pM
zxa`J}!+PXjGDlAQ3p??gY*TId<-PAce}3;d{<yl@L@*kU#{EAILVvBXJZfMZz*3(;
zP((3DJzRcC<`9HY=JsmXdI<NjdaqvUnEO4S`Dcx@CUvNLiA#N@lr37L-gop3+u2*C
z_yY>-|9~AGhuFAToaWQRV-X~xn8<W6)WML&l*wQitKgKeJScdaFdC$Jpoc6Nu$;+Q
z1E9oMCM@WuoOOW8lPp#uPb~HAu;tWYy@92Eg3wSBB?Cg8OFT!^o#Ppr;SrgW8Bsqp
zz9O_XBYjLgi}%UGrFHFGxeGM%)I0FkW@v%sd(a?afpY?WtK$Wx{x7nL<__3(+5o$`
zh3Lx6Nim?V_5p9q+y%7ZE40)_b25i7w4-%waL2fsDQ#O4%fY$CkZg#rNw*u~>H5RR
z#pFq-f^Ijk>TakEkx5951Gp8t)7Fs~=QLna3aO@R&HY#@CUqIV2k{WJ7d9mUUei1%
zCib)_mgatx#GFTx6*3W75ld!T;EqnmDc5PPjK>8;D6<`@p%`;ohDHM}PGcVBamGv|
zQgNSI24L$8nZ>|do}Q_JX&;tqD)P5BH}l{&gnfaV&0`IouqJ&-thZuMuC_iiOUBd6
zQ*l49JeBd{H<DYh_4-xv@`SSU6Ztv15OUP#;^IWf#M+6-wG^B)d19+B1%NmITc?<q
zcG-<PXafk<8VKaJv5S1TtRu9E+DPsI7PgIl+a1{k!0bd-KRZ?GP*6v*3z=%@%^hUw
zH<)a}p56v@F4s$G*q2aUk5{qT$00|?Rim^pjl1M}9#3-(_iS@1YhM!EHXZ+UCg_;6
U{d-_3o1u@^Q49NS3wtpC1Cq%roB#j-

literal 0
HcmV?d00001

diff --git a/test/__pycache__/test_flake8.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_flake8.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df33d1d7ce4cf030f75a9d7d2595cb2db409dcf0
GIT binary patch
literal 989
zcmYjQ%Wl&^6rCA6j^m`I52@v~@TMx>lt(G5DpD08D#U^Xh)op9ay^sO&DahzlZI6G
zhO+GsAQsIAiC>y6D}JFX?$`u%q`A2-oqO&X=hM=Xhv2lfCxa^sq2DIBeQXGh;L3Mk
zD59979<F{#Y7vA{YWEu0R0wy{X0KWKn7cifxo532ud;1wQTrRZ#Fx0&q4*;T8h-!}
zy$`TnNuwkSr-?cZnGm^<v8gPM15Oj}Yq;_!7=?zw;tKakiAqdKNeWbu5h<<0lHV1+
zCKS8@rnbos$lRt4WnJ5IG;-uys5c5U$KnHcj`1=Ab>ovcrp^ztfl3?VCT%LW0KOZD
zy4S4&&+#U7)7B=UUSTJYFB{5(&UVq5L*G|W#YUx75{h8Iwpq=N#PD2TU>)Ea((MNL
zVr}cuc=jZaez)rzcQ=rlh&UkHg1j-i7u(1AB%}To_2Zl}U#b~reyxrF*q=rsOR}Ln
z@HF-=R=mSVN+wi=wg+ecu@`g%fvMU%8qXAyDvX3)4daOOP_VIx^K={uraMp$PbLXh
zNhY<!bLf!zZYWRl38z(SIOO?B#KSB~nQnzL8Zd(ZXoFm&5g=F7Q#sTYXPLIo@+8yC
zuc~a4XK&1t>h(o8`!eQH7AdF%j%XN;PQ96K8!|Q8v)jSbI;h7zza6~zg~WY;xY>^n
z`jnmb#piIEi_w7R)4mk3iQ_y|BIlfmz7e7_;ebaYw!br;>2{?~9j^nFTmgfuWo#oC
zp7R(zLd!@zgj}$s-Gx-50||XEti|1_NyADzf=xxDK&byI-Tapm=6}{(1sOB7AYi`y
z`k|Z}O5159Mm?t{1TT_g3gj{Kt4(j22Zn<CGR^6Pv%@<ONyExTYp8==%fSx*3&hDA
AivR!s

literal 0
HcmV?d00001

diff --git a/test/__pycache__/test_pep257.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_pep257.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b6355ad401a01ed7cd9a34112ca34b92937168cf
GIT binary patch
literal 941
zcmYjPOK%e~5VpOW&E{45kV?xbLPD^Y04)Vnk*Ww$Ar3u&xI~dw+qDz6@je)D8d4<}
z%7F_vjvSgJf5}%){Dqzv?-CSC9?v&3{^ntSXtnADSMC0IaA*<o+kn+&BRIyb-lE`y
zQ%QQXI9Y5lLP>1*D%5zGbmD5STIf`|Jy*Erwexx*8{FddS8_$KXs^lXd*W68ARjsP
zsJ7!EN<xE{tAa`u_bc4$2Z|i?ayDe#`cCJB+jBZ2Gdf~(He>2rPOllS%-Dc($JhsK
zVe@KkUE2#Xa?~62S7u~E;T>uiv7iY<ztQP}a`z|OCUXmMmDdp0Zxh~_SrJ96ox5nS
z&FlqM{ssHC$(+sc#15LLns&6E3zeHL0vKKZ#bP~r!@6CMUT)rdkWC+Xs@?6jjk@nC
z%^>ucw$N_G{^i}1be!;Zm~zop`BaLw07yZ#AGRj}BvCR{m+SSTKq&!v!E%5sc=f=m
z1H){(ew<BB4SoQ6!w&-~eGnOhX`BThbQ9hF**KDUlql`U6eE=0@zuw4EO`;?4`q55
zNIwZ;p=-Vh2Es5P+aQHFK;|NQtcH5+WuZoC^4gTCx0WH|b0&i%$kF3fz&xIAn9SwX
zy;bnI1SR*ARq*r|fgQYh^E^E4b8*p!PyQr@(Lkn?zJk!;Fimnur4-ONT^GX7M7DSE
ztdmW3qZmbrw~$J$qafBAwTX+5cS5$w8i6}#^IF<n&IMX%(7S#)$xc}`w$cGHfhfnR
z{5R?9KN~dgPHzKemgLI<%ug;~#VM)G=Y|oEJxX!`J*O<J$X|52B6dv)?u$6(V=0bq
RAyQ2gF4-ha>RJwU=wCoU3NHWv

literal 0
HcmV?d00001

diff --git a/test/__pycache__/test_projection.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_projection.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8413a88371ec487791bfb98a4279f60cf8b3edae
GIT binary patch
literal 20692
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
VmY(5%%P@6&x3$YkTX}2e{{ivUlX(CD

literal 0
HcmV?d00001

diff --git a/test/__pycache__/test_projection_geometry.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_projection_geometry.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..daecbc751bbc7609edc20368ee278eb37f545dfe
GIT binary patch
literal 11871
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
PFPWC^PB~*v!72O~e)GO}

literal 0
HcmV?d00001

diff --git a/test/__pycache__/test_region_of_intrest.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_region_of_intrest.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..469b57f673dd3002e757812d9c29a8ea600abac0
GIT binary patch
literal 12566
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

literal 0
HcmV?d00001

diff --git a/test/__pycache__/test_volume.cpython-38-pytest-8.2.2.pyc b/test/__pycache__/test_volume.cpython-38-pytest-8.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e4329085a29c878eed2392f2e4b4eda540f9cb6f
GIT binary patch
literal 9180
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
Dv|_&K

literal 0
HcmV?d00001

diff --git a/test/test_copyright.py b/test/test_copyright.py
deleted file mode 100644
index 97a3919..0000000
--- a/test/test_copyright.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2015 Open Source Robotics Foundation, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ament_copyright.main import main
-import pytest
-
-
-# Remove the `skip` decorator once the source file(s) have a copyright header
-@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
-@pytest.mark.copyright
-@pytest.mark.linter
-def test_copyright():
-    rc = main(argv=['.', 'test'])
-    assert rc == 0, 'Found errors'
diff --git a/test/test_flake8.py b/test/test_flake8.py
deleted file mode 100644
index 27ee107..0000000
--- a/test/test_flake8.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2017 Open Source Robotics Foundation, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ament_flake8.main import main_with_errors
-import pytest
-
-
-@pytest.mark.flake8
-@pytest.mark.linter
-def test_flake8():
-    rc, errors = main_with_errors(argv=[])
-    assert rc == 0, \
-        'Found %d code style errors / warnings:\n' % len(errors) + \
-        '\n'.join(errors)
diff --git a/test/test_pep257.py b/test/test_pep257.py
deleted file mode 100644
index b234a38..0000000
--- a/test/test_pep257.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2015 Open Source Robotics Foundation, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ament_pep257.main import main
-import pytest
-
-
-@pytest.mark.linter
-@pytest.mark.pep257
-def test_pep257():
-    rc = main(argv=['.', 'test'])
-    assert rc == 0, 'Found code style errors / warnings'
diff --git a/test/test_projection.py b/test/test_projection.py
new file mode 100644
index 0000000..06075e4
--- /dev/null
+++ b/test/test_projection.py
@@ -0,0 +1,134 @@
+import pytest
+import numpy as np
+from rq_controller.common import PyProjection  # Adjust this import according to your module's path
+from rq_interfaces.msg import Projection
+from sensor_msgs.msg import Image
+import ros2_numpy
+
+@pytest.fixture
+def example_message():
+    msg = Projection()
+    msg.projection_geometry.focal_spot_postion_mm.x = 10.0
+    msg.projection_geometry.focal_spot_postion_mm.y = 20.0
+    msg.projection_geometry.focal_spot_postion_mm.z = 30.0
+    msg.projection_geometry.detector_postion_mm.x = 40.0
+    msg.projection_geometry.detector_postion_mm.y = 50.0
+    msg.projection_geometry.detector_postion_mm.z = 60.0
+    msg.projection_geometry.detector_orientation_quad.x = 0.0
+    msg.projection_geometry.detector_orientation_quad.y = 0.0
+    msg.projection_geometry.detector_orientation_quad.z = 0.0
+    msg.projection_geometry.detector_orientation_quad.w = 1.0
+    msg.projection_geometry.focal_spot_orientation_quad.x = 0.0
+    msg.projection_geometry.focal_spot_orientation_quad.y = 0.0
+    msg.projection_geometry.focal_spot_orientation_quad.z = 0.0
+    msg.projection_geometry.focal_spot_orientation_quad.w = 1.0
+    msg.projection_geometry.header.frame_id = "test_frame"
+    
+    image_array = np.zeros((10, 10), dtype=np.uint16)
+    msg.image = ros2_numpy.msgify(Image, image_array, encoding='mono16')
+    
+    msg.detector_heigth_mm = 100.0
+    msg.detector_width_mm = 200.0
+    msg.voltage_kv = 120.0
+    msg.current_ua = 150.0
+    msg.exposure_time_ms = 500.0
+    return msg
+
+def test_initialization():
+    focal_spot_mm = np.array([1.0, 2.0, 3.0])
+    detector_postion_mm = np.array([4.0, 5.0, 6.0])
+    detector_orientation_quad = np.array([0.0, 0.0, 0.0, 1.0])
+    image = np.zeros((10, 10), dtype=np.uint16)
+    detector_heigth_mm = 10.0
+    detector_width_mm = 20.0
+    voltage_kv = 100.0
+    current_ua = 200.0
+    exposure_time_ms = 1000.0
+    frame_id = "test_frame"
+    focal_spot_orientation_quad = np.array([0.0, 0.0, 0.0, 1.0])
+
+    projection = PyProjection(focal_spot_mm, detector_postion_mm, detector_orientation_quad, image, detector_heigth_mm, detector_width_mm, frame_id, focal_spot_orientation_quad, voltage_kv, current_ua, exposure_time_ms)
+
+    assert np.array_equal(projection.focal_spot_mm, focal_spot_mm)
+    assert np.array_equal(projection.detector_postion_mm, detector_postion_mm)
+    assert np.array_equal(projection.detector_orientation_quad, detector_orientation_quad)
+    assert np.array_equal(projection.focal_spot_orientation_quad, focal_spot_orientation_quad)
+    assert np.array_equal(projection.image, image)
+    assert projection.detector_heigth_mm == detector_heigth_mm
+    assert projection.detector_width_mm == detector_width_mm
+    assert projection.voltage_kv == voltage_kv
+    assert projection.current_ua == current_ua
+    assert projection.exposure_time_ms == exposure_time_ms
+    assert projection.frame_id == frame_id
+
+def test_dummy_method():
+    projection = PyProjection.dummy()
+
+    assert np.array_equal(projection.focal_spot_mm, np.array([0.0, 100.0, 0.0]))
+    assert np.array_equal(projection.detector_postion_mm, np.array([0.0, -100.0, 0.0]))
+    assert np.array_equal(projection.detector_orientation_quad, np.array([1.0, 0.0, 0.0, 1.0]))
+    assert np.array_equal(projection.image, np.zeros((10, 10), dtype=np.uint16))
+    assert projection.detector_heigth_mm == 10.0
+    assert projection.detector_width_mm == 10.0
+    assert projection.frame_id == "object"
+
+def test_from_message(example_message):
+    projection = PyProjection.from_message(example_message)
+
+    assert np.array_equal(projection.focal_spot_mm, np.array([10.0, 20.0, 30.0]))
+    assert np.array_equal(projection.detector_postion_mm, np.array([40.0, 50.0, 60.0]))
+    assert np.array_equal(projection.detector_orientation_quad, np.array([0.0, 0.0, 0.0, 1.0]))
+    assert np.array_equal(projection.focal_spot_orientation_quad, np.array([0.0, 0.0, 0.0, 1.0]))
+    assert np.array_equal(projection.image, np.zeros((10, 10), dtype=np.uint16))
+    assert projection.detector_heigth_mm == 100.0
+    assert projection.detector_width_mm == 200.0
+    assert projection.voltage_kv == 120.0
+    assert projection.current_ua == 150.0
+    assert projection.exposure_time_ms == 500.0
+    assert projection.frame_id == "test_frame"
+
+def test_as_message(example_message):
+    projection = PyProjection.from_message(example_message)
+    msg = projection.as_message()
+
+    assert msg.projection_geometry.focal_spot_postion_mm.x == 10.0
+    assert msg.projection_geometry.focal_spot_postion_mm.y == 20.0
+    assert msg.projection_geometry.focal_spot_postion_mm.z == 30.0
+    assert msg.projection_geometry.detector_postion_mm.x == 40.0
+    assert msg.projection_geometry.detector_postion_mm.y == 50.0
+    assert msg.projection_geometry.detector_postion_mm.z == 60.0
+    assert msg.projection_geometry.detector_orientation_quad.x == 0.0
+    assert msg.projection_geometry.detector_orientation_quad.y == 0.0
+    assert msg.projection_geometry.detector_orientation_quad.z == 0.0
+    assert msg.projection_geometry.detector_orientation_quad.w == 1.0
+    assert msg.projection_geometry.focal_spot_orientation_quad.x == 0.0
+    assert msg.projection_geometry.focal_spot_orientation_quad.y == 0.0
+    assert msg.projection_geometry.focal_spot_orientation_quad.z == 0.0
+    assert msg.projection_geometry.focal_spot_orientation_quad.w == 1.0
+    assert msg.projection_geometry.header.frame_id == "test_frame"
+    assert np.array_equal(ros2_numpy.numpify(msg.image), np.zeros((10, 10), dtype=np.uint16))
+    assert msg.detector_heigth_mm == 100.0
+    assert msg.detector_width_mm == 200.0
+    assert msg.voltage_kv == 120.0
+    assert msg.current_ua == 150.0
+    assert msg.exposure_time_ms == 500.0
+
+def test_properties():
+    focal_spot_mm = np.array([1.0, 2.0, 3.0])
+    detector_postion_mm = np.array([4.0, 5.0, 6.0])
+    detector_orientation_quad = np.array([0.0, 0.0, 0.0, 1.0])
+    image = np.zeros((20, 30), dtype=np.uint16)
+    detector_heigth_mm = 100.0
+    detector_width_mm = 200.0
+    voltage_kv = 100.0
+    current_ua = 200.0
+    exposure_time_ms = 1000.0
+    frame_id = "test_frame"
+    focal_spot_orientation_quad = np.array([0.0, 0.0, 0.0, 1.0])
+
+    projection = PyProjection(focal_spot_mm, detector_postion_mm, detector_orientation_quad, image, detector_heigth_mm, detector_width_mm, frame_id, focal_spot_orientation_quad, voltage_kv, current_ua, exposure_time_ms)
+
+    assert projection.detector_heigth_px == 20
+    assert projection.detector_width_px == 30
+    assert projection.pixel_pitch_x_mm == 200.0 / 30
+    assert projection.pixel_pitch_y_mm == 100.0 / 20
\ No newline at end of file
diff --git a/test/test_projection_geometry.py b/test/test_projection_geometry.py
new file mode 100644
index 0000000..7d4c44d
--- /dev/null
+++ b/test/test_projection_geometry.py
@@ -0,0 +1,78 @@
+import pytest
+import numpy as np
+from rq_interfaces.msg import ProjectionGeometry
+from rq_controller.common import PyProjectionGeometry  
+
+
+@pytest.fixture
+def example_message():
+    msg = ProjectionGeometry()
+    msg.focal_spot_postion_mm.x = 10.
+    msg.focal_spot_postion_mm.y = 20.
+    msg.focal_spot_postion_mm.z = 30.
+    msg.detector_postion_mm.x = 40.
+    msg.detector_postion_mm.y = 50.
+    msg.detector_postion_mm.z = 60.
+    msg.detector_orientation_quad.x = 0.
+    msg.detector_orientation_quad.y = 0.
+    msg.detector_orientation_quad.z = 0.
+    msg.detector_orientation_quad.w = 1.
+    msg.focal_spot_orientation_quad.x = 0.
+    msg.focal_spot_orientation_quad.y = 0.
+    msg.focal_spot_orientation_quad.z = 0.
+    msg.focal_spot_orientation_quad.w = 1.
+    msg.header.frame_id = "test_frame"
+    return msg
+
+def test_initialization():
+    focal_spot_mm = np.array([1.0, 2.0, 3.0])
+    detector_postion_mm = np.array([4.0, 5.0, 6.0])
+    detector_orientation_quad = np.array([0.0, 0.0, 0.0, 1.0])
+    frame_id = "test_frame"
+    focal_spot_orientation_quad = np.array([0.0, 0.0, 0.0, 1.0])
+
+    geometry = PyProjectionGeometry(focal_spot_mm, detector_postion_mm, detector_orientation_quad, frame_id, focal_spot_orientation_quad)
+
+    assert np.array_equal(geometry.focal_spot_mm, focal_spot_mm)
+    assert np.array_equal(geometry.detector_postion_mm, detector_postion_mm)
+    assert np.array_equal(geometry.detector_orientation_quad, detector_orientation_quad)
+    assert np.array_equal(geometry.focal_spot_orientation_quad, focal_spot_orientation_quad)
+    assert geometry.frame_id == frame_id
+
+def test_dummy_method():
+    geometry = PyProjectionGeometry.dummy()
+
+    assert np.array_equal(geometry.focal_spot_mm, np.array([1.0, 0.0, 0.0]))
+    assert np.array_equal(geometry.detector_postion_mm, np.array([-1.0, 0.0, 0.0]))
+    assert np.array_equal(geometry.detector_orientation_quad, np.array([0.0, 0.0, 0.0, 1.0]))
+    assert geometry.frame_id == "object"
+    assert np.array_equal(geometry.focal_spot_orientation_quad, np.array([0.0, 0.0, 0.0, 1.0]))
+
+def test_from_message(example_message):
+    geometry = PyProjectionGeometry.from_message(example_message)
+
+    assert np.array_equal(geometry.focal_spot_mm, np.array([10, 20, 30]))
+    assert np.array_equal(geometry.detector_postion_mm, np.array([40, 50, 60]))
+    assert np.array_equal(geometry.detector_orientation_quad, np.array([0, 0, 0, 1]))
+    assert np.array_equal(geometry.focal_spot_orientation_quad, np.array([0, 0, 0, 1]))
+    assert geometry.frame_id == "test_frame"
+
+def test_as_message(example_message):
+    geometry = PyProjectionGeometry.from_message(example_message)
+    msg = geometry.as_message()
+
+    assert msg.focal_spot_postion_mm.x == 10
+    assert msg.focal_spot_postion_mm.y == 20
+    assert msg.focal_spot_postion_mm.z == 30
+    assert msg.detector_postion_mm.x == 40
+    assert msg.detector_postion_mm.y == 50
+    assert msg.detector_postion_mm.z == 60
+    assert msg.detector_orientation_quad.x == 0
+    assert msg.detector_orientation_quad.y == 0
+    assert msg.detector_orientation_quad.z == 0
+    assert msg.detector_orientation_quad.w == 1
+    assert msg.focal_spot_orientation_quad.x == 0
+    assert msg.focal_spot_orientation_quad.y == 0
+    assert msg.focal_spot_orientation_quad.z == 0
+    assert msg.focal_spot_orientation_quad.w == 1
+    assert msg.header.frame_id == "test_frame"
diff --git a/test/test_region_of_intrest.py b/test/test_region_of_intrest.py
new file mode 100644
index 0000000..678f052
--- /dev/null
+++ b/test/test_region_of_intrest.py
@@ -0,0 +1,100 @@
+import pytest
+import numpy as np
+from rq_controller.common import PyRegionOfIntrest  # Adjust this import according to your module's path
+from rq_interfaces.msg import RegionOfIntrest
+from visualization_msgs.msg import Marker
+
+@pytest.fixture
+def example_message():
+    msg = RegionOfIntrest()
+    
+    marker = Marker()
+    marker.pose.position.x = 10.0
+    marker.pose.position.y = 20.0
+    marker.pose.position.z = 30.0
+    marker.scale.x = 40.0
+    marker.scale.y = 50.0
+    marker.scale.z = 60.0
+    marker.header.frame_id = "test_frame"
+    
+    msg.region_of_intrest_stack.markers.append(marker)
+    
+    msg.resolution.x = 0.1
+    msg.resolution.y = 0.1
+    msg.resolution.z = 0.1
+    
+    return msg
+
+def test_initialization():
+    center_points_mm = np.array([[1.0, 2.0, 3.0]])
+    dimensions_mm = np.array([[4.0, 5.0, 6.0]])
+    resolution_mm = np.array([[0.1, 0.1, 0.1]])
+    frame_id = "test_frame"
+
+    roi = PyRegionOfIntrest(center_points_mm, dimensions_mm, frame_id, resolution_mm)
+
+    assert np.array_equal(roi.center_points_mm, center_points_mm)
+    assert np.array_equal(roi.dimensions_mm, dimensions_mm)
+    assert np.array_equal(roi.resolution_mm, resolution_mm)
+    assert roi.frame_id == frame_id
+
+def test_dummy_method():
+    roi = PyRegionOfIntrest.dummy()
+
+    assert roi.center_points_mm.shape == (1, 3)
+    assert roi.dimensions_mm.shape == (1, 3)
+    assert roi.frame_id == "object"
+
+def test_from_message(example_message):
+    roi = PyRegionOfIntrest.from_message(example_message)
+
+    assert np.array_equal(roi.center_points_mm, np.array([[10.0, 20.0, 30.0]]))
+    assert np.array_equal(roi.dimensions_mm, np.array([[40.0, 50.0, 60.0]]))
+    assert np.array_equal(roi.resolution_mm, np.array([[0.1, 0.1, 0.1]]))
+    assert roi.frame_id == "test_frame"
+
+def test_as_message(example_message):
+    roi = PyRegionOfIntrest.from_message(example_message)
+    msg = roi.as_message()
+
+    assert len(msg.region_of_intrest_stack.markers) == 1
+    marker = msg.region_of_intrest_stack.markers[0]
+
+    assert marker.pose.position.x == 10.0
+    assert marker.pose.position.y == 20.0
+    assert marker.pose.position.z == 30.0
+    assert marker.scale.x == 40.0
+    assert marker.scale.y == 50.0
+    assert marker.scale.z == 60.0
+    assert marker.header.frame_id == "test_frame"
+    
+    assert msg.resolution.x == 0.1
+    assert msg.resolution.y == 0.1
+    assert msg.resolution.z == 0.1
+
+def test_number_of_rois(example_message):
+    roi = PyRegionOfIntrest.from_message(example_message)
+    assert roi.number_of_rois == 1
+
+def test_shape(example_message):
+    roi = PyRegionOfIntrest.from_message(example_message)
+    assert roi.shape == (399, 499, 599)
+
+def test_get_grid(example_message):
+    roi = PyRegionOfIntrest.from_message(example_message)
+    grid = roi.get_grid(0)
+
+    assert grid.shape == (399, 499, 599, 3)
+    assert np.array_equal(grid[0, 0, 0], np.array([10.0 - 20.0, 20.0 - 25.0, 30.0 - 30.0]))
+    assert np.array_equal(grid[-1, -1, -1], np.array([10.0 + 20.0, 20.0 + 25.0, 30.0 + 30.0]))
+
+def test_next_neighbor():
+    grid_mm = np.zeros((20, 30, 40, 3))
+    grid_mm[:, :, :, 0] = np.linspace(0, 20, 20).reshape(-1, 1, 1)
+    grid_mm[:, :, :, 1] = np.linspace(0, 30, 30).reshape(1, -1, 1)
+    grid_mm[:, :, :, 2] = np.linspace(0, 40, 40).reshape(1, 1, -1)
+
+    point_mm = np.array([11.0, 16.0, 21.0])
+    index = PyRegionOfIntrest.next_neighbor(grid_mm, point_mm)
+
+    assert np.array_equal(index, [10, 15, 20])
diff --git a/test/test_volume.py b/test/test_volume.py
new file mode 100644
index 0000000..e2a8ff8
--- /dev/null
+++ b/test/test_volume.py
@@ -0,0 +1,98 @@
+import pytest
+import numpy as np
+from rq_controller.common import PyVolume, PyRegionOfIntrest  # Adjust this import according to your module's path
+from rq_controller.common.volume import VOLUME_TYPES
+from rq_interfaces.msg import Volume
+from visualization_msgs.msg import Marker
+from sensor_msgs.msg import Image
+import ros2_numpy
+
+
+@pytest.fixture
+def example_message():
+    msg = Volume()
+
+    marker = Marker()
+    marker.pose.position.x = 10.0
+    marker.pose.position.y = 20.0
+    marker.pose.position.z = 30.0
+    marker.scale.x = 40.0
+    marker.scale.y = 50.0
+    marker.scale.z = 60.0
+    marker.header.frame_id = "test_frame"
+
+    msg.grid.region_of_intrest_stack.markers.append(marker)
+    
+    msg.grid.resolution.x = 1.
+    msg.grid.resolution.y = 1.
+    msg.grid.resolution.z = 1.
+
+    msg.datatype = VOLUME_TYPES.UINT_16
+
+    shape = (40, 50, 60)
+    slices = np.random.randint(0, 65535, size=shape, dtype=np.uint16)
+    for i in range(shape[2]):
+        image_msg = ros2_numpy.msgify(Image, slices[:, :, i], encoding='mono16')
+        msg.slices.append(image_msg)
+    
+    return msg
+
+
+def test_initialization():
+    roi = PyRegionOfIntrest.dummy()
+    array = np.random.randint(0, 255, size=roi.shape, dtype=np.uint8)
+    volume = PyVolume(array, roi, VOLUME_TYPES.UINT_8)
+
+    assert np.array_equal(volume.array, array)
+    assert volume.roi == roi
+    assert volume.data_typ == VOLUME_TYPES.UINT_8
+
+
+def test_dummy_method():
+    volume = PyVolume.dummy()
+
+    assert volume.array.shape == volume.roi.shape
+    assert volume.data_typ == VOLUME_TYPES.UINT_8
+
+
+def test_from_message(example_message):
+    volume = PyVolume.from_message(example_message)
+
+    assert volume.array.shape == (40, 50, 60)
+    assert volume.data_typ == VOLUME_TYPES.UINT_16
+    assert volume.roi.frame_id == "test_frame"
+
+
+def test_as_message(example_message):
+    volume = PyVolume.from_message(example_message)
+    msg = volume.as_message()
+
+    assert msg.datatype == VOLUME_TYPES.UINT_16
+    assert len(msg.slices) == 60
+
+    for i, slice_msg in enumerate(msg.slices):
+        slice_array = ros2_numpy.numpify(slice_msg)
+        assert np.array_equal(slice_array, volume.array[:, :, i])
+ 
+
+def test_get_data_type():
+    assert PyVolume.get_data_type(VOLUME_TYPES.UINT_16) == np.uint16
+    assert PyVolume.get_data_type(VOLUME_TYPES.UINT_8) == np.uint8
+    with pytest.raises(ValueError):
+        PyVolume.get_data_type(-1)
+
+
+def test_enum_to_numpify():
+    assert PyVolume.enum_to_numpify(VOLUME_TYPES.UINT_16) == 'mono16'
+    assert PyVolume.enum_to_numpify(VOLUME_TYPES.UINT_8) == 'mono8'
+    with pytest.raises(ValueError):
+        PyVolume.enum_to_numpify(-1)
+
+
+def test_shape():
+    roi = PyRegionOfIntrest.dummy()
+    array = np.random.randint(0, 255, size=roi.shape, dtype=np.uint8)
+    volume = PyVolume(array, roi, VOLUME_TYPES.UINT_8)
+
+    assert volume.shape == array.shape
+
-- 
GitLab