using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Reflection; using System.Windows.Forms; using Flir.Atlas.Image; using Flir.Atlas.Live.Device; using Flir.Atlas.Live.Discovery; using Newtonsoft.Json; using ThermoCamUtils; namespace ThermalImageStreamerDemo { public partial class MainForm : Form { #region Private Fields private const string FileFilter = "IR Image Files(*.jpg;*.img;*.seq;*.fcf)|*.jpg;*.img;*.seq;*.fcf|All files (*.*)|*.*"; private readonly Timer _timerRefreshUi = new Timer(); #if false private Camera IRCam.ThermoCamera; #endif private RecorderForm _recorder; #endregion Private Fields #region Protected Fields protected const string colorPath = "images\\Color"; protected const string confFileName = "ThermoConf.json"; protected const string currImgFile = "ThermalData.jpg"; protected const string thermPath = "images\\Therm"; /// /// Ultimo range di temperature osservato /// protected Range _lastTempRange = new Range(-999, 5000); protected int currPoint = -1; /// /// Contenitore oggetti Image x FlirCam /// protected ImageData ImgData = new ImageData(); #if false protected double lastCalcTemp = 0; protected Point ImgData.lastPoint = new Point(); protected double lastReadTemp = 0; #endif protected double msLastCam = 0; protected double msLastCol = 0; protected double msLastMsr = 0; protected double msLastPts = 0; protected double msLastTra = 0; protected Point refTempPoint = new Point(); protected Stopwatch sw = new Stopwatch(); #endregion Protected Fields #region Public Fields // Config File Names public static readonly string BASE_PATH = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); #endregion Public Fields #region Public Constructors public MainForm() { InitializeComponent(); _timerRefreshUi.Interval = 100; _timerRefreshUi.Tick += _timerRefreshUi_Tick; // cerco se ho un set di parametri già impostati... tryReloadConf(); // calcolo i currConf.destPoints updateDestPt(); } #endregion Public Constructors #region Private Properties private bool IsDirty { get; set; } private bool liveView { get { return checkLive.Checked; } set { checkLive.Checked = value; } } #endregion Private Properties #region Protected Properties protected string confPath { get { // se esiste il file... path assoluto string answ = confFileName; if (!Path.IsPathRooted(answ)) { answ = BASE_PATH + "\\" + confFileName; } return answ; } } protected ThermoCamConf currConf { get { return ImgData.currConf; } set { ImgData.currConf = value; } } protected int dimX { get { int answ = 100; int.TryParse(txtAB.Text, out answ); currConf.TargetSize.X = answ; return answ; } set { currConf.TargetSize.X = value; txtAB.Text = $"{value}"; } } protected int dimY { get { int answ = 100; int.TryParse(txtBC.Text, out answ); currConf.TargetSize.Y = answ; return answ; } set { currConf.TargetSize.Y = value; txtBC.Text = $"{value}"; } } /// /// Ultimo range di temperature osservato /// protected Range lastTempRange { get { return _lastTempRange; } set { _lastTempRange = value; lblMinTemp.Text = $"{value.Minimum:N2}"; lblMaxTemp.Text = $"{value.Maximum:N2}"; } } protected double maxScale { get { double answ = 1000; double.TryParse(txtMaxScale.Text, out answ); currConf.TargetRange.Max = answ; return answ; } set { currConf.TargetRange.Max = value; txtMaxScale.Text = $"{value:N0}"; } } protected double minScale { get { double answ = 0; double.TryParse(txtMinScale.Text, out answ); currConf.TargetRange.Min = answ; return answ; } set { currConf.TargetRange.Min = value; txtMinScale.Text = $"{value:N0}"; } } protected bool pointSetup { get { return chkPointSetup.Checked; } set { chkPointSetup.Checked = value; } } protected bool readTemp { get { return chkReadTemp.Checked; } set { chkReadTemp.Checked = value; } } protected bool saveEnabled { get { return chkSaveAll.Checked; } set { chkSaveAll.Checked = value; } } protected bool showOrigBig { get { return chkShowOrig.Checked; } set { chkShowOrig.Checked = value; } } #endregion Protected Properties #region Private Methods private void _recorder_SelectedFileMouseDoubleClick(object sender, SelectedFileEventArgs e) { var playback = new PlaybackForm(e.FilePath); playback.Show(); } private void _timerRefreshUi_Tick(object sender, EventArgs e) { // se è in live view --> continuo if (liveView) { takePicture(saveEnabled); processImage(); refreshDisplay(); } // avanzo prog bar progBar.Increment(3); if (progBar.Value >= progBar.Maximum) { progBar.Value = 0; } } private void btnLoad_Click(object sender, EventArgs e) { string dirPath = $"{BASE_PATH}\\{thermPath}"; string filePath = $"{dirPath}\\{currImgFile}"; if (File.Exists(filePath)) { try { ImgData.Thermal = new ThermalImageFile(filePath); ImgData.Thermal.TemperatureUnit = TemperatureUnit.Celsius; ImgData.Thermal.Scale.IsAutoAdjustEnabled = true; // carico DIRETTAMENTE da file... ImgData.Origin = (Bitmap)Image.FromFile(filePath); ImgData.Decorated = (Bitmap)Image.FromFile(filePath); // aggiorno visualizzazione processImage(); refreshDisplay(); } catch { } } } private void btnReset_Click(object sender, EventArgs e) { pointSetup = true; currConf.origPoints = new SetPoints(); } private void btnSave_Click(object sender, EventArgs e) { takePicture(true); processImage(); refreshDisplay(); } private void Camera_ConnectionStatusChanged(object sender, Flir.Atlas.Live.ConnectionStatusChangedEventArgs e) { BeginInvoke((Action)(() => toolStripConnectionStatus.Text = e.Status.ToString())); BeginInvoke((Action)(() => cameraToolStripMenuItem.Enabled = e.Status == ConnectionStatus.Connected)); // se si disconnette --> NUOVA richiesta connessione alla CHILD... // TODO FARE!!! } private void checkLive_CheckedChanged(object sender, EventArgs e) { // non serve fare nulla } private void chkPointSetup_CheckedChanged(object sender, EventArgs e) { // deselezione Get Temp if (readTemp) { readTemp = false; } } private void chkReadTemp_CheckedChanged(object sender, EventArgs e) { // deselezione setup punti if (pointSetup) { pointSetup = false; } if (!readTemp) { lblReadTemp.Text = "? °C"; lblTempRGB.Text = "? °C"; } } private void ConnectCamera(CameraDeviceInfo cameraDeviceInfo) { DisposeCamera(); switch (cameraDeviceInfo.SelectedStreamingFormat) { case ImageFormat.FlirFileFormat: IRCam.ThermoCamera = new ThermalCamera(); break; case ImageFormat.Argb: IRCam.ThermoCamera = new VideoOverlayCamera(); break; default: throw new ArgumentOutOfRangeException(); } IRCam.ThermoCamera.ConnectionStatusChanged += Camera_ConnectionStatusChanged; IRCam.ThermoCamera.GetImage().Changed += Image_Changed; IRCam.ThermoCamera.Connect(cameraDeviceInfo); if (IRCam.ThermoCamera.Recorder == null && _recorder != null) { _recorder.Dispose(); _recorder = null; } if (_recorder != null) { if (!_recorder.IsDisposed) _recorder.Initialize(IRCam.ThermoCamera); } recorderToolStripMenuItem.Enabled = IRCam.ThermoCamera.Recorder != null; _timerRefreshUi.Start(); } private void DisconnectCamera() { if (IRCam.ThermoCamera == null) return; IRCam.ThermoCamera.Disconnect(); } private void disconnectToolStripMenuItem_Click(object sender, EventArgs e) { DisconnectCamera(); } private void discoveryToolStripMenuItem_Click(object sender, EventArgs e) { var discoveryDlg = new DiscoveryForm(); if (discoveryDlg.ShowDialog() == DialogResult.OK) { ConnectCamera(discoveryDlg.SelectedCameraDevice); // salvo nome camera in conf attuale! currConf.CameraName = discoveryDlg.SelectedCameraDevice.Name; saveConf(); } } private void displayImages() { if (ImgData.Thermal != null) { lblImgA.Text = showOrigBig ? "ORIGINAL image" : "FINAL image"; lblImgB.Text = showOrigBig ? "FINAL image" : "ORIGINAL image"; pBoxA.Image = showOrigBig ? ImgData.ColorTransf : ImgData.Decorated; pBoxB.Image = showOrigBig ? ImgData.Decorated : ImgData.ColorTransf; pBoxC.Image = ImgData.GrayTransf; } } private void DisposeCamera() { _timerRefreshUi.Stop(); if (IRCam.ThermoCamera == null) return; if (_recorder != null) { _recorder.UnInitialize(); _recorder.Dispose(); } IRCam.ThermoCamera.ConnectionStatusChanged -= Camera_ConnectionStatusChanged; IRCam.ThermoCamera.GetImage().Changed -= Image_Changed; IRCam.ThermoCamera.Dispose(); } private void drawCrossAtPoints() { if (ImgData.Thermal != null) { sw.Restart(); // se ho dei punti x trasformazione --> disegno if (currConf.origPoints.curr > 0) { foreach (var item in currConf.origPoints.Coords) { drawPoint(Color.Green, item); } } // se ho punto acquisizione temp --> disegno! if (refTempPoint != null) { drawPoint(Color.Blue, refTempPoint); } sw.Stop(); msLastPts = sw.ElapsedMilliseconds; } } private void drawPoint(Color currColor, Point currPoint) { // disegno! using (Graphics gr = Graphics.FromImage(ImgData.Decorated)) { gr.SmoothingMode = SmoothingMode.AntiAlias; Point[] crsPoints = new Point[4]; for (int i = 0; i < 4; i++) { crsPoints[i] = currPoint; } crsPoints[0].X = crsPoints[0].X - 4; crsPoints[1].X = crsPoints[1].X + 4; crsPoints[2].Y = crsPoints[2].Y - 4; crsPoints[3].Y = crsPoints[3].Y + 4; using (Pen thick_pen = new Pen(currColor, 1)) { gr.DrawLine(thick_pen, crsPoints[0], crsPoints[1]); gr.DrawLine(thick_pen, crsPoints[2], crsPoints[3]); } } } #if false private void getTemperatures() { if (ImgData.Thermal != null) { lastReadTemp = 0; if (readTemp) { // recupero temp da FLIR lastReadTemp = ImgData.Thermal.GetValueAt(ImgData.lastPoint).Value; // calcolo temp da RGB considerato limiti minMAX... //int rgbVal = ImgData.lastImageOrig.Image.GetPixel(ImgData.lastPoint.X, ImgData.lastPoint.Y).R; int rgbVal = getRSmooth(ImgData.lastPoint); lastCalcTemp = lastTempRange.Minimum + ((lastTempRange.Maximum - lastTempRange.Minimum) * rgbVal / 255); } } } #endif private void Image_Changed(object sender, Flir.Atlas.Image.ImageChangedEventArgs e) { IsDirty = true; } private void lblOrigA_Click(object sender, EventArgs e) { lblOrigA.ForeColor = toggleEditPoint(0); } private void lblOrigB_Click(object sender, EventArgs e) { lblOrigB.ForeColor = toggleEditPoint(1); } private void lblOrigC_Click(object sender, EventArgs e) { lblOrigC.ForeColor = toggleEditPoint(2); } private void lblOrigD_Click(object sender, EventArgs e) { lblOrigD.ForeColor = toggleEditPoint(3); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { if (_recorder != null && _recorder.IsDisposed == false) { _recorder.Dispose(); } DisposeCamera(); } private void MainForm_Load(object sender, EventArgs e) { } private void MainForm_Shown(object sender, EventArgs e) { // avvio l'autoconnessione if (!string.IsNullOrEmpty(currConf.CameraName)) { discoveryCamera(); } } private void openToolStripMenuItem_Click(object sender, EventArgs e) { using (var dialog = new OpenFileDialog()) { dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) + @"\FLIR"; dialog.Filter = FileFilter; if (dialog.ShowDialog() != DialogResult.OK) return; var playback = new PlaybackForm(dialog.FileName); playback.Show(); } } private void pictureBox1_Click(object sender, EventArgs e) { // calcolo la ratio img originale --> pictureBox double ratio = 1; try { if (ImgData.Origin != null && ImgData.Origin.Width > 0) { ratio = (double)pBoxA.Width / ImgData.Origin.Width; } } catch { } // trasformo punto in equivalente originale var mea = (MouseEventArgs)e; ImgData.lastPoint = new Point() { X = (int)(mea.X / ratio), Y = (int)(mea.Y / ratio) }; lblPoint.Text = $"({mea.X},{mea.Y}) --> ({ImgData.lastPoint.X},{ImgData.lastPoint.Y})"; if (pointSetup) { // max 4 punti... se sono già FULL --> imposto singolo valore nuovo if (currConf.origPoints.curr >= 4) { if (currPoint >= 0) { currConf.origPoints.Coords[currPoint] = ImgData.lastPoint; } else { currConf.origPoints = new SetPoints(); } } else { // salvo coordinate in base al num corrente... try { currConf.origPoints.Coords.Add(ImgData.lastPoint); currConf.origPoints.curr++; } catch { } } // salvo conf saveConf(); // aggiorno visualizzazione processImage(); refreshDisplay(); } else if (readTemp) { refTempPoint = ImgData.lastPoint; // aggiorno visualizzazione processImage(); refreshDisplay(); } } private void processImage() { // disegno immagine ImgData.getTemperatures(readTemp); ImgData.calculateTarget(chkParallel.Checked, chkRevProc.Checked); } private void recorderToolStripMenuItem_Click(object sender, EventArgs e) { if (_recorder == null || _recorder.IsDisposed) { _recorder = new RecorderForm(); _recorder.Initialize(IRCam.ThermoCamera); _recorder.SelectedFileMouseDoubleClick += _recorder_SelectedFileMouseDoubleClick; } _recorder.Show(); _recorder.Focus(); } private void refreshDisplay() { drawCrossAtPoints(); displayImages(); updateDisplay(); updateTempDisplay(); updateStatDisplay(); } /// /// Salva su file il file di conf corrente /// /// private bool saveConf() { bool answ = false; try { string rawData = JsonConvert.SerializeObject(currConf, Formatting.Indented); File.WriteAllText(confPath, rawData); answ = true; } catch { } return answ; } private void saveImgFile() { try { // fix percorsi string dirPath = $"{BASE_PATH}\\{thermPath}"; string filePath = $"{dirPath}\\{currImgFile}"; if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } // salvo! ImgData.Thermal.Scale.IsAutoAdjustEnabled = true; ImgData.Thermal.SaveSnapshot(filePath); } catch { } } /// /// recupera immagine effettuando eventuale salvataggio /// /// private void takePicture(bool doSave) { sw.Restart(); if (!IsDirty) return; IsDirty = false; if (IRCam.ThermoCamera == null) return; IRCam.ThermoCamera.GetImage().EnterLock(); try { // non funziona #if false // se richiesto imposto temperature min/max... if (!chkAutoTemp.Checked) { Range limits = new Range(273.1 + minTemp, 273.1 + maxTemp); IRCam.ThermoCamera.RemoteControl.CameraSettings.SetScaleLimits(limits); } #endif ImgData.Thermal = (ThermalImage)IRCam.ThermoCamera.GetImage(); ImgData.Thermal.TemperatureUnit = TemperatureUnit.Celsius; ImgData.Thermal.Scale.IsAutoAdjustEnabled = true; // salvo img locale ImgData.Origin = ImgData.Thermal.Image; ImgData.Decorated = ImgData.Thermal.Image; if (doSave) { saveImgFile(); } } catch (Exception exception) { Trace.TraceError(exception.Message); } finally { IRCam.ThermoCamera.GetImage().ExitLock(); } sw.Stop(); msLastCam = sw.ElapsedMilliseconds; #if false ThermalImage myImg = (ThermalImage)_stream.GetImage(); myImg.TemperatureUnit = TemperatureUnit.Celsius; string currPath = $"{labelOutputPath.Text}\\{DateTime.Now:yyyyMMyy_HHmmss}.jpg"; myImg.SaveSnapshot(currPath); #endif } /// /// Verifica se il punto era in edit, altrimenti segnala e lo attiva... restituendo colore x il controllo /// /// private Color toggleEditPoint(int selPoint) { Color answ = Color.Black; lblOrigA.ForeColor = answ; lblOrigB.ForeColor = answ; lblOrigC.ForeColor = answ; lblOrigD.ForeColor = answ; readTemp = false; pointSetup = true; if (currPoint == selPoint) { // deseleziono currPoint = -1; } else { // indico selezionato currPoint = selPoint; answ = Color.Green; } return answ; } private void tryReloadConf() { if (File.Exists(confPath)) { // se non è vuoto.... string rawData = File.ReadAllText(confPath); if (!string.IsNullOrEmpty(rawData)) { try { currConf = JsonConvert.DeserializeObject(rawData); } catch (Exception exc) { } } } } private void txtAB_TextChanged(object sender, EventArgs e) { // ricalcolo in proporzione altro valore... dimY = (int)((double)dimX * 256 / 320); // aggiorno punti dest updateDestPt(); } private void txtBC_TextChanged(object sender, EventArgs e) { // ricalcolo in proporzione altro valore... dimX = (int)((double)dimY * 320 / 256); // aggiorno punti dest updateDestPt(); } private void updateDestPt() { currConf.destPoints.Coords = new List(); // ciclo i punti A-B-C-D... currConf.destPoints.Coords.Add(new Point() { X = 0, Y = 0 }); currConf.destPoints.Coords.Add(new Point() { X = dimX, Y = 0 }); currConf.destPoints.Coords.Add(new Point() { X = dimX, Y = dimY }); currConf.destPoints.Coords.Add(new Point() { X = 0, Y = dimY }); // salvo conf saveConf(); } private void updateDisplay() { lblOrigA.Text = "A"; lblOrigB.Text = "B"; lblOrigC.Text = "C"; lblOrigD.Text = "D"; // popolo if (currConf.origPoints.Coords.Count > 0) { lblOrigA.Text = $"A: ({currConf.origPoints.Coords[0].X},{currConf.origPoints.Coords[0].Y})"; } if (currConf.origPoints.Coords.Count > 1) { lblOrigB.Text = $"B: ({currConf.origPoints.Coords[1].X},{currConf.origPoints.Coords[1].Y})"; } if (currConf.origPoints.Coords.Count > 2) { lblOrigC.Text = $"C: ({currConf.origPoints.Coords[2].X},{currConf.origPoints.Coords[2].Y})"; } if (currConf.origPoints.Coords.Count > 3) { lblOrigD.Text = $"D: ({currConf.origPoints.Coords[3].X},{currConf.origPoints.Coords[3].Y})"; ImgData.calculateTarget(chkParallel.Checked, chkRevProc.Checked); } } private void updateStatDisplay() { lblStats.Text = $"Camera: {msLastCam}ms | Persp {msLastTra}ms | Color {msLastCol}ms | Points {msLastPts}ms | Measures {msLastMsr}ms"; } private void updateTempDisplay() { if (ImgData.Thermal != null) { if (readTemp) { // mostro temp da FLIR lblReadTemp.Text = $"{ImgData.lastReadTemp:N2} °C"; //calcolo da immagine B/N lblTempRGB.Text = $"{ImgData.lastCalcTemp:N2} °C"; // cambio colore se > 1 grado... double delta = Math.Abs(ImgData.lastReadTemp - ImgData.lastCalcTemp); if (delta < 1) { lblTempRGB.ForeColor = Color.Black; } else if (delta < 2) { lblTempRGB.ForeColor = Color.Blue; } else if (delta < 3) { lblTempRGB.ForeColor = Color.Orange; } else { lblTempRGB.ForeColor = Color.Red; } } } } #endregion Private Methods #if false /// /// Calcola valore R ponderato dato un punto + intorno /// /// /// protected int getRSmooth(Point reqPoint) { int answ = 0; if (ImgData.Origin != null) { // solo se il punto è entro limiti immagine --> 1 pixel entro bordo if (reqPoint.X > 0 && reqPoint.X < ImgData.Origin.Width) { if (reqPoint.Y > 0 && reqPoint.Y < ImgData.Origin.Height) { int rgbValMain = ImgData.Origin.GetPixel(ImgData.lastPoint.X, ImgData.lastPoint.Y).R; int rgbValN = ImgData.Origin.GetPixel(ImgData.lastPoint.X, ImgData.lastPoint.Y - 1).R; int rgbValNO = ImgData.Origin.GetPixel(ImgData.lastPoint.X - 1, ImgData.lastPoint.Y - 1).R; int rgbValO = ImgData.Origin.GetPixel(ImgData.lastPoint.X - 1, ImgData.lastPoint.Y).R; int rgbValSO = ImgData.Origin.GetPixel(ImgData.lastPoint.X - 1, ImgData.lastPoint.Y + 1).R; int rgbValS = ImgData.Origin.GetPixel(ImgData.lastPoint.X, ImgData.lastPoint.Y + 1).R; int rgbValSE = ImgData.Origin.GetPixel(ImgData.lastPoint.X + 1, ImgData.lastPoint.Y + 1).R; int rgbValE = ImgData.Origin.GetPixel(ImgData.lastPoint.X + 1, ImgData.lastPoint.Y).R; int rgbValNE = ImgData.Origin.GetPixel(ImgData.lastPoint.X + 1, ImgData.lastPoint.Y - 1).R; // calcolo valore ponderato answ = (int)Math.Round((double)(1 * rgbValMain + 1 * (rgbValN + rgbValO + rgbValS + rgbValE) + rgbValNO + rgbValSO + rgbValSE + rgbValNE) / 10, 0); } } } return answ; } #endif #region Public Methods public void discoveryCamera() { var discoveryDialog = new DiscoveryForm(); discoveryDialog.CameraName = currConf.CameraName; if (!string.IsNullOrEmpty(currConf.CameraName)) { //discoveryDialog.WindowState = FormWindowState.Minimized; if (discoveryDialog.ShowDialog() == DialogResult.OK) { ConnectCamera(discoveryDialog.SelectedCameraDevice); } } } #endregion Public Methods } }