cal_psy

  1from psychopy import visual, event, core
  2import numpy as np
  3from cal_lib import GammaFitter
  4
  5class GrayLevels:
  6    """
  7    A class to measure and calibrate monitor gamma using a SpyderX colorimeter and PsychoPy.
  8
  9    This class allows calibration of the SpyderX device, measurement of luminance levels for
 10    various grayscale levels, and fitting of the gamma curve using the provided luminance data.
 11
 12    Attributes:
 13        spyder (object): An instance of the SpyderX class used for photometric measurements.
 14        win (psychopy.visual.Window): The PsychoPy window for displaying gray levels.
 15        bg_rect (psychopy.visual.Rect): A full-screen rectangle used to simulate background color.
 16    """
 17
 18    def __init__(self, spyder, fullscr=False):
 19        """
 20        Initializes the GrayLevels class.
 21
 22        Args:
 23            spyder (SpyderX): An initialized SpyderX object for luminance measurements.
 24            fullscr (bool): Whether to open the PsychoPy window in full-screen mode.
 25                            Defaults to False.
 26        """
 27        self.spyder = spyder
 28        self.win = visual.Window([800, 600], color=[0, 0, 0], units="norm", waitBlanking=True, fullscr=fullscr)
 29        self.bg_rect = visual.Rect(self.win, width=2, height=2, fillColor=[0, 0, 0], lineColor=None)
 30        self.bg_rect.draw()
 31
 32    def calibrate(self):
 33        """
 34        Calibrates the SpyderX device to measure the baseline luminance.
 35
 36        Displays instructions for the user to close the SpyderX sensor to perform a black
 37        level calibration and waits for user confirmation before proceeding.
 38        """
 39        print('### GRAYLEVELS ### Spyder calibration', end=' ')
 40        instruction = visual.TextStim(self.win,
 41                                      text="Close the SpyderX to measure baseline and press space to start.",
 42                                      color=[1, 1, 1])
 43        self.bg_rect.draw()
 44        instruction.draw()
 45        self.win.flip()
 46        event.waitKeys(keyList=["space"])
 47
 48        self.spyder.calibrate()
 49        print('DONE')
 50
 51    def measure(self, pause=1, gamma=None, num_levels=12,wait_user=True):
 52        """
 53        Measures luminance levels across a range of grayscale values.
 54
 55        Displays a series of gray levels on the monitor and uses the SpyderX device to measure
 56        the corresponding luminance values. The gamma curve is then fitted using the GammaFitter class.
 57
 58        Args:
 59            pause (float): The time (in seconds) to pause after each gray level display to ensure stabilization.
 60                           Defaults to 1 second.
 61            gamma (float, optional): A predefined gamma value to set for the monitor (for testing the linearity of the monitor after correction). If None, gamma remains unchanged.
 62                                     Defaults to None. This parameter works on Windows only with one monitor or in mirror mode.
 63            num_levels (int): The number of gray levels to display and measure. Defaults to 12.
 64            wait_user (bool): wait for keypress to start
 65
 66        Returns:
 67            GammaFitter: An instance of the GammaFitter class containing the gamma value and the fit result.
 68        """
 69        if gamma is not None:
 70            self.win.setGamma(gamma)
 71        gray_levels = np.linspace(-1, 1, num_levels)
 72        luminance_readings = []
 73
 74        if wait_user:
 75            instruction = visual.TextStim(self.win,
 76                                          text="Position the SpyderX on the monitor and press space to start.",
 77                                          color=[1, 1, 1])
 78            self.bg_rect.fillColor = [-1, -1, -1]
 79            self.bg_rect.draw()
 80            instruction.draw()
 81            self.win.flip()
 82            event.waitKeys(keyList=["space"])
 83        self.bg_rect.fillColor = [-1, -1, -1]
 84        self.bg_rect.draw()
 85        self.win.flip()
 86        core.wait(pause)
 87
 88        for gray in gray_levels:
 89            self.bg_rect.fillColor = [gray, gray, gray]
 90            self.bg_rect.draw()
 91            self.win.flip()
 92            core.wait(pause)  # Wait for the screen to stabilize
 93
 94            XYZ = self.spyder.measure()
 95            luminance = XYZ[1]  # Y component of XYZ is luminance
 96            luminance_readings.append(luminance)
 97            print(f"### GRAYLEVELS ### The luminance for gray level {gray:.3f} is {luminance:.3f}")
 98
 99        gfit = GammaFitter(gray_levels, luminance_readings)
100        gfit.fit()
101        print(f"### GRAYLEVELS ### Monitor Gamma value: {gfit.gamma}")
102        gfit.plot()
103
104        if gamma is not None:
105            # Reset monitor gamma to default
106            self.win.setGamma(1)
107        return gfit
108
109    def close(self):
110        """
111        Closes the PsychoPy window and releases the SpyderX device.
112
113        Ensures that all resources are properly cleaned up.
114        """
115        self.win.close()
116        self.spyder.close()
117
118if __name__ == '__main__':
119    from cal_lib import SpyderX
120    libusb_path = r"C:\cancellami\vcpkg\installed\x64-windows\bin\libusb-1.0.dll"  # Replace with actual path
121    spyder = SpyderX(libusb_path)
122    gl = GrayLevels(spyder)
123    gl.calibrate()
124    gammas = list()
125    gfit = gl.measure(num_levels=12)
126    gammas.append(gfit.gamma)
127    for i in range(0,3):
128        gfit = gl.measure(num_levels=12,wait_user=False)
129        gammas.append(gfit.gamma)
130    #gl.measure(gamma=np.mean(gammas),num_levels=12,wait_user=False)
131    gl.close()
132    print(f'Display Gamma avg: {np.mean(gammas)}')
class GrayLevels:
  6class GrayLevels:
  7    """
  8    A class to measure and calibrate monitor gamma using a SpyderX colorimeter and PsychoPy.
  9
 10    This class allows calibration of the SpyderX device, measurement of luminance levels for
 11    various grayscale levels, and fitting of the gamma curve using the provided luminance data.
 12
 13    Attributes:
 14        spyder (object): An instance of the SpyderX class used for photometric measurements.
 15        win (psychopy.visual.Window): The PsychoPy window for displaying gray levels.
 16        bg_rect (psychopy.visual.Rect): A full-screen rectangle used to simulate background color.
 17    """
 18
 19    def __init__(self, spyder, fullscr=False):
 20        """
 21        Initializes the GrayLevels class.
 22
 23        Args:
 24            spyder (SpyderX): An initialized SpyderX object for luminance measurements.
 25            fullscr (bool): Whether to open the PsychoPy window in full-screen mode.
 26                            Defaults to False.
 27        """
 28        self.spyder = spyder
 29        self.win = visual.Window([800, 600], color=[0, 0, 0], units="norm", waitBlanking=True, fullscr=fullscr)
 30        self.bg_rect = visual.Rect(self.win, width=2, height=2, fillColor=[0, 0, 0], lineColor=None)
 31        self.bg_rect.draw()
 32
 33    def calibrate(self):
 34        """
 35        Calibrates the SpyderX device to measure the baseline luminance.
 36
 37        Displays instructions for the user to close the SpyderX sensor to perform a black
 38        level calibration and waits for user confirmation before proceeding.
 39        """
 40        print('### GRAYLEVELS ### Spyder calibration', end=' ')
 41        instruction = visual.TextStim(self.win,
 42                                      text="Close the SpyderX to measure baseline and press space to start.",
 43                                      color=[1, 1, 1])
 44        self.bg_rect.draw()
 45        instruction.draw()
 46        self.win.flip()
 47        event.waitKeys(keyList=["space"])
 48
 49        self.spyder.calibrate()
 50        print('DONE')
 51
 52    def measure(self, pause=1, gamma=None, num_levels=12,wait_user=True):
 53        """
 54        Measures luminance levels across a range of grayscale values.
 55
 56        Displays a series of gray levels on the monitor and uses the SpyderX device to measure
 57        the corresponding luminance values. The gamma curve is then fitted using the GammaFitter class.
 58
 59        Args:
 60            pause (float): The time (in seconds) to pause after each gray level display to ensure stabilization.
 61                           Defaults to 1 second.
 62            gamma (float, optional): A predefined gamma value to set for the monitor (for testing the linearity of the monitor after correction). If None, gamma remains unchanged.
 63                                     Defaults to None. This parameter works on Windows only with one monitor or in mirror mode.
 64            num_levels (int): The number of gray levels to display and measure. Defaults to 12.
 65            wait_user (bool): wait for keypress to start
 66
 67        Returns:
 68            GammaFitter: An instance of the GammaFitter class containing the gamma value and the fit result.
 69        """
 70        if gamma is not None:
 71            self.win.setGamma(gamma)
 72        gray_levels = np.linspace(-1, 1, num_levels)
 73        luminance_readings = []
 74
 75        if wait_user:
 76            instruction = visual.TextStim(self.win,
 77                                          text="Position the SpyderX on the monitor and press space to start.",
 78                                          color=[1, 1, 1])
 79            self.bg_rect.fillColor = [-1, -1, -1]
 80            self.bg_rect.draw()
 81            instruction.draw()
 82            self.win.flip()
 83            event.waitKeys(keyList=["space"])
 84        self.bg_rect.fillColor = [-1, -1, -1]
 85        self.bg_rect.draw()
 86        self.win.flip()
 87        core.wait(pause)
 88
 89        for gray in gray_levels:
 90            self.bg_rect.fillColor = [gray, gray, gray]
 91            self.bg_rect.draw()
 92            self.win.flip()
 93            core.wait(pause)  # Wait for the screen to stabilize
 94
 95            XYZ = self.spyder.measure()
 96            luminance = XYZ[1]  # Y component of XYZ is luminance
 97            luminance_readings.append(luminance)
 98            print(f"### GRAYLEVELS ### The luminance for gray level {gray:.3f} is {luminance:.3f}")
 99
100        gfit = GammaFitter(gray_levels, luminance_readings)
101        gfit.fit()
102        print(f"### GRAYLEVELS ### Monitor Gamma value: {gfit.gamma}")
103        gfit.plot()
104
105        if gamma is not None:
106            # Reset monitor gamma to default
107            self.win.setGamma(1)
108        return gfit
109
110    def close(self):
111        """
112        Closes the PsychoPy window and releases the SpyderX device.
113
114        Ensures that all resources are properly cleaned up.
115        """
116        self.win.close()
117        self.spyder.close()

A class to measure and calibrate monitor gamma using a SpyderX colorimeter and PsychoPy.

This class allows calibration of the SpyderX device, measurement of luminance levels for various grayscale levels, and fitting of the gamma curve using the provided luminance data.

Attributes: spyder (object): An instance of the SpyderX class used for photometric measurements. win (psychopy.visual.Window): The PsychoPy window for displaying gray levels. bg_rect (psychopy.visual.Rect): A full-screen rectangle used to simulate background color.

GrayLevels(spyder, fullscr=False)
19    def __init__(self, spyder, fullscr=False):
20        """
21        Initializes the GrayLevels class.
22
23        Args:
24            spyder (SpyderX): An initialized SpyderX object for luminance measurements.
25            fullscr (bool): Whether to open the PsychoPy window in full-screen mode.
26                            Defaults to False.
27        """
28        self.spyder = spyder
29        self.win = visual.Window([800, 600], color=[0, 0, 0], units="norm", waitBlanking=True, fullscr=fullscr)
30        self.bg_rect = visual.Rect(self.win, width=2, height=2, fillColor=[0, 0, 0], lineColor=None)
31        self.bg_rect.draw()

Initializes the GrayLevels class.

Args: spyder (SpyderX): An initialized SpyderX object for luminance measurements. fullscr (bool): Whether to open the PsychoPy window in full-screen mode. Defaults to False.

spyder
win
bg_rect
def calibrate(self):
33    def calibrate(self):
34        """
35        Calibrates the SpyderX device to measure the baseline luminance.
36
37        Displays instructions for the user to close the SpyderX sensor to perform a black
38        level calibration and waits for user confirmation before proceeding.
39        """
40        print('### GRAYLEVELS ### Spyder calibration', end=' ')
41        instruction = visual.TextStim(self.win,
42                                      text="Close the SpyderX to measure baseline and press space to start.",
43                                      color=[1, 1, 1])
44        self.bg_rect.draw()
45        instruction.draw()
46        self.win.flip()
47        event.waitKeys(keyList=["space"])
48
49        self.spyder.calibrate()
50        print('DONE')

Calibrates the SpyderX device to measure the baseline luminance.

Displays instructions for the user to close the SpyderX sensor to perform a black level calibration and waits for user confirmation before proceeding.

def measure(self, pause=1, gamma=None, num_levels=12, wait_user=True):
 52    def measure(self, pause=1, gamma=None, num_levels=12,wait_user=True):
 53        """
 54        Measures luminance levels across a range of grayscale values.
 55
 56        Displays a series of gray levels on the monitor and uses the SpyderX device to measure
 57        the corresponding luminance values. The gamma curve is then fitted using the GammaFitter class.
 58
 59        Args:
 60            pause (float): The time (in seconds) to pause after each gray level display to ensure stabilization.
 61                           Defaults to 1 second.
 62            gamma (float, optional): A predefined gamma value to set for the monitor (for testing the linearity of the monitor after correction). If None, gamma remains unchanged.
 63                                     Defaults to None. This parameter works on Windows only with one monitor or in mirror mode.
 64            num_levels (int): The number of gray levels to display and measure. Defaults to 12.
 65            wait_user (bool): wait for keypress to start
 66
 67        Returns:
 68            GammaFitter: An instance of the GammaFitter class containing the gamma value and the fit result.
 69        """
 70        if gamma is not None:
 71            self.win.setGamma(gamma)
 72        gray_levels = np.linspace(-1, 1, num_levels)
 73        luminance_readings = []
 74
 75        if wait_user:
 76            instruction = visual.TextStim(self.win,
 77                                          text="Position the SpyderX on the monitor and press space to start.",
 78                                          color=[1, 1, 1])
 79            self.bg_rect.fillColor = [-1, -1, -1]
 80            self.bg_rect.draw()
 81            instruction.draw()
 82            self.win.flip()
 83            event.waitKeys(keyList=["space"])
 84        self.bg_rect.fillColor = [-1, -1, -1]
 85        self.bg_rect.draw()
 86        self.win.flip()
 87        core.wait(pause)
 88
 89        for gray in gray_levels:
 90            self.bg_rect.fillColor = [gray, gray, gray]
 91            self.bg_rect.draw()
 92            self.win.flip()
 93            core.wait(pause)  # Wait for the screen to stabilize
 94
 95            XYZ = self.spyder.measure()
 96            luminance = XYZ[1]  # Y component of XYZ is luminance
 97            luminance_readings.append(luminance)
 98            print(f"### GRAYLEVELS ### The luminance for gray level {gray:.3f} is {luminance:.3f}")
 99
100        gfit = GammaFitter(gray_levels, luminance_readings)
101        gfit.fit()
102        print(f"### GRAYLEVELS ### Monitor Gamma value: {gfit.gamma}")
103        gfit.plot()
104
105        if gamma is not None:
106            # Reset monitor gamma to default
107            self.win.setGamma(1)
108        return gfit

Measures luminance levels across a range of grayscale values.

Displays a series of gray levels on the monitor and uses the SpyderX device to measure the corresponding luminance values. The gamma curve is then fitted using the GammaFitter class.

Args: pause (float): The time (in seconds) to pause after each gray level display to ensure stabilization. Defaults to 1 second. gamma (float, optional): A predefined gamma value to set for the monitor (for testing the linearity of the monitor after correction). If None, gamma remains unchanged. Defaults to None. This parameter works on Windows only with one monitor or in mirror mode. num_levels (int): The number of gray levels to display and measure. Defaults to 12. wait_user (bool): wait for keypress to start

Returns: GammaFitter: An instance of the GammaFitter class containing the gamma value and the fit result.

def close(self):
110    def close(self):
111        """
112        Closes the PsychoPy window and releases the SpyderX device.
113
114        Ensures that all resources are properly cleaned up.
115        """
116        self.win.close()
117        self.spyder.close()

Closes the PsychoPy window and releases the SpyderX device.

Ensures that all resources are properly cleaned up.