Skip to content

Commit

Permalink
Merge pull request #1337 from nipreps/fix/handle-nan-iqms
Browse files Browse the repository at this point in the history
ENH: Use ``orjson`` to serialize JSON, addressing Numpy serialization issues
  • Loading branch information
oesteban authored Aug 23, 2024
2 parents 6c45860 + 0e486ab commit 1a26b93
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 21 deletions.
21 changes: 11 additions & 10 deletions mriqc/interfaces/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import re
from pathlib import Path

import simplejson as json
import orjson as json
from nipype.interfaces.base import (
BaseInterfaceInputSpec,
DynamicTraitedSpec,
Expand Down Expand Up @@ -187,16 +187,17 @@ def _run_interface(self, runtime):
self._out_dict['provenance'] = {}
self._out_dict['provenance'].update(prov_dict)

with open(out_file, 'w') as f:
f.write(
json.dumps(
self._out_dict,
sort_keys=True,
indent=2,
ensure_ascii=False,
)
Path(out_file).write_bytes(
json.dumps(
self._out_dict,
option=(
json.OPT_SORT_KEYS
| json.OPT_INDENT_2
| json.OPT_APPEND_NEWLINE
| json.OPT_SERIALIZE_NUMPY
),
)

)
return runtime


Expand Down
37 changes: 26 additions & 11 deletions mriqc/interfaces/webapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
#
# https://www.nipreps.org/community/licensing/
#
import json
from pathlib import Path

import orjson
from nipype.interfaces.base import (
BaseInterfaceInputSpec,
Bunch,
Expand Down Expand Up @@ -127,6 +128,7 @@ class UploadIQMsInputSpec(BaseInterfaceInputSpec):

class UploadIQMsOutputSpec(TraitedSpec):
api_id = traits.Either(None, traits.Str, desc='Id for report returned by the web api')
payload_file = File(desc='Submitted payload (only for debugging)')


class UploadIQMs(SimpleInterface):
Expand All @@ -153,6 +155,18 @@ def _run_interface(self, runtime):
modality=self.inputs.modality,
)

payload_str = orjson.dumps(
payload,
option=(
orjson.OPT_SORT_KEYS
| orjson.OPT_INDENT_2
| orjson.OPT_APPEND_NEWLINE
| orjson.OPT_SERIALIZE_NUMPY
),
)
Path('payload.json').write_bytes(payload_str)
self._results['payload_file'] = str(Path('payload.json').absolute())

try:
self._results['api_id'] = response.json()['_id']
except (AttributeError, KeyError, ValueError):
Expand All @@ -161,7 +175,7 @@ def _run_interface(self, runtime):
'QC metrics upload failed to create an ID for the record '
f'uploaded. Response from server follows: {response.text}'
'\n\nPayload:\n'
f'{json.dumps(payload, indent=2)}'
f'{payload_str}'
)
config.loggers.interface.warning(errmsg)

Expand All @@ -177,7 +191,7 @@ def _run_interface(self, runtime):
'',
'',
'Payload:',
json.dumps(payload, indent=2),
payload_str,
]
)
config.loggers.interface.warning(errmsg)
Expand Down Expand Up @@ -209,8 +223,6 @@ def upload_qc_metrics(
"""
from copy import deepcopy
from json import dumps, loads
from pathlib import Path

import requests

Expand All @@ -219,7 +231,7 @@ def upload_qc_metrics(
errmsg = 'Unknown API endpoint' if not endpoint else 'Authentication failed.'
return Bunch(status_code=1, text=errmsg)

in_data = loads(Path(in_iqms).read_text())
in_data = orjson.loads(Path(in_iqms).read_bytes())

# Extract metadata and provenance
meta = in_data.pop('bids_meta')
Expand Down Expand Up @@ -267,20 +279,23 @@ def upload_qc_metrics(

start_message = messages.QC_UPLOAD_START.format(url=endpoint)
config.loggers.interface.info(start_message)

errmsg = None
try:
# if the modality is bold, call "bold" endpoint
response = requests.post(
f'{endpoint}/{modality}',
headers=headers,
data=dumps(data),
data=orjson.dumps(data, option=orjson.OPT_SERIALIZE_NUMPY),
timeout=15,
)
except requests.ConnectionError as err:
errmsg = f'Error uploading IQMs -- Connection error:\n{err}'
return Bunch(status_code=1, text=errmsg)
errmsg = (f'Error uploading IQMs: Connection error:', f'{err}')
except requests.exceptions.ReadTimeout as err:
errmsg = f'Error uploading IQMs -- {endpoint} seems down:\n{err}'
return Bunch(status_code=1, text=errmsg)
errmsg = (f'Error uploading IQMs: Server {endpoint} is down.', f'{err}')

if errmsg is not None:
response = Bunch(status_code=1, text='\n'.join(errmsg))

return response, data

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
"nitransforms ~= 24.0",
"niworkflows ~=1.10.1",
"numpy ~=1.20",
"orjson",
"pandas",
"pybids >= 0.15.6",
"PyYAML",
Expand Down

0 comments on commit 1a26b93

Please sign in to comment.