{"id":18908951,"url":"https://github.com/bitdefender/dicom3d","last_synced_at":"2025-04-15T05:32:20.746Z","repository":{"id":62568086,"uuid":"221014934","full_name":"bitdefender/dicom3d","owner":"bitdefender","description":"Library to help reconstructing sliced section images from volumetric medical scans","archived":false,"fork":false,"pushed_at":"2023-04-07T10:34:47.000Z","size":708,"stargazers_count":8,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-07T22:19:07.511Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bitdefender.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-11T15:48:06.000Z","updated_at":"2024-07-15T05:02:57.000Z","dependencies_parsed_at":"2022-11-03T16:30:36.270Z","dependency_job_id":null,"html_url":"https://github.com/bitdefender/dicom3d","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitdefender%2Fdicom3d","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitdefender%2Fdicom3d/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitdefender%2Fdicom3d/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitdefender%2Fdicom3d/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bitdefender","download_url":"https://codeload.github.com/bitdefender/dicom3d/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223624352,"owners_count":17175191,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T09:29:48.404Z","updated_at":"2024-11-08T09:29:48.937Z","avatar_url":"https://github.com/bitdefender.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"About\n========\n\n**dicom3d** is a comprehensive Python package for reconstructing arbitrary defined 3D sections \nfrom volumetric medical scans (CTs or RMNs), scale accurate and with a builtin pixel-to-world \ncoordinate mapping system.\n\nIt comes preloaded with the necessary mathematical backend to manipulate space information from \nmedical scans, and provides a mapping system that can transparently handle different imaging \nproperties such as pixel density, dataset thickness, patient orientation, patient positioning, \netc.\n\nDependencies\n------------\nIt relies on **pydicom** for loading DICOM medical images and **numpy** for array manipulation.\n\nDocumentation\n-------------\n\nDocumentation is available on [dicom3d.readthedocs.org](https://dicom3d.readthedocs.org).\n\nInstallation\n=============\n\nFrom PyPi package repository:\n\n    pip install dicom3d\n\nQuick start\n===========\n\nA quick way of understanding **dicom3d** is to explore its built-in examples.\n\nTo run them, execute the **dicom3d.examples** utility after installing the package:\n\n```\n$ python3 -m dicom3d.examples\n[ 1] - dicomdir/loading.py\n[ 2] - section/planar.py\n[ 3] - section/basic.py\n[ 4] - section/translation.py\n[ 5] - section/make_gif.py\n[ 6] - section/rotation.py\n[ 7] - geometry/vectors.py\n[ 8] - geometry/planes.py\n[ 9] - geometry/basic.py\nSelect example [1-9]:\n```\n\nSome of the examples will require a volumetric scan to work with, stored in a folder with \nmultiple *.DCM* files. You can download many free CT or RMN scans from \n[canerimagingarchive.net](https://nbia.cancerimagingarchive.net)\n\nWhen you have them, navigate to the folder you downloaded them and run **dicom3d.examples** \nutility. The utility will look for those scans starting from the current working directory.\n\nFor example, to run the **basic.py** from **dicom3d**:\n\n```\n\u003e$ cd dicom_data/test_files\n\u003e$ python3 -m dicom3d.examples section/basic.py\n\n===-----------------------------------------------------===\n |                   BASIC SECTIONS                      |\n===-----------------------------------------------------===\n\n    This example shows how to create basic sections from\n    a volumetric scan.\n\n    It will create the sagittal, axial and coronal medical\n    sections form the selected volumetric scan.\n\n    The volumetric scan must a folder of .DCM files located\n    in the current working directory.\n\n    Each section image is plotted using matplotlib.\n\n===-----------------------------------------------------===\n\nSelect a folder containing a list of '.dcm' files:\n[ 1] - dicomdir_1\n[ 2] - series_ct_lung_3\n[ 3] - series_ct_lung_2\n[ 4] - series_ct_heart_1\n[ 5] - series_brain_1\n[ 6] - series_ct_heart_2\n[ 7] - series_ct_lung_1\n[ 8] - series_brain_2\nSelect series [1-8]:\n```\n\nAfter selecting one series, the example will slice the volumetric scan and plot the \nresulted *axial*, *sagittal* and *coronal* sections.\n\n```\nLoading series from:  series_ct_lung_2\n---\nOrigin: X:-9.65 Y:-158.65 Z:260.00\nAxial    plane: 0.00X + 0.00Y + 1.00Z = 260.00 DPI: 17.86\nCoronal  plane: 1.00X + 0.00Y + 0.00Z = -9.65\nSagittal plane: 0.00X + 1.00Y + 0.00Z = -158.65\n---\nCreating axial image..done.\nCreating sagittal image..done.\nCreating coronal image..done.\n---\nPlotting..\n```\n\n![Sections Image](dicom3d/examples/images/axial_sagital_coronal.png)\n\nBasics\n------\n\nlet's now see how the **basic.py** example is built, step by step.\n\nUsing **dicom3d** does not require a heavy prior-art in geometry. For defining sections, \nyou need to work with planes, vectors, and three-dimensional coordinates, but **dicom3d** \ncan help you build those relatively to existing geometry.\n\n### Defining a plane\n\nFirst, let's define section planes:\n\n```python\naxial    = d3d.Plane.from_axes(\"xy\")\ncoronal  = d3d.Plane.from_axes(\"xz\")\nsagittal = d3d.Plane.from_axes(\"yz\")\n```\n\nWe start from the basic **OXY**, **OXZ** and **OYZ** planes. These are the planes parallel to \nthe medical *axial*, *coronal* and *sagittal* section planes respectively. They describe the \nbottom, left and front planes of the volumetric scan. To become the medical planes their name \nsuggests, we need to translate them to the center of the volumetric scan. \n\nSimplest way to get the scan's center point is:\n\n```python\nmiddle_dataset = series.middle()\norigin = middle_dataset.center()\n\n# this will not print a tuple as excepted, but\n# it will print the string representation of\n# dicom3d.geometry.Point object\nprint(\"Origin is: %s\" % (origin))\n```\n\nThis code gets the dataset in the middle of the scan's Z segment and then its corresponding \ngeometric center in world coordinates.\n\nAnd now, to make the translation:\n\n```python\n# note: no operation on planes, vectors or points\n# will alter the source object, rather it will create a copy \naxial    = axial.move(origin)\ncoronal  = coronal.move(origin)\nsagittal = coronal.move(origin)\n\nprint(\"Axial: %s Coronal: %s Sagittal: %s\" % (axial, coronal sagittal))\n```\n\nAccording to the DICOM standard, these are now the mathematical plane definitions for the \ncorresponding medical planes *axial*, *sagittal* and *coronal*.\n\n### Constructing a section\n\nThe last step is to build the actual section. For this, we need a point to work as a center \nand a plane. The sliced section will be reconstructed around this point. For example, a \nsection of 128x128 pixels will extend from (-64,-64) to (64,64) in the local coordinate \nsystem, with the point (0,0) mapped to the given origin, in world coordinates.\n\n```python\nsection_axial    = d3d.Section.from_plane(series, axial   , origin)\nsection_coronal  = d3d.Section.from_plane(series, coronal , origin)\nsection_sagittal = d3d.Section.from_plane(series, sagittal, origin)\n```\n\n### Wrap-up\n\nPutting it all together, we should end up with this:\n\n```python\nfrom dicom3d.data import select_file\nimport dicom3d as d3d\n\ndef select_series_path():\n    print(\"Select a folder containing a list of '.dcm' files:\")\n    return select_file(\n\t\tbase_dir = os.path.abspath('.'), \n\t\tpattern = \"*\",\n\t\tsearch_files = False,\n\t\tsearch_dirs = True,\n\t\tselection_name = \"series\" )\n\n# retrieve a series sample, asuming your CT/RMN scans \n# are located in the current working directory\npath   = select_series_path()\nseries = d3d.series.from_directory(path)\n\n# get scan center\norigin = series.middle().center()\n\n# build medical sections\naxial    = d3d.Plane.from_axes(\"xy\").move(origin)\ncoronal  = d3d.Plane.from_axes(\"xz\").move(origin)\nsagittal = d3d.Plane.from_axes(\"yz\").move(origin)\n\n# construct sections\nsection_axial    = d3d.Section.from_plane(series, axial   , origin)\nsection_coronal  = d3d.Section.from_plane(series, coronal , origin)\nsection_sagittal = d3d.Section.from_plane(series, sagittal, origin)\n\n# size of the resulting image will be 256 by 256 pixels\nsize = (256,256)\nimg_axial    = section_axial.image(size)\nimg_coronal  = section_coronal.image(size)\nimg_sagittal = section_sagittal.image(size)\n```\n\nThat's it! We can now plot the **numpy** images using **matplotlib**.\n\n### Dimensions\n\nDefining the size of a section in pixels has the disadvantage of not being \"portable\" across\ndifferent DICOM datasets, if their *PixelSpacing* varies from one to another. \n\nFor example, a section with 100x100 pixels can map to 100x100 millimeters if the DICOM has the \n*PixelSpacing* attribute set to (1,1). The same size can translate to 50x50 millimeters if the \n*PixelSpacing* attribute is (0.5, 0.5), half the previous.  \n\nTo handle this, **dicom3d** will calculate the distance in millimeters and convert it to pixels, \nwhen you want to construct section images.\n\nTo do that, you just need to pass *floats* instead of *integers*:\n\n```python\n# this image will be 30 mm in width and 200 mm in height\nsize = (30.0, 200.0)\nimg_coronal = section_coronal.image(size)\n```\n\nLocal Coordinate System\n------------------------\n\n**dicom3d** attaches to each dataset, a **dicom3d.LocalCoordinateSystem** object for \nmapping pixel information, from the cartesian/local space (x,y), to world cooridnates measured \nin millimeters. Sections created via **dicom3d** have the same mapping feature, therefore to know \nwhere local point (-10,-10) lies in world coordinates, you could use:\n\n```python\n# transform the given local point, in respect \n# to section's origin, to world coordinates\nx,y,z = section_coronal.to_mm(-10,-10)\n\n# we should get back the local point -10,-10\nx,y = section_coronal.to_local((x,y,z))\n```\n\nSame for datasets:\n\n```python\n# get a 'random' dataset\ndataset = series.first()\n\n# transfrom local to world and viceversa\nx,y,z = dataset.to_mm(-10,-10)\nx,y = dataset.to_local((x,y,z))\n```\n\n### Rotation, translation and scaling\n\nThe **dicom3d.LocalCoordinateSystem** has built-in support for these operations, therefore to \nrotate the LCS around a given axis, you can write:\n\n```python\n    # rotate the LCS around Z axis by 30 degrees\n    section.transform = \\\n        section.transform.rotate(\"z\", d3d.radians(30))\n```\n\nOr to translate it on a given axis:\n\n```python\n    # move LCS on the Z axis by 100\n    section.transform = section.transform.move(\n        d3d.Vector.from_axis(\"z\"), 100.0)\n```\n\nTo modify its scaling factor:\n\n```python\n    # the scaling factor is divided by 2\n    section.transform = section.transform.scale(0.5)\n```\n\nDirect alteration of a *Local Coordinate System* is also possible, if needed. The only requirement \nis to call its **update** function after modifying internal parameters, to calculate the internal \ntransformation matrix.\n\n```python\n    # swap X and Y axis\n    x_axis = section.transform.x_vector\n    section.transform.x_vector = section.transform.y_vector\n    section.transform_y_vector = x_axis\n    section.transform.update() \n```\n\nPlanar datasets\n---------------\n\nIf you are not interested in building sections for volumetric 3D scans and you work only with \nplanar DICOM datasets, you can still use **dicom3d** for its mapping system of pixel to world \ncoordinates or for creating translated, rotated or scaled planar sections.\n\nFor taking measurements using the cartesian mapping, you can use a pseudo-series of **dicom3d** \ndatasets, as follows:\n\n```python\n    import dicom3d as d3d\n    import pydicom\n    from pydicom.data import get_testdata_files\n\n    def getMyDataset():\n        files = get_testdata_files(\"MR_small_bigendian.dcm\")\n        print(\"Loading dataset from: \", files[0])\n        return pydicom.dcmread(files[0])\n        \n    # assuming you have a function returning sa pydicom dataset\n    dataset = getMyDataset()\n\n    # construct a section from a single dataset\n    planar_section = d3d.Section.from_dataset(dataset)\n\n    # get wrapped dicom3d.Dataset class\n    dataset = planar_section.series.first()\n    origin = dataset.center()\n\n    # navigate through dataset\n    x,y,z = dataset.to_mm(0,0)\n    print(\"Pixel at (0,0) is at X:%.2f Y:%.2f Z:%.2f\" % (\n        x,y,z))\n\n    x,y,z = dataset.to_mm(dataset.Columns,dataset.Rows)\n    print(\"Pixel at (%d,%d) is at X:%.2f Y:%.2f Z:%.2f\" % (\n        dataset.Columns, dataset.Rows, x,y,z))\n```\n\nAnd now you may get the corresponding numpy image of the section:\n\n```python\n    # extract an image of 96x96 pixels containing dataset center\n    width, height = 96, 96\n    img = planar_section.image((width,height))\n```\n\nNow that we have a **dicom3d.Section** for the planar dataset that we can use it to produce\nan image, rotated, translated or scaled or to use its coordinating system to map pixels\nto world coordinates, like this:\n\n```python\n    # top left and bottom right pixel coordinates\n    topleft = planar_section.to_mm(-width/2, -height/2)\n    btmright = planar_section.to_mm(width/2, height/2)\n\n    # center coordinates\n    center = planar_section.to_mm(0,0)\n\n    # because datasets are parallel to OXY, we can ignore the Z coordinate\n    xs,ys,_ = topleft\n    xe,ye,_ = topright\n    ox,oy,oz = center\n\n    print(\"The %d x %d pixels section it's %.2fmm x %.2fmm in size\" % (\n        width, height,\n        abs(xe-xs), abs(ye-ys)\n    ))\n\n    print(\"Center of section is at X:%.2f Y:%.2f Z:%.2f\" % (\n        ox, oy, oz\n    ))\n```\n\nIf you want to run this example, run:\n\n    python3 -m dicom3d.examples section/planar.py\n\nAnd you will should see the following images plotted:\n\n![Sections Image](dicom3d/examples/images/planar_rotate_translate.png)\n\nFor the scaled sections: \n\n![Sections Image](dicom3d/examples/images/density.png)\n\nRoadmap\n=======\nFeatures we expect to develop in the near future for **dicom3d**:\n- [ ] performance improvement for section reconstruction\n- [ ] depth-information for sections\n- [ ] a set of predefined, configurable image filters for segmenting information after creating a section\n- [ ] overlaying information over reconstructed sections \n\nLicense\n=======\nLicence for this Python package is the **MIT License** under **Copyright (c) 2020 Bitdefender**.\n\nAuthor\n======\nThis library was developed by *Alex Mircescu* (`amircescu`@`bitdefender.com`) for an experimental \nmachine learning application conducted by *Florin Gogianu* (`fgogianu`@`bitdefender.com`).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitdefender%2Fdicom3d","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbitdefender%2Fdicom3d","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitdefender%2Fdicom3d/lists"}