This file is indexed.

/usr/share/psychtoolbox-3/PsychGLImageProcessing/PsychVideoSwitcher.m is in psychtoolbox-3-common 3.0.9+svn2579.dfsg1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
function varargout = PsychVideoSwitcher(cmd, varargin)
% PsychVideoSwitcher(command [,arg1, arg2, .....]);
%
% Psychtoolbox support for the Xiangrui Li et al. "VideoSwitcher" video
% attenuator device for high precision luminance output with up to 16 bits
% luminance resolution.
%
% This routine incorporates code contributed by Xiangru Li for switching
% between monochrome and color display mode and for performing the
% reference Matlab routines for image formatting.
%
% Options: 'command' is a command string, specifying a subcommand. The
% following subcommands are supported with the following options:
%
% Public functions:
%
%
% PsychVideoSwitcher('SwitchMode', screenIdx, enableLuminanceMode [, VideoSwitcherIsABox])
% - Switch programmatically between high precision luminance mode and
% standard RGB true color display mode. 'screenIdx' is the screen index of the
% for the display screen to switch.
%
% 'enableLuminanceMode' must be set to 0 to switch to RGB mode, and to 1 to
% switch to high precision luminance mode.
%
% 'VideoSwitcherIsABox' is an optional argument: If set to 1, then perform
% switching procedure for an external (box) device. If set to zero, then
% perform procedure for an internal (PCI card) device. If argument is
% omitted, the proper default type is read from a configuration file stored
% in the path mypath = PsychtoolboxConfigDir('VideoSwitcher');
%
% If you create a file named 'VideoSwitcherIsABox' in that directory
% mypath, the switcher is assumed to be an external box. If you create a
% file named 'VideoSwitcherIsACard', the code assumes a PCI card based
% device. If no such files exist, the switcher is assumed to be a box.
%
% It is important for the code to know if the switcher is a box or a card,
% as the switching strategy is different.
%
%
% PsychVideoSwitcher('SetTrigger', win, triggerLine [, count=infinite]);
% - Set trigger line and options for VideoSwitcher connected to the display
% of onscreen window 'win'.
%
% 'triggerLine' defines the vertical (y) position of a trigger line to be
% drawn to the green channel of the final image, in order to trigger the
% trigger-circuit of the VideoSwitcher. If you set it to a negative value
% or to empty [], triggering will be disabled (This is the default).
%
% 'count' Optional: Number of redraw cycles (invocations of
% Screen('Flip')), that the triggerline should be drawn. If left out, the
% line will be drawn on each redraw until disabled. If set to some value,
% it will be drawn for 'count' number of redraws, then disabled.
%
% The trigger mechanism only works if the VideoSwitcher is set to high
% precision luminance display. It will emit a TTL pulse of 100 microseconds
% duration when the triggerline is drawn by your display. Only one TTL
% trigger pulse per video refresh is possible.
%
%
% PsychVideoSwitcher('SetBackgroundLuminanceHint', win, luminance);
% - Tell the driver the 'luminance' value of the background pixels of
% onscreen windows 'win'. The driver will use this hint to optimize
% conversion of background pixels. This allows for a quite significant
% speedup if your stimulus only covers a fraction of the display area and
% the remaining area is just a uniform luminance background. This is only
% functional if you use the driver that uses the calibration LUT, not the
% simple driver.
%
%
% RGBImage = PsychVideoSwitcher('MapLuminanceToRGB', lum, ratio [, trigger]);
% - Perform conversion of a luminance image into a RGBImage. This is a pure
% Matlab based implementation for graphics hardware that is not capable of
% supporting the imaging pipeline.
%
%  Inputs:
%     lum: luminance (MxN matrix with values from 0 to 1)
%     ratio: blue to red ratio of the video switcher
%     trigger: when non-zero, a trigger will be sent in current frame
%         trigger=1 or 'top', the first line of image
%         trigger=2 or 'auto',  the first line with non-zero image
%         trigger=3 or 'middle', the middle line of image
%     If you omit ratio, you should give it in this code
%  Output: RGB image (MxNx3 matrix with values from 0 to 255)
%
%
% RGBImage = PsychVideoSwitcher('MapLuminanceToRGBCalibrated', lum, ratio, lut [, trigger])
% - Perform conversion of a luminance image into a RGBImage. This is a pure
% Matlab based implementation for graphics hardware that is not capable of
% supporting the imaging pipeline.
%
%  Inputs:
%     lum: luminance (MxN matrix with values from 0 to 1)
%     ratio: blue to red ratio of the video switcher
%     lut: The 257 slots calibrated luminance table as described below.
%     trigger: when non-zero, a trigger will be sent in current frame
%         trigger=1 or 'top', the first line of image
%         trigger=2 or 'auto',  the first line with non-zero image
%         trigger=3 or 'middle', the middle line of image
%     If you omit ratio, you should give it in this code
%  Output: RGB image (MxNx3 matrix with values from 0 to 255)
% 
% Note: this runs slow when lum matrix is large. 200x200 can take 1 second.
%
% This uses the calibrated luminance table saved in calibratedlum.mat.
% For detail, check the paper http://lobes.usc.edu/Journals/JNM03.pdf
% 
% The steps to get the table:
%
%  1. Switch the video switcher to grayscale mode;
%
%  2. Set up the equipement to accurately measure screen luminance;
%     you can use a photometer or data acquisition system;
%
%  3. Measure 257 luminance levels at RGB of [0 0 b] and [btrr 0 255],
%     where b is 0:255, and btrr the blue to red ratio of switcher; 
%
%  4. Store 257 luminance in a variable callum and normalize them:
%     callum=callum/callum(257);
%
%  5. Save it to a configuration file. See the help for subfunction
%     'GetDefaultConfig' below for how and where to store the calibration
%     table.
%
%
% [btrr, lut] = PsychVideoSwitcher('GetDefaultConfig', win);
% - Get default 'btrr' parameter and 'lut' lookup table from configuration
% files and return them. This function can be used by you to get switcher
% parameters for use with the Matlab conversion functions above. It will be
% automatically used by the imaging pipeline (by PsychImaging() command) if
% you select VideoSwitcher as an output device but don't provide explicit
% settings for 'btrr' and/or 'lut'.
%
% Configuration files for the VideoSwitcher should be stored in the folder
% whose path you get if you type mypath = PsychtoolboxConfigDir('VideoSwitcher');
%
% You can store a configuration file specific to a display screen
% 'screenid' (with the numbering as in Screen('Screens')). The file should
% have the name SettingsforScreen_X.mat with X being the screen number,
% e.g., SettingsforScreen_0.mat . This allows you to store per-display
% device settings. Alternatively you can store settings in a "global"
% config file named GlobalSettings.mat if they are not specific to the
% display. The stored mat file should contain up to two variables:
%
% The variable 'btrr' should store the measured/calibrated BTRR
% Blue-To-Red-Ratio for yor switcher and display setup. This variable is
% mandatory.
%
% The optional variable 'lut' would be a 257 elements double vector of
% luminance calibration data, as described in the help text immediately
% above the help for this subfunction (== help for
% 'MapLuminanceToRGBCalibrated').
%
% You would create such a file by typing, e.g.:
% btrr = 128;
% lut = the lookup table vector created by calibration...
% save 'GlobalSettings.mat' btrr lut
%
%
%
%
% Internal helper functions for Psychtoolbox - Must not be called from
% normal user code!!
%
% luttexid = PsychVideoSwitcher('GetLUTTexture', win, lut, btrr, shader);
% - Convert blue-to-luminance calibration lookup table 'lut' into a lookup
% table texture for the imaging pipeline, set it up and return a texture
% handle 'luttexid' to it. 'btrr' is the required BTRR value. 'shader' is
% the GLSL shader handle of the output formatting shader used.
%
%
% PsychVideoSwitcher(win);
% - If 'win' is a numeric onscreen window handle, perform all operations to
% implement the green channel trigger functionality for onscreen window
% 'win'. This routine uses MOGL glXXX() functions to implement drawing of
% proper trigger pixel values to the green channel for trigger creation.
%

% History:
% 05/25/08 mk  Initial incomplete implementation. Incorporates code from
%              Xiangrui Li in helper subroutines.
% 05/24/11 xl  3rd input for SwitchMode is not manditory since it is not
%              used by box version.

% GL access is needed for setup of green trigger channel in callback:
global GL;

% Cell array of trigger settings per window:
persistent triggerForWindow;

% Cell array with cached btrr and lut for window handle:
persistent cachedStuffForWindow;

% Cache info if videoswitcher is of external box type or PCI card type:
persistent VideoSwitcherIsABox;

% Subfunction dispatch:
if nargin < 1
    error('You must provide a command string!');
end

if isscalar(cmd) && isnumeric(cmd)
    % Special callback from within PTB imaging pipeline:
    if isempty(triggerForWindow) || isempty(triggerForWindow{cmd})
        % Invalid window handle or trigger disabled: Just skip.
        return;
    end

    % Extract triggerLine for this window:
    triggerSpec = triggerForWindow{cmd};
    triggerLine = triggerSpec.triggerline;

    % Trigger disabled or trigger drawn for wanted 'count' number of
    % redraws?
    if triggerLine < 0 || triggerSpec.count == 0
        % Yes. Just return.
        return;
    end
    
    % Decrement count:
    triggerSpec.count = triggerSpec.count - 1;
    triggerForWindow{cmd} = triggerSpec;
    
    % Due to a bug (or misfeature?) in imaging pipeline, we have unit 1
    % active here. Need to disable it:
    % FIXME: Fix the pipeline in next beta release cycle!
    % MK: Fixed in source code, but all Screen mex files need recompile!
    glPushAttrib(GL.ENABLE_BIT);

    glActiveTexture(GL.TEXTURE1);
    glDisable(GL.TEXTURE_RECTANGLE_ARB);
    
    % This one for unit 0 is always needed, because unit 0 is enabled by
    % design of the pipeline, not due to a bug:
    glActiveTexture(GL.TEXTURE0);
    glDisable(GL.TEXTURE_RECTANGLE_ARB);
    
    % Disable all channels for writing except green channel:
    glColorMask(GL.FALSE, GL.TRUE, GL.FALSE, GL.FALSE);
    glColor4f(0, 1, 0, 0);

    % Draw horizontal line 'triggerLine' into green channel of bound
    % framebuffer: We actually draw a stripe of 10 lines width, just to make
    % sure the VideoSwitcher picks it up, regardless of resolution.
    glBegin(GL.QUADS);
    glVertex2i(0, triggerLine);
    glVertex2i(10000, triggerLine);
    glVertex2i(10000, triggerLine+10);
    glVertex2i(0, triggerLine+10);    
    glEnd();
    
    % Reenable all channels for writing:
    glColorMask(GL.TRUE, GL.TRUE, GL.TRUE, GL.TRUE);
    glPopAttrib;
    
    % Done. Return to calling routine:
    return;
end

if ~ischar(cmd)
    error('Subcommand must be a string!');
end

if strcmpi(cmd, 'MapLuminanceToRGB')
    % Call lum2rgb() function to use simple conversion of luminance image
    % to RGB image:
    varargout{1} = lum2rgb(varargin{:});
    
    return;
end

if strcmpi(cmd, 'MapLuminanceToRGBCalibrated')
    % Call lum2rgb() function to use lut based conversion of luminance image
    % to RGB image:
    varargout{1} = lum2calrgb(varargin{:});
    
    return;
end

if strcmpi(cmd, 'SwitchMode')
    % Switch VideoSwitcher between monochrome and color mode by sending a
    % sequence of frames with special green-channel control codes:
    if nargin < 2
        error('screenId is missing!');
    end
    
    screenid = varargin{1};
    if ~ismember(screenid, Screen('Screens'))
        error('Invalid screenId provided - No such display screen!');
    end

    if nargin < 3 || isempty(varargin{2})
        enableLuminanceMode = 0; % XL: for box, this is not used
    else
        enableLuminanceMode = varargin{2};
    end
    
    if isempty(enableLuminanceMode) || ~isscalar(enableLuminanceMode) || ~isnumeric(enableLuminanceMode) || ~ismember(enableLuminanceMode, [0,1])
        error('Invalid enableLuminanceMode flag provided. Must be 0 or 1!');
    end

    if nargin < 4
        % No type parameter provided:
        if isempty(VideoSwitcherIsABox)
            % Check if type is stored in config file:
            lpath = sprintf('%sVideoSwitcherIsABox', PsychtoolboxConfigDir('VideoSwitcher'));
            if exist(lpath, 'file')
                VideoSwitcherIsABox = 1;
            else
                lpath = sprintf('%sVideoSwitcherIsACard', PsychtoolboxConfigDir('VideoSwitcher'));
                if exist(lpath, 'file')
                    VideoSwitcherIsABox = 0;
                else
                    fprintf('PsychVideoSwitcher: Could not find out if VideoSwitcher is a box or a PCI card. Just assuming it is a box...\n');
                    VideoSwitcherIsABox = 1;
                end
            end
        end
    else
        VideoSwitcherIsABox = varargin{3};
        if isempty(VideoSwitcherIsABox) || ~isscalar(VideoSwitcherIsABox) || ~isnumeric(VideoSwitcherIsABox) || ~ismember(VideoSwitcherIsABox, [0,1])
            error('Invalid VideoSwitcherIsABox flag provided. Must be 0 or 1!');
        end
    end
    
    % Enumerate all onscreen windows for screen 'screenid':
    allWindows = Screen('Windows');
    allWindowKinds = Screen('WindowKind', allWindows);
    onscreenWins = allWindows(allWindowKinds == 1);
    for win = onscreenWins
        % Is 'win' an onscreen window on screen screenid?
        if Screen('WindowScreenNumber', win) == screenid
            % This 'win'dow is an onscreen window on our target screenid.
            % Close it -- We don't want any onscreen windows on the screen
            % to switch, as they could clash/interfere with the switching
            % operation. Recycling them is impossible as we don't know
            % their configuration wrt. imaging mode, size etc.:
            Screen('Close', win);
        end
    end
    
    % Ok, our target screen 'screenid' is clear of any onscreen windows.
    % Create our own one: Fullscreen, black background clear color, no
    % imaging pipe or anything else, double-buffered. Disable all
    % sync-tests, warnings etc. for this switch action:
    oldverbosity = Screen('Preference', 'Verbosity', 1);
    oldsynclevel = Screen('Preference', 'SkipSyncTests', 2);
    oldvisuallevel = Screen('Preference', 'VisualDebugLevel', 0);
    win = Screen('OpenWindow', screenid, 0, []);
    
    % Hide mouse cursor:
    HideCursor;
    
    % Get current display size:
    [width, height]=Screen('WindowSize', win);

    % Get video refresh duration: We query nominal framerate, as this is faster
    % and sufficient for our purpose:
    ifi=Screen('NominalFramerate', win);
    if ifi == 0
        % Special case: Invalid framerate -- This usually means 60 Hz:
        ifi = 1/60;
    else
        % ifi is 1/Hz:
        ifi = 1/ifi;
    end

    if VideoSwitcherIsABox
        % Perform switching procedure for external box based VideoSwitcher:
        switchColorBox(win, width, height, ifi, enableLuminanceMode);
    else
        % Perform switching procedure for internal PCI based VideoSwitcher:
        switchColorCard(win, width, height, ifi, enableLuminanceMode);
    end
    
    % Show mouse cursor again:
    ShowCursor;

    % Close our window:
    Screen('Close', win);
    
    % Restore old settings:
    Screen('Preference', 'Verbosity', oldverbosity);
    Screen('Preference', 'SkipSyncTests', oldsynclevel);
    Screen('Preference', 'VisualDebugLevel', oldvisuallevel);
    
    return;
end

if strcmpi(cmd, 'SetTrigger')
    % Define the trigger line for a specific onscreen window:
    
    if nargin < 3
        error('You must provide the windowhandle and triggerline to the "SetTrigger" subfunction.');
    end
    
    win = varargin{1};
    if ~isscalar(win) || ~isnumeric(win) || Screen('WindowKind', win)~=1
        error('The "window" argument must be a scalar windowhandle of an onscreen window for the "SetTrigger" subfunction.');
    end

    triggerline = varargin{2};
    
    % Empty triggerline == Disable trigger:
    if isempty(triggerline)
        triggerForWindow{win} = [];
    else
        if ~isscalar(triggerline) || ~isnumeric(triggerline)
            error('The "triggerLine" argument must be a scalar numeric value for the "SetTrigger" subfunction.');
        end

        % Store this setting in our cell array:
        triggerSpec.triggerline = triggerline;
        
        % Optional count provided?
        if nargin < 4 || isempty(varargin{3})
            % Nope: Set to infinite count:
            triggerSpec.count = -1;
        else
            % Yes: Set it.
            triggerSpec.count = varargin{3};
        end
                
        triggerForWindow{win} = triggerSpec;
    end
    
    return;
end

if strcmpi(cmd, 'GetDefaultConfig')
    % Retrieve default configuration for a specific onscreen window:
    
    if nargin < 2
        screenid = 0;
    else
        % Retrieve window handle for the already opened onscreen window on
        % which VideoSwitcher is supposed to be operating:
        win = varargin{1};
        if ~isscalar(win) || ~isnumeric(win)
            error('The "window" argument must be a scalar windowhandle of an onscreen window or a scalar screenid for the "GetDefaultConfig" subfunction.');
        end

        % Screenid or windowhandle?
        if ismember(win, Screen('Screens'))
            % Screenid - Assign:
            screenid = win;
        else
            % Either windowhandle or error:
            if Screen('WindowKind', win)~=1
                error('The "window" argument must be a scalar windowhandle of an onscreen window or a scalar screenid for the "GetDefaultConfig" subfunction.');
            end

            % The 'win'dowhandle by itself is meaningless. We map the windowhandle
            % to its associated screenid and then lookup proper configuration files
            % for that screen:
            screenid = Screen('WindowScreenNumber', win);
        end
    end
    
    % First check for a per-screen file:
    lpath = sprintf('%sSettingsforScreen_%i.mat', PsychtoolboxConfigDir('VideoSwitcher'), screenid);
    if exist(lpath, 'file')
        % Per screen file: Load it.
        switcherconfig = load(lpath);
    else
        % No such per screen file. Try global default file:
        lpath = sprintf('%sGlobalSettings.mat', PsychtoolboxConfigDir('VideoSwitcher'));
        if exist(lpath, 'file')
            % Load global file:
            switcherconfig = load(lpath);
        else
            % No files at all. Setup a default config:
            switcherconfig.btrr = 128;
            switcherconfig.lut = linspace(0, 1, 257);
            fprintf('\nPsychVideoSwitcher: Warning: No meaningful configuration for VideoSwitcher available in configuration directory:\n%s\nUsing fake default settings: BTRR=128 and a linear 257 slot lut...\n\n', PsychtoolboxConfigDir('VideoSwitcher'));
            lpath = [];
        end
    end
    
    if ~isempty(lpath)
        fprintf('PsychVideoSwitcher: Info: Configuration for VideoSwitcher loaded from default config file:\n%s\n', lpath);
    end
    
    % Return BTRR setting:
    varargout{1} = switcherconfig.btrr;
    
    % Return Blue channel to luminance calibration lut, if any:
    if isfield(switcherconfig, 'lut')
        varargout{2} = switcherconfig.lut;
    else
        varargout{2} = [];
    end
    
    return;
end

if strcmpi(cmd, 'GetLUTTexture')
    if nargin < 4
        error('You must provide the window handle, lut lookup table and btrr to the "GetLUTTexture" subfunction.');
    end
    
    win = varargin{1};
    if ~isscalar(win) || ~isnumeric(win) || Screen('WindowKind', win)~=1
        error('The "window" argument must be a scalar windowhandle of an onscreen window or a scalar screenid for the "GetLUTTexture" subfunction.');
    end
    
    lut = varargin{2};
    
    if isempty(lut)
        error('You must provide the lut lookup table as 2nd argument to the "GetLUTTexture" subfunction.');
    end
    
    if ~isvector(lut)
        error('lut must be a vector!');
    end

    if size(lut, 1) ~=1
        lut = transpose(lut);
    end
    
    [channels, nslots] = size(lut);

    if channels~=1
        error('lut must be a vector!');
    end
    
    if nslots~=257
        error('lut must have 257 slots, aka columns!');
    end

    if ~isa(lut,'double')
        error('lut must by of double precision type!');
    end

    btrr = varargin{3};
    if isempty(btrr) || ~isscalar(btrr) || ~isa(btrr,'double')
        error('You must provide a valid scalar double btrr value as 3rd argument!');
    end
    
    if nargin < 5
        shader = 0;
    else
        shader = varargin{4};
        if isempty(shader)
            shader = 0;
        end
    end
    
    
    % mylut is the input lut: Row 1=Red, 2=Green, 3=Blue, 4=Alpha
    % component of a texel, 257 texels for the 257 texel wide, 1 row high
    % RGBA float texture:
    mylut = zeros(4, 257);
    
    % Fill with normalized lut:
    lut = lut / lut(257);
    
    % Hmm, a dirty trick, indeed: We shift the maximum value by a very
    % small epsilon to avoid a boundary artifact - a wrong conversion for a
    % input luminance value of exactly 1.0:
    lut(257) = lut(257) + 1e-7;
    
    % Red channel (channel 1) contains lut values, aka minimum luminance
    % values for each corresponding blue channel driver value:
    mylut(1,:) = lut;
    
    % Green channel (channel 2) contains the upper bound, ie. the channel 1
    % value of the preceding slot:
    mylut(2,1:256) = lut(2:257);

    % The upper limit of slot 257 is not defined. Set it to a value higher
    % than the minimum of slot 257:
    mylut(2,257) = lut(257);
    
    mylut(3,1:256) = repmat(btrr, 1, 256) ./ ( mylut(2,1:256) - mylut(1,1:256) );
    % This is not well-defined at all: Just set it to a high value and hope
    % for the best... As luminance values are clamped to a maximum of 1.0,
    % thi...
    mylut(3,257) = 1e10;
    
    % Build texture:
    luttex = glGenTextures(1);
    glBindTexture(GL.TEXTURE_RECTANGLE_EXT, luttex);
    glTexImage2D(GL.TEXTURE_RECTANGLE_EXT, 0, GL.RGBA_FLOAT32_APPLE, 257, 1, 0, GL.RGBA, GL.FLOAT, moglsingle(mylut));

    % Make sure we use nearest neighbour sampling:
    glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
    glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_MAG_FILTER, GL.NEAREST);

    % And that we clamp to edge:
    glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_WRAP_S, GL.CLAMP);
    glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_WRAP_T, GL.CLAMP);

    glBindTexture(GL.TEXTURE_RECTANGLE_EXT, 0);

    % Return texture handle:
    varargout{1} = luttex;

    % We store an internal backup copy of used input lut and btrr for this
    % window handle:
    wincache.btrr = btrr;
    wincache.lut = lut;
    wincache.shader = shader;
    cachedStuffForWindow{win} = wincache;
    
    return;
end

if strcmpi(cmd, 'SetBackgroundLuminanceHint')
    if nargin < 3
        error('You must provide the window handle and luminance hint value to the "SetBackgroundLuminanceHint" subfunction.');
    end
    
    win = varargin{1};
    if ~isscalar(win) || ~isnumeric(win) || Screen('WindowKind', win)~=1
        error('The "window" argument must be a scalar windowhandle of an onscreen window or a scalar screenid for the "SetBackgroundLuminanceHint" subfunction.');
    end
    
    lum = varargin{2};
    if ~isscalar(lum) || ~isnumeric(lum)
        error('The "backgroundLuminance" argument must be a scalar luminance value in range [0 ; 1] for the "SetBackgroundLuminanceHint" subfunction.');
    end
    
    % Ok, we have luminance value and window handle. Do we have relevant
    % cached info for this windowhandle?
    if isempty(cachedStuffForWindow) || isempty(cachedStuffForWindow{win})
        fprintf('PsychVideoSwitcher(''SetBackgroundLuminanceHint''): I do not have neccessary internal information for this onscreen window!\n');
        fprintf('Call PsychImaging(''OpenWindow'',...) first! This also has no function if you don''t use the calibrated VideoSwitcher driver.\n');
    end
    
    % Extract info:
    wincache = cachedStuffForWindow{win};
    
    % Only do this if the LUT based converter is used:
    if wincache.shader ~= 0
        % Map luminance value to color values via Matlab based conversion
        % routines:
        RGBPixel = lum2calrgb(lum, wincache.btrr, wincache.lut, 0);

        % Store (R,B) values in 0-1 range in red and blue channels, the
        % luminance "key" value in the green channel:
        redv = double(RGBPixel(1,1,1))/255;
        greenv = lum;
        bluev = double(RGBPixel(1,1,3))/255;

        % Find GLSL shader handle for VideoSwitcher shader:

        % Set values in shader:
        glUseProgram(wincache.shader);
        glUniform3f(glGetUniformLocation(wincache.shader, 'BackgroundPixel'), redv, greenv, bluev);
        glUseProgram(0);
    end
    
    % Done.
    return;
end

% Unknown command string - No match to any of the routines above:
error('Unknown subcommand provided!');

return; %#ok<UNRCH>

% Helper functions:
%
% Convert luminance matrix into RGB image to drive a video switcher.
%
% Usage: RGBimage = lum2rgb (lum, ratio [, trigger])
%  Inputs:
%     lum: luminance (MxN matrix with values from 0 to 1)
%     ratio: blue to red ratio of the video switcher
%     trigger: when non-zero, a trigger will be sent in current frame
%         trigger=1 or 'auto', the first line of image
%         trigger=2 or 'top',  the first line with non-zero image
%         trigger=3 or 'middle', the middle line of image
%     If you omit ratio, you should give it in this code
%  Output: RGB image (MxNx3 matrix with values from 0 to 255)
% XL, 01/03/2008
function img = lum2rgb (lum, ratio, trigger)
if nargin<3 || isempty(trigger), trigger=0; end
if nargin<2 || isempty(ratio), ratio=128; end
if nargin<1, disp('Usage: RGBimage = lum2rgb (lum, ratio [, trigger])'); return; end

sz=size(lum);
img = uint8(zeros(sz(1),sz(2),3));   % preallocate memory
lum=lum*255;	% now 0 to 255
img(:,:,3) = uint8(min(255,floor((ratio+1)/ratio*lum)));       % first calculates BLUE
img(:,:,1) = uint8((ratio+1)*lum-double(img(:,:,3))*ratio);	% then remainder is achieved by RED

if trigger==0, return; end

switch trigger
    case {2, 'top'}
        line = 1;    % first line of the image
    case {3, 'middle'}
        line = round(sz(1)/2);    % middle of image
    otherwise
        line = ceil(find(lum',1,'first')/sz(2));    % the first line where lum is non-zero
end
img(line,:,2) = uint8(255);         % set green to 255. Will send trigger when displayed
return;

% Convert luminance matrix into RGB image to drive a video switcher.
% This uses the calibrated luminance table saved in calibratedlum.mat.
% For detail, check the paper http://lobes.usc.edu/Journals/JNM03.pdf
% 
% The steps to get the table:
%  1. Switch the video switcher to grayscale mode;
%  2. Set up the equipement to accurately measure screen luminance;
%     you can use a photometer or data acquisition system;
%  3. Measure 257 luminance levels at RGB of [0 0 b] and [btrr 0 255],
%     where b is 0:255, and btrr the blue to red ratio of switcher; 
%  4. Store 257 luminance in variable callum and normalize them:
%     callum=callum/callum(257);
%  5. Save it under a proper folder with Matlab path: 
%     save calibratedlum callum;
% 
% Usage: RGBimage = lum2calrgb (lum, ratio [, trigger])
%  Inputs:
%     lum: luminance (MxN matrix with values from 0 to 1)
%     ratio: blue to red ratio of the video switcher
%     trigger: when non-zero, a trigger will be sent in current frame
%         trigger=1 or 'auto', the first line of image
%         trigger=2 or 'top',  the first line with non-zero image
%         trigger=3 or 'middle', the middle line of image
%     If you omit ratio, you should give it in this code
%  Output: RGB image (MxNx3 matrix with values from 0 to 255)
% 
% Note: this runs slow when lum matrix is large. 200x200 can take 1 second.
% XL, 01/08/2008
function img = lum2calrgb (lum, ratio, callum, trigger)
if nargin<4 || isempty(trigger), trigger=0; end
if nargin<3, disp('Usage: RGBimage = lum2calrgb (lum, ratio, lut [, trigger])'); return; end

if length(callum)~=257
    error('Calibrated luminance table must have 257 entries.');
end

sz=size(lum);
blue=ones(sz)*255;
for i=1:256
    ind=find(lum>=callum(i) & lum<callum(i+1));
    blue(ind)=i-1; %#ok<FNDSB>
end
img=zeros(sz(1),sz(2),3);   % preallocate memory
img(:,:,3)=blue;
img(:,:,1)=(lum-callum(blue+1))./(callum(blue+2)-callum(blue+1))*ratio;  % red
img=uint8(img);

if trigger==0, return; end
switch trigger
    case {2, 'top'}
        line = 1;    % first line of the image
    case {3, 'middle'}
        line = round(sz(1)/2);    % middle of image
    otherwise
        line = ceil(find(lum',1,'first')/sz(2));    % the first line where lum is non-zero
end
img(line,:,2) = uint8(255);         % set green to 255. Will send trigger when displayed

return;

% Switch between normal color mode and high grayscale mode of a box video switcher. 
% Usage for box switcher:
%  switchColor(windowOrScreenNumber) to switch back and forth between two modes.
% Call it to switch to grayscale mode after you open a window, and 
% call it again to switch back to normal color mode after you finish display.
% XL, 01/08/2008
function switchColorBox(w, width, height, ifi, enableLuminanceMode) %#ok<INUSD>

% Doesn't help doesn't hurt either:
Screen('FillRect',w,0);

ppb=1/ifi*width*height/4e6; % pixels per bit based on 4 MHz detection frequency

% these 2 signals will make a switch
bits=[1 1 1 0 0 1 0 1 0 1 0 1 1 1 1 0 0 0 1 1 0 1 1 0 1; ... 
      1 1 0 1 1 0 1 1 1 0 1 1 0 1 1 0 1 0 1 0 1 1 0 0 1]; 

% factor means the percent of time to display pixels within a frame.
% Here we use a large range of factor so it will work for different settings.
range=[0.6 0.85];   % you can narrow the range at specific monitor setting
step=0.2/ppb; % in case of switch failure, reduce 0.2 to about 0.15
pixelPos=round((0:25)*ppb/range(1));
img1=zeros(1, pixelPos(26), 3); % one line image, will change green layer
img2=img1;
for i=1:25
    img1(1, pixelPos(i)+1:pixelPos(i+1), 2)=bits(1,i)*255;
    img2(1, pixelPos(i)+1:pixelPos(i+1), 2)=bits(2,i)*255;
end
tex(1)=Screen('MakeTexture',w,img1);
tex(2)=Screen('MakeTexture',w,img2);

for i=1:2
    for factor=(range(1):step:range(2))/range(1)
        rd=rand*ppb*0.5; % random shift up to half a bit
        Screen('DrawTexture',w, tex(i),[],[rd 0 pixelPos(26)/factor+rd 1]); 
        Screen('Flip',w);
    end
end

% Release textures:
Screen('Close', tex);

return;

% Switch between normal color mode and high grayscale mode of a card video switcher. 
% Usage: switchColorCard [(color,windowOrScreenNumber)]
%   Optional inputs: 
%     color: 0, 'BW' or 'gray' to switch to high grayscale monochrome mode, 
%        otherwise (or omit) to switch to color mode.
%     windowOrScreenNumber: windowPtr or screen number. Default is current onscreen.
% XL, 11/2007

function switchColorCard(w, width, height, ifi, enableLuminanceMode)

Screen('FillRect',w,0);

ppb=1/ifi*width*height/4e6; % pixels per bit based on 4 MHz detection frequency

% 1st row to gray, 2nd color
bits=[1 1 1 0 0 1 0 1 0 1 0 1 1 1 1 0 0 0 1 1 0 1 1 0 1; ... 
      1 1 0 1 1 0 1 1 1 0 1 1 0 1 1 0 1 0 1 0 1 1 0 0 1]; 

if enableLuminanceMode==1
    % Switch to monochrome mode:
    color=1;
else
    % Switch to RGB mode:
    color=2;
end

% factor means the percent of time to display pixels within a frame.
% Here we use a large range of factor so it will work for different settings.
range=[0.6 0.85];   % you can narrow the range at specific monitor setting
step=0.2/ppb; % in case of switch failure, reduce 0.2 to about 0.15
pixelPos=round((0:25)*ppb/range(1));
img=zeros(1, pixelPos(26), 3); % one line image, will change green layer
for i=1:25
    img(1, pixelPos(i)+1:pixelPos(i+1), 2)=bits(color,i)*255;
end
tex=Screen('MakeTexture',w,img);

for factor=(range(1):step:range(2))/range(1)
    rd=rand*ppb*0.5; % random shift up to half a bit
    Screen('DrawTexture',w,tex,[],[rd 0 rd+pixelPos(26)/factor 1]);
    Screen('Flip',w); 
end

% MK: Release textures:
Screen('Close', tex);

return;