photonscore.PhotonPix
PhotonPix.PhotonPixRemote
Python remote control interface for the PhotonPix detector module.
PhotonPix is an MCP-PMT photon counting module: detector, high-voltage supply, CFD and protection circuits in one housing, on USB. This module provides a gRPC client to control the PhotonPix Control desktop application, which must be running and reachable on the network.
One running application drives every PhotonPix plugged into the machine, so every call here names the detector by its serial number — the one laser engraved on the housing and shown in the S/N column of the GUI.
The communication protocol uses a settings diff pattern: stage changes with the setup_* methods, then call PhotonPixRemote.sync to push them and refresh the read-only status fields in a single round trip.
Set two detectors up and arm their photocathodes:
from photonscore import PhotonPixRemote
pix = PhotonPixRemote("192.168.1.10:50053")
pix.sync()
print(pix.detectors) # ('H604-B513', 'Z837-E839')
pix.setup_detector("H604-B513", high_voltage=3870,
zero_cross=2200, threshold=400)
pix.setup_detector("Z837-E839", high_voltage=4025,
zero_cross=2200, threshold=400)
pix.sync()
pix.all_photocathodes(True)
pix.sync()
print(pix.master_alarm) # overexposure or overheat on any detectorDependencies: grpc, photonscore.proto.photonpix_v1_remote_pb2 (generated from photonscore/proto/photonpix_v1_remote.proto)
PhotonPixRemote
class PhotonPixRemote(target = None)Remote control handle for every PhotonPix on one host.
Wraps the gRPC PhotonPixRemote service exposed by the PhotonPix Control application. Configuration changes are staged locally in new_settings; call sync to commit them and read the current state back into read_settings.
The constructor does not sync, so detectors is empty until the first sync.
Attributes
grpc_target(str): Resolvedhost:portof the gRPC server.grpc_channel(grpc.Channel): Underlying insecure gRPC channel.grpc_client(PhotonPixRemoteStub): Generated gRPC stub.new_settings(PhotonPixRemoteSettings): Buffer of changes staged but not yet sent.read_settings(PhotonPixRemoteSettings): State returned by the most recentsync.
sync
def sync()Push pending settings and refresh all status fields.
Sends the accumulated new_settings diff via the Sync RPC. The reply — the full state as it was before this call's changes — lands in read_settings; call sync again to read the applied values back. After the call new_settings is reset so the next sync only sends newly staged changes.
Calling this with nothing staged is a harmless way to poll, e.g. to see which detectors are plugged in or whether one is in alarm.
detectors
property
detectorstuple: Serial numbers of the connected detectors, sorted, from the most recent sync. Detectors are enumerated as they are plugged in, so this changes between syncs.
state
def state(serial = None)Read back a detector's settings and status from the last sync.
Arguments
serial(str): Serial number of the detector. Omit for a{serial: state}dict covering every connected detector.
Returns: high_voltage, zero_cross, threshold, photocathode_on and label as set, plus the read-only alarm, loaded_from_config, selected, high_voltage_now and high_voltage_ramping. A setting the software has not written since it saw the detector reads None — the DACs are write-only, nothing is read off the hardware.
Raises: KeyError The serial number was not among the connected detectors at the last sync.
setup_detector
def setup_detector(
serial,
high_voltage = None,
zero_cross = None,
threshold = None,
photocathode_on = None,
label = None,
)Stage configuration changes for a single detector.
Only the parameters passed as non-None are written; the rest are left untouched. Changes are buffered and take effect on the next sync.
The three analog settings are 12-bit DAC codes, 0 to 4095, the same numbers the GUI and config.json show. Approximately: high voltage spans 0 to -2460 V, zero cross -23 to +23 mV, threshold 0 to -120 mV.
Arguments
serial(str): Serial number of the detector, as engraved on it.high_voltage(int): MCP high voltage (0 to 4095). The supply is walked toward it rather than jumped, a full 0–4095 swing taking about 5 s; watchhigh_voltage_rampinginstateto know when it has arrived. A detector listed inconfig.jsoncannot be driven above its level there — seehigh_voltage_limitinstate— and asking for more raisesINVALID_ARGUMENT.zero_cross(int): CFD zero crossing (0 to 4095).threshold(int): CFD threshold (0 to 4095).photocathode_on(bool): Switch the photocathode on or off. Prepare the optical path before switching it on.label(str): Free text shown in the GUI's Description column; saved toconfig.jsonwhen the application exits.
Raises: ValueError A DAC setting is outside 0 to 4095.
all_photocathodes
def all_photocathodes(on)Stage the GUI's All Photocathodes switch; applied on the next sync.
Arguments
on(bool): Switch every connected detector's photocathode on or off. This wins over aphotocathode_onstaged for a single detector in the samesync.
master_alarm
property
master_alarmbool: At least one connected detector was in alarm at the most recent sync — the overexposure or overheat protection has fired and cut that detector's photocathode. Reduce the light, then switch the photocathode back on.
master_photocathodes_on
property
master_photocathodes_onbool: Every connected detector had its photocathode on at the most recent sync.
wait_for_ramp
def wait_for_ramp(timeout = 10.0, poll = 0.1)Sync until no detector's high voltage is still ramping.
The MCP supply is walked toward a new setting over up to five seconds, so a value written by setup_detector is not on the hardware the moment sync returns. Call this when the next step needs the voltage to have arrived.
Arguments
timeout(float): Give up after this many seconds.poll(float): Seconds between syncs.
Returns: True once every detector has arrived, False on timeout.
alarms
def alarms()Serial numbers of the detectors in alarm at the last sync.
Returns: The detectors whose protection circuit has fired, sorted.