Skip to content

get_j() feeds uninitialized memory into the Junge slope fit for zero-count bins #405

Description

@animmosmith

Found via a NumPy UserWarning surfaced in #404's CI (unrelated PR - just where it was noticed).

`pyopia.statistics.get_j()`:

```python
p = np.polyfit(
np.log(dias[ind]),
np.log(number_distribution[ind], where=number_distribution[ind] > 0),
1,
)
```

`where=` without a matching `out=` tells NumPy to skip computing `log()` for non-positive values (guarding against `log(0)`/`log(negative)`), but the skipped positions in the result are left as uninitialized memory - whatever bytes happened to already be there - not zero, not NaN. That array is then fed directly into `np.polyfit` for the Junge slope fit.

`number_distribution` is a real particle-count distribution, so a zero-count bin in the 150-300 um fitting range is a completely normal situation (an empty size bin), not an edge case. Whenever that happens, the Junge slope calculation is currently corrupted by garbage at exactly those points, rather than excluding them as the `where=` clause seems to have intended.

`get_j()` is called from `nc_vc_from_stats()`, which returns `junge_slope` as one of its four outputs and ultimately populates the `junge` column in `image_stats` (see `pyopia.process.CalculateImageStats`) - so this affects real, user-facing output, not just an internal detail.

Fix: exclude non-positive `number_distribution` bins from the fit entirely, by folding `number_distribution > 0` into the `ind` mask used for both the x (`log(dias)`) and y (`log(number_distribution)`) arrays, rather than only masking the `log()` call itself.

No existing test coverage for `get_j()`.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions