Skip to content

121 the modified sinc smoother implementation#213

Merged
paucablop merged 14 commits intopaucablop:mainfrom
NusretSalli:121-The-Modified-Sinc-Smoother-Implementation
Nov 2, 2025
Merged

121 the modified sinc smoother implementation#213
paucablop merged 14 commits intopaucablop:mainfrom
NusretSalli:121-The-Modified-Sinc-Smoother-Implementation

Conversation

@NusretSalli
Copy link
Copy Markdown
Collaborator

This PR tries to close #121 - I don't think it is needed to be squashed before merging, but ultimately, this can be done if needed.

Description of the implementation

Based on the issue, it was suggested that a base FIR class was to be created, since Savitzky-Golay- and the Modified Sinc Smoother both belong to the same type of filter (which is Finite Impulse Response filter). The implementation has been carried out by creating/updating the following files:

  • chemotools/smooth/_Base.py has been updated to include the new class _BaseFIRFilter used for the Savtizky-Golay filter as well as the modified sinc smoother
  • chemotools/smooth/_modified_sinc_smoother.py has been created to implement the modified sinc smoother based on the following paper: Schmid, M.; Rath, D.; Diebold, U. "Why and How Savitzky–Golay Filters Should Be Replaced." ACS measurement science Au 2022, 2 (2), 185-196.
  • chemotools/smooth/_savitzky_golay_filter.py has been updated to have it use the _BaseFIRFilter class.
  • tests/smooth/test_modified_sinc.py have been added to test the functionality of the smoother.

Note: I realized that while the file was created to test Saviztky-Golay filter, it didn't have any. For now I haven't added any, but if needed, I can do that 😃

Results from the implementation

I've gone ahead and tried to test / highlight the changes (or for Savtizky-Golay's case; no changes 😆 ) to make sure the implementations can be used as intended. This has been done by using data from chemotools - namely the fermentation and coffee data.

To make sure that the functionality of Savtizky-Golay works as intended, the implementation from Scipy has been used as a "benchmark". From there I simply extract the kernels from both scipy's Savitzky-Golay filter and mine and compare them. I've also added the kernel for modified sinc smoother (see below):

image

Zooming in, we can see that the kernels are identical (this have been verified numerically as well but isn't as interesting as figures).

image

If there is anything, let me know! 😄

@NusretSalli NusretSalli requested a review from paucablop October 23, 2025 19:30
@NusretSalli
Copy link
Copy Markdown
Collaborator Author

NusretSalli commented Oct 23, 2025

Hmm, it seems like we have some issues regarding installing task where we hit an API rate limit 😢 - any experience with this?

It seems like the sphinx documentation didn't create any problems. I assume it is because it uses the taskfile.dev while the check that fails doesn't?

Any help would be much appreciated.

Edit: After re-running the workflow, the checks passed - don't know entirely why the mentioned error appeared. If any of you know why it might have happened, please let me know

@NusretSalli NusretSalli self-assigned this Oct 24, 2025
@NusretSalli NusretSalli requested a review from Copilot October 24, 2025 21:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements the Modified Sinc Filter smoother as suggested in issue #121 and refactors the existing Savitzky-Golay filter to use a common base class for FIR (Finite Impulse Response) filters.

Key changes:

  • Created a new _BaseFIRFilter base class to share common FIR filter functionality
  • Implemented the Modified Sinc Filter based on Schmid et al. (2022) with configurable parameters and passband-flattening corrections
  • Refactored SavitzkyGolayFilter to inherit from _BaseFIRFilter

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
chemotools/smooth/_base.py Added _BaseFIRFilter base class with common fit/transform logic and padding strategies
chemotools/smooth/_modified_sinc_smoother.py New implementation of Modified Sinc Filter with kernel computation based on sinc core, Gaussian-like window, and optional kappa corrections
chemotools/smooth/_savitzky_golay_filter.py Refactored to inherit from _BaseFIRFilter and implement kernel computation via scipy's savgol_coeffs
tests/smooth/test_modified_sinc.py Comprehensive test suite for Modified Sinc Filter covering sklearn compliance, kernel properties, boundary modes, and axis behavior
chemotools/smooth/__init__.py Added ModifiedSincFilter to public API exports

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


class ModifiedSincFilter(_BaseFIRFilter):
"""
Modified Sinc smoothing (MS) for denoising while preserving peak positions based on the paper "Why and How Savitzky–Golay Filters Should Be Replaced."
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'Savitzky–Golay' to 'Savitzky-Golay' for consistency with the filename and other references in the codebase.

Copilot uses AI. Check for mistakes.

References
----------
[1] Schmid, M.; Rath, D.; Diebold, U. "Why and How Savitzky–Golay Filters Should Be Replaced."
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'Savitzky–Golay' to 'Savitzky-Golay' for consistency with the filename and other references in the codebase.

Suggested change
[1] Schmid, M.; Rath, D.; Diebold, U. "Why and How SavitzkyGolay Filters Should Be Replaced."
[1] Schmid, M.; Rath, D.; Diebold, U. "Why and How Savitzky-Golay Filters Should Be Replaced."

Copilot uses AI. Check for mistakes.
paucablop
paucablop previously approved these changes Nov 2, 2025
Copy link
Copy Markdown
Owner

@paucablop paucablop left a comment

Choose a reason for hiding this comment

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

Hi @NusretSalli It looks awesome, very nice job, clear, and well structured!!! Thanks a lot!! 😄

return np.moveaxis(out, -1, ax)

# --- hooks for subclasses ---
def _compute_kernel(self) -> np.ndarray:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi @NusretSalli the base class looks great, just a small comment, what do you think of using the decorator @abstractmethod for the class? a bit like this one (https://github.com/paucablop/chemotools/blob/82826e4d17c03d84a91e3650fb86662eadba3593/chemotools/baseline/_base.py#L114C1-L115C1)

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.

Yes, this is something that I should have done. I'll add this right away! :)

xp = self._pad_1d(x, m)
return np.convolve(xp, self.kernel_, mode="valid") # same length as x

def _pad_1d(self, x: np.ndarray, m: int) -> np.ndarray:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think this is really nice! 🤩

if self.alpha <= 0:
raise ValueError("alpha must be positive.")

# ---- 1) Eq. 5: index -> normalized coordinate in [-1, 1] ----
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Check (really nice!) - @paucablop review 2025/11/02😎

dtype=np.float64,
)
# solve the system using linear algebra.
rhs = np.array([1.0, 0.0, 0.0], dtype=np.float64)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Clever calculation of A 🧠

@@ -0,0 +1,200 @@
# Authors: Nusret Emirhan Salli <nusret.emirhan.salli@gmail.com>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

On top of the file, could you add this?

"""
The :mod:chemotools.smooth._modified_sinc_smoother module implements the Modified Sinc Filter (SGF) transformation.
"""

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.

I will do that! I totally forgot this.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Very nice Nusret!! 💪 I checked with the reference and it is great and easy to follow!! Thanks a lot for the clear and good implementation!

@paucablop paucablop merged commit f6909f7 into paucablop:main Nov 2, 2025
5 checks passed
),
dtype=np.float64,
)
except Exception:
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 like to drop the note that this computation can lead to numerical problems, especially for higher polynomial degrees and window sizes (found to be useful here).

The same problem applies to scipy (see this issue).
For fixing this problem, the algorithm needs to be adapted (see here).

Just a side note, no need for immediate action 😅

@NusretSalli NusretSalli deleted the 121-The-Modified-Sinc-Smoother-Implementation branch November 24, 2025 19:16
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.

Implement successor of Savitzky-Golay: The Modified Sinc Smoother

4 participants