diff --git a/example/volume_example.py b/example/volume_example.py new file mode 100644 index 0000000000000000000000000000000000000000..b0574ec3faef18d15c1e2a229ce1f81586d1c686 --- /dev/null +++ b/example/volume_example.py @@ -0,0 +1,28 @@ +from rq_controller.common import PyVolume +from rq_controller.common.io.rq_json import RqJsonWriter, RqJsonLoader +from pathlib import Path + + +FOLDER = Path('./example/data') + + +def main(): + volume = PyVolume.dummy() + print(f'Shape (x / y / z): {volume.shape}') + msg = volume.as_message() + print(f'Number of slices: {len(msg.slices)}') + + volume_2 = PyVolume.from_message(msg) + print(f'Shape (x / y / z): {volume_2.shape}') + + writer = RqJsonWriter() + writer.write_volume(writer.get_volume_save_path_i(FOLDER, 1), volume_2) + + loader = RqJsonLoader() + volume_3 = loader.load_volume(writer.get_volume_save_path_i(FOLDER, 1)) + print(f'Shape (x / y / z): {volume_3.shape}') + + +if __name__ == '__main__': + main() + diff --git a/rq_controller.egg-info/PKG-INFO b/rq_controller.egg-info/PKG-INFO index fd6b18adf9edb636a2d3c758364fcddabf085874..1fb26ba9904274a5b958e8e373e7a6a3504338b4 100644 --- a/rq_controller.egg-info/PKG-INFO +++ b/rq_controller.egg-info/PKG-INFO @@ -1,8 +1,11 @@ Metadata-Version: 2.1 -Name: rq_controller +Name: rq-controller Version: 0.0.0 Summary: TODO: Package description Maintainer: root Maintainer-email: simon.wittl@th-deg.de License: TODO: License declaration -Requires-Dist: setuptools +Platform: UNKNOWN + +UNKNOWN + diff --git a/rq_controller.egg-info/SOURCES.txt b/rq_controller.egg-info/SOURCES.txt index 14660b4209872b85004f95cbfa4b212d33189da8..19eb4697b7f18a691334f2b580575ea1d39d0b6c 100644 --- a/rq_controller.egg-info/SOURCES.txt +++ b/rq_controller.egg-info/SOURCES.txt @@ -1,10 +1,16 @@ +.gitignore README.md package.xml setup.cfg setup.py +.vscode/c_cpp_properties.json +.vscode/settings.json +example/projection_example.py +example/data/projection_00001.geom-json +example/data/projection_00001.tif resource/rq_controller rq_controller/__init__.py -rq_controller/rq_workflow_client.py +rq_controller/rq_workflow.py rq_controller.egg-info/PKG-INFO rq_controller.egg-info/SOURCES.txt rq_controller.egg-info/dependency_links.txt @@ -15,12 +21,23 @@ rq_controller/common/__init__.py rq_controller/common/projection.py rq_controller/common/projection_geometry.py rq_controller/common/region_of_intrest.py +rq_controller/common/volume.py +rq_controller/common/__pycache__/__init__.cpython-38.pyc +rq_controller/common/__pycache__/projection.cpython-38.pyc +rq_controller/common/__pycache__/projection_geometry.cpython-38.pyc +rq_controller/common/__pycache__/region_of_intrest.cpython-38.pyc rq_controller/common/io/__init__.py rq_controller/common/io/loader.py rq_controller/common/io/writer.py +rq_controller/common/io/__pycache__/__init__.cpython-38.pyc +rq_controller/common/io/__pycache__/loader.cpython-38.pyc +rq_controller/common/io/__pycache__/writer.cpython-38.pyc rq_controller/common/io/rq_json/__init__.py -rq_controller/common/io/rq_json/load.py -rq_controller/common/io/rq_json/write.py +rq_controller/common/io/rq_json/json_load.py +rq_controller/common/io/rq_json/json_write.py +rq_controller/common/io/rq_json/__pycache__/__init__.cpython-38.pyc +rq_controller/common/io/rq_json/__pycache__/load.cpython-38.pyc +rq_controller/common/io/rq_json/__pycache__/write.cpython-38.pyc test/test_copyright.py test/test_flake8.py test/test_pep257.py \ No newline at end of file diff --git a/rq_controller/__pycache__/__init__.cpython-38.pyc b/rq_controller/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92399741f3d3f4b877595124493573a15902c93f Binary files /dev/null and b/rq_controller/__pycache__/__init__.cpython-38.pyc differ diff --git a/rq_controller/common/__init__.py b/rq_controller/common/__init__.py index c737cfd538cf3619c143c13eb69f57789a04503e..49190b8087a1085ee69dbe334d271973097b6935 100644 --- a/rq_controller/common/__init__.py +++ b/rq_controller/common/__init__.py @@ -1,2 +1,2 @@ from .projection import PyProjectionGeometry, PyProjection -from .region_of_intrest import PyRegionOfIntrest \ No newline at end of file +from .volume import PyVolume, PyRegionOfIntrest \ No newline at end of file diff --git a/rq_controller/common/__pycache__/projection.cpython-38.pyc b/rq_controller/common/__pycache__/projection.cpython-38.pyc index 2dbfc5085d556677756493e4a428385bae72688f..84787faff2867b84f7673e065471b45fd6f6c070 100644 Binary files a/rq_controller/common/__pycache__/projection.cpython-38.pyc and b/rq_controller/common/__pycache__/projection.cpython-38.pyc differ diff --git a/rq_controller/common/__pycache__/region_of_intrest.cpython-38.pyc b/rq_controller/common/__pycache__/region_of_intrest.cpython-38.pyc index f5698e732fe423453feb6decb77378412c1d5826..4107f998cbedcee04cc429fe8e351e74307c759d 100644 Binary files a/rq_controller/common/__pycache__/region_of_intrest.cpython-38.pyc and b/rq_controller/common/__pycache__/region_of_intrest.cpython-38.pyc differ diff --git a/rq_controller/common/io/__pycache__/writer.cpython-38.pyc b/rq_controller/common/io/__pycache__/writer.cpython-38.pyc index 76b818b42aeab0bdecf1a52ee916a3bfdd4414f6..def95e0b1ea611bbf747c17bf753baa721ce50b5 100644 Binary files a/rq_controller/common/io/__pycache__/writer.cpython-38.pyc and b/rq_controller/common/io/__pycache__/writer.cpython-38.pyc differ diff --git a/rq_controller/common/io/loader.py b/rq_controller/common/io/loader.py index fe8c5b0091d5febfd83baefb66f4754473357157..4ce8e32f6ed7a28bc59d90c75cf0c9d6db5f219e 100644 --- a/rq_controller/common/io/loader.py +++ b/rq_controller/common/io/loader.py @@ -1,25 +1,30 @@ import numpy as np import json from pathlib import Path -from ...common import PyProjectionGeometry, PyProjection, PyRegionOfIntrest +from ...common import PyProjectionGeometry, PyProjection, PyRegionOfIntrest, PyVolume class BaseDataLoader(): def __init__(self, porjection_geometry_suffix: str, projection_suffix: str, - region_of_intrest_suffix: str): + region_of_intrest_suffix: str, + volume_suffix: str): self.porjection_geometry_suffix = porjection_geometry_suffix self.projection_suffix = projection_suffix self.region_of_intrest_suffix = region_of_intrest_suffix + self.volume_suffix = volume_suffix def load_projection_geometry(self, load_path: Path) -> PyProjectionGeometry: - raise NotImplementedError + raise NotImplementedError() def load_projection(self, load_path: Path) -> PyProjection: - raise NotImplementedError + raise NotImplementedError() def load_region_of_intrest(self, load_path: Path) -> PyRegionOfIntrest: - raise NotImplementedError + raise NotImplementedError() + + def load_volume(self, load_path: Path) -> PyVolume: + raise NotImplementedError() \ No newline at end of file diff --git a/rq_controller/common/io/rq_json/__pycache__/__init__.cpython-38.pyc b/rq_controller/common/io/rq_json/__pycache__/__init__.cpython-38.pyc index bb5f48d900fd86908f27dd3f2b51e11b9ca06ed7..533f2156e73fb0d56b4a716687557d50ce8233eb 100644 Binary files a/rq_controller/common/io/rq_json/__pycache__/__init__.cpython-38.pyc and b/rq_controller/common/io/rq_json/__pycache__/__init__.cpython-38.pyc differ diff --git a/rq_controller/common/io/rq_json/__pycache__/json_load.cpython-38.pyc b/rq_controller/common/io/rq_json/__pycache__/json_load.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d3cf5cbc6c20d1760fd51380de06d162393fbbf Binary files /dev/null and b/rq_controller/common/io/rq_json/__pycache__/json_load.cpython-38.pyc differ diff --git a/rq_controller/common/io/rq_json/__pycache__/json_write.cpython-38.pyc b/rq_controller/common/io/rq_json/__pycache__/json_write.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbd680e22197406156ea2d812a841eadbb940ca4 Binary files /dev/null and b/rq_controller/common/io/rq_json/__pycache__/json_write.cpython-38.pyc differ diff --git a/rq_controller/common/io/rq_json/json_load.py b/rq_controller/common/io/rq_json/json_load.py index 2727ef3332e4cd7c8d307cf31cda8258e266e109..d414d60a4e3d7ff53517691f3671b91bb9b61f1b 100644 --- a/rq_controller/common/io/rq_json/json_load.py +++ b/rq_controller/common/io/rq_json/json_load.py @@ -2,13 +2,15 @@ import numpy as np import json from pathlib import Path -from ..loader import BaseDataLoader, PyProjection, PyProjectionGeometry, PyRegionOfIntrest +from ..loader import BaseDataLoader, PyProjection, PyProjectionGeometry, PyRegionOfIntrest, PyVolume from PIL import Image +import xtiff +import pyometiff class RqJsonLoader(BaseDataLoader): def __init__(self): - super().__init__('.geom-json', '.tif', '.roi-json') + super().__init__('.geom-json', '.tif', '.roi-json', '.ome.tiff') def load_json(self, load_path: Path) -> dict: with open(str(load_path), 'r') as f: @@ -51,9 +53,25 @@ class RqJsonLoader(BaseDataLoader): data_dict = self.load_json(load_path) region_of_intrest = PyRegionOfIntrest( - start_point_mm=np.array(data_dict['start_point_mm']), - end_point_mm=np.array(data_dict['end_point_mm']), - frame_id=data_dict['resoluion_mm'], - resoluion_mm=np.array(data_dict['resoluion_mm'])) + center_points_mm=np.array(data_dict['center_points_mm']), + dimensions_mm=np.array(data_dict['dimensions_mm']), + frame_id=data_dict['frame_id'], + resolution_mm=np.array(data_dict['resolution_mm'])) return region_of_intrest + + def load_volume(self, load_path: Path) -> PyVolume: + load_path_roi = load_path.parent / f'{load_path.stem}{self.region_of_intrest_suffix}' + roi = self.load_region_of_intrest(load_path_roi) + reader = pyometiff.OMETIFFReader(load_path) + volume, _, _ = reader.read() + + if volume.dtype == np.uint16: + data_type = 0 + elif volume.dtype == np.uint8: + data_type = 1 + else: + raise ValueError('data type not implemented.') + + return PyVolume(volume, roi, data_type) + diff --git a/rq_controller/common/io/rq_json/json_write.py b/rq_controller/common/io/rq_json/json_write.py index 4099fa910fde5e4d963496ee93a7953ab2d9b79c..2628163c02685626012dc8bf99e676691628da7d 100644 --- a/rq_controller/common/io/rq_json/json_write.py +++ b/rq_controller/common/io/rq_json/json_write.py @@ -2,13 +2,15 @@ import numpy as np import json from pathlib import Path -from ..writer import BaseDataWriter, PyProjection, PyProjectionGeometry, PyRegionOfIntrest +from ..writer import BaseDataWriter, PyProjection, PyProjectionGeometry, PyRegionOfIntrest, PyVolume from PIL import Image +import xtiff +import pyometiff class RqJsonWriter(BaseDataWriter): def __init__(self): - super().__init__('.geom-json', '.tif', '.roi-json') + super().__init__('.geom-json', '.tif', '.roi-json', '.ome.tiff') def write_json(self, save_path: Path, data_dict: dict): with open(str(save_path), 'w') as f: @@ -61,6 +63,12 @@ class RqJsonWriter(BaseDataWriter): data_dict['center_points_mm'] = region_of_intrest.center_points_mm.tolist() data_dict['dimensions_mm'] = region_of_intrest.dimensions_mm.tolist() data_dict['frame_id'] = region_of_intrest.frame_id - data_dict['resoluion_mm'] = region_of_intrest.resolution_mm.tolist() + data_dict['resolution_mm'] = region_of_intrest.resolution_mm.tolist() self.write_json(save_path, data_dict) + + def write_volume(self, save_path: Path, volume: PyVolume): + save_path_region_of_intrest = save_path.parent / f'{save_path.stem}{self.region_of_intrest_suffix}' + self.write_region_of_intrest(save_path_region_of_intrest, volume.roi) + writer = pyometiff.OMETIFFWriter(save_path, volume.array, dict()) + writer.write() \ No newline at end of file diff --git a/rq_controller/common/io/writer.py b/rq_controller/common/io/writer.py index 73f1c40ee2ee594d3a1c598f1fd3b8600de74bbc..be86283acb84da1848a374ea17062636b51a13cb 100644 --- a/rq_controller/common/io/writer.py +++ b/rq_controller/common/io/writer.py @@ -1,31 +1,36 @@ import numpy as np import json from pathlib import Path -from ...common import PyProjectionGeometry, PyProjection, PyRegionOfIntrest +from ...common import PyProjectionGeometry, PyProjection, PyRegionOfIntrest, PyVolume class BaseDataWriter(): projection_name: str = 'projection' projection_geometry_name: str ='geometry' region_of_intrest_name: str = 'roi' + volume_name: str = 'volume' def __init__(self, porjection_geometry_suffix: str, projection_suffix: str, - region_of_intrest_suffix: str): + region_of_intrest_suffix: str, + volume_suffix: str): self.porjection_geometry_suffix = porjection_geometry_suffix self.projection_suffix = projection_suffix self.region_of_intrest_suffix = region_of_intrest_suffix + self.volume_suffix = volume_suffix def write_projection_geometry(self, save_path: Path, projection_geometry: PyProjectionGeometry): - raise NotImplementedError + raise NotImplementedError() def write_projection(self, save_path: Path, projection: PyProjection): - raise NotImplementedError + raise NotImplementedError() def write_region_of_intrest(self, save_path: Path, region_of_intrest: PyRegionOfIntrest): raise NotImplementedError + def write_volume(save_path: Path, region_of_intrest: PyRegionOfIntrest): + raise NotImplementedError() def get_next_projection_save_path(self, save_folder: Path) -> Path: return self.get_projection_save_path_i(save_folder, self.number_of_projections(save_folder) + 1) @@ -45,6 +50,12 @@ class BaseDataWriter(): def get_region_of_intrest_save_path_i(self, save_folder: Path, i) -> Path: return save_folder / f'{self.region_of_intrest_name}_{i:05}{self.region_of_intrest_suffix}' + def get_next_volume_save_path(self, save_folder: Path) -> Path: + return self.get_volume_save_path_i(save_folder, self.number_of_volumes(save_folder) + 1) + + def get_volume_save_path_i(self, save_folder: Path, i) -> Path: + return save_folder / f'{self.volume_name}_{i:05}{self.volume_suffix}' + def number_of_projection_geometries(self, folder: Path) -> int: return len(list(folder.glob(f'{self.projection_geometry_name}*{self.porjection_geometry_suffix}'))) @@ -53,3 +64,6 @@ class BaseDataWriter(): def number_of_region_of_intrests(self, folder: Path) -> int: return len(list(folder.glob(f'{self.region_of_intrest_name}*{self.region_of_intrest_suffix}'))) + + def number_of_volumes(self, folder: Path) -> int: + return len(list(folder.glob(f'{self.volume_name}*{self.volume_suffix}'))) diff --git a/rq_controller/common/projection.py b/rq_controller/common/projection.py index 979e8f4d3d88e649dc33bb8807037efa091132b3..ea5c1ee5010bf24e08ba4d471d4dc3c860de0c39 100644 --- a/rq_controller/common/projection.py +++ b/rq_controller/common/projection.py @@ -114,6 +114,13 @@ class PyProjection(PyProjectionGeometry): return message + def get_projection_geometry(self) -> PyProjectionGeometry: + return PyProjectionGeometry(self.focal_spot_mm, + self.detector_postion_mm, + self.detector_orientation_quad, + self.frame_id, + self.focal_spot_orientation_quad) + @property def detector_heigth_px(self) -> int: return self.image.shape[0] @@ -128,4 +135,4 @@ class PyProjection(PyProjectionGeometry): @property def pixel_pitch_y_mm(self) -> float: - return self.detector_heigth_mm / self.detector_heigth_px \ No newline at end of file + return self.detector_heigth_mm / self.detector_heigth_px diff --git a/rq_controller/common/region_of_intrest.py b/rq_controller/common/region_of_intrest.py index 1636edd5ed7f0c2f8505d9b35d277cfc46428da0..6a3115bb4cd6953c55794278c691c5d45b21ddb0 100644 --- a/rq_controller/common/region_of_intrest.py +++ b/rq_controller/common/region_of_intrest.py @@ -7,15 +7,15 @@ from visualization_msgs.msg import Marker class PyRegionOfIntrest(): 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])): - self.center_points_mm = center_points_mm - self.dimensions_mm = dimensions_mm + self.center_points_mm = center_points_mm.reshape((-1, 3)) + self.dimensions_mm = dimensions_mm.reshape((-1, 3)) self.frame_id = frame_id - self.resolution_mm = resolution_mm + self.resolution_mm = resolution_mm.reshape((-1, 3)) @classmethod def dummy(cls): return cls((np.random.random((3, )) - 0.5) * 20., - (np.random.random((3, )) - 0.5) * 10.) + np.random.random((3, )) * 10.) @classmethod def from_message(cls, msg: RegionOfIntrest): @@ -45,6 +45,12 @@ class PyRegionOfIntrest(): def number_of_rois(self) -> int: return self.center_points_mm.shape[0] + @property + def shape(self) -> tuple: + shape = self.dimensions_mm[0] // self.resolution_mm[0] + return (int(shape[0]), int(shape[1]), int(shape[2])) + + def as_message(self) -> RegionOfIntrest: message = RegionOfIntrest() roi_list = list() @@ -52,13 +58,13 @@ class PyRegionOfIntrest(): for i in range(self.number_of_rois): roi = Marker() - roi.pose.position.x = self.center_points_mm[i][0] - roi.pose.position.y = self.center_points_mm[i][1] - roi.pose.position.z = self.center_points_mm[i][2] + roi.pose.position.x = float(self.center_points_mm[i][0]) + roi.pose.position.y = float(self.center_points_mm[i][1]) + roi.pose.position.z = float(self.center_points_mm[i][2]) - roi.scale.x = self.dimensions_mm[i][0] - roi.scale.y = self.dimensions_mm[i][1] - roi.scale.z = self.dimensions_mm[i][2] + roi.scale.x = float(self.dimensions_mm[i][0]) + roi.scale.y = float(self.dimensions_mm[i][1]) + roi.scale.z = float(self.dimensions_mm[i][2]) roi.header.frame_id = self.frame_id @@ -66,8 +72,8 @@ class PyRegionOfIntrest(): message.region_of_intrest_stack.markers = roi_list - message.resolution.x = self.resolution_mm[0] - message.resolution.x = self.resolution_mm[1] - message.resolution.x = self.resolution_mm[2] + message.resolution.x = float(self.resolution_mm[0][0]) + message.resolution.y = float(self.resolution_mm[0][1]) + message.resolution.z = float(self.resolution_mm[0][2]) return message diff --git a/rq_controller/common/volume.py b/rq_controller/common/volume.py new file mode 100644 index 0000000000000000000000000000000000000000..3ac025ed40081d0c09566ec3e223bf09c656a5dd --- /dev/null +++ b/rq_controller/common/volume.py @@ -0,0 +1,101 @@ +from numpy import ndarray +from numpy.core.multiarray import array as array +from rq_interfaces.msg import Volume +from visualization_msgs.msg import Marker +from .region_of_intrest import PyRegionOfIntrest +from sensor_msgs.msg import Image +from enum import IntEnum + +import numpy as np +import ros2_numpy + + +class VOLUME_TYPES(IntEnum): + UINT_16 = 0 + UINT_8 = 1 + + +class PyVolume(): + def __init__(self, array: ndarray, roi: PyRegionOfIntrest, data_type: VOLUME_TYPES = ...): + self.roi = roi + self.array = array + self.data_typ = data_type + + @staticmethod + def get_data_type(volume_type: VOLUME_TYPES) -> np.dtype: + if volume_type == VOLUME_TYPES.UINT_16: + return np.uint16 + elif volume_type == VOLUME_TYPES.UINT_8: + return np.uint8 + else: + raise ValueError('Datatype is not implemented') + + @staticmethod + def enum_to_numpify(volume_type: VOLUME_TYPES) -> str: + if volume_type == VOLUME_TYPES.UINT_16: + return 'mono16' + elif volume_type == VOLUME_TYPES.UINT_8: + return 'mono8' + else: + raise ValueError('Datatype is not implemented') + + @classmethod + def dummy(cls): + 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): + roi: Marker = msg.grid.region_of_intrest_stack.markers[0] + center_points_mm = np.array([ + roi.pose.position.x, + roi.pose.position.y, + roi.pose.position.z]) + + dimensions_mm = np.array([ + roi.scale.x, + roi.scale.y, + roi.scale.z]) + + frame_id = roi.header.frame_id + resolution_mm = np.array([ + msg.grid.resolution.x, + msg.grid.resolution.y, + msg.grid.resolution.z]) + + py_roi = PyRegionOfIntrest(center_points_mm, dimensions_mm, frame_id, resolution_mm) + + data_typ = msg.datatype + + array = np.zeros(py_roi.shape, dtype=cls.get_data_type(data_typ)) + + for i, slice in enumerate(msg.slices): + array[:, :, i] = ros2_numpy.numpify(slice).reshape((py_roi.shape[0], py_roi.shape[1])).astype(cls.get_data_type(data_typ)) + + return cls(array, py_roi, data_typ) + + def as_message(self) -> Volume: + message = Volume() + + message.datatype = self.data_typ + message.grid = self.roi.as_message() + message.slices = list() + + for z in range(self.array.shape[2]): + message.slices.append( + ros2_numpy.msgify(Image, + self.array[:, :, z].astype(self.get_data_type(self.data_typ)), + self.enum_to_numpify(self.data_typ))) + + return message + + @property + def shape(self): + return self.array.shape + + + + + diff --git a/rq_controller/rq_workflow.py b/rq_controller/rq_workflow.py index 662b0c7af1c566c43305e5222b985f3ed6b3db28..b72956595a9900de0860035c5a34be70cf9ca447 100644 --- a/rq_controller/rq_workflow.py +++ b/rq_controller/rq_workflow.py @@ -51,6 +51,7 @@ class WorkflowNode(Node): reached, cost, _ = self.hardware_interface.reachability_response_2_py(response) return reached, cost + def main(): rclpy.init()