Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify custom metadata to be included in JSON sidecars #80

Merged
merged 3 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions manual_correction.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,21 @@ def get_parser():
"final dataset.",
action='store_true'
)
parser.add_argument(
'-json-metadata', metavar="<file>", required=False,
help="R|A custom JSON file containing metadata to be added to the JSON sidecar of all corrected labels. "
"This flag is useful, for example, when a label was obtained automatically and you want to include this "
"information into the JSON sidecar."
"Below is an example JSON file:\n"
+ dedent(
"""
{
"Name": "sct_deepseg_sc",
"Version": "SCT v6.2",
"Date": "yyyy-mm-dd hh:mm:ss"
}\n
"""),
)
parser.add_argument(
'-v', '--verbose',
help="Full verbose (for debugging)",
Expand Down Expand Up @@ -475,11 +490,28 @@ def correct_centerline(fname, fname_label, viewer='sct_get_centerline'):
viewer_not_found(viewer)


def update_json(fname_nifti, name_rater):
def load_custom_json(fname):
"""
Load custom JSON file.
:param fname: path to the custom JSON file.
:return: dictionary with the metadata to be added to the JSON sidecar.
"""
if not os.path.isfile(fname):
sys.exit("ERROR: The file {} does not exist.".format(fname))
try:
with open(fname, "r") as f:
json_metadata = json.load(f)
return json_metadata
except json.JSONDecodeError:
sys.exit("ERROR: The file {} is not a valid JSON file.".format(fname))


def update_json(fname_nifti, name_rater, json_metadata):
"""
Create/update JSON sidecar with meta information
:param fname_nifti: str: File name of the nifti image to associate with the JSON sidecar
:param name_rater: str: Name of the expert rater
:param json_metadata: dict: Dictionary with the metadata to be added to the JSON sidecar
:return:
"""
fname_json = fname_nifti.replace('.gz', '').replace('.nii', '.json')
Expand All @@ -501,6 +533,10 @@ def update_json(fname_nifti, name_rater):
# Init new json dict
json_dict = {'SpatialReference': 'orig',
'GeneratedBy': []}
# NOTE: we add the custom metadata only when initializing a new JSON file. Because it does not make sense to add
# these metadata into already existing labels, which we do not know how they were generated.
if json_metadata:
json_dict['GeneratedBy'].append(json_metadata)

# If the label was modified or just checked, add "Name": "Manual" to the JSON sidecar
json_dict['GeneratedBy'].append({'Name': 'Manual',
Expand Down Expand Up @@ -726,6 +762,9 @@ def main():
if not file_list:
sys.exit("ERROR: No segmentation file found in {}.".format(args.path_label))

# If a custom JSON file containing metadata was provided, load it, and verify that it is a valid JSON file
json_metadata = load_custom_json(args.json_metadata) if args.json_metadata else None

# Get name of expert rater (skip if -qc-only is true)
if not args.qc_only:
name_rater = input("Enter your name (Firstname Lastname). It will be used to generate a json sidecar with each "
Expand Down Expand Up @@ -856,10 +895,10 @@ def main():
if args.add_seg_only:
# We use update_json because we are adding a new segmentation, and we want to create
# a JSON file
update_json(fname_out, name_rater)
update_json(fname_out, name_rater, json_metadata)
# Generate QC report
else:
update_json(fname_out, name_rater)
update_json(fname_out, name_rater, json_metadata)
# Generate QC report
generate_qc(fname, fname_out, task, fname_qc, subject, args.config, args.qc_lesion_plane, suffix_dict)

Expand Down
36 changes: 34 additions & 2 deletions tests/test_create_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_create_json(tmp_path):
nifti_file.touch()

# Call the function with modified=True
update_json(str(nifti_file), "Test Rater")
update_json(str(nifti_file), "Test Rater", json_metadata=None)

# Check that the JSON file was created and contains the expected metadata
expected_metadata = {'SpatialReference': 'orig',
Expand All @@ -35,6 +35,38 @@ def test_create_json(tmp_path):
assert metadata == expected_metadata


def test_create_json_custom_metadata(tmp_path):
"""
Test that the function update_json() creates a JSON file with the expected metadata and includes custom metadata
provided by the user as a JSON file
"""
# Create a temporary file for testing
fname_label = "sub-001_ses-01_T1w_seg-manual.nii.gz"
nifti_file = tmp_path / fname_label
nifti_file.touch()

custom_metada = {'Name': 'sct_deepseg_sc',
'Author': "SCT v6.2",
'Date': "2024-02-21 00:00:00"}

# Call the function with modified=True
update_json(str(nifti_file), "Test Rater", json_metadata=custom_metada)

# Check that the JSON file was created and contains the expected metadata
expected_metadata = {'SpatialReference': 'orig',
'GeneratedBy': [{'Name': 'sct_deepseg_sc',
'Author': "SCT v6.2",
'Date': "2024-02-21 00:00:00"},
{'Name': 'Manual',
'Author': "Test Rater",
'Date': time.strftime('%Y-%m-%d %H:%M:%S')}]}
json_file = tmp_path / fname_label.replace(".nii.gz", ".json")
assert json_file.exists()
with open(str(json_file), "r") as f:
metadata = json.load(f)
assert metadata == expected_metadata


def test_update_json(tmp_path):
"""
Test that the function update_json() updates (appends to) the JSON file with the expected metadata.
Expand All @@ -52,7 +84,7 @@ def test_update_json(tmp_path):
'Date': "2023-01-01 00:00:00"}]}, f)

# Call the function with modified=True
update_json(str(nifti_file), "Test Rater 2")
update_json(str(nifti_file), "Test Rater 2", json_metadata=None)

# Check that the JSON file was created and contains the expected metadata
expected_metadata = {'SpatialReference': 'orig',
Expand Down
Loading