diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e6ce3..a2cd509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ This document contains the Spec2nii release history in reverse chronological order. +0.7.1 (WIP) +-------------------------------- +- The --anon flag can be passed with any call to anonymise after writing files. + 0.7.0 (Saturday 5th August 2023) -------------------------------- - Fixed a bug in Philips Classic DICOM orientations (supplementing the fixes to Enhanced DICOM in `0.6.11`) diff --git a/spec2nii/anonymise.py b/spec2nii/anonymise.py index 194c1fc..c01736c 100644 --- a/spec2nii/anonymise.py +++ b/spec2nii/anonymise.py @@ -13,17 +13,18 @@ from nifti_mrs.hdr_ext import Hdr_Ext -def anon_nifti_mrs(args): - """Function for anonymising input NIfTI-MRS files. +def anon_nifti_mrs(nifti_mrs_img, extra_keys=None, verbose=False): + """Function for anonymising an existing nifti-mrs object - :param args: Command line arguments parsed in spec2nii.py - :return: List of anonymised images - :rtype: [nib.nifti2.Nifti2Image,] - :return: List of output names - :rtype: [str,] + :param nifti_mrs_img: NIfTI-MRS object to anonymise + :type nifti_mrs_img: nifti_mrs.nifti_mrs.NIFTI_MRS + :param extra_keys: List of keys to explicitly remove, defaults to None + :type extra_keys: List, optional + :param verbose: Print verbose output, defaults to False + :type verbose: bool, optional + :return: Anonymised NIfTI-MRS object + :rtype: nifti_mrs.nifti_mrs.NIFTI_MRS """ - # Load data - nifti_mrs_img = NIFTI_MRS(args.file) # Extract header extension hdr_ext = nifti_mrs_img.hdr_ext.to_dict() @@ -38,8 +39,8 @@ def iter_json(in_dict): removed = {} for key, value in in_dict.items(): # Explicitly set on command line - if args.remove\ - and key in args.remove: + if extra_keys\ + and key in extra_keys: removed.update({key: value}) # Standard defined elif key in standard_defined: @@ -65,7 +66,7 @@ def iter_json(in_dict): anon_hdr, removed_dict = iter_json(hdr_ext) - if args.verbose: + if verbose: pp = pprint.PrettyPrinter(indent=4) print('\nThe following keys were removed:') pp.pprint(removed_dict) @@ -73,16 +74,31 @@ def iter_json(in_dict): pp.pprint(anon_hdr) # Make new NIfTI-MRS image - anon_out = gen_nifti_mrs_hdr_ext( + return gen_nifti_mrs_hdr_ext( nifti_mrs_img[:], nifti_mrs_img.dwelltime, Hdr_Ext.from_header_ext(anon_hdr), affine=nifti_mrs_img.getAffine('voxel', 'world')) + +def anon_file(args): + """Function for anonymising NIfTI-MRS files stored on drive. + + :param args: Command line arguments parsed in spec2nii.py + :return: List of anonymised images + :rtype: [nib.nifti2.Nifti2Image,] + :return: List of output names + :rtype: [str,] + """ + # Process output name. if args.fileout: fname_out = [args.fileout, ] else: fname_out = [args.file.with_suffix('').with_suffix('').name, ] - return [anon_out, ], fname_out + return [ + anon_nifti_mrs( + NIFTI_MRS(args.file), + verbose=args.verbose, + extra_keys=args.remove), ], fname_out diff --git a/spec2nii/spec2nii.py b/spec2nii/spec2nii.py index ad8b6c5..1e76f33 100644 --- a/spec2nii/spec2nii.py +++ b/spec2nii/spec2nii.py @@ -47,7 +47,12 @@ def add_common_parameters(subparser): help="Override SpectrometerFrequency field with input(s). Input in MHz.") subparser.add_argument("--override_dwelltime", type=float, help="Override dwell time field with input. Input in seconds.") + subparser.add_argument( + '--anon', + action='store_true', + help="Create file without sensitive metadata. For greater control use spec2nii anon.") subparser.add_argument('--verbose', action='store_true') + return subparser # Auto subcommand - heuristic ID of file type @@ -234,7 +239,8 @@ def add_common_parameters(subparser): nifti1=False, override_nucleus=None, override_frequency=None, - override_dwelltime=None) + override_dwelltime=None, + anon=False) parser_dump = subparsers.add_parser('dump', help='Dump contents of headers from existing NIfTI-MRS file.') parser_dump.add_argument('file', help='NIfTI-MRS file', type=Path) @@ -264,7 +270,8 @@ def add_common_parameters(subparser): override_nucleus=None, override_frequency=None, override_dwelltime=None, - verbose=False) + verbose=False, + anon=False) if len(sys.argv) == 1: parser.print_usage(sys.stderr) @@ -284,6 +291,12 @@ def add_common_parameters(subparser): if self.imageOut: self.implement_overrides(args) + + if args.anon: + from spec2nii.anonymise import anon_nifti_mrs + for idx, nifti_mrs_img in enumerate(self.imageOut): + self.imageOut[idx] = anon_nifti_mrs(nifti_mrs_img, verbose=args.verbose) + self.validate_output() self.write_output(args.json, args.nifti1) self.validate_write(args.verbose) @@ -641,8 +654,8 @@ def varian(self, args): # Anonymise function def anon(self, args): - from spec2nii.anonymise import anon_nifti_mrs - self.imageOut, self.fileoutNames = anon_nifti_mrs(args) + from spec2nii.anonymise import anon_file + self.imageOut, self.fileoutNames = anon_file(args) # Dump function @staticmethod diff --git a/tests/test_anon.py b/tests/test_anon.py index a857b85..b8844e9 100644 --- a/tests/test_anon.py +++ b/tests/test_anon.py @@ -52,3 +52,26 @@ def test_anon(tmp_path): assert 'PatientName' not in hdr_ext_a assert 'PatientSex' not in hdr_ext_a assert 'RepetitionTime' not in hdr_ext_a + + +def test_inline_anon(tmp_path): + # Convert twix + subprocess.check_call(['spec2nii', 'twix', + '-e', 'image', + '-f', 'original', + '-o', tmp_path, + '-j', str(data_path), + '--anon', + '--verbose']) + + img_o = read_nifti_mrs(tmp_path / 'original.nii.gz') + + hdr_ext_codes = img_o.header.extensions.get_codes() + hdr_ext_o = json.loads(img_o.header.extensions[hdr_ext_codes.index(44)].get_content()) + + assert 'SpectrometerFrequency' in hdr_ext_o + assert 'ResonantNucleus' in hdr_ext_o + assert 'dim_5' in hdr_ext_o + assert 'OriginalFile' not in hdr_ext_o + assert 'PatientName' not in hdr_ext_o + assert 'PatientDoB' not in hdr_ext_o