Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
astewartau committed Dec 18, 2024
1 parent c01699e commit 76192d4
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 124 deletions.
2 changes: 1 addition & 1 deletion dicompare/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.1.8"
__version__ = "0.1.9"

# Import core functionalities
from .io import get_dicom_values, load_dicom, load_json_session, load_dicom_session, load_python_session
Expand Down
21 changes: 9 additions & 12 deletions dicompare/cli/check_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ def main():

# Load the reference models and fields
if args.json_ref:
acquisition_fields, reference_fields, ref_session = load_json_session(json_ref=args.json_ref)
reference_fields, ref_session = load_json_session(json_ref=args.json_ref)
elif args.python_ref:
ref_models = load_python_session(module_path=args.python_ref)
acquisition_fields = ["ProtocolName"]
acquisition_fields = ["ProtocolName"]

# Load the input session
in_session = load_dicom_session(
Expand All @@ -33,17 +33,14 @@ def main():
)

if args.json_ref:
# Group by all existing unique combinations of reference fields
in_session = (
in_session.groupby(reference_fields)
.apply(lambda x: x.reset_index(drop=True))
.reset_index(drop=True) # Reset the index to avoid index/column ambiguity
)

# Assign unique group numbers for each combination of reference fields
# reset index to avoid issues with groupby
in_session.reset_index(drop=True, inplace=True)
# Group by acquisition fields to create Series labels starting from 1 for each acquisition
in_session["Series"] = (
in_session.groupby(reference_fields, dropna=False).ngroup().add(1).apply(lambda x: f"Series {x}")
)
in_session.groupby(acquisition_fields).apply(
lambda group: group.groupby(reference_fields, dropna=False).ngroup().add(1)
).reset_index(level=0, drop=True) # Reset multi-index back to DataFrame
).apply(lambda x: f"Series {x}")

if args.json_ref:
session_map = map_to_json_reference(in_session, ref_session)
Expand Down
59 changes: 23 additions & 36 deletions dicompare/cli/gen_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,15 @@
import json
import pandas as pd
from dicompare.io import load_dicom_session
from dicompare.utils import clean_string
from dicompare.utils import clean_string, make_hashable

def make_hashable(value):
"""
Convert a value into a hashable format.
Handles lists, dictionaries, and other non-hashable types.
"""
if isinstance(value, list):
return tuple(value)
elif isinstance(value, dict):
return tuple((k, make_hashable(v)) for k, v in value.items())
elif isinstance(value, set):
return tuple(sorted(make_hashable(v) for v in value))
return value


def create_json_reference(session_df, acquisition_fields, reference_fields, name_template="{ProtocolName}"):
def create_json_reference(session_df, reference_fields):
"""
Create a JSON reference from the session DataFrame.
Args:
session_df (pd.DataFrame): DataFrame of the DICOM session.
acquisition_fields (List[str]): Fields to uniquely identify each acquisition.
reference_fields (List[str]): Fields to include in JSON reference.
name_template (str): Naming template for acquisitions/series.
Returns:
dict: JSON structure representing the reference.
Expand All @@ -43,36 +27,41 @@ def create_json_reference(session_df, acquisition_fields, reference_fields, name
for acquisition_name, group in session_df.groupby("Acquisition"):
acquisition_entry = {"fields": [], "series": []}

# Add acquisition-level fields
for field in acquisition_fields:
# Check reference fields for constant or varying values
varying_fields = []
for field in reference_fields:
unique_values = group[field].dropna().unique()
if len(unique_values) == 1:
# Constant field: Add to acquisition-level fields
acquisition_entry["fields"].append({"field": field, "value": unique_values[0]})
else:
# Varying field: Track for series-level fields
varying_fields.append(field)

# Group by series within each acquisition
series_fields = list(set(reference_fields) - set(acquisition_fields))
if series_fields:
series_groups = group.groupby(series_fields, dropna=False)

# Group by series based on varying fields
if varying_fields:
series_groups = group.groupby(varying_fields, dropna=False)
for i, (series_key, series_group) in enumerate(series_groups, start=1):
series_entry = {
"name": f"Series {i}",
"fields": [{"field": field, "value": series_key[j]} for j, field in enumerate(series_fields)]
"fields": [{"field": field, "value": series_key[j]} for j, field in enumerate(varying_fields)]
}
acquisition_entry["series"].append(series_entry)

# Exclude reference fields from acquisition-level fields if they appear in series
acquisition_entry["fields"] = [
field for field in acquisition_entry["fields"] if field["field"] not in reference_fields
]
else:
# No varying fields: Create a single series and move acquisition-level fields to it
series_entry = {
"name": "Series 1",
"fields": acquisition_entry["fields"] # Move all acquisition-level fields to the series
}
acquisition_entry["fields"] = []
acquisition_entry["series"].append(series_entry)

# Add to JSON reference
json_reference["acquisitions"][clean_string(acquisition_name)] = acquisition_entry

return json_reference



def main():
parser = argparse.ArgumentParser(description="Generate a JSON reference for DICOM compliance.")
parser.add_argument("--in_session_dir", required=True, help="Directory containing DICOM files for the session.")
Expand All @@ -90,14 +79,12 @@ def main():

# Filter fields in DataFrame
relevant_fields = set(args.acquisition_fields + args.reference_fields)
session_data = session_data[list(relevant_fields.intersection(session_data.columns)) + ["Acquisition"]]
session_data = session_data[["Acquisition"] + list(relevant_fields.intersection(session_data.columns))]

# Generate JSON reference
json_reference = create_json_reference(
session_df=session_data,
acquisition_fields=args.acquisition_fields,
reference_fields=args.reference_fields,
name_template=args.name_template,
reference_fields=args.reference_fields
)

# Write JSON to output file
Expand Down
9 changes: 4 additions & 5 deletions dicompare/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,27 +204,26 @@ def process_fields(fields: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
reference_data = normalize_numeric_values(reference_data)

acquisitions = {}
acquisition_fields = set()
series_fields = set()
reference_fields = set()

for acq_name, acquisition in reference_data.get("acquisitions", {}).items():
acq_entry = {
"fields": process_fields(acquisition.get("fields", [])),
"series": []
}
acquisition_fields.update(field["field"] for field in acquisition.get("fields", []))
reference_fields.update(field["field"] for field in acquisition.get("fields", []))

for series in acquisition.get("series", []):
series_entry = {
"name": series["name"],
"fields": process_fields(series.get("fields", []))
}
acq_entry["series"].append(series_entry)
series_fields.update(field["field"] for field in series.get("fields", []))
reference_fields.update(field["field"] for field in series.get("fields", []))

acquisitions[acq_name] = acq_entry

return sorted(acquisition_fields), sorted(series_fields), {"acquisitions": acquisitions}
return sorted(reference_fields), {"acquisitions": acquisitions}

def load_python_session(module_path: str) -> Tuple[List[str], List[str], Dict[str, BaseValidationModel]]:
"""
Expand Down
20 changes: 0 additions & 20 deletions dicompare/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,26 +162,6 @@ def map_to_json_reference(in_session_df: pd.DataFrame, ref_session: dict) -> dic
series_fields.add(field["field"])
series_fields = list(series_fields)

# Group input session by acquisition and series fields
def group_series(df: pd.DataFrame, group_keys: List[str]) -> pd.Series:

# Avoid duplicate columns by resetting the index and renaming
df = df.reset_index(drop=True)

# Group by the specified keys and assign unique group numbers
return df.groupby(group_keys, dropna=False).ngroup()

in_session_df["SeriesGroup"] = group_series(in_session_df, series_fields)
#in_session_df["Series"] = "Series " + (in_session_df.groupby("Acquisition")["SeriesGroup"].rank(method="dense").astype(int)).astype(str)
in_session_df["Series"] = (
"Series " +
(in_session_df.groupby("Acquisition")["SeriesGroup"]
.rank(method="dense")
.fillna(0) # Replace NaN with a default value (e.g., 0)
.astype(int))
.astype(str)
)

# Prepare lists for input acquisitions + series and reference acquisitions + series
input_acquisition_series = in_session_df[["Acquisition", "Series"]].drop_duplicates().values.tolist()
reference_acquisition_series = []
Expand Down
17 changes: 8 additions & 9 deletions dicompare/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def test_read_json_session(temp_json):
json_data = {
"acquisitions": {
"acq-Example": {
"fields": [{"field": "ProtocolName", "value": "Example"}],
"series": [
{
"name": "Series 1",
Expand All @@ -122,13 +121,10 @@ def test_read_json_session(temp_json):
}
}
json_path = temp_json(json_data)
acq_fields, series_fields, acquisitions = load_json_session(json_path)
reference_fields, acquisitions = load_json_session(json_path)

# Validate acquisition fields
assert acq_fields == ["ProtocolName"]

# Validate series fields
assert set(series_fields) == {"SeriesDescription", "EchoTime", "ImageType"}
# Validate reference fields
assert set(reference_fields) == {"SeriesDescription", "EchoTime", "ImageType"}

# Validate acquisitions structure
assert "acq-Example" in acquisitions["acquisitions"]
Expand Down Expand Up @@ -164,15 +160,18 @@ def test_read_dicom_session_read_json_session_numeric_datatype_encoding(tmp_path
session_dir=str(dicom_dir)
)

result_json = create_json_reference(result, ["ProtocolName"], ["EchoTime", "SeriesDescription"])
result_json = create_json_reference(result, ["EchoTime", "SeriesDescription"])

# Save the dataframe as a JSON file
json_path = tmp_path / "session_output.json"
with open(json_path, "w") as json_file:
json.dump(result_json, json_file, indent=4)

# Use `read_json_session` to load the JSON
acq_fields, series_fields, loaded_result = load_json_session(str(json_path))
reference_fields, loaded_result = load_json_session(str(json_path))

# Validate that the reference fields are loaded correctly
assert set(reference_fields) == {"EchoTime", "SeriesDescription"}

# Validate the EchoTime values and their types in the JSON
acquisitions = loaded_result["acquisitions"]
Expand Down
6 changes: 3 additions & 3 deletions docs/common.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
let pyodide;
let tagInputfmGenRef_acquisitionFields, tagInputfmGenRef_referenceFields;
//const dicompare_url = "http://localhost:8000/dist/dicompare-0.1.7-py3-none-any.whl";
//const dicompare_url = "http://localhost:8000/dist/dicompare-0.1.8-py3-none-any.whl";
//const valid_fields_url = "http://localhost:8000/valid_fields.json";
const dicompare_url = "dicompare==0.1.8"
const valid_fields_url = "https://raw.githubusercontent.com/astewartau/dicompare/v0.1.8/valid_fields.json";
const dicompare_url = "dicompare==0.1.9"
const valid_fields_url = "https://raw.githubusercontent.com/astewartau/dicompare/v0.1.9/valid_fields.json";

async function initTagify() {
// Fetch the list of valid fields for Tagify
Expand Down
Loading

0 comments on commit 76192d4

Please sign in to comment.