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.
Files changed (98) hide show
  1. pycinema/Core.py +530 -0
  2. pycinema/__init__.py +4 -0
  3. pycinema/_version.py +1 -0
  4. pycinema/filters/CSVReader.py +59 -0
  5. pycinema/filters/Calculator.py +55 -0
  6. pycinema/filters/CinemaDatabaseReader.py +65 -0
  7. pycinema/filters/CinemaDatabaseWriter.py +128 -0
  8. pycinema/filters/ColorMapping.py +248 -0
  9. pycinema/filters/ColorSource.py +21 -0
  10. pycinema/filters/DSIReader.py +58 -0
  11. pycinema/filters/DepthCompositing.py +169 -0
  12. pycinema/filters/ExportTableToDatabase.py +82 -0
  13. pycinema/filters/ImageAnnotation.py +113 -0
  14. pycinema/filters/ImageBorder.py +52 -0
  15. pycinema/filters/ImageCanny.py +38 -0
  16. pycinema/filters/ImageConvertGrayscale.py +31 -0
  17. pycinema/filters/ImageFilterPIL.py +61 -0
  18. pycinema/filters/ImageMetrics.py +48 -0
  19. pycinema/filters/ImageReader.py +99 -0
  20. pycinema/filters/ImageSort.py +57 -0
  21. pycinema/filters/ImageView.py +248 -0
  22. pycinema/filters/ImagesToTable.py +39 -0
  23. pycinema/filters/InspectorView.py +31 -0
  24. pycinema/filters/MLTFPredictor.py +62 -0
  25. pycinema/filters/MLTFReader.py +73 -0
  26. pycinema/filters/MaskCompositing.py +85 -0
  27. pycinema/filters/ParallelCoordinates.py +533 -0
  28. pycinema/filters/ParametersView.py +243 -0
  29. pycinema/filters/Plot.py +55 -0
  30. pycinema/filters/PlotLineItem.py +51 -0
  31. pycinema/filters/PlotTables.py +86 -0
  32. pycinema/filters/Python.py +53 -0
  33. pycinema/filters/RenderView.py +132 -0
  34. pycinema/filters/Shader.py +114 -0
  35. pycinema/filters/ShaderDemoScene.py +250 -0
  36. pycinema/filters/ShaderFXAA.py +161 -0
  37. pycinema/filters/ShaderIBS.py +201 -0
  38. pycinema/filters/ShaderLineAO.py +286 -0
  39. pycinema/filters/ShaderPBR.py +270 -0
  40. pycinema/filters/ShaderPhong.py +132 -0
  41. pycinema/filters/ShaderPointAO.py +292 -0
  42. pycinema/filters/ShaderSSAO.py +138 -0
  43. pycinema/filters/SqliteDatabaseReader.py +81 -0
  44. pycinema/filters/TableEditor.py +28 -0
  45. pycinema/filters/TableQuery.py +100 -0
  46. pycinema/filters/TableView.py +157 -0
  47. pycinema/filters/TableWriter.py +43 -0
  48. pycinema/filters/TextEditor.py +49 -0
  49. pycinema/filters/TextFileReader.py +41 -0
  50. pycinema/filters/ValueSource.py +21 -0
  51. pycinema/filters/YamlFileReader.py +41 -0
  52. pycinema/filters/__init__.py +48 -0
  53. pycinema/filters/synema/SynemaColorImageModelFactory.py +43 -0
  54. pycinema/filters/synema/SynemaColorImageModelTrainer.py +130 -0
  55. pycinema/filters/synema/SynemaColorImageViewSynthesis.py +86 -0
  56. pycinema/filters/synema/SynemaScalarImageModelFactory.py +40 -0
  57. pycinema/filters/synema/SynemaScalarImageModelTrainer.py +139 -0
  58. pycinema/filters/synema/SynemaScalarImageViewSynthesis.py +90 -0
  59. pycinema/filters/synema/__init__.py +6 -0
  60. pycinema/ipy/CinemaDatabaseViewer.py +88 -0
  61. pycinema/ipy/ColorMappingWidgets.py +134 -0
  62. pycinema/ipy/ImageViewer.py +52 -0
  63. pycinema/ipy/NumberWidget.py +38 -0
  64. pycinema/ipy/ParameterWidgets.py +224 -0
  65. pycinema/ipy/__init__.py +5 -0
  66. pycinema/scripts/__init__.py +3 -0
  67. pycinema/scripts/browse.py +51 -0
  68. pycinema/scripts/compose.py +148 -0
  69. pycinema/scripts/discover.py +96 -0
  70. pycinema/scripts/explore.py +62 -0
  71. pycinema/scripts/imagegrid.py +39 -0
  72. pycinema/scripts/recolor.py +60 -0
  73. pycinema/scripts/view.py +61 -0
  74. pycinema/theater/FilterBrowser.py +98 -0
  75. pycinema/theater/Icons.py +26 -0
  76. pycinema/theater/SplitFrame.py +137 -0
  77. pycinema/theater/TabFrame.py +115 -0
  78. pycinema/theater/Theater.py +270 -0
  79. pycinema/theater/View.py +76 -0
  80. pycinema/theater/ViewStyle.py +21 -0
  81. pycinema/theater/__init__.py +6 -0
  82. pycinema/theater/node_editor/Edge.py +57 -0
  83. pycinema/theater/node_editor/InputText.py +104 -0
  84. pycinema/theater/node_editor/Node.py +114 -0
  85. pycinema/theater/node_editor/NodeEditorStyle.py +75 -0
  86. pycinema/theater/node_editor/Port.py +174 -0
  87. pycinema/theater/node_editor/__init__.py +5 -0
  88. pycinema/theater/views/FilterView.py +31 -0
  89. pycinema/theater/views/NodeEditorView.py +346 -0
  90. pycinema/theater/views/SelectionView.py +69 -0
  91. pycinema/theater/views/__init__.py +4 -0
  92. pycinema-3.3.0.data/data/fonts/NotoSansMono-VariableFont_wdth,wght.ttf +0 -0
  93. pycinema-3.3.0.data/scripts/cinema +222 -0
  94. pycinema-3.3.0.data/scripts/pycinema-description.md +14 -0
  95. pycinema-3.3.0.dist-info/METADATA +57 -0
  96. pycinema-3.3.0.dist-info/RECORD +98 -0
  97. pycinema-3.3.0.dist-info/WHEEL +5 -0
  98. 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
@@ -0,0 +1,4 @@
1
+ from ._version import __version__
2
+
3
+ from .Core import *
4
+ from .scripts import *
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;