public void Initialize(Configuration config)
        {
            UpdateStatus(HardwareStatus.Initializing);

            asyncControl = new Thread(() =>
            {
                try
                {
                    counter = new CounterController(config.CounterPort, 250000);
                    counter.OnLogMessage += OnLogMessage;
                }
                catch (Exception)
                {
                    OnLogMessage("Failed to open counter on " + config.CounterPort);
                    UpdateStatus(HardwareStatus.Error);
                    return;
                }

                try
                {
                    printer = new PrinterController(config.PrinterPort, 250000);
                    printer.OnLogMessage += OnLogMessage;
                }
                catch (Exception)
                {
                    OnLogMessage("Failed to open printer on " + config.PrinterPort);
                    UpdateStatus(HardwareStatus.Error);
                    CloseSerialConnections();

                    return;
                }

                UpdateStatus(HardwareStatus.Idle);
            });

            asyncControl.Start();
        }
        public void StartScan(Configuration config)
        {
            if (asyncControl.IsAlive)
            {
                OnLogMessage("Invalid thread state. Aborting scan.");
                return;
            }

            if (Status == HardwareStatus.Error)
            {
                OnLogMessage("Hardware status is error. Aborting scan.");
                return;
            }

            cancelScan = false;

            asyncControl = new Thread(() =>
            {
                try
                {
                    var startColumn = (int)((config.Origin.X - config.RowOverscan) * config.XStepsPerMM);
                    var endColumn = (int)((config.Origin.X + config.Size.Width + config.RowOverscan) * config.XStepsPerMM);
                    var overscanCols = (int)(config.Origin.X * config.XStepsPerMM) - startColumn;
                    if (startColumn < 0)
                    {
                        OnLogMessage("Overscan region extends outside the scannable region. Inset the scan area or reduce the overscan.");
                        return;
                    }

                    OnLogMessage(string.Format("Counter columns {0} to {1}, overscan {2}", startColumn, endColumn, overscanCols));
                    var rows = (int)Math.Ceiling(config.Size.Height / config.RowStride);
                    var columns = endColumn - startColumn + 1;


                    var dimensions = new[] { columns, rows, 2 };
                    using (var fits = new MiniFits(config.DataFile, dimensions, MiniFitsType.U16, true))
                    {
                        fits.WriteKey("OPERATOR", config.Operator, null);
                        fits.WriteKey("DATETIME", DateTime.Now.ToString("G"), "Time at the start of the scan");
                        fits.WriteKey("DESCRIPT", config.Description, null);
                        fits.WriteKey("SCANAREA", string.Format("{0:F3} {1:F3} {2:F3} {3:F3}", config.Origin.X, config.Origin.Y, config.Size.Width, config.Size.Height), "The requested scan area (X Y W H in mm)");
                        fits.WriteKey("ROWSTART", startColumn, "The step count of the first data column");
                        fits.WriteKey("ROWSTRID", config.RowStride, 6, "The step size between rows (in mm)");
                        fits.WriteKey("ROWSPEED", config.ScanSpeed, 4, "The row scan speed (in mm/minute)");
                        fits.WriteKey("OVERSCAN", overscanCols, 6, "The row overscan before/after the horizontal scan area (in columns).");
                        fits.WriteKey("COLSTRID", config.ColumnStride, 6, "The step size between columns (in mm)");

                        fits.WriteKey("DARKCNTA", config.PrimaryDarkCounts, 4, "Mean dark counts per unbinned pixel for detector A");
                        fits.WriteKey("DARKCNTB", config.SecondaryDarkCounts, 4, "Mean dark counts per unbinned pixel for detector B");

                        var data = new ushort[rows * columns * 2];

                        OnLogMessage("Moving to start position.");
                        UpdateStatus(HardwareStatus.Homing);

                        printer.MoveToHome();
                        counter.ZeroPositionCounter();

                        printer.MoveToPosition(0, config.Origin.Y, config.FocusHeight, config.SlewSpeed);
                        printer.MoveToPosition(config.Origin.X - config.RowOverscan, config.Origin.Y, config.FocusHeight, config.SlewSpeed);

                        UpdateStatus(HardwareStatus.Scanning);
                        OnLogMessage("Starting scan.");


                        double TotalTime=1;

                        // Scan rows
                        for (var i = 0; i < rows; i+=1)
                        {
                            var StartTime = DateTime.Now;

                            if (cancelScan)
                                break;
                            OnLogMessage("Starting read at: " + counter.ReadPositionCounter().ToString());

                            OnLogMessage(string.Format("Scanning row {0} of {1} ({2:F0}%)", i + 1, rows, i * 100 / rows));
                            counter.Start();

                            // Collect photons while scanning left -> right
                            // Scanning in one direction only ensures that the same optical configuration is used for each row
                            printer.MoveDeltaX(config.Size.Width + 2 * config.RowOverscan, config.ScanSpeed);
                            counter.Stop();

                            // Read and save data to file
                            var primary = counter.ReadHistogram(CounterChannel.Primary, startColumn, endColumn);
                            var secondary = counter.ReadHistogram(CounterChannel.Secondary, startColumn, endColumn);

                            Array.Copy(primary, 0, data, i * columns, columns);
                            Array.Copy(secondary, 0, data, (rows + i) * columns, columns);

                            //OnLogMessage(string.Join(" ", primary));

                            fits.WriteImageData(data);

                            counter.ResetHistogram();
                            UpdateStatus(HardwareStatus.Scanning, i * 100m / rows);

                            
                            // Return to the start of the next row
                            printer.MoveDeltaX(-(config.Size.Width + 2 * config.RowOverscan), config.SlewSpeed);
                            printer.MoveDeltaY(config.RowStride, config.ScanSpeed);
                            
                            /**
                            //Scan on the way back too
                            OnLogMessage("Starting at " + counter.ReadPositionCounter().ToString());
                            printer.MoveDeltaY(config.RowStride, config.ScanSpeed);
                            counter.Start();
                            printer.MoveDeltaX(-1 * (config.Size.Width + (2 * config.RowOverscan)), config.ScanSpeed);
                            counter.Stop();

                            primary = counter.ReadHistogram(CounterChannel.Primary, startColumn, endColumn);
                            secondary = counter.ReadHistogram(CounterChannel.Secondary, startColumn, endColumn);

                            counter.ResetHistogram();

                            Array.Copy(primary, 0, data, (i + 1) * columns, columns);
                            Array.Copy(secondary, 0, data, (rows + i + 1) * columns, columns);
                            fits.WriteImageData(data);
                            */

                            //Calculate total, average and finish times
                            TimeSpan RowTime = DateTime.Now - StartTime;
                            TotalTime += RowTime.TotalSeconds;
                            var AverageRowTime = TotalTime / (i+1);
                            DateTime ECT=DateTime.Now;
                            ECT=ECT.AddSeconds(AverageRowTime*(rows-i));         
                            OnLogMessage(string.Format("Time elapsed: {0}s, Average Time per row: {1}s", TotalTime.ToString("f0"), AverageRowTime.ToString("f0")));
                            OnLogMessage("Estimated Completion: " + ECT.ToLongTimeString());

                        }

                        OnLogMessage("Scan complete.");
                        UpdateStatus(HardwareStatus.Homing);
                        printer.MoveToHome();

                        UpdateStatus(HardwareStatus.Idle);
                    }
                }
                catch (PrinterException e)
                {
                    OnLogMessage("Printer error: " + e.Message);
                    UpdateStatus(HardwareStatus.Error);
                    CloseSerialConnections();
                }
                catch (CounterException e)
                {
                    OnLogMessage("Counter error: " + e.Message);
                    UpdateStatus(HardwareStatus.Error);

                    // TODO: Send emergency stop (M112)
                    CloseSerialConnections();
                }
                catch (MiniFitsException e)
                {
                    OnLogMessage("File error: " + e.Message);
                    UpdateStatus(HardwareStatus.Error);

                    // TODO: Turn off laser and home printer
                    // TODO: Send emergency stop (M112)
                    CloseSerialConnections();
                }
            });

            asyncControl.Start();
        }