From a3432106fd5f14ae1593b253570b3b98df8677d9 Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Wed, 1 Jan 2025 18:38:03 -0500 Subject: [PATCH 1/8] Update pydicom supported to >=2.4..0 * fix DVH repr problems with 'np.int(...) repr' * collections.Callable -> collections.abc.Callable --- HISTORY.rst | 1 + README.rst | 2 +- dicompylercore/dicomparser.py | 12 ++++-------- dicompylercore/dvh.py | 17 +++++++---------- dicompylercore/util.py | 2 +- docs/conf.py | 2 +- setup.py | 2 +- tests/test_dicomparser.py | 8 ++------ tests/test_dose.py | 11 +++-------- tests/test_dvhcalc.py | 8 ++------ 10 files changed, 23 insertions(+), 42 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 748f996..c29cda0 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,7 @@ History 0.5.7 (unreleased) ------------------ - Dropped support for Python 2. +- pydicom 3.X supported (requirement now >= pydicom 2.4.0) 0.5.6 (2023-05-08) ------------------ diff --git a/README.rst b/README.rst index bed3b00..970ee7f 100644 --- a/README.rst +++ b/README.rst @@ -22,7 +22,7 @@ Dependencies ------------ - `numpy `__ 1.2 or higher -- `pydicom `__ 0.9.9 or higher (pydicom 1.0 compatible) +- `pydicom `__ 2.4.0 or higher - `matplotlib `__ 1.3.0 or higher (for DVH calculation) - Optional: diff --git a/dicompylercore/dicomparser.py b/dicompylercore/dicomparser.py index 5f92eb7..d38481e 100755 --- a/dicompylercore/dicomparser.py +++ b/dicompylercore/dicomparser.py @@ -11,13 +11,9 @@ import logging import numpy as np -try: - from pydicom.dicomio import read_file - from pydicom.dataset import Dataset, validate_file_meta - from pydicom.pixel_data_handlers.util import pixel_dtype -except ImportError: - from dicom import read_file - from dicom.dataset import Dataset +from pydicom.dicomio import dcmread +from pydicom.dataset import Dataset, validate_file_meta +from pydicom.pixel_data_handlers.util import pixel_dtype import random from numbers import Number from io import BytesIO @@ -59,7 +55,7 @@ def __init__(self, dataset, memmap_pixel_array=False): elif isinstance(dataset, (str, BytesIO, Path)): try: with open(dataset, "rb") as fp: - self.ds = read_file(fp, defer_size=100, force=True, + self.ds = dcmread(fp, defer_size=100, force=True, stop_before_pixels=memmap_pixel_array) if memmap_pixel_array: self.offset = fp.tell() + 8 diff --git a/dicompylercore/dvh.py b/dicompylercore/dvh.py index cbebe13..39e56c3 100644 --- a/dicompylercore/dvh.py +++ b/dicompylercore/dvh.py @@ -108,16 +108,13 @@ def from_data(cls, data, binsize=1): def __repr__(self): """String representation of the class.""" - return 'DVH(%s, %r bins: [%r:%r] %s, volume: %r %s, name: %r, ' \ - 'rx_dose: %d %s%s)' % \ - (self.dvh_type, self.counts.size, self.bins.min(), - self.bins.max(), self.dose_units, - self.volume, self.volume_units, - self.name, - 0 if not self.rx_dose else self.rx_dose, - self.dose_units, - ', *Notes: ' + self.notes if self.notes else '') - + return ( + f'DVH({self.dvh_type}, {self.counts.size} bins: ' + f'[{self.bins.min()}:{self.bins.max()}] {self.dose_units}, ' + f'volume: {self.volume} {self.volume_units}, name: {self.name}, ' + f'rx_dose: {0 if not self.rx_dose else self.rx_dose} ' + f'{self.dose_units}{", *Notes: " + self.notes if self.notes else ""})' + ) def __eq__(self, other): """Comparison method between two DVH objects. diff --git a/dicompylercore/util.py b/dicompylercore/util.py index 33f4129..cca6cfe 100755 --- a/dicompylercore/util.py +++ b/dicompylercore/util.py @@ -132,7 +132,7 @@ def piecewise(x, condlist, funclist, *args, **kw): y = np.zeros(x.shape, x.dtype) for k in range(n): item = funclist[k] - if not isinstance(item, collections.Callable): + if not isinstance(item, collections.abc.Callable): y[condlist[k]] = item else: vals = x[condlist[k]] diff --git a/docs/conf.py b/docs/conf.py index 2660b01..38b475b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,7 @@ 'sphinx.ext.napoleon'] autodoc_mock_imports = [ - 'numpy', 'dicom', 'pydicom', 'pydicom', 'dicom', + 'numpy', 'dicom', 'pydicom', 'PIL', 'numpy.core', 'matplotlib', 'skimage', 'scipy'] autodoc_member_order = 'bysource' diff --git a/setup.py b/setup.py index e6102f3..1708669 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ include_package_data=True, install_requires=[ "numpy>=1.2", - "pydicom>=0.9.9", + "pydicom>=2.4.0,<4", "matplotlib>=1.3.0" ], extras_require={ diff --git a/tests/test_dicomparser.py b/tests/test_dicomparser.py index 0733813..b25c064 100644 --- a/tests/test_dicomparser.py +++ b/tests/test_dicomparser.py @@ -9,12 +9,8 @@ import os from dicompylercore import dicomparser from dicompylercore.config import pil_available, shapely_available -try: - from pydicom.multival import MultiValue as mv - from pydicom.valuerep import DSfloat -except ImportError: - from dicom.multival import MultiValue as mv - from dicom.valuerep import DSfloat +from pydicom.multival import MultiValue as mv +from pydicom.valuerep import DSfloat from numpy import array, arange from numpy.testing import assert_array_equal, assert_array_almost_equal diff --git a/tests/test_dose.py b/tests/test_dose.py index 0e020df..3564f88 100644 --- a/tests/test_dose.py +++ b/tests/test_dose.py @@ -10,14 +10,9 @@ import os from dicompylercore import dicomparser, dose -try: - from pydicom.dataset import Dataset - from pydicom.sequence import Sequence - from pydicom import read_file as read_dicom -except ImportError: - from dicom.dataset import Dataset - from dicom.sequence import Sequence - from dicom import read_file as read_dicom +from pydicom.dataset import Dataset +from pydicom.sequence import Sequence +from pydicom import dcmread as read_dicom from numpy import arange, zeros from numpy.testing import ( assert_array_almost_equal, diff --git a/tests/test_dvhcalc.py b/tests/test_dvhcalc.py index f66c464..224342b 100644 --- a/tests/test_dvhcalc.py +++ b/tests/test_dvhcalc.py @@ -10,12 +10,8 @@ from dicompylercore.config import skimage_available from dicompylercore.dvh import DVH from dicompylercore.dvhcalc import get_dvh -try: - from pydicom.dataset import Dataset - from pydicom.sequence import Sequence -except ImportError: - from dicom.dataset import Dataset - from dicom.sequence import Sequence +from pydicom.dataset import Dataset +from pydicom.sequence import Sequence from numpy import arange from numpy.testing import assert_allclose from .util import fake_rtdose, fake_ss From 690eebcf6858e454616455b4d75f4085f6289ac7 Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Wed, 1 Jan 2025 23:28:11 -0500 Subject: [PATCH 2/8] Resolve fixing of file_meta info --- dicompylercore/dicomparser.py | 40 +++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/dicompylercore/dicomparser.py b/dicompylercore/dicomparser.py index d38481e..5e10229 100755 --- a/dicompylercore/dicomparser.py +++ b/dicompylercore/dicomparser.py @@ -14,6 +14,7 @@ from pydicom.dicomio import dcmread from pydicom.dataset import Dataset, validate_file_meta from pydicom.pixel_data_handlers.util import pixel_dtype +from pydicom.uid import ImplicitVRLittleEndian, ExplicitVRBigEndian import random from numbers import Number from io import BytesIO @@ -29,6 +30,39 @@ logger = logging.getLogger('dicompylercore.dicomparser') +def _fix_meta_info(ds: Dataset) -> None: + """Ensure the file meta info exists and has the correct values + for transfer syntax and media storage UIDs. + + Copied from pydicom 2.4 and edited + + .. warning:: + + The transfer syntax for ``is_implicit_VR = False`` and + ``is_little_endian = True`` is ambiguous and will therefore not + be set. + + Parameters + ---------- + ds: pydicom Dataset + + """ + ds.ensure_file_meta() + + if ds.is_little_endian and ds.is_implicit_VR: + ds.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian + elif not ds.is_little_endian and not ds.is_implicit_VR: + ds.file_meta.TransferSyntaxUID = ExplicitVRBigEndian + elif not ds.is_little_endian and ds.is_implicit_VR: + raise NotImplementedError("Implicit VR Big Endian is not a " + "supported Transfer Syntax.") + + if 'SOPClassUID' in ds: + ds.file_meta.MediaStorageSOPClassUID = ds.SOPClassUID + if 'SOPInstanceUID' in ds: + ds.file_meta.MediaStorageSOPInstanceUID = ds.SOPInstanceUID + + class DicomParser: """Class to parse DICOM / DICOM RT files.""" @@ -74,12 +108,14 @@ def __init__(self, dataset, memmap_pixel_array=False): raise AttributeError # Fix dataset file_meta if incorrect + self.ds.ensure_file_meta() try: validate_file_meta(self.ds.file_meta) - except ValueError: + except (AttributeError, ValueError): logger.debug('Fixing invalid File Meta for ' + str(self.ds.SOPInstanceUID)) - self.ds.fix_meta_info() + _fix_meta_info(self.ds) + validate_file_meta(self.ds.file_meta) # Remove the PixelData attribute if it is not set. # i.e. RTStruct does not contain PixelData and its presence can confuse From 8cb2e37818a757fe7c4f0a49c807a8ba4c5ff8e2 Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Thu, 2 Jan 2025 00:05:06 -0500 Subject: [PATCH 3/8] Update workflows for testing pydicom 2.4, 3.X * update some of the actions script versions --- .github/workflows/build.yml | 18 +++++++++++++----- .github/workflows/codacy.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/dependency-review.yml | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4dbf66a..d0f0bdb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,19 +13,22 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] - + python-version: ["3.10", "3.11", "3.12", "3.13"] + pydicom-version: ["latest"] + include: + - python-version: "3.10" + pydicom-version: "2.4" steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Cache Python dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ hashFiles('setup.py') }} @@ -37,6 +40,11 @@ jobs: pip install coverage pip install coveralls pip install codecov + + - name: Force pydicom version if needed + if: startsWith(matrix.pydicom-version, '2') + run: | + pip install -U pydicom==${{ matrix.pydicom-version }} - name: Run tests via coverage run: | diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml index f7df36d..2a21301 100644 --- a/.github/workflows/codacy.yml +++ b/.github/workflows/codacy.yml @@ -36,7 +36,7 @@ jobs: steps: # Checkout the repository to the GitHub Actions runner - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fcd7c19..00d75ae 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b0dedc4..0d4a013 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 From b18a9328a6a9e927939034f6d752c1edf147e33e Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Thu, 2 Jan 2025 10:12:24 -0500 Subject: [PATCH 4/8] Fix style and import issues --- dicompylercore/dicomparser.py | 35 ++++++++++++++++++----------------- dicompylercore/dvh.py | 3 ++- tests/test_dicomparser.py | 4 ++-- tests/test_dose.py | 4 +--- tests/test_dvhcalc.py | 8 ++++---- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/dicompylercore/dicomparser.py b/dicompylercore/dicomparser.py index 5e10229..2e4a0ac 100755 --- a/dicompylercore/dicomparser.py +++ b/dicompylercore/dicomparser.py @@ -30,7 +30,7 @@ logger = logging.getLogger('dicompylercore.dicomparser') -def _fix_meta_info(ds: Dataset) -> None: +def _fix_meta_info(dataset: Dataset) -> None: """Ensure the file meta info exists and has the correct values for transfer syntax and media storage UIDs. @@ -44,24 +44,25 @@ def _fix_meta_info(ds: Dataset) -> None: Parameters ---------- - ds: pydicom Dataset + dataset: pydicom Dataset """ - ds.ensure_file_meta() - - if ds.is_little_endian and ds.is_implicit_VR: - ds.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian - elif not ds.is_little_endian and not ds.is_implicit_VR: - ds.file_meta.TransferSyntaxUID = ExplicitVRBigEndian - elif not ds.is_little_endian and ds.is_implicit_VR: - raise NotImplementedError("Implicit VR Big Endian is not a " - "supported Transfer Syntax.") - - if 'SOPClassUID' in ds: - ds.file_meta.MediaStorageSOPClassUID = ds.SOPClassUID - if 'SOPInstanceUID' in ds: - ds.file_meta.MediaStorageSOPInstanceUID = ds.SOPInstanceUID - + dataset.ensure_file_meta() + + if dataset.is_little_endian and dataset.is_implicit_VR: + dataset.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian + elif not dataset.is_little_endian and not dataset.is_implicit_VR: + dataset.file_meta.TransferSyntaxUID = ExplicitVRBigEndian + elif not dataset.is_little_endian and dataset.is_implicit_VR: + raise NotImplementedError( + "Implicit VR Big Endian is not a supported Transfer Syntax." + ) + + if 'SOPClassUID' in dataset: + dataset.file_meta.MediaStorageSOPClassUID = dataset.SOPClassUID + if 'SOPInstanceUID' in dataset: + dataset.file_meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID + class DicomParser: """Class to parse DICOM / DICOM RT files.""" diff --git a/dicompylercore/dvh.py b/dicompylercore/dvh.py index 39e56c3..65d866e 100644 --- a/dicompylercore/dvh.py +++ b/dicompylercore/dvh.py @@ -113,7 +113,8 @@ def __repr__(self): f'[{self.bins.min()}:{self.bins.max()}] {self.dose_units}, ' f'volume: {self.volume} {self.volume_units}, name: {self.name}, ' f'rx_dose: {0 if not self.rx_dose else self.rx_dose} ' - f'{self.dose_units}{", *Notes: " + self.notes if self.notes else ""})' + f'{self.dose_units}" + f'{", *Notes: " + self.notes if self.notes else ""}' ) def __eq__(self, other): """Comparison method between two DVH objects. diff --git a/tests/test_dicomparser.py b/tests/test_dicomparser.py index b25c064..f90f04a 100644 --- a/tests/test_dicomparser.py +++ b/tests/test_dicomparser.py @@ -7,12 +7,12 @@ import unittest import os -from dicompylercore import dicomparser -from dicompylercore.config import pil_available, shapely_available from pydicom.multival import MultiValue as mv from pydicom.valuerep import DSfloat from numpy import array, arange from numpy.testing import assert_array_equal, assert_array_almost_equal +from dicompylercore import dicomparser +from dicompylercore.config import pil_available, shapely_available basedata_dir = "tests/testdata" example_data = os.path.join(basedata_dir, "example_data") diff --git a/tests/test_dose.py b/tests/test_dose.py index 3564f88..622afc8 100644 --- a/tests/test_dose.py +++ b/tests/test_dose.py @@ -8,10 +8,7 @@ import unittest import os -from dicompylercore import dicomparser, dose -from pydicom.dataset import Dataset -from pydicom.sequence import Sequence from pydicom import dcmread as read_dicom from numpy import arange, zeros from numpy.testing import ( @@ -21,6 +18,7 @@ ) import warnings from dicompylercore.config import mpl_available, scipy_available +from dicompylercore import dicomparser, dose basedata_dir = "tests/testdata" example_data = os.path.join(basedata_dir, "example_data") diff --git a/tests/test_dvhcalc.py b/tests/test_dvhcalc.py index 224342b..f7e71bf 100644 --- a/tests/test_dvhcalc.py +++ b/tests/test_dvhcalc.py @@ -6,15 +6,15 @@ import unittest import os -from dicompylercore import dicomparser, dvhcalc -from dicompylercore.config import skimage_available -from dicompylercore.dvh import DVH -from dicompylercore.dvhcalc import get_dvh from pydicom.dataset import Dataset from pydicom.sequence import Sequence from numpy import arange from numpy.testing import assert_allclose from .util import fake_rtdose, fake_ss +from dicompylercore import dicomparser, dvhcalc +from dicompylercore.config import skimage_available +from dicompylercore.dvh import DVH +from dicompylercore.dvhcalc import get_dvh basedata_dir = "tests/testdata" From e7f9bb84da713d4b55b08aef8021907abe68dc24 Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Thu, 2 Jan 2025 10:17:32 -0500 Subject: [PATCH 5/8] Fix syntax --- dicompylercore/dvh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dicompylercore/dvh.py b/dicompylercore/dvh.py index 65d866e..9c31e6d 100644 --- a/dicompylercore/dvh.py +++ b/dicompylercore/dvh.py @@ -113,8 +113,8 @@ def __repr__(self): f'[{self.bins.min()}:{self.bins.max()}] {self.dose_units}, ' f'volume: {self.volume} {self.volume_units}, name: {self.name}, ' f'rx_dose: {0 if not self.rx_dose else self.rx_dose} ' - f'{self.dose_units}" - f'{", *Notes: " + self.notes if self.notes else ""}' + f'{self.dose_units}' + f'{", *Notes: " + self.notes if self.notes else ""})' ) def __eq__(self, other): """Comparison method between two DVH objects. From 9ed6ac8e35f3ef33dd7a146b8ce8fa26759587c4 Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Thu, 2 Jan 2025 15:30:13 -0500 Subject: [PATCH 6/8] Update Python versions in setup.py * make pydicom versions in build name more obvious --- .github/workflows/build.yml | 6 +++--- setup.py | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0f0bdb..4635775 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,10 @@ jobs: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] - pydicom-version: ["latest"] + pydicom-version: ["pydicom-latest"] include: - python-version: "3.10" - pydicom-version: "2.4" + pydicom-version: "pydicom~=2.4.4" steps: - name: Checkout repository uses: actions/checkout@v4 @@ -44,7 +44,7 @@ jobs: - name: Force pydicom version if needed if: startsWith(matrix.pydicom-version, '2') run: | - pip install -U pydicom==${{ matrix.pydicom-version }} + pip install -U "${{ matrix.pydicom-version }}" - name: Run tests via coverage run: | diff --git a/setup.py b/setup.py index 1708669..4fd07f6 100644 --- a/setup.py +++ b/setup.py @@ -56,11 +56,10 @@ 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: Medical Science Apps.', 'Topic :: Scientific/Engineering :: Physics' ], From dc0a2e92a1c6f1e6cce1630faa331071c763bbfd Mon Sep 17 00:00:00 2001 From: Darcy Mason Date: Thu, 2 Jan 2025 17:51:59 -0500 Subject: [PATCH 7/8] Fix force of old pydicom --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4635775..d4b7213 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: pydicom-version: ["pydicom-latest"] include: - python-version: "3.10" - pydicom-version: "pydicom~=2.4.4" + pydicom-version: "pydicom==2.4.0" steps: - name: Checkout repository uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: pip install codecov - name: Force pydicom version if needed - if: startsWith(matrix.pydicom-version, '2') + if: startsWith(matrix.pydicom-version, 'pydicom==') run: | pip install -U "${{ matrix.pydicom-version }}" From 666f212a77ad221b47fcf9c2fa96bde8d6be5cf7 Mon Sep 17 00:00:00 2001 From: Aditya Panchal Date: Thu, 9 Jan 2025 21:24:19 -0600 Subject: [PATCH 8/8] Update README to reflect Python 3.10 compatibility and badge changes --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 970ee7f..a116852 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ Other information - Free software: `BSD license `__ - Documentation: `Read the docs `__ -- Tested on Python 3.7+ +- Tested on Python 3.10+ Dependencies ------------ @@ -85,7 +85,7 @@ This package was created with :target: http://mybinder.org/repo/bastula/dicom-notebooks .. |pypi| image:: https://img.shields.io/pypi/v/dicompyler-core.svg :target: https://pypi.python.org/pypi/dicompyler-core -.. |Python Version| image:: https://img.shields.io/badge/python-3.7+-blue.svg +.. |Python Version| image:: https://img.shields.io/badge/python-3.10+-blue.svg :target: https://pypi.python.org/pypi/dicompyler-core .. |GH Actions| image:: https://github.com/dicompyler/dicompyler-core/actions/workflows/build.yml/badge.svg :target: https://github.com/dicompyler/dicompyler-core/actions @@ -97,8 +97,8 @@ This package was created with :target: https://app.codacy.com/gh/dicompyler/dicompyler-core/dashboard .. |Codecov| image:: https://codecov.io/gh/dicompyler/dicompyler-core/branch/master/graph/badge.svg :target: https://codecov.io/gh/dicompyler/dicompyler-core -.. |Total Lines| image:: https://img.shields.io/tokei/lines/github/dicompyler/dicompyler-core - :target: https://img.shields.io/tokei/lines/github/dicompyler/dicompyler-core +.. |Total Lines| image:: https://img.shields.io/endpoint?url=https://ghloc.vercel.app/api/dicompyler/dicompyler-core/badge?style=flat&logoColor=white&label=Lines%20of%20Code + :target: https://ghloc.vercel.app/dicompyler/dicompyler-core?branch=master .. |Code Size| image:: https://img.shields.io/github/languages/code-size/dicompyler/dicompyler-core :target: https://img.shields.io/github/languages/code-size/dicompyler/dicompyler-core .. |Zenodo| image:: https://zenodo.org/badge/51550203.svg