photonscore.PhotonPix

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 detector

Dependencies: 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): Resolved host:port of 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 recent sync.

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

detectors

tuple: 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; watch high_voltage_ramping in state to know when it has arrived. A detector listed in config.json cannot be driven above its level there — see high_voltage_limit in state — and asking for more raises INVALID_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 to config.json when 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 a photocathode_on staged for a single detector in the same sync.

master_alarm

property

master_alarm

bool: 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_on

bool: 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.