Skip to content

Commit

Permalink
Allow to specify custom metadata to be included in JSON sidecars (#80)
Browse files Browse the repository at this point in the history
* Add a new arg '-json-metadata' allowing to provide a custom JSON file containing metadata to be added to the JSON sidecar of all corrected labels.

* Add tests for newly added '-json-metadata' flag. Update other tests to be compatible with new 'json_metadata' param.

* Use 'yyyy-mm-dd hh:mm:ss' date-time format for 'Date' in JSON sidecars
  • Loading branch information
valosekj authored Mar 1, 2024
1 parent ba2fd9d commit e09272a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 5 deletions.
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

0 comments on commit e09272a

Please sign in to comment.