{ "cells": [ { "cell_type": "code", "metadata": { "tags": [ "hide-input" ], "ExecuteTime": { "end_time": "2024-09-19T07:34:13.119093Z", "start_time": "2024-09-19T07:34:13.000023Z" } }, "source": [ "import _engine_plugin_guard\n", "\n", "import os\n", "import shutil as sh\n", "\n", "import numpy as np\n", "\n", "# delete test project if the notebook was already run\n", "proj_path = os.path.join(os.path.abspath('..'), 'test_project')\n", "if os.path.isdir(proj_path):\n", "\tsh.rmtree(proj_path)\n", "\n", "# formatting helpers\n", "def bold(string: str):\n", "\treturn '\\033[1m' + string + '\\033[0m'" ], "outputs": [], "execution_count": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial on the Python bindings for ImFusion Labels" ] }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2024-09-19T07:34:15.334200Z", "start_time": "2024-09-19T07:34:13.122479Z" } }, "source": [ "# The labels module resides in the imfusion package\n", "import imfusion\n", "from imfusion import labels" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Public beta build of ImFusion Python SDK. Not for commercial use.\n" ] } ], "execution_count": 2 }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2024-09-19T07:34:15.537813Z", "start_time": "2024-09-19T07:34:15.535471Z" } }, "source": [ "# A lot of information can already be found in the module's docstring:\n", "labels?" ], "outputs": [], "execution_count": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Setting up an ImFusion Labels project\n", "\n", "`Project` is the central entity of the labels module. It holds the definitions of all annotations (Tags, Labelmap Layers, Keypoint Layers and BoundingBox Layers) and provides access to the project's database in the form of `Descriptor` instances (we will get to those in the next section).\n", "The `Project` class can either create a new Labels project or load an existing one. All interactions with the annotations and data go through an instance of that class.\n", "Let's create a new project for now:" ] }, { "cell_type": "code", "metadata": { "ExecuteTime": { "end_time": "2024-09-19T07:34:15.649676Z", "start_time": "2024-09-19T07:34:15.646368Z" } }, "source": [ "# Create an empty project named 'TestProject' stored in a folder called 'test_project' in the notebooks parent directory\n", "# Creating the project instance will also create the project folder on disk.\n", "project = labels.Project(name='TestProject', project_path=os.path.join(os.path.abspath('..'), 'test_project'))" ], "outputs": [], "execution_count": 4 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "### Annotation Layers\n", "\n", "Labels has the concept of annotation layers for storing any type of annotations (Labelmaps, Landmarks, BoudingBoxes), meaning that each type of annotation can have multiple different data associated with it. An example would be having one labelmap layer per annotator in a multi-annotator dataset.\n", "The definitions for these layers can be added through the `Project` instance analagously to the tag definitions. The instances of these layers also have a very similar interface to the tags." ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:15.687699Z", "start_time": "2024-09-19T07:34:15.681150Z" } }, "source": [ "# Let's add two labelmap layers, a landmark layer and a bounding box layer\n", "project.add_labelmap_layer(name='Segmentation')\n", "project.add_landmark_layer(name='Points of Interest')\n", "project.add_boundingbox_layer(name='Region of Interest')\n", "\n", "print(f'Here are the defined labelmap layers:', f'{project.labelmap_layers=}', '', sep='\\n')\n", "print(f'Here are the defined landmark layers:', f'{project.landmark_layers=}', '', sep='\\n')\n", "print(f'Here are the defined bounding box layers:', f'{project.boundingbox_layers=}', '', sep='\\n')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Here are the defined labelmap layers:\n", "project.labelmap_layers=LabelMapsAccessor(\n", "\tLabelMapLayer(id=p6mWrFYpzc8m, name='Segmentation')\n", ")\n", "\n", "Here are the defined landmark layers:\n", "project.landmark_layers=LandmarkLayersAccessor(\n", "\tLandmarkLayer(id=dICO9aWsoPsO, name='Points of Interest')\n", ")\n", "\n", "Here are the defined bounding box layers:\n", "project.boundingbox_layers=BoundingBoxLayersAccessor(\n", "\tBoundingBoxLayer(id=QZcOWd9ZeKSw, name='Region of Interest')\n", ")\n", "\n" ] } ], "execution_count": 5 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:15.732796Z", "start_time": "2024-09-19T07:34:15.729441Z" } }, "source": [ "# Each layer can store independent annotation definitions\n", "segmentation_layer = project.labelmap_layers[0]\n", "points_layer = project.landmark_layers[0]\n", "roi_layer = project.boundingbox_layers[0]\n", "\n", "\n", "#For now the layers are empty\n", "print(bold('Background label is automatically added with a value of 0 when the layer is created.'))\n", "print(f'{segmentation_layer.annotations=}')\n", "print(f'{points_layer.annotations=}')\n", "print(f'{roi_layer.annotations=}')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001B[1mBackground label is automatically added with a value of 0 when the layer is created.\u001B[0m\n", "segmentation_layer.annotations=LabelsAccessor(\n", "\tLabel(name='Background', color=(0, 0, 0), value=0)\n", ")\n", "points_layer.annotations=LandmarksAccessor(\n", "\t\n", ")\n", "roi_layer.annotations=BoundingBoxAccessor(\n", "\t\n", ")\n" ] } ], "execution_count": 6 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:15.790801Z", "start_time": "2024-09-19T07:34:15.785783Z" } }, "source": [ "# We can add new definitions to a layer using the add_annotation method\n", "segmentation_layer.add_annotation(name='Circle', value=1, color=(128, 0, 0))\n", "segmentation_layer.add_annotation(name='Core', value=2, color=(128, 128, 0))\n", "points_layer.add_annotation(name='Center', color=(0, 128, 0))\n", "roi_layer.add_annotation(name='Containing Box', color=(0, 0, 128))\n", "\n", "print(f'{segmentation_layer.annotations=}')\n", "print(f'{points_layer.annotations=}')\n", "print(f'{roi_layer.annotations=}')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "segmentation_layer.annotations=LabelsAccessor(\n", "\tLabel(name='Background', color=(0, 0, 0), value=0),\n", "\tLabel(name='Circle', color=(128, 0, 0), value=1),\n", "\tLabel(name='Core', color=(128, 128, 0), value=2)\n", ")\n", "points_layer.annotations=LandmarksAccessor(\n", "\tLandmark(name='Center', color=(0, 128, 0))\n", ")\n", "roi_layer.annotations=BoundingBoxAccessor(\n", "\tBoundingBox(name='Containing Box', color=(0, 0, 128))\n", ")\n" ] } ], "execution_count": 7 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "### Tags\n", "\n", "Tag definitions can be added using the `add_tag` method on a `Project` instance. This method will add the new tag to the project and return an instance of the `Tag` class that represents the newly created tag. This class bundles information about the tag definition with the actual tag value (if it is accessed through a `Descriptor` as we will see later). Tags, much like most of the other classes in the module that represent annotations in Labels, have a name, a color and a value. Additionally, they store a reference to their owning project and descriptor and know their index." ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:15.843780Z", "start_time": "2024-09-19T07:34:15.839766Z" } }, "source": [ "# New tags can be added\n", "project.add_tag(name='MyBool', kind=labels.TagKind.Bool)\n", "project.add_tag(name='MyNumber', kind=labels.TagKind.Float, color=(10, 20, 30))\n", "enum_tag = project.add_tag(name='MyChoices', kind=labels.TagKind.Enum, options=['A', 'B'])\n", "enum_tag.add_option('C') # We can also add options to enum tags afterwards\n", "print(f'Here are the defined tags:\\n{project.tags=}')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Here are the defined tags:\n", "project.tags=TagsAccessor(\n", "\tTag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255)),\n", "\tTag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30)),\n", "\tTag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'])\n", ")\n" ] } ], "execution_count": 8 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:15.892190Z", "start_time": "2024-09-19T07:34:15.889650Z" } }, "source": [ "# You can retrieve tag definitions from the project using their name or index:\n", "print(f'Using name:\\t\\t{project.tags[\"MyNumber\"]=}\\n'\n", "\t f'Using index:\\t{project.tags[1]=}')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using name:\t\tproject.tags[\"MyNumber\"]=Tag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30))\n", "Using index:\tproject.tags[1]=Tag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30))\n" ] } ], "execution_count": 9 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:15.944339Z", "start_time": "2024-09-19T07:34:15.941252Z" } }, "source": [ "# Using a Tag instance you can access the information in the tag definition and the parents of the tag\n", "print(f'{enum_tag.name=}',\n", "\t f'{enum_tag.color=}',\n", "\t f'{enum_tag.options=}',\n", "\t '',\n", "\t bold('These are the parents of this tag:'),\n", "\t f'{enum_tag.project=}',\n", "\t f'{enum_tag.descriptor=}',\n", "\t '',\n", "\t bold('Index in the project`s tag definitions:'),\n", "\t f'{enum_tag.index=}',\n", "\t '',\n", "\t bold('The tag has no value, since it is accessed through the project and not a descriptor:'),\n", "\t f'{enum_tag.value=}',\n", "\t sep='\\n')" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "enum_tag.name='MyChoices'\n", "enum_tag.color=(255, 255, 255)\n", "enum_tag.options=['default', 'A', 'B', 'C']\n", "\n", "\u001B[1mThese are the parents of this tag:\u001B[0m\n", "enum_tag.project=\n", "enum_tag.descriptor=None\n", "\n", "\u001B[1mIndex in the project`s tag definitions:\u001B[0m\n", "enum_tag.index=2\n", "\n", "\u001B[1mThe tag has no value, since it is accessed through the project and not a descriptor:\u001B[0m\n", "enum_tag.value=None\n" ] } ], "execution_count": 10 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "## Adding data\n", "\n", "Data in Labels is handled as instances of the `Descriptor` class. It stores the meta information about a data samples, lets you load the underlying image and access the annotations on that particular sample. New data can be added to the project using the `add_descriptor` method on the `Project` class.\n", "It expects a SharedImageSet as the basis for creating a new descriptor. Optionally, the descriptor can be named using the `name` parameter. If `name` is not supplied it will use the name of the SharedImageSet.\n", "Additionally, we can tell Labels whether it should store a copy of the data in the project folder or not using the `own_copy` parameter.\n", "If the SharedImageSet was created on the fly (it will not have a DataSourceComponent) then this flag is ignored and Labels always saves a copy to the project folder." ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.043229Z", "start_time": "2024-09-19T07:34:15.993930Z" } }, "source": [ "# Let's create a circle and use that as a data sample\n", "side_length = 128\n", "radius = side_length // 4\n", "circle = np.zeros((side_length,side_length))\n", "Y, X = np.ogrid[:side_length, :side_length]\n", "dist_from_center = np.sqrt((X - side_length//2)**2 + (Y-side_length//2)**2)\n", "circle[dist_from_center <= radius] = 1\n", "\n", "# Now we create a SharedImageSet with the circle and add it to the project\n", "circle_sis = imfusion.SharedImageSet(circle[None, ..., None]) # We have to create a batch and a channel dimension\n", "descriptor = project.add_descriptor(circle_sis, name='Circle')\n", "\n", "# We can also load data from disk and add it to our project\n", "# To demonstrate this, we will first save our circle to disk and then load it back in:\n", "save_path = os.path.join(project.path, 'circle.imf')\n", "imfusion.save([circle_sis], save_path)\n", "circle_sis2, *_ = imfusion.load(save_path)\n", "circle_sis2.name = 'Circle-from-disk' # If we give the SIS a name we won't have to specify it when adding it as a descriptor\n", "descriptor2 = project.add_descriptor(circle_sis2)\n", "\n", "# Note that the two descriptors have different identifiers, since they are treated as separate entities\n", "# Also note that for the descriptor which we added from a numpy array `own_copy` was automatically set to True, meaning that labels saved a copy of the data in the project folder\n", "print(descriptor)\n", "print(descriptor2)" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[GUI.Animations] No global backend set: installing a dummy one.\n", "Descriptor(\n", "\tboundingbox_layers=BoundingBoxLayersAccessor(\n", "\t\tBoundingBoxLayer(id=QZcOWd9ZeKSw, name='Region of Interest', value=None)\n", "\t)\n", "\tbyte_size=65536\n", "\tcomments=''\n", "\tgrouping=[]\n", "\thas_data=False\n", "\theight=128\n", "\tidentifier='oWsRLnHFRuXn'\n", "\timport_time=1726731256011\n", "\tis_locked=False\n", "\tlabelmap_layers=LabelMapsAccessor(\n", "\t\tLabelMapLayer(id=p6mWrFYpzc8m, name='Segmentation', value=None)\n", "\t)\n", "\tlandmark_layers=LandmarkLayersAccessor(\n", "\t\tLandmarkLayer(id=dICO9aWsoPsO, name='Points of Interest', value=None)\n", "\t)\n", "\tlatest_edit_time=1726731256011\n", "\tload_path=('/data/imfusion/github/public-python-demos/test_project/data/oWsRLnHFRuXn.imf', '')\n", "\tmodality=\n", "\tn_channels=1\n", "\tn_images=1\n", "\tn_slices=1\n", "\tname='Circle'\n", "\toriginal_data_path=''\n", "\town_copy=True\n", "\tpatient_name=''\n", "\tproject=\n", "\tregion_of_interest=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\troi=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\tscale=1.0\n", "\tseries_instance_uid=''\n", "\tshift=0.0\n", "\tspacing=array([1., 1., 1.])\n", "\tsub_file_id=''\n", "\ttags=TagsAccessor(\n", "\t\tTag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255), value=False),\n", "\t\tTag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30), value=0.0),\n", "\t\tTag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'], value='default')\n", "\t)\n", "\tthumbnail_path='/data/imfusion/github/public-python-demos/test_project/thumbnails/data/oWsRLnHFRuXn.png'\n", "\ttop_down=True\n", "\ttype=\n", "\twidth=128)\n", "Descriptor(\n", "\tboundingbox_layers=BoundingBoxLayersAccessor(\n", "\t\tBoundingBoxLayer(id=QZcOWd9ZeKSw, name='Region of Interest', value=None)\n", "\t)\n", "\tbyte_size=65536\n", "\tcomments=''\n", "\tgrouping=[]\n", "\thas_data=False\n", "\theight=128\n", "\tidentifier='ayEssZSFPlal'\n", "\timport_time=1726731256036\n", "\tis_locked=False\n", "\tlabelmap_layers=LabelMapsAccessor(\n", "\t\tLabelMapLayer(id=p6mWrFYpzc8m, name='Segmentation', value=None)\n", "\t)\n", "\tlandmark_layers=LandmarkLayersAccessor(\n", "\t\tLandmarkLayer(id=dICO9aWsoPsO, name='Points of Interest', value=None)\n", "\t)\n", "\tlatest_edit_time=1726731256036\n", "\tload_path=('circle.imf', '')\n", "\tmodality=\n", "\tn_channels=1\n", "\tn_images=1\n", "\tn_slices=1\n", "\tname='Circle-from-disk'\n", "\toriginal_data_path='circle.imf'\n", "\town_copy=False\n", "\tpatient_name=''\n", "\tproject=\n", "\tregion_of_interest=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\troi=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\tscale=1.0\n", "\tseries_instance_uid=''\n", "\tshift=0.0\n", "\tspacing=array([1., 1., 1.])\n", "\tsub_file_id=''\n", "\ttags=TagsAccessor(\n", "\t\tTag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255), value=False),\n", "\t\tTag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30), value=0.0),\n", "\t\tTag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'], value='default')\n", "\t)\n", "\tthumbnail_path='/data/imfusion/github/public-python-demos/test_project/thumbnails/data/ayEssZSFPlal.png'\n", "\ttop_down=True\n", "\ttype=\n", "\twidth=128)\n" ] } ], "execution_count": 11 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.056152Z", "start_time": "2024-09-19T07:34:16.052101Z" } }, "source": [ "# We can now find the descriptors in the project using the `descriptor` attribute\n", "print(project.descriptors)" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Descriptor(\n", "\tboundingbox_layers=BoundingBoxLayersAccessor(\n", "\t\tBoundingBoxLayer(id=QZcOWd9ZeKSw, name='Region of Interest', value=None)\n", "\t)\n", "\tbyte_size=65536\n", "\tcomments=''\n", "\tgrouping=[]\n", "\thas_data=False\n", "\theight=128\n", "\tidentifier='oWsRLnHFRuXn'\n", "\timport_time=1726731256011\n", "\tis_locked=False\n", "\tlabelmap_layers=LabelMapsAccessor(\n", "\t\tLabelMapLayer(id=p6mWrFYpzc8m, name='Segmentation', value=None)\n", "\t)\n", "\tlandmark_layers=LandmarkLayersAccessor(\n", "\t\tLandmarkLayer(id=dICO9aWsoPsO, name='Points of Interest', value=None)\n", "\t)\n", "\tlatest_edit_time=1726731256011\n", "\tload_path=('/data/imfusion/github/public-python-demos/test_project/data/oWsRLnHFRuXn.imf', '')\n", "\tmodality=\n", "\tn_channels=1\n", "\tn_images=1\n", "\tn_slices=1\n", "\tname='Circle'\n", "\toriginal_data_path=''\n", "\town_copy=True\n", "\tpatient_name=''\n", "\tproject=\n", "\tregion_of_interest=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\troi=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\tscale=1.0\n", "\tseries_instance_uid=''\n", "\tshift=0.0\n", "\tspacing=array([1., 1., 1.])\n", "\tsub_file_id=''\n", "\ttags=TagsAccessor(\n", "\t\tTag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255), value=False),\n", "\t\tTag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30), value=0.0),\n", "\t\tTag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'], value='default')\n", "\t)\n", "\tthumbnail_path='/data/imfusion/github/public-python-demos/test_project/thumbnails/data/oWsRLnHFRuXn.png'\n", "\ttop_down=True\n", "\ttype=\n", "\twidth=128), Descriptor(\n", "\tboundingbox_layers=BoundingBoxLayersAccessor(\n", "\t\tBoundingBoxLayer(id=QZcOWd9ZeKSw, name='Region of Interest', value=None)\n", "\t)\n", "\tbyte_size=65536\n", "\tcomments=''\n", "\tgrouping=[]\n", "\thas_data=False\n", "\theight=128\n", "\tidentifier='ayEssZSFPlal'\n", "\timport_time=1726731256036\n", "\tis_locked=False\n", "\tlabelmap_layers=LabelMapsAccessor(\n", "\t\tLabelMapLayer(id=p6mWrFYpzc8m, name='Segmentation', value=None)\n", "\t)\n", "\tlandmark_layers=LandmarkLayersAccessor(\n", "\t\tLandmarkLayer(id=dICO9aWsoPsO, name='Points of Interest', value=None)\n", "\t)\n", "\tlatest_edit_time=1726731256036\n", "\tload_path=('circle.imf', '')\n", "\tmodality=\n", "\tn_channels=1\n", "\tn_images=1\n", "\tn_slices=1\n", "\tname='Circle-from-disk'\n", "\toriginal_data_path='circle.imf'\n", "\town_copy=False\n", "\tpatient_name=''\n", "\tproject=\n", "\tregion_of_interest=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\troi=(array([0, 0, 0], dtype=int32), array([0, 0, 0], dtype=int32))\n", "\tscale=1.0\n", "\tseries_instance_uid=''\n", "\tshift=0.0\n", "\tspacing=array([1., 1., 1.])\n", "\tsub_file_id=''\n", "\ttags=TagsAccessor(\n", "\t\tTag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255), value=False),\n", "\t\tTag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30), value=0.0),\n", "\t\tTag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'], value='default')\n", "\t)\n", "\tthumbnail_path='/data/imfusion/github/public-python-demos/test_project/thumbnails/data/ayEssZSFPlal.png'\n", "\ttop_down=True\n", "\ttype=\n", "\twidth=128)]\n" ] } ], "execution_count": 12 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.115979Z", "start_time": "2024-09-19T07:34:16.110650Z" } }, "source": [ "# We can also retrieve the image from the descriptor\n", "circle_from_descriptor = descriptor.load_image(crop_to_roi=False).numpy() # We don't have a roi on this descriptor but this argument can be used to either get the full or the cropped image\n", "assert np.allclose(circle, circle_from_descriptor.squeeze())" ], "outputs": [], "execution_count": 13 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "## Adding annotations to data\n", "\n", "While the project holds the definitions of tags and annotations, their values can only be changed when accessing them through a `Descriptor` instance." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "### Tags" ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.158450Z", "start_time": "2024-09-19T07:34:16.154084Z" } }, "source": [ "# Given our descriptor, we can set the values of its tags\n", "descriptor.tags['MyBool'] = True\n", "descriptor.tags['MyNumber'] = 9001\n", "descriptor.tags['MyChoices'] = 'B'\n", "\n", "# Since the tags now have non-default values they implicitly evaluate to True\n", "for tag in descriptor.tags:\n", "\tprint(tag)\n", "\tassert tag\n", "\n", "# We can reset the tags by setting them to the default values of their respective tag type\n", "descriptor.tags['MyBool'] = False\n", "descriptor.tags['MyNumber'] = 0\n", "descriptor.tags['MyChoices'] = 'default'\n", "\n", "# Now the tags will evaluate to False again\n", "for tag in descriptor.tags:\n", "\tprint(tag)\n", "\tassert not tag" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Tag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255), value=True)\n", "Tag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30), value=9001.0)\n", "Tag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'], value='B')\n", "Tag(name='MyBool', id='1v0gMQm8VH5w', kind=, color=(255, 255, 255), value=False)\n", "Tag(name='MyNumber', id='Yk4jntz9YmwX', kind=, color=(10, 20, 30), value=0.0)\n", "Tag(name='MyChoices', id='byOPycbPqM9o', kind=, color=(255, 255, 255), options=['default', 'A', 'B', 'C'], value='default')\n" ] } ], "execution_count": 14 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "### Annotations" ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.213440Z", "start_time": "2024-09-19T07:34:16.205409Z" } }, "source": [ "# Now we can add some annotation to our circle\n", "\n", "# First let's add a segmentation map of the pixels that belong to the circle\n", "# Note that this labelmap will be saved to disk once we call `save_new_data` (that's just how Labels currently works)\n", "labelmap = imfusion.SharedImageSet(circle.astype(np.uint8)[None, ..., None])\n", "descriptor.labelmap_layers['Segmentation'].save_new_data(labelmap)\n", "\n", "# And we can verify that it is indeed stored for this descriptor\n", "print(descriptor.labelmap_layers['Segmentation'].load())" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "imfusion.SharedImageSet(size: 1, [imfusion.SharedImage(UBYTE width: 128 height: 128)])\n" ] } ], "execution_count": 15 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.265072Z", "start_time": "2024-09-19T07:34:16.260535Z" } }, "source": [ "# We can also add a landmark at the center of the circle\n", "point_set = labels.LandmarkSet.from_descriptor(descriptor=descriptor, layer_name='Points of Interest')\n", "point_set.add(type='Center', frame=0, world=(0, 0, 0))\n", "\n", "descriptor.landmark_layers['Points of Interest'].save_new_data(point_set)\n", "\n", "print(descriptor.landmark_layers['Points of Interest'].load().asdict())" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Center': {0: [(0.0, 0.0, 0.0)]}}\n" ] } ], "execution_count": 16 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.319383Z", "start_time": "2024-09-19T07:34:16.314527Z" } }, "source": [ "# Finally let's add a box around the circle\n", "# It works analogous to adding a landmark\n", "box_set = labels.BoxSet.from_descriptor(descriptor=descriptor, layer_name='Region of Interest')\n", "box_set.add(type='Containing Box', frame=0, top_left=(-32, -32, 1), lower_right=(32, 32, 1))\n", "\n", "descriptor.boundingbox_layers['Region of Interest'].save_new_data(box_set)\n", "\n", "print(descriptor.boundingbox_layers['Region of Interest'].load().asdict())" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Containing Box': {0: [((-32.0, -32.0, 1.0), (32.0, 32.0, 1.0))]}}\n" ] } ], "execution_count": 17 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "## Saving the project\n", "\n", "Almost all actions we perform on the `Project` instance are held in memory. The only exception is when we add data to the annotation layers of a descriptor as it is written to disk immediately. To save the modifications on the project we have to call its `save` method" ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.373316Z", "start_time": "2024-09-19T07:34:16.368537Z" } }, "source": [ "# Here we save all the changes we made to the project to disk\n", "project.save()\n", "\n", "# You can now open the project through the Labels GUI and verify that everything is as you expect it to be" ], "outputs": [], "execution_count": 18 }, { "cell_type": "markdown", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "## Loading the project" ] }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.429032Z", "start_time": "2024-09-19T07:34:16.423426Z" } }, "source": [ "# Let's load the project back in and verify that everything is still there\n", "loaded_project = labels.Project.load(project.path)\n", "\n", "for loaded, expected in zip(loaded_project.descriptors, project.descriptors):\n", "\tassert loaded.name == expected.name\n", "\tassert loaded.identifier == expected.identifier\n", "\n", "for loaded, expected in zip(loaded_project.tags, project.tags):\n", "\tassert loaded.name == expected.name\n", "\tassert loaded.kind == expected.kind" ], "outputs": [], "execution_count": 19 }, { "cell_type": "code", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "ExecuteTime": { "end_time": "2024-09-19T07:34:16.483684Z", "start_time": "2024-09-19T07:34:16.473564Z" } }, "source": [ "# We can modify the loaded project and then save it back.\n", "# For example let's add a new label and modify the labelmap of one of the circle descriptors\n", "\n", "# Instead of calling `Project.save()` at the end, you can also use a context manager to automatically save the project when exiting the context\n", "with loaded_project:\n", "\n", "\t# We can also modify the annotation definition from the descriptor\n", "\tdescriptor = loaded_project.descriptors[0]\n", "\tlabelmap_layer = descriptor.labelmap_layers['Segmentation']\n", "\tlabelmap_layer.add_annotation(name='Core', value=2)\n", "\n", "\t# Let's load the label map and modify it\n", "\tlabelmap = labelmap_layer.load().numpy().squeeze()\n", "\tlabelmap[dist_from_center < radius//2] = 2\n", "\n", "\t# Now we can write the labelmap back to disk\n", "\tlabelmap_layer.save_new_data(imfusion.SharedImageSet(labelmap[None, ..., None]))" ], "outputs": [], "execution_count": 20 } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.10" } }, "nbformat": 4, "nbformat_minor": 4 }