Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions PYME/Acquire/Hardware/Simulator/simcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,18 @@ class RandomDistribution(HasTraits):
n_instances = Int(1)
region_size = Float(5000)
generator = Instance(HasTraits)
# force one of the points to be at the origin (dirty hack to make sure there is a structure present in the simulator at startup)
force_at_origin = Bool(False)

def points(self):
xp = self.region_size*np.random.uniform(-1, 1, self.n_instances)
yp = self.region_size*np.random.uniform(-1, 1, self.n_instances)

if self.force_at_origin:
xp[0] = 0.0
yp[0] = 0.0


for xi, yi in zip(xp, yp):
for p in self.generator.points():
p1 = np.copy(p)
Expand Down
2 changes: 1 addition & 1 deletion PYME/Acquire/Scripts/init_sim_htsms.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def sim_controls(MainFrame, scope):
MainFrame.camPanels.append((msc, 'Simulation'))

from PYME.simulation import pointsets
scope.simcontrol.point_gen = simcontrol.RandomDistribution(n_instances=25,region_size=70e3,
scope.simcontrol.point_gen = simcontrol.RandomDistribution(n_instances=25,region_size=70e3, force_at_origin=True,
generator=simcontrol.Group(generators=[pointsets.WiglyFibreSource(),
simcontrol.AssignChannel(channel=1, generator=pointsets.SHNucleusSource())
]))
Expand Down
141 changes: 79 additions & 62 deletions PYME/misc/pyme_zeroconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,72 +21,89 @@
import time
#import Pyro.core
import threading
import logging
logger = logging.getLogger(__name__)

class PatchedServiceInfo(zeroconf.ServiceInfo):
"""
A patched version of the zero-conf ServiceInfo object which ensures that we get the port number as well as the address and name
"""

def request(self, zc, timeout):
"""Returns true if the service could be discovered on the
network, and updates this object with details discovered.
if zeroconf.__version__ >= '0.35':
# new ServiceInfo class

class PatchedServiceInfo(zeroconf.ServiceInfo):
"""
A patched version of the zero-conf ServiceInfo object which ensures that we get the port number as well as the address and name
"""
now = zeroconf.current_time_millis()
delay = zeroconf._LISTENER_TIME
next_ = now + delay
last = now + timeout
@property
def _is_complete(self) -> bool:
"""The ServiceInfo has all expected properties."""
return bool(self.text is not None and self.port is not None and (self._ipv4_addresses or self._ipv6_addresses))

record_types_for_check_cache = [
(zeroconf._TYPE_SRV, zeroconf._CLASS_IN),
(zeroconf._TYPE_TXT, zeroconf._CLASS_IN),
]
if self.server is not None:
record_types_for_check_cache.append((zeroconf._TYPE_A, zeroconf._CLASS_IN))
for record_type in record_types_for_check_cache:
cached = zc.cache.get_by_details(self.name, *record_type)
if cached:
self.update_record(zc, now, cached)
else:

if None not in (self.server, self.address, self.text, self.port):
return True
logger.warning(f'using an old version of zeroconf ({zeroconf.__version__}), consider upgrading')
class PatchedServiceInfo(zeroconf.ServiceInfo):
"""
A patched version of the zero-conf ServiceInfo object which ensures that we get the port number as well as the address and name
"""

def request(self, zc, timeout):
"""Returns true if the service could be discovered on the
network, and updates this object with details discovered.
"""
now = zeroconf.current_time_millis()
delay = zeroconf._LISTENER_TIME
next_ = now + delay
last = now + timeout

try:
zc.add_listener(self, zeroconf.DNSQuestion(self.name, zeroconf._TYPE_ANY, zeroconf._CLASS_IN))
while None in (self.server, self.address, self.text, self.port):
if last <= now:
return False
if next_ <= now:
out = zeroconf.DNSOutgoing(zeroconf._FLAGS_QR_QUERY)
out.add_question(
zeroconf.DNSQuestion(self.name, zeroconf._TYPE_SRV, zeroconf._CLASS_IN))

if self.port is not None:
out.add_answer_at_time(
zc.cache.get_by_details(
self.name, zeroconf._TYPE_SRV, zeroconf._CLASS_IN), now)

out.add_question(
zeroconf.DNSQuestion(self.name, zeroconf._TYPE_TXT, zeroconf._CLASS_IN))
out.add_answer_at_time(
zc.cache.get_by_details(
self.name, zeroconf._TYPE_TXT, zeroconf._CLASS_IN), now)

if self.server is not None:
record_types_for_check_cache = [
(zeroconf._TYPE_SRV, zeroconf._CLASS_IN),
(zeroconf._TYPE_TXT, zeroconf._CLASS_IN),
]
if self.server is not None:
record_types_for_check_cache.append((zeroconf._TYPE_A, zeroconf._CLASS_IN))
for record_type in record_types_for_check_cache:
cached = zc.cache.get_by_details(self.name, *record_type)
if cached:
self.update_record(zc, now, cached)

if None not in (self.server, self.address, self.text, self.port):
return True

try:
zc.add_listener(self, zeroconf.DNSQuestion(self.name, zeroconf._TYPE_ANY, zeroconf._CLASS_IN))
while None in (self.server, self.address, self.text, self.port):
if last <= now:
return False
if next_ <= now:
out = zeroconf.DNSOutgoing(zeroconf._FLAGS_QR_QUERY)
out.add_question(
zeroconf.DNSQuestion(self.name, zeroconf._TYPE_SRV, zeroconf._CLASS_IN))

if self.port is not None:
out.add_answer_at_time(
zc.cache.get_by_details(
self.name, zeroconf._TYPE_SRV, zeroconf._CLASS_IN), now)

out.add_question(
zeroconf.DNSQuestion(self.server, zeroconf._TYPE_A, zeroconf._CLASS_IN))
zeroconf.DNSQuestion(self.name, zeroconf._TYPE_TXT, zeroconf._CLASS_IN))
out.add_answer_at_time(
zc.cache.get_by_details(
self.server, zeroconf._TYPE_A, zeroconf._CLASS_IN), now)
zc.send(out)
next_ = now + delay
delay *= 2

zc.wait(min(next_, last) - now)
now = zeroconf.current_time_millis()
finally:
zc.remove_listener(self)
self.name, zeroconf._TYPE_TXT, zeroconf._CLASS_IN), now)

if self.server is not None:
out.add_question(
zeroconf.DNSQuestion(self.server, zeroconf._TYPE_A, zeroconf._CLASS_IN))
out.add_answer_at_time(
zc.cache.get_by_details(
self.server, zeroconf._TYPE_A, zeroconf._CLASS_IN), now)
zc.send(out)
next_ = now + delay
delay *= 2

zc.wait(min(next_, last) - now)
now = zeroconf.current_time_millis()
finally:
zc.remove_listener(self)

return True
return True

class ZCListener(object):
def __init__(self, protocol='_pyme-pyro'):
Expand Down Expand Up @@ -115,7 +132,7 @@ def add_service(self, zc, _type, name):

info = PatchedServiceInfo(_type, name)
if info.request(zc, 5000):
if is_port_open(socket.inet_ntoa(info.address), info.port):
if is_port_open(socket.inet_ntoa(info.addresses[0]), info.port):
with self._lock:
#info = zc.get_service_info(_type, name)
self.advertised_services[nm] = info
Expand All @@ -142,7 +159,7 @@ def _poll_services_open(self):
# check to see if the services are up (without lock)
dead_svcs = []
for name, info in svcs:
if not is_port_open(socket.inet_ntoa(info.address), info.port):
if not is_port_open(socket.inet_ntoa(info.addresses[0]), info.port):
dead_svcs.append((name, info))

# delete any dead services
Expand Down Expand Up @@ -202,9 +219,9 @@ def register_service(self, name, address, port, desc={}):
raise RuntimeError('Name "%s" already exists' %name)

info = PatchedServiceInfo("%s._tcp.local." % self._protocol,
"%s.%s._tcp.local." % (name, self._protocol),
socket.inet_aton(address), port, 0, 0,
desc)
name="%s.%s._tcp.local." % (name, self._protocol),
addresses=[socket.inet_aton(address),], port=port, weight=0, priority=0,
properties=desc)

self._services[name] = info
self.zc.register_service(info)
Expand Down
49 changes: 43 additions & 6 deletions PYME/recipes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#import wx
import six

from PYME.recipes.traits import HasTraits, Float, List, Bool, Int, CStr, Enum, File, on_trait_change, Input, Output
from PYME.recipes.traits import HasTraits, HasStrictTraits, Float, List, Bool, Int, CStr, Enum, File, on_trait_change, Input, Output, Instance

#for some reason traitsui raises SystemExit when called from sphinx on OSX
#This is due to the framework build problem of anaconda on OSX, and also
Expand All @@ -33,15 +33,44 @@
_legacy_modules = {}
module_names = {}

def register_module(moduleName):
def register_module(moduleName, prefix=None):
"""
Register a module so that it is discoverable by the recipe architecture

Recipe module prefixes are treated slightly differently depending on whether it is a PYME internal
recipe, or comes from an external module. For PYME-internal recipe modules, their prefix is the name
of the (python) module in which the recipe is defined. For external (plugin) recipe modules, it is
the name of the python **package** which contains the recipe module - a plugin may define multiple
recipes in different files, but they will all be accessible using the package prefix. This is a change
from previous behaviour where the most proximal module name was used in plugin derived modules (similar
to internal recipe modules). This change was made to a) decrease the chance of collisions, where plugin
modules shadowed exisiting internal recipe modules and b) make module provenance (is this an internal
or plugin-derived module) clear to assist debugging.

If the plugin package name is not sufficiently distinctive, or happens to shadow any of the internal
PYME recipe module names, plugin authors have the opportuntiy to specify their own prefix as a second
argument to register_module. If taking this route, the prefix should be an identifier of the
group/individual maintaining the plugin - e.g. baddeleylab.
"""
def c_decorate(cls):
py_module = cls.__module__.split('.')[-1]
full_module_name = '.'.join([py_module, moduleName])
if not prefix:
top_level_mod = cls.__module__.split('.')[0]
py_module = cls.__module__.split('.')[-1]

if top_level_mod == 'PYME':
prefix_ = py_module
else:
prefix_ = top_level_mod
else:
prefix_ = prefix

full_module_name = '.'.join([prefix_, moduleName])

all_modules[full_module_name] = cls
_legacy_modules[moduleName] = cls #allow acces by non-hierarchical names for backwards compatibility

module_names[cls] = full_module_name
cls._module_name = full_module_name
return cls

return c_decorate
Expand All @@ -60,6 +89,8 @@ def c_decorate(cls):
_legacy_modules[full_module_name] = cls
_legacy_modules[moduleName] = cls #allow access by non-hierarchical names for backwards compatibility

cls._module_name = full_module_name

#module_names[cls] = full_module_name
return cls

Expand All @@ -70,7 +101,7 @@ class MissingInputError(Exception):
pass


class ModuleBase(HasTraits):
class ModuleBase(HasStrictTraits):
"""
Recipe modules represent a "functional" processing block, the effects of which depend solely on its
inputs and parameters. They read a number of named inputs from the recipe namespace and write the
Expand All @@ -83,7 +114,13 @@ class ModuleBase(HasTraits):

If you want side effects - e.g. saving something to disk, look at the OutputModule class.
"""
_invalidate_parent = True
_invalidate_parent = Bool(True)
_parent=Instance(object)

_initial_set = Bool(False)
_success = Bool(False)
_last_error = Instance(object)


def __init__(self, parent=None, invalidate_parent = True, **kwargs):
self._parent = parent
Expand Down
Loading