Skip to content

Commit

Permalink
refactor: Add conv_response_list utility function
Browse files Browse the repository at this point in the history
  • Loading branch information
jjjermiah committed Mar 3, 2024
1 parent adcfde5 commit 1aa0ad1
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 48 deletions.
63 changes: 63 additions & 0 deletions docs/tutorial_files/2_ExploreCollections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,66 @@ Passing a `prefix` parameter will return a list of collections that match the pr
)

print(counts_df)



Modality Methods
^^^^^^^^^^^^^^^^^^
The :meth:`getModalityValues` method can provide insight to the available modality types in the NBIA database.

The method has the following signature:

.. automethod:: nbiatoolkit.NBIAClient.getModalityValues

Passing no parameters to the method will return a list of all modality types available in the NBIA database.
Filtering by :code:`Collection` and :code:`BodyPartExamined` is also possible.
The :code:`Counts` parameter can be set to :code:`True` to return the number of patients for each modality type.

.. tabs::

.. tab:: Python

.. tabs::

.. tab:: Default Query
.. exec_code::

# --- hide: start ---
from nbiatoolkit import NBIAClient
# --- hide: stop ---

with NBIAClient(return_type="dataframe") as client:
modalities = client.getModalityValues()

print(modalities)

.. tab:: Filtered Query

.. exec_code::

# --- hide: start ---
from nbiatoolkit import NBIAClient
# --- hide: stop ---

with NBIAClient(return_type="dataframe") as client:
modalities = client.getModalityValues(
collection = "TCGA-BLCA",
)

print(modalities)

.. tab:: Counts Query

.. exec_code::

# --- hide: start ---
from nbiatoolkit import NBIAClient
# --- hide: stop ---

with NBIAClient(return_type="dataframe") as client:
modalities = client.getModalityValues(
collection = "TCGA-BLCA",
counts = True
)

print(modalities)
57 changes: 31 additions & 26 deletions src/nbiatoolkit/nbia.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from calendar import c
from inspect import getmodule
from re import I
import re
import zipfile
from tempfile import TemporaryDirectory
from .dicomsort import DICOMSorter
Expand All @@ -18,6 +19,7 @@
convertDateFormat,
parse_response,
ReturnType,
conv_response_list,
)
import pandas as pd
import requests
Expand All @@ -32,29 +34,6 @@
__version__ = "0.33.0"


# function that takes a list of dictionaries and returns either a list or a dataframe
def conv_response_list(
response_json: List[dict[Any, Any]],
return_type: ReturnType,
) -> List[dict[Any, Any]] | pd.DataFrame:
"""_summary_
:param response_json: _description_
:type response_json: List[dict[Any, Any]]
:param return_type: _description_
:type return_type: ReturnType
:return: _description_
:rtype: List[dict[Any, Any]] | pd.DataFrame
"""

assert isinstance(response_json, list), "The response JSON must be a list"

if return_type == ReturnType.LIST:
return response_json
elif return_type == ReturnType.DATAFRAME:
return pd.DataFrame(data=response_json)


def downloadSingleSeries(
SeriesInstanceUID: str,
downloadDir: str,
Expand Down Expand Up @@ -366,17 +345,43 @@ def getModalityValues(
self,
Collection: str = "",
BodyPartExamined: str = "",
Counts: bool = False,
return_type: Optional[Union[ReturnType, str]] = None,
) -> List[dict[Any, Any]] | pd.DataFrame:
"""Retrieves possible modality values from the NBIA database.
Args:
Collection (str, optional): Collection name to filter by. Defaults to "".
BodyPartExamined (str, optional): BodyPart name to filter by. Defaults to "".
Counts (bool, optional): Flag to indicate whether to return patient counts. Defaults to False.
return_type (Optional[Union[ReturnType, str]], optional):
Return type of the response. Defaults to None which uses the default return type.
Returns:
List[dict[Any, Any]] | pd.DataFrame:
List of modality values or DataFrame containing the modality values.
"""

returnType: ReturnType = self._get_return(return_type)

PARAMS: dict = self.parsePARAMS(params=locals())

response: List[dict[Any, Any]]
response = self.query_api(
endpoint=NBIA_ENDPOINTS.GET_MODALITY_VALUES, params=PARAMS
endpoint = (
NBIA_ENDPOINTS.GET_MODALITY_PATIENT_COUNT
if Counts
else NBIA_ENDPOINTS.GET_MODALITY_VALUES
)

response: List[dict[Any, Any]]
response = self.query_api(endpoint=endpoint, params=PARAMS)

if Counts:
for modality in response:
modality["Modality"] = modality["criteria"]
modality["PatientCount"] = modality["count"]
del modality["criteria"]
del modality["count"]

return conv_response_list(response, returnType)

def getPatients(
Expand Down
6 changes: 4 additions & 2 deletions src/nbiatoolkit/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
from .nbia_endpoints import NBIA_ENDPOINTS, NBIA_BASE_URLS
from .nbia_endpoints import NBIA_ENDPOINTS, NBIA_BASE_URLS, ReturnType
from .md5 import validateMD5
from .parsers import (
convertMillis,
clean_html,
convertDateFormat,
parse_response,
ReturnType,
)

from .conv_response_list import conv_response_list

__all__ = [
"NBIA_ENDPOINTS",
"NBIA_BASE_URLS",
"validateMD5",
"convertMillis",
"clean_html",
"convertDateFormat",
"conv_response_list",
"parse_response",
"ReturnType",
]
26 changes: 26 additions & 0 deletions src/nbiatoolkit/utils/conv_response_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Any, List
from .nbia_endpoints import ReturnType
import pandas as pd


# function that takes a list of dictionaries and returns either a list or a dataframe
def conv_response_list(
response_json: List[dict[Any, Any]],
return_type: ReturnType,
) -> List[dict[Any, Any]] | pd.DataFrame:
"""Function that takes in a list of dictionaries and returns either a list or a dataframe.
:param response_json: A response from the API in the form of a list of dictionaries.
:type response_json: List[dict[Any, Any]]
:param return_type: The desired return type for the response.
:type return_type: ReturnType
:return: Either a list of dictionaries or a DataFrame.
:rtype: List[dict[Any, Any]] | pd.DataFrame
"""

assert isinstance(response_json, list), "The response JSON must be a list"

if return_type == ReturnType.LIST:
return response_json
elif return_type == ReturnType.DATAFRAME:
return pd.DataFrame(data=response_json)
16 changes: 9 additions & 7 deletions src/nbiatoolkit/utils/nbia_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class NBIA_ENDPOINTS(Enum):
GET_COLLECTION_DESCRIPTIONS = "getCollectionDescriptions"

GET_MODALITY_VALUES = "v2/getModalityValues"
GET_MODALITY_PATIENT_COUNT = "getModalityValuesAndCounts"

GET_PATIENTS = "v2/getPatient"
GET_NEW_PATIENTS_IN_COLLECTION = "v2/NewPatientsInCollection"
Expand All @@ -42,15 +43,16 @@ class NBIA_ENDPOINTS(Enum):
DOWNLOAD_SERIES = "v2/getImageWithMD5Hash"
GET_DICOM_TAGS = "getDicomTags"

# curl -H "Authorization:Bearer YOUR_ACCESS_TOKEN" -k "https://services.cancerimagingarchive.net/nbia-api/services/v2/getSeriesMetaData"
# curl -H "Authorization:Bearer YOUR_ACCESS_TOKEN" -k "https://services.cancerimagingarchive.net/nbia-api/services/v2/getSeriesSize"

# curl -H "Authorization:Bearer YOUR_ACCESS_TOKEN" -k "https://services.cancerimagingarchive.net/nbia-api/services/v2/getUpdatedSeries"

# curl -H "Authorization:Bearer YOUR_ACCESS_TOKEN" -k "https://services.cancerimagingarchive.net/nbia-api/services/getSeriesMetadata2" -d "list=1.3.6.1.4.1.14519.5.2.1.6834.5010.322628904903035357840500590726"
# Helper functions
def __str__(self):
return self.value

def _format(self):
return self.value.split("/")[-1]


# so that users can decide between a List or a pd.DataFrame
class ReturnType(Enum):
LIST = "list"
DATAFRAME = "dataframe"

# change .value so that DATAFRAME returns "pd.DataFrame"
9 changes: 0 additions & 9 deletions src/nbiatoolkit/utils/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@
from typing import Union, Any, Dict, List, Literal, Optional, Tuple
import pandas as pd
import requests
from enum import Enum


# so that users can decide between a List or a pd.DataFrame
class ReturnType(Enum):
LIST = "list"
DATAFRAME = "dataframe"

# change .value so that DATAFRAME returns "pd.DataFrame"


def clean_html(html_string: str) -> str:
Expand Down
30 changes: 30 additions & 0 deletions tests/test_ModalityQueries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from src.nbiatoolkit import NBIAClient
from src.nbiatoolkit.utils import *
import pandas as pd


@pytest.fixture(scope="session")
def nbia_client():
return NBIAClient()


def test_getModalityValues(nbia_client: NBIAClient):
modality_values = nbia_client.getModalityValues()

assert isinstance(modality_values, list)
assert len(modality_values) > 0
assert isinstance(modality_values[0], dict)
assert len(modality_values[0].keys()) == 1

assert isinstance(modality_values[0]["Modality"], str)


def test_getModalityValuesCounts(nbia_client: NBIAClient):
modality_values_counts = nbia_client.getModalityValues(Counts=True)

assert isinstance(modality_values_counts, list)
assert len(modality_values_counts) > 0
assert isinstance(modality_values_counts[0], dict)
assert len(modality_values_counts[0].keys()) == 2
8 changes: 4 additions & 4 deletions tests/test_dicom_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ def test_sanitizeFileName_empty_string():

def test_sanitizeFileName_assertions():
with pytest.raises(AssertionError):
sanitizeFileName(None)
sanitizeFileName(None) # type: ignore
with pytest.raises(AssertionError):
sanitizeFileName(123)
sanitizeFileName(123) # type: ignore


###############################################################################
Expand Down Expand Up @@ -113,10 +113,10 @@ def test__truncateUID_with_lastDigits_greater_than_length_of_UID(uid):
def test__truncateUID_with_invalid_input_types(uid):
lastDigits = "5"
with pytest.raises(AssertionError):
_truncateUID(uid, lastDigits)
_truncateUID(uid, lastDigits) # type: ignore


def test__truncateUID_with_None_input(uid):
lastDigits = None
with pytest.raises(AssertionError):
_truncateUID(uid, lastDigits)
_truncateUID(uid, lastDigits) # type: ignore

0 comments on commit 1aa0ad1

Please sign in to comment.