#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 05 15:48:53 2016

@author: Sebastien Popoff
"""

import ctypes as ct
import platform
import numpy as np
import six

if six.PY3:
    import winreg as _winreg
else:
    import _winreg

# Standard parameter
ALP_DEFAULT = 0
ALP_ENABLE = 1

## Return codes
ALP_OK = 0x00000000  # Successfull execution
ALP_NOT_ONLINE = 1001  # The specified ALP has not been found or is not ready.

##	parameters ##

## AlpDevInquire

ALP_DEVICE_NUMBER = 2000  # Serial number of the ALP device
ALP_VERSION = 2001  # Version number of the ALP device
ALP_DEV_STATE = 2002  # current ALP status, see above
ALP_AVAIL_MEMORY = 2003  # ALP on-board sequence memory available for further sequence
# 	allocation (AlpSeqAlloc); number of binary pictures
# Temperatures. Data format: signed long with 1 LSB=1/256 degrees C
ALP_DDC_FPGA_TEMPERATURE = (
    2050  # V4100 Rev B: LM95231. External channel: DDC FPGAs Temperature Diode
)
ALP_APPS_FPGA_TEMPERATURE = (
    2051  # V4100 Rev B: LM95231. External channel: Application FPGAs Temperature Diode
)
ALP_PCB_TEMPERATURE = (
    2052  # V4100 Rev B: LM95231. Internal channel. "Board temperature"
)

# 	AlpDevControl - ControlTypes & ControlValues
ALP_SYNCH_POLARITY = 2004  # Select frame synch output signal polarity
ALP_TRIGGER_EDGE = 2005  # Select active input trigger edge (slave mode)
ALP_LEVEL_HIGH = 2006  # Active high synch output
ALP_LEVEL_LOW = 2007  # Active low synch output
ALP_EDGE_FALLING = 2008  # High to low signal transition
ALP_EDGE_RISING = 2009  # Low to high signal transition

ALP_TRIGGER_TIME_OUT = 2014  # trigger time-out (slave mode)
ALP_TIME_OUT_ENABLE = 0  # Time-out enabled (default)
ALP_TIME_OUT_DISABLE = 1  # Time-out disabled */

ALP_USB_CONNECTION = 2016  # Re-connect after a USB interruption

ALP_DEV_DMDTYPE = 2021  # Select DMD type; only allowed for a new allocated ALP-3 device
ALP_DMDTYPE_XGA = 1  # 1024*768 mirror pixels (0.7" Type A, D3000)
ALP_DMDTYPE_SXGA_PLUS = 2  # 1400*1050 mirror pixels (0.95" Type A, D3000)
ALP_DMDTYPE_1080P_095A = 3  # 1920*1080 mirror pixels (0.95" Type A, D4x00)
ALP_DMDTYPE_XGA_07A = 4  # 1024*768 mirror pixels (0.7" Type A, D4x00)
ALP_DMDTYPE_XGA_055A = 5  # 1024*768 mirror pixels (0.55" Type A, D4x00)
ALP_DMDTYPE_XGA_055X = 6  # 1024*768 mirror pixels (0.55" Type X, D4x00)
ALP_DMDTYPE_WUXGA_096A = 7  # 1920*1200 mirror pixels (0.96" Type A, D4100)
ALP_DMDTYPE_WQXGA_400MHZ_090A = 8  # 2560*1600 mirror pixels (0.90" Type A, DLPC910) at standard clock rate (400 MHz)
ALP_DMDTYPE_WQXGA_480MHZ_090A = 9  # WQXGA at extended clock rate (480 MHz); WARNING: This mode requires temperature control of DMD
ALP_DMDTYPE_WXGA_S450 = 12  # # 1280x800 (DLP650LNIR WXGA S450)
ALP_DMDTYPE_DISCONNECT = 255  # behaves like 1080p (D4100)

ALP_DEV_DISPLAY_HEIGHT = 2057  # number of mirror rows on the DMD
ALP_DEV_DISPLAY_WIDTH = 2058  # number of mirror columns on the DMD

ALP_DEV_DMD_MODE = 2064  # query/set DMD PWR_FLOAT mode, valid options: ALP_DMD_RESUME (normal operation: "wake up DMD"), ALP_DMD_POWER_FLOAT
ALP_DMD_RESUME = 0  # default mode, Wake up DMD; Auto-Shutdown on loss of supply voltage safely switches off DMD
ALP_DMD_POWER_FLOAT = 1  # power down, release micro mirrors from deflected state

ALP_PWM_LEVEL = 2063  # PWM pin duty-cycle as percentage: 0..100%; after AlpDevAlloc: 0%

# AlpDevControlEx
ALP_DEV_DYN_SYNCH_OUT1_GATE = 2023
ALP_DEV_DYN_SYNCH_OUT2_GATE = 2024
ALP_DEV_DYN_SYNCH_OUT3_GATE = 2025


class tAlpDynSynchOutGate(ct.Structure):
    # For ControlType ALP_DEV_DYN_TRIG_OUT[1..3]_GATE of function AlpDevControlEx
    # Configure compiler to not insert padding bytes! (e.g. #pragma pack)
    _fields_ = [
        ("Period", ct.c_ubyte),  # Period=1..16 enables output; 0: tri-state
        ("Polarity", ct.c_ubyte),  # 0: active pulse is low, 1: high
        ("Gate", ct.c_ubyte * 16),
    ]  # #Period number of bytes; each one is 0 or 1
    # Period bytes are used!


# AlpSeqControl - ControlTypes
ALP_SEQ_REPEAT = 2100  # Non-continuous display of a sequence (AlpProjStart) allows
# 	for configuring the number of sequence iterations.
ALP_SEQ_REPETE = ALP_SEQ_REPEAT  # According to the typo made in primary documentation (ALP API description)
ALP_FIRSTFRAME = 2101  # First image of this sequence to be displayed.
ALP_LASTFRAME = 2102  # Last image of this sequence to be displayed.

ALP_BITNUM = (
    2103  # A sequence can be displayed with reduced bit depth for faster speed.
)
ALP_BIN_MODE = 2104  # Binary mode: select from ALP_BIN_NORMAL and ALP_BIN_UNINTERRUPTED (AlpSeqControl)

ALP_BIN_NORMAL = 2105  # Normal operation with progammable dark phase
ALP_BIN_UNINTERRUPTED = 2106  # Operation without dark phase

ALP_PWM_MODE = 2107  # ALP_DEFAULT, ALP_FLEX_PWM
ALP_FLEX_PWM = 3  # ALP_PWM_MODE: all bit planes of the sequence are displayed as
# 	fast as possible in binary uninterrupted mode;
# 	use ALP_SLAVE mode to achieve a custom pulse-width modulation timing for generating gray-scale

ALP_DATA_FORMAT = 2110  # Data format and alignment
ALP_DATA_MSB_ALIGN = 0  # Data is MSB aligned (default)
ALP_DATA_LSB_ALIGN = 1  # Data is LSB aligned
ALP_DATA_BINARY_TOPDOWN = (
    2  # Data is packed binary, top row first; bit7 of a byte = leftmost of 8 pixels
)
ALP_DATA_BINARY_BOTTOMUP = 3  # Data is packed binary, bottom row first
# XGA:   one pixel row occupies 128 byte of binary data.
#        Byte0.Bit7 = top left pixel (TOPDOWN format)
# 1080p and WUXGA: one pixel row occupies 256 byte of binary data.
#        Byte0.Bit7 = top left pixel (TOPDOWN format)
# SXGA+: one pixel row occupies 176 byte of binary data. First byte ignored.
#        Byte1.Bit7 = top left pixel (TOPDOWN format)

ALP_SEQ_PUT_LOCK = 2119  # ALP_DEFAULT: Lock Sequence Memory in AlpSeqPut;
# 	Not ALP_DEFAULT: do not lock, instead allow writing sequence image data even currently displayed


ALP_FIRSTLINE = 2111  # Start line position at the first image
ALP_LASTLINE = 2112  # Stop line position at the last image
ALP_LINE_INC = 2113  # Line shift value for the next frame
ALP_SCROLL_FROM_ROW = 2123  # combined value from ALP_FIRSTFRAME and ALP_FIRSTLINE
ALP_SCROLL_TO_ROW = 2124  # combined value from ALP_LASTFRAME and ALP_LASTLINE

# 	Frame Look Up Table (FLUT): sequence settings select how to use the FLUT.
# 	The look-up table itself is shared across all sequences.
# 	(use ALP_FLUT_SET_MEMORY controls for accessing it)
ALP_FLUT_MODE = 2118  # Select Frame LookUp Table usage mode:
ALP_FLUT_NONE = 0  # linear addressing, do not use FLUT (default)
ALP_FLUT_9BIT = 1  # Use FLUT for frame addressing: 9-bit entries
ALP_FLUT_18BIT = 2  # Use FLUT for frame addressing: 18-bit entries

ALP_FLUT_ENTRIES9 = 2120  # Determine number of FLUT entries; default=1
# 	Entries: supports all values from 1 to ALP_FLUT_MAX_ENTRIES9
ALP_FLUT_OFFSET9 = 2122  # Determine offset of FLUT index; default=0
# 	Offset supports multiples of 256;
# 	For ALP_FLUT_18BIT, the effective index is half of the 9-bit index.
# 	--> "ALP_FLUT_ENTRIES18" and "ALP_FLUT_FRAME_OFFSET18" are 9-bit settings divided by 2
# 	The API does not reject overflow! (FRAME_OFFSET+ENTRIES > MAX_ENTRIES).
# 	The user is responsible for correct settings.

ALP_SEQ_DMD_LINES = 2125  # Area of Interest: Value = MAKELONG(StartRow, RowCount)

ALP_X_SHEAR_SELECT = 2132  # ALP_DEFAULT or ALP_ENABLE

ALP_DMD_MASK_SELECT = 2134  # ALP_DEFAULT or ALP_ENABLE, ALP_DMD_MASK_16X8
ALP_DMD_MASK_16X16 = ALP_ENABLE
ALP_DMD_MASK_16X8 = 2  # XGA only


def MAKELONG(StartRow, RowCount):
    return StartRow + (RowCount << 16)


#  AlpSeqInquire
ALP_BITPLANES = 2200  # Bit depth of the pictures in the sequence
ALP_PICNUM = 2201  # Number of pictures in the sequence
ALP_PICTURE_TIME = 2203  # Time between the start of consecutive pictures in the sequence in microseconds,
# 	the corresponding in frames per second is
# 	picture rate [fps] = 1 000 000 / ALP_PICTURE_TIME [µs]
ALP_ILLUMINATE_TIME = 2204  # Duration of the display of one picture in microseconds
ALP_SYNCH_DELAY = 2205  # Delay of the start of picture display with respect
# 	to the frame synch output (master mode) in microseconds
ALP_SYNCH_PULSEWIDTH = (
    2206  # Duration of the active frame synch output pulse in microseconds
)
ALP_TRIGGER_IN_DELAY = 2207  # Delay of the start of picture display with respect to the
# 	active trigger input edge in microseconds
ALP_MAX_SYNCH_DELAY = (
    2209  # Maximal duration of frame synch output to projection delay in microseconds
)
ALP_MAX_TRIGGER_IN_DELAY = (
    2210  # Maximal duration of trigger input to projection delay in microseconds
)

ALP_MIN_PICTURE_TIME = (
    2211  # Minimum time between the start of consecutive pictures in microseconds
)
ALP_MIN_ILLUMINATE_TIME = (
    2212  # Minimum duration of the display of one picture in microseconds
)
# 	depends on ALP_BITNUM and ALP_BIN_MODE
ALP_MAX_PICTURE_TIME = 2213  # Maximum value of ALP_PICTURE_TIME

# 	ALP_PICTURE_TIME = ALP_ON_TIME + ALP_OFF_TIME
# 	ALP_ON_TIME may be smaller than ALP_ILLUMINATE_TIME
ALP_ON_TIME = 2214  # Total active projection time
ALP_OFF_TIME = 2215  # Total inactive projection time

#  AlpProjInquire & AlpProjControl & ...Ex - InquireTypes, ControlTypes & Values */
ALP_PROJ_MODE = 2300  # Select from ALP_MASTER and ALP_SLAVE mode */
ALP_MASTER = 2301  # The ALP operation is controlled by internal */
# 	timing, a synch signal is sent out for any */
# 	picture displayed */
ALP_SLAVE = 2302  # The ALP operation is controlled by external */
# 	trigger, the next picture in a sequence is */
# 	displayed after the detection of an external */
# 	input trigger signal. */
ALP_PROJ_STEP = 2329  # ALP operation should run in ALP_MASTER mode,
# 											but each frame is repeatedly displayed
# 											until a trigger event is received.
# 											Values (conditions): ALP_LEVEL_HIGH |
# 											LOW, ALP_EDGE_RISING | FALLING.
# 											ALP_DEFAULT disables the trigger and
# 											makes the sequence progress "as usual".
# 											If an event is "stored" in edge mode due
# 											to a past edge, then it will be
# 											discarded during
# 											AlpProjControl(ALP_PROJ_STEP). */
ALP_PROJ_SYNC = 2303  # Select from ALP_SYNCHRONOUS and ALP_ASYNCHRONOUS mode */
ALP_SYNCHRONOUS = 2304  # The calling program gets control back after completion */
# 	of sequence display. */
ALP_ASYNCHRONOUS = 2305  # The calling program gets control back immediatelly. */

ALP_PROJ_INVERSION = 2306  # Reverse dark into bright */
ALP_PROJ_UPSIDE_DOWN = 2307  # Turn the pictures upside down */

ALP_PROJ_STATE = 2400  # Inquire only */

ALP_FLUT_MAX_ENTRIES9 = 2324  # Inquire FLUT size */
# Transfer FLUT memory to ALP. Use AlpProjControlEx and pUserStructPtr of type tFlutWrite. */
ALP_FLUT_WRITE_9BIT = 2325  # 9-bit look-up table entries */
ALP_FLUT_WRITE_18BIT = 2326  # 18-bit look-up table entries  */


# for ALP_FLUT_WRITE_9BIT, ALP_FLUT_WRITE_18BIT (both versions share the same data type),
# to be used with AlpProjControlEx */
class tFlutWrite(ct.Structure):
    _fields_ = [
        (
            "nOffset",
            ct.c_long,
        ),  # first LUT entry to transfer (write FrameNumbers[0] to LUT[nOffset]):
        # number of 9-bit or 18-bit entries to transfer;
        # For nSize=ALP_DEFAULT(0) the API sets nSize to its maximum value. This
        # requires nOffset=0
        ("nSize", ct.c_long),
        # nOffset+nSize must not exceed ALP_FLUT_MAX_ENTRIES9 (ALP_FLUT_WRITE_9BIT)
        # or ALP_FLUT_MAX_ENTRIES9/2 (ALP_FLUT_WRITE_18BIT).
        # The ALP API reads only the first nSize entries from this array. It
        # extracts 9 or 18 least significant bits from each entry.
        ("FrameNumbers", ct.c_ulong * 4096),
    ]


ALP_X_SHEAR = 2337  # use AlpProjControlEx and UserStructPtr of type tAlpShearTable


class tAlpShearTable(ct.Structure):  # AlpProjControlEx, ControlType ALP_X_SHEAR
    _fields_ = [
        ("nOffset", ct.c_long),
        ("nSize", ct.c_long),
        ("nShiftDistance", ct.c_long * 2048),
    ]  # values range from 0 to 511


ALP_DMD_MASK_WRITE = 2339  # use AlpProjControlEx and UserStructPtr of type tAlpDmdMask


class tAlpDmdMask(ct.Structure):  # AlpProjControlEx, ControlType ALP_DMD_MASK_WRITE
    _fields_ = [
        ("nRowOffset", ct.c_long),  # Bitmap position in a 16x16 mask, ALP_DEFAULT=0
        (
            "nRowCount",
            ct.c_long,
        ),  # rows to be written or ALP_DEFAULT (full DMD 16x16 mask)
        ("Bitmap", ct.c_ubyte * 2048),
    ]  # each bit controls a block of DMD pixels


## Sequence Queue API Extension:
ALP_PROJ_QUEUE_MODE = 2314
ALP_PROJ_LEGACY = 0  # ALP_DEFAULT: emulate legacy mode: 1 waiting position. AlpProjStart replaces enqueued and still waiting sequences */
ALP_PROJ_SEQUENCE_QUEUE = 1  # manage active sequences in a queue */

ALP_PROJ_QUEUE_ID = 2315  # provide the QueueID (ALP_ID) of the most recently enqueued sequence (or ALP_INVALID_ID) */
ALP_PROJ_QUEUE_MAX_AVAIL = (
    2316  # total number of waiting positions in the sequence queue */
)
ALP_PROJ_QUEUE_AVAIL = 2317  # number of available waiting positions in the queue */
# 	   bear in mind that when a sequence runs, it is already dequeued and does not consume a waiting position any more */
ALP_PROJ_PROGRESS = 2318  # (AlpProjInquireEx) inquire detailled progress of the running sequence and the queue */
ALP_PROJ_RESET_QUEUE = 2319  # Remove all enqueued sequences from the queue. The currently running sequence is not affected. ControlValue must be ALP_DEFAULT */
ALP_PROJ_ABORT_SEQUENCE = 2320  # abort the current sequence (ControlValue=ALP_DEFAULT) or a specific sequence (ControlValue=QueueID); abort after last frame of current iteration */
ALP_PROJ_ABORT_FRAME = 2321  # similar, but abort after next frame */
# 	  	Only one abort request can be active at a time. If it is requested to
# 		abort another sequence before the old request is completed, then
# 		AlpProjControl returns ALP_NOT_IDLE. (Please note, that AlpProjHalt
# 		and AlpDevHalt work anyway.) If the QueueID points to a sequence
# 		behind an indefinitely started one (AlpProjStartCont) then it returns
# 		ALP_PARM_INVALID in order to prevent dead-locks. */
ALP_PROJ_WAIT_UNTIL = 2323  # When does AlpProjWait complete regarding the last frame? or after picture time of last frame */
ALP_PROJ_WAIT_PIC_TIME = 0  # ALP_DEFAULT: AlpProjWait returns after picture time */
ALP_PROJ_WAIT_ILLU_TIME = 1  # AlpProjWait returns after illuminate time (except binary uninterrupted sequences, because an "illuminate time" is not applicable there) */


# for AlpProjInquireEx(ALP_PROJ_PROGRESS):
class tAlpProjProgress(ct.Structure):
    _fields_ = [
        ("CurrentQueueId", ct.c_ulong),
        (
            "SequenceId",
            ct.c_ulong,
        ),  # Consider that a sequence can be enqueued multiple times!
        ("nWaitingSequences", ct.c_ulong),  # number of sequences waiting in the queue
        # track iterations and frames: device-internal counters are incompletely
        # reported; The API description contains more details on that.
        ("nSequenceCounter", ct.c_ulong),  # Number of iterations to be done
        ("nSequenceCounterUnderflow", ct.c_ulong),  # nSequenceCounter can
        # underflow (for indefinitely long Sequences: AlpProjStartCont);
        # nSequenceCounterUnderflow is 0 before, and non-null afterwards
        ("nFrameCounter", ct.c_ulong),  # Frames left inside current iteration
        ("nPictureTime", ct.c_ulong),  # micro seconds of each frame; this is
        # reported, because the picture time of the original sequence could
        # already have changed in between
        ("nFramesPerSubSequence", ct.c_ulong),  # Each sequence iteration
        # displays this number of frames. It is reported to the user just for
        # convenience, because it depends on different parameters. */
        ("nFlagse", ct.c_ulong),
    ]
    # may be a combination of ALP_FLAG_SEQUENCE_ABORTING | SEQUENCE_INDEFINITE | QUEUE_IDLE | FRAME_FINISHED


ALP_FLAG_QUEUE_IDLE = ct.c_ulong(1)
ALP_FLAG_SEQUENCE_ABORTING = ct.c_ulong(2)
ALP_FLAG_SEQUENCE_INDEFINITE = ct.c_ulong(
    4
)  # AlpProjStartCont: this sequence runs indefinitely long, until aborted
ALP_FLAG_FRAME_FINISHED = ct.c_ulong(
    8
)  # illumination of last frame finished, picture time still progressing
ALP_FLAG_RSVD0 = ct.c_ulong(16)  # reserved


# for AlpSeqPutEx():
class tAlpLinePut(ct.Structure):
    _fields_ = [
        (
            "TransferMode",
            ct.c_long,
        ),  # common first member of AlpSeqPutEx' UserStructPtr argument
        ("PicOffset", ct.c_long),
        ("PicLoad", ct.c_long),
        ("LineOffset", ct.c_long),
        ("LineLoad", ct.c_long),
    ]


ALP_PUT_LINES = ct.c_long(1)  # not ulong; need to be long in the tAlpLinePut struct

ALP_ERRORS = {
    1001: "The specified ALP device has not been found or is not ready.",
    1002: "The ALP device is not in idle state.",
    1003: "The specified ALP device identifier is not valid.",
    1004: "The specified ALP device is already allocated.",
    1005: "One of the parameters is invalid.",
    1006: "Error accessing user data.",
    1007: "The requested memory is not available (full?).",
    1008: "The sequence specified is currently in use.",
    1009: "The ALP device has been stopped while image data transfer was active.",
    1010: "Initialization error.",
    1011: "Communication error.",
    1012: "The specified ALP has been removed.",
    1013: "The onboard FPGA is unconfigured.",
    1014: "The function is not supported by this version of the driver file VlxUsbLd.sys.",
    1018: "Waking up the DMD from PWR_FLOAT did not work (ALP_DMD_POWER_FLOAT)",
    1019: "Support in ALP drivers missing. Update drivers and power-cycle device.",
    1020: "SDRAM Initialization failed.",
}


class ALPError(Exception):
    def __init__(self, error_code):
        super(ALPError, self).__init__(ALP_ERRORS[error_code])


def afficheur(bitPlane):
    nSizeX = 2560
    nSizeY = 1600
    display = np.zeros((nSizeY, nSizeX))
    for jj in range(nSizeY):
        for ii in range(nSizeX // 8):
            Q = bitPlane[jj * nSizeX // 8 + ii]
            R = [0, 0, 0, 0, 0, 0, 0, 0]
            k = 7
            while Q != 0:
                R[k] = Q % 2
                Q = Q // 2
                k -= 1
            for ll in range(8):
                display[jj, ii * 8 + ll] = R[ll]
    return display


def img_to_bitplane(imgArray):
    """
    Convert a binary image into a bitplane.
    """
    bitPlane = np.packbits(imgArray).tolist()
    return bitPlane


class ALP4(object):
    """
    This class controls a Vialux DMD board based on the Vialux ALP 4.X API.
    """

    def __init__(self, version="4.3", libDir=None):
        os_type = platform.system()

        if libDir is None:
            try:
                reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
                key = _winreg.OpenKey(reg, r"SOFTWARE\ViALUX\ALP-" + version)
                libDir = (_winreg.QueryValueEx(key, "Path"))[
                    0
                ] + "/ALP-{0} high-speed API/".format(version)
            except EnvironmentError:
                raise ValueError(
                    "Cannot auto detect libDir! Please specify it manually."
                )

        if libDir.endswith("/"):
            libPath = libDir
        else:
            libPath = libDir + "/"
            ## Load the ALP dll
        if os_type == "Windows":
            if ct.sizeof(ct.c_voidp) == 8:  ## 64bit
                libPath += "x64/"
            elif not (ct.sizeof(ct.c_voidp) == 4):  ## 32bit
                raise OSError("System not supported.")
        else:
            raise OSError("System not supported.")

        if version == "4.1":
            libPath += "alpD41.dll"
        elif version == "4.2":
            libPath += "alpV42.dll"
        elif version == "4.3":
            libPath += "alp4395.dll"
        elif version == "4.4":
            libPath += "Alp44.dll"
        else:
            raise ValueError("Version not supported.")

        # print("Loading library: " + libPath)

        self._ALPLib = ct.CDLL(libPath)

        ## Class parameters
        # ID of the current ALP device
        self.ALP_ID = ct.c_ulong(0)
        # Type of DMD found
        self.DMDType = ct.c_long(0)
        # Pointer to the last stored image sequence
        self._lastDDRseq = None
        # List of all Sequences
        self.Seqs = []

    def _checkError(self, returnValue, errorString, warning=False):
        if not (returnValue == ALP_OK):
            if not warning:
                raise ALPError(returnValue)
            else:
                print(errorString + "\n" + ALP_ERRORS[returnValue])

    def Initialize(self, DeviceNum=None):
        """
        Initialize the communication with the DMD.

        Usage:
        Initialize(DeviceNum = None)

        PARAMETERS
        ----------
        DeviceNum : int
                    Serial number of the DMD to initialize, useful for multiple DMD control.
                    If not specify, open the first available DMD.
        """
        if DeviceNum is None:
            DeviceNum = ct.c_long(ALP_DEFAULT)

        self._checkError(
            self._ALPLib.AlpDevAlloc(DeviceNum, ALP_DEFAULT, ct.byref(self.ALP_ID)),
            "Cannot open DMD.",
        )
        self._checkError(
            self._ALPLib.AlpDevInquire(
                self.ALP_ID, ALP_DEV_DMDTYPE, ct.byref(self.DMDType)
            ),
            "Inquery fails.",
        )

        nSizeX = ct.c_long(0)
        self._checkError(
            self._ALPLib.AlpDevInquire(
                self.ALP_ID, ALP_DEV_DISPLAY_WIDTH, ct.byref(nSizeX)
            ),
            "Inquery ALP_DEV_DISPLAY_WIDTH fails.",
        )
        self.nSizeX = nSizeX.value

        nSizeY = ct.c_long(0)
        self._checkError(
            self._ALPLib.AlpDevInquire(
                self.ALP_ID, ALP_DEV_DISPLAY_HEIGHT, ct.byref(nSizeY)
            ),
            "Inquery ALP_DEV_DISPLAY_HEIGHT fails.",
        )
        self.nSizeY = nSizeY.value

        print(
            "DMD found, resolution = "
            + str(self.nSizeX)
            + " x "
            + str(self.nSizeY)
            + "."
        )

    def SeqAlloc(self, nbImg=1, bitDepth=1):
        """
        This function provides ALP memory for a sequence of pictures. All pictures of a sequence have the
        same  bit  depth.  The  function  allocates  memory  from  the  ALP  board  RAM. The  user  has  no  direct
        read/write  access.  ALP  functions  provide  data  transfer  using  the  sequence  memory  identifier
        (SequenceId) of type ALP_ID.
        Pictures can be loaded into the ALP RAM using the SeqPut function.
        The availability of ALP memory can be tested using the DevInquire function.
        When a sequence is no longer required, release it using SeqFree.


        Usage:
        SeqAlloc(nbImg = 1, bitDepth = 1)

        PARAMETERS
        ----------
        nbImg : int
                Number of images in the sequence.
        bitDepth : int
                   Quantization of the image between 1 (on/off) and 8 (256 pwm grayscale levels).

        See ALPLib.AlpSeqAlloc in the ALP API description for more information.

        RETURNS
        -------
        SequenceID : ctypes c_ulong
                     Id of the created sequence.
                     This id is stored internally as the last created sequence and
                     erase the previous one. When a sequence relasted function is used without
                     specifying a SequenceId, it will use the stored SequenceId.

        """

        SequenceId = ct.c_long(0)
        self.Seqs.append(
            SequenceId
        )  # Put SequenceId in list of all Sequences to keep track of them
        # Allocate memory on the DDR RAM for the sequence of image.
        self._checkError(
            self._ALPLib.AlpSeqAlloc(
                self.ALP_ID, ct.c_long(bitDepth), ct.c_long(nbImg), ct.byref(SequenceId)
            ),
            "Cannot allocate image sequence.",
        )

        self._lastDDRseq = SequenceId
        return SequenceId

    def SeqPutEx(
        self,
        imgData,
        LineOffset,
        LineLoad,
        SequenceId=None,
        PicOffset=0,
        PicLoad=0,
        dataFormat="Python",
    ):
        """
        Image data transfer using AlpSeqPut is based on whole DMD frames. Applications that only
        update small regions inside a frame suffer from overhead of this default behavior. An extended
        ALP API function is available to reduce this overhead.

        The AlpSeqPutEx function offers the same functionality as the standard function (AlpSeqPut),
        but in addition, it is possible to select a section within a sequence frame using the
        LineOffset and LineLoad parameters of the tAlpLinePut data-structure (see below) and update
        only this section of the SDRAM-memory associated with the sequence for a range of
        sequence-pictures (selected via the PicOffset and PicLoad parameters of tAlpLinePut in
        similarity to AlpSeqPut).

        This results in accelerated transfer-time of small image data updates (due to the fact that the
        amount of transferred data is reduced).

        Therefore, the user only passes the lines of the pictures he wants to update via the UserArrayPtr
        (that would be PicLoad*LineLoad lines in total).

        PARAMETERS
        ----------

        imgData : list, 1D array or 1D ndarray
                  Data stream corresponding to a sequence of nSizeX by nSizeX images.
                  Values has to be between 0 and 255.
        LineOffset : int
                     Defines the offset of the frame-section. The frame-data of this section is transferred
                     for each of the frames selected with PicOffset and PicLoad. The value of this
                     parameter must be greater or equal to zero, otherwise ALP_PARM_INVALID is returned.
        LineLoad : int
                   Defines the size of the frame-section. If the value of the parameter is
                   less than zero or if LineOffset+LineLoad exceeds the number of lines
                   per sequence-frame, ALP_PARM_INVALID is returned. If LineLoad is
                   zero, this value is adjusted to include all lines of the frame, starting at
                   line LineOffset
        SequenceId : ctypes c_long
                     Sequence identifier. If not specified, set the last sequence allocated in the DMD board memory
        PicOffset : int, optional
                    Picture number in the sequence (starting at 0) where the data upload is
                    started; the meaning depends upon ALP_DATA_FORMAT.
                    By default, PifOffset = 0.
        PicLoad : int, optional
                 number of pictures that are to be loaded into the sequence memory.
                 Depends on ALP_DATA_FORMAT.
                 PicLoad = 0 correspond to a complete sequence.
                 By default, PicLoad = 0.
        dataFormat : string, optional
                 Specify the type of data sent as image.
                 Should be ' Python' or 'C'.
                 If the data is of Python format, it is converted into a C array before sending to the DMD via the dll.
                 By default dataFormat = 'Python'
        """

        if not SequenceId:
            SequenceId = self._lastDDRseq

        LinePutParam = tAlpLinePut(
            ALP_PUT_LINES,
            ct.c_long(PicOffset),
            ct.c_long(PicLoad),
            ct.c_long(LineOffset),
            ct.c_long(LineLoad),
        )

        if dataFormat not in ["Python", "C"]:
            raise ValueError('dataFormat must be one of "Python" or "C"')

        if dataFormat == "Python":
            pImageData = imgData.astype(np.uint8).ctypes.data_as(ct.c_void_p)
        elif dataFormat == "C":
            pImageData = ct.cast(imgData, ct.c_void_p)

        self._checkError(
            self._ALPLib.AlpSeqPutEx(self.ALP_ID, SequenceId, LinePutParam, pImageData),
            "Cannot send image sequence to device.",
        )

    def SeqPut(
        self, imgData, SequenceId=None, PicOffset=0, PicLoad=0, dataFormat="Python"
    ):
        """
        This  function  allows  loading user  supplied  data  via  the  USB  connection  into  the  ALP  memory  of  a
        previously allocated sequence (AlpSeqAlloc) or a part of such a sequence. The loading operation can
        run  concurrently to  the  display  of  other sequences.  Data  cannot be  loaded  into  sequences that  are
        currently started for display. Note: This protection can be disabled by ALP_SEQ_PUT_LOCK.

        The function loads PicNum pictures into the ALP memory reserved for the specified sequence starting
        at picture PicOffset. The calling program is suspended until the loading operation is completed.

        The  ALP  API  compresses  image  data  before  sending  it  over  USB.  This  results  in  a  virtual
        improvement of data transfer speed. Compression ratio is expected to vary depending on image data.
        Incompressible data do not cause overhead delays.

        Usage:
        SeqPut(imgData, nbImg = 1, bitDepth = 1)

        PARAMETERS
        ----------

        imgData : list, 1D array or 1D ndarray
                  Data stream corresponding to a sequence of nSizeX by nSizeX images.
                  Values has to be between 0 and 255.
        SequenceId : ctypes c_long
                     Sequence identifier. If not specified, set the last sequence allocated in the DMD board memory
        PicOffset : int, optional
                    Picture number in the sequence (starting at 0) where the data upload is
                    started; the meaning depends upon ALP_DATA_FORMAT.
                    By default, PifOffset = 0.
        PicLoad : int, optional
                 number of pictures that are to be loaded into the sequence memory.
                 Depends on ALP_DATA_FORMAT.
                 PicLoad = 0 correspond to a complete sequence.
                 By default, PicLoad = 0.
        dataFormat : string, optional
                 Specify the type of data sent as image.
                 Should be ' Python' or 'C'.
                 If the data is of Python format, it is converted into a C array before sending to the DMD via the dll.
                 By default dataFormat = 'Python'

        SEE ALSO
        --------

        See ALPLib.AlpSeqPut in the ALP API description for more information.
        """

        if not SequenceId:
            SequenceId = self._lastDDRseq

        if dataFormat == "Python":
            pImageData = imgData.astype(np.uint8).ctypes.data_as(ct.c_void_p)
        elif dataFormat == "C":
            pImageData = ct.cast(imgData, ct.c_void_p)
        else:
            raise ValueError('dataFormat must be one of "Python" or "C"')

        self._checkError(
            self._ALPLib.AlpSeqPut(
                self.ALP_ID,
                SequenceId,
                ct.c_long(PicOffset),
                ct.c_long(PicLoad),
                pImageData,
            ),
            "Cannot send image sequence to device.",
        )

    def ImgToBitPlane(self, imgArray, bitShift=0):
        """
        Create a bit plane from the imgArray.
        The bit plane is an (nSizeX x nSizeY / 8) array containing only the bit values
        corresponding to the bit number bitShift.
        For a bit depth = 8, 8 bit planes can be extracted from the imgArray by iterating ImgToBitPlane.

        WARNING: It is recommended to directly generate images as bitplanes for better performances.

        Usage:

        ImgToBitPlane(imgArray,bitShift = 0)

        PARAMETERS
        ----------

        imgArray: 1D array or list
                  An image of the same resolution as the DMD (nSizeX by nSizeY).

        bitShift: int, optional
                  Bit plane to extract form the imgArray (0 to 8),
                  Has to be <= bit depth.

        RETURNS
        -------

        bitPlane: list
                  Array (nSizeX x nSizeY)/8


        """
        return img_to_bitplane(imgArray, bitShift)

    def SetTiming(
        self,
        SequenceId=None,
        illuminationTime=None,
        pictureTime=None,
        synchDelay=None,
        synchPulseWidth=None,
        triggerInDelay=None,
    ):
        """
        Set the timing properties of the sequence to display.

        Usage:

        SetTiming( SequenceId = None, illuminationTime = None, pictureTime = None, synchDelay = None, \
                  synchPulseWidth = None, triggerInDelay = None)

        PARAMETERS
        ----------

        SequenceId : c_ulong, optional
                       Identified of the sequence. If not specified, set the last sequence allocated in the DMD board memory
        illuminationTime: c_ulong, optional
                           Display time of a single image of the sequence in microseconds.
                           If not specified, use the highest possible value compatible with pictureTime.
        pictureTime : int, optional
                        Time between the start of two consecutive picture, up to 10^7 microseconds = 10 seconds.
                        With illuminationTime, it sets the display rate.
                        If not specified, the value is set to minimize the dark time according illuminationTime.
                        If illuminationTime is also not specified, set to a frame rate of 30Hz.
        synchDelay : Specifies the time delay between the start of the output sync pulse and the start of the display (master mode).
                       Value between 0 and 130,000 microseconds. Set to 0 if not specified.
        synchPulseWidth : Duration of the sync output pulse.
                         By default equals synchDelay + illuminationTime in normal mode.
                         By default equals ALP_ILLUMINATION_TIME in binary uninterrupted mode.
        triggerInDelay : Length of the trigger signal in microseconds, set to 0 by default.


        SEE ALSO
        --------
        See ALPLib.AlpSeqAlloc in the ALP API description for more information.
        """
        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq
        if SequenceId is None:
            raise ValueError("No sequence to display.")

        if synchDelay is None:
            synchDelay = ALP_DEFAULT
        if synchPulseWidth is None:
            synchPulseWidth = ALP_DEFAULT
        if triggerInDelay is None:
            triggerInDelay = ALP_DEFAULT
        if illuminationTime is None:
            illuminationTime = ALP_DEFAULT
        if pictureTime is None:
            pictureTime = ALP_DEFAULT

        self._checkError(
            self._ALPLib.AlpSeqTiming(
                self.ALP_ID,
                SequenceId,
                ct.c_long(illuminationTime),
                ct.c_long(pictureTime),
                ct.c_long(synchDelay),
                ct.c_long(synchPulseWidth),
                ct.c_long(triggerInDelay),
            ),
            "Cannot set timing.",
        )

    def DevInquire(self, inquireType):
        """
        Ask the controller board the value of a specified parameter about the ALP device.

        Usage: Inquire(request)

        PARAMETERS
        ----------

        inquireType : ctypes c_ulong
                      Sepcifies the type of value to return.


        RETURNS
        -------

        value : c_double
                Value of the requested parameter.

        SEE ALSO
        --------

        See AlpDevInquire in the ALP API description for request types.

        """

        ret = ct.c_long(0)

        self._checkError(
            self._ALPLib.AlpDevInquire(self.ALP_ID, inquireType, ct.byref(ret)),
            "Error sending request.",
        )
        return ret.value

    def SeqInquire(self, inquireType, SequenceId=None):
        """
        Ask the controller board the value of a specified parameter about an image sequence.


        Usage: Inquire(self, inquireType,  SequenceId = None)

        PARAMETERS
        ----------

        inquireType : ctypes c_ulong
                  Sepcifies the type of value to return.
        SequenceId : ctyles c_long, optional
                     Identified of the sequence. If not specified, set the last sequence allocated in the DMD board memory

        RETURNS
        -------

        value : int
                Value of the requested parameter.


        SEE ALSO
        --------
        See AlpSeqInquire in the ALP API description for request types.
        """
        ret = ct.c_long(0)

        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq

        self._checkError(
            self._ALPLib.AlpSeqInquire(
                self.ALP_ID, SequenceId, inquireType, ct.byref(ret)
            ),
            "Error sending request.",
        )
        return ret.value

    def ProjInquire(self, inquireType, SequenceId=None):
        """
        Usage: ProjInquire(self, inquireType, SequenceId = None)

        PARAMETERS
        ----------

        request : ctypes c_ulong
                  Sepcifies the type of value to return.
        SequenceId : ctyles c_long, optional
                     Identified of the sequence. If not specified, set the last sequence allocated in the DMD board memory

        RETURNS
        -------

        value : int
                Value of the requested parameter.


        SEE ALSO
        --------
        See AlpProjInquire in the ALP API description for request types.
        """
        ret = ct.c_long(0)

        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq

        self._checkError(
            self._ALPLib.AlpProjInquire(
                self.ALP_ID, SequenceId, inquireType, ct.byref(ret)
            ),
            "Error sending request.",
        )
        return ret.value

    def ProjInquireEx(self, inquireType, SequenceId=None):
        """
        Data objects that do not fit into a simple 32-bit number can be inquired using this function.
        Meaning and layout of the data depend on the InquireType.

        Usage: ProjInquireEx(self, inquireType, UserStructPtr, SequenceId = None)

        PARAMETERS
        ----------

        inquireType : ctypes c_ulong
                      Sepcifies the type of value to return.
        SequenceId : ctypes c_long, optional
                     Identified of the sequence. If not specified, set the last sequence allocated in the DMD board memory

        RETURNS
        -------

        UserStructPtr : ctypes POINTER
                        Pointer to a data structure which shall be filled out by AlpSeqInquireEx.


        SEE ALSO
        --------
        See AlpProjInquireEx in the ALP API description for request types.
        """
        UserStructPtr = ct.c_double(0)

        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq

        self._checkError(
            self._ALPLib.AlpProjInquire(
                self.ALP_ID, SequenceId, inquireType, ct.byref(UserStructPtr)
            ),
            "Error sending request.",
        )
        return UserStructPtr

    def DevControl(self, controlType, value):
        """
        This  function  is used to  change  the  display  properties  of  the  ALP.
        The  default  values  are  assigned during device allocation by AllocateSequence.

        Usage: Control(self, controlType, value)

        PARAMETERS
        ----------

        controlType: ctypes c_ulong
                     Specifies the type of value to set.

        SEE ALSO
        --------
        See AlpDevControl in the ALP API description for control types.
        """
        self._checkError(
            self._ALPLib.AlpDevControl(self.ALP_ID, controlType, ct.c_long(value)),
            "Error sending request.",
        )

    def DevControlEx(self, controlType, userStruct):
        """
        Data objects that do not fit into a simple 32-bit number can be written using this function. Meaning and
        layout of the data depend on the ControlType.

        Usage: Control(self, controlType, value)

        PARAMETERS
        ----------

        controlType : ctypes c_ulong
                      Specifies the type of value to set.
        userStruct : tAlpDynSynchOutGate structure
                     It contains synch parameters.


        SEE ALSO
        --------

        See AlpDevControlEx in the ALP API description for control types.
        """
        self._checkError(
            self._ALPLib.AlpDevControlEx(
                self.ALP_ID, controlType, ct.byref(userStruct)
            ),
            "Error sending request.",
        )

    def ProjControl(self, controlType, value):
        """
        This function controls the system parameters that are in effect for all sequences. These parameters
        are maintained until they are modified again or until the ALP is freed. Default values are in effect after
        ALP allocation. All parameters can be read out using the AlpProjInquire function.
        This function is only allowed if the ALP is in idle wait state (ALP_PROJ_IDLE), which can be enforced
        by the AlpProjHalt function.

        Usage: Control(self, controlType, value)

        PARAMETERS
        ----------
        controlType : attribute flag (ctypes c_ulong)
                      Specify the paramter to set.

        value : c_double
                Value of the parameter to set.

        SEE ALSO
        --------

        See AlpProjControl in the ALP API description for control types.
        """
        self._checkError(
            self._ALPLib.AlpProjControl(self.ALP_ID, controlType, ct.c_long(value)),
            "Error sending request.",
        )

    def ProjControlEx(self, controlType, pointerToStruct):
        """
        Data  objects  that  do  not  fit  into  a  simple  32-bit  number  can  be  written  using  this  function.  These
        objects are unique to the ALP device, so they may affect display of all sequences.
        Meaning and layout of the data depend on the ControlType.

        Usage: Control(self, controlType, value)

        PARAMETERS
        ----------
        controlType : attribute flag (ctypes c_ulong)
            Specify the paramter to set.

        pointerToStruct : ctypes POINTER
            Pointer to a tFlutWrite structure. Create a tFlutWrite object and pass it to the function using ctypes.byref
            (Requires importing ctypes)


        SEE ALSO
        --------
        See AlpProjControlEx in the ALP API description for control types.
        """
        self._checkError(
            self._ALPLib.AlpProjContro(self.ALP_ID, controlType, pointerToStruct),
            "Error sending request.",
        )

    def SeqControl(self, controlType, value, SequenceId=None):
        """
        This function is used to change the display properties of a sequence.
        The default values are assigned during sequence allocation by AlpSeqAlloc.
        It  is  allowed  to  change  settings  of  sequences  that  are  currently  in  use.
        However  the  new  settings become effective after restart using AlpProjStart or AlpProjStartCont.

        Usage: SeqControl(self, controlType, value,  SequenceId = None)

        PARAMETERS
        ----------

        controlType : attribute flag (ctypes c_ulong)
            Specify the paramter to set.

        value : ctypes c_double
                Value of the parameter to set.

        SequenceId : ctypes c_long, optional
                     Identified of the sequence. If not specified, set the last sequence allocated in the DMD board memory


        SEE ALSO
        --------

        See AlpSeqControl in the ALP API description for control types.
        """

        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq

        self._checkError(
            self._ALPLib.AlpSeqControl(
                self.ALP_ID, SequenceId, controlType, ct.c_long(value)
            ),
            "Error sending request.",
        )

    def FreeSeq(self, SequenceId=None):
        """
        Frees a previously allocated sequence. The ALP memory reserved for the specified sequence in the device DeviceId is released.


        Usage: FreeSeq(SequenceId = None)

        PARAMETERS
        ----------

        SequenceId : ctypes c_long, optional
                     Identified of the sequence. If not specified, free the last sequence allocated in the DMD board memory
        """

        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq

        self.Seqs.remove(SequenceId)  # Removes the last SequenceId from sequence list
        self._checkError(
            self._ALPLib.AlpSeqFree(self.ALP_ID, SequenceId),
            "Unable to free the image sequence.",
            warning=True,
        )

    def Run(self, SequenceId=None, loop=True):
        """
        Display a sequence loaded into the DDR memory.

        Usage: Run( SequenceId = None, loop = True)

        PARAMETERS
        ----------

        SequenceId : ctypes c_ulong
                     Id of the sequence to run.
                     If no sequence pointer is given, display the last sequence stored.
        loop : bool
               If True, display the sequence continuously using ALPLib.AlpProjStartCont.
               If False, display it once using ALPLib.AlpProjStart. Set to True by default.

        SEE ALSO
        --------
        See ALPLib.AlpProjStart and ALPLib.AlpProjStartCont in the ALP API description for more information.
        """
        if (SequenceId is None) and (self._lastDDRseq):
            SequenceId = self._lastDDRseq
        if SequenceId is None:
            self._raiseError("No sequence to display.")

        if loop:
            self._checkError(
                self._ALPLib.AlpProjStartCont(self.ALP_ID, SequenceId),
                "Cannot launch sequence.",
            )
        else:
            self._checkError(
                self._ALPLib.AlpProjStart(self.ALP_ID, SequenceId),
                "Cannot launch sequence.",
            )

    def Wait(self):
        """
        This function is used to wait for the completion of the running sequence display.

        Usage: Wait()
        """
        self._checkError(
            self._ALPLib.AlpProjWait(self.ALP_ID), "Cannot go in wait mode."
        )

    def Halt(self):
        """
        This   function   puts   the   ALP   in   an   idle   wait   state.   Current   sequence   display   is   canceled
        (ALP_PROJ_IDLE) and the loading of sequences is aborted (AlpSeqPut).

        Usage: Halt()
        """
        self._checkError(self._ALPLib.AlpDevHalt(self.ALP_ID), "Cannot stop device.")

    def Free(self):
        """
        This  function  de-allocates  a  previously  allocated  ALP  device.  The  memory  reserved  by  calling
        AlpSeqAlloc is also released.
        The ALP has to be in idle wait state, see also AlpDevHalt.

        Usage: Free()
        """
        self._checkError(self._ALPLib.AlpDevFree(self.ALP_ID), "Cannot free device.")
        del self._ALPLib
