Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update extract_values API and add new split_values filter #6001

Merged
merged 26 commits into from
May 5, 2024

Conversation

user27182
Copy link
Contributor

@user27182 user27182 commented Apr 30, 2024

Overview

Follow up from #5963.

In this PR:

  • Update/modify the API for extract_values to make it more general and more explicit
  • Enable extracting values from multi-component scalar arrays
  • Add a new split_values filter (direct alternative to split_labels filter originally proposed in Add split_labels filter and update split_bodies #5950)

These features are motivated by #5968 to enable extracting surfaces generated by the contour_labels filter, which outputs a 2-component scalar array.

Details

  • Add component_mode parameter with options for extracting values using a single component, 'any' component, 'all' components, or specific 'multi' component
  • Separate values parameter into values and ranges. This makes things more explicit and allows setting a range directly with [lower, upper] instead of requiring [[lower, upper]]
  • values now supports specifying multi-component vectors when component_mode is values
  • Separate keep_original_ids param into pass_point_ids and pass_cell_ids parameters. This is more consistent with other filters.
  • Make include_cells param dynamic to allow for use with PointSets
  • Remove default behaviour of extracting unique values and splitting the output when no input values are specified.
    • ``split=True` must now be set explicitly to split meshes
    • Empty input args are no longer accepted
    • This default behaviour is what split_values does instead.

@pyvista-bot pyvista-bot added the enhancement Changes that enhance the library label Apr 30, 2024
Copy link

codecov bot commented Apr 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.95%. Comparing base (4c71dd3) to head (718fec7).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6001      +/-   ##
==========================================
+ Coverage   96.94%   96.95%   +0.01%     
==========================================
  Files         140      140              
  Lines       24413    24494      +81     
==========================================
+ Hits        23668    23749      +81     
  Misses        745      745              

@user27182
Copy link
Contributor Author

@pyvista-bot preview

@pyvista-bot
Copy link
Contributor

@user27182
Copy link
Contributor Author

LGTM

Copy link
Member

@tkoyama010 tkoyama010 left a comment

Choose a reason for hiding this comment

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

We should avoid using if statements in test functions. If necessary, please split the function into multiple functions. It makes it easy to review :)


@pytest.mark.parametrize('component_offset', [0, -0.5, None])
@pytest.mark.parametrize('component', [0, 1, 'any', 'all'])
def test_extract_values_component_split(labeled_data, component_offset, component):
Copy link
Member

Choose a reason for hiding this comment

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

#6001 (review) is saying this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test has been updated to remove the if-else blocks. if-else has been replaced with explicit test case tuples which map the inputs to the expected outputs.

@user27182 user27182 marked this pull request as draft May 1, 2024 14:38
@user27182 user27182 changed the title Add component parameter to extract_values filter Update extract_values API to support multi-component scalars May 2, 2024
@user27182
Copy link
Contributor Author

@pyvista-bot preview

@pyvista-bot
Copy link
Contributor

@user27182
Copy link
Contributor Author

@pyvista-bot preview

@pyvista-bot
Copy link
Contributor

@user27182 user27182 changed the title Update extract_values API to support multi-component scalars Update extract_values API and add new split_values filter May 3, 2024
@user27182
Copy link
Contributor Author

@pyvista-bot preview

@user27182 user27182 marked this pull request as ready for review May 3, 2024 23:47
@pyvista-bot
Copy link
Contributor

@user27182
Copy link
Contributor Author

@pyvista-bot preview

@pyvista-bot
Copy link
Contributor

Copy link
Member

@tkoyama010 tkoyama010 left a comment

Choose a reason for hiding this comment

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

  • Remove default behaviour of extracting unique values and splitting the output when no input values are specified.
    • ``split=True` must now be set explicitly to split meshes
    • Empty input args are no longer accepted
    • This default behaviour is what split_values does instead.

For the above change, I would like to see a deprecation warning output if a previous input is made. Once that and the minor modifications to the docstring only are fixed, the rest is fine. thank you for making the test code easier to read.

pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
pyvista/core/filters/data_set.py Outdated Show resolved Hide resolved
Comment on lines +5569 to +5686
array_ = array_[:, component_mode_] if num_components > 1 else array_
component_logic_function = None
elif isinstance(component_mode_, str) and component_mode_ in ['any', 'all', 'multi']:
if array_.ndim == 1:
component_logic_function = None
elif component_mode_ == 'any':
component_logic_function = functools.partial(np.any, axis=1)
elif component_mode_ in ['all', 'multi']:
component_logic_function = functools.partial(np.all, axis=1)
else:
raise ValueError(
f"Invalid component '{component_mode_}'. Must be an integer, 'any', 'all', or 'multi'.",
)
return array_, num_components, component_logic_function

def _get_inputs_from_dict(input_):
# Get extraction values from dict if applicable.
# If dict, also validate names/labels mapped to the values
if not isinstance(input_, dict):
return None, input_
else:
dict_keys, dict_values = list(input_.keys()), list(input_.values())
if all(isinstance(key, str) for key in dict_keys):
return dict_keys, dict_values
elif all(isinstance(val, str) for val in dict_values):
return dict_values, dict_keys
else:
raise TypeError(
"Invalid dict mapping. The dict's keys or values must contain strings.",
)

def _validate_values_and_ranges(array_, values_, ranges_, num_components_, component_mode_):
# Make sure we have input values to extract
is_multi_mode = component_mode_ == 'multi'
if values_ is None:
if ranges_ is None:
raise TypeError(
'No ranges or values were specified. At least one must be specified.',
)
elif is_multi_mode:
raise TypeError(
f"Ranges cannot be extracted using component mode '{component_mode_}'. Expected {None}, got {ranges_}.",
)
elif (
len(seq := list(val)) == 2
and all(isinstance(item, (int, np.integer, float, np.floating)) for item in seq)
and seq[0] <= seq[1]
):
values[i] = seq
continue
raise ValueError(
f'Invalid value {val}. Value must be number or a sequence of two numbers representing a [lower, upper] range.',
)
isinstance(values_, str) and values_ == '_unique'
): # Private flag used by `split_values` to use unique values
axis = 0 if is_multi_mode else None
values_ = np.unique(array_, axis=axis)

# Validate values
if values_ is not None:
if is_multi_mode:
values_ = np.atleast_2d(values_)
if values_.ndim > 2:
raise ValueError(
f'Component values cannot be more than 2 dimensions. Got {values_.ndim}.',
)
if not values_.shape[1] == num_components_:
raise ValueError(
f'Num components in values array ({values_.shape[1]}) must match num components in data array ({num_components_}).',
)
else:
values_ = np.atleast_1d(values_)
if values_.ndim > 1:
raise ValueError(
f'Values must be one-dimensional. Got {values_.ndim}d values.',
)
if not (
np.issubdtype(dtype := values_.dtype, np.floating)
or np.issubdtype(dtype, np.integer)
):
raise TypeError('Values must be numeric.')

# Validate ranges
if ranges_ is not None:
ranges_ = np.atleast_2d(ranges_)
if (ndim := ranges_.ndim) > 2:
raise ValueError(f'Ranges must be 2 dimensional. Got {ndim}.')
if not (
np.issubdtype(dtype := ranges_.dtype, np.floating)
or np.issubdtype(dtype, np.integer)
):
raise TypeError('Ranges must be numeric.')
is_valid_range = ranges_[:, 0] <= ranges_[:, 1]
not_valid = np.invert(is_valid_range)
if np.any(not_valid):
invalid_ranges = ranges_[not_valid]
raise ValueError(
f'Invalid range {invalid_ranges[0]} specified. Lower value cannot be greater than upper value.',
)
return values_, ranges_
Copy link
Member

Choose a reason for hiding this comment

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

Are we going to move these functions to the validation package?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes we should. I was actually thinking about how much easier it would have been to simply use those instead... I'll work on finishing up #5571 soon.

@user27182
Copy link
Contributor Author

  • Remove default behaviour of extracting unique values and splitting the output when no input values are specified.

    • ``split=True` must now be set explicitly to split meshes
    • Empty input args are no longer accepted
    • This default behaviour is what split_values does instead.

For the above change, I would like to see a deprecation warning output if a previous input is made.

Thanks, I can add a deprecation warning. In this case a deprecation would probably be needed for other API changes, such as no longer allowing setting a range as part of values (this was moved to a new ranges param.).

This filter, extract_values, is brand new and not yet released. The deprecation would be useful only to anyone using this function directly from the main branch within the last week or so when #5963 was merged. Do you still want me to add deprecations?

@tkoyama010
Copy link
Member

  • Remove default behaviour of extracting unique values and splitting the output when no input values are specified.

    • ``split=True` must now be set explicitly to split meshes
    • Empty input args are no longer accepted
    • This default behaviour is what split_values does instead.

For the above change, I would like to see a deprecation warning output if a previous input is made.

Thanks, I can add a deprecation warning. In this case a deprecation would probably be needed for other API changes, such as no longer allowing setting a range as part of values (this was moved to a new ranges param.).

This filter, extract_values, is brand new and not yet released. The deprecation would be useful only to anyone using this function directly from the main branch within the last week or so when #5963 was merged. Do you still want me to add deprecations?

Oh, I see, that's fine then. No need to do it :)

@tkoyama010 tkoyama010 enabled auto-merge (squash) May 5, 2024 17:55
@user27182
Copy link
Contributor Author

@tkoyama010 all of your other suggestions (which were marked as resolved) seem reasonable, feel free to apply them all. I didn't see them at first and so only replied to the deprecation comment initially.

@tkoyama010 tkoyama010 merged commit e9f5a64 into pyvista:main May 5, 2024
27 checks passed
@user27182 user27182 deleted the feat/extract_values_component branch May 6, 2024 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Changes that enhance the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants