Skip to content

Add vit attribution hugo - #163

Open
HugoDeBosschere wants to merge 183 commits into
devfrom
add_vit_attribution_hugo
Open

HugoDeBosschere wants to merge 183 commits into
devfrom
add_vit_attribution_hugo

Conversation

@HugoDeBosschere

@HugoDeBosschere HugoDeBosschere commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Description

This is the PR that merges the Image side of the library with the Text side.
I checked with notebooks that were previously on this branch that the outputs of both the LegacyImageAttributionExplainers and the LegacyTextAttributionExplainers are the same as the outputs of the merged version.

All the tests should pass.

The SobolPerturbator was easily adapted to image by just removing the binarization of the mask at the end (mask < 0.5).

To handle arguments specific to certain modality, it was chosen to do that through the AttributionExplainer
(eg if isinstance(self.perturbator, ImageMaskPerturbator):
self.perturbator.patch_size = self.patch_size
self.perturbator.granularity_combination_strategy = self.resize_strategy )

bad_arguments_decorator.py was added in commons to create a decorator allowing to explain to current v0 user why their code suddenly breaks with the update and how to easily fix the problem. a test file was added to cover this behavior and put in a specific file because the use of the decorator may be short-lived.

Type of Change

  • 💥 Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I've read the CODE_OF_CONDUCT.md document.
  • I've read the CONTRIBUTING.md guide.
  • I've successfully run the style checks using make lint.
  • I've written tests for all new methods and classes that I created and successfully ran make test.
  • I've written the docstring in Google format for all the methods and classes that I used.

HugoDeBosschere and others added 30 commits May 26, 2026 17:40
Additive image-modality extension on top of the merged attr-inference-refacto:

- ImageClassificationInferenceWrapper (ClassificationInferenceWrapper subclass):
  __init__ drops pad_token_id lookup; _prepare_inputs stacks pixel tensors
  without padding; _compute_gradients differentiates w.r.t. pixel_values and
  collapses channels via .abs().mean(dim=1).flatten() to fit the 1D `l` contract
  (l = H*W). Runtime assert on (3, H, W) channel dim in _prepare_inputs.

- ImageGranularity (standalone Enum, can't subclass Granularity): PIXEL/PATCH
  with DEFAULT=PATCH; duck-typed get_indices, get_association_matrix,
  granularity_score_aggregation (no generation branch), and get_decomposition
  returning (row, col) int tuples instead of strings. Generation/text-only
  branches are stripped; PATCH aggregation asserts >=2 pixels per unit.

- ImageAttributionOutput + ImageClassificationAttributionExplainer in a new
  attributions/image_base.py: AttributionOutput mirror with ImageGranularity
  default and tuple-coordinate elements; explainer subclasses
  ClassificationAttributionExplainer, swaps tokenizer for image_processor,
  drops the text-side setup_token_ids call (no pad/mask tokens for ViT),
  adds a preprocess flag, accepts PIL/numpy/torch.Tensor/BatchFeature in
  process_model_inputs, and rewrites explain() with patch_size in place of
  tokenizer and ImageAttributionOutput as the output type.

No tests, no perturbator, no visualization yet — gradient-only MVP.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…on method)

- ImagePerturbator: no-op image-side perturbator. Subclass of Perturbator that
  returns (model_inputs, None), replacing the text-keyed default that would
  KeyError on a ViT BatchFeature.
- ImageSaliency: thin subclass of ImageClassificationAttributionExplainer with
  use_gradient=True, input_x_gradient=True; no MultitaskExplainerMixin
  (classification-only MVP). Defaults to ImagePerturbator + default Aggregator.
- Wire ImagePerturbator as the default fallback in ImageClassificationAttributionExplainer.
- Re-exports through perturbations/, methods/, attributions/, and top-level
  interpreto/ (alongside ImageGranularity).
- Sanity-runs end-to-end against hf-internal-testing/tiny-random-vit: returns
  (1, 225) attributions matching the model's 15x15 patch grid.
- first_tests/first_test_image.py: ad-hoc sanity script (not a pytest test).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ded the firs_test_image.py file to check that it works
…near_interpolation_image_perturbation and the downstream methods that depend on it: ImageSmoothGrad, ImageIntergratedGradients and ImageGradientShap. I also added the tests of the methods in first_tests/first_test_image.py and plotted the results of the methods on a similar graph
…s ie the counterpart of IdsPerturbator for images. Added the specific logic for the perturbator and the actual explainer method for sobol, lime, occlusion, var_grad and square_grad. Also added it for kernel_shap but modified compared to the text version because the weighted sampling of the text version does not correspond to the actual shapley kernel. Modified first_tests/ to check with a tiny random vit that it prints something. also modified the relevant __init__.py files to add the newly defined techniques
…default number of perturbations to 10 for easier testing. for image_base.py the source of truth for patch_size is int the attributionexplainer
…the same plot and decided to leave the rescaling to the imshow function rather than leaving it to _prepare_heatmap so that we may plot a color bar on the side of each image that corresponds to the unormalized scores. adapted the actual_vit.py example to work with the new function to plot several different techniques on the same plot
…ngsPerturbator now becomes TensorPerturbator. perturb_embeds thus become perturb_tensor. IdsPerturbator becomes MaskPerturbator. Both kinds of perturbation have (task,modality) tuple children (Image Generation does not exist). There is also a new ImageInferenceWrapper and TextInferenceWrapper that both inherit from the InferenceWrapper class (which may need to be abstracted). AttributionExplainer has been abstracted and there are the same (task, modality) children as for the perturbations. All the other changes are just casading from these modifications (import changes, inheritance changes). I also had to copy and paste process_targets and process_inputs_to_explain_and_targets from TextClassificationAttributionExplainer to ImageClassificationAttributionExplainer since the latter used to inherit from the former and needed those methods to function correctly.
…he necessary imports into the files that were affected by this change
…of FactoryGeneratedMeta to avoid metaclass conflict. Changed the attributions test and ran them to ensure that nothing was broken by the new modifications
…y and created a new parent class called Granularity to harmonize typing in order to be able to merge the image attribution methods with the text attribution methods. Executed the pytest tests and they still work.
… for images in order to have a more image point of view on the resize explanations. Added 3 types of interpolation: BILINEAR, BICUBIC and AREA (which is just a mean) all derived from the torch library in order to be able to do the interpolation on gpu (I also changed the moment where contributions was put on cpu in order to be stored in ImageAttributionOutput). GranularityAggregationStrategy and GranularityResizeStrategy now both inherit from GranularityCombinationStrategy following the same pattern as the one from Granularity. This is done in order to then be able to implement all the methods in one and only class. All the Granularity related classes / methods have been put on the granularity.py file though the image_granularity.py files remain because I have not yet tested the changes.
… the image*.py files for now as long as we're not sure everything runs fine
… ImageMaskPerturbator. Went from something that was almost a copy paste of the text modality to interpolation methods that are more adapted for working on images. The real_mask matrix is now created using the resize function of the GranularityResizeStrategy class. A new class ha been added to GranularityResizeStategy (NEAREST) to keep the original behavior of methods such as Occlusion. To avoid leaking of arguments into the perturbator when we will want to merge later on, the granularity_combination_strategy is given to the ImageMaskPerturbator thourgh the ImageClassificationExplainer when the perturbator is an instance of ImageMaskPerturbator
… coming from mask based

perturbations. This implied changing ImageAttirbutionOuput: adding a attributions_image field in ImageAttributionOuptut containing the image reshaped to pixel space ready to be plotted in the visualization function. This takes away the responsability of resizing/intrepolation from the viz to the explainer. Scraped the elements field which was only needed for the visualization in order to know which patch went where. kept the attributions field in prevision for the Insertion Deletion metric. Changed some dim annotations for mask_generator which can both be t l and t g. Renamed granularity_resize_strategy in resize_strategy (I felt it was clearer this way). Added a function called
resize_image in granularity.py that handles the resizing of the explanation to image size through the chosen resize_strategy. To clarify what the new flow is for mask-based methods: first we sample mask of size (t g) then we resize the mask to pixel space (t h w) (= real_mask). we apply the real mask to perturb the inputs but perturb returns
gran_mask and we aggregate using the value of gran_mask (this is because the computation would take too long with real_mask). we obtain an explanation of size (t g) that we resize with the same resize method to (t h w). this is then shown by the viz function.
…ecute all the code twice on the Mask based methods. This did not have a lot of ripple effect and cleanly separates different responsibilities
…y the AttributionExplainer. Should fix some tests that previously did not pass. Also uncommented a lot of stuff that should already have been uncommented
@AntoninPoche
AntoninPoche changed the base branch from add_vit_attribution_dev to main September 3, 2026 14:33

@AntoninPoche AntoninPoche left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall really solid pull request, thank you very much for your work!!

In addition, I would like a small tutorial notebook.

Most of the comments on the core are questions and curiosity, notably on merging even more text and image.

But there are quite a lot of comments on the tests. You can see that the tests are not validated on the github runner because they timeout the 10 minutes.

This is mainly due to large cartesian product between parameters on "fast" tests. The comments further detail how to solve this. In addition, we should refrain from using "real" models in the fast tests (only hf-internal-testing). If one slipped through in the text, it is an error from our side.

Nonetheless, really nice work!

Comment thread interpreto/attributions/methods/sobol_attribution.py Outdated
Comment on lines +50 to +52
from interpreto.attributions.inference_wrappers.text_classification_inference_wrapper import (
TextClassificationInferenceWrapper,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Surely also need the generation one. This one is a mistake on my part on the merge. Just adding a comment not to forget.

Comment thread interpreto/attributions/perturbations/base.py Outdated
Comment thread interpreto/attributions/perturbations/base.py Outdated
raise ValueError(
"Inputs are treated one by one in the perturbator, "
f"but received pixel_values of shape {tuple(pixel_values.shape)} "
"- expected shape (1, 3, H, W)."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You say that the expected shape is (1, 3, H, W), but is the 3 something hardcoded? Could it be 1 or more (like for satellite images)?

If so, you could remove all (n, 3, H, W) by (n, C, H, W).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It is hardcoded in several places for now but not because of any real need for it, solely because I previously thought that well formed inputs must have 3 channels. It would be a shame to not support satellite images just because of that so I can totally change the code (_validate_batch_feature eg) but I do have to change the code and not just comments.

I'm waiting for your confirmation to go ahead on this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think going with c instead of 3 is better.

Comment thread tests/test_image_granularity.py Outdated
)


def test_resize_fail(null_matrix, wrong_patch_size):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do not use fixtures for this; write them in the test.

A good rule of thumb is, does it increase or decrease the number of lines? For these, it increases it.

The second is: how costly is it to create the object, and how many times do I need it, compared to the amount of memory it will take up while running the tests?

Here, creating these objects does not take any time.

strategy.resize(input=null_matrix, output_size=None, patch_size=wrong_patch_size)


def test_resize_nearest(matrix):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

matrix, if defined just before the tests, can be a fixture. It emphasizes that tests use the same object.

def test_resize_nearest(matrix):
strategy = GranularityResizeStrategy.NEAREST
bool_tensor = strategy.resize(matrix, output_size=(2, 2)) == torch.tensor([[1, 0], [0, 3]])
assert bool_tensor[0, 0, 0] and bool_tensor[0, 0, 1] and bool_tensor[0, 1, 0] and bool_tensor[0, 1, 1], (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here I would write this differently for clarity, you can parametrize with the strategies and the expected:

@pytest.mark.parametrize("strategy, expected", [
    (GranularityResizeStrategy.NEAREST, [[1, 0], [0, 3]]),
    (GranularityResizeStrategy.BILINEAR, [[30 / 49, 15 / 49], [15 / 49, 65 / 49]]),
    (GranularityResizeStrategy.BICUBIC, [[ 0.981445, -0.278320], [-0.278320,  2.075195]]),  # from chatgpt
    (GranularityResizeStrategy.AREA, [[0.75, 0.0], [0.0, 1.75]])
])
def test_resize(strategy, expected, matrix):
    resized = strategy.resize(matrix)
    assert torch.allclose(resized, expected, atol=1e-5), (
        f"{strategy} resize of the 4x4 diagonal matrix to 2x2 must match the analytically expected values within 1e-5"
    )

)


# NOTE: this used to be a test for the now useless granularity_resize function. We keep it for now in case it proves useful later

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think it can be removed.

GranularityResizeStrategy.AREA,
],
)
def test_resize_to_image_output_size(granularity, h_in, w_in, t, strategy):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am not sure I understand the difference between this test and the first one in this file...

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Public explainer classes are replaced by functions, HTML rendering permits script injection, and several correctness and compatibility regressions remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR unifies text and image attribution pipelines, adds ViT support, and introduces breaking API renames with migration diagnostics.

Changes:

  • Adds image granularities, inference, perturbation, visualization, and tests.
  • Generalizes attribution methods across text and image modalities.
  • Renames public parameters and text-specific classes.
File summaries
File Description
tests/visualizations/test_text_attributions.py Updates license text.
tests/visualizations/test_concepts.py Adds a license header.
tests/visualizations/test_commons.py Adds a license header.
tests/test_text_granularity.py Migrates tests to TextGranularity.
tests/test_image_granularity.py Tests image resizing and granularities.
tests/test_bad_arguments_decorator.py Tests renamed-argument diagnostics.
tests/concepts/splitters/test_model_with_split_points.py Updates text granularity references.
tests/concepts/interpretation/test_inputs_to_concepts_attributions.py Updates the Sobol parameter name.
tests/attributions/test_text_base.py Uses renamed text explainer APIs.
tests/attributions/test_image_base.py Tests image explainer processing.
tests/attributions/test_base_text_generation.py Uses the renamed generation explainer.
tests/attributions/test_aggregators.py Adapts aggregator tests to merged perturbators.
tests/attributions/perturbations/test_text_perturbators.py Tests modality-composed text perturbators.
tests/attributions/metrics/test_insertion_deletion.py Migrates metric tests to text granularity.
tests/attributions/methods/test_text_methods_sanity.py Updates text method construction.
tests/attributions/methods/test_sobol.py Tests the generalized Sobol API.
tests/attributions/methods/test_lime.py Tests the generalized LIME API.
tests/attributions/methods/test_kernelshap.py Tests generalized KernelSHAP.
tests/attributions/methods/test_image_methods_sanity.py Adds end-to-end image sanity checks.
tests/attributions/methods/test_attribution_methods_tensor.py Updates a documented Sobol argument.
tests/attributions/methods/test_attribution_methods_NLP.py Migrates NLP method tests.
tests/attributions/inference_wrappers/test_text_generation_inference_wrapper.py Uses the renamed text wrapper.
tests/attributions/inference_wrappers/test_text_classification_inference_wrapper.py Uses the renamed text wrapper.
tests/attributions/inference_wrappers/test_inference_wrapper.py Updates wrapper references and licensing.
tests/attributions/inference_wrappers/test_image_classification_inference_wrapper.py Tests image inference and gradients.
pyproject.toml Adds a dependency and lint exclusion.
interpreto/visualizations/text_attributions.py Adds text attribution HTML rendering.
interpreto/visualizations/css/visualization.css Removes trailing whitespace.
interpreto/visualizations/concepts.py Adds a license header.
interpreto/visualizations/commons.py Adds a license header.
interpreto/visualizations/__init__.py Exports text and image plots.
interpreto/concepts/splitters/model_with_split_points.py Migrates concept splitting to text granularity.
interpreto/concepts/base.py Updates granularity documentation.
interpreto/commons/bad_arguments_decorator.py Adds renamed-argument errors.
interpreto/commons/__init__.py Exports new modality utilities.
interpreto/attributions/plots/__init__.py Adds the plots package.
interpreto/attributions/perturbations/sobol_perturbation.py Generalizes Sobol masks.
interpreto/attributions/perturbations/shap_perturbation.py Generalizes SHAP masks.
interpreto/attributions/perturbations/random_perturbation.py Generalizes random masking.
interpreto/attributions/perturbations/occlusion_perturbation.py Generalizes occlusion masks.
interpreto/attributions/perturbations/linear_interpolation_perturbation.py Supports text and image tensors.
interpreto/attributions/perturbations/insertion_deletion_perturbation.py Updates tokenizer typing.
interpreto/attributions/perturbations/gradient_shap_perturbation.py Supports image-shaped baselines.
interpreto/attributions/perturbations/gaussian_noise_perturbation.py Generalizes Gaussian perturbations.
interpreto/attributions/perturbations/base.py Introduces modality-specific perturbator bases.
interpreto/attributions/perturbations/__init__.py Exports generalized perturbators.
interpreto/attributions/metrics/insertion_deletion.py Uses renamed text wrappers and token setup.
interpreto/attributions/methods/var_grad.py Adds multimodal VarGrad support.
interpreto/attributions/methods/square_grad.py Adds multimodal SquareGrad support.
interpreto/attributions/methods/sobol_attribution.py Adds multimodal Sobol support.
interpreto/attributions/methods/smooth_grad.py Adds multimodal SmoothGrad support.
interpreto/attributions/methods/saliency.py Adds multimodal saliency support.
interpreto/attributions/methods/occlusion.py Adds multimodal occlusion support.
interpreto/attributions/methods/lime.py Adds multimodal LIME support.
interpreto/attributions/methods/kernel_shap.py Adds multimodal KernelSHAP support.
interpreto/attributions/methods/integrated_gradients.py Adds multimodal integrated gradients.
interpreto/attributions/methods/gradient_shap.py Adds multimodal GradientSHAP support.
interpreto/attributions/inference_wrappers/text_generation_inference_wrapper.py Renames the generation wrapper.
interpreto/attributions/inference_wrappers/text_classification_inference_wrapper.py Renames the classification wrapper.
interpreto/attributions/inference_wrappers/image_classification_inference_wrapper.py Adds image classification inference.
interpreto/attributions/inference_wrappers/__init__.py Exports modality-specific wrappers.
interpreto/attributions/aggregations/sobol_aggregation.py Renames the Sobol sample parameter.
interpreto/attributions/aggregations/base.py Supports channel-aware gradient tensors.
interpreto/__init__.py Exports image and text granularities.
docs/api/concepts/probes.md Adjusts example formatting.
docs/api/concepts/overview.md Reformats a constructor example.
docs/api/attributions/overview.md Updates attribution API typing.
.gitignore Ignores generated sanity-check images.
.github/workflows/build.yml Pre-caches image and additional text models.
Review details

Suppressed comments (2)

interpreto/commons/bad_arguments_decorator.py:93

  • Like general_bad_argument, this class decorator replaces Sobol with a function, breaking class identity and subclassing. This must also wrap Sobol.__init__ in place so stacking both decorators still returns the original class.
    docs/api/attributions/overview.md:93
  • AttributionExplainer now accepts processor, not tokenizer, and supports both text and image processors. Keeping the removed keyword in this API reference makes the documented call fail.
- `tokenizer` (`PreTrainedTokenizerBase`): Hugging Face tokenizer associated with the model,
  • Files reviewed: 71/78 changed files
  • Comments generated: 7
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


import torch
from beartype import beartype
from jaxtyping import Float, jaxtyped
Comment on lines +70 to +78
@functools.wraps(func)
def wrapper(*args, **kwargs):
if "tokenizer" in kwargs:
raise TokenizerError()
if "granularity_aggregation_strategy" in kwargs:
raise AggregationStrategyError()
return func(*args, **kwargs)

return wrapper
device: torch.device | None = None,
batch_size: int = 4,
n_input_perturbations: int = 32,
sobol_indices_order: SobolIndicesOrders = SobolIndicesOrders.TOTAL_ORDER,
Comment thread pyproject.toml
"nvidia-cusparse-cu11>=11.7.4.91; sys_platform=='Linux'",
"nvidia-nccl-cu11>=2.14.3; sys_platform=='Linux'",
"nvidia-nvtx-cu11>=11.7.91; sys_platform=='Linux'",
"torchvision>=0.27.0",
Comment on lines +437 to 438
test_attribution_output_size(bert_model, bert_processor, Occlusion, sentences)
test_attribution_output_size(bert_model, bert_tokenizer, VarGrad, sentences)

- `model` (`PreTrainedModel`): Hugging Face model to explain,
- `tokenizer` (`PreTrainedTokenizer`): Hugging Face tokenizer associated with the model,
- `tokenizer` (`PreTrainedTokenizerBase`): Hugging Face tokenizer associated with the model,
"hf-internal-testing/tiny-random-vit",
"hf-internal-testing/tiny-random-BeitForImageClassification",
"hf-internal-testing/tiny-random-ViTForImageClassification",
"akahana/vit-base-cats-vs-dogs",
Comment on lines +70 to +74
IMAGE_CLASSIFICATION_MODELS = [
"hf-internal-testing/tiny-random-vit",
"hf-internal-testing/tiny-random-BeitForImageClassification",
"hf-internal-testing/tiny-random-ViTForImageClassification",
]

@AntoninPoche AntoninPoche Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would extend and change this list, we have some big misses, in particular, the dino models.

[
    "hf-internal-testing/tiny-random-ViTForImageClassification",
    "hf-internal-testing/tiny-random-BeitForImageClassification",
    "hf-internal-testing/tiny-random-DeiTForImageClassification",
    "hf-internal-testing/tiny-random-SwinForImageClassification",

    # Not hf-internal-testing:
    "bumblebee-testing/tiny-random-Dinov2Model-use_swiglu_ffn-True",
    "optimum-intel-internal-testing/tiny-random-dinov-3",
]

Comment thread interpreto/attributions/base.py Outdated
# rebuilds from pixel_values alone, so any other key a BatchFeature legally carries
# (bool_masked_pos, interpolate_pos_encoding) would be dropped here. Nothing in scope
# produces them; revisit if a head that needs them comes into scope.
processed = self.image_processor(validated["pixel_values"], return_tensors="pt")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With this line, you remove any keys that are not "pixel_values".

In addition, the provided inputs are already BatchFeature. Therefore, once validated, I see no reason to process again. I am unsure exactly what the processor does, but I imagine users will not expect us to process the inputs in this case again.

@AntoninPoche
AntoninPoche changed the base branch from main to dev September 18, 2026 11:53
…maximalist way of showing all the possible shapes rather than using *rest
…ybody understand anything, changed the default value of TextGranularity from ALL_TOKENS to WORD
… Also changed slightly a test to correspond to the changes made to the code
…rityAggregationStrategy and GranularityResizeStrategy as it was only useful for typing and for tyding up a bit the inheritance. Instead replaced the typing by GranularityAggregationStrategy | GranularityResizeStrategy which allows also the user to know all the values of the Enum they have access to. Changed other stuff to settle bugs / tests problems
…lowing for the possibility of downsampling which was not used anywhere in the library and not really wished for either

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants