using FLY.Thick.BlowingScan.Common; using FLY.Thick.BlowingScan.IService; using FLY.Thick.BlowingScan.Server; using MathNet.Numerics.LinearAlgebra.Double; using Misc; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using ThickTcpUiInWindow; using FObjBase; using System.Collections.ObjectModel; using System.Diagnostics; namespace FLY.Thick.BlowingScan.UI.Client { public class BlowingScanAnaylze : INotifyPropertyChanged { #region 缓存区 /// <summary> /// 从BlowingScanService 接收过来的数据; /// 缓冲区第一个记录序号 /// </summary> public int FirstBM { get; set; } = -1; /// <summary> /// 从BlowingScanService 接收过来的数据; /// 缓冲区最后一个记录序号 /// </summary> public int LastBM { get; set; } = -1; /// <summary> /// 从BlowingScanService 接收过来的数据; /// 缓冲区 /// </summary> public List<FlyData_BlowingScan> mBufList; public ObservableCollection<ScanInfoCell> ScanInfoList { get; } = new ObservableCollection<ScanInfoCell>(); #endregion #region 试调整参数 private int solvecnt = 4; /// <summary> /// 多少次扫描解一次方程 /// </summary> public int SolveCnt { get { return solvecnt; } set { if (value < 4) value = 4; if (solvecnt != value) { solvecnt = value; } } } private int smooth = 1; /// <summary> /// 平滑 /// </summary> public int Smooth { get { return smooth; } set { if (value < 0) value = 0; if (smooth != value) { smooth = value; } } } /// <summary> /// 旋转架转动角度 /// </summary> public double RAngle { get; set; } = 350; /// <summary> /// 人字架到测厚仪膜的长度 单位m /// </summary> public double FilmLength { get; set; } = 23; #endregion #region 常量参数 public int N2 { get; set; } = 400; public int Sensor { get; set; } = 240; public int OrgBoltNo { get; set; } = 1; public int NBolts { get; set; } = 88; #endregion #region 状态 public bool IsStep2Ing { get; set; } #endregion #region 机架校正 public GageData mGagedata = new GageData(); #endregion BlowingAngleDetect mAngleDetect = new BlowingAngleDetect(); public BlowingScanAnaylze() { MarkNo1.PropertyChanged += MarkNo_PropertyChanged; MarkNo2.PropertyChanged += MarkNo_PropertyChanged; } public int GetMarkNoFromRList(int idx) { if (idx >= ScanInfoList.Count()) return LastBM; else if (idx < 0) return (LastBM - (ScanInfoList.Count() - 1)); int markno = idx+ (LastBM - (ScanInfoList.Count() - 1)); return markno; } int GetIdxFromRList(int markno) { int idx = markno - (LastBM - (ScanInfoList.Count() - 1)); if (idx >= ScanInfoList.Count()) return -1; else if (idx < 0) return -1; return idx; } private void MarkNo_PropertyChanged(object sender, PropertyChangedEventArgs e) { MarkData markdata = sender as MarkData; if (e.PropertyName == "Number") { int idx = GetIdxFromRList(markdata.Number); if (idx >= 0) { markdata.IsValid = true; if (!ScanInfoList[idx].HasCast)//解方程 ToThicks_SolveEquation(idx); markdata.StartTime = ScanInfoList[idx].StartTime; markdata.ThicksDT = ScanInfoList[idx].ThicksDT; markdata.Thicks = ScanInfoList[idx].thicks; markdata.Direction = ScanInfoList[idx].Direction; markdata.Border = mBufList[idx].FilmRange; markdata.PosOfGrid = mBufList[idx].PosOfGrid; markdata.Time = mBufList[idx].Time; markdata.OrgThicks = mBufList[idx].Thicks; markdata.Power = ScanInfoList[idx].power; markdata.AvgThicks = ScanInfoList[idx].thicks_avg; } else { markdata.IsValid = false; } UpdateCurrR(); UpdateIsFocus(); } } /// <summary> /// 能进入第2步解方程 /// </summary> /// <returns></returns> public bool CanStep2() { if (mBufList == null) return false; else if (mBufList.Count() == 0) return false; else if (IsStep2Ing) return false; return true; } /// <summary> /// 更新 BufList /// </summary> /// <param name="firstbm"></param> /// <param name="lastbm"></param> /// <param name="datas"></param> public void Step1(int firstbm, int lastbm, List<FlyData_BlowingScan> datas) { FirstBM = firstbm; LastBM = lastbm; mBufList = datas; if (mBufList == null) return; BufList2AngleInfo(); BufList2ScanInfoList(); BufList2GageData(); //当MarkNo1.Number 不在范围内, 限制它们 if (FirstBM < LastBM) { MarkNo1.Number = KeepInRange(MarkNo1.Number, FirstBM + SolveCnt - 1, LastBM); MarkNo2.Number = KeepInRange(MarkNo2.Number, FirstBM + SolveCnt - 1, LastBM); if (MarkNo1.Number == MarkNo2.Number) { MarkNo2.Number = MarkNo1.Number + SolveCnt; } } else if (FirstBM > LastBM) { //转了一个圈,很异常,不处理 MarkNo1.Number = FirstBM + SolveCnt - 1; MarkNo2.Number = MarkNo1.Number + SolveCnt; } else { //没有数据 } } int KeepInRange(int number, int begin, int end) { if (number < begin) number = begin; else if (number > end) number = end; return number; } /// <summary> /// 解方程 /// </summary> public void Step2() { IsStep2Ing = true; if (mBufList == null) { IsStep2Ing = false; return; } GetSolveEquation(); ClearAllResult(); MarkNo1.Refresh(); MarkNo2.Refresh(); //ToThicks_SolveEquation(); IsStep2Ing = false; } /// <summary> /// 清除全部计算结果 /// </summary> public void ClearAllResult() { for (int i = 0; i < mBufList.Count(); i++) { ScanInfoCell scaninfo = ScanInfoList[i]; scaninfo.HasCast = false; } } /// <summary> /// 转为 旋转架信息 /// </summary> void BufList2AngleInfo() { mAngleDetect.mLimitList.Clear(); BlowingAngleDetect.LimitCell c_last = null; for (int i=0; i < mBufList.Count(); i++) { FlyData_BlowingScan f = mBufList[i]; if (f.SignNo == -1) continue; if (c_last == null) { c_last = new BlowingAngleDetect.LimitCell(); c_last.no = f.SignNo; c_last.dt_begin = f.Time; c_last.dt_end = f.LeaveTime; mAngleDetect.mLimitList.Add(c_last); } else if (c_last.no != f.SignNo) { c_last = new BlowingAngleDetect.LimitCell(); c_last.no = f.SignNo; c_last.dt_begin = f.Time; c_last.dt_end = f.LeaveTime; mAngleDetect.mLimitList.Add(c_last); } else { c_last.no = f.SignNo; c_last.dt_begin = f.Time; c_last.dt_end = f.LeaveTime; } } mAngleDetect.RotationCnt = mBufList.Last().RotateCnt; mAngleDetect.LimitSignTime = mBufList.Last().LimitSignTime; mAngleDetect.RenZiJiaPeriod = mBufList.Last().RotatePeriod; //更新默认时间 if (mAngleDetect.mLimitList.Count() > 0) { if(mAngleDetect.mLimitList[0].dt_begin != DateTime.MinValue) mAngleDetect.mDefaultTime = mAngleDetect.mLimitList[0].dt_begin - mAngleDetect.RenZiJiaPeriod; else mAngleDetect.mDefaultTime = mAngleDetect.mLimitList[0].dt_end - mAngleDetect.LimitSignTime - mAngleDetect.RenZiJiaPeriod; } } /// <summary> /// 转为 位置与厚度信息 /// </summary> void BufList2ScanInfoList() { ScanInfoList.Clear(); for (int i = 0; i < mBufList.Count(); i++) { FlyData_BlowingScan f = mBufList[i]; int posOfGrid = f.PosOfGrid; Range boltrange = new Range() { Begin = f.FilmRange.Begin + Sensor / 2 + N2, End = f.FilmRange.End - (Sensor / 2 + N2) }; Range border = f.FilmRange; ScanInfoCell scaninfocell = new ScanInfoCell(NBolts, OrgBoltNo); //对应的grid号 int bp_idx = boltrange.Begin / posOfGrid; int ep_idx = boltrange.End / posOfGrid; if (bp_idx < 0) bp_idx = 0; if (ep_idx > (f.Thicks.Length - 1)) ep_idx = f.Thicks.Length - 1; for (int j = bp_idx; j <= ep_idx; j++) { int idx = j; int pos = j * posOfGrid + posOfGrid / 2;//一个grid的中间脉冲,也就是大概而已 int thick = f.Thicks[idx]; pos -= border.Begin;//膜上面的位置 ScanPosCell scanpos = new ScanPosCell() { pos = pos, thick = thick, dt = f.ThicksDt[idx] }; //scaninfocell.StartTime 为整幅数据最早的时间 if (j == bp_idx) { scaninfocell.StartTime = scanpos.dt; } else { if (scaninfocell.StartTime > scanpos.dt) scaninfocell.StartTime = scanpos.dt; } scaninfocell.frame.Add(scanpos); } ScanInfoList.Add(scaninfocell); } } /// <summary> /// 计算平均数据 /// </summary> void BufList2GageData() { int[] gage_sum = null; int[] gage_cnt = null; Range border = new Range(); int posOfGrid = 10; for (int i = 0; i < mBufList.Count(); i++) { FlyData_BlowingScan f = mBufList[i]; posOfGrid = f.PosOfGrid; if (gage_sum == null) { gage_sum = f.Thicks.Clone() as int[]; gage_cnt = new int[f.Thicks.Count()]; border.Copy(f.FilmRange); } for (int j = 0; j < gage_sum.Count() && j < f.Thicks.Count(); j++) { if (Misc.MyBase.ISVALIDATA(f.Thicks[j])) { if (gage_cnt[j] > 0) gage_sum[j] += f.Thicks[j]; else gage_sum[j] = f.Thicks[j]; gage_cnt[j]++; } } if (border.Begin < f.FilmRange.Begin) border.Begin = f.FilmRange.Begin; if (border.End > f.FilmRange.End) border.End = f.FilmRange.End; } mGagedata.PosOfGrid = posOfGrid; mGagedata.Border = border; for (int i = 0; i < gage_sum.Count(); i++) { if (gage_cnt[i] > 0) { gage_sum[i] = gage_sum[i] / gage_cnt[i]; } } mGagedata.OrgThicks = gage_sum; } /// <summary> /// 第2步,修改 旋转角度,膜距离 执行, 耗时很短 /// </summary> void GetSolveEquation() { mAngleDetect.RAngle = RAngle; //mAngleDetect.FilmLength = FilmLength; //获取角度信息 for (int i = 0; i < mBufList.Count(); i++) { FlyData_BlowingScan f = mBufList[i]; ScanInfoCell scaninfocell = ScanInfoList[i]; scaninfocell.equationList.Clear(); scaninfocell.State = ScanInfoCell.STATE.OK; for (int j = 0; j < scaninfocell.frame.Count(); j++) { ScanPosCell scanpos = scaninfocell.frame[j]; BlowingAngleDetect.FilmInfo filminfo; int ret = mAngleDetect.GetFilmInfo( out filminfo, scanpos.dt, f.FilmRange.Width, scanpos.pos, FilmLength, f.Velocity); //探头直径转为角度范围 double sensor_angle = 180.0 * Sensor / f.FilmRange.Width; if (ret == 0) { scanpos.angle1 = filminfo.angle1; scanpos.angle2 = filminfo.angle2; scanpos.direction = filminfo.direction; //TODO scanpos.rotateAngle = 180 * scanpos.pos / f.FilmRange.Width; } else { scaninfocell.State = ScanInfoCell.STATE.Err_GetAngle; break; } if (j == 0) scaninfocell.Direction = scanpos.direction; else { if (scaninfocell.Direction != scanpos.direction) { scaninfocell.Direction = DIRECTION.FIX; scaninfocell.State = ScanInfoCell.STATE.Err_Solve; break; } } scaninfocell.AddEquation(j,sensor_angle); } } } /// <summary> /// 最后1步,解方程 /// </summary> void ToThicks_SolveEquation() { for (int i = 0; i < mBufList.Count(); i++) { ToThicks_SolveEquation(i); } MarkNo1.Refresh(); MarkNo2.Refresh(); } bool ToThicks_SolveEquation(int scaninfo_idx) { ScanInfoCell scaninfo = ScanInfoList[scaninfo_idx]; scaninfo.HasCast = true; if (scaninfo.State != ScanInfoCell.STATE.OK) return false; if (scaninfo.Direction == DIRECTION.FIX)//当前是Fix 不解方程 { scaninfo.State = ScanInfoCell.STATE.Err_Solve; return false; } //全部方向都必须一致,当然也不能是Fix //for (int i = 0; i < (SolveCnt - 1); i++) //{ // if (scaninfo.Direction != ScanInfoList[scaninfo_idx - 1 - i].Direction) // { // scaninfo.State = ScanInfoCell.STATE.ERR_Solve_Maybe; // break; // } //} //平均值图 { int[] _sum = new int[scaninfo.nbolts]; int[] _cnt = new int[scaninfo.nbolts]; foreach (EquationCell e in scaninfo.equationList) { int avg = e.GetAvg(); foreach (KeyValuePair<int, int> kv in e.boltPower) { _sum[kv.Key] += avg; _cnt[kv.Key] += 1; scaninfo.thicks_avg[kv.Key] = _sum[kv.Key] / _cnt[kv.Key]; } } } List<EquationCell> equationlist = new List<EquationCell>(); scaninfo.solveMember.Clear(); int cnt = 0; for (int i = scaninfo_idx; i >= 0; i--) { if (ScanInfoList[i].Direction == DIRECTION.FIX) continue; cnt++; equationlist.AddRange(ScanInfoList[i].equationList); //用到的扫描副 scaninfo.solveMember.Add(i); if (cnt >= SolveCnt) break; } //计算 每个分区的权 for (int i = 0; i < scaninfo.power.Count(); i++) { scaninfo.power[i] = 0; } foreach (EquationCell e in equationlist) { foreach (KeyValuePair<int, int> kv in e.boltPower) { //if(e.mainBolt == kv.Key) scaninfo.power[kv.Key] += kv.Value; //else // scaninfo.power[kv.Key+scaninfo.nbolts] += kv.Value; } } if (cnt < SolveCnt)//方程不够!!!! { scaninfo.State = ScanInfoCell.STATE.Err_Solve; return false; } if (equationlist.Count < 100)//方程数太少 { scaninfo.State = ScanInfoCell.STATE.Err_Solve; return false; } SolveEquation(equationlist, ScanInfoList[scaninfo_idx].thicks); //2105 条方程,耗时 0.3s //SolveEquation(equationlist, ScanInfoList[scaninfo_idx].thicks); //不能有任何一个NULL_VALUE for (int i = 0; i < scaninfo.thicks.Count(); i++) if (!Misc.MyBase.ISVALIDATA(scaninfo.thicks[i])) return false; if (Smooth > 0) { int[] data = new int[scaninfo.thicks.Count()]; for (int i = 0; i < scaninfo.thicks.Count(); i++) { int sum = 0; for (int j = 0; j < (Smooth * 2 + 1); j++) { int index = i + j - Smooth; if (index < 0) index += scaninfo.thicks.Count(); else if (index >= scaninfo.thicks.Count()) index -= scaninfo.thicks.Count(); sum += scaninfo.thicks[index]; } data[i] = (int)(sum / (Smooth * 2 + 1)); } Array.Copy(data, scaninfo.thicks, data.Count()); } scaninfo.ThicksDT = ScanInfoList[scaninfo_idx - (SolveCnt - 1)].StartTime; return true; } /// <summary> /// 解方程 /// </summary> /// <param name="equationList">N条方程</param> /// <param name="frame_out">输出的结果</param> void SolveEquation(List<EquationCell> equationList, int[] frame_out) { int nrow = equationList.Count(); //A~AX = A~B //转换为 DenseMatrix 与 DenseVector DenseMatrix matrixA = new DenseMatrix(nrow, NBolts); DenseMatrix matrixAT = new DenseMatrix(NBolts, nrow); DenseVector vectorB = new DenseVector(nrow); DenseVector vectorATB = new DenseVector(NBolts); int rowindex = 0; foreach (EquationCell ec in equationList) { double[] datas = ec.GetPower(NBolts); matrixA.SetRow(rowindex, datas); matrixAT.SetColumn(rowindex, datas); vectorB[rowindex] = ec.thick; rowindex++; } DenseMatrix ATA = (DenseMatrix)matrixA.TransposeThisAndMultiply(matrixA); DenseVector ATB = (DenseVector)matrixA.TransposeThisAndMultiply(vectorB); DenseVector X = (DenseVector)ATA.Solve(ATB); int j; for (int i = 0; i < NBolts; i++) { frame_out[i] = (int)X[i]; } } #region INotifyPropertyChanged 接口 protected void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; #endregion #region 两组数据 private double currR = -1; public double CurrR { get { return currR; } set { if (currR != value) { currR = value; NotifyPropertyChanged("CurrR"); } } } public MarkData MarkNo1 = new MarkData(); public MarkData MarkNo2 = new MarkData(); void UpdateCurrR() { if (MarkNo1.IsValid && MarkNo2.IsValid) { CurrR = Misc.MyMath.Correl(MarkNo1.Thicks, MarkNo2.Thicks); } else { CurrR = -1; } } void UpdateIsFocus() { int idx1 = GetIdxFromRList(MarkNo1.Number); int idx2 = GetIdxFromRList(MarkNo2.Number); for (int i = 0; i < ScanInfoList.Count(); i++) { ScanInfoList[i].IsFocus = false; ScanInfoList[i].IsFocus2 = false; } if (idx1 > 0) { ScanInfoList[idx1].IsFocus = true; foreach (int i in ScanInfoList[idx1].solveMember) { ScanInfoList[i].IsFocus = true; } } if (idx2 > 0) { ScanInfoList[idx2].IsFocus2 = true; foreach (int i in ScanInfoList[idx2].solveMember) { ScanInfoList[i].IsFocus2 = true; } } } #endregion #region 调试 public bool Save(string path) { try { using (StreamWriter sw = new StreamWriter(path, false, Encoding.GetEncoding("GB2312"))) { bool hasHeader = false; foreach (FlyData_BlowingScan f in mBufList) { if (!hasHeader) { hasHeader = true; sw.WriteLine(f.GetHeader()); } sw.WriteLine(f.ToString()); } sw.Flush(); sw.Close(); } return true; } catch (Exception e) { return false; } } public bool Load(string path) { if (mBufList != null) mBufList.Clear(); else mBufList = new List<FlyData_BlowingScan>(); using (StreamReader sr = new StreamReader(path, Encoding.GetEncoding("GB2312"))) { string header = sr.ReadLine(); while (!sr.EndOfStream) { string s = sr.ReadLine(); FlyData_BlowingScan f = new FlyData_BlowingScan(); if (f.TryParse(header, s)) { mBufList.Add(f); } else { return false; } } } return true; } #endregion } public class GageData : INotifyPropertyChanged { private Range border = new Range(); /// <summary> /// 膜边界,单位脉冲 /// </summary> [PropertyChanged.DoNotCheckEquality] public Range Border { get; set; } /// <summary> /// pos / grid /// </summary> public int PosOfGrid { get; set; } = 10; /// <summary> /// 厚度数据, 大小 grid总数 /// </summary> [PropertyChanged.DoNotCheckEquality] public int[] OrgThicks { get; set; } #region INotifyPropertyChanged 接口 public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; #endregion } public class MarkData : INotifyPropertyChanged { public int Number { get; set; } = -1; /// <summary> /// 获取了焦点 /// </summary> public bool IsFocus { get; set; } /// <summary> /// 有效 /// </summary> public bool IsValid { get; set; } /// <summary> /// 扫描时间 /// </summary> public DateTime StartTime { get; set; } = DateTime.MinValue; /// <summary> /// 旋转架旋转的方向,当同一次扫描中,有两个方向,Direction= Fix; /// </summary> public Misc.DIRECTION Direction { get; set; } = DIRECTION.FORWARD; /// <summary> /// 下面解方程得到的一幅数据对应的数据,与 StartTime 不一样 /// </summary> public DateTime ThicksDT { get; set; } = DateTime.MinValue; /// <summary> /// 一幅数据 /// </summary> [PropertyChanged.DoNotCheckEquality] public int[] Thicks { get; set; } /// <summary> /// 膜边界,单位脉冲 /// </summary> [PropertyChanged.DoNotCheckEquality] public Range Border { get; set; } /// <summary> /// pos / grid /// </summary> public int PosOfGrid { get; set; } = 10; /// <summary> /// orgthick时间 /// </summary> public DateTime Time { get; set; } = DateTime.MinValue; /// <summary> /// 厚度数据, 大小 grid总数 /// </summary> [PropertyChanged.DoNotCheckEquality] public int[] OrgThicks { get; set; } /// <summary> /// 权重图 /// </summary> [PropertyChanged.DoNotCheckEquality] public int[] Power { get; set; } /// <summary> /// 均值图 /// </summary> [PropertyChanged.DoNotCheckEquality] public int[] AvgThicks { get; set; } public MarkData() { } public void Refresh() { NotifyPropertyChanged("Number"); } #region INotifyPropertyChanged 接口 protected void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; #endregion } public class BlowingAngleDetect : INotifyPropertyChanged { #region 自定义类 public class LimitCell { public LimitCell Clone() { LimitCell l = new LimitCell(); l.dt_begin = dt_begin; l.dt_end = dt_end; l.no = no; return l; } public DateTime dt_begin = DateTime.MinValue; public DateTime dt_end = DateTime.MinValue; public int no; public override string ToString() { return no.ToString() + " (" + dt_begin.ToString() + "-" + dt_end.ToString() + ")"; } } #endregion #region 成员变量 #region 参数 /// <summary> /// 离开限位 到 撞下一个限位 的 旋转架转动总角度 单位° /// </summary> public double RAngle { get; set; } = 350; /// <summary> /// 离开限位 到 撞下一个限位 的 旋转架转动时间, 需要初始值,以后测量出来的 /// </summary> public TimeSpan RenZiJiaPeriod { get; set; } /// <summary> /// 加速减速时间, 加速减速时间+正反转冷却时间 = 撞限位 到 离开限位 的时间 /// </summary> public TimeSpan AccDecTime { get; set; } /// <summary> /// 撞限位 到 离开限位 的时间, 需要初始值,以后测量出来的 /// </summary> public TimeSpan LimitSignTime { get; set; } /// <summary> /// 人字架到测厚仪膜长 单位m /// </summary> public double FilmLength { get; set; } #endregion #region 状态 /// <summary> /// 正反转冷却时间, 默认1s, 一定要小于撞限位 到 离开限位 的时间 /// </summary> public TimeSpan SwapCoolTime { get; protected set; } #endregion public List<LimitCell> mLimitList = new List<LimitCell>(); /// <summary> /// 累计旋转次数 /// </summary> public int RotationCnt { get; set; } int LimitIndexToRotationCnt(int index) { if (RotationCnt == 0) return 0; else return RotationCnt - mLimitList.Count + index + 1; } #endregion public DateTime mDefaultTime; public BlowingAngleDetect() { RAngle = 337;//° RenZiJiaPeriod = TimeSpan.FromMinutes(8); AccDecTime = TimeSpan.FromSeconds(1); LimitSignTime = TimeSpan.FromSeconds(4); FilmLength = 25;//m SwapCoolTime = LimitSignTime - AccDecTime - AccDecTime; mDefaultTime = DateTime.Now; } public class FilmInfo { /// <summary> /// 上层 位于膜泡的角度 0~360° /// </summary> public double angle1; /// <summary> /// 下层 位于膜泡的角度 0~360° /// </summary> public double angle2; /// <summary> /// 膜泡旋转方向 /// </summary> public Misc.DIRECTION direction; /// <summary> /// 线速度 /// </summary> public double filmVelocity; /// <summary> /// 累计旋转次数 /// </summary> public int rotationCnt; /// <summary> /// 在匀速阶段; 在限位信号 开始~结束 期间 inCV = false /// </summary> public bool inCV; } public enum GetLimitTimeResult { /// <summary> /// 获取成功 /// </summary> OK, /// <summary> /// 不存在该点 /// </summary> NOEXIST, /// <summary> /// 限位已经撞了,但膜还没走完,等吧 /// </summary> ISFURTURE } /// <summary> /// 获取当前在膜上的测量点,对应于膜泡的角度信息, 当时间点,比LimitList.Last.dt+RenZiJiaPeriod还要后,更新RenZiJiaPeriod /// 返回 -1, 当前的时间点在列表以前; /// 返回 0, 当前的时间点在列表中; /// 返回 1, 当前的时间点在列表的未来; /// </summary> /// <param name="filminfo">输出资料</param> /// <param name="dt">测量的时间点</param> /// <param name="filmWidth">被压扁后的膜宽度</param> /// <param name="filmPosH">探头所在膜的横向位置</param> /// <param name="velocity">线速度</param> /// <returns></returns> public int GetFilmInfo(out FilmInfo filminfo, DateTime dt, double filmWidth, double filmPosH, double filmLen, double velocity) { filminfo = null; //与0°的偏移 double a = filmPosH / filmWidth * 180; if ((a < 0) || (a > 180)) { return -1;//不在膜的范围内 } DateTime ago_dt; double angle0; double angle1; Misc.DIRECTION direction; int rotationcnt; bool inCV; if (dt == DateTime.MinValue) { //TODO, BUG return -1; } ago_dt = GetAgo(dt, filmLen, velocity); bool r = GetAngle(a, ago_dt, out angle0, out angle1, out direction, out rotationcnt, out inCV); if (r == false)//太久之前了 return -1; filminfo = new FilmInfo() { angle1 = angle0, angle2 = angle1, filmVelocity = velocity, direction = direction, rotationCnt = rotationcnt, inCV = inCV }; return 0; } #region 人字架时间点<-->24m后膜时间点 /// <summary> /// 输入当前时间点,找到 刚出人字架时的时间点 /// </summary> /// <param name="dt">当前时间</param> /// <param name="filmlength">24米真实长度</param> /// <param name="velocity">线速度</param> /// <returns>24米以前的时间</returns> DateTime GetAgo(DateTime dt, double filmlength, double velocity) { TimeSpan ts = TimeSpan.FromMinutes(filmlength / velocity); return dt - ts; } #endregion /// <summary> /// 计算出角度 -180~180 恒速阶段 /// </summary> /// <param name="dtEnd">旋转结束时间点</param> /// <param name="dtStart">旋转开始时间点</param> /// <param name="noStart">开始时间点对应的限位 0 or 1</param> /// <param name="dt">用于转换为角度的时间</param> /// <param name="angle">输出: 角度</param> /// <param name="direction">输出: 方向</param> void LimitListCal_ConstantVelocity(DateTime dtEnd, DateTime dtStart, int noStart, DateTime dt, out double angle, out Misc.DIRECTION direction) { TimeSpan ts1 = dt - dtStart; TimeSpan ts2 = dtEnd - dtStart; angle = RAngle * ts1.Ticks / ts2.Ticks; if (noStart == 0) { angle = -RAngle / 2 + angle; direction = Misc.DIRECTION.FORWARD; } else { angle = RAngle / 2 - angle; direction = Misc.DIRECTION.BACKWARD; } } /// <summary> /// 计算出角度 -180~180 变速阶段 /// </summary> /// <param name="dtEnd"></param> /// <param name="dtStart"></param> /// <param name="noStart">dtStart的信号</param> /// <param name="dt">dt 在 [dtStart , dtEnd]</param> /// <param name="renZiJiaPeriod">上一次两个限位的间距时间</param> /// <param name="angle">角度</param> /// <param name="direction">方向, 只有正向 与 反向</param> void LimitListCal_SpeedChange(DateTime dtEnd, DateTime dtStart, int noStart, DateTime dt, TimeSpan renZiJiaPeriod, out double angle, out Misc.DIRECTION direction) { //默认 dt 在 dtStart <= dt <= dtEnd, 出错不处理!!! if ((dt < dtStart) || (dt > dtEnd)) { throw new Exception("(dt<dtStart)|| (dt>dtEnd) dt=" + dt.ToString() + " dtStart=" + dtStart.ToString() + " dtEnd=" + dtEnd.ToString()); } TimeSpan accDecTime = AccDecTime; if ((dtEnd - dtStart) < (accDecTime + accDecTime)) accDecTime = TimeSpan.FromTicks((dtEnd - dtStart).Ticks / 2); DateTime dtMid = dtStart + TimeSpan.FromTicks((dtEnd - dtStart).Ticks / 2); if (noStart == 0) { if (dt < dtMid) direction = Misc.DIRECTION.BACKWARD; else direction = Misc.DIRECTION.FORWARD; } else { if (dt < dtMid) direction = Misc.DIRECTION.FORWARD; else direction = Misc.DIRECTION.BACKWARD; } double dAngle; if (dt < (dtStart + accDecTime))//减速 { TimeSpan ts = dt - dtStart;//已经过去时间 double vStart = RAngle / renZiJiaPeriod.TotalSeconds;//开始速度 °/s double vEnd = 0;//结束速度 °/s double a = (vEnd - vStart) / accDecTime.TotalSeconds;//加速度 °/s^2 double vCurr = vStart + a * ts.TotalSeconds;//当前速度 //开始速度 °/s dAngle = (vStart + vCurr) / 2 * ts.TotalSeconds;//总角度 ° } else if (dt < (dtEnd - accDecTime))//停止 { double vStart = RAngle / renZiJiaPeriod.TotalSeconds;//开始速度 °/s double vEnd = 0;//结束速度 °/s double a = (vEnd - vStart) / accDecTime.TotalSeconds;//加速度 °/s^2 dAngle = (vStart + vEnd) / 2 * accDecTime.TotalSeconds;//总角度 ° } else //加速 { TimeSpan ts = dtEnd - dt; double vStart = 0; //开始速度 °/s double vEnd = RAngle / renZiJiaPeriod.TotalSeconds;//结束速度 °/s double a = (vEnd - vStart) / accDecTime.TotalSeconds;//加速度 °/s^2 double vCurr = vEnd - a * ts.TotalSeconds;//当前速度 //开始速度 °/s dAngle = (vEnd + vCurr) / 2 * ts.TotalSeconds;//总角度 ° } if (noStart == 0) angle = -RAngle / 2 - dAngle; else angle = RAngle / 2 + dAngle; } /// <summary> /// 时间点查找旋转角度 -180~180 /// </summary> /// <param name="dt">时间</param> /// <param name="modifyparam">当dt 发生在限位列表的未来,是否修改参数,如RenZiJiaPeriod</param> /// <param name="angle"></param> /// <param name="direction"></param> /// <param name="rotationCnt"></param> /// <param name="inCV"></param> bool LimitListSearch(DateTime dt, bool modifyparam, out double angle, out Misc.DIRECTION direction, out int rotationCnt, out bool inCV) { int index; ListFindIndexResult ret = LimitList_FindIndex(dt, out index); DateTime dt_start; DateTime dt_end; int no; //TODO,rotationCnt 还没处理!!!!! switch (ret) { case ListFindIndexResult.Future://发生在未来 { if (dt < mDefaultTime) mDefaultTime = dt - TimeSpan.FromSeconds(10); //检测是否需要修正 RenZiJiaPeriod if (mLimitList.Count == 0) { dt_start = mDefaultTime; no = 0; } else { dt_start = mLimitList[index].dt_end; no = mLimitList[index].no; } rotationCnt = LimitIndexToRotationCnt(index); if (dt > (dt_start + RenZiJiaPeriod)) { if (modifyparam) { //修正!!!! RenZiJiaPeriod = dt - dt_start + TimeSpan.FromSeconds(10); } else { dt = dt_start + RenZiJiaPeriod;//只是为了看,随便了。 时间停止下来 } } dt_end = dt_start + RenZiJiaPeriod; inCV = true; LimitListCal_ConstantVelocity(dt_end, dt_start, no, dt, out angle, out direction); } break; case ListFindIndexResult.BetweenCells: { dt_start = mLimitList[index].dt_end; dt_end = mLimitList[index + 1].dt_begin; no = mLimitList[index].no; rotationCnt = LimitIndexToRotationCnt(index); inCV = true; LimitListCal_ConstantVelocity(dt_end, dt_start, no, dt, out angle, out direction); } break; case ListFindIndexResult.Past://过去 { if (mLimitList[0].no == 0) no = 1; else no = 0; dt_start = mDefaultTime; dt_end = mLimitList[0].dt_begin; if (dt < dt_start) { //数据发生在太遥远的过去,删除!!!!! angle = 0; direction = DIRECTION.FIX; rotationCnt = -1; inCV = false; return false; } rotationCnt = LimitIndexToRotationCnt(-1); inCV = true; LimitListCal_ConstantVelocity(dt_end, dt_start, no, dt, out angle, out direction); } break; case ListFindIndexResult.InCell://TODO { dt_start = mLimitList[index].dt_begin; dt_end = mLimitList[index].dt_end; no = mLimitList[index].no; rotationCnt = LimitIndexToRotationCnt(index); DateTime dtMid = dt_start + TimeSpan.FromTicks((dt_end - dt_start).Ticks / 2); if (dt < dtMid)//以限位的中间时间为分界 { rotationCnt--; } TimeSpan renZiJiaPeriod; if (index >= 1) { renZiJiaPeriod = mLimitList[index].dt_begin - mLimitList[index - 1].dt_end; } else { renZiJiaPeriod = mLimitList[index].dt_begin - mDefaultTime; } inCV = false; LimitListCal_SpeedChange(dt_end, dt_start, no, dt, renZiJiaPeriod, out angle, out direction); } break; default: //case ListFindIndexResult.MaybeInCell: { dt_start = mLimitList[index].dt_begin; dt_end = dt_start + LimitSignTime; if (dt > dt_end) { if (modifyparam) //基本不会发生!!! { LimitSignTime = dt - dt_start + TimeSpan.FromSeconds(5); dt_end = dt_start + LimitSignTime; } else { dt_end = dt; } } no = mLimitList[index].no; rotationCnt = LimitIndexToRotationCnt(index); DateTime dtMid = dt_start + TimeSpan.FromTicks((dt_end - dt_start).Ticks / 2); if (dt < dtMid)//以限位的中间时间为分界 { rotationCnt--; } TimeSpan renZiJiaPeriod; if (index >= 1) { renZiJiaPeriod = mLimitList[index].dt_begin - mLimitList[index - 1].dt_end; } else { renZiJiaPeriod = mLimitList[index].dt_begin - mDefaultTime; } inCV = false; LimitListCal_SpeedChange(dt_end, dt_start, no, dt, renZiJiaPeriod, out angle, out direction); } break; } return true; } enum ListFindIndexResult { /// <summary> /// 发生在未来, index.dt_end ~ 未来, 可能 index都还没有发生 /// </summary> Future, /// <summary> /// 发生在index.dt_begin ~ index.dt_end /// </summary> InCell, /// <summary> /// 应该发生在index.dt_begin ~ index.dt_end, 但 index.dt_end还没有数据 /// </summary> MaybeInCell, /// <summary> /// 发生在 index.dt_end ~ (index+1).dt_begin /// </summary> BetweenCells, /// <summary> /// 发生在 过去 /// </summary> Past } /// <summary> /// </summary> /// <param name="dt"></param> /// <param name="index"></param> /// <returns></returns> ListFindIndexResult LimitList_FindIndex(DateTime dt, out int index) { index = 0; if (mLimitList.Count <= 0)//没数据,所以是发生在未来的 return ListFindIndexResult.Future; for (int i = mLimitList.Count - 1; i >= 0; i--) { LimitCell lc = mLimitList[i]; if (dt >= lc.dt_begin) { //找到了 index = i; if (lc.dt_end == DateTime.MinValue) { return ListFindIndexResult.MaybeInCell; } else if (dt <= mLimitList[i].dt_end) { return ListFindIndexResult.InCell; } else if (i == (mLimitList.Count - 1))//这个是未来的时间点,还没发生!!! { return ListFindIndexResult.Future; } else { return ListFindIndexResult.BetweenCells; } } } //这个在过去的时间 return ListFindIndexResult.Past; } /// <summary> /// 获取膜角度, TODO /// </summary> /// <param name="a">与0°的偏移, 0~180</param> /// <param name="dt">时间点</param> /// <param name="angle0">上面的膜对应的膜角度</param> /// <param name="angle1">下面的膜对应的膜角度</param> /// <param name="direction">人字架正转?反转? FIX, 停止!</param> /// <param name="rotationCnt">扫描次数</param> /// <param name="inCV">在恒速阶段</param> bool GetAngle(double a, DateTime dt, out double angle0, out double angle1, out Misc.DIRECTION direction, out int rotationCnt, out bool inCV) { angle0 = 0; angle1 = 0; direction = Misc.DIRECTION.FORWARD; rotationCnt = 0; inCV = true; if (a > 180 || a < 0) return false; //压扁对面的角度 double a_other = 360 - a; double angle; bool r = LimitListSearch(dt, false, out angle, out direction, out rotationCnt, out inCV); if (r == false) return false; a += angle; if (a >= 360) a -= 360; else if (a < 0) a += 360; a_other += angle; if (a_other >= 360) a_other -= 360; else if (a_other < 0) a_other += 360; angle0 = a; angle1 = a_other; return true; } #region INotifyPropertyChanged 成员 public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname)); } } #endregion } public class ScanPosCell { public DateTime dt = DateTime.MinValue; public int pos;//位于膜的位置,不是机架 public int thick;//两层的厚度 //由pos转换!! public double angle1;//上层 位于膜泡的角度 0~360° public double angle2;//下层 位于膜泡的角度 0~360° public Misc.DIRECTION direction;//只有一整副数据都是同一个方向,才能解方程 public double rotateAngle;//0~180° 可以认为是夹扁后膜的变形 public ScanPosCell Clone() { ScanPosCell scanpos = new ScanPosCell(); scanpos.dt = dt; scanpos.pos = pos; scanpos.thick = thick; scanpos.angle1 = angle1; scanpos.angle2 = angle2; scanpos.direction = direction; scanpos.rotateAngle = rotateAngle; return scanpos; } } public class EquationCell { public Dictionary<int, int> boltPower = new Dictionary<int, int>();//key=boltindex, value=power public Dictionary<int, int> heatRotatePower = new Dictionary<int, int>();//key=boltindex, value=power public int thick = 0; public int mainBolt;//主分区号 public void Add(int boltindex1, int boltindex2, int thick) { if (boltPower.ContainsKey(boltindex1)) { boltPower[boltindex1]++; } else { boltPower.Add(boltindex1, 1); } if (boltPower.ContainsKey(boltindex2)) { boltPower[boltindex2]++; } else { boltPower.Add(boltindex2, 1); } this.thick += thick; } public override string ToString() { string str = ""; foreach (var b in boltPower) { str += "[" + b.Key.ToString() + "," + b.Value.ToString() + "] "; } return str; } public double[] GetPower(int nbolts) { double[] power = new double[nbolts]; for (int i = 0; i < power.Count(); i++) power[i] = 0; foreach (var kv in boltPower) { power[kv.Key] = kv.Value; } return power; } public int GetAvg() { int cnt = 0; for (int i = 0; i < boltPower.Count(); i++) { cnt += boltPower.Values.ElementAt(i); } if (cnt > 0) return thick / cnt; else return Misc.MyBase.NULL_VALUE; } } public class ScanInfoCell : INotifyPropertyChanged { /// <summary> /// 已经解完方程 /// </summary> public bool HasCast { get; set; } #region 数据 public enum STATE { /// <summary> /// 获取角度出错 /// </summary> Err_GetAngle, /// <summary> /// 解方程出错 /// </summary> Err_Solve, /// <summary> /// 解方程可能出错 /// </summary> ERR_Solve_Maybe, /// <summary> /// 没问题 /// </summary> OK } public STATE State { get; set; } = STATE.OK; /// <summary> /// 扫描时间 /// </summary> public DateTime StartTime { get; set; } = DateTime.MinValue; /// <summary> /// 旋转架旋转的方向,当同一次扫描中,有两个方向,Direction= Fix; /// </summary> public Misc.DIRECTION Direction { get; set; } = DIRECTION.FIX; /// <summary> /// 当前在观察 /// </summary> public bool IsFocus { get; set; } /// <summary> /// 当前在观察 /// </summary> public bool IsFocus2 { get; set; } /// <summary> /// 原始数据(时间,位置,总厚度) /// </summary> public List<ScanPosCell> frame = new List<ScanPosCell>(); /// <summary> /// 方程列表 /// </summary> public List<EquationCell> equationList = new List<EquationCell>(); /// <summary> /// 下面解方程得到的一幅数据对应的数据,与 StartTime 不一样 /// </summary> public DateTime ThicksDT { get; set; } = DateTime.MinValue; /// <summary> /// 一幅数据 /// </summary> public int[] thicks; /// <summary> /// 分区数 /// </summary> public int nbolts; /// <summary> /// 复位区号 /// </summary> public int orgboltno; #endregion #region 分析观察 /// <summary> /// 解方程 用的scaninfo /// </summary> public List<int> solveMember = new List<int>(); /// <summary> /// 每个分区出现次数, 上下分区各不相同 /// </summary> public int[] power; /// <summary> /// 只用当前 scaninfo 的方程。 算出 厚度。 /// </summary> public int[] thicks_avg; #endregion public ScanInfoCell(int nbolts, int orgboltno) { this.nbolts = nbolts; this.orgboltno = orgboltno; thicks = new int[nbolts]; power = new int[nbolts]; thicks_avg = new int[nbolts]; for (int i = 0; i < nbolts; i++) { thicks[i] = Misc.MyBase.NULL_VALUE; thicks_avg[i] = Misc.MyBase.NULL_VALUE; } } int GetBoltIndexFromAngle(double a) { int boltno1st = 1; int nbolts = thicks.Count(); int boltindex = (int)(a / 360 * nbolts); boltindex += orgboltno - boltno1st; if (boltindex >= nbolts) boltindex -= nbolts; else if (boltindex < 0) boltindex += nbolts; return boltindex; } /// <summary> /// 完成了一些方程,返回true /// </summary> /// <param name="scanpos_idx"></param> /// <returns></returns> public bool AddEquation(int scanpos_idx,double sensor_angle) { ScanPosCell scanpos = frame[scanpos_idx]; EquationCell equation = new EquationCell(); equationList.Add(equation); //TOD0 equation.Add( GetBoltIndexFromAngle(scanpos.angle1), GetBoltIndexFromAngle(scanpos.angle2), scanpos.thick); for (int i = 1; i < sensor_angle / 2; i++) { equation.Add( GetBoltIndexFromAngle(scanpos.angle1 + i), GetBoltIndexFromAngle(scanpos.angle2 + i), scanpos.thick); equation.Add( GetBoltIndexFromAngle(scanpos.angle1 - i), GetBoltIndexFromAngle(scanpos.angle2 - i), scanpos.thick); } return true; } #region INotifyPropertyChanged 接口 protected void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; #endregion } }