Github Repos

Sample C. Software that interpolated around focus pixels (each 14-bits) in certain Canon cameras (EOS-M was what I was using).

// need for StructLayout
using int16_t = System.Int16;
using int32_t = System.Int32;
using int64_t = System.Int64;
using int8_t = System.Byte;
using pixelType = System.Byte;
using uint16_t = System.UInt16;
using uint32_t = System.UInt32;
using uint64_t = System.UInt64;
using uint8_t = System.Byte;

namespace FocusPixelFixer
{
	using System;
	using System.Collections;
	using System.Diagnostics;
	using System.IO;
	using System.Runtime.InteropServices;
	using System.Threading.Tasks;

	/// <summary>
	/// The raw helper.
	/// </summary>
	internal static class RAWHelper
	{
		#region Methods

		/// <summary>
		/// The read struct.
		/// </summary>
		/// <param name="buffer">
		/// The buffer.
		/// </param>
		/// <typeparam name="T">
		/// </typeparam>
		/// <returns>
		/// The <see cref="T"/>.
		/// </returns>
		internal static T ReadStruct<T>(this byte[] buffer) where T : struct
		{
			var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
			var result = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
			handle.Free();
			return result;
		}

		#endregion
	}

	/// <summary>
	/// The program.
	/// </summary>
	internal class Program
	{
		// STRUCTS OF RAW

		// END STRUCTS FOR RAW

		// private pixelType[, ,] RGBData = new pixelType[0, 0, 0];  // see above -> using pixelType = System.Byte;
		#region Static Fields

		/// <summary>
		/// The bit reverse table.
		/// </summary>
		public static readonly byte[] BitReverseTable =
			{
				0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 
				0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 
				0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 
				0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 
				0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 
				0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 
				0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 
				0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 
				0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 
				0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 
				0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 
				0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 
				0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 
				0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 
				0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 
				0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 
				0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 
				0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 
				0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 
				0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 
				0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 
				0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 
				0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 
				0xbf, 0x7f, 0xff
			};

		#endregion

		#region Public Methods and Operators

		/// <summary>
		/// The reverse with lookup table.
		/// </summary>
		/// <param name="toReverse">
		/// The to reverse.
		/// </param>
		/// <returns>
		/// The <see cref="byte"/>.
		/// </returns>
		public static byte ReverseWithLookupTable(byte toReverse)
		{
			return BitReverseTable[toReverse];
		}

		#endregion

		// main
		#region Methods

		/// <summary>
		/// The interpolate around focus pixels.
		/// </summary>
		/// <param name="ReaderSource">
		/// The reader source.
		/// </param>
		/// <param name="WriterTarget">
		/// The writer target.
		/// </param>
		/// <param name="iFrameCount">
		/// The i frame count.
		/// </param>
		/// <param name="Footer">
		/// The footer.
		/// </param>
		private static void InterpolateAroundFocusPixels(
			BinaryReader ReaderSource, 
			BinaryWriter WriterTarget, 
			int iFrameCount, 
			raw_footer Footer)
		{
			// *************************
			// LOOP THROUGH FRAMES
			// *************************
			Parallel.For(
				1, 
				iFrameCount, 
				iFrameNumber =>
					{
						var PixelData = new ushort[Footer.yRes, Footer.xRes];
						var FrameBuffer = new byte[Footer.frameSize];

						/* read RAW block into byte[] array */
						ReaderSource.BaseStream.Position = Footer.frameSize * iFrameNumber; // goto frame
						ReaderSource.Read(FrameBuffer, 0, Footer.frameSize); // read into FrameBuffer
						var framebytescopy = ProcessFrame(PixelData, FrameBuffer, Footer);
						WriterTarget.BaseStream.Seek(Footer.frameSize * iFrameNumber, SeekOrigin.Begin);
						WriterTarget.Write(framebytescopy);

						Console.WriteLine("Processed frame " + iFrameNumber + " of " + iFrameCount);
					});

			Console.WriteLine("Done!");
		}

		/// <summary>
		/// The main.
		/// </summary>
		/// <param name="args">
		/// The args.
		/// </param>
		private static void Main(string[] args)
		{
			string FileNameNoDots = null;

			if (args.Length <= 0)
			{
				return;
			}

			// Filename dropped onto exe
			string FileName = args[0];

			if (!File.Exists(FileName))
			{
				return;
			}
			
			FileNameNoDots = FileName.ToLower().Replace(".raw", "_fpfix.raw");

			if (File.Exists(FileNameNoDots))
			{
				File.Delete(FileNameNoDots);
			}

			Console.WriteLine("Please wait, creating copy of RAW file");
			Console.WriteLine("Copying to: " + FileNameNoDots + string.Empty);
			File.Copy(FileName, FileNameNoDots);

			// Assume we're okay at this point, so start converting!

			// Open source File
			BinaryReader ReaderSource;
			ReaderSource = new BinaryReader(File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.Read));

			// Open target File
			var writeStream = File.OpenWrite(FileNameNoDots);
			var WriterTarget = new BinaryWriter(writeStream);

			// Get number of frames to process
			/* file footer data, see g3gg0's MLV viewer for more sophistication */
			raw_footer Footer;
			var headerSize = Marshal.SizeOf(typeof(raw_footer)); // how much to read into "buf"
			var buf = new byte[headerSize]; // will hold the footer data
			ReaderSource.BaseStream.Position = ReaderSource.BaseStream.Length - headerSize; // get us to end of file before footer
			var myread = ReaderSource.Read(buf, 0, headerSize); // no error checking here
			Footer = RAWHelper.ReadStruct<raw_footer>(buf); // read binary data into our footer struct

			ReaderSource.BaseStream.Position = 0; // go back to beginning of file

			Console.WriteLine("Frame width: " + Footer.xRes + string.Empty);
			Console.WriteLine("Frame height: " + Footer.yRes + string.Empty);
			Console.WriteLine("Frame size: " + Footer.frameSize + string.Empty);
			Console.WriteLine("Frame count: " + Footer.frameCount + string.Empty);

			// ********************************
			// Interpolate around Focus Pixels
			// ********************************
			InterpolateAroundFocusPixels(ReaderSource, WriterTarget, Footer.frameCount, Footer);

			// Finish up
		
			ReaderSource.Close();
			WriterTarget.Close();
		}

		// main

		/// <summary>
		/// The process frame.
		/// </summary>
		/// <param name="PixelData">
		/// The pixel data.
		/// </param>
		/// <param name="FrameBuffer">
		/// The frame buffer.
		/// </param>
		/// <param name="Footer">
		/// The footer.
		/// </param>
		/// <returns>
		/// The <see cref="byte[]"/>.
		/// </returns>
		private static unsafe byte[] ProcessFrame(ushort[,] PixelData, byte[] FrameBuffer, raw_footer Footer)
		{
			var framebool = new bool[(Footer.xRes * Footer.yRes * 14)];

			// From g3gg0's MLV project.  Reads RAW frame into ushorts.  I wrote
			// different approach to write them back.
			var pitch = (Footer.xRes * 14) / 8; // for example, 2,240 with a 1,280 width at 14bits width
			var pos = 0; // Footer.frameSize* iFrameNumber;
			fixed (byte* pSrc = FrameBuffer)
			{
				for (var y = 0; y < Footer.yRes; y++)
				{
					for (var x = 0; x < Footer.xRes; x++)
					{
						// dest[y, x] = BitExtract(pSrc, pos, x, bpp);
						var value = 0;
						var src_pos = x * 14 / 16;
						var byteNum = pos + (2 * src_pos);
						var bits_to_left = ((14 * x) - (16 * src_pos)) % 16;

						var shift_right = 16 - 14 - bits_to_left;

						value = (int)pSrc[byteNum] | (((int)pSrc[byteNum + 1]) << 8);

						if (shift_right >= 0)
						{
							value >>= shift_right;
						}
						else
						{
							var val2 = (int)pSrc[byteNum + 2] | (((int)pSrc[byteNum + 3]) << 8);
							value <<= -shift_right;
							value |= val2 >> (16 + shift_right);
						}

						value &= (1 << 14) - 1;
						PixelData[y, x] = (ushort)value;
					}

					pos += pitch; // advance pitch above, or 2,240 bits for 1280x720
				}
			}

			ushort CPInterpolated = 0;

			// Create BitArray from the array.
			var framebits = new BitArray(framebool);

			var xFocusDots = FocusDots.xFocusDots;
			var LRNext = 2;
			var TBNext = 2;
			
			// *******************************************************
			for (var p = 0; p < xFocusDots.Length / 2; p++)
			{
				var x = xFocusDots[p, 0];
				var y = xFocusDots[p, 1];

				if (x <= 2 || y <= 2)
				{
					continue;
				}
				// BOUNDS CHECKING!
				// If we DO NOT have another line of those pixels, like from 720
				// then we need to not look for it, and take current value
				if (Footer.xRes - x < 3)
				{
					LRNext = 0;
				}

				if (Footer.yRes - y < 3)
				{
					TBNext = 0;
				}

				// Let's nail our suspect onto a cross of red pixels 
				var CP = (double)PixelData[y, x]; // Center pixel
				var LP = (double)PixelData[y, x - LRNext]; // Left
				var RP = (double)PixelData[y, x + LRNext]; // Right
				var TP = (double)PixelData[y + TBNext, x]; // Top
				var BP = (double)PixelData[y - TBNext, x]; // Bottom

				var AvgLR = (LP + RP) / 2; // average left right
				var AvgTB = (TP + BP) / 2; // average vertical

				CPInterpolated = 0;

				// If differs by more then 10% left right
				if (Math.Abs((CP / AvgLR) - 1) > 0.05)
				{
					// Remember, we interpolate using the perpindicular line (top/bottom), assuminge this might be a line
					CPInterpolated = (ushort)AvgTB;
				}

				// If differs by more then 10% left right
				if (Math.Abs((CP / AvgTB) - 1) > 0.05)
				{
					// Perpindicular cross line
					CPInterpolated = (ushort)AvgLR;
				}

				// If we don't have a vert or horizontal setting, make it an average of all
				if (CPInterpolated == 0)
				{
					PixelData[y, x] = (ushort)((LP + RP + TP + BP) / 4.0);
				}

				// We found a distortion so blend in...
				if (CPInterpolated > 0)
				{
					PixelData[y, x] = CPInterpolated;
				}
			}
			 
 // if x and y > 2

			// We not have the frame adjusted through it's PixelData[,] representation
			// So now let's write that to a copy of the frame
			var frameBitCount = 0;

			// Replace these bits with each of our 14 bit raw pixel values
			for (var y = 0; y < Footer.yRes; y++)
			{
				for (var x = 0; x < Footer.xRes; x++)
				{
					for (var shift = 13; shift >= 0; shift--)
					{
						framebits[frameBitCount] = 1 == ((PixelData[y,x] >> shift) & 0x1); 
						frameBitCount++;
					}
				}
			}

			var size = framebits.Length / 8 + (framebits.Length % 8 == 0 ? 0 : 1);
			// We now need to write all the bits of our frame into bytes
			var framebytes = new byte[size];
			
			framebits.CopyTo(framebytes, 0);

			// Create copy, because we need to swap bytes

			var framebytescopy = new byte[size];
			for (var fc = 0; fc < framebytes.Length; fc = fc + 2)
			{
				framebytescopy[fc] = ReverseWithLookupTable(framebytes[fc + 1]);
				framebytescopy[fc + 1] = ReverseWithLookupTable(framebytes[fc]);
			}

			return framebytescopy;
		}

		#endregion

		/// <summary>
		/// The raw_footer.
		/// </summary>
		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		private struct raw_footer
		{
			/// <summary>
			/// The magic.
			/// </summary>
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
			public string magic;

			/// <summary>
			/// The x res.
			/// </summary>
			public short xRes;

			/// <summary>
			/// The y res.
			/// </summary>
			public short yRes;

			/// <summary>
			/// The frame size.
			/// </summary>
			public int frameSize;

			/// <summary>
			/// The frame count.
			/// </summary>
			public int frameCount;

			/// <summary>
			/// The frame skip.
			/// </summary>
			public int frameSkip;

			/// <summary>
			/// The source fpsx 1000.
			/// </summary>
			public int sourceFpsx1000;

			/// <summary>
			/// The reserved 3.
			/// </summary>
			public int reserved3;

			/// <summary>
			/// The reserved 4.
			/// </summary>
			public int reserved4;

			/// <summary>
			/// The raw_info.
			/// </summary>
			public raw_info_2 raw_info;
		}

		/// <summary>
		/// The raw_info_2.
		/// </summary>
		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		private struct raw_info_2
		{
			/// <summary>
			/// The api_version.
			/// </summary>
			public int api_version; // increase this when changing the structure

			/// <summary>
			/// The buffer.
			/// </summary>
			public uint buffer; // points to image data

			/// <summary>
			/// The height.
			/// </summary>
			public int height;

			/// <summary>
			/// The width.
			/// </summary>
			public int width;

			/// <summary>
			/// The pitch.
			/// </summary>
			public int pitch;

			/// <summary>
			/// The frame_size.
			/// </summary>
			public int frame_size;

			/// <summary>
			/// The bits_per_pixel.
			/// </summary>
			public int bits_per_pixel; // 14

			/// <summary>
			/// The black_level.
			/// </summary>
			public int black_level; // autodetected

			/// <summary>
			/// The white_level.
			/// </summary>
			public int white_level; // somewhere around 13000 - 16000, varies with camera, settings etc

			/// <summary>
			/// The jpeg.
			/// </summary>
			public raw_info_crop2 jpeg;

			/// <summary>
			/// The active_area.
			/// </summary>
			public raw_info_active_area2 active_area;

			/// <summary>
			/// The exposure_bias.
			/// </summary>
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
			public int[] exposure_bias; // DNG Exposure Bias (idk what's that)

			/// <summary>
			/// The cfa_pattern.
			/// </summary>
			public int cfa_pattern; // stick to 0x02010100 (RGBG) if you can

			/// <summary>
			/// The calibration_illuminant 1.
			/// </summary>
			public int calibration_illuminant1;

			/// <summary>
			/// The color_matrix 1.
			/// </summary>
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)]
			public int[] color_matrix1; // DNG Color Matrix

			/// <summary>
			/// The dynamic_range.
			/// </summary>
			public int dynamic_range; // EV x100, from analyzing black level and noise (very close to DxO)
		}

		/// <summary>
		/// The raw_info_active_area 2.
		/// </summary>
		private struct raw_info_active_area2
		{
			#region Fields

			/// <summary>
			/// The x 1.
			/// </summary>
			public int x1;

			/// <summary>
			/// The x 2.
			/// </summary>
			public int x2;

			/// <summary>
			/// The y 1.
			/// </summary>
			public int y1;

			/// <summary>
			/// The y 2.
			/// </summary>
			public int y2;

			#endregion
		}

		/// <summary>
		/// The raw_info_crop 2.
		/// </summary>
		private struct raw_info_crop2
		{
			#region Fields

			/// <summary>
			/// The height.
			/// </summary>
			public int height; // DNG JPEG size

			/// <summary>
			/// The width.
			/// </summary>
			public int width; // DNG JPEG size

			/// <summary>
			/// The x.
			/// </summary>
			public int x; // DNG JPEG top left corner

			/// <summary>
			/// The y.
			/// </summary>
			public int y; // DNG JPEG top left corner

			#endregion
		}
	} // class
} // namespace

Python code which controls a Sony camera through USB and 2 stepper motors in my DigiTiler invention

# Tkinter python 2.7, or tkinter 3.x
# pip install pyserial

from Tkinter import *
import sys
import glob
import serial
import time

# Defaults
h_step_value_default = "8100"
v_step_value_default = "5500"
h_tiles_default = "7"
v_tiles_default = "7"
timeformove = "1000" # eventually make config option

# instance tkinter
master = Tk()
poll = False
master.title("DigiTiler Control")
sendsteps =  StringVar()
sendstring =  StringVar()
strVersion =  StringVar()

global comPort
global comPortName
global serial_list
global motor_moves_on

# for debugging movements, turn motors off
motor_moves_on = True

comPort = serial.Serial()

comPortName = ""

######## CODE ###########
# we need to put in top so buttons know

running = True

# So we can break out of loop if motors go off course
def start():
    """Enable scanning by setting the global flag to True."""
    global running
    running = True

def stop():
    """Stop scanning by setting the global flag to False."""
    global running
    running = False

def motors_disable():
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    comPort.write('EM,0,0\r'.encode('ascii'))  # disable motors


def h_step_forward():
    #comPortName = ser_list_ports_entry.get()
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    sendsteps = h_step_entry.get()
    print(sendsteps)
    sendstring = 'sm,'+timeformove+','+sendsteps+',0,\r'
    print(sendstring)
    comPort.write(sendstring.encode('ascii'))
    strVersion = comPort.readline()

    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)

    #Update OFFSETS
    offset = int(str(h_offset_entry.get()))
    #print("offset is: "+str(offset))
    hstep = int(h_step_entry.get())
    offset = offset + hstep
    h_offset_entry.delete(0, END)
    h_offset_entry.insert(0,str(offset))
    #serialPort.close()

def h_step_back():
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    sendsteps = h_step_entry.get()
    sendsteps = "-"+sendsteps
    print(sendsteps)
    sendstring = 'sm,' + timeformove + ',' + sendsteps + ',0,\r'
    print(sendstring)
    comPort.write(sendstring.encode('ascii'))
    strVersion = comPort.readline()
    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)

    # Update OFFSETS
    offset = int(str(h_offset_entry.get()))
    # print("offset is: "+str(offset))
    hstep = int(h_step_entry.get()) * -1
    offset = offset + hstep
    h_offset_entry.delete(0, END)
    h_offset_entry.insert(0, str(offset))


def v_step_forward():
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    sendsteps = v_step_entry.get()
    print(sendsteps)
    sendstring = 'sm,' + timeformove + ',0,' + sendsteps + '\r'
    print(sendstring)
    comPort.write(sendstring.encode('ascii'))
    strVersion = comPort.readline()
    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)

    # Update OFFSETS
    offset = int(str(v_offset_entry.get()))
    # print("offset is: "+str(offset))
    vstep = int(v_step_entry.get())
    offset = offset + vstep
    v_offset_entry.delete(0, END)
    v_offset_entry.insert(0, str(offset))

def v_step_back():
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    sendsteps = v_step_entry.get()
    sendsteps = "-"+sendsteps
    print(sendsteps)
    sendstring = 'sm,' + timeformove + ',0,' + sendsteps + '\r'
    print(sendstring)
    comPort.write(sendstring.encode('ascii'))
    strVersion = comPort.readline()
    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)

    # Update OFFSETS
    offset = int(str(v_offset_entry.get()))
    # print("offset is: "+str(offset))
    vstep = int(v_step_entry.get()) *-1
    offset = offset + vstep
    v_offset_entry.delete(0, END)
    v_offset_entry.insert(0, str(offset))

def h_step_default():
    pass

def v_step_default():
    pass

#Offsets
def h_offset_center():

    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    # set opposite to go back; that is *-1
    hmove = int(h_offset_entry.get()) * -1
    sendsteps = str(hmove)
    print(sendsteps)
    sendstring = 'sm,' + timeformove + ',' + sendsteps + ',0,\r'
    print(sendstring)
    comPort.write(sendstring.encode('ascii'))
    strVersion = comPort.readline()

    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)

    # Update OFFSETS
    h_offset_entry.delete(0, END)
    h_offset_entry.insert(0, str("0"))
    # serialPort.close()

def h_offset_fullleft():
    pass

def h_offset_fullright():
    pass

def v_offset_center():
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    # set opposite to go back; that is *-1
    vmove = int(v_offset_entry.get()) * -1
    sendsteps = str(vmove)
    print(sendsteps)
    sendstring = 'sm,' + timeformove + ',0,' + sendsteps + '\r'
    print(sendstring)
    comPort.write(sendstring.encode('ascii'))
    strVersion = comPort.readline()

    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)

    # Update OFFSETS
    v_offset_entry.delete(0, END)
    v_offset_entry.insert(0, str("0"))

def v_offset_top():
    pass

def v_offset_bottom():
    pass

### DATA ###

def save_current_values():
    pass

### SERIEAL ####

def list_serial_ports():
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    serial_list = []

    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            serial_list.append(port)
        except (OSError, serial.SerialException):
            pass
    #ser_list_ports_entry.insert(20,serial_list)
    # Put into optionmenu
    dropdownvariable = StringVar()
    dropdownvariable.set(serial_list[0])

    ser_list_optionmenu = OptionMenu(master, dropdownvariable, *serial_list, command=ser_port_selected)
    ser_list_optionmenu.grid(row=1, column=2, padx=4, pady=4)

    return serial_list

def ser_test_port():
    # we now get through optionmenu
    #comPortName = ser_list_ports_entry.get()
    print("comPortName is: "+comPortName)
    comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
    comPort.write('v\r'.encode('ascii'))
    strVersion = comPort.readline()
    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)
    #serialPort.close()

def shutter_fire():
    time.sleep(1.5)
    # fire shutter
    comCMD = 'S2,24000,5\r'.encode('ascii')
    print(comCMD)
    comPort.write(comCMD)
    time.sleep(.450)
    comCMD = 'S2,12000,5\r'.encode('ascii')
    comPort.write(comCMD)
    strVersion = comPort.readline()
    status_entry.delete(0, END)
    status_entry.insert(50, strVersion)
    time.sleep(.5) # wait half a second before moving

##########################
### RUN ROUTINES TILING
###########################
def run_RLBT():

    shotnumber = 1

    if running==True:

        # Put camera in start position
        # fire first shutter
        # Open PORT
        if comPortName != "":
            comPort = serial.Serial(comPortName, timeout=1.0)  # 1 second timeout!
            global comPort

        time.sleep(3) # wait for board to catchup or first shutter doesn't fire

        # defaults
        hstep = h_step_entry.get()
        vstep = v_step_entry.get()
        htiles = int(h_tiles_entry.get())
        vtiles = int(v_tiles_entry.get())

        # Row, or horizontal movements
        for r in range(1,(htiles+1)):

            # are moving horizontally forward or backwards?
            if r % 2 == 0:  # EVEN row
                # HORIZONTAL
                hmove = str(int(hstep) * -1)  # reverse
            else:  # ODD row
                hmove = hstep  # forward

            # Set depending on odd/even row
            vmove = vstep  # default

            # Now go through vertical tiles (on each row)
            for c in range(1, (vtiles+1)):

                # FIRE SHUTTER always
                if motor_moves_on:
                    shutter_fire()

                print("Shot: " + str(shotnumber))
                shotnumber += 1

                # ONLY MOVE CAMERA if not at end, if at end
                # we'll leave that outside loop

                if c != vtiles:
                    print('Tile '+str(r)+','+str(c)+' H-Step='+str(hmove))
                    # Move
                    sendstring = 'sm,' + timeformove + ',' + str(hmove) + ',0,\r'

                    if motor_moves_on:
                        time.sleep(.250) # wait quarter of a second
                        comPort.write(sendstring.encode('ascii'))
                        strVersion = comPort.readline()
                        status_entry.delete(0, END)
                        status_entry.insert(50, strVersion)

                # check for stop in VERTICAL LOOP
                master.update()

                if not running:
                    break

            # We're at the end of a row, so move up, but only if not at end
            if r != htiles:
                print('Tile ' + str(r) + ' V-Step=' + str(vmove))
                # Move
                sendstring = 'sm,' + timeformove + ',0,' + str(vmove) + '\r'
                if motor_moves_on:
                    time.sleep(.250)  # wait quarter of a second
                    comPort.write(sendstring.encode('ascii'))
                    strVersion = comPort.readline()
                    status_entry.delete(0, END)
                    status_entry.insert(50, strVersion)

            # check for stop HORIZONTAL
            master.update()
            if (running == False):
                break

        status_entry.delete(0,END)
        status_entry.insert(0,"Tiling done")

        comPort.close()

def ser_port_selected(selected_opt):
    global comPortName
    comPortName = selected_opt
    print("comPortName is now: "+comPortName)

def quit():
    running = False
    if comPort is not None:
        try:
            comPort.close()
        except serial.SerialException:
            pass

    master.destroy()


#########################

onrow = 1

# First Row Group description
ser_list_ports_btn = Button(master, text='Get Serial Ports', command=list_serial_ports)
ser_test_port_btn = Button(master, text='Test Port', command=ser_test_port)

ser_list_ports_btn.grid(row=onrow, column=1,padx=4,pady=4)
# optionmenu in column 2 !
ser_test_port_btn.grid(row=onrow, column=3,padx=4,pady=4)

# List Serial Ports
onrow+=1

onrow+=1

# Group description
g1_lbl = Label(master, text="Tile distance values for steppers")
g1_lbl.grid(row=onrow, column=2)
onrow+=1

# H-Step
h_step_lbl = Label(master, text="H Step")
h_step_entry = Entry(master)
h_step_entry.insert(10,h_step_value_default)
h_step_btn_forward = Button(master, text='Forward', command=h_step_forward)
h_step_btn_back = Button(master, text='Back', command=h_step_back)
h_step_btn_default = Button(master, text='Default', command=h_step_default)

h_step_lbl.grid(row=onrow,column=1,padx=4,pady=4)
h_step_entry.grid(row=onrow, column=2,padx=4,pady=4)
h_step_btn_forward.grid(row=onrow, column=3,padx=4,pady=4)
h_step_btn_back.grid(row=onrow, column=4,padx=4,pady=4)
h_step_btn_default.grid(row=onrow, column=5,padx=4,pady=4)

onrow+=1

# V-Step
v_step_lbl = Label(master, text="V Step")
v_step_entry = Entry(master)
v_step_entry.insert(10,v_step_value_default)
v_step_btn_forward = Button(master, text='Up', command=v_step_forward)
v_step_btn_back = Button(master, text='Down', command=v_step_back)
v_step_btn_default = Button(master, text='Default', command=v_step_default)

v_step_lbl.grid(row=onrow,column=1,padx=4,pady=4)
v_step_entry.grid(row=onrow, column=2,padx=4,pady=4)
v_step_btn_forward.grid(row=onrow, column=3,padx=4,pady=4)
v_step_btn_back.grid(row=onrow, column=4,padx=4,pady=4)
v_step_btn_default.grid(row=onrow, column=5,padx=4,pady=4)

onrow+=1

# Second Row Group description
g2_lbl = Label(master, text="Widths (H and V) in Tiles")
g2_lbl.grid(row=onrow, column=2)
onrow+=1

# H Tiles
h_tiles_lbl = Label(master, text="H Tiles")
h_tiles_entry = Entry(master)
h_tiles_entry.insert(2,h_tiles_default)

h_tiles_lbl.grid(row=onrow,column=1,padx=4,pady=4)
h_tiles_entry.grid(row=onrow, column=2,padx=4,pady=4)

onrow+=1

# V Tiles
v_tiles_lbl = Label(master, text="V Tiles")
v_tiles_entry = Entry(master)
v_tiles_entry.insert(2,v_tiles_default)

v_tiles_lbl.grid(row=onrow,column=1,padx=4,pady=4)
v_tiles_entry.grid(row=onrow, column=2,padx=4,pady=4)

onrow+=1

############## OFFSETS ####################
# Group description
g1_lbl = Label(master, text="Current Offsets")
g1_lbl.grid(row=onrow, column=2)
onrow+=1

# H-Offset
h_offset_lbl = Label(master, text="H Offset")
h_offset_entry = Entry(master)
h_offset_entry.insert(0,"0")
h_offset_btn_center = Button(master, text='Center', command=h_offset_center)
h_offset_btn_fullleft = Button(master, text='<< Left', command=h_offset_fullleft)
h_offset_btn_fullright = Button(master, text='Right >>', command=h_offset_fullright)

h_offset_lbl.grid(row=onrow,column=1,padx=4,pady=4)
h_offset_entry.grid(row=onrow, column=2,padx=4,pady=4)
h_offset_btn_center.grid(row=onrow, column=3,padx=4,pady=4)
h_offset_btn_fullleft.grid(row=onrow, column=4,padx=4,pady=4)
h_offset_btn_fullright.grid(row=onrow, column=5,padx=4,pady=4)

onrow+=1

# V-Offset
v_offset_lbl = Label(master, text="V Offset")
v_offset_entry = Entry(master)
v_offset_entry.insert(0,"0")
v_offset_btn_center = Button(master, text='Center', command=v_offset_center)
v_offset_btn_top = Button(master, text='<< Top', command=v_offset_top)
v_offset_btn_bottom = Button(master, text='Bottom >>', command=v_offset_bottom)

v_offset_lbl.grid(row=onrow,column=1,padx=4,pady=4)
v_offset_entry.grid(row=onrow, column=2,padx=4,pady=4)
v_offset_btn_center.grid(row=onrow, column=3,padx=4,pady=4)
v_offset_btn_top.grid(row=onrow, column=4,padx=4,pady=4)
v_offset_btn_bottom.grid(row=onrow, column=5,padx=4,pady=4)

onrow+=1

### RUN ROUTINES ####
run_RLBT_btn = Button(master, text='Run R-L-B-T', command=run_RLBT)
run_LRTB_btn = Button(master, text='Run L-R-T-B')
stop_btn = Button(master,text='STOP!',command=stop)
motors_disable_btn = Button(master,text='Motors Disable',command=motors_disable)
shutter_fire_btn = Button(master,text='Fire Shutter',command=shutter_fire)

run_RLBT_btn.grid(row=onrow,column=1,padx=4,pady=4,sticky=W+E+N+S)
run_LRTB_btn.grid(row=onrow,column=2,padx=4,pady=4,sticky=W+E+N+S)
stop_btn.grid(row=onrow,column=3,padx=4,pady=4,sticky=W+E+N+S)
motors_disable_btn.grid(row=onrow,column=4,padx=4,pady=4,sticky=W+E+N+S)
shutter_fire_btn.grid(row=onrow,column=5,padx=4,pady=4,sticky=W+E+N+S)

onrow+=1

### STATUS ###
status_entry = Entry(master)
status_entry.grid(row=onrow,column=0,columnspan=6,padx=4,pady=4,sticky=W+E+N+S)
status_entry.insert(50,"Status...")

onrow+=1

# Final Buttons
# 3rd Row, column 0 and 1

#Button(master, text='Save Current Values', command=save_current_values).grid(row=onrow, column=1, sticky=W, padx=10, pady=4)
Button(master, text='Quit', command=quit).grid(row=onrow, column=1, sticky=W, padx=10, pady=4)

# Pack


mainloop( )