Skip to content

finalize_outputs: multiclass uint8 conversion rescales each chunk by a different factor #1432

Description

@axiosdevs

vesuvius.finalize_outputs --mode multiclass writes a volume documented as [softmax_c0...softmax_cN, argmax], but the uint8 conversion rescales that array by a factor that depends on the contents of each chunk, so neither the probabilities nor the class indices survive.

finalize_outputs.py, in apply_finalization:

softmax = exp_logits / np.sum(exp_logits, axis=0, keepdims=True)
output_data = np.concatenate([softmax, argmax], axis=0)     # [0,1] concatenated with [0, C-1]
...
min_val = output_np.min()
max_val = output_np.max()
if min_val < max_val:
    output_np = ((output_np - min_val) / (max_val - min_val) * 255).astype(np.uint8)

max_val is the largest class index present in that chunk, so the scale factor changes from chunk to chunk.

Reproduction on a 4-class volume, calling the function directly:

chunk A (contains classes 0 and 3):
   argmax values 0, 3     -> written as 0, 255
   softmax probability 1.000 -> written as 85
chunk B (contains classes 0 and 1):
   argmax values 0, 1     -> written as 0, 255
   softmax probability 1.000 -> written as 255

So class 1 and class 3 are both stored as 255, a probability of 1.0 is stored as 85 in one chunk and 255 in another, and the encoding is discontinuous at every chunk boundary. The same path is used by the fused blend_and_finalize (blending.py). With --threshold in multiclass the output is argmax only, and that too is rescaled per chunk, so class indices are not recoverable there either.

A fix would quantize the two parts on fixed, chunk-independent scales, e.g.

softmax_u8 = np.clip(softmax * 255.0, 0, 255).astype(np.uint8)   # same scale as the binary path
argmax_u8 = argmax.astype(np.uint8)                              # raw class index
output_np = np.concatenate([softmax_u8, argmax_u8], axis=0)

which keeps probabilities comparable across chunks and leaves class indices readable, at the cost of changing the byte values existing multiclass artifacts carry. Happy to send that as a PR if the encoding change is acceptable — or a variant that keeps a single scale but documents it, if downstream consumers already depend on the current bytes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions