From 36c6834132ad6bc549bc6d5770ec18f981339bd5 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 09:42:15 +0100 Subject: [PATCH 01/11] added gamma parameter and implemented a correction with any gamma --- psychopy_pixx/calibration/calibration.py | 9 ++++----- psychopy_pixx/devices/viewpixx.py | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index b09326a..ac7be40 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -145,9 +145,9 @@ def measure_luminances( @click.option('--levelspost', help='Number of measurements after linearization', default=100) @click.option('--restests', help='Number of test points for luminance resolution', default=5) @click.option('--plot', help='Show plots.', is_flag=True) -@click.option('--linearize/--no-linearize', help='Do (not) linearize the luminance. Default is linearizing.', default=True) +@click.option('--gamma', help='Gamma with which the monitor is to be corrected. (default: 1.0 (linearization))', type=float, default=1.0) @click.option('--measures', help='Number of measurements to average per color level (only S470 photometer).', type=int, default=250) -def calibration_routine_cli(levels, monitor, screen, photometer, port, random, levelspost, restests, plot, linearize, measures): +def calibration_routine_cli(levels, monitor, screen, photometer, port, random, levelspost, restests, plot, measures, gamma=1.0): from psychopy import monitors, visual # lazy import print(f"Setup monitor {monitor}, search for photometer {photometer} ...") @@ -205,9 +205,8 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, l 'random_measures': random, } - if linearize: - print("Linearize luminance ...") - vpixx.linearize_luminance() + print(f'Correct luminance (gamma={gamma}) ...') + vpixx.correct_luminance(gamma) if levelspost > 0: print(f"Measure luminances again for validation ...") diff --git a/psychopy_pixx/devices/viewpixx.py b/psychopy_pixx/devices/viewpixx.py index c1c0533..9138006 100644 --- a/psychopy_pixx/devices/viewpixx.py +++ b/psychopy_pixx/devices/viewpixx.py @@ -43,7 +43,7 @@ def __init__(self, win, gamma=None): self._setup_shader() - def linearize_luminance(self, assert_register=True): + def correct_luminance(self, gamma=1.0, assert_register=True): if assert_register: try: calib_reg = self.window.monitor.currentCalib['viewpixx']['register'] @@ -55,7 +55,7 @@ def linearize_luminance(self, assert_register=True): for k, v in calib_reg.items(): assert register[k] == v, f"Expects {k}={v}, got {k}={register[k]}" - self.shader_clut = interp_clut(self.window.monitor) + self.shader_clut = interp_clut(self.window.monitor, gamma) @property def shader_clut(self) -> np.ndarray: @@ -218,7 +218,7 @@ def use_calibration_register(self): self.register = self.window.monitor.currentCalib['viewpixx']['register'] -def interp_clut(monitor): +def interp_clut(monitor, gamma): lums = monitor.getLumsPre() levels = monitor.getLevelsPre() @@ -247,7 +247,7 @@ def interp_clut(monitor): desired_lums = np.linspace(0, 1, 2**16, endpoint=True) desired_levels = np.empty((nguns, len(desired_lums))) for gun in range(4): - desired_levels[gun, :] = np.interp(x=desired_lums, xp=lums[gun], fp=levels) + desired_levels[gun, :] = (np.interp(x=desired_lums, xp=lums[gun], fp=levels))**gamma # = (invertedfunction)**gamma (if gamma == 1.0 it is just a linearisation) return desired_levels From c19e9a06fcd72de5281afd0b8f6eb15d7c6ce97a Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 09:46:53 +0100 Subject: [PATCH 02/11] added inverted parameter and implemented a inverted measurement of luminance values --- psychopy_pixx/calibration/calibration.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index ac7be40..5fa2262 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -15,6 +15,7 @@ def measure_luminances( allGuns=True, autoMode='auto', random=False, + inverted=False, stimSize=4, n_measures=50): """Automatically measures a series of gun values and measures @@ -72,9 +73,14 @@ def measure_luminances( if photometer.type == 'S470' and n_measures is not None: photometer.n_repeat = n_measures - if random: + if random and inverted: + print(f'ERROR: you can not set both random={random} and inverted={inverted}!') + return 1 + if random and not inverted: shuffled_index = np.random.permutation(len(levels)) toTest = levels[shuffled_index] + elif not random and inverted: + toTest = levels[::-1] else: toTest = levels @@ -142,12 +148,13 @@ def measure_luminances( @click.option('-p', '--photometer', required=True, help='photometer name supported by psychopy') @click.option('--port', help='Port of the photometer', default=None) @click.option('--random', help='Measure in randomized order.', is_flag=True) +@click.option('--inverted', help='Measure in inverted order.', is_flag=True) @click.option('--levelspost', help='Number of measurements after linearization', default=100) @click.option('--restests', help='Number of test points for luminance resolution', default=5) @click.option('--plot', help='Show plots.', is_flag=True) @click.option('--gamma', help='Gamma with which the monitor is to be corrected. (default: 1.0 (linearization))', type=float, default=1.0) @click.option('--measures', help='Number of measurements to average per color level (only S470 photometer).', type=int, default=250) -def calibration_routine_cli(levels, monitor, screen, photometer, port, random, levelspost, restests, plot, measures, gamma=1.0): +def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0): from psychopy import monitors, visual # lazy import print(f"Setup monitor {monitor}, search for photometer {photometer} ...") @@ -172,7 +179,7 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, l register_str = "\n".join(f"\t{key}: {val}" for key, val in monitor_state.items()) click.confirm(f'This is your monitor state. Ok?\n{register_str}\n' , abort=True) - measure_kwargs = dict(window=window, photometer=photometer, random=random, + measure_kwargs = dict(window=window, photometer=photometer, random=random, inverted=inverted, allGuns=False, n_measures=measures) print(f"Measure a few black and white screens ...") blackwhiteLums = measure_luminances(np.array([1, 1, 1 , 0, 0, 0]), **measure_kwargs)[0] From 2a51d5d23bfa5cb32714596b4bc9046a5e5200fc Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:19:22 +0100 Subject: [PATCH 03/11] added --all_measurements and --savefiles parameter and implemented saving/logging of (all) measurements --- psychopy_pixx/calibration/calibration.py | 69 ++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index 5fa2262..7d67010 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -3,6 +3,9 @@ import numpy as np import click import matplotlib.pyplot as plt +import csv +import os +from datetime import datetime from psychopy_pixx.calibration.photometer import findPhotometer from psychopy_pixx.devices import ViewPixx @@ -17,7 +20,9 @@ def measure_luminances( random=False, inverted=False, stimSize=4, - n_measures=50): + n_measures=50, + all_measurements=False, + savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): """Automatically measures a series of gun values and measures the luminance with a photometer. @@ -61,6 +66,8 @@ def measure_luminances( sf=[window.clientSize[0] / 512.0, window.clientSize[1] / 512.0]) testPatch = visual.PatchStim(window, tex='sqr', size=stimSize, color=initRGB, units='norm') + + date_time = datetime.now().strftime("%Y-%m-%d_%H-%M") # save date and time for file distinction if autoMode != 'semi': message.setText('Q to quit at any time') @@ -90,6 +97,22 @@ def measure_luminances( guns = [0] # this will hold the measured luminance values lumsList = np.zeros((4, len(toTest))) + + # create list for logging all measurments + if all_measurements: + allLums_data = [] + + # prepare logging + if all_measurements and savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': + + data_file = f"{savefiles}/allMeasurments{date_time}.csv" + writer = csv.writer(open(data_file, 'w')) + # create header + header = ['levels'] + for i in range(1, n_measures + 1): + header.append(f'measurement_{i:03}') + writer.writerow(header) + # for each gun, for each value run test for gun in guns: for valN, DACval in enumerate(toTest): @@ -115,7 +138,11 @@ def measure_luminances( # take measurement if autoMode == 'auto': - actualLum = photometer.getLum() + if all_measurements: + actualLum, lums = photometer.getLum(return_all=True) + allLums_data.append([DACval] + lums) + else: + actualLum = photometer.getLum() print(f"\t{valN + 1}/{len(toTest)} At DAC value {DACval:.2f}\t: {actualLum:.2f}cd/m^2") lumsList[gun, valN] = actualLum # check for quit request @@ -136,6 +163,9 @@ def measure_luminances( elif thisKey in (' ', 'space'): done = True + if all_measurements and savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': + writer.writerow(allLums_data[-1]) + if random: # revert shuffling lumsList = lumsList[:, shuffled_index.argsort()] return lumsList @@ -154,9 +184,22 @@ def measure_luminances( @click.option('--plot', help='Show plots.', is_flag=True) @click.option('--gamma', help='Gamma with which the monitor is to be corrected. (default: 1.0 (linearization))', type=float, default=1.0) @click.option('--measures', help='Number of measurements to average per color level (only S470 photometer).', type=int, default=250) -def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0): +@click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') +@click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) +def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False): from psychopy import monitors, visual # lazy import + # check if paths exist + # Note: I have to use the string with the uuid as control, so if the option is not set, I dont log/plot + if savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': + if not os.path.isdir(savefiles): + print(f'ERROR: "{savefiles}" is no valide directory') + return 1 + if plot!='no_plots_8e26a619-e688-4dcf-b010-7bd5fca459d8': + if not os.path.isdir(savefiles): + print(f'ERROR: "{plot}" is no valide directory') + return 1 + print(f"Setup monitor {monitor}, search for photometer {photometer} ...") monitor = monitors.Monitor(monitor) photometer = findPhotometer(device=photometer, ports=port) @@ -188,11 +231,24 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i or blackwhiteLums[3:].max() - blackwhiteLums[3:].min() > 0.1): raise ValueError(f"Black / white measurements are quite differerent." f"Probably something is wrong. {blackwhiteLums}") + if savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': + data = np.vstack((100*np.array([1, 1, 1 , 0, 0, 0]), blackwhiteLums)).T + date_time = datetime.now().strftime("%Y-%m-%d_%H-%M") # save date and time for file distinction + data_file = f"{savefiles}/blackwhiteLums_{date_time}.csv" + np.savetxt(data_file, data, fmt="%.2f", delimiter=",", header='levels,luminance_gun1,luminance_gun2,luminance_gun3,luminance_gun4') # luminances in cd/m2" click.confirm(f'Your monitor shows {minLum:.2f} cd/m^2 to {maxLum:.2f} cd/m^2. Ok?', abort=True) + # measurements print(f"Measure luminance series ...") levelsPre = np.linspace(0, 1, levels, endpoint=True) - lumsPre = measure_luminances(levelsPre, **measure_kwargs) + measure_kwargs_realMeasurment = dict(window=window, photometer=photometer, random=random, inverted=inverted, + allGuns=False, n_measures=measures, all_measurements=all_measurements, savefiles=savefiles) + lumsPre = measure_luminances(levelsPre, **measure_kwargs_realMeasurment) + if savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': + data = np.vstack((100*levelsPre, lumsPre)).T # percent for better accuracy, all 4 post guns + date_time = datetime.now().strftime("%Y-%m-%d_%H-%M") # save date and time for file distinction + data_file = f"{savefiles}/luminancePre_{date_time}.csv" + np.savetxt(data_file, data, fmt="%.2f", delimiter=",", header='levels,luminance_gun1,luminance_gun2,luminance_gun3,luminance_gun4') # luminances in cd/m2" print("Create new monitor calibration.") monitor.newCalib(width=monitor.getWidth(), distance=monitor.getDistance()) @@ -236,6 +292,11 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i print("Save new monitor calibration ...") monitor.save() window.close() + if savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5' and levelspost > 0: + data = np.vstack((100*levelsPost, lumsPost)).T # percent for better accuracy, all 4 post guns + date_time = datetime.now().strftime("%Y-%m-%d_%H-%M") # save date and time for file distinction + data_file = f"{savefiles}/luminancePost_{date_time}.csv" + np.savetxt(data_file, data, fmt="%.2f", delimiter=",", header='levels,luminance_gun1,luminance_gun2,luminance_gun3,luminance_gun4') # luminances in cd/m2" print("Plot measurements ...") plt.plot(levelsPre, lumsPre[0], label='pre') From 7d9525e6d832e0cdc7da179ea73d321b19a0b0b8 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:31:33 +0100 Subject: [PATCH 04/11] minor fixes --- psychopy_pixx/calibration/calibration.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index 7d67010..6dacb3a 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -206,6 +206,7 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i if photometer is None: raise ValueError('Photometer not found. You might specify (another) port or name.') + # monitor setup monitor_size = monitor.getSizePix() if monitor_size is None: raise ValueError("No monitor size defined. Please setup monitor in psychopy's monitor center.") @@ -274,7 +275,7 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i if levelspost > 0: print(f"Measure luminances again for validation ...") levelsPost = np.linspace(0, 1, levelspost, endpoint=True) - lumsPost = measure_luminances(levelsPost, **measure_kwargs) + lumsPost = measure_luminances(levelsPost, **measure_kwargs_realMeasurment) monitor.setLumsPost(lumsPost) monitor.setLevelsPost(levelsPost) @@ -283,7 +284,7 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i reslevels = np.linspace(0, 1, restests, endpoint=False) resoffset = np.r_[np.inf, np.arange(14, 6 - 1, -1).astype(float)] reslevels = reslevels.reshape(-1, 1) + 2**-resoffset.reshape(1, -1) - reslums = measure_luminances(reslevels.ravel(), **measure_kwargs) + reslums = measure_luminances(reslevels.ravel(), **measure_kwargs_realMeasurment) reslums = reslums.reshape(4, reslevels.shape[0], reslevels.shape[1]).transpose(1, 0, 2) monitor.currentCalib['lumsRes'] = reslums monitor.currentCalib['levelsRes'] = reslevels From 87516e630e613ed4140f63d4c616d14cb905f958 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:34:31 +0100 Subject: [PATCH 05/11] add 'run as script' option --- psychopy_pixx/calibration/calibration.py | 31 ++++++++++++++---------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index 6dacb3a..3d473a8 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -186,7 +186,10 @@ def measure_luminances( @click.option('--measures', help='Number of measurements to average per color level (only S470 photometer).', type=int, default=250) @click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') @click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) -def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False): +@click.option('--script', help='prevents calibration from opening plots and user prompts to be able to use the tool more automated (for pre calibration)', is_flag=True) +def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, +savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False): + from psychopy import monitors, visual # lazy import # check if paths exist @@ -215,13 +218,14 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i monitor=monitor, allowGUI=True, winType='pyglet', screen=screen) vpixx = ViewPixx(window) - monitor_state = { - 'Width': monitor.getWidth(), - 'Distance': monitor.getDistance(), - 'SizePix': monitor_size, - **vpixx.register} - register_str = "\n".join(f"\t{key}: {val}" for key, val in monitor_state.items()) - click.confirm(f'This is your monitor state. Ok?\n{register_str}\n' , abort=True) + if not script: + monitor_state = { + 'Width': monitor.getWidth(), + 'Distance': monitor.getDistance(), + 'SizePix': monitor_size, + **vpixx.register} + register_str = "\n".join(f"\t{key}: {val}" for key, val in monitor_state.items()) + click.confirm(f'This is your monitor state. Ok?\n{register_str}\n' , abort=True) measure_kwargs = dict(window=window, photometer=photometer, random=random, inverted=inverted, allGuns=False, n_measures=measures) @@ -237,7 +241,8 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i date_time = datetime.now().strftime("%Y-%m-%d_%H-%M") # save date and time for file distinction data_file = f"{savefiles}/blackwhiteLums_{date_time}.csv" np.savetxt(data_file, data, fmt="%.2f", delimiter=",", header='levels,luminance_gun1,luminance_gun2,luminance_gun3,luminance_gun4') # luminances in cd/m2" - click.confirm(f'Your monitor shows {minLum:.2f} cd/m^2 to {maxLum:.2f} cd/m^2. Ok?', abort=True) + if not script: + click.confirm(f'Your monitor shows {minLum:.2f} cd/m^2 to {maxLum:.2f} cd/m^2. Ok?', abort=True) # measurements print(f"Measure luminance series ...") @@ -311,8 +316,8 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i plot_file = f"{monitor.currentCalibName}_luminance.pdf" print(f"Save plot {plot_file}...") plt.savefig(plot_file) - if plot: - plt.show() + if not script: + plt.show() if restests > 0: for lums, levels in zip(monitor.currentCalib['lumsRes'], monitor.currentCalib['levelsRes']): @@ -327,8 +332,8 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i plot_file = f"{monitor.currentCalibName}_resolution.pdf" print(f"Save plot {plot_file}...") plt.savefig(plot_file) - if plot: - plt.show() + if not script: + plt.show() print("Done.") From f8266b39b01b8089baf2302d0758aa8c255a3e17 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:38:42 +0100 Subject: [PATCH 06/11] added parameter --lut and implemented the use of a pre-trained lut for calibration --- psychopy_pixx/calibration/calibration.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index 3d473a8..74c5c5d 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -6,6 +6,7 @@ import csv import os from datetime import datetime +import pandas as pd from psychopy_pixx.calibration.photometer import findPhotometer from psychopy_pixx.devices import ViewPixx @@ -187,8 +188,9 @@ def measure_luminances( @click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') @click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) @click.option('--script', help='prevents calibration from opening plots and user prompts to be able to use the tool more automated (for pre calibration)', is_flag=True) +@click.option('--lut', help='look up table (lut) the script should use for correction/calibration', is_flag=False, flag_value='.', default='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, -savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False): +savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): from psychopy import monitors, visual # lazy import @@ -255,6 +257,15 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i date_time = datetime.now().strftime("%Y-%m-%d_%H-%M") # save date and time for file distinction data_file = f"{savefiles}/luminancePre_{date_time}.csv" np.savetxt(data_file, data, fmt="%.2f", delimiter=",", header='levels,luminance_gun1,luminance_gun2,luminance_gun3,luminance_gun4') # luminances in cd/m2" + + #try to set pretrained lut + if lut != 'no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': + lut = pd.read_csv(lut) + lut = lut.sort_values(by='levels') + print(lut) + levelsPre = lut['levels'].values + print(levelsPre) + lumsPre = np.array([lut['prediciton'].values, lut['prediciton'].values, lut['prediciton'].values, lut['prediciton'].values]) print("Create new monitor calibration.") monitor.newCalib(width=monitor.getWidth(), distance=monitor.getDistance()) From 8c75a8243285fcb33df1969e6ea55dc6bdbeb350 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:42:08 +0100 Subject: [PATCH 07/11] fixed plot parameter --- psychopy_pixx/calibration/calibration.py | 52 ++++++++++++------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index 74c5c5d..d1f458e 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -182,7 +182,7 @@ def measure_luminances( @click.option('--inverted', help='Measure in inverted order.', is_flag=True) @click.option('--levelspost', help='Number of measurements after linearization', default=100) @click.option('--restests', help='Number of test points for luminance resolution', default=5) -@click.option('--plot', help='Show plots.', is_flag=True) +@click.option('--plot', is_flag=False, flag_value='.', help='Create, show and save plots.', default='no_plots_8e26a619-e688-4dcf-b010-7bd5fca459d8') @click.option('--gamma', help='Gamma with which the monitor is to be corrected. (default: 1.0 (linearization))', type=float, default=1.0) @click.option('--measures', help='Number of measurements to average per color level (only S470 photometer).', type=int, default=250) @click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') @@ -315,36 +315,38 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i data_file = f"{savefiles}/luminancePost_{date_time}.csv" np.savetxt(data_file, data, fmt="%.2f", delimiter=",", header='levels,luminance_gun1,luminance_gun2,luminance_gun3,luminance_gun4') # luminances in cd/m2" - print("Plot measurements ...") - plt.plot(levelsPre, lumsPre[0], label='pre') - if levelspost > 0: - plt.plot([0, 1], [lumsPre[0, 0], lumsPre[0, -1]], '--', linewidth=3, label='expected') - plt.plot(levelsPost, lumsPost[0], label='post') - plt.xlabel("Grey Level") - plt.ylabel("Luminance [cd/m^2]") - plt.legend(loc='best') - plt.title(f'Luminance before and after calibration ({photometer.type}, {monitor.currentCalibName})') - plot_file = f"{monitor.currentCalibName}_luminance.pdf" - print(f"Save plot {plot_file}...") - plt.savefig(plot_file) - if not script: - plt.show() + if plot != 'no_plots_8e26a619-e688-4dcf-b010-7bd5fca459d8': + print("Plot measurements ...") + plt.plot(levelsPre, lumsPre[0], label='pre') + if levelspost > 0: + plt.plot([0, 1], [lumsPre[0, 0], lumsPre[0, -1]], '--', linewidth=3, label='expected') + plt.plot(levelsPost, lumsPost[0], label='post') + plt.xlabel("Grey Level") + plt.ylabel("Luminance [cd/m^2]") + plt.legend(loc='best') + plt.title(f'Luminance before and after calibration ({photometer.type}, {monitor.currentCalibName})') + plot_file = f"{plot}/{monitor.currentCalibName}_luminance.pdf" + print(f"Save plot {plot_file}...") + plt.savefig(plot_file) + if not script: + plt.show() if restests > 0: for lums, levels in zip(monitor.currentCalib['lumsRes'], monitor.currentCalib['levelsRes']): lums = lums[0] plt.plot(-resoffset[1:], lums[1:] - lums[0], label=f'{levels[0]:.4f}') - plt.xlabel("Log2(grey level difference)") - plt.ylabel("Luminance difference") - plt.yscale('log', base=2) - plt.title(f'Luminance resolution ({photometer.type}, {monitor.currentCalibName})') - plt.legend(loc='best', title='Grey level') - plot_file = f"{monitor.currentCalibName}_resolution.pdf" - print(f"Save plot {plot_file}...") - plt.savefig(plot_file) - if not script: - plt.show() + if plot != 'no_plots_8e26a619-e688-4dcf-b010-7bd5fca459d8': + plt.xlabel("Log2(grey level difference)") + plt.ylabel("Luminance difference") + plt.yscale('log', base=2) + plt.title(f'Luminance resolution ({photometer.type}, {monitor.currentCalibName})') + plt.legend(loc='best', title='Grey level') + plot_file = f"{plot}/{monitor.currentCalibName}_resolution.pdf" + print(f"Save plot {plot_file}...") + plt.savefig(plot_file) + if not script: + plt.show() print("Done.") From a31762930f44468ad1b440f384261e6b998121d8 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:45:38 +0100 Subject: [PATCH 08/11] added --no_scanning parameter to choose the operating mode of the monitor --- psychopy_pixx/calibration/calibration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index d1f458e..a7177f3 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -188,9 +188,10 @@ def measure_luminances( @click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') @click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) @click.option('--script', help='prevents calibration from opening plots and user prompts to be able to use the tool more automated (for pre calibration)', is_flag=True) +@click.option('--no_scanning', help='with this option you swith from "scanning backlight" to "normal backlight"', is_flag=True) @click.option('--lut', help='look up table (lut) the script should use for correction/calibration', is_flag=False, flag_value='.', default='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, -savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): +savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, no_scanning=False, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): from psychopy import monitors, visual # lazy import @@ -219,6 +220,7 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i fullscr=0, size=monitor_size, gamma=1, units='norm', useFBO=True, monitor=monitor, allowGUI=True, winType='pyglet', screen=screen) vpixx = ViewPixx(window) + vpixx.scanning_backlight = not no_scanning if not script: monitor_state = { From edf0921f4347fcf088850fff24958af36eb2337a Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:47:32 +0100 Subject: [PATCH 09/11] added --bg_intensity parameter to choose the backlight intensity of the monitor --- psychopy_pixx/calibration/calibration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index a7177f3..ffe4dd6 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -189,9 +189,10 @@ def measure_luminances( @click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) @click.option('--script', help='prevents calibration from opening plots and user prompts to be able to use the tool more automated (for pre calibration)', is_flag=True) @click.option('--no_scanning', help='with this option you swith from "scanning backlight" to "normal backlight"', is_flag=True) +@click.option('--bg_intensity', help='intensity of the backlight', default=255) @click.option('--lut', help='look up table (lut) the script should use for correction/calibration', is_flag=False, flag_value='.', default='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, -savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, no_scanning=False, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): +savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, no_scanning=False, bg_intensity=255, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): from psychopy import monitors, visual # lazy import @@ -221,6 +222,8 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i monitor=monitor, allowGUI=True, winType='pyglet', screen=screen) vpixx = ViewPixx(window) vpixx.scanning_backlight = not no_scanning + vpixx.backlight = bg_intensity + print(f'backlight intensity is: {vpixx.backlight}') if not script: monitor_state = { From d71e16306b5310cb446f419795b78fe1eeaf8ab9 Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 10:55:37 +0100 Subject: [PATCH 10/11] added time estimation print --- psychopy_pixx/calibration/calibration.py | 30 ++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index ffe4dd6..eaa03d8 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -22,6 +22,7 @@ def measure_luminances( inverted=False, stimSize=4, n_measures=50, + timeestimation_output=False, all_measurements=False, savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): """Automatically measures a series of gun values and measures @@ -99,6 +100,10 @@ def measure_luminances( # this will hold the measured luminance values lumsList = np.zeros((4, len(toTest))) + # for (approx) ending time calculation timestamps and counter + counter = 0 + start = time.time() + # create list for logging all measurments if all_measurements: allLums_data = [] @@ -117,6 +122,9 @@ def measure_luminances( # for each gun, for each value run test for gun in guns: for valN, DACval in enumerate(toTest): + # counter for timeestimation + counter = valN + 1 + lum = (DACval * 2) - 1 # from range 0:1 into -1:1 # only do luminanc=-1 once if lum == -1 and gun > 0: @@ -144,7 +152,20 @@ def measure_luminances( allLums_data.append([DACval] + lums) else: actualLum = photometer.getLum() - print(f"\t{valN + 1}/{len(toTest)} At DAC value {DACval:.2f}\t: {actualLum:.2f}cd/m^2") + print(f"\t{valN+1:4d}/{len(toTest)} At DAC value {DACval:5.3f}\t: {actualLum:6.2f}cd/m^2") + if timeestimation_output and counter%10 == 0: + current = time.time()-start + duration = current/counter + estimated_end = time.time() + (duration * (len(toTest)-counter)) + time_format_current = time.strftime('%H:%M:%S', time.gmtime(current)) + time_format_duration = time.strftime('%H:%M:%S', time.gmtime(duration)) + time_format_end = time.strftime('%d.%m.%y %H:%M', time.localtime(estimated_end)) + print('') + print(f' Time-Estimation:') + print(f' We needed {time_format_current} unitl now ({counter} levels).') + print(f' This results into {time_format_duration} per level.') + print(f' Estimated ending time: {time_format_end}') + print('') lumsList[gun, valN] = actualLum # check for quit request for thisKey in event.getKeys(): @@ -153,7 +174,7 @@ def measure_luminances( return np.array([]) elif autoMode == 'semi': - print(f"\t{valN + 1}/{len(toTest)} At DAC value {DACval:.2f}") + print(f"\t{valN+1:4d}/{len(toTest)} At DAC value {DACval:5.3f}") done = False while not done: @@ -188,11 +209,12 @@ def measure_luminances( @click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') @click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) @click.option('--script', help='prevents calibration from opening plots and user prompts to be able to use the tool more automated (for pre calibration)', is_flag=True) +@click.option('--timeestimation_output', help='prints a time estimation of how much longer the calibration will take', is_flag=True) @click.option('--no_scanning', help='with this option you swith from "scanning backlight" to "normal backlight"', is_flag=True) @click.option('--bg_intensity', help='intensity of the backlight', default=255) @click.option('--lut', help='look up table (lut) the script should use for correction/calibration', is_flag=False, flag_value='.', default='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') def calibration_routine_cli(levels, monitor, screen, photometer, port, random, inverted, levelspost, restests, plot, measures, gamma=1.0, -savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, no_scanning=False, bg_intensity=255, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): +savefiles='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5', all_measurements=False, script=False, timeestimation_output=False, no_scanning=False, bg_intensity=255, lut='no_lut_f99fc889-c6e3-4588-ad44-4f8a9554f7b5'): from psychopy import monitors, visual # lazy import @@ -255,7 +277,7 @@ def calibration_routine_cli(levels, monitor, screen, photometer, port, random, i print(f"Measure luminance series ...") levelsPre = np.linspace(0, 1, levels, endpoint=True) measure_kwargs_realMeasurment = dict(window=window, photometer=photometer, random=random, inverted=inverted, - allGuns=False, n_measures=measures, all_measurements=all_measurements, savefiles=savefiles) + allGuns=False, n_measures=measures, timeestimation_output=timeestimation_output, all_measurements=all_measurements, savefiles=savefiles) lumsPre = measure_luminances(levelsPre, **measure_kwargs_realMeasurment) if savefiles!='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5': data = np.vstack((100*levelsPre, lumsPre)).T # percent for better accuracy, all 4 post guns From b0208c40862207eae33e867644fd6874fdfc54ba Mon Sep 17 00:00:00 2001 From: JSchmiegel Date: Tue, 9 Jan 2024 11:08:35 +0100 Subject: [PATCH 11/11] minor help text changes --- psychopy_pixx/calibration/calibration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psychopy_pixx/calibration/calibration.py b/psychopy_pixx/calibration/calibration.py index eaa03d8..eb1b9ac 100755 --- a/psychopy_pixx/calibration/calibration.py +++ b/psychopy_pixx/calibration/calibration.py @@ -206,9 +206,9 @@ def measure_luminances( @click.option('--plot', is_flag=False, flag_value='.', help='Create, show and save plots.', default='no_plots_8e26a619-e688-4dcf-b010-7bd5fca459d8') @click.option('--gamma', help='Gamma with which the monitor is to be corrected. (default: 1.0 (linearization))', type=float, default=1.0) @click.option('--measures', help='Number of measurements to average per color level (only S470 photometer).', type=int, default=250) -@click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files, not only in psychopys monitor', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') +@click.option('--savefiles', is_flag=False, flag_value='.', help='save measurements as files into the given directory', default='no_savefile_f99fc889-c6e3-4588-ad44-4f8a9554f7b5') @click.option('--all_measurements', help='with this option, all measurments from the photometer are saved', is_flag=True) -@click.option('--script', help='prevents calibration from opening plots and user prompts to be able to use the tool more automated (for pre calibration)', is_flag=True) +@click.option('--script', help='prevents routine from opening plots and user prompts to be able to use the tool more automated', is_flag=True) @click.option('--timeestimation_output', help='prints a time estimation of how much longer the calibration will take', is_flag=True) @click.option('--no_scanning', help='with this option you swith from "scanning backlight" to "normal backlight"', is_flag=True) @click.option('--bg_intensity', help='intensity of the backlight', default=255)