Skip to content

Commit 1a26b93

Browse files
authored
Merge pull request #1337 from nipreps/fix/handle-nan-iqms
ENH: Use ``orjson`` to serialize JSON, addressing Numpy serialization issues
2 parents 6c45860 + 0e486ab commit 1a26b93

File tree

3 files changed

+38
-21
lines changed

3 files changed

+38
-21
lines changed

mriqc/interfaces/bids.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import re
2424
from pathlib import Path
2525

26-
import simplejson as json
26+
import orjson as json
2727
from nipype.interfaces.base import (
2828
BaseInterfaceInputSpec,
2929
DynamicTraitedSpec,
@@ -187,16 +187,17 @@ def _run_interface(self, runtime):
187187
self._out_dict['provenance'] = {}
188188
self._out_dict['provenance'].update(prov_dict)
189189

190-
with open(out_file, 'w') as f:
191-
f.write(
192-
json.dumps(
193-
self._out_dict,
194-
sort_keys=True,
195-
indent=2,
196-
ensure_ascii=False,
197-
)
190+
Path(out_file).write_bytes(
191+
json.dumps(
192+
self._out_dict,
193+
option=(
194+
json.OPT_SORT_KEYS
195+
| json.OPT_INDENT_2
196+
| json.OPT_APPEND_NEWLINE
197+
| json.OPT_SERIALIZE_NUMPY
198+
),
198199
)
199-
200+
)
200201
return runtime
201202

202203

mriqc/interfaces/webapi.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
#
2121
# https://www.nipreps.org/community/licensing/
2222
#
23-
import json
23+
from pathlib import Path
2424

25+
import orjson
2526
from nipype.interfaces.base import (
2627
BaseInterfaceInputSpec,
2728
Bunch,
@@ -127,6 +128,7 @@ class UploadIQMsInputSpec(BaseInterfaceInputSpec):
127128

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

131133

132134
class UploadIQMs(SimpleInterface):
@@ -153,6 +155,18 @@ def _run_interface(self, runtime):
153155
modality=self.inputs.modality,
154156
)
155157

158+
payload_str = orjson.dumps(
159+
payload,
160+
option=(
161+
orjson.OPT_SORT_KEYS
162+
| orjson.OPT_INDENT_2
163+
| orjson.OPT_APPEND_NEWLINE
164+
| orjson.OPT_SERIALIZE_NUMPY
165+
),
166+
)
167+
Path('payload.json').write_bytes(payload_str)
168+
self._results['payload_file'] = str(Path('payload.json').absolute())
169+
156170
try:
157171
self._results['api_id'] = response.json()['_id']
158172
except (AttributeError, KeyError, ValueError):
@@ -161,7 +175,7 @@ def _run_interface(self, runtime):
161175
'QC metrics upload failed to create an ID for the record '
162176
f'uploaded. Response from server follows: {response.text}'
163177
'\n\nPayload:\n'
164-
f'{json.dumps(payload, indent=2)}'
178+
f'{payload_str}'
165179
)
166180
config.loggers.interface.warning(errmsg)
167181

@@ -177,7 +191,7 @@ def _run_interface(self, runtime):
177191
'',
178192
'',
179193
'Payload:',
180-
json.dumps(payload, indent=2),
194+
payload_str,
181195
]
182196
)
183197
config.loggers.interface.warning(errmsg)
@@ -209,8 +223,6 @@ def upload_qc_metrics(
209223
210224
"""
211225
from copy import deepcopy
212-
from json import dumps, loads
213-
from pathlib import Path
214226

215227
import requests
216228

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

222-
in_data = loads(Path(in_iqms).read_text())
234+
in_data = orjson.loads(Path(in_iqms).read_bytes())
223235

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

268280
start_message = messages.QC_UPLOAD_START.format(url=endpoint)
269281
config.loggers.interface.info(start_message)
282+
283+
errmsg = None
270284
try:
271285
# if the modality is bold, call "bold" endpoint
272286
response = requests.post(
273287
f'{endpoint}/{modality}',
274288
headers=headers,
275-
data=dumps(data),
289+
data=orjson.dumps(data, option=orjson.OPT_SERIALIZE_NUMPY),
276290
timeout=15,
277291
)
278292
except requests.ConnectionError as err:
279-
errmsg = f'Error uploading IQMs -- Connection error:\n{err}'
280-
return Bunch(status_code=1, text=errmsg)
293+
errmsg = (f'Error uploading IQMs: Connection error:', f'{err}')
281294
except requests.exceptions.ReadTimeout as err:
282-
errmsg = f'Error uploading IQMs -- {endpoint} seems down:\n{err}'
283-
return Bunch(status_code=1, text=errmsg)
295+
errmsg = (f'Error uploading IQMs: Server {endpoint} is down.', f'{err}')
296+
297+
if errmsg is not None:
298+
response = Bunch(status_code=1, text='\n'.join(errmsg))
284299

285300
return response, data
286301

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies = [
3030
"nitransforms ~= 24.0",
3131
"niworkflows ~=1.10.1",
3232
"numpy ~=1.20",
33+
"orjson",
3334
"pandas",
3435
"pybids >= 0.15.6",
3536
"PyYAML",

0 commit comments

Comments
 (0)