Skip to content

Commit

Permalink
Merge pull request #42 from ifm/develop
Browse files Browse the repository at this point in the history
Release 0.9.1
  • Loading branch information
cwiede authored Mar 28, 2022
2 parents 3416523 + 9439ab8 commit 935f6af
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 30 deletions.
16 changes: 15 additions & 1 deletion nexxT/core/FilterMockup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from nexxT.interface import InputPort, OutputPort, InputPortInterface, OutputPortInterface
from nexxT.core.FilterEnvironment import FilterEnvironment
from nexxT.core.PropertyCollectionImpl import PropertyCollectionImpl
from nexxT.core.Exceptions import PortNotFoundError, PortExistsError, PropertyCollectionChildExists
from nexxT.core.Exceptions import (PortNotFoundError, PortExistsError, PropertyCollectionChildExists,
PropertyCollectionPropertyNotFound)
from nexxT.core.Utils import assertMainThread, MethodInvoker
import nexxT

Expand All @@ -33,6 +34,19 @@ def __init__(self, library, factoryFunction, propertyCollection, graph):
self._propertyCollectionImpl = propertyCollection
self._pluginClass = None
self._createFilterAndUpdatePending = None
rootPc = propertyCollection
while rootPc.parent() is not None:
rootPc = rootPc.parent()
tmpRootPc = PropertyCollectionImpl("root", None)
try:
cfgfile = rootPc.getProperty("CFGFILE")
tmpRootPc.defineProperty("CFGFILE", cfgfile, "copy of original CFGFILE.", options=dict(enum=[cfgfile]))
except PropertyCollectionPropertyNotFound:
pass
tmpPc = PropertyCollectionImpl("temp", tmpRootPc)
with FilterEnvironment(self._library, self._factoryFunction, tmpPc, self) as tmpEnv:
self.updatePortInformation(tmpEnv)
del tmpPc
try:
# add also a child collection for the nexxT internals
pc = PropertyCollectionImpl("_nexxT", propertyCollection)
Expand Down
13 changes: 11 additions & 2 deletions nexxT/core/Graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ def cleanup(self):
# BaseGraph gets the node name, this method gets arguments
# for constructing a filter
@Slot(str, str, object)
def addNode(self, library, factoryFunction, suggestedName=None):
def addNode(self, library, factoryFunction, suggestedName=None,
dynamicInputPorts=None, dynamicOutputPorts=None):
"""
Add a node to the graph, given a library and a factory function for instantiating the plugin.
:param library: definition file of the plugin
Expand All @@ -86,15 +87,23 @@ def addNode(self, library, factoryFunction, suggestedName=None):
assertMainThread()
if suggestedName is None:
suggestedName = factoryFunction
if dynamicInputPorts is None:
dynamicInputPorts = []
if dynamicOutputPorts is None:
dynamicOutputPorts = []
name = super().uniqueNodeName(suggestedName)
try:
propColl = self._properties.getChildCollection(name)
except PropertyCollectionChildNotFound:
propColl = PropertyCollectionImpl(name, self._properties)
propColl.propertyChanged.connect(self.setDirty)
filterMockup = FilterMockup(library, factoryFunction, propColl, self)
filterMockup.createFilterAndUpdate()
self._filters[name] = filterMockup
for din in dynamicInputPorts:
self.addDynamicInputPort(name, din)
for dout in dynamicOutputPorts:
self.addDynamicOutputPort(name, dout)
filterMockup.createFilterAndUpdate()
assert super().addNode(name) == name
if factoryFunction == "compositeNode" and hasattr(library, "checkRecursion"):
try:
Expand Down
15 changes: 13 additions & 2 deletions nexxT/core/PropertyCollectionImpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,18 @@ def defineProperty(self, name, defaultVal, helpstr, options=None, propertyHandle
"Pass either options or propertyHandler to defineProperty but not both.")
if options is None:
options = {}
ignoreInconsistentOptions = False
if "ignoreInconsistentOptions" in options:
ignoreInconsistentOptions = options["ignoreInconsistentOptions"]
del options["ignoreInconsistentOptions"]
if propertyHandler is None:
propertyHandler = defaultHandler(defaultVal)(options)
assert isinstance(propertyHandler, PropertyHandler)
assert isinstance(options, dict)
if propertyHandler.validate(defaultVal) != defaultVal:
raise PropertyInconsistentDefinition("The validation of the default value must be the identity!")
raise PropertyInconsistentDefinition(
"The validation of the default value must be the identity (%s != %s)!" %
(repr(propertyHandler.validate(defaultVal)), repr(defaultVal)))
if not name in self._properties:
self._properties[name] = Property(defaultVal, helpstr, propertyHandler)
p = self._properties[name]
Expand All @@ -138,7 +144,12 @@ def defineProperty(self, name, defaultVal, helpstr, options=None, propertyHandle
if p.defaultVal != defaultVal or p.helpstr != helpstr:
raise PropertyInconsistentDefinition(name)
if not isinstance(p.handler, type(propertyHandler)) or options != p.handler.options():
raise PropertyInconsistentDefinition(name)
if ignoreInconsistentOptions:
p.handler = propertyHandler
logger.debug("option %s has inconsistent options but ignoring as requested.", name)
else:
raise PropertyInconsistentDefinition(name)

p.used = True
return p.value

Expand Down
17 changes: 10 additions & 7 deletions nexxT/core/SubConfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,27 @@ def load(self, cfg, compositeLookup):
# apply node gui state
nextP = PropertyCollectionImpl("_nexxT", p, {"thread": n["thread"]})
logger.debug("loading: subconfig %s / node %s -> thread: %s", self._name, n["name"], n["thread"])
tmp = self._graph.addNode(n["library"], n["factoryFunction"], suggestedName=n["name"])
tmp = self._graph.addNode(n["library"], n["factoryFunction"], suggestedName=n["name"],
dynamicInputPorts=n["dynamicInputPorts"],
dynamicOutputPorts=n["dynamicOutputPorts"])
if tmp != n["name"]:
raise NexTInternalError("addNode(...) has set unexpected name for node.")
else:
# composite node handling
if n["library"] == "composite://port":
# the special nodes are already there, nothing to do here
pass
for dip in n["dynamicInputPorts"]:
self._graph.addDynamicInputPort(n["name"], dip)
for dop in n["dynamicOutputPorts"]:
self._graph.addDynamicOutputPort(n["name"], dop)
elif n["library"] == "composite://ref":
name = n["factoryFunction"]
cf = compositeLookup(name)
tmp = self._graph.addNode(cf, "compositeNode", suggestedName=n["name"])
tmp = self._graph.addNode(cf, "compositeNode", suggestedName=n["name"],
dynamicInputPorts=n["dynamicInputPorts"],
dynamicOutputPorts=n["dynamicOutputPorts"])
if tmp != n["name"]:
raise NexTInternalError("addNode(...) has set unexpected name for node.")
for dip in n["dynamicInputPorts"]:
self._graph.addDynamicInputPort(n["name"], dip)
for dop in n["dynamicOutputPorts"]:
self._graph.addDynamicOutputPort(n["name"], dop)
# make sure that the filter is instantiated and the port information is updated immediately
self._graph.getMockup(n["name"]).createFilterAndUpdate()
for c in cfg["connections"]:
Expand Down
20 changes: 14 additions & 6 deletions nexxT/examples/framework/ImageView.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,26 @@ def onClose(self):
# delete the widget reference
self._widget = None

def onPortDataChanged(self, port):
def interpretAndUpdate(self):
"""
The deferred update method, called from the MainWindow service at user-defined framerate.
"""
sample = self.inPort.getData()
if sample.getDatatype() == "example/image":
npa = byteArrayToNumpy(sample.getContent())
self._widget.setData(npa)

def onPortDataChanged(self, port): # pylint: disable=unused-argument
"""
Notification of new data.
:param port: the port where the data arrived.
:return:
"""
if port.getData().getDatatype() == "example/image":
# convert to numpy array
npa = byteArrayToNumpy(port.getData().getContent())
# send to the widget
self._widget.setData(npa)
if self._widget.isVisible():
# don't consume processing time if not shown
mw = Services.getService("MainWindow")
mw.deferredUpdate(self, "interpretAndUpdate")

class DisplayWidget(QWidget):
"""
Expand Down
5 changes: 4 additions & 1 deletion nexxT/interface/PropertyCollections.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ def defineProperty(self, name, defaultVal, helpstr, options=None, propertyHandle
:param defaultVal: the default value of the property. Note that this value will be used to determine the
property's type. Currently supported types are string, int and float
:param helpstr: a help string for the user (presented as a tool tip)
:param options: a dict mapping string to qvariant (common options: min, max, enum)
:param options: a dict mapping string to qvariant (common options: 'min', 'max', 'enum')
all properties support the option 'ignoreInconsistentOptions' (default: False). If this option
is True, then nexxT allows that the options change over time. Even if present, the option type
and its default values are not allowed to change.
:param propertyHandler: a PropertyHandler instance, or None for automatic choice according to defaultVal
:return: the current value of this property
"""
Expand Down
37 changes: 29 additions & 8 deletions nexxT/services/gui/BrowserWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,31 @@

logger = logging.getLogger(__name__)

class StatCache:
"""
Class for caching file-system related accesses to prevent unnecessary slowliness for network drives.
"""
MAX_NUM_CACHE_ENTRIES = 20*1024 # 1024 entries are ~40 kB -> ~ 1 MB cache

def __init__(self):
self._cache = {}

def __call__(self, method, *args):
if (method, args) not in self._cache:
# remove entries from cache until the size is within reasonable limits
while len(self._cache) > self.MAX_NUM_CACHE_ENTRIES:
self._cache.pop(next(iter(self._cache)))
self._cache[method, args] = method(*args)
return self._cache[method, args]

class FolderListModel(QAbstractTableModel):
"""
This class provides a model for browsing a folder.
"""
folderChanged = Signal(str) # emitted when the folder changes

statCache = StatCache()

def __init__(self, parent):
super().__init__(parent=parent)
self._folder = None
Expand Down Expand Up @@ -68,7 +87,7 @@ def fileToIndex(self, filename):
return QModelIndex()

def _match(self, path):
if path.is_dir():
if self.statCache(path.is_dir):
return True
res = QDir.match(self._filter, path.name)
return res
Expand All @@ -79,7 +98,7 @@ def _reset(self, folder, flt):
self.endRemoveRows()
if folder is not None:
listDrives = False
f = Path(folder).resolve()
f = self.statCache(Path(folder).resolve)
if platform.system() == "Windows":
folder = Path(folder)
if folder.name == ".." and folder.parent == Path(folder.drive + "/"):
Expand All @@ -89,20 +108,22 @@ def _reset(self, folder, flt):
self._filter = flt
if platform.system() == "Windows":
if listDrives:
self._children = [Path("%s:/" % dl) for dl in string.ascii_uppercase if Path("%s:/" % dl).exists()]
self._children = [Path("%s:/" % dl) for dl in string.ascii_uppercase
if self.statCache(Path("%s:/" % dl).exists)]
else:
self._children = [f / ".."]
else:
self._children = ([] if f.root == f else [f / ".."])
if not listDrives:
self._children += [x for x in f.glob("*") if self._match(x)]
self._children.sort(key=lambda c: (c.is_file(), c.drive, int(c.name != ".."), c.name))
self._children.sort(key=lambda c: (self.statCache(c.is_file), c.drive, int(c.name != ".."), c.name))
self.beginInsertRows(QModelIndex(), 0, len(self._children)-1)
self.endInsertRows()
if listDrives:
self.folderChanged.emit("<Drives>")
else:
self.folderChanged.emit(str(self._folder) + (os.path.sep if self._folder.is_dir() else ""))
self.folderChanged.emit(str(self._folder) + (os.path.sep if self.statCache(self._folder.is_dir)
else ""))

def folder(self):
"""
Expand Down Expand Up @@ -154,7 +175,7 @@ def data(self, index, role):
if c.is_dir():
return ""
try:
s = c.stat().st_size
s = self.statCache(c.stat).st_size
except Exception: # pylint: disable=broad-except
return ""
if s >= 1024*1024*1024:
Expand All @@ -171,7 +192,7 @@ def data(self, index, role):
return ""
if role == Qt.DecorationRole:
if index.column() == 0:
if c.is_dir():
if self.statCache(c.is_dir):
return self._iconProvider.icon(QFileIconProvider.Drive)
return self._iconProvider.icon(QFileInfo(str(c.absolute())))
if role == Qt.UserRole:
Expand All @@ -180,7 +201,7 @@ def data(self, index, role):
if role in [Qt.DisplayRole, Qt.EditRole]:
if index.column() == 3:
if index.row() > 0:
return str(c) + (os.path.sep if c.is_dir() else "")
return str(c) + (os.path.sep if self.statCache(c.is_dir) else "")
return str(c.parent) + os.path.sep
return None

Expand Down
Loading

0 comments on commit 935f6af

Please sign in to comment.