Skip to content

Rename img_as_* functions to rescale_to_*#7697

Closed
lagru wants to merge 17 commits intoscikit-image:mainfrom
lagru:rename_img_as
Closed

Rename img_as_* functions to rescale_to_*#7697
lagru wants to merge 17 commits intoscikit-image:mainfrom
lagru:rename_img_as

Conversation

@lagru
Copy link
Copy Markdown
Member

@lagru lagru commented Feb 6, 2025

Description

according to the skimage2 roadmap. The new functions are no longer available in our top-level skimage module as a welcome side effect.

This also proposes

  • to add the bit-depth in the new name of
    • img_as_int (to rescale_to_int16) and
    • img_as_uint (to rescale_to_uint16).
  • to always scale to [0, 1] when the target dtype is float (previously this happened only if the input was integer)
  • a temporary flag legacy_float_range to preserve the old behavior during the deprecation period.

Closes #1234.

Checklist

Release note

For maintainers and optionally contributors, please refer to the instructions on how to document this PR for the release notes.

...

lagru added 3 commits February 6, 2025 12:47
I opted for `rescale_to_int16` and `rescale_to_uint16`, since that makes
the bit-depth a lot more obvious at a first glance.
@lagru lagru added 🔽 Deprecation Involves deprecation 📜 type: API Involves API change(s) 🥾 Path to skimage2 A step towards the new "API 2.0" labels Feb 6, 2025
@lagru lagru added this to the 0.26 milestone Feb 6, 2025
lagru added 2 commits February 6, 2025 14:58
including all public places where they could be imported from.
@lagru lagru marked this pull request as ready for review February 6, 2025 14:01
hmaarrfk
hmaarrfk previously approved these changes Feb 6, 2025
Copy link
Copy Markdown
Member

@stefanv stefanv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @lagru, looks good to me.

Do you think we can get away with removing the (often confusing) notion of [-1, 1] images? Users can, of course, easily rescale themselves to that range: (rescale_to_float(mydata) * 2) - 1.

@lagru lagru added this to skimage2 Mar 3, 2025
@github-project-automation github-project-automation bot moved this to To Do in skimage2 Mar 3, 2025
@lagru lagru moved this from To Do to In progress in skimage2 Mar 3, 2025
@lagru lagru removed this from skimage2 Mar 3, 2025
@lagru lagru added this to skimage2 Mar 3, 2025
@github-project-automation github-project-automation bot moved this to To Do in skimage2 Mar 3, 2025
@lagru lagru moved this from To Do to In progress in skimage2 Mar 3, 2025
@lagru lagru removed this from skimage2 Mar 3, 2025
@lagru lagru linked an issue Mar 4, 2025 that may be closed by this pull request
lagru added 5 commits March 4, 2025 22:04
Not sure if this is the right call. One one hand we don't want to
advertise these anymore. But technically not including them might break
users who do `from skimage.util import *`.
# ==========================

data = ski.util.img_as_float(ski.data.cells3d()[:, 1, :, :]) # grab just the nuclei
data = ski.util.rescale_to_float(ski.data.cells3d()[:, 1, :, :]) # grab just the nuclei
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In places where rescale_to_float* functions are seen by users, I checked whether setting legacy_float_range=True is necessary. In this case, cells3d() returns a an uint8-array. So this parameter has no effect (it's only meaningful for signed ints).

testing.assert_allclose(img2, r2.astype(bool))

img3 = img_as_float(img)
img3 = rescale_to_float(img, legacy_float_range=True)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cases like this, legacy_float_range=True wouldn't be necessary, img is boolean, but for internal stuff I chose to be on the safe side and just add legacy_float_range=True. That was a lot faster than me going through every case and trying to determine if the input could be an signed integer, and potentially missing an edge case and introducing an unnecessary error.

@stefanv
Copy link
Copy Markdown
Member

stefanv commented Mar 4, 2025

I'm also proposing to add the bit-depth in the new name of

img_as_int (to rescale_to_int16) and
img_as_uint (to rescale_to_uint16).

👍 How about rescale_to_float64 then?

@stefanv
Copy link
Copy Markdown
Member

stefanv commented Mar 4, 2025

ubyte -> uint8?

@lagru
Copy link
Copy Markdown
Member Author

lagru commented Mar 4, 2025

ubyte -> uint8?

Can do. (Edit see 785d728)

👍 How about rescale_to_float64 then?

rescale_to_float64 already exists? And rescale_to_float has it's own special behavior for lower precision floats.

Copy link
Copy Markdown
Member

@stefanv stefanv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments; but overall looks good to me.

I like your suggestion to stick with numpy conventions!

By default and if ``False``, the contents of integer images will be
scaled to the range [0.0, 1.0] if the target `dtype` is floating point.
However, if legacy behavior is enabled, images with signed integers
will be scaled to [-1.0, 1.0] instead. This parameter as no effect on
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the parameter not affect floating point images? I suppose because, if they already include negative values, they go to [-1, 1], otherwise to [0, 1]?

Copy link
Copy Markdown
Member Author

@lagru lagru Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the rescaling only happens if a conversion from int is necessary. Floats are considered fine and just the dtype is potentially changed. E.g.

ski.util.rescale_to_float64(np.array([-10, 10], dtype=float))
# array([-10.,  10.])
ski.util.rescale_to_float64(np.array([-10, 10], dtype=np.float32))
# array([-10.,  10.])

At least that's the same behavior as the img_as_float* functions have.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, that seems very wrong!

Copy link
Copy Markdown
Member Author

@lagru lagru Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I found that a bit weird too. We could totally change this in a follow-up PR? E.g. make legacy_float_range=True preserve the old weird behavior and make the default always scale between [0, 1] regardless of input dtype.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the new function, that should certainly be the case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a first attempt at addressing this in fc09911. The docstrings probably need more polishing. But have a look. I'm curious what you think of the edge case handling (inf -> NaN, uniform -> 0) in _normalize_float_0_to_1.

@stefanv
Copy link
Copy Markdown
Member

stefanv commented Mar 4, 2025

One other observation: we rely on this flag in our test suite. What do we do when we remove that flag? It would be good to get the test suite in good enough shape not to need this flag anywhere.

Then again, since the default range changes in 2.0, perhaps all those test outcomes change as well, and we're fine. Just worth checking.

@lagru
Copy link
Copy Markdown
Member Author

lagru commented Mar 4, 2025

One other observation: we rely on this flag in our test suite. What do we do when we remove that flag? It would be good to get the test suite in good enough shape not to need this flag anywhere.

Then we pay extra close attention. In most cases it should be pretty obvious what dtype goes in. I mainly didn't do it in this PR as to not delay it even further. But could be a follow-up PR...

Co-authored-by: Stefan van der Walt <sjvdwalt@gmail.com>
@lagru lagru added this to skimage2 Mar 5, 2025
@github-project-automation github-project-automation bot moved this to To Do in skimage2 Mar 5, 2025
@lagru lagru moved this from To Do to To Review in skimage2 Mar 5, 2025
Copy link
Copy Markdown
Member

@stefanv stefanv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One discussion to wrap up, but I think this is good to go?

By default and if ``False``, the contents of integer images will be
scaled to the range [0.0, 1.0] if the target `dtype` is floating point.
However, if legacy behavior is enabled, images with signed integers
will be scaled to [-1.0, 1.0] instead. This parameter as no effect on
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the new function, that should certainly be the case.

raise NotImplementedError("phase_cross_correlation unavailable")
shifts = (-2.3, 1.7, 5.4, -3.2)[:ndims]
phantom = img_as_float(data.binary_blobs(length=image_size, n_dim=ndims))
phantom = rescale_to_float(data.binary_blobs(length=image_size, n_dim=ndims))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the update, this now always scales float arrays to the target range [0, 1]. Still need to check if this affects any of these gallery examples in a negative way.

@lagru lagru requested a review from stefanv March 8, 2025 16:16
@stefanv
Copy link
Copy Markdown
Member

stefanv commented Mar 8, 2025

Pyodide test failures are related.

@stefanv
Copy link
Copy Markdown
Member

stefanv commented Mar 8, 2025

@lagru For consistency, can we use only rescale_to_float32 and rescale_to_float64, and hide rescale_to_float, even if we have to keep it for backward compatibility?

Copy link
Copy Markdown
Member

@stefanv stefanv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

out -= float_min / float_ptp

except FloatingPointError:
if float_ptp == 0:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to do an early exit without doing anything if float_ptp is zero.

@lagru
Copy link
Copy Markdown
Member Author

lagru commented Mar 11, 2025

See my comment in #1234 (comment). Closing this for now, while we resolve that discussion on which direction we want to take this.

@lagru lagru closed this Mar 11, 2025
@github-project-automation github-project-automation bot moved this from To Review to Done in skimage2 Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔽 Deprecation Involves deprecation 🥾 Path to skimage2 A step towards the new "API 2.0" 📜 type: API Involves API change(s)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Rename conversion functions rescale_to_*

3 participants