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

Feature/new filter #1068

Merged
merged 57 commits into from
Jan 29, 2024
Merged

Feature/new filter #1068

merged 57 commits into from
Jan 29, 2024

Conversation

kloss-o
Copy link
Contributor

@kloss-o kloss-o commented Feb 4, 2022

Until now it was not so easy to filter for multiple values with the filter function in neo. To solve this problem, I introduced "FilterConditions". These can be provided to the filter function as the value of a dictionary.

An example of the syntax:

seg.filter({“electrode_id”: FilterCondition) or seg.filter(electrode_id = FilterCondition)

In order to be able to filter for several electrode_ids, there are some objects that inherit from FilterCondition and provide different filtering options.

For example, seg.filter({"electrode_id": neo.FilterIsIn([0, 1, 5]) can be used to filter for all objects with electrode_id 0, 1 or 5.

List of all currently added FilterConditions:

-FilterEqual(x), returns id == x

-FilterIsNot(x), returns id != x

-FilterLessThan(x), returns id < x

-FilterLessThanEqual(x), returns id <= x

-FilterGreaterThan(x), returns id > x

-FilterGreaterThanEqual(x), returns id >= x

-FilterIsIn([x, y, z]), returns id == x or id == y or id == z

-FilterInRange(a, b), returns a <= id <= b

Additional FilterConditions can be added at any time and without much effort.

@pep8speaks
Copy link

pep8speaks commented Feb 4, 2022

Hello @kloss-o! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2022-06-28 12:08:16 UTC

@mdenker mdenker linked an issue Feb 8, 2022 that may be closed by this pull request
Copy link
Member

@JuliaSprenger JuliaSprenger left a comment

Choose a reason for hiding this comment

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

Thanks for looking into improving the filter functionality! This has been on our todo list for quite some time. Your approach is adding a number of new classes to neo. Intuitively this looks like an approach that might add a bit of overhead to filtering. Did you also run some performance tests with your new implementation? @samuelgarcia What do you think?

neo/core/container.py Outdated Show resolved Hide resolved
neo/core/container.py Outdated Show resolved Hide resolved
neo/core/container.py Outdated Show resolved Hide resolved
neo/core/container.py Outdated Show resolved Hide resolved
neo/core/container.py Outdated Show resolved Hide resolved
neo/core/container.py Outdated Show resolved Hide resolved
neo/core/container.py Outdated Show resolved Hide resolved
neo/test/coretest/test_container.py Outdated Show resolved Hide resolved
neo/test/coretest/test_container.py Outdated Show resolved Hide resolved
neo/test/coretest/test_container.py Show resolved Hide resolved
kloss-o and others added 3 commits February 10, 2022 14:13
Co-authored-by: Julia Sprenger <julia.sprenger@rwth-aachen.de>
Co-authored-by: Julia Sprenger <julia.sprenger@rwth-aachen.de>
Co-authored-by: Julia Sprenger <julia.sprenger@rwth-aachen.de>
@kloss-o
Copy link
Contributor Author

kloss-o commented Feb 10, 2022

Thank you for your feedback and suggestions. I'm looking into it.

@mdenker
Copy link
Member

mdenker commented Feb 10, 2022

Thanks for the PR, Oliver, and for reviewing the code, Julia.

Regarding performance, there is actually another follow-up PR by @kloss-o in prep that would improve filtering speed quite a bit, but unrelated to this concrete implementation. @kloss-o , I think when you did some benchmarking the FilterCondition implementation (without further optimization) was comparable in speed to the traditional implementation?

The idea behind the different Filter classes was, for one, that it would allow users to also define their own "custom" filters as a filter function, like (in pseudocode):

class FilterGoodElectrodesSurroundedByGoodOnes(FilterCondition)
    def test(electrode):
       return is_good_quality[electrode] and np.all(is_good_quality[i] for i in neighbor_electrodes(electrode])

Secondly, that it would not require any new or ambiguous syntax (e.g., something like ["property", "<=", 5]) in the function call to filter(). In particular, the neo filter function would continue to work as before with the existing syntax, but adds additional functionality.

However, I think there are many other options. An alternative could be to revert to, e.g., simple filter functions, at the expense of being slightly more ambiguous.

@JuliaSprenger JuliaSprenger added this to the 0.11.0 milestone Mar 3, 2022
@Moritz-Alexander-Kern
Copy link
Member

The following notebook is intended to show the application of the 'new filter' function in practice.
The use cases were derived from the Neo - Elephant tutorials and aim to demonstrate how the suggested 'new filter' improves the workflow.

Link to notebook tutorial_new_filter_examples.ipynb:

GitHub gist: https://gist.github.com/Moritz-Alexander-Kern/afdfb707bde9052c6cd02ccddd2c4b9e
GIN: https://gin.g-node.org/NeuralEnsemble/neo_elephant_teaching/src/new_filter/tutorial_new_filter_examples.ipynb

@JuliaSprenger
Copy link
Member

Here some notes on the discussion of this PR during the neo core meeting today.

@JuliaSprenger
Copy link
Member

Hi @kloss-o @Moritz-Alexander-Kern any news on this? Tell me if the notes from the last meeting are not detailed enough for you to continue on this topic.

Moritz-Alexander-Kern and others added 2 commits July 21, 2023 14:39
# Conflicts:
#	neo/core/container.py
#	neo/test/coretest/test_container.py
Copy link
Member

@apdavison apdavison left a comment

Choose a reason for hiding this comment

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

I think this is in good shape now and almost ready to merge. I have suggested some changes to filter class names, and I would also prefer it if all whitespace-only changes were reverted, to make it easier to see where the code has actually changed. The most important change, I think, is to keep the functions in their own module and do not import them into the top-level namespace, i.e. they should be used as

from neo.filters import LessThan

or

import neo.filters as nf

...nf.LessThan...

@@ -35,6 +37,11 @@
from neo.core.analogsignal import AnalogSignal
from neo.core.irregularlysampledsignal import IrregularlySampledSignal

# Import FilterClasses
from neo.core.filters import Equal, IsIn, IsNot,\
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the filters should be imported into the top-level namespace,
rather just from neo.core import filters

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Agree, I used from neo.core import filters

@@ -18,27 +20,28 @@ def unique_objs(objs):
using the "is" test.
"""
seen = set()
return [obj for obj in objs
if id(obj) not in seen and not seen.add(id(obj))]
return [obj for obj in objs if id(obj) not in seen and not seen.add(id(obj))]
Copy link
Member

Choose a reason for hiding this comment

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

please revert all white-space only changes, they make it harder to see the substantial changes.

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

White-space only changes removed.



def filterdata(data, targdict=None, objects=None, **kwargs):
"""
Return a list of the objects in data matching *any* of the search terms
Return a list of child objects matching *any* of the search terms
Copy link
Member

Choose a reason for hiding this comment

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

child of what? I think the original docstring is more understandable, perhaps put "objects in data" to make it clear that we're referring to the variable named data.

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Reverted to:
Return a list of the objects in data matching *any* of the search terms

results.append(obj)

# remove duplicates from results
res = []
[res.append(result) for result in results if not any([result is elem for elem in res])]
Copy link
Member

Choose a reason for hiding this comment

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

This looks as though it will be slow - perhaps use a set to eliminate duplicates?

Choose a reason for hiding this comment

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

I tried with:

res = list(set(results))

but I get:

>       res = list(set(results))
E       TypeError: unhashable type: 'SpikeTrain'

any ideas/help?

Copy link
Member

Choose a reason for hiding this comment

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

perhaps use the Python id() function?, e.g.,

unique_results = { id(res): res for res in results }.values()

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Perfect, thanks!

Used:

    # remove duplicates from results
    results = list({ id(res): res for res in results }.values())

@@ -188,13 +195,12 @@ class Container(BaseNeo):
# Containers that are listed when pretty-printing
_repr_pretty_containers = ()

def __init__(self, name=None, description=None, file_origin=None,
**annotations):
def __init__(self, name=None, description=None, file_origin=None, **annotations):
Copy link
Member

Choose a reason for hiding this comment

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

same comment as above about whitespace-only changes

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Removed whitespace-only changes.

raise NotImplementedError()


class Equal(FilterCondition):
Copy link
Member

Choose a reason for hiding this comment

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

I would call this Equals or EqualTo

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Used Equals

return x != self.control


class LessThanEqual(FilterCondition):
Copy link
Member

Choose a reason for hiding this comment

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

LessThanOrEquals or LessThanOrEqualTo

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Used LessThanOrEquals

return x <= self.control


class GreaterThanEqual(FilterCondition):
Copy link
Member

Choose a reason for hiding this comment

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

GreaterThanOrEquals or GreaterThanOrEqualTo

Copy link
Member

@Moritz-Alexander-Kern Moritz-Alexander-Kern Jul 28, 2023

Choose a reason for hiding this comment

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

Used GreaterThanOrEquals

@Moritz-Alexander-Kern
Copy link
Member

Moritz-Alexander-Kern commented Aug 2, 2023

Hey @apdavison and @JuliaSprenger ,
Thank you for the reviews and the valuable feedback you provided.
I considered all your comments and made changes to the code.

If you have any further suggestions or if there are any remaining issues, please don't hesitate to let me or @mdenker know.

@apdavison apdavison merged commit 3af1010 into NeuralEnsemble:master Jan 29, 2024
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Advanced filter functionality
6 participants