/// <summary> /// Initialise the ArrayMetadataV1 structure /// </summary> /// <param name="errorNumber">Transaction error number.</param> /// <param name="clientTransactionID">Client's transaction ID</param> /// <param name="serverTransactionID">Device's transaction ID</param> /// <param name="imageElementType">Intended element type of the resultant array.</param> /// <param name="transmissionElementType">Element type actually sent.</param> /// <param name="arrayRank">Rank of the arrayresultant array.</param> /// <param name="arrayDimension1">Size of the first dimension of the resultant array (array[Dimension1, Dimension2, Dimension3]).</param> /// <param name="arrayDimension1">Size of the second dimension of the resultant array (array[Dimension1, Dimension2, Dimension3]).</param> /// <param name="arrayDimension2">Size of the third dimension of the resultant array (array[Dimension1, Dimension2, Dimension3]).</param> public ArrayMetadataV1(AlpacaErrors errorNumber, uint clientTransactionID, uint serverTransactionID, ImageArrayElementTypes imageElementType, ImageArrayElementTypes transmissionElementType, int arrayRank, int arrayDimension1, int arrayDimension2, int arrayDimension3) { MetadataVersion = 1; this.ErrorNumber = errorNumber; this.ClientTransactionID = clientTransactionID; this.ServerTransactionID = serverTransactionID; DataStart = AlpacaTools.ARRAY_METADATAV1_LENGTH; this.ImageElementType = imageElementType; this.TransmissionElementType = transmissionElementType; this.Rank = arrayRank; this.Dimension1 = arrayDimension1; this.Dimension2 = arrayDimension2; this.Dimension3 = arrayDimension3; }
/// <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}"); } }