-
Notifications
You must be signed in to change notification settings - Fork 1
/
WriteComments.py
226 lines (186 loc) · 9.3 KB
/
WriteComments.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
"""
Add/remove/extend/clear cluster comments
On selection of one cluster the complete comment string is available in
a user input prompt and can be modified and will be replaced. If
selecting multiple clusters, the shared comments will be provided in the
user prompt and only additions are made to the comments of each cluster.
More elaborate actions are possible with prepending the input with '!'
to replace the comments completely with the new input (or clear it if
typing '!' only) and '~' to remove parts of the comments from all
selected clusters.
Multiple comments are separated by a delimiter (see configuration below,
'_' by default).
Comments are divided into two types: short hand notations (characters),
and custom comments. The short hand notations allow automatic expansion
from single characters to predefined comment parts (see `pairs` below)
for faster work flow. Multiple short hand notations are specified by
grouping the characters together, e.g. asm for a, s and m. The short
hand notations will always be converted back and forth when going back
to editing a comment. To trigger the expansion, they must remain in the
beginning of the string separated from the custom comments (if present)
by the delimiter.
The custom comments can be any words added after the short hand
notations separated by the delimiter (if present). The custom comments
are treated individually if they are separated by the delimiter.
Configuration:
On first use, a JSON file will be created in the Phy configuration
directory, usually {HOME}/.phy/plugin_writecomments.json. The delimiter
and the short hand notation pairs can be adjusted there.
Note:
Only single-character, lower-case short hand notations are supported.
"""
import json
from phy import IPlugin, connect
from phy.utils import phy_config_dir
from pathlib import Path
import logging
logger = logging.getLogger('phy')
class WriteComments(IPlugin):
# Load config
def __init__(self):
filepath = Path(phy_config_dir()) / 'plugin_writecomments.json'
# Default config
dflts = dict()
dflts['delimiter'] = '_' # Comment delimiter
dflts['pairs'] = { # Character-comment pairs
'a': 'axon',
'm': 'misaligned',
's': 'spontaneous',
'n': 'noisefloor',
}
# Create config file with defaults if it does not exist
if not filepath.exists():
logger.debug("Create default config at %s.", filepath)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(dflts, f, ensure_ascii=False, indent=4)
# Load config
logger.debug("Load %s for config.", filepath)
with open(filepath, 'r') as f:
try:
data = json.load(f)
except json.decoder.JSONDecodeError as e:
logger.warning("Error decoding JSON: %s", e)
data = dflts
self.delimiter = data.get('delimiter', dflts['delimiter'])
self.pairs = data.get('pairs', dflts['pairs'])
# Allow lower case keys only
for k in self.pairs.keys():
if not k.islower():
self.pairs[k.lower()] = self.pairs[k]
del self.pairs[k]
self.pairs_inv = {v: k for k, v in self.pairs.items()}
logger.debug("Available short hand notations are %s.",
', '.join(self.pairs.keys()))
def load_comments(self, controller):
"""Load selected comments a list of as sets"""
cluster_ids = controller.supervisor.selected
if 'comment' not in controller.supervisor.fields:
logger.debug('Comment field does not exist. Return empty list.')
return []
allcmt = controller.supervisor.get_labels('comment')
# Find shared comments (ignoring empty comments)
comments = []
for cid in cluster_ids:
cmt = allcmt[cid].split(self.delimiter) if allcmt[cid] else []
cmt = set(cmt).difference('')
comments.append(cmt)
return comments
def split_comments(self, comments):
"""Split list of sets into notations and custom comments"""
pair_set = set(self.pairs.values())
chars = [c.intersection(pair_set) for c in comments]
comments = [c.difference(pair_set) for c in comments]
return chars, comments
def attach_to_controller(self, controller):
def get_comments():
"""Fetch common comments among selected clusters"""
logger.debug('Retrieving comments from selected clusters.')
comments = self.load_comments(controller)
logger.debug('Splitting comments.')
chars, comments = self.split_comments(comments)
# Keep non-empty comments only
chars = [p for p in chars if p]
# comments = [c for c in comments if c] # Comments not shared
# Keep shared comments only
if len(chars) > 0:
chars = chars[0].intersection(*chars)
if len(comments) > 0:
comments = list(comments[0].intersection(*comments))
logger.debug('Number of comments found: %i, %i (chars, comments).',
len(chars), len(comments))
# Collapse characters without delimiter
chars = ''.join(self.pairs_inv[c] for c in chars)
chars = [chars] if chars else []
# Join the comments back together
ret = self.delimiter.join(chars + comments)
logger.debug('Created prompt default as \'%s\'.', ret)
return ret
@connect
def on_gui_ready(sender, gui):
@controller.supervisor.actions.add(name='Add comment', alias='com',
shortcut='alt+w', prompt=True,
prompt_default=get_comments)
def Add_comment(*userinput):
"""
Add comments to selected clusters, prepend with '~' to
remove comments, prepend with '!' to replace completely,
type '!' to clear all
"""
logger.debug('Non-empty user input received.')
cluster_ids = controller.supervisor.selected
# Treat multiple arguments as single argument:
# Commas are kept, spaces represent the delimiter
userinput = [','.join(map(str, u)) if isinstance(u, list)
else u for u in userinput]
userinput = self.delimiter.join(map(str, userinput))
# Check if replacement if requested
replace = userinput.startswith('!')
userinput = userinput.lstrip('!')
# Check to remove comments
remove = userinput.startswith('~')
userinput = userinput.lstrip('~')
# Clear comments
if not userinput:
controller.supervisor.label('comment', None)
logger.info('Clear metadata_comment for clusters %s.',
', '.join(map(str, cluster_ids)))
return
# Otherwise replace/remove/merge with existing
comments_old = self.load_comments(controller)
chars_old, comments_old = self.split_comments(comments_old)
# Extract short hand notations and custom comments
chars_new, *comments_new = userinput.split(self.delimiter)
chars_new = chars_new.lower()
if set(chars_new).issubset(self.pairs.keys()):
chars_new = set(self.pairs[c] for c in set(chars_new))
else:
comments_new = [chars_new] + comments_new
chars_new = set()
comments_new = set(comments_new).difference('')
assert len(cluster_ids) == len(comments_old) == len(chars_old)
# Merge for each cluster with their previous comments
comments = []
for char_old, cmt_old in zip(chars_old, comments_old):
if (len(cluster_ids) == 1 or replace) and not remove:
# Replace old comment completely
char = chars_new
comment = comments_new
elif remove:
# Remove new from old comments
char = char_old.difference(chars_new)
comment = cmt_old.difference(comments_new)
else:
# Merge old and new comments
char = char_old.union(chars_new)
comment = cmt_old.union(comments_new)
# Combine final comment string
char, comment = list(char), list(comment)
comments.append(self.delimiter.join(char + comment))
if len(set(comments)) == 1:
logger.debug('Set the same comment to all clusters')
controller.supervisor.label('comment', comments[0])
else:
logger.debug('Set the comments individually')
for cid, comment in zip(cluster_ids, comments):
controller.supervisor.label('comment', comment,
cluster_ids=cid)