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 zcmb_fy>App6rb78+dJDBW0L?OY)ApuxQL<@R+NB1K{UpK(3~u-mYuotUiRad-NW(e zT-jBs6e%g7<R3_ze<PYUMdXe;Qc&KT-Mw9(F99O<<hS#7-n@D9K7McfczL-^;Q8G8 zx&PBTA%7s#d~{%L5kj-CV895Y5s7J!QXFfM*3;pwM@DS+%-HH#l-wiCVCFGl=8@L3 znf8cutrOJPrLyBENt*k4m?qhFSCeZyA|3Jo`FlK#c`goQyZ#p7vgXqQ<`x>W2Ln!e zl#!msC>Wt}<Aup|_`&7_<0b8D%u+uaXp1?|E!Jk8W3y**n=P^BV{$}$Ef}w`bI&PT zW#^9#_~HP+2K)uKer)vGYy-@{DA#umYO5+seODu-?ZWerj|9(nl4owd$K64B6gTa= zzB`&7(G8Oz8nZAND6xJT_>r59(%khE<}#jxCsN^#(kw)esIRFAL9g=IW?OA`JI_V< zWSsMCOYvxcQstp4FOJ=t$!IfSz7YOFcgx+W)PXf<U1QrYapN$GLU246+2%}LWr_!; zpe1itTCIR1zC~r@K;F}_&*rRsP@fmwhX&7d`x&?s)_}1ISXJBGIR5*qZ}{WiHxPcz zy%3DeasfX54te+fU2vvQHa3D>MZYjlTImza@IR|G78FJ$_xjsOyN;FALAf-x(}Z^| zx!gEZ9Lo!p7aB2<mu4U5k1bpM(qwt1q3Dd5C3rp-Nnlmx!5hbr+2jru+I1LA>6Cb6 zssW`_6R0-Tf$CEOsKF?dh~}?X#en&EKY<MPqj178WQ&Ri5+%$1B;aLM<Eq<Y(8y{0 z(lGr)<Ta|Cx$MK`_9?aVF8&btjT=<)e6JLA@AAGsj&e85+#5wA-l)q)sYMVfkmLO= zf0xqP?wX<lNhFq#oI|pVWCaMMxsmax-w>G1DbZI%tbyELJHgg2<Ii@*Q*S>NKlGz? ze>W2W@<E#9B8?(0=C~k@<22c=E7O}*=IB7SJTHVK^}Gw9KT|F6oJEV@<m#e~-K<sw zSi#kUV+a0Jv3vo8DapwT$|%#AKG0uM#TlnK4LL@!OC9M@Jm%!p40`Vf4)}&u!^UxZ zFe?)T!$npV2*d%VlR=}D9;&jQtENTkx7^$FJMD-09+XLuIM4iO%p0viU<(#D5<L0< z{@{828#Hq*8A-F~I#lXGl&RPi1idUxb)=gx%uowyt<n{``et%@kzt!kSm+=J7Y9Ag z1_6Rn^`Hd^RLbwazySR`r9*9~D{3feVgwGga?Vn7))42cVKJwdbGDkZ)to&vLBbxk zFhYk$Dd8vyM@cwJ!YL(Cx;^Y1T2ll`ghgv=9+?nX>R$yfoeggC4sjJ}3|a8-!6P^3 zS>_M^8!5p*?#;7z6zEmoLkn$aaj{^0$bGnl)U{FHS(u;u|1*&qr^4DQ8UO?2f|Fn) zZ&*=-P<KgeK={N(AabJt_%%LD`lF-=l1>T?L5PraUjc-)_PEcuDB#2vAEByCNDy=6 z+6?UjxJ7XR7a4^<@?vA72JR^}^J%+;2k^=ud<kkZOkvV#BV4?(j8kuF{cV}*iyF1G z$!80*yQ#BF#VRN~g)5lr_^0ywcNpa4h#W#A9s;&pf|fkgpaCPNl^ir<;PjG%Ha)aT zNlQsurKIJdq*Y2<9!lD!q^%_3(yp}FO42SR?NZVx=UTANDc3n_ol~xJ)H<hJ=csiK zE7mz4u4{w+9S^8NzhCPDNt0(>l-KA*eyi&yPye+#7K7jH8eZMs&z6R$?(x+XeL;cL znX3Ts1X1t=BXyz!fo#TC(a<jA=n9fgkX%Jlcfn7Qt=ru-V7r<)4=-t#$u^&fZ9l8> z@G;1#%NemPZ3S?2rAaFe-feAiZILH8YhByj4)((Y@3@|9dtRKfafIU~5AWzvIb(Ys zO9RiV3s%5?j6`;V$j>tPCt@#U;$!$Ct|PgD1lxCmU>^`B5XA2cCtK85*2wCc%NrVP zcQm@{s`+jU4e5{Jb_#|Fbg(p&aXdOuyUX*?`a>+~z8^q;0{AN&Z-Agu(AZSjpgK_N 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