|
1 | 1 | import collections
|
2 | 2 | import numpy as np
|
| 3 | +import warnings |
3 | 4 | from copy import deepcopy
|
4 | 5 | import surfa as sf
|
5 | 6 |
|
@@ -289,3 +290,74 @@ def __init__(self, mapping, target=None):
|
289 | 290 | """
|
290 | 291 | self.mapping = dict(mapping)
|
291 | 292 | self.target = target
|
| 293 | + |
| 294 | + def invert(self, target_labels=None, strict=False): |
| 295 | + """ |
| 296 | + Invert the label mapping dictionary |
| 297 | +
|
| 298 | + Parameters |
| 299 | + ---------- |
| 300 | + target_labels : LabelLookup, optional |
| 301 | + LabelLookup that will be assigned as the target of the returned LabelRecoder |
| 302 | + If not assigned, the output volume using this recoder will not have a LabelMapping |
| 303 | + strict : bool, optional |
| 304 | + Enforce the inverted label recoding is 1-to-1 |
| 305 | + |
| 306 | + Returns |
| 307 | + ------- |
| 308 | + LabelRecoder |
| 309 | + A LabelRecoder object with the k,v pairs of the original mapping swapped |
| 310 | + |
| 311 | + If the input mapping is many-to-1, the function will raise a `KeyError` unless |
| 312 | + the `strict` param is set to `False`. In the case where the input mapping is |
| 313 | + many-to-1 and `strict` is set to `False`, the returned LabelRecoder will map |
| 314 | + to the minimum value of the 'many' labels. e.g {1:0, 2:0} the inverse will |
| 315 | + be {0:1}. |
| 316 | + |
| 317 | + Raises |
| 318 | + ------ |
| 319 | + KeyError |
| 320 | + If `strict` is set to `True` and inverted label mapping is not 1-to-1 |
| 321 | +
|
| 322 | + Examples |
| 323 | + -------- |
| 324 | + Load an aseg volume, recode the labels to tissue types using the tissue_type_recoder, |
| 325 | + then invert the tissue_type_recoder and remap the labels back. |
| 326 | + Note that the tissue_type_recoder is not 1-to-1, so labels classes in the second |
| 327 | + remapped volume will be merged. |
| 328 | + |
| 329 | + # load the aseg |
| 330 | + >>> aseg = sf.load_volume('aseg.mgz') |
| 331 | +
|
| 332 | + # create the tissue_type_recoder obj |
| 333 | + >>> lr = sf.freesurfer.tissue_type_recoder() |
| 334 | +
|
| 335 | + # remap the aseg labels |
| 336 | + >>> aseg_to_tissue = sf.labels.recode(aseg,lr) |
| 337 | +
|
| 338 | + # invert the label recoder and assign the standard cLUT as target labels |
| 339 | + >>> inv_lr = lr.invert(target_labels=sf.freesurfer.labels()) |
| 340 | +
|
| 341 | + # re-remap the aseg labels |
| 342 | + >>> tissue_to_aseg = sf.labels.recode(aseg,inv_lr) |
| 343 | + """ |
| 344 | + # invert the mapping dictionary, handling many-to-1 mapping case |
| 345 | + inv_mapping = {} |
| 346 | + for k,v in self.mapping.items(): |
| 347 | + if v not in inv_mapping.keys(): |
| 348 | + inv_mapping[v] = [k] |
| 349 | + else: |
| 350 | + inv_mapping[v].append(k) |
| 351 | + |
| 352 | + test = [len(x) == 1 for x in inv_mapping.values()] |
| 353 | + |
| 354 | + # raise key error if many-to-1 and strict |
| 355 | + if False in test and strict: |
| 356 | + raise KeyError('Cannot strictly invert a many-to-1 LabelRecoder') |
| 357 | + elif False in test: |
| 358 | + warnings.warn('The label remapping is not 1-to-1, some classes will be merged.') |
| 359 | + |
| 360 | + [x.sort() for x in inv_mapping.values()] |
| 361 | + inv_mapping = {k:v[0] for k,v in inv_mapping.items()} |
| 362 | + |
| 363 | + return LabelRecoder(inv_mapping, target_labels) |
0 commit comments