コード例 #1
0
        /// <summary>
        /// Alpaca Extension - Extracts the error message from a byte array returned by an Alpaca device.
        /// </summary>
        /// <param name="errorMessageBytes">The byte array from which to extract the error message.</param>
        /// <returns>The error message as a string.</returns>
        /// <exception cref="InvalidOperationException">The byte array is smaller than the smallest metadata size.</exception>
        /// <exception cref="InvalidOperationException">The byte array equals the smallest metadata length and thus does not contain an error message.</exception>
        public static string GetErrrorMessage(this byte[] errorMessageBytes)
        {
            // Validate error message array
            if (errorMessageBytes.Length < ARRAY_METADATAV1_LENGTH)
            {
                throw new InvalidOperationException($"GetErrrorMessage - Supplied array size: {errorMessageBytes.Length} is smaller than the minimum metadata size ({ARRAY_METADATAV1_LENGTH})");
            }
            if (errorMessageBytes.Length == ARRAY_METADATAV1_LENGTH)
            {
                throw new InvalidOperationException($"GetErrrorMessage - The byte array length equals the metadata length, the supplied array does not contain any message bytes.");
            }

            // Get the metadata version
            int metadataVersion = errorMessageBytes.GetMetadataVersion();

            // Process according to metadata version
            switch (metadataVersion)
            {
            case 1:
                ArrayMetadataV1 arrayMetadataV1 = errorMessageBytes.GetMetadataV1();
                return(Encoding.UTF8.GetString(errorMessageBytes, arrayMetadataV1.DataStart, errorMessageBytes.Length - arrayMetadataV1.DataStart));

            default:
                throw new InvalidValueException($"GetErrrorMessage - The supplied array contains an unsupported metadata version number: {metadataVersion}. This component supports metadata version 1.");
            }
        }
コード例 #2
0
        /// <summary>
        /// Convert an Alpaca error number and message to a byte array for transfer to a client.
        /// </summary>
        /// <param name="metadataVersion">Required metadata version - Currently 1</param>
        /// <param name="alpacaErrorNumber">Alpaca error number</param>
        /// <param name="errorMessage">Error message to encode.</param>
        /// <returns></returns>
        /// <exception cref="InvalidValueException"></exception>
        public static byte[] ErrorMessageToByteArray(int metadataVersion, uint clientTransactionID, uint serverTransactionID, AlpacaErrors alpacaErrorNumber, string errorMessage)
        {
            // Validate supplied parameters
            if (metadataVersion != 1)
            {
                throw new InvalidValueException($"ErrorMessageToByteArray - Unsupported metadata version: {metadataVersion}.");
            }

            if (alpacaErrorNumber == AlpacaErrors.AlpacaNoError)
            {
                throw new InvalidValueException($"ErrorMessageToByteArray - Supplied error number is {alpacaErrorNumber}, this indicates 'Success' rather than an 'Error'.");
            }

            if ((alpacaErrorNumber < AlpacaErrors.AlpacaNoError) | (alpacaErrorNumber > AlpacaErrors.DriverMax))
            {
                throw new InvalidValueException($"ErrorMessageToByteArray - Invalid Alpaca error number: {alpacaErrorNumber}.");
            }

            if (string.IsNullOrEmpty(errorMessage))
            {
                throw new InvalidValueException($"ErrorMessageToByteArray - Error message is either null or an empty string.");
            }

            switch (metadataVersion)
            {
            case 1:
                // Create a metadata structure containing the supplied error number
                ArrayMetadataV1 arrayMetadataV1 = new ArrayMetadataV1(alpacaErrorNumber, clientTransactionID, serverTransactionID, ImageArrayElementTypes.Unknown, ImageArrayElementTypes.Unknown, 0, 0, 0, 0);

                // Create a byte array from the metadata structure
                byte[] arrayMetadataV1Bytes = arrayMetadataV1.ToByteArray <ArrayMetadataV1>();

                // Create a byte array containing the UTF8 encoded string
                byte[] errorMessagebytes = Encoding.UTF8.GetBytes(errorMessage);

                // Create a return byte array that is large enough to contain both the metadata bytes and the UTF8 encoded message bytes
                byte[] returnByteArray = new byte[ARRAY_METADATAV1_LENGTH + errorMessagebytes.Length];

                // Copy the metadata bytes to the start of the return array
                Array.Copy(arrayMetadataV1Bytes, returnByteArray, arrayMetadataV1Bytes.Length);

                // Copy the error message bytes after the metadata bytes
                Array.Copy(errorMessagebytes, 0, returnByteArray, ARRAY_METADATAV1_LENGTH, errorMessagebytes.Length);

                // Return the composite byte array
                return(returnByteArray);

            default:
                throw new InvalidValueException($"ErrorMessageToByteArray - Unsupported metadata version: {metadataVersion}");
            }
        }
コード例 #3
0
        /// <summary>
        /// Alpaca Extension - Extracts the array metadata from in version 1 form from a byte array returned by an Alpaca device.
        /// </summary>
        /// <param name="imageBytes">The byte array from which to extract the metadata.</param>
        /// <returns>The metadata as a version 1 structure.</returns>
        /// <exception cref="InvalidOperationException">The byte array is smaller than the version 1 metadata size.</exception>
        public static ArrayMetadataV1 GetMetadataV1(this byte[] imageBytes)
        {
            if (imageBytes.Length < ARRAY_METADATAV1_LENGTH)
            {
                throw new InvalidOperationException($"GetMetadataV1 - Supplied array size: {imageBytes.Length} is smaller than the minimum metadata size ({ARRAY_METADATAV1_LENGTH}");
            }

            // Initialise array to hold the metadata bytes
            byte[] metadataV1Bytes = new byte[ARRAY_METADATAV1_LENGTH];

            // Copy the metadata bytes from the image array to the metadata bytes array
            Array.Copy(imageBytes, 0, metadataV1Bytes, 0, ARRAY_METADATAV1_LENGTH);

            // Create the metadata structure from the metadata bytes and return it to the caller
            ArrayMetadataV1 metadataV1 = metadataV1Bytes.ToStructure <ArrayMetadataV1>();

            return(metadataV1);
        }
コード例 #4
0
        /// <summary>
        /// Alpaca Extension - Convert a byte array to a 2D or 3D mage array based on the array metadata.
        /// </summary>
        /// <param name="imageBytes">byte array to convert</param>
        /// <returns>2D or 3D array as specified in the array metadata.</returns>
        /// <exception cref="InvalidValueException">The byte array is null.</exception>
        public static Array ToImageArray(this byte[] imageBytes)
        {
            ImageArrayElementTypes imageElementType;
            ImageArrayElementTypes transmissionElementType;
            int rank;
            int dimension1;
            int dimension2;
            int dimension3;
            int dataStart;

            // Validate the incoming array
            if (imageBytes is null)
            {
                throw new InvalidValueException("ToImageArray - Supplied array is null.");
            }
            if (imageBytes.Length <= ARRAY_METADATAV1_LENGTH)
            {
                throw new InvalidValueException($"ToImageArray - Supplied array does not exceed the size of the mandatory metadata. Arrays must contain at least {ARRAY_METADATAV1_LENGTH} bytes. The supplied array has a length of {imageBytes.Length}.");
            }

            int metadataVersion = imageBytes.GetMetadataVersion();

            // Get the metadata version and extract the supplied values
            switch (metadataVersion)
            {
            case 1:
                ArrayMetadataV1 metadataV1 = imageBytes.GetMetadataV1();
                // Set the array type, rank and dimensions
                imageElementType        = metadataV1.ImageElementType;
                transmissionElementType = metadataV1.TransmissionElementType;
                rank       = metadataV1.Rank;
                dimension1 = metadataV1.Dimension1;
                dimension2 = metadataV1.Dimension2;
                dimension3 = metadataV1.Dimension3;
                dataStart  = metadataV1.DataStart;

                Debug.WriteLine($"ToImageArray - Element type: {imageElementType} Transmission type: {transmissionElementType}");

                break;

            default:
                throw new InvalidValueException($"ToImageArray - The supplied array contains an unsupported metadata version number: {metadataVersion}. This component supports metadata version 1.");
            }

            // Validate the metadata
            if (imageElementType == ImageArrayElementTypes.Unknown)
            {
                throw new InvalidValueException("ToImageArray - ImageArrayElementType is 0, meaning ImageArrayElementTypes.Unknown");
            }
            if (imageElementType > Enum.GetValues(typeof(ImageArrayElementTypes)).Cast <ImageArrayElementTypes>().Max())
            {
                throw new InvalidValueException($"ToImageArray - The ImageArrayElementType value {((int)imageElementType)} is outside the valid range 0 to {Enum.GetValues(typeof(ImageArrayElementTypes)).Cast<ImageArrayElementTypes>().Max()}");
            }
            if (transmissionElementType == ImageArrayElementTypes.Unknown)
            {
                throw new InvalidValueException("ToImageArray - The TransmissionElementType is 0, meaning ImageArrayElementTypes.Unknown");
            }

            // Convert the returned byte[] into the form that the client is expecting
            if ((imageElementType == ImageArrayElementTypes.Int32) & (transmissionElementType == ImageArrayElementTypes.Int16)) // Handle the special case where Int32 has been converted to Int16 for transmission
            {
                switch (rank)
                {
                case 2:     // Rank 2
                    Int16[,] short2dArray = new Int16[dimension1, dimension2];
                    Buffer.BlockCopy(imageBytes, dataStart, short2dArray, 0, imageBytes.Length - dataStart);

                    int[,] int2dArray = new int[dimension1, dimension2];
                    Parallel.For(0, short2dArray.GetLength(0), (i) =>
                    {
                        for (int j = 0; j < short2dArray.GetLength(1); j++)
                        {
                            int2dArray[i, j] = short2dArray[i, j];
                        }
                    });
                    return(int2dArray);

                case 3:     // Rank 3
                    Int16[,,] short3dArray = new Int16[dimension1, dimension2, dimension3];
                    Buffer.BlockCopy(imageBytes, dataStart, short3dArray, 0, imageBytes.Length - dataStart);

                    int[,,] int3dArray = new int[dimension1, dimension2, dimension3];
                    Parallel.For(0, short3dArray.GetLength(0), (i) =>
                    {
                        for (int j = 0; j < short3dArray.GetLength(1); j++)
                        {
                            for (int k = 0; k < short3dArray.GetLength(2); k++)
                            {
                                int3dArray[i, j, k] = short3dArray[i, j, k];
                            }
                        }
                    });
                    return(int3dArray);

                default:
                    throw new InvalidValueException($"ToImageArray - Returned array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                }
            }
            else if ((imageElementType == ImageArrayElementTypes.Int32) & (transmissionElementType == ImageArrayElementTypes.UInt16)) // Handle the special case where Int32 values has been converted to UInt16 for transmission
            {
                switch (rank)
                {
                case 2:     // Rank 2
                    UInt16[,] uInt16Array2D = new UInt16[dimension1, dimension2];
                    Buffer.BlockCopy(imageBytes, dataStart, uInt16Array2D, 0, imageBytes.Length - dataStart);

                    int[,] int2dArray = new int[dimension1, dimension2];
                    Parallel.For(0, uInt16Array2D.GetLength(0), (i) =>
                    {
                        for (int j = 0; j < uInt16Array2D.GetLength(1); j++)
                        {
                            int2dArray[i, j] = uInt16Array2D[i, j];
                        }
                    });
                    return(int2dArray);

                case 3:     // Rank 3
                    UInt16[,,] uInt16Array3D = new UInt16[dimension1, dimension2, dimension3];
                    Buffer.BlockCopy(imageBytes, dataStart, uInt16Array3D, 0, imageBytes.Length - dataStart);

                    int[,,] int3dArray = new int[dimension1, dimension2, dimension3];
                    Parallel.For(0, uInt16Array3D.GetLength(0), (i) =>
                    {
                        for (int j = 0; j < uInt16Array3D.GetLength(1); j++)
                        {
                            for (int k = 0; k < uInt16Array3D.GetLength(2); k++)
                            {
                                int3dArray[i, j, k] = uInt16Array3D[i, j, k];
                            }
                        }
                    });
                    return(int3dArray);

                default:
                    throw new InvalidValueException($"ToImageArray - Returned array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                }
            }
            else // Handle all other cases where the expected array type and the transmitted array type are the same
            {
                if (imageElementType == transmissionElementType) // Required and transmitted array element types are the same
                {
                    switch (imageElementType)
                    {
                    case ImageArrayElementTypes.Byte:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            byte[,] byte2dArray = new byte[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, byte2dArray, 0, imageBytes.Length - dataStart);
                            return(byte2dArray);

                        case 3:         // Rank 3
                            byte[,,] byte3dArray = new byte[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, byte3dArray, 0, imageBytes.Length - dataStart);
                            return(byte3dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned byte array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.Int16:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            short[,] short2dArray = new short[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, short2dArray, 0, imageBytes.Length - dataStart);
                            return(short2dArray);

                        case 3:         // Rank 3
                            short[,,] short3dArray = new short[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, short3dArray, 0, imageBytes.Length - dataStart);
                            return(short3dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned Int16 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.UInt16:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            UInt16[,] uInt16Array2D = new UInt16[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, uInt16Array2D, 0, imageBytes.Length - dataStart);
                            return(uInt16Array2D);

                        case 3:         // Rank 3
                            UInt16[,,] uInt16Array3D = new UInt16[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, uInt16Array3D, 0, imageBytes.Length - dataStart);
                            return(uInt16Array3D);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned UInt16 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.Int32:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            int[,] int2dArray = new int[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, int2dArray, 0, imageBytes.Length - dataStart);
                            return(int2dArray);

                        case 3:         // Rank 3
                            int[,,] int3dArray = new int[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, int3dArray, 0, imageBytes.Length - dataStart);
                            return(int3dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned Int32 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.UInt32:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            UInt32[,] uInt32Array2D = new UInt32[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, uInt32Array2D, 0, imageBytes.Length - dataStart);
                            return(uInt32Array2D);

                        case 3:         // Rank 3
                            UInt32[,,] uInt32Array3D = new UInt32[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, uInt32Array3D, 0, imageBytes.Length - dataStart);
                            return(uInt32Array3D);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned UInt32 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.Int64:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            Int64[,] int642dArray = new Int64[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, int642dArray, 0, imageBytes.Length - dataStart);
                            return(int642dArray);

                        case 3:         // Rank 3
                            Int64[,,] int643dArray = new Int64[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, int643dArray, 0, imageBytes.Length - dataStart);
                            return(int643dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned Int64 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.Single:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            Single[,] single2dArray = new Single[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, single2dArray, 0, imageBytes.Length - dataStart);
                            return(single2dArray);

                        case 3:         // Rank 3
                            Single[,,] single3dArray = new Single[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, single3dArray, 0, imageBytes.Length - dataStart);
                            return(single3dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned Int64 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.Double:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            Double[,] double2dArray = new Double[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, double2dArray, 0, imageBytes.Length - dataStart);
                            return(double2dArray);

                        case 3:         // Rank 3
                            Double[,,] double3dArray = new Double[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, double3dArray, 0, imageBytes.Length - dataStart);
                            return(double3dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned Int64 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    case ImageArrayElementTypes.Decimal:
                        switch (rank)
                        {
                        case 2:         // Rank 2
                            Decimal[,] decimal2dArray = new Decimal[dimension1, dimension2];
                            Buffer.BlockCopy(imageBytes, dataStart, decimal2dArray, 0, imageBytes.Length - dataStart);
                            return(decimal2dArray);

                        case 3:         // Rank 3
                            Decimal[,,] decimal3dArray = new Decimal[dimension1, dimension2, dimension3];
                            Buffer.BlockCopy(imageBytes, dataStart, decimal3dArray, 0, imageBytes.Length - dataStart);
                            return(decimal3dArray);

                        default:
                            throw new InvalidValueException($"ToImageArray - Returned Int64 array cannot be handled because it does not have a rank of 2 or 3. Returned array rank:{rank}.");
                        }

                    default:
                        throw new InvalidValueException($"ToImageArray - The device has returned an unsupported image array element type: {imageElementType}.");
                    }
                }
                else // An unsupported combination of array element types has been returned
                {
                    throw new InvalidValueException($"ToImageArray - The device has returned an unsupported combination of Output type: {imageElementType} and Transmission type: {transmissionElementType}.");
                }
            }
        }
コード例 #5
0
        /// <summary>
        /// Alpaca Extension - Convert the array or error message to a byte array for transmission to a client
        /// </summary>
        /// <param name="imageArray">The 2D or 3D source image array. (Ignored when returning an error.)</param>
        /// <param name="metadataVersion">Metadata version to use (Currently 1).</param>
        /// <param name="clientTransactionID">Client's transaction ID.</param>
        /// <param name="serverTransactionID">Device's transaction ID.</param>
        /// <param name="errorNumber">Error number. 0 for success, non-zero for an error.</param>
        /// <param name="errorMessage">Error message. Empty string for success, error message for an error.</param>
        /// <returns>Byte array prefixed with array metadata.</returns>
        /// <exception cref="InvalidValueException">If only one of the error number and error message indicates an error.</exception>
        /// <exception cref="InvalidValueException">Image array is null for a successful transaction or the array rank is <2 or >3 or the array is of type object</exception>
        /// <exception cref="InvalidValueException">The array element type is not supported.</exception>
        public static byte[] ToByteArray(this Array imageArray, int metadataVersion, uint clientTransactionID, uint serverTransactionID, AlpacaErrors errorNumber, string errorMessage)
        {
            int transmissionElementSize; // Managed size of transmitted elements

            // Handle error conditions
            if ((errorNumber != 0) | (!string.IsNullOrWhiteSpace(errorMessage)))
            {
                // Validate error parameters
                if ((errorNumber == 0) & (!string.IsNullOrWhiteSpace(errorMessage)))
                {
                    throw new InvalidValueException($"ToByteArray - Error number is {errorNumber} but an error message has been supplied: '{errorMessage}'");
                }
                if ((errorNumber != 0) & (string.IsNullOrWhiteSpace(errorMessage)))
                {
                    throw new InvalidValueException($"ToByteArray - Error number is {errorNumber} but no error message has been supplied: '{errorMessage}'");
                }

                // Handle metadata versions
                switch (metadataVersion)
                {
                case 1:
                    byte[]          errorMessageBytes = Encoding.UTF8.GetBytes(errorMessage);
                    ArrayMetadataV1 metadataVersion1  = new ArrayMetadataV1();
                    metadataVersion1.ErrorNumber = errorNumber;

                    byte[] metadataBytes = metadataVersion1.ToByteArray();
                    byte[] returnBytes   = new byte[metadataBytes.Length + errorMessageBytes.Length];
                    Array.Copy(metadataBytes, returnBytes, metadataBytes.Length);
                    Array.Copy(errorMessageBytes, 0, returnBytes, metadataBytes.Length, errorMessageBytes.Length);
                    return(returnBytes);

                default:
                    throw new InvalidValueException($"Unsupported metadata version: {metadataVersion}");
                }
            }

            // At this point we have a successful transaction so validate the incoming array
            if (imageArray is null)
            {
                throw new InvalidValueException("ToByteArray - Supplied array is null.");
            }
            if ((imageArray.Rank < 2) | (imageArray.Rank > 3))
            {
                throw new InvalidValueException($"ToByteArray - Only arrays of rank 2 and 3 are supported. The supplied array has a rank of {imageArray.Rank}.");
            }

            // We can't handle object arrays so test for this
            string arrayTypeName = imageArray.GetType().Name;

            if (arrayTypeName.ToLowerInvariant().Contains("object"))
            {
                throw new InvalidValueException($"ToByteArray - Object arrays of type {arrayTypeName} are not supported.");
            }

            // Set the array type
            ImageArrayElementTypes intendedElementType     = ImageArrayElementTypes.Unknown;
            ImageArrayElementTypes transmissionElementType = ImageArrayElementTypes.Unknown;

            // Get the type code of the array elements
            TypeCode arrayElementTypeCode = Type.GetTypeCode(imageArray.GetType().GetElementType());

            // Set the element type of the intended array and default the transmission element type to be the same as the intended type
            switch (arrayElementTypeCode)
            {
            case TypeCode.Byte:
                intendedElementType     = ImageArrayElementTypes.Byte;
                transmissionElementType = ImageArrayElementTypes.Byte;
                transmissionElementSize = 1;
                break;

            case TypeCode.Int16:
                intendedElementType     = ImageArrayElementTypes.Int16;
                transmissionElementType = ImageArrayElementTypes.Int16;
                transmissionElementSize = 2;
                break;

            case TypeCode.Int32:
                intendedElementType     = ImageArrayElementTypes.Int32;
                transmissionElementType = ImageArrayElementTypes.Int32;
                transmissionElementSize = 4;
                break;

            case TypeCode.Int64:
                intendedElementType     = ImageArrayElementTypes.Int64;
                transmissionElementType = ImageArrayElementTypes.Int64;
                transmissionElementSize = 8;
                break;

            case TypeCode.Single:
                intendedElementType     = ImageArrayElementTypes.Single;
                transmissionElementType = ImageArrayElementTypes.Single;
                transmissionElementSize = 4;
                break;

            case TypeCode.Double:
                intendedElementType     = ImageArrayElementTypes.Double;
                transmissionElementType = ImageArrayElementTypes.Double;
                transmissionElementSize = 8;
                break;

            case TypeCode.Decimal:
                intendedElementType     = ImageArrayElementTypes.Decimal;
                transmissionElementType = ImageArrayElementTypes.Decimal;
                transmissionElementSize = 16;
                break;

            default:
                throw new InvalidValueException($"ToByteArray - Received an unsupported return array type: {imageArray.GetType().Name}, with elements of type: {imageArray.GetType().GetElementType().Name}");
            }

            // Special handling for Int32 arrays - see if we can convert them to Int16 or UInt16 arrays
            if (arrayElementTypeCode == TypeCode.Int32)
            {
                // NOTE
                // NOTE - This algorithm uses a UInt16 array to transmit an array with Int16 values because we are only interested in the byte values for this purpose,
                // NOTE - not the arithmetic interpretation of those bytes.
                // NOTE

                bool arrayIsInt16  = true; // Flag indicating whether the supplied array conforms to the Int16 value range -32768 to +32767. Start by assuming success
                bool arrayIsUint16 = true; // Flag indicating whether the supplied array conforms to the UInt16 value range 0 to +65535. Start by assuming success

                // Handle 2D and 3D arrays
                switch (imageArray.Rank)
                {
                case 2:
                    UInt16[,] uInt16Array = new UInt16[imageArray.GetLength(0), imageArray.GetLength(1)];     // Array to hold the 16bit transmission array (either Int16 or UInt16 values)

                    // Get the device's Int32 image array
                    int[,] int2dArray = (int[, ])imageArray;

                    // Parellelise the array copy to improve performance
                    Parallel.For(0, imageArray.GetLength(0), (i) => // Iterate over the slowest changing dimension
                    {
                        Int32 int32ElementValue;                    // Local variable to hold the Int32 element being tested (saves calculating an array offset later in the process)
                        Int16 int16ElementValue;                    // Local variable to hold the Int16 element value (saves calculating an array offset later in the process)
                        UInt16 uInt16ElementValue;                  // Local variable to hold the Unt16 element value (saves calculating an array offset later in the process)
                        bool arrayIsInt16Internal  = true;          // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsInt16 variable.
                        bool arrayIsUint16Internal = true;          // Local variable to hold the UInt16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsInt16 variable.

                        // Iterate over the fastest changing dimension
                        for (int j = 0; j < imageArray.GetLength(1); j++)
                        {
                            // Get the current array element value
                            int32ElementValue = int2dArray[i, j];

                            // Truncate the supplied 4-byte Int32 value to create a 2-byte UInt16 value
                            uInt16ElementValue = (UInt16)int32ElementValue;

                            // Truncate the supplied 4-byte Int32 value to create a 2-byte Int16 value
                            int16ElementValue = (Int16)int32ElementValue;

                            // Store the UInt16 value in the array.
                            uInt16Array[i, j] = uInt16ElementValue;


                            // Compare the Int16 and Int32 values.
                            if (int16ElementValue != int32ElementValue)
                            {
                                arrayIsInt16Internal = false;
                            }

                            // Compare the UInt16 and Int32 values.
                            if (uInt16ElementValue != int32ElementValue)
                            {
                                arrayIsUint16Internal = false;
                            }
                        }

                        // Update the master arrayIsInt16 and arrayIsUint16 variables as the logical AND of the mater and update values.
                        arrayIsInt16  &= arrayIsInt16Internal;
                        arrayIsUint16 &= arrayIsUint16Internal;
                    });

                    // Return the appropriate array if either a UInt16 or Int16 array was provided by the device
                    if (arrayIsUint16)                                           // Supplied array has UInt16 values so return the shorter array in place of the supplied Int32 array
                    {
                        imageArray = uInt16Array;                                // Assign the Int16 array to the imageArray variable in place of the Int32 array
                        transmissionElementType = ImageArrayElementTypes.UInt16; // Flag that the array elements are UInt16
                        transmissionElementSize = sizeof(UInt16);                // Indicate that the transmitted elements are of UInt16 size rather than Int32 size
                    }
                    else if (arrayIsInt16)                                       // Supplied array has Int16 values so return the shorter array in place of the supplied Int32 array
                    {
                        imageArray = uInt16Array;                                // Assign the UInt16 array to the imageArray variable in place of the Int32 array (at the byte level Int32 and UInt32 are equivalent - both consist of two bytes)
                        transmissionElementType = ImageArrayElementTypes.Int16;  // Flag that the array elements are Int16
                        transmissionElementSize = sizeof(Int16);                 // Indicate that the transmitted elements are of UInt16 size rather than Int32 size
                    }
                    else
                    {
                        // No action, continue to use the supplied Int32 array because its values fall outside the Uint16 and Int16 number ranges
                    }
                    break;

                case 3:
                    UInt16[,,] uInt163dArray = new UInt16[imageArray.GetLength(0), imageArray.GetLength(1), imageArray.GetLength(2)];      // Array to hold the 16bit transmission array (either Int16 or UInt16 values)

                    // Get the device's Int32 image array
                    Int32[,,] int3dArray = (Int32[, , ])imageArray;

                    // Parellelise the array copy to improve performance
                    Parallel.For(0, imageArray.GetLength(0), (i) => // Iterate over the slowest changing dimension
                    {
                        bool arrayIsInt16Internal1  = true;         // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayisInt16 variable.
                        bool arrayIsUint16Internal1 = true;         // Local variable to hold the UInt16 status within this particular thread. Used to reduce thread conflict when updating the arrayisInt16 variable.

                        // Iterate over the mid changing dimension
                        for (int j = 0; j < imageArray.GetLength(1); j++)
                        {
                            Int32 int32ElementValue;            // Local variable to hold the Int32 element being tested (saves calculating an array offset later in the process)
                            Int16 int16ElementValue;            // Local variable to hold the Int16 element value (saves calculating an array offset later in the process)
                            UInt16 uInt16ElementValue;          // Local variable to hold the UInt16 element value (saves calculating an array offset later in the process)
                            bool arrayIsInt16Internal2  = true; // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsInt16 variable.
                            bool arrayIsUInt16Internal2 = true; // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsUInt16 variable.

                            // Iterate over the fastest changing dimension
                            for (int k = 0; k < imageArray.GetLength(2); k++)
                            {
                                // Get the current array element value
                                int32ElementValue = int3dArray[i, j, k];

                                // Truncate the supplied 4-byte Int32 value to create a 2-byte UInt16 value
                                uInt16ElementValue = (UInt16)int32ElementValue;

                                // Truncate the supplied 4-byte Int32 value to create a 2-byte Int16 value
                                int16ElementValue = (Int16)int32ElementValue;

                                // Copy the Int32 value to the corresponding Int16 array element.
                                uInt163dArray[i, j, k] = uInt16ElementValue;

                                // Compare the Int16 and Int32 values.
                                if (int16ElementValue != int32ElementValue)
                                {
                                    arrayIsInt16Internal2 = false;                                             // If they are not the same the Int32 value was outside the range of Int16 and arrayIsInt16Internal will be set false
                                }
                                // Compare the UInt16 and Int32 values.
                                if (uInt16ElementValue != int32ElementValue)
                                {
                                    arrayIsUInt16Internal2 = false;
                                }
                            }

                            // Update the arrayIsInt16Internal1 and arrayIsUint16Internal1 variables as the logical AND of the mater and update values.
                            arrayIsInt16Internal1  &= arrayIsInt16Internal2;
                            arrayIsUint16Internal1 &= arrayIsUInt16Internal2;
                        }

                        // Update the master arrayIsInt16 and arrayIsUint16 variables as the logical AND of the mater and update values.
                        arrayIsInt16  &= arrayIsInt16Internal1;
                        arrayIsUint16 &= arrayIsUint16Internal1;
                    });

                    // Return the appropriate array if either a UInt16 or Int16 array was provided by the device
                    if (arrayIsUint16)                                           // Supplied array has UInt16 values so return the shorter array in place of the supplied Int32 array
                    {
                        imageArray = uInt163dArray;                              // Assign the Int16 array to the imageArray variable in place of the Int32 array
                        transmissionElementType = ImageArrayElementTypes.UInt16; // Flag that the array elements are UInt16
                        transmissionElementSize = sizeof(UInt16);                // Indicate that the transmitted elements are of UInt16 size rather than Int32 size
                    }
                    else if (arrayIsInt16)                                       // Supplied array has Int16 values so return the shorter array in place of the supplied Int32 array
                    {
                        imageArray = uInt163dArray;                              // Assign the UInt16 array to the imageArray variable in place of the Int32 array (at the byte level Int32 and UInt32 are equivalent - both consist of two bytes)
                        transmissionElementType = ImageArrayElementTypes.Int16;  // Flag that the array elements are Int16
                        transmissionElementSize = sizeof(Int16);                 // Indicate that the transmitted elements are of UInt16 size rather than Int32 size
                    }
                    else
                    {
                        // No action, continue to use the supplied Int32 array because its values fall outside the Uint16 and Int16 number ranges
                    }
                    break;

                default:
                    throw new InvalidValueException($"ToByteArray - The camera returned an array of rank: {imageArray.Rank}, which is not supported.");
                }
            }

            switch (metadataVersion)
            {
            case 1:
                // Create a version 1 metadata structure
                ArrayMetadataV1 metadataVersion1;
                if (imageArray.Rank == 2)
                {
                    metadataVersion1 = new ArrayMetadataV1(AlpacaErrors.AlpacaNoError, clientTransactionID, serverTransactionID, intendedElementType, transmissionElementType, 2, imageArray.GetLength(0), imageArray.GetLength(1), 0);
                }
                else
                {
                    metadataVersion1 = new ArrayMetadataV1(AlpacaErrors.AlpacaNoError, clientTransactionID, serverTransactionID, intendedElementType, transmissionElementType, 3, imageArray.GetLength(0), imageArray.GetLength(1), imageArray.GetLength(2));
                }

                // Turn the metadata structure into a byte array
                byte[] metadataVersion2Bytes = metadataVersion1.ToByteArray <ArrayMetadataV1>();

                // Create a return array of size equal to the sum of the metadata and image array lengths
                byte[] imageArrayBytesV2 = new byte[imageArray.Length * transmissionElementSize + metadataVersion2Bytes.Length];     // Size the image array bytes as the product of the transmission element size and the number of elements

                // Copy the metadata bytes to the start of the return byte array
                Array.Copy(metadataVersion2Bytes, imageArrayBytesV2, metadataVersion2Bytes.Length);

                // Copy the image array bytes after the metadata
                Buffer.BlockCopy(imageArray, 0, imageArrayBytesV2, metadataVersion2Bytes.Length, imageArray.Length * transmissionElementSize);

                // Return the byte array
                return(imageArrayBytesV2);

            default:
                throw new InvalidValueException($"ToByteArray - Unsupported metadata version: {metadataVersion}");
            }
        }