pycinema 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycinema/Core.py +530 -0
- pycinema/__init__.py +4 -0
- pycinema/_version.py +1 -0
- pycinema/filters/CSVReader.py +59 -0
- pycinema/filters/Calculator.py +55 -0
- pycinema/filters/CinemaDatabaseReader.py +65 -0
- pycinema/filters/CinemaDatabaseWriter.py +128 -0
- pycinema/filters/ColorMapping.py +248 -0
- pycinema/filters/ColorSource.py +21 -0
- pycinema/filters/DSIReader.py +58 -0
- pycinema/filters/DepthCompositing.py +169 -0
- pycinema/filters/ExportTableToDatabase.py +82 -0
- pycinema/filters/ImageAnnotation.py +113 -0
- pycinema/filters/ImageBorder.py +52 -0
- pycinema/filters/ImageCanny.py +38 -0
- pycinema/filters/ImageConvertGrayscale.py +31 -0
- pycinema/filters/ImageFilterPIL.py +61 -0
- pycinema/filters/ImageMetrics.py +48 -0
- pycinema/filters/ImageReader.py +99 -0
- pycinema/filters/ImageSort.py +57 -0
- pycinema/filters/ImageView.py +248 -0
- pycinema/filters/ImagesToTable.py +39 -0
- pycinema/filters/InspectorView.py +31 -0
- pycinema/filters/MLTFPredictor.py +62 -0
- pycinema/filters/MLTFReader.py +73 -0
- pycinema/filters/MaskCompositing.py +85 -0
- pycinema/filters/ParallelCoordinates.py +533 -0
- pycinema/filters/ParametersView.py +243 -0
- pycinema/filters/Plot.py +55 -0
- pycinema/filters/PlotLineItem.py +51 -0
- pycinema/filters/PlotTables.py +86 -0
- pycinema/filters/Python.py +53 -0
- pycinema/filters/RenderView.py +132 -0
- pycinema/filters/Shader.py +114 -0
- pycinema/filters/ShaderDemoScene.py +250 -0
- pycinema/filters/ShaderFXAA.py +161 -0
- pycinema/filters/ShaderIBS.py +201 -0
- pycinema/filters/ShaderLineAO.py +286 -0
- pycinema/filters/ShaderPBR.py +270 -0
- pycinema/filters/ShaderPhong.py +132 -0
- pycinema/filters/ShaderPointAO.py +292 -0
- pycinema/filters/ShaderSSAO.py +138 -0
- pycinema/filters/SqliteDatabaseReader.py +81 -0
- pycinema/filters/TableEditor.py +28 -0
- pycinema/filters/TableQuery.py +100 -0
- pycinema/filters/TableView.py +157 -0
- pycinema/filters/TableWriter.py +43 -0
- pycinema/filters/TextEditor.py +49 -0
- pycinema/filters/TextFileReader.py +41 -0
- pycinema/filters/ValueSource.py +21 -0
- pycinema/filters/YamlFileReader.py +41 -0
- pycinema/filters/__init__.py +48 -0
- pycinema/filters/synema/SynemaColorImageModelFactory.py +43 -0
- pycinema/filters/synema/SynemaColorImageModelTrainer.py +130 -0
- pycinema/filters/synema/SynemaColorImageViewSynthesis.py +86 -0
- pycinema/filters/synema/SynemaScalarImageModelFactory.py +40 -0
- pycinema/filters/synema/SynemaScalarImageModelTrainer.py +139 -0
- pycinema/filters/synema/SynemaScalarImageViewSynthesis.py +90 -0
- pycinema/filters/synema/__init__.py +6 -0
- pycinema/ipy/CinemaDatabaseViewer.py +88 -0
- pycinema/ipy/ColorMappingWidgets.py +134 -0
- pycinema/ipy/ImageViewer.py +52 -0
- pycinema/ipy/NumberWidget.py +38 -0
- pycinema/ipy/ParameterWidgets.py +224 -0
- pycinema/ipy/__init__.py +5 -0
- pycinema/scripts/__init__.py +3 -0
- pycinema/scripts/browse.py +51 -0
- pycinema/scripts/compose.py +148 -0
- pycinema/scripts/discover.py +96 -0
- pycinema/scripts/explore.py +62 -0
- pycinema/scripts/imagegrid.py +39 -0
- pycinema/scripts/recolor.py +60 -0
- pycinema/scripts/view.py +61 -0
- pycinema/theater/FilterBrowser.py +98 -0
- pycinema/theater/Icons.py +26 -0
- pycinema/theater/SplitFrame.py +137 -0
- pycinema/theater/TabFrame.py +115 -0
- pycinema/theater/Theater.py +270 -0
- pycinema/theater/View.py +76 -0
- pycinema/theater/ViewStyle.py +21 -0
- pycinema/theater/__init__.py +6 -0
- pycinema/theater/node_editor/Edge.py +57 -0
- pycinema/theater/node_editor/InputText.py +104 -0
- pycinema/theater/node_editor/Node.py +114 -0
- pycinema/theater/node_editor/NodeEditorStyle.py +75 -0
- pycinema/theater/node_editor/Port.py +174 -0
- pycinema/theater/node_editor/__init__.py +5 -0
- pycinema/theater/views/FilterView.py +31 -0
- pycinema/theater/views/NodeEditorView.py +346 -0
- pycinema/theater/views/SelectionView.py +69 -0
- pycinema/theater/views/__init__.py +4 -0
- pycinema-3.3.0.data/data/fonts/NotoSansMono-VariableFont_wdth,wght.ttf +0 -0
- pycinema-3.3.0.data/scripts/cinema +222 -0
- pycinema-3.3.0.data/scripts/pycinema-description.md +14 -0
- pycinema-3.3.0.dist-info/METADATA +57 -0
- pycinema-3.3.0.dist-info/RECORD +98 -0
- pycinema-3.3.0.dist-info/WHEEL +5 -0
- pycinema-3.3.0.dist-info/top_level.txt +1 -0
pycinema/Core.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import traceback
|
|
3
|
+
import pprint
|
|
4
|
+
import re
|
|
5
|
+
import numpy as np
|
|
6
|
+
from ast import literal_eval
|
|
7
|
+
import PIL
|
|
8
|
+
import io
|
|
9
|
+
import logging as log
|
|
10
|
+
import os
|
|
11
|
+
import importlib.resources
|
|
12
|
+
|
|
13
|
+
CORE_NAN_VALUES = ['NaN', 'NAN', 'nan']
|
|
14
|
+
|
|
15
|
+
################################################################################
|
|
16
|
+
# general helper functions
|
|
17
|
+
################################################################################
|
|
18
|
+
def isURL(path):
|
|
19
|
+
s = path.strip()
|
|
20
|
+
if s.startswith("http") or s.startswith("HTTP"):
|
|
21
|
+
return True
|
|
22
|
+
else:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
def isNumber(s):
|
|
26
|
+
t = type(s)
|
|
27
|
+
if t == int or t == float:
|
|
28
|
+
return True
|
|
29
|
+
else:
|
|
30
|
+
# assume it is a string
|
|
31
|
+
try:
|
|
32
|
+
sf = float(s)
|
|
33
|
+
return True
|
|
34
|
+
except ValueError:
|
|
35
|
+
return False
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
def imageFromMatplotlibFigure(figure,dpi):
|
|
39
|
+
# Create image stream
|
|
40
|
+
image_stream = io.BytesIO()
|
|
41
|
+
figure.savefig(image_stream, format='png', dpi=dpi)
|
|
42
|
+
image_stream.seek(0)
|
|
43
|
+
|
|
44
|
+
# Parse stream to pycinema image
|
|
45
|
+
rawImage = PIL.Image.open(image_stream)
|
|
46
|
+
if rawImage.mode == 'RGB':
|
|
47
|
+
rawImage = rawImage.convert('RGBA')
|
|
48
|
+
image = Image({ 'rgba': np.asarray(rawImage) })
|
|
49
|
+
return image
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# get the path where this module has been installed
|
|
53
|
+
#
|
|
54
|
+
def getModulePath():
|
|
55
|
+
"""
|
|
56
|
+
Returns the file system path to a resource within a package.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
# Attempt to get the path directly if the resource is a file
|
|
60
|
+
resource_path = importlib.resources.files('pycinema').joinpath('Core.py')
|
|
61
|
+
if os.path.isfile(resource_path):
|
|
62
|
+
return str(os.path.dirname(resource_path))
|
|
63
|
+
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
raise FileNotFoundError(f"Resource '{resource}' not found in package '{package}'")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
raise Exception(f"An error occurred: {e}")
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# return a list of scripts installed with this module
|
|
71
|
+
#
|
|
72
|
+
def getPycinemaModuleScripts():
|
|
73
|
+
scriptdir = os.path.join(getModulePath(), 'scripts')
|
|
74
|
+
|
|
75
|
+
scripts = glob.glob(scriptdir + "/*.py")
|
|
76
|
+
|
|
77
|
+
names = []
|
|
78
|
+
for script in scripts:
|
|
79
|
+
curpath, curname = os.path.split(script)
|
|
80
|
+
if curname != "__init__.py":
|
|
81
|
+
names.append(curname.removesuffix('.py'))
|
|
82
|
+
|
|
83
|
+
return names
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# given the base name of a script, search module and environment paths
|
|
87
|
+
# to find and return the full path for that script
|
|
88
|
+
#
|
|
89
|
+
# the base name of the script is the filename, with no '.py' suffix
|
|
90
|
+
#
|
|
91
|
+
def getPathForScript(name):
|
|
92
|
+
|
|
93
|
+
scriptdirs = [os.path.join(getModulePath(), 'scripts')]
|
|
94
|
+
if 'PYCINEMA_SCRIPT_DIR' in os.environ:
|
|
95
|
+
scriptdirs.append(os.path.abspath(os.environ['PYCINEMA_SCRIPT_DIR']))
|
|
96
|
+
|
|
97
|
+
scriptpath = None
|
|
98
|
+
for scriptdir in scriptdirs:
|
|
99
|
+
# iterate over the directories, overwriting the script if it exists
|
|
100
|
+
if os.path.exists(scriptdir):
|
|
101
|
+
# first, assume the user supplied the correct filename
|
|
102
|
+
possible_script = os.path.join(scriptdir, name)
|
|
103
|
+
if os.path.isfile(possible_script):
|
|
104
|
+
scriptpath = possible_script
|
|
105
|
+
|
|
106
|
+
# if that doesn't exist, search for one with an extension
|
|
107
|
+
else:
|
|
108
|
+
if os.path.isfile(possible_script + ".py"):
|
|
109
|
+
scriptpath = possible_script + ".py"
|
|
110
|
+
else:
|
|
111
|
+
log.debug("script directory does not exist: \'" + scriptdir + "\'")
|
|
112
|
+
|
|
113
|
+
return scriptpath
|
|
114
|
+
|
|
115
|
+
################################################################################
|
|
116
|
+
# table helper functions
|
|
117
|
+
################################################################################
|
|
118
|
+
|
|
119
|
+
# get the column index from a table, return -1 on failure
|
|
120
|
+
def getColumnIndexFromTable(table, colname):
|
|
121
|
+
ID = -1
|
|
122
|
+
|
|
123
|
+
colnames = table[0]
|
|
124
|
+
if colname in colnames:
|
|
125
|
+
ID = colnames.index(colname)
|
|
126
|
+
|
|
127
|
+
return ID
|
|
128
|
+
|
|
129
|
+
# get a column of values from a table
|
|
130
|
+
def getColumnFromTable(table, colname, autocast=False, nan_remove=False, nan_replace=None, missing_remove=False, missing_replace=None):
|
|
131
|
+
colID = getColumnIndexFromTable(table, colname)
|
|
132
|
+
|
|
133
|
+
if colID == -1:
|
|
134
|
+
log.error("ERROR: no column named \'" + colname + "\'")
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
# start with all values
|
|
139
|
+
# these will be strings
|
|
140
|
+
cleaned_column = [row[colID] for row in table[1:]]
|
|
141
|
+
|
|
142
|
+
# remove values
|
|
143
|
+
if nan_remove:
|
|
144
|
+
cleaned_column = [x for x in cleaned_column if x not in CORE_NAN_VALUES]
|
|
145
|
+
if missing_remove:
|
|
146
|
+
cleaned_column = [x for x in cleaned_column if x != '']
|
|
147
|
+
|
|
148
|
+
# replace values
|
|
149
|
+
if nan_replace:
|
|
150
|
+
i = 0
|
|
151
|
+
while i < len(cleaned_column):
|
|
152
|
+
if cleaned_column[i] in CORE_NAN_VALUES:
|
|
153
|
+
print("replacing " + cleaned_column[i])
|
|
154
|
+
cleaned_column[i] = nan_replace
|
|
155
|
+
i += 1
|
|
156
|
+
|
|
157
|
+
if missing_replace:
|
|
158
|
+
i = 0
|
|
159
|
+
while i < len(cleaned_column):
|
|
160
|
+
if cleaned_column[i] == '':
|
|
161
|
+
cleaned_column[i] = missing_replace
|
|
162
|
+
i += 1
|
|
163
|
+
|
|
164
|
+
# TODO: create a separate function call to determine column type
|
|
165
|
+
if autocast:
|
|
166
|
+
if (any(isNumber(item) for item in cleaned_column) and not (isinstance(cleaned_column, np.ndarray) and np.issubdtype(cleaned_column.dtype, np.floating))):
|
|
167
|
+
cleaned_column = np.array(cleaned_column, dtype=float)
|
|
168
|
+
|
|
169
|
+
return cleaned_column
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# get extent of the table (nRows,nCols)
|
|
173
|
+
def getTableExtent(table):
|
|
174
|
+
try:
|
|
175
|
+
nRows = len(table)
|
|
176
|
+
if nRows < 1:
|
|
177
|
+
return (0,0)
|
|
178
|
+
nCols = len(table[1])
|
|
179
|
+
for i in range(2,nRows):
|
|
180
|
+
if len(table[i]) != nCols:
|
|
181
|
+
return (nRows,-1)
|
|
182
|
+
return (nRows,nCols)
|
|
183
|
+
except:
|
|
184
|
+
return (-1,-1)
|
|
185
|
+
|
|
186
|
+
################################################################################
|
|
187
|
+
# Image Class
|
|
188
|
+
################################################################################
|
|
189
|
+
class Image():
|
|
190
|
+
image_id_counter = -1
|
|
191
|
+
|
|
192
|
+
def __init__(self, channels=None, meta=None):
|
|
193
|
+
self.meta = meta or {}
|
|
194
|
+
self.channels = channels or {}
|
|
195
|
+
if 'id' not in self.meta:
|
|
196
|
+
self.meta['id'] = Image.image_id_counter
|
|
197
|
+
Image.image_id_counter += -1
|
|
198
|
+
|
|
199
|
+
def __str__(self):
|
|
200
|
+
result = '{ PyCinemaImage:\n'
|
|
201
|
+
result += ' meta: \n' + pprint.pformat(self.meta, indent=4)
|
|
202
|
+
result += ' channels: \n' + pprint.pformat(self.channels, indent=4)
|
|
203
|
+
result += '\n}\n'
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def __repr__(self):
|
|
207
|
+
return self.__str__()
|
|
208
|
+
|
|
209
|
+
def getChannel(self,regex):
|
|
210
|
+
for c,data in self.channels.items():
|
|
211
|
+
if c==regex:
|
|
212
|
+
return data
|
|
213
|
+
for c,data in self.channels.items():
|
|
214
|
+
if re.search(regex, c, re.IGNORECASE):
|
|
215
|
+
return data
|
|
216
|
+
raise Exception('Channel Not Found')
|
|
217
|
+
|
|
218
|
+
def copy(self):
|
|
219
|
+
return Image(
|
|
220
|
+
self.channels.copy(),
|
|
221
|
+
self.meta.copy()
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def shape(self):
|
|
226
|
+
# get first channel
|
|
227
|
+
for c in self.channels:
|
|
228
|
+
return self.channels[c].shape
|
|
229
|
+
return (0,0,0)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def resolution(self):
|
|
233
|
+
return self.shape[:2][::-1]
|
|
234
|
+
|
|
235
|
+
################################################################################
|
|
236
|
+
# Port Class
|
|
237
|
+
################################################################################
|
|
238
|
+
class Port():
|
|
239
|
+
def __init__(self, name, value, parent, is_input = False):
|
|
240
|
+
self.parent = parent
|
|
241
|
+
self.name = name
|
|
242
|
+
self.is_input = is_input
|
|
243
|
+
self.connections = []
|
|
244
|
+
self.time = -1
|
|
245
|
+
self._value = value
|
|
246
|
+
self.default = value
|
|
247
|
+
t = type(value)
|
|
248
|
+
if t == int:
|
|
249
|
+
self.type = int
|
|
250
|
+
elif t == float:
|
|
251
|
+
self.type = float
|
|
252
|
+
elif t == str:
|
|
253
|
+
self.type = str
|
|
254
|
+
else:
|
|
255
|
+
self.type = object
|
|
256
|
+
|
|
257
|
+
self._listeners = {}
|
|
258
|
+
|
|
259
|
+
self.propagate_value = lambda v: self.trigger('value_set', v)
|
|
260
|
+
|
|
261
|
+
def on(self, eventName, listener):
|
|
262
|
+
if not eventName in self._listeners:
|
|
263
|
+
self._listeners[eventName] = []
|
|
264
|
+
self._listeners[eventName].append(listener)
|
|
265
|
+
|
|
266
|
+
def off(self, eventName, listener):
|
|
267
|
+
if not eventName in self._listeners:
|
|
268
|
+
return
|
|
269
|
+
self._listeners[eventName].remove(listener)
|
|
270
|
+
|
|
271
|
+
def trigger(self, eventName, data):
|
|
272
|
+
if not eventName in self._listeners:
|
|
273
|
+
return
|
|
274
|
+
for listener in self._listeners[eventName]:
|
|
275
|
+
listener(data)
|
|
276
|
+
|
|
277
|
+
def valueIsPort(self):
|
|
278
|
+
return isinstance(self._value, Port)
|
|
279
|
+
|
|
280
|
+
def valueIsPortList(self):
|
|
281
|
+
return isinstance(self._value, list) and len(self._value)>0 and isinstance(self._value[0], Port)
|
|
282
|
+
|
|
283
|
+
def get(self):
|
|
284
|
+
if isinstance(self._value, Port):
|
|
285
|
+
return self._value.get()
|
|
286
|
+
elif self.valueIsPortList():
|
|
287
|
+
result = []
|
|
288
|
+
for port in self._value:
|
|
289
|
+
result.append(port.get())
|
|
290
|
+
return result
|
|
291
|
+
return self._value
|
|
292
|
+
|
|
293
|
+
def getTime(self):
|
|
294
|
+
if isinstance(self._value, Port):
|
|
295
|
+
return self._value.time
|
|
296
|
+
return self.time
|
|
297
|
+
|
|
298
|
+
def set(self, value, update=True, propagate_back=False):
|
|
299
|
+
if Filter._debug:
|
|
300
|
+
print(type(self.parent).__name__+"->"+self.name, str(value)[:40])
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
np.testing.assert_equal(self._value,value)
|
|
304
|
+
return
|
|
305
|
+
except:
|
|
306
|
+
pass
|
|
307
|
+
# if self._value == value:
|
|
308
|
+
# return
|
|
309
|
+
|
|
310
|
+
self.time = time.time()
|
|
311
|
+
|
|
312
|
+
# if old value is a port
|
|
313
|
+
if isinstance(self._value, Port):
|
|
314
|
+
if propagate_back:
|
|
315
|
+
self._value.set(value,update,True)
|
|
316
|
+
return
|
|
317
|
+
else:
|
|
318
|
+
# stop listing for push events
|
|
319
|
+
self._value.off('value_set', self.propagate_value)
|
|
320
|
+
self._value.connections.remove(self)
|
|
321
|
+
Filter.trigger('connection_removed', [self._value,self])
|
|
322
|
+
elif self.valueIsPortList():
|
|
323
|
+
for port in self._value:
|
|
324
|
+
port.off('value_set', self.propagate_value)
|
|
325
|
+
port.connections.remove(self)
|
|
326
|
+
Filter.trigger('connection_removed', [port,self])
|
|
327
|
+
|
|
328
|
+
# replace old value with new value
|
|
329
|
+
self._value = value
|
|
330
|
+
self.trigger('value_set', value)
|
|
331
|
+
|
|
332
|
+
# if new value is a port listen for push events
|
|
333
|
+
if isinstance(self._value, Port):
|
|
334
|
+
self._value.on('value_set', self.propagate_value)
|
|
335
|
+
self._value.connections.append(self)
|
|
336
|
+
Filter.trigger('connection_added', [self._value,self])
|
|
337
|
+
elif self.valueIsPortList():
|
|
338
|
+
for port in self._value:
|
|
339
|
+
port.on('value_set', self.propagate_value)
|
|
340
|
+
port.connections.append(self)
|
|
341
|
+
Filter.trigger('connection_added', [port,self])
|
|
342
|
+
|
|
343
|
+
# if value of a port was changed trigger update of listeners
|
|
344
|
+
if self.is_input and update and not Filter._processing:
|
|
345
|
+
self.parent.update()
|
|
346
|
+
|
|
347
|
+
class PortList():
|
|
348
|
+
def __init__(self, filter, ports, areInputs=True):
|
|
349
|
+
self.__ports = {}
|
|
350
|
+
for name in ports:
|
|
351
|
+
setattr(self, name, Port(name, ports[name], filter, areInputs))
|
|
352
|
+
self.__ports[name] = getattr(self,name)
|
|
353
|
+
|
|
354
|
+
def ports(self):
|
|
355
|
+
return self.__ports.items()
|
|
356
|
+
|
|
357
|
+
def size(self):
|
|
358
|
+
return len(self.__ports)
|
|
359
|
+
|
|
360
|
+
################################################################################
|
|
361
|
+
# Filter Class
|
|
362
|
+
################################################################################
|
|
363
|
+
class Filter():
|
|
364
|
+
|
|
365
|
+
_debug = False
|
|
366
|
+
_processing = False
|
|
367
|
+
_ID_COUNTER = {}
|
|
368
|
+
_filters = {}
|
|
369
|
+
_listeners = {}
|
|
370
|
+
|
|
371
|
+
@staticmethod
|
|
372
|
+
def on(eventName,listener):
|
|
373
|
+
if not eventName in Filter._listeners:
|
|
374
|
+
Filter._listeners[eventName] = []
|
|
375
|
+
Filter._listeners[eventName].append(listener)
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def off(eventName,listener):
|
|
379
|
+
if not eventName in Filter._listeners: return
|
|
380
|
+
id = listener.__repr__()
|
|
381
|
+
Filter._listeners[eventName] = [l for l in Filter._listeners[eventName] if l.__repr__()!=id]
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def trigger(eventName, data):
|
|
385
|
+
if not eventName in Filter._listeners:
|
|
386
|
+
return
|
|
387
|
+
for listener in Filter._listeners[eventName]:
|
|
388
|
+
listener(data)
|
|
389
|
+
|
|
390
|
+
def __init__(self, inputs={}, outputs={}):
|
|
391
|
+
if Filter._debug:
|
|
392
|
+
print('created',self)
|
|
393
|
+
cls = self.__class__.__name__
|
|
394
|
+
if cls not in Filter._ID_COUNTER:
|
|
395
|
+
Filter._ID_COUNTER[cls] = 0
|
|
396
|
+
self.id = self.__class__.__name__+'_'+str(Filter._ID_COUNTER[cls])+''
|
|
397
|
+
Filter._ID_COUNTER[cls] += 1
|
|
398
|
+
|
|
399
|
+
self.inputs = PortList(self, inputs)
|
|
400
|
+
self.outputs = PortList(self, outputs, False)
|
|
401
|
+
self.time = -2
|
|
402
|
+
self._filters[self] = self
|
|
403
|
+
|
|
404
|
+
self.trigger('filter_created',self)
|
|
405
|
+
|
|
406
|
+
def getInputFilters(self):
|
|
407
|
+
filters = set({})
|
|
408
|
+
for _, iPort in self.inputs.ports():
|
|
409
|
+
if isinstance(iPort._value, Port):
|
|
410
|
+
filters.add(iPort._value.parent)
|
|
411
|
+
elif iPort.valueIsPortList():
|
|
412
|
+
for port in iPort._value:
|
|
413
|
+
filters.add(port.parent)
|
|
414
|
+
return filters
|
|
415
|
+
|
|
416
|
+
def getOutputFilters(self):
|
|
417
|
+
filters = set({})
|
|
418
|
+
for _, oPort in self.outputs.ports():
|
|
419
|
+
for iPort in list(oPort.connections):
|
|
420
|
+
filters.add(iPort.parent)
|
|
421
|
+
return filters
|
|
422
|
+
|
|
423
|
+
def delete(self):
|
|
424
|
+
if Filter._debug:
|
|
425
|
+
print('deleted',self)
|
|
426
|
+
|
|
427
|
+
# reset dependencies
|
|
428
|
+
for _, oPort in self.outputs.ports():
|
|
429
|
+
for iPort in list(oPort.connections):
|
|
430
|
+
iPort.set(iPort.default, False)
|
|
431
|
+
for _, iPort in self.inputs.ports():
|
|
432
|
+
iPort.set(iPort.default, False)
|
|
433
|
+
|
|
434
|
+
# delete from filter list
|
|
435
|
+
del Filter._filters[self]
|
|
436
|
+
|
|
437
|
+
# signal filter destruction
|
|
438
|
+
self.trigger('filter_deleted',self)
|
|
439
|
+
|
|
440
|
+
# update pipeline
|
|
441
|
+
self.update()
|
|
442
|
+
|
|
443
|
+
def _update(self):
|
|
444
|
+
# needs to be overriden
|
|
445
|
+
return 1
|
|
446
|
+
|
|
447
|
+
def computeDAG(edges):
|
|
448
|
+
for f in Filter._filters:
|
|
449
|
+
edges[f] = set({})
|
|
450
|
+
|
|
451
|
+
for f in Filter._filters:
|
|
452
|
+
for _, port in f.outputs.ports():
|
|
453
|
+
for listener in port.connections:
|
|
454
|
+
edges[f].add(listener.parent)
|
|
455
|
+
|
|
456
|
+
return 1
|
|
457
|
+
|
|
458
|
+
def computeTopologicalOrdering(edges):
|
|
459
|
+
|
|
460
|
+
edgesR = {}
|
|
461
|
+
for n in edges:
|
|
462
|
+
for m in edges[n]:
|
|
463
|
+
if not m in edgesR:
|
|
464
|
+
edgesR[m] = 0
|
|
465
|
+
edgesR[m]+=1
|
|
466
|
+
|
|
467
|
+
L = []
|
|
468
|
+
S = []
|
|
469
|
+
for f in Filter._filters:
|
|
470
|
+
if f not in edgesR or not edgesR[f]:
|
|
471
|
+
S.append(f)
|
|
472
|
+
|
|
473
|
+
while len(S)>0:
|
|
474
|
+
n = S.pop()
|
|
475
|
+
L.append(n)
|
|
476
|
+
for m in edges[n]:
|
|
477
|
+
edgesR[m]-=1
|
|
478
|
+
if edgesR[m]<1:
|
|
479
|
+
S.append(m)
|
|
480
|
+
return L
|
|
481
|
+
|
|
482
|
+
def update(self):
|
|
483
|
+
if Filter._processing: return 0
|
|
484
|
+
|
|
485
|
+
Filter._processing = True
|
|
486
|
+
|
|
487
|
+
dagt = time.time()
|
|
488
|
+
edges = {}
|
|
489
|
+
Filter.computeDAG(edges)
|
|
490
|
+
filters = Filter.computeTopologicalOrdering(edges)
|
|
491
|
+
|
|
492
|
+
if Filter._debug:
|
|
493
|
+
print("--------------------------------")
|
|
494
|
+
print("DAG (%.2fs)" % (time.time()-dagt))
|
|
495
|
+
for f in filters:
|
|
496
|
+
print(' ',f,edges[f])
|
|
497
|
+
print("--------------------------------")
|
|
498
|
+
|
|
499
|
+
for i,f in enumerate(filters):
|
|
500
|
+
lt = f.time
|
|
501
|
+
needsUpdate = False
|
|
502
|
+
for _, iPort in f.inputs.ports():
|
|
503
|
+
if iPort.valueIsPortList():
|
|
504
|
+
for p in iPort._value:
|
|
505
|
+
if lt<p.getTime():
|
|
506
|
+
needsUpdate = True
|
|
507
|
+
elif lt<iPort.getTime():
|
|
508
|
+
needsUpdate = True
|
|
509
|
+
if f==self or needsUpdate:
|
|
510
|
+
t0 = time.time()
|
|
511
|
+
if Filter._debug:
|
|
512
|
+
print('PROCESS',f)
|
|
513
|
+
try:
|
|
514
|
+
f._update()
|
|
515
|
+
Filter.trigger('filter_updated',f)
|
|
516
|
+
except Exception:
|
|
517
|
+
traceback.print_exc()
|
|
518
|
+
Filter._processing = False
|
|
519
|
+
return 0
|
|
520
|
+
f.time = time.time()
|
|
521
|
+
if Filter._debug:
|
|
522
|
+
print(" -> Done (%.2fs)" % (f.time-t0))
|
|
523
|
+
elif Filter._debug:
|
|
524
|
+
print('SKIP',f)
|
|
525
|
+
|
|
526
|
+
Filter._processing = False
|
|
527
|
+
return 1
|
|
528
|
+
|
|
529
|
+
def help(self):
|
|
530
|
+
print('Documentation Missing')
|
pycinema/__init__.py
ADDED
pycinema/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.3.0"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from pycinema import Filter, isURL
|
|
2
|
+
import csv
|
|
3
|
+
import requests
|
|
4
|
+
import logging as log
|
|
5
|
+
from os.path import exists
|
|
6
|
+
|
|
7
|
+
class CSVReader(Filter):
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__(
|
|
11
|
+
inputs={
|
|
12
|
+
'file': ''
|
|
13
|
+
},
|
|
14
|
+
outputs={
|
|
15
|
+
'table': [[]]
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def _update(self):
|
|
20
|
+
|
|
21
|
+
table = []
|
|
22
|
+
csvPath = self.inputs.file.get()
|
|
23
|
+
|
|
24
|
+
if isURL(csvPath):
|
|
25
|
+
with requests.Session() as s:
|
|
26
|
+
log.debug("requesting " + csvPath)
|
|
27
|
+
download = s.get(csvPath)
|
|
28
|
+
decoded = download.content.decode('utf-8')
|
|
29
|
+
csvdecoded = csv.reader(decoded.splitlines(), delimiter=',')
|
|
30
|
+
rows = list(csvdecoded)
|
|
31
|
+
for row in rows:
|
|
32
|
+
table.append(row)
|
|
33
|
+
|
|
34
|
+
else:
|
|
35
|
+
if not csvPath:
|
|
36
|
+
self.outputs.table.set([[]])
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
if not exists(csvPath):
|
|
40
|
+
log.error("file not found: '" + csvPath + "'")
|
|
41
|
+
self.outputs.table.set([[]])
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
with open(csvPath, 'r+') as csvfile:
|
|
46
|
+
rows = csv.reader(csvfile, delimiter=',')
|
|
47
|
+
for row in rows:
|
|
48
|
+
table.append(row)
|
|
49
|
+
except:
|
|
50
|
+
log.error("Unable to open file: '" + csvPath + "'")
|
|
51
|
+
self.outputs.table.set([[]])
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
# remove empty lines
|
|
55
|
+
table = list(filter(lambda row: len(row)>0, table))
|
|
56
|
+
|
|
57
|
+
self.outputs.table.set(table)
|
|
58
|
+
|
|
59
|
+
return 1
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from pycinema import Filter, getTableExtent, isNumber
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
class Calculator(Filter):
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__(
|
|
10
|
+
inputs={
|
|
11
|
+
'table': [[]],
|
|
12
|
+
'label': 'result',
|
|
13
|
+
'expression': ''
|
|
14
|
+
},
|
|
15
|
+
outputs={
|
|
16
|
+
'table': [[]]
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def _update(self):
|
|
21
|
+
|
|
22
|
+
iTable = self.inputs.table.get()
|
|
23
|
+
extent = getTableExtent(iTable)
|
|
24
|
+
|
|
25
|
+
if extent[0]<2 or extent[1]<1:
|
|
26
|
+
self.outputs.table.set([[]])
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
oTable = copy.deepcopy(iTable)
|
|
30
|
+
|
|
31
|
+
variables = {}
|
|
32
|
+
for c in range(0,extent[1]):
|
|
33
|
+
if isNumber(oTable[1][c]):
|
|
34
|
+
variables[oTable[0][c]] = np.array([float(row[c]) for row in oTable[1:]])
|
|
35
|
+
|
|
36
|
+
variables['__result'] = ''
|
|
37
|
+
|
|
38
|
+
expression = self.inputs.expression.get().strip()
|
|
39
|
+
if expression=='':
|
|
40
|
+
expression = '0'
|
|
41
|
+
|
|
42
|
+
exec('__result = '+expression,variables)
|
|
43
|
+
result = variables['__result']
|
|
44
|
+
|
|
45
|
+
if type(result) not in [list, np.ndarray]:
|
|
46
|
+
result = [result for i in range(0,extent[0]-1)]
|
|
47
|
+
|
|
48
|
+
oTable[0].append( self.inputs.label.get() )
|
|
49
|
+
for i in range(1,extent[0]):
|
|
50
|
+
oTable[i] = list(oTable[i])
|
|
51
|
+
oTable[i].append(float(result[i-1]))
|
|
52
|
+
|
|
53
|
+
self.outputs.table.set(oTable)
|
|
54
|
+
|
|
55
|
+
return 1;
|