nibabel icon indicating copy to clipboard operation
nibabel copied to clipboard

PAR/REC Changes for Philips Software R11

Open alexdaniel654 opened this issue 3 months ago • 4 comments

My site has just upgraded our scanner from software release R5.9 to R11. When I've tried to read in the test data I acquired I get the following error:

File "c:\Users\ppzajd\Desktop\Temp\nibabel_testing\nibabel_r11_test.py", line 5, in <module>
    r11_img = nib.load('R11_T2W_TSE.PAR')
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\ppzajd\Git\nibabel\nibabel\loadsave.py", line 114, in load
    img = image_klass.from_filename(filename, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\ppzajd\Git\nibabel\nibabel\parrec.py", line 1332, in from_filename
    return klass.from_file_map(
           ^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\ppzajd\Git\nibabel\nibabel\parrec.py", line 1295, in from_file_map
    hdr = klass.header_class.from_fileobj(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\ppzajd\Git\nibabel\nibabel\parrec.py", line 767, in from_fileobj
    info, image_defs = parse_PAR_header(fileobj)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\ppzajd\Git\nibabel\nibabel\parrec.py", line 524, in parse_PAR_header
    general_info = _process_gen_dict(gen_dict)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\ppzajd\Git\nibabel\nibabel\parrec.py", line 351, in _process_gen_dict
    props = _hdr_key_dict[key]
            ~~~~~~~~~~~~~^^^^^
KeyError: 'Max. number of Diffusion values'

Having done a bit of debugging it seems that Philips have helpfully capitalised the D of diffusion in the general information key of the PAR files in this software release i.e. the old PAR files was

# === GENERAL INFORMATION ========================================================
#
.    Patient name                       :   R_5_Data

...

.    Diffusion echo time [ms]           :   0.0000
.    Max. number of diffusion values    :   1
.    Max. number of gradient orients    :   1

and is now

# === GENERAL INFORMATION ========================================================
#
.    Patient name                       :   R_511_Data

...

.    Diffusion echo time [ms]           :   0.0000
.    Max. number of Diffusion values    :   1
.    Max. number of gradient orients    :   1

From a quick look at the nibabel code it looks to me like the easiest solution would be to make the key matching with _hdr_key_dict case insensitive. I have a draft solution that seems to be working by changing the _process_gen_dict function as below.

def _process_gen_dict(gen_dict):
    """Process `gen_dict` key, values into `general_info`"""
    general_info = {}
    for key, value in gen_dict.items():
        # get props for this hdr field
        # Match key in _hdr_key_dict case-insensitively
        props = None
        for dict_key in _hdr_key_dict:
            if dict_key.lower() == key.lower():
                props = _hdr_key_dict[dict_key]
                break
        # turn values into meaningful dtype
        if len(props) == 2:
            # only dtype spec and no shape
            value = props[1](value)
        elif len(props) == 3:
            # array with dtype and shape
            value = np.fromstring(value, props[1], sep=' ')
            # if shape is None, allow arbitrary length
            if props[2] is not None:
                value.shape = props[2]
        general_info[props[0]] = value
    return general_info

I'm happy to work on this in a PR but thought it was worth opening an issue first to check maintainers are happy with my proposed solution before I invest any more time (especially as this would be my first contribution to nibabel). I can provide more information/test data if that's helpful.

alexdaniel654 avatar Sep 04 '25 14:09 alexdaniel654

Yes, please! If at all possible, it would be great to add a test file so we can be sure that we don't break things in the future.

effigies avatar Sep 05 '25 18:09 effigies

I wonder whether we can just add that key are well (with the different capitalization)?

matthew-brett avatar Sep 05 '25 21:09 matthew-brett

Alternately:

_hdr_key_dict = {key.lower(): val for key, val in _hdr_key_dict.items()}

[...]
props = _hdr_key_dict[key.lower()]

effigies avatar Sep 05 '25 21:09 effigies

Thanks for the positive responses. It'll be nice to give back a little bit to a library I've used pretty much every day for years. I think @effigies solution is the cleanest of the three, less verbose than my suggestion and avoids having two keys for the same value.

I'll take a look at adding some tests and submit a pull request. I've got a busy couple of weeks coming up but will try and squeeze it in.

alexdaniel654 avatar Sep 11 '25 10:09 alexdaniel654