Custom Sequence#

XChroma is built to handle custom-made acquisition sequences for your experiments. To do this, you must write a class inherited from the SequenceWorker class, define your custom run method, and then export it and define arguments in the main.py file.

Directory structure of the files used :

XChroma/
├── XChroma/
├── main.py
└── sequence.py

A Simple Sequence#

Let’s build a simple sequence that performs measurements to get absorbance and save it.

Step 1: Define the Class in sequence.py#

First, let’s define the class in the sequence.py file:

from XChroma.sequence_control import SequenceWorker

class SequenceSimple(SequenceWorker):
    def run(self):
        # Here goes the sequence code

The class needs to be inherited from the XChroma.sequence_control.SequenceWorker.

Step 2: Define the Measurements#

To get the absorbance spectrum, we need 3 different measurements:

  • The intensity spectrum of interest

  • A baseline from a reference curve (zero)

  • A dark signal spectrum

We will also use input() to introduce delays between the measurements.

from XChroma.sequence_control import SequenceWorker


class SequenceSimple(SequenceWorker):
    def run(self):
        print("Sequence started!")
        self.reset_servo() # Make sure all shutter are closed to start
        self.controller.send_command("a")  # Open the shutter for the probe signal

        # Measurement of the baseline
        _ = input("Put the Baseline (zero) sample. Please ensure you get signal from the spectrometer.")

        self.data_spectro.lavg.clear()
        time.sleep(10)  # Wait for automatic averaging as defined in the interface
        self.data_spectro.save_data(self.data_spectro.avg_i, cycle=0, spectype="zero", csv_path="Spectrums.csv")
        self.data_spectro.zero = self.data_spectro.avg_i

        self.progress_signal.emit(int(33))  # Update progress bar to 33%

        # Measurement of the sample
        _ = input("Put the sample.")

        self.data_spectro.lavg.clear()
        time.sleep(10)  # Wait for automatic averaging as defined in the interface
        self.data_spectro.save_data(self.data_spectro.avg_i, cycle=0, spectype="sample", csv_path="Spectrums.csv")
        self.data_spectro.zero = self.data_spectro.avg_i

        self.progress_signal.emit(int(66))  # Update progress bar to 66%

        # Measurement of the static signal
        self.reset_servo()  # Make sure servos are shut for the dark measurement
        time.sleep(10)  # Wait for automatic averaging as defined in the interface
        self.data_spectro.save_data(self.data_spectro.avg_i, cycle=0, spectype="static", csv_path="Spectrums.csv")
        self.data_spectro.static = self.data_spectro.static

        self.progress_signal.emit(int(100))  # Update progress bar to 100%

        self.controller.send_command("a")  # Open the shutter to view the result in the app

        self.finished_signal.emit()  # Mandatory!

Step 3: Use the Sequence in main.py#

Now, in the main.py file, import your custom sequence:

from sequence import SequenceSimple

Then, add it to the sequence_dict:

sequence_dict = {
    "Sequence Simple": {"class": SequenceSimple, "args": {}},
}
sequence_dict["Sequence Simple"] = {"class": SequenceSimple, "args": {}}

Step 4: Run Your Application#

You can run your app and see the results !

python main.py

Adding GUI control#

To add GUI control over the parameters of the Sequence, some steps differ from the basic sequence.

Update the Class Definition#

First, modify the class to include a new __init__ method to handle the additional parameters coming from the GUI. The class will now accept parameters such as delay and servo_letter from the GUI, in addition to controller and data_spectro.

from XChroma.sequence_control import SequenceWorker

class SequenceSimple(SequenceWorker):
    def __init__(self, controller, data_spectro, delay, servo_letter):
        super().__init__(controller, data_spectro)  # Mandatory!
        self.delay1 = delay  # Store delay value
        self.servo_letter = servo_letter  # Store servo letter for reuse in the run method

    def run(self):
        # Here goes the sequence code

Caution

In this case, the controller and data_spectro arguments need to be passed to the parent class (via super()), and the additional arguments can be of type str, float, or int.

Add the Sequence to main.py#

To use this new sequence in main.py, you need to update the sequence_dict to pass the new arguments (servo_letter and delay).

sequence_dict = {
    "Sequence Simple": {"class": SequenceSimple, "args": {"servo_letter": str, "delay": int}},
}
sequence_dict["Sequence Simple"] = {"class": SequenceSimple, "args": {"servo_letter": str, "delay": int}}

Caution

Don’t forget to define the parameters correctly in the GUI before running the sequence.

Run the Application#

Once everything is set up, you can run the app with:

python main.py

Use GUI Parameters in the run Method#

Now that you have added the necessary parameters to the class, you can use these GUI-defined parameters of the run method.

For example, you can use the delay parameter to add a sleep time before running the next step, and the servo_letter parameter to control witch servo is used:

GUI Control Example
def run(self):
    time.sleep(self.delay1)  # Use the delay parameter defined in the GUI
    self.controller.send_command(self.servo_letter)  # Send the command with the selected servo letter
    # Add more sequence steps...