Compare commits
2 Commits
21decdbbe9
...
e8cdb27687
| Author | SHA1 | Date |
|---|---|---|
|
|
e8cdb27687 | |
|
|
c9ad8d2aff |
|
|
@ -36,6 +36,7 @@ namespace AgroBase.Forms.Direcional
|
|||
this.lblTipoMovimento = new System.Windows.Forms.Label();
|
||||
this.btnSalvarSentido = new System.Windows.Forms.Button();
|
||||
this.gpbDF = new System.Windows.Forms.GroupBox();
|
||||
this.chbDFUsar = new System.Windows.Forms.CheckBox();
|
||||
this.label5 = new System.Windows.Forms.Label();
|
||||
this.txtDFOffsetAngulo = new System.Windows.Forms.TextBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
|
|
@ -47,6 +48,7 @@ namespace AgroBase.Forms.Direcional
|
|||
this.lblDFEsquerda = new System.Windows.Forms.Label();
|
||||
this.lblDFDireita = new System.Windows.Forms.Label();
|
||||
this.gpbEF = new System.Windows.Forms.GroupBox();
|
||||
this.chbEFUsar = new System.Windows.Forms.CheckBox();
|
||||
this.label6 = new System.Windows.Forms.Label();
|
||||
this.txtEFOffsetAngulo = new System.Windows.Forms.TextBox();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
|
|
@ -58,6 +60,7 @@ namespace AgroBase.Forms.Direcional
|
|||
this.lblEFEsquerda = new System.Windows.Forms.Label();
|
||||
this.lblEFDireita = new System.Windows.Forms.Label();
|
||||
this.gpbDT = new System.Windows.Forms.GroupBox();
|
||||
this.chbDTUsar = new System.Windows.Forms.CheckBox();
|
||||
this.label8 = new System.Windows.Forms.Label();
|
||||
this.txtDTOffsetAngulo = new System.Windows.Forms.TextBox();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
|
|
@ -69,6 +72,7 @@ namespace AgroBase.Forms.Direcional
|
|||
this.lblDTEsquerda = new System.Windows.Forms.Label();
|
||||
this.lblDTDireita = new System.Windows.Forms.Label();
|
||||
this.gpbET = new System.Windows.Forms.GroupBox();
|
||||
this.chbETUsar = new System.Windows.Forms.CheckBox();
|
||||
this.label7 = new System.Windows.Forms.Label();
|
||||
this.txtETOffsetAngulo = new System.Windows.Forms.TextBox();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
|
|
@ -79,10 +83,14 @@ namespace AgroBase.Forms.Direcional
|
|||
this.cmbETEsquerda = new System.Windows.Forms.ComboBox();
|
||||
this.lblETEsquerda = new System.Windows.Forms.Label();
|
||||
this.lblETDireita = new System.Windows.Forms.Label();
|
||||
this.chbDFUsar = new System.Windows.Forms.CheckBox();
|
||||
this.chbDTUsar = new System.Windows.Forms.CheckBox();
|
||||
this.chbEFUsar = new System.Windows.Forms.CheckBox();
|
||||
this.chbETUsar = new System.Windows.Forms.CheckBox();
|
||||
this.txtDTOffsetRef = new System.Windows.Forms.TextBox();
|
||||
this.label9 = new System.Windows.Forms.Label();
|
||||
this.label10 = new System.Windows.Forms.Label();
|
||||
this.txtETOffsetRef = new System.Windows.Forms.TextBox();
|
||||
this.label11 = new System.Windows.Forms.Label();
|
||||
this.txtEFOffsetRef = new System.Windows.Forms.TextBox();
|
||||
this.label12 = new System.Windows.Forms.Label();
|
||||
this.txtDFOffsetRef = new System.Windows.Forms.TextBox();
|
||||
this.gpbSentido.SuspendLayout();
|
||||
this.gpbDF.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.nudDFOffset)).BeginInit();
|
||||
|
|
@ -109,7 +117,7 @@ namespace AgroBase.Forms.Direcional
|
|||
this.gpbSentido.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.gpbSentido.Name = "gpbSentido";
|
||||
this.gpbSentido.Padding = new System.Windows.Forms.Padding(2);
|
||||
this.gpbSentido.Size = new System.Drawing.Size(376, 427);
|
||||
this.gpbSentido.Size = new System.Drawing.Size(376, 463);
|
||||
this.gpbSentido.TabIndex = 80;
|
||||
this.gpbSentido.TabStop = false;
|
||||
this.gpbSentido.Text = "Sentido de giro";
|
||||
|
|
@ -117,7 +125,7 @@ namespace AgroBase.Forms.Direcional
|
|||
// lblIP
|
||||
//
|
||||
this.lblIP.AutoSize = true;
|
||||
this.lblIP.Location = new System.Drawing.Point(9, 378);
|
||||
this.lblIP.Location = new System.Drawing.Point(9, 422);
|
||||
this.lblIP.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.lblIP.Name = "lblIP";
|
||||
this.lblIP.Size = new System.Drawing.Size(17, 13);
|
||||
|
|
@ -126,7 +134,7 @@ namespace AgroBase.Forms.Direcional
|
|||
//
|
||||
// txtIP
|
||||
//
|
||||
this.txtIP.Location = new System.Drawing.Point(12, 395);
|
||||
this.txtIP.Location = new System.Drawing.Point(12, 439);
|
||||
this.txtIP.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtIP.Name = "txtIP";
|
||||
this.txtIP.Size = new System.Drawing.Size(85, 20);
|
||||
|
|
@ -154,7 +162,7 @@ namespace AgroBase.Forms.Direcional
|
|||
//
|
||||
// btnSalvarSentido
|
||||
//
|
||||
this.btnSalvarSentido.Location = new System.Drawing.Point(275, 391);
|
||||
this.btnSalvarSentido.Location = new System.Drawing.Point(275, 435);
|
||||
this.btnSalvarSentido.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.btnSalvarSentido.Name = "btnSalvarSentido";
|
||||
this.btnSalvarSentido.Size = new System.Drawing.Size(88, 24);
|
||||
|
|
@ -165,6 +173,8 @@ namespace AgroBase.Forms.Direcional
|
|||
//
|
||||
// gpbDF
|
||||
//
|
||||
this.gpbDF.Controls.Add(this.label12);
|
||||
this.gpbDF.Controls.Add(this.txtDFOffsetRef);
|
||||
this.gpbDF.Controls.Add(this.chbDFUsar);
|
||||
this.gpbDF.Controls.Add(this.label5);
|
||||
this.gpbDF.Controls.Add(this.txtDFOffsetAngulo);
|
||||
|
|
@ -180,11 +190,21 @@ namespace AgroBase.Forms.Direcional
|
|||
this.gpbDF.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.gpbDF.Name = "gpbDF";
|
||||
this.gpbDF.Padding = new System.Windows.Forms.Padding(2);
|
||||
this.gpbDF.Size = new System.Drawing.Size(168, 152);
|
||||
this.gpbDF.Size = new System.Drawing.Size(168, 175);
|
||||
this.gpbDF.TabIndex = 9;
|
||||
this.gpbDF.TabStop = false;
|
||||
this.gpbDF.Text = "M4 - Direito Frente (DF)";
|
||||
//
|
||||
// chbDFUsar
|
||||
//
|
||||
this.chbDFUsar.AutoSize = true;
|
||||
this.chbDFUsar.Location = new System.Drawing.Point(127, 124);
|
||||
this.chbDFUsar.Name = "chbDFUsar";
|
||||
this.chbDFUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbDFUsar.TabIndex = 19;
|
||||
this.chbDFUsar.Text = "L";
|
||||
this.chbDFUsar.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.AutoSize = true;
|
||||
|
|
@ -294,6 +314,8 @@ namespace AgroBase.Forms.Direcional
|
|||
//
|
||||
// gpbEF
|
||||
//
|
||||
this.gpbEF.Controls.Add(this.label11);
|
||||
this.gpbEF.Controls.Add(this.txtEFOffsetRef);
|
||||
this.gpbEF.Controls.Add(this.chbEFUsar);
|
||||
this.gpbEF.Controls.Add(this.label6);
|
||||
this.gpbEF.Controls.Add(this.txtEFOffsetAngulo);
|
||||
|
|
@ -309,11 +331,21 @@ namespace AgroBase.Forms.Direcional
|
|||
this.gpbEF.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.gpbEF.Name = "gpbEF";
|
||||
this.gpbEF.Padding = new System.Windows.Forms.Padding(2);
|
||||
this.gpbEF.Size = new System.Drawing.Size(168, 152);
|
||||
this.gpbEF.Size = new System.Drawing.Size(168, 175);
|
||||
this.gpbEF.TabIndex = 9;
|
||||
this.gpbEF.TabStop = false;
|
||||
this.gpbEF.Text = "M2 - Esquerdo Frente (EF)";
|
||||
//
|
||||
// chbEFUsar
|
||||
//
|
||||
this.chbEFUsar.AutoSize = true;
|
||||
this.chbEFUsar.Location = new System.Drawing.Point(127, 124);
|
||||
this.chbEFUsar.Name = "chbEFUsar";
|
||||
this.chbEFUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbEFUsar.TabIndex = 21;
|
||||
this.chbEFUsar.Text = "L";
|
||||
this.chbEFUsar.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label6
|
||||
//
|
||||
this.label6.AutoSize = true;
|
||||
|
|
@ -423,6 +455,8 @@ namespace AgroBase.Forms.Direcional
|
|||
//
|
||||
// gpbDT
|
||||
//
|
||||
this.gpbDT.Controls.Add(this.label9);
|
||||
this.gpbDT.Controls.Add(this.txtDTOffsetRef);
|
||||
this.gpbDT.Controls.Add(this.chbDTUsar);
|
||||
this.gpbDT.Controls.Add(this.label8);
|
||||
this.gpbDT.Controls.Add(this.txtDTOffsetAngulo);
|
||||
|
|
@ -434,15 +468,25 @@ namespace AgroBase.Forms.Direcional
|
|||
this.gpbDT.Controls.Add(this.cmbDTEsquerda);
|
||||
this.gpbDT.Controls.Add(this.lblDTEsquerda);
|
||||
this.gpbDT.Controls.Add(this.lblDTDireita);
|
||||
this.gpbDT.Location = new System.Drawing.Point(195, 216);
|
||||
this.gpbDT.Location = new System.Drawing.Point(195, 239);
|
||||
this.gpbDT.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.gpbDT.Name = "gpbDT";
|
||||
this.gpbDT.Padding = new System.Windows.Forms.Padding(2);
|
||||
this.gpbDT.Size = new System.Drawing.Size(168, 152);
|
||||
this.gpbDT.Size = new System.Drawing.Size(168, 175);
|
||||
this.gpbDT.TabIndex = 9;
|
||||
this.gpbDT.TabStop = false;
|
||||
this.gpbDT.Text = "M3 - Direito Trás (DT)";
|
||||
//
|
||||
// chbDTUsar
|
||||
//
|
||||
this.chbDTUsar.AutoSize = true;
|
||||
this.chbDTUsar.Location = new System.Drawing.Point(125, 124);
|
||||
this.chbDTUsar.Name = "chbDTUsar";
|
||||
this.chbDTUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbDTUsar.TabIndex = 23;
|
||||
this.chbDTUsar.Text = "L";
|
||||
this.chbDTUsar.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label8
|
||||
//
|
||||
this.label8.AutoSize = true;
|
||||
|
|
@ -552,6 +596,8 @@ namespace AgroBase.Forms.Direcional
|
|||
//
|
||||
// gpbET
|
||||
//
|
||||
this.gpbET.Controls.Add(this.label10);
|
||||
this.gpbET.Controls.Add(this.txtETOffsetRef);
|
||||
this.gpbET.Controls.Add(this.chbETUsar);
|
||||
this.gpbET.Controls.Add(this.label7);
|
||||
this.gpbET.Controls.Add(this.txtETOffsetAngulo);
|
||||
|
|
@ -563,15 +609,25 @@ namespace AgroBase.Forms.Direcional
|
|||
this.gpbET.Controls.Add(this.cmbETEsquerda);
|
||||
this.gpbET.Controls.Add(this.lblETEsquerda);
|
||||
this.gpbET.Controls.Add(this.lblETDireita);
|
||||
this.gpbET.Location = new System.Drawing.Point(13, 216);
|
||||
this.gpbET.Location = new System.Drawing.Point(13, 239);
|
||||
this.gpbET.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.gpbET.Name = "gpbET";
|
||||
this.gpbET.Padding = new System.Windows.Forms.Padding(2);
|
||||
this.gpbET.Size = new System.Drawing.Size(168, 152);
|
||||
this.gpbET.Size = new System.Drawing.Size(168, 175);
|
||||
this.gpbET.TabIndex = 0;
|
||||
this.gpbET.TabStop = false;
|
||||
this.gpbET.Text = "M1 - Esquerdo Trás (ET)";
|
||||
//
|
||||
// chbETUsar
|
||||
//
|
||||
this.chbETUsar.AutoSize = true;
|
||||
this.chbETUsar.Location = new System.Drawing.Point(127, 124);
|
||||
this.chbETUsar.Name = "chbETUsar";
|
||||
this.chbETUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbETUsar.TabIndex = 23;
|
||||
this.chbETUsar.Text = "L";
|
||||
this.chbETUsar.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label7
|
||||
//
|
||||
this.label7.AutoSize = true;
|
||||
|
|
@ -679,52 +735,88 @@ namespace AgroBase.Forms.Direcional
|
|||
this.lblETDireita.TabIndex = 2;
|
||||
this.lblETDireita.Text = "Direita";
|
||||
//
|
||||
// chbDFUsar
|
||||
// txtDTOffsetRef
|
||||
//
|
||||
this.chbDFUsar.AutoSize = true;
|
||||
this.chbDFUsar.Location = new System.Drawing.Point(127, 124);
|
||||
this.chbDFUsar.Name = "chbDFUsar";
|
||||
this.chbDFUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbDFUsar.TabIndex = 19;
|
||||
this.chbDFUsar.Text = "L";
|
||||
this.chbDFUsar.UseVisualStyleBackColor = true;
|
||||
this.txtDTOffsetRef.Location = new System.Drawing.Point(65, 146);
|
||||
this.txtDTOffsetRef.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtDTOffsetRef.Name = "txtDTOffsetRef";
|
||||
this.txtDTOffsetRef.Size = new System.Drawing.Size(57, 20);
|
||||
this.txtDTOffsetRef.TabIndex = 24;
|
||||
this.txtDTOffsetRef.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// chbDTUsar
|
||||
// label9
|
||||
//
|
||||
this.chbDTUsar.AutoSize = true;
|
||||
this.chbDTUsar.Location = new System.Drawing.Point(125, 124);
|
||||
this.chbDTUsar.Name = "chbDTUsar";
|
||||
this.chbDTUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbDTUsar.TabIndex = 23;
|
||||
this.chbDTUsar.Text = "L";
|
||||
this.chbDTUsar.UseVisualStyleBackColor = true;
|
||||
this.label9.AutoSize = true;
|
||||
this.label9.Location = new System.Drawing.Point(10, 149);
|
||||
this.label9.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.label9.Name = "label9";
|
||||
this.label9.Size = new System.Drawing.Size(55, 13);
|
||||
this.label9.TabIndex = 25;
|
||||
this.label9.Text = "Offset Ref";
|
||||
//
|
||||
// chbEFUsar
|
||||
// label10
|
||||
//
|
||||
this.chbEFUsar.AutoSize = true;
|
||||
this.chbEFUsar.Location = new System.Drawing.Point(127, 124);
|
||||
this.chbEFUsar.Name = "chbEFUsar";
|
||||
this.chbEFUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbEFUsar.TabIndex = 21;
|
||||
this.chbEFUsar.Text = "L";
|
||||
this.chbEFUsar.UseVisualStyleBackColor = true;
|
||||
this.label10.AutoSize = true;
|
||||
this.label10.Location = new System.Drawing.Point(10, 149);
|
||||
this.label10.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.label10.Name = "label10";
|
||||
this.label10.Size = new System.Drawing.Size(55, 13);
|
||||
this.label10.TabIndex = 27;
|
||||
this.label10.Text = "Offset Ref";
|
||||
//
|
||||
// chbETUsar
|
||||
// txtETOffsetRef
|
||||
//
|
||||
this.chbETUsar.AutoSize = true;
|
||||
this.chbETUsar.Location = new System.Drawing.Point(127, 124);
|
||||
this.chbETUsar.Name = "chbETUsar";
|
||||
this.chbETUsar.Size = new System.Drawing.Size(32, 17);
|
||||
this.chbETUsar.TabIndex = 23;
|
||||
this.chbETUsar.Text = "L";
|
||||
this.chbETUsar.UseVisualStyleBackColor = true;
|
||||
this.txtETOffsetRef.Location = new System.Drawing.Point(65, 146);
|
||||
this.txtETOffsetRef.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtETOffsetRef.Name = "txtETOffsetRef";
|
||||
this.txtETOffsetRef.Size = new System.Drawing.Size(57, 20);
|
||||
this.txtETOffsetRef.TabIndex = 26;
|
||||
this.txtETOffsetRef.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// label11
|
||||
//
|
||||
this.label11.AutoSize = true;
|
||||
this.label11.Location = new System.Drawing.Point(11, 149);
|
||||
this.label11.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.label11.Name = "label11";
|
||||
this.label11.Size = new System.Drawing.Size(55, 13);
|
||||
this.label11.TabIndex = 29;
|
||||
this.label11.Text = "Offset Ref";
|
||||
//
|
||||
// txtEFOffsetRef
|
||||
//
|
||||
this.txtEFOffsetRef.Location = new System.Drawing.Point(66, 146);
|
||||
this.txtEFOffsetRef.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtEFOffsetRef.Name = "txtEFOffsetRef";
|
||||
this.txtEFOffsetRef.Size = new System.Drawing.Size(57, 20);
|
||||
this.txtEFOffsetRef.TabIndex = 28;
|
||||
this.txtEFOffsetRef.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// label12
|
||||
//
|
||||
this.label12.AutoSize = true;
|
||||
this.label12.Location = new System.Drawing.Point(10, 149);
|
||||
this.label12.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.label12.Name = "label12";
|
||||
this.label12.Size = new System.Drawing.Size(55, 13);
|
||||
this.label12.TabIndex = 31;
|
||||
this.label12.Text = "Offset Ref";
|
||||
//
|
||||
// txtDFOffsetRef
|
||||
//
|
||||
this.txtDFOffsetRef.Location = new System.Drawing.Point(65, 146);
|
||||
this.txtDFOffsetRef.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.txtDFOffsetRef.Name = "txtDFOffsetRef";
|
||||
this.txtDFOffsetRef.Size = new System.Drawing.Size(57, 20);
|
||||
this.txtDFOffsetRef.TabIndex = 30;
|
||||
this.txtDFOffsetRef.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
//
|
||||
// frmDirConfig
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.White;
|
||||
this.ClientSize = new System.Drawing.Size(398, 447);
|
||||
this.ClientSize = new System.Drawing.Size(398, 485);
|
||||
this.Controls.Add(this.gpbSentido);
|
||||
this.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.Name = "frmDirConfig";
|
||||
|
|
@ -805,5 +897,13 @@ namespace AgroBase.Forms.Direcional
|
|||
private System.Windows.Forms.CheckBox chbEFUsar;
|
||||
private System.Windows.Forms.CheckBox chbDTUsar;
|
||||
private System.Windows.Forms.CheckBox chbETUsar;
|
||||
private System.Windows.Forms.Label label12;
|
||||
private System.Windows.Forms.TextBox txtDFOffsetRef;
|
||||
private System.Windows.Forms.Label label11;
|
||||
private System.Windows.Forms.TextBox txtEFOffsetRef;
|
||||
private System.Windows.Forms.Label label9;
|
||||
private System.Windows.Forms.TextBox txtDTOffsetRef;
|
||||
private System.Windows.Forms.Label label10;
|
||||
private System.Windows.Forms.TextBox txtETOffsetRef;
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,9 @@ namespace AgroBase.Forms.Direcional
|
|||
TextBox txtOffsetAngulo = FuncoesGlobais.FindControlRecursive<TextBox>(gpbSentido, "txt" + Modulo.Modulo_ID + "OffsetAngulo");
|
||||
txtOffsetAngulo.Text = Modulo.DirMotor.OffsetAnguloReal.ToString();
|
||||
|
||||
TextBox txtOffsetRef = FuncoesGlobais.FindControlRecursive<TextBox>(gpbSentido, "txt" + Modulo.Modulo_ID + "OffsetRef");
|
||||
txtOffsetRef.Text = Modulo.DirMotor.OffsetReferenciamento.ToString();
|
||||
|
||||
CheckBox chbUsar = FuncoesGlobais.FindControlRecursive<CheckBox>(gpbSentido, "chb" + Modulo.Modulo_ID + "Usar");
|
||||
chbUsar.Checked = Modulo.DirMotor.UsarAnguloSensor;
|
||||
}
|
||||
|
|
@ -119,6 +122,9 @@ namespace AgroBase.Forms.Direcional
|
|||
TextBox txtOffsetAngulo = FuncoesGlobais.FindControlRecursive<TextBox>(gpbSentido, "txt" + Modulo.Modulo_ID + "OffsetAngulo");
|
||||
Modulo.DirMotor.OffsetAnguloReal = Convert.ToDouble(txtOffsetAngulo.Text);
|
||||
|
||||
TextBox txtOffsetRef = FuncoesGlobais.FindControlRecursive<TextBox>(gpbSentido, "txt" + Modulo.Modulo_ID + "OffsetRef");
|
||||
Modulo.DirMotor.OffsetReferenciamento = Convert.ToDouble(txtOffsetRef.Text);
|
||||
|
||||
CheckBox chbUsar = FuncoesGlobais.FindControlRecursive<CheckBox>(gpbSentido, "chb" + Modulo.Modulo_ID + "Usar");
|
||||
Modulo.DirMotor.UsarAnguloSensor = chbUsar.Checked;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -339,13 +339,28 @@ namespace AgroBase.Models.Modules
|
|||
{
|
||||
var op = Variaveis.OperacaoEmAndamento;
|
||||
|
||||
if (op.CalibrandoRuntime)
|
||||
if (op == null)
|
||||
return;
|
||||
|
||||
long tickSolicitado = Stopwatch.GetTimestamp();
|
||||
|
||||
lock (_metricasLock)
|
||||
{
|
||||
long agoraTicks = Stopwatch.GetTimestamp();
|
||||
DateTime agora = DateTime.Now;
|
||||
double esperaLockSeg = (agoraTicks - tickSolicitado) / (double)Stopwatch.Frequency;
|
||||
double tempoMovimentoAtualSeg = op.Sensoriamento?.Movimentacao?.TempoMovimentoSegs ?? 0.0;
|
||||
|
||||
if (op.CalibrandoRuntime)
|
||||
{
|
||||
/*
|
||||
* Durante a calibragem não integra, mas mantém
|
||||
* o relógio ancorado no instante atual.
|
||||
*/
|
||||
ReancorarMetricas(agoraTicks, tempoMovimentoAtualSeg, encerrarTrechos: true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool operacaoConcluida = op.Sensoriamento?.Operacao?.StatusOperacaoAtual == StatusOperacao.Concluido;
|
||||
bool operacaoAtiva = op.Sensoriamento?.Operacao?.OperacaoIniciada == true && !operacaoConcluida;
|
||||
|
|
@ -411,10 +426,11 @@ namespace AgroBase.Models.Modules
|
|||
: new HashSet<int>();
|
||||
|
||||
double vazaoAtualMLs = operacaoAtiva ? ObterVazaoAtualConfiavelMLs(agora) : 0.0;
|
||||
double tempoMovimentoAtualSeg = op.Sensoriamento?.Movimentacao?.TempoMovimentoSegs ?? 0.0;
|
||||
|
||||
if (_ultimoTickMetricas != 0)
|
||||
{
|
||||
bool haviaAtuacaoNoIntervaloAnterior = _bicosAtivosIntervaloAnterior.Count > 0 || _bombasAtivasIntervaloAnterior.Count > 0 || _vazaoIntervaloAnteriorMLs > 0.01;
|
||||
|
||||
double dt = (agoraTicks - _ultimoTickMetricas) / (double)Stopwatch.Frequency;
|
||||
|
||||
if (dt > 0 && dt <= IntervaloMaximoIntegracaoSeg)
|
||||
|
|
@ -443,8 +459,26 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
if (dt > IntervaloMaximoIntegracaoSeg)
|
||||
{
|
||||
Variaveis.MostrarLog("[ATU/METRICAS] Integração ignorada por dt alto: " + dt.ToString("0.000") + "s");
|
||||
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, "[ATU/METRICAS] Integração ignorada por dt alto: " + dt.ToString("0.000") + "s");
|
||||
string mensagem =
|
||||
"[ATU/METRICAS] Integração ignorada por dt alto: " +
|
||||
$"{dt:0.000}s; " +
|
||||
$"opAtiva={operacaoAtiva}; " +
|
||||
$"atuacaoAnterior={haviaAtuacaoNoIntervaloAnterior}; " +
|
||||
$"bicosAnterior={_bicosAtivosIntervaloAnterior.Count}; " +
|
||||
$"bombasAnterior={_bombasAtivasIntervaloAnterior.Count}; " +
|
||||
$"vazaoAnterior={_vazaoIntervaloAnteriorMLs:0.00}mL/s; " +
|
||||
$"esperaLock={esperaLockSeg * 1000.0:0.0}ms";
|
||||
|
||||
Variaveis.MostrarLog(mensagem);
|
||||
|
||||
/*
|
||||
* Só vira evento operacional se efetivamente
|
||||
* perdemos um intervalo de pulverização.
|
||||
*/
|
||||
if (operacaoAtiva && haviaAtuacaoNoIntervaloAnterior)
|
||||
{
|
||||
op.Sensoriamento?.InserirLog(Dispositivo, StatusModulo.Alerta, 80, mensagem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -494,6 +528,24 @@ namespace AgroBase.Models.Modules
|
|||
}
|
||||
}
|
||||
|
||||
private void ReancorarMetricas(long agoraTicks, double tempoMovimentoAtualSeg, bool encerrarTrechos)
|
||||
{
|
||||
if (encerrarTrechos)
|
||||
{
|
||||
foreach (var bico in BicosPulverizadores ?? new List<AtuadorBicoModel>())
|
||||
{
|
||||
bico?.AtualizarEstadoTrecho(false);
|
||||
}
|
||||
}
|
||||
_ultimoTickMetricas = agoraTicks;
|
||||
_tempoMovimentoAnteriorSeg = tempoMovimentoAtualSeg;
|
||||
_bicosAtivosIntervaloAnterior = new HashSet<int>();
|
||||
_bombasAtivasIntervaloAnterior = new HashSet<int>();
|
||||
_potenciaBombasIntervaloAnterior = new Dictionary<int, double>();
|
||||
_vazaoIntervaloAnteriorMLs = 0.0;
|
||||
ZerarVazoesInstantaneas();
|
||||
}
|
||||
|
||||
private void IntegrarIntervaloAnterior(double dt, double tempoMovimentoIntervaloSeg)
|
||||
{
|
||||
var bicosDoIntervalo = (BicosPulverizadores ??
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace AgroBase.Models.Modules
|
|||
public Sentido Sentido_SP { get; set; } = Sentido.Parado;
|
||||
public double Angulo_SP { get; set; } = 0;
|
||||
public double FolgaMecanica { get; set; } = 0;
|
||||
public double OffsetReferenciamento { get; set; } = 0;
|
||||
public double Velocidade_Max { get; set; } = 600;
|
||||
public int Velocidade
|
||||
{
|
||||
|
|
@ -149,11 +150,16 @@ namespace AgroBase.Models.Modules
|
|||
return !Comandar || Sentido_SP == Sentido.Parado ? 0 : Angulo_SP;
|
||||
}
|
||||
}
|
||||
public double ErroAnguloSP
|
||||
public double ErroAngulo
|
||||
{
|
||||
get
|
||||
{
|
||||
return Math.Abs(AnguloLeitura - AnguloSPAtual);
|
||||
double anguloSp =
|
||||
!Comandar || Sentido_SP == Sentido.Parado
|
||||
? 0
|
||||
: Angulo_SP;
|
||||
|
||||
return anguloSp - AnguloLeitura;
|
||||
}
|
||||
}
|
||||
public bool RetornandoAzero
|
||||
|
|
@ -165,13 +171,7 @@ namespace AgroBase.Models.Modules
|
|||
return foraZero && parar;
|
||||
}
|
||||
}
|
||||
public bool EmMovimento
|
||||
{
|
||||
get
|
||||
{
|
||||
return Math.Abs(RPM) > 1;
|
||||
}
|
||||
}
|
||||
public bool EmMovimento { get; set; }
|
||||
public bool AtingiuAnguloSP
|
||||
{
|
||||
get
|
||||
|
|
@ -207,6 +207,109 @@ namespace AgroBase.Models.Modules
|
|||
}
|
||||
|
||||
|
||||
private DateTime _erroAngularDesde = DateTime.MinValue;
|
||||
private DateTime _recuperacaoAngularDesde = DateTime.MinValue;
|
||||
|
||||
public DateTime UltimaMudancaAnguloSP { get; private set; }
|
||||
public DateTime InicioMovimentoAngular { get; private set; }
|
||||
|
||||
public bool ErroSeguimentoAngularPersistente { get; private set; }
|
||||
|
||||
public void AtualizarSaudeSeguimentoAngular(DateTime agora)
|
||||
{
|
||||
const double toleranciaFalhaGraus = 3.0;
|
||||
const double toleranciaRecuperacaoGraus = 2.0;
|
||||
|
||||
TimeSpan tempoGracaSP = TimeSpan.FromMilliseconds(750);
|
||||
TimeSpan persistenciaFalha = TimeSpan.FromSeconds(2);
|
||||
TimeSpan persistenciaRecuperacao = TimeSpan.FromMilliseconds(500);
|
||||
TimeSpan timeoutMovimento = TimeSpan.FromSeconds(15);
|
||||
|
||||
if (!Comandar)
|
||||
{
|
||||
ResetarSaudeSeguimentoAngular();
|
||||
return;
|
||||
}
|
||||
|
||||
bool emGracaSP =
|
||||
UltimaMudancaAnguloSP != DateTime.MinValue &&
|
||||
agora - UltimaMudancaAnguloSP <= tempoGracaSP;
|
||||
|
||||
bool movimentoDentroDoPrazo =
|
||||
EmMovimento &&
|
||||
InicioMovimentoAngular != DateTime.MinValue &&
|
||||
agora - InicioMovimentoAngular <= timeoutMovimento;
|
||||
|
||||
bool movimentoExcedeuTimeout =
|
||||
EmMovimento &&
|
||||
InicioMovimentoAngular != DateTime.MinValue &&
|
||||
agora - InicioMovimentoAngular > timeoutMovimento;
|
||||
|
||||
double ErroAnguloAbs = Math.Abs(ErroAngulo);
|
||||
|
||||
bool candidatoFalha =
|
||||
ErroAnguloAbs > toleranciaFalhaGraus &&
|
||||
!emGracaSP &&
|
||||
(!EmMovimento || movimentoExcedeuTimeout);
|
||||
|
||||
if (candidatoFalha)
|
||||
{
|
||||
_recuperacaoAngularDesde = DateTime.MinValue;
|
||||
|
||||
if (_erroAngularDesde == DateTime.MinValue)
|
||||
_erroAngularDesde = agora;
|
||||
|
||||
if (agora - _erroAngularDesde >= persistenciaFalha)
|
||||
ErroSeguimentoAngularPersistente = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_erroAngularDesde = DateTime.MinValue;
|
||||
|
||||
bool recuperado =
|
||||
ErroAnguloAbs <= toleranciaRecuperacaoGraus ||
|
||||
movimentoDentroDoPrazo ||
|
||||
emGracaSP;
|
||||
|
||||
if (!recuperado)
|
||||
{
|
||||
_recuperacaoAngularDesde = DateTime.MinValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_recuperacaoAngularDesde == DateTime.MinValue)
|
||||
_recuperacaoAngularDesde = agora;
|
||||
|
||||
if (agora - _recuperacaoAngularDesde >= persistenciaRecuperacao)
|
||||
ErroSeguimentoAngularPersistente = false;
|
||||
}
|
||||
|
||||
private void ResetarSaudeSeguimentoAngular()
|
||||
{
|
||||
_erroAngularDesde = DateTime.MinValue;
|
||||
_recuperacaoAngularDesde = DateTime.MinValue;
|
||||
ErroSeguimentoAngularPersistente = false;
|
||||
}
|
||||
|
||||
public void AtualizarEmMovimento(bool emMovimentoAgora)
|
||||
{
|
||||
DateTime agora = DateTime.Now;
|
||||
|
||||
if (emMovimentoAgora && !EmMovimento)
|
||||
{
|
||||
InicioMovimentoAngular = agora;
|
||||
}
|
||||
|
||||
if (!emMovimentoAgora)
|
||||
{
|
||||
InicioMovimentoAngular = DateTime.MinValue;
|
||||
}
|
||||
|
||||
EmMovimento = emMovimentoAgora;
|
||||
}
|
||||
|
||||
|
||||
public Dictionary<TipoMovimentoDirecional, ConfiguracaoSentidoMotor> ConfigSentidos { get; set; }
|
||||
public List<FuncoesPinout> Funcoes { get; set; }
|
||||
|
||||
|
|
@ -313,38 +416,41 @@ namespace AgroBase.Models.Modules
|
|||
var _Controle = op.Controle;
|
||||
int AnguloTipoMovimento = VariaveisEquipamento.AnguloMovimento[_Controle.TipoMovimento];
|
||||
|
||||
Sentido novoSentido = Sentido.Parado;
|
||||
double novoAngulo = 0;
|
||||
|
||||
if (!Comandar)
|
||||
{
|
||||
Sentido_SP = Sentido.Parado;
|
||||
novoSentido = Sentido.Parado;
|
||||
}
|
||||
else if (Direcao == Direcao.Direita)
|
||||
{
|
||||
Sentido_SP = ConfigSentidos[_Controle.TipoMovimento].Direita;
|
||||
novoSentido = ConfigSentidos[_Controle.TipoMovimento].Direita;
|
||||
}
|
||||
else if (Direcao == Direcao.Esquerda)
|
||||
{
|
||||
Sentido_SP = ConfigSentidos[_Controle.TipoMovimento].Esquerda;
|
||||
novoSentido = ConfigSentidos[_Controle.TipoMovimento].Esquerda;
|
||||
}
|
||||
else
|
||||
{
|
||||
Sentido_SP = Sentido.Parado;
|
||||
novoSentido = Sentido.Parado;
|
||||
}
|
||||
|
||||
UltimaDirecao = Direcao;
|
||||
int mx = Sentido_SP == Sentido.Parado ? 0 : Sentido_SP == Sentido.Antihorario ? -1 : 1;
|
||||
int mx = novoSentido == Sentido.Parado ? 0 : novoSentido == Sentido.Antihorario ? -1 : 1;
|
||||
|
||||
if (Sentido_SP == Sentido.Parado)
|
||||
if (novoSentido == Sentido.Parado)
|
||||
{
|
||||
Angulo_SP = 0;
|
||||
novoAngulo = 0;
|
||||
UltimaDirecao = Direcao.Parado;
|
||||
}
|
||||
else if (AnguloTipoMovimento != -1)
|
||||
{
|
||||
Angulo_SP = AnguloTipoMovimento;
|
||||
novoAngulo = AnguloTipoMovimento;
|
||||
}
|
||||
else
|
||||
{
|
||||
Angulo_SP = _Controle.Angulo;
|
||||
novoAngulo = _Controle.Angulo;
|
||||
}
|
||||
|
||||
if (Comandar)
|
||||
|
|
@ -353,8 +459,16 @@ namespace AgroBase.Models.Modules
|
|||
//Console.WriteLine($"[{Mod_ID}] Angulo Atualizado para {Variaveis.OperacaoEmAndamento.SimulacaoAnguloControle}");
|
||||
}
|
||||
|
||||
Angulo_SP = Math.Abs(Angulo_SP) * mx;
|
||||
Angulo_SP += AnguloFolgaCompensar;
|
||||
novoAngulo = Math.Abs(novoAngulo) * mx;
|
||||
novoAngulo += AnguloFolgaCompensar;
|
||||
|
||||
if (Math.Abs(novoAngulo - Angulo_SP) >= 0.05)
|
||||
{
|
||||
UltimaMudancaAnguloSP = DateTime.Now;
|
||||
}
|
||||
|
||||
Angulo_SP = novoAngulo;
|
||||
Sentido_SP = novoSentido;
|
||||
|
||||
if (_Simulando)
|
||||
{
|
||||
|
|
@ -473,9 +587,7 @@ namespace AgroBase.Models.Modules
|
|||
_sentido = _sentido == Sentido.Horario ? Sentido.Antihorario : Sentido.Horario;
|
||||
Variaveis.MostrarLog($"[DirecionalModel] [ReferenciaMotorRelativo] [DIR {Mod_ID}] - Invertendo Sentido de Giro para {_sentido.ToString()}");
|
||||
|
||||
double offset = Mod_ID == "DT" ? -2 : 0;
|
||||
|
||||
_anguloSP = CalcularCentroCompensado(AnguloEntrada, AnguloSaida, _sentido, offset);
|
||||
_anguloSP = CalcularCentroCompensado(AnguloEntrada, AnguloSaida, _sentido, OffsetReferenciamento);
|
||||
}
|
||||
|
||||
verificacoesAngulo++;
|
||||
|
|
|
|||
|
|
@ -494,6 +494,8 @@ namespace AgroBase.Models.Modules
|
|||
modulo.DirMotor.EnviarComandoControle(controleDir.UltimaDirecao);
|
||||
enviouControle = true;
|
||||
}
|
||||
|
||||
modulo.DirMotor.AtualizarSaudeSeguimentoAngular(agora);
|
||||
}
|
||||
|
||||
if (enviouControle)
|
||||
|
|
|
|||
|
|
@ -625,9 +625,9 @@ namespace AgroBase.Models.Modules
|
|||
posicao = CanMessagePosicaoDados.Dados1,
|
||||
indice = 0,
|
||||
analise_health = true,
|
||||
maximo = 5.2,
|
||||
minimo = 4.9,
|
||||
nominal = 5.0,
|
||||
maximo = 5.3,
|
||||
minimo = 4.75,
|
||||
nominal = 5.1,
|
||||
unidade_medida = "V"
|
||||
},
|
||||
new SensorValoresLeituraModel()
|
||||
|
|
@ -636,9 +636,9 @@ namespace AgroBase.Models.Modules
|
|||
posicao = CanMessagePosicaoDados.Dados1,
|
||||
indice = 1,
|
||||
analise_health = true,
|
||||
maximo = 1500,
|
||||
maximo = 800,
|
||||
minimo = 100,
|
||||
nominal = 280,
|
||||
nominal = 430,
|
||||
unidade_medida = "mA"
|
||||
},
|
||||
new SensorValoresLeituraModel()
|
||||
|
|
|
|||
|
|
@ -4094,6 +4094,21 @@ namespace AgroBase.Models
|
|||
|
||||
op.TempoIniciarOperacao = Math.Max(0, Convert.ToInt32(Math.Round(tempoAguardarInicio)));
|
||||
|
||||
|
||||
JObject emergenciaSistema = RedisService.GetField<JObject>(CtxKey.DadosOperacao, "emergencia_sistema");
|
||||
bool emergenciaSistemicaSolicitada = emergenciaSistema?["solicitada"]?.Value<bool>() ?? RedisService.GetField<bool>(CtxKey.DadosOperacao, "emergencia_sistema_solicitada", false);
|
||||
|
||||
bool emergenciaAnterior = Operacao.Emergencia;
|
||||
bool emergencia = emergenciaAnterior || emergenciaSistemicaSolicitada;
|
||||
|
||||
if (emergenciaSistemicaSolicitada && !emergenciaAnterior)
|
||||
{
|
||||
string codigo = emergenciaSistema?["codigo"]?.Value<string>() ?? "INTERTRAVAMENTO_SISTEMA";
|
||||
string motivo = emergenciaSistema?["motivo"]?.Value<string>() ?? "Intertravamento sistêmico solicitado";
|
||||
int severidade = emergenciaSistema?["severidade"]?.Value<int>() ?? 90;
|
||||
InserirLog(T_Code.Mov, StatusModulo.Falha, Math.Max(0, 100 - severidade), $"Emergência sistêmica acionada [{codigo}]: {motivo}");
|
||||
}
|
||||
|
||||
var operacaoAtualizada = new OperacaoSensoriamentoLogModel()
|
||||
{
|
||||
Momento = agora,
|
||||
|
|
@ -4102,7 +4117,7 @@ namespace AgroBase.Models
|
|||
Status = op.Status,
|
||||
StatusOperacaoAnterior = Operacao.StatusOperacaoAnterior,
|
||||
OperacaoIniciada = Operacao.OperacaoIniciada,
|
||||
Emergencia = Operacao.Emergencia,
|
||||
Emergencia = emergencia,
|
||||
Pausa = Operacao.Pausa,
|
||||
Calibrando = Operacao.Calibrando,
|
||||
Finalizando = Operacao.Finalizando,
|
||||
|
|
@ -4889,7 +4904,7 @@ namespace AgroBase.Models
|
|||
? (velocidade / velocidadeMax) * 100.0
|
||||
: 0;
|
||||
|
||||
double erroAngulo = dir != null ? CalcularErroAngulo(dir) : 0;
|
||||
double erroAngulo = dir?.ErroAngulo ?? 0;
|
||||
|
||||
return new DirecionalModuloLogModel
|
||||
{
|
||||
|
|
@ -4919,6 +4934,7 @@ namespace AgroBase.Models
|
|||
|
||||
ErroAngulo = Math.Round(erroAngulo, 2),
|
||||
ErroAnguloAbs = Math.Round(Math.Abs(erroAngulo), 2),
|
||||
ErroSeguimentoAngularPersistente = dir?.ErroSeguimentoAngularPersistente ?? false,
|
||||
AtingiuAnguloSP = dir?.AtingiuAnguloSP ?? false,
|
||||
RetornandoAzero = dir?.RetornandoAzero ?? false,
|
||||
|
||||
|
|
@ -4943,19 +4959,6 @@ namespace AgroBase.Models
|
|||
};
|
||||
}
|
||||
|
||||
double CalcularErroAngulo(DirecionalModel dir)
|
||||
{
|
||||
if (dir == null)
|
||||
return 0;
|
||||
|
||||
double anguloSp =
|
||||
!dir.Comandar || dir.Sentido_SP == Sentido.Parado
|
||||
? 0
|
||||
: dir.Angulo_SP;
|
||||
|
||||
return anguloSp - dir.AnguloLeitura;
|
||||
}
|
||||
|
||||
DirecionalStatusLogModel CriarStatus((bool, List<string>) status)
|
||||
{
|
||||
return new DirecionalStatusLogModel
|
||||
|
|
@ -5021,7 +5024,7 @@ namespace AgroBase.Models
|
|||
|
||||
model.AnguloMedioAbs = MediaOuZero(controlando, x => Math.Abs(x.DirMotor.AnguloLeitura));
|
||||
model.AnguloSPMedioAbs = MediaOuZero(controlando, x => Math.Abs(x.DirMotor.Angulo_SP));
|
||||
model.ErroAnguloMedioAbs = MediaOuZero(controlando, x => Math.Abs(CalcularErroAngulo(x.DirMotor)));
|
||||
model.ErroAnguloMedioAbs = MediaOuZero(controlando, x => Math.Abs(x.DirMotor.ErroAngulo));
|
||||
|
||||
model.Status =
|
||||
dados != null && controle != null
|
||||
|
|
@ -5999,11 +6002,9 @@ namespace AgroBase.Models
|
|||
|
||||
public DirecionalStatusLogModel Status { get; set; } = new DirecionalStatusLogModel();
|
||||
|
||||
public bool StatusGeralOk =>
|
||||
Disponivel &&
|
||||
Status.Ok &&
|
||||
ModulosInicializados > 0 &&
|
||||
!Modulos.Any(x => x.ErroAnguloAbs > 3.0 && x.Controlar);
|
||||
public bool StatusEstruturalOk => Disponivel && Status.Ok && ModulosInicializados > 0;
|
||||
|
||||
public bool StatusGeralOk => StatusEstruturalOk && !Modulos.Any(x => x.Controlar && x.ErroSeguimentoAngularPersistente);
|
||||
|
||||
public List<DirecionalModuloLogModel> Modulos { get; set; } = new List<DirecionalModuloLogModel>();
|
||||
|
||||
|
|
@ -6078,6 +6079,7 @@ namespace AgroBase.Models
|
|||
// Diagnóstico geométrico
|
||||
public double ErroAngulo { get; set; }
|
||||
public double ErroAnguloAbs { get; set; }
|
||||
public bool ErroSeguimentoAngularPersistente { get; set; }
|
||||
public bool AtingiuAnguloSP { get; set; }
|
||||
public bool RetornandoAzero { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1228,6 +1228,8 @@ namespace AgroBase.Services
|
|||
short rpm = DecodeInt16BE(data, 1);
|
||||
|
||||
item.RPM.valor = rpm;
|
||||
|
||||
Variaveis.OperacaoEmAndamento?.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.DirMotor._EnderecoCAN_Tx == item.EnderecoTx)?.DirMotor?.AtualizarEmMovimento(Math.Abs(rpm) > 1);
|
||||
}
|
||||
|
||||
private void ProcessarPulsosRecebidos(MKS057DModel item, byte[] data)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AgroBase.Models;
|
||||
using AgroBase.Models.Modules;
|
||||
using AgroBase.Models.Operadores;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
|
@ -788,15 +789,30 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
bool possuiComandoServo = servo.UltimoComandoEnviado != DateTime.MinValue;
|
||||
|
||||
DateTime ultimaRespostaAnguloServo =
|
||||
servo.ValoresLeituras?
|
||||
.Where(x =>x.funcao == FuncoesPinout.ServoAnguloLeitura)
|
||||
.OrderByDescending(x =>x.atual?.lidoEm ?? DateTime.MinValue)
|
||||
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||
|
||||
bool possuiRespostaServo = ultimaRespostaAnguloServo != DateTime.MinValue;
|
||||
|
||||
double idadeUltimoComandoServoMs =
|
||||
servo.UltimoComandoEnviado == DateTime.MinValue
|
||||
!possuiComandoServo
|
||||
? 999999
|
||||
: (agora_dt - servo.UltimoComandoEnviado).TotalMilliseconds;
|
||||
: Math.Max(0, (agora_dt - servo.UltimoComandoEnviado).TotalMilliseconds);
|
||||
|
||||
double idadeUltimaRespostaServoMs =
|
||||
servo.UltimaLeitura == DateTime.MinValue
|
||||
!possuiRespostaServo
|
||||
? 999999
|
||||
: (agora_dt - servo.UltimaLeitura).TotalMilliseconds;
|
||||
: Math.Max(0, (agora_dt - ultimaRespostaAnguloServo).TotalMilliseconds);
|
||||
|
||||
bool telemetriaPosComandoServo =
|
||||
possuiComandoServo &&
|
||||
possuiRespostaServo &&
|
||||
ultimaRespostaAnguloServo >= servo.UltimoComandoEnviado;
|
||||
|
||||
var servoObj = new
|
||||
{
|
||||
|
|
@ -806,15 +822,21 @@ namespace AgroBase.Services.Operadores
|
|||
tipo,
|
||||
controlar = servo.Controlar,
|
||||
mandatorio = servo.Mandatorio,
|
||||
|
||||
angulo_sp = servo._AnguloControle,
|
||||
angulo_desejado = servo.UltimoAnguloDesejado,
|
||||
angulo_leitura = servo._AnguloLeiutra,
|
||||
|
||||
possui_comando = possuiComandoServo,
|
||||
telemetria_pos_comando = telemetriaPosComandoServo,
|
||||
|
||||
ultimo_comando_ms = idadeUltimoComandoServoMs,
|
||||
|
||||
ultima_resposta_ms = idadeUltimaRespostaServoMs,
|
||||
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { },
|
||||
|
||||
angulo_desejado = servo.UltimoAnguloDesejado,
|
||||
angulo_leitura = servo._AnguloLeiutra,
|
||||
ultimo_comando_ms = idadeUltimoComandoServoMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaServoMs,
|
||||
};
|
||||
|
||||
senServosAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -843,15 +865,30 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
bool possuiComandoRele = rele.UltimoComandoEnviado != DateTime.MinValue;
|
||||
bool possuiRespostaRele = rele.UltimaLeitura != DateTime.MinValue;
|
||||
double idadeUltimoComandoReleMs = !possuiComandoRele ? 999999 : Math.Max(0, (agora_dt - rele.UltimoComandoEnviado).TotalMilliseconds);
|
||||
double idadeUltimaRespostaReleMs = !possuiRespostaRele ? 999999 : Math.Max(0, (agora_dt - rele.UltimaLeitura).TotalMilliseconds);
|
||||
DateTime ultimaRespostaEstadoRele =
|
||||
rele.ValoresLeituras?
|
||||
.Where(x =>x.funcao == FuncoesPinout.EstadoLeitura)
|
||||
.OrderByDescending(x =>x.atual?.lidoEm ?? DateTime.MinValue)
|
||||
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||
|
||||
bool telemetriaPosComando =
|
||||
bool possuiComandoRele = rele.UltimoComandoEnviado != DateTime.MinValue;
|
||||
|
||||
bool possuiRespostaRele = ultimaRespostaEstadoRele != DateTime.MinValue;
|
||||
|
||||
double idadeUltimoComandoReleMs =
|
||||
!possuiComandoRele
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - rele.UltimoComandoEnviado).TotalMilliseconds);
|
||||
|
||||
double idadeUltimaRespostaReleMs =
|
||||
!possuiRespostaRele
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - ultimaRespostaEstadoRele).TotalMilliseconds);
|
||||
|
||||
bool telemetriaPosComandoRele =
|
||||
possuiComandoRele &&
|
||||
possuiRespostaRele &&
|
||||
rele.UltimaLeitura >= rele.UltimoComandoEnviado;
|
||||
ultimaRespostaEstadoRele >= rele.UltimoComandoEnviado;
|
||||
|
||||
var releObj = new
|
||||
{
|
||||
|
|
@ -870,7 +907,7 @@ namespace AgroBase.Services.Operadores
|
|||
estado_leitura = rele._EstadoLeitura,
|
||||
|
||||
possui_comando = possuiComandoRele,
|
||||
telemetria_pos_comando = telemetriaPosComando,
|
||||
telemetria_pos_comando = telemetriaPosComandoRele,
|
||||
|
||||
ultimo_comando_ms = idadeUltimoComandoReleMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaReleMs,
|
||||
|
|
@ -902,15 +939,50 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
DateTime ultimaRespostaLedId =
|
||||
led.ValoresLeituras?
|
||||
.Where(x =>x.funcao == FuncoesPinout.StatusLedID)
|
||||
.OrderByDescending(x => x.atual?.lidoEm ?? DateTime.MinValue)
|
||||
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||
|
||||
DateTime ultimaRespostaLedStatus =
|
||||
led.ValoresLeituras?
|
||||
.Where(x =>x.funcao == FuncoesPinout.StatusLedLeitura)
|
||||
.OrderByDescending(x =>x.atual?.lidoEm ?? DateTime.MinValue)
|
||||
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||
|
||||
bool possuiComandoLed = led.UltimoComandoEnviado != DateTime.MinValue;
|
||||
|
||||
bool possuiRespostaLed =
|
||||
ultimaRespostaLedId != DateTime.MinValue &&
|
||||
ultimaRespostaLedStatus != DateTime.MinValue;
|
||||
|
||||
/*
|
||||
* Usa a mais antiga das duas respostas.
|
||||
* Assim ultima_resposta_ms representa a idade do conjunto completo.
|
||||
*/
|
||||
DateTime ultimaRespostaLed =
|
||||
possuiRespostaLed
|
||||
? (ultimaRespostaLedId <= ultimaRespostaLedStatus ? ultimaRespostaLedId : ultimaRespostaLedStatus)
|
||||
: DateTime.MinValue;
|
||||
|
||||
double idadeUltimoComandoLedMs =
|
||||
led.UltimoComandoEnviado == DateTime.MinValue
|
||||
!possuiComandoLed
|
||||
? 999999
|
||||
: (agora_dt - led.UltimoComandoEnviado).TotalMilliseconds;
|
||||
: Math.Max(0, (agora_dt - led.UltimoComandoEnviado).TotalMilliseconds);
|
||||
|
||||
double idadeUltimaRespostaLedMs =
|
||||
led.UltimaLeitura == DateTime.MinValue
|
||||
!possuiRespostaLed
|
||||
? 999999
|
||||
: (agora_dt - led.UltimaLeitura).TotalMilliseconds;
|
||||
: Math.Max(0, (agora_dt - ultimaRespostaLed).TotalMilliseconds);
|
||||
|
||||
bool telemetriaPosComandoLed =
|
||||
possuiComandoLed &&
|
||||
possuiRespostaLed &&
|
||||
ultimaRespostaLedId >= led.UltimoComandoEnviado &&
|
||||
ultimaRespostaLedStatus >= led.UltimoComandoEnviado;
|
||||
|
||||
int statusLedSp = led.UltimoComportamentoDesejado != null ? (int)led.UltimoComportamentoDesejado.statusLED : -1;
|
||||
|
||||
var ledObj = new
|
||||
{
|
||||
|
|
@ -920,16 +992,24 @@ namespace AgroBase.Services.Operadores
|
|||
tipo,
|
||||
controlar = led.Controlar,
|
||||
mandatorio = led.Mandatorio,
|
||||
|
||||
status_led_id_sp = led.UltimoComportamentoDesejado?.id ?? -1,
|
||||
|
||||
status_led_sp = statusLedSp,
|
||||
|
||||
status_led_id = (int)led._StatusLedId,
|
||||
status_led_leitura = (int)led._StatusLed,
|
||||
|
||||
possui_comando = possuiComandoLed,
|
||||
telemetria_pos_comando = telemetriaPosComandoLed,
|
||||
|
||||
ultimo_comando_ms = idadeUltimoComandoLedMs,
|
||||
|
||||
ultima_resposta_ms = idadeUltimaRespostaLedMs,
|
||||
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { },
|
||||
|
||||
status_led_id_sp = led.UltimoComportamentoDesejado?.id ?? -1,
|
||||
status_led_sp = led.UltimoComportamentoDesejado?.statusLED,
|
||||
status_led_id = led._StatusLedId,
|
||||
status_led_leitura = led._StatusLed,
|
||||
ultimo_comando_ms = idadeUltimoComandoLedMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaLedMs,
|
||||
};
|
||||
|
||||
senLedsAtivos.Add($"{tipo}.{id}");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import json
|
|||
import os
|
||||
import time
|
||||
import threading
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
|
|
@ -15,6 +16,9 @@ from camera_worker.oak_fcc3_core.oak_fcc3_client import OakFcc3Client
|
|||
from camera_worker.camera_imu import IMUCamera
|
||||
|
||||
|
||||
CAMERA_MULTISPECTRAL_VERSION = "production_v1_2026_08_24"
|
||||
|
||||
|
||||
class CameraMultispectral:
|
||||
"""
|
||||
Adaptador do módulo OAK-FFC-3 multiespectral para o ecossistema do robô.
|
||||
|
|
@ -28,6 +32,9 @@ class CameraMultispectral:
|
|||
|
||||
A classe NÃO expõe lógica radiométrica/fusão/flatfield para o worker.
|
||||
Isso fica dentro do core OAK-FCC-3.
|
||||
|
||||
Em modo produto, hardware/resoluções/Bayer/target_size vêm exclusivamente
|
||||
do module_params homologado e do hardware validado pelo Manager/Client.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -41,6 +48,7 @@ class CameraMultispectral:
|
|||
target_size=None,
|
||||
cache_max_age_s=0.15,
|
||||
timeout_s=1.0,
|
||||
require_product_contract=False,
|
||||
):
|
||||
self.mostrar_log = mostrar_log
|
||||
self.mx_id = str(mx_id) if mx_id else None
|
||||
|
|
@ -61,11 +69,42 @@ class CameraMultispectral:
|
|||
self.rodando = False
|
||||
self.ultima_saude = {}
|
||||
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
# width/height recebidos do caller são somente compatibilidade legado.
|
||||
# Em produto, resolução nativa vem do module_params/hardware real.
|
||||
self.legacy_width = int(width)
|
||||
self.legacy_height = int(height)
|
||||
self.requested_target_size = (
|
||||
None
|
||||
if target_size is None
|
||||
else [int(target_size[0]), int(target_size[1])]
|
||||
)
|
||||
|
||||
# Aliases históricos. São sobrescritos pelo contrato do Client e passam
|
||||
# a significar exclusivamente a resolução RGB nativa de referência.
|
||||
self.width = self.legacy_width
|
||||
self.height = self.legacy_height
|
||||
|
||||
self.fps = int(fps)
|
||||
self.target_size = target_size
|
||||
self.target_size = self.requested_target_size
|
||||
self.tensor_size = self.requested_target_size
|
||||
self.timeout_s = float(timeout_s)
|
||||
self.require_product_contract = bool(require_product_contract)
|
||||
|
||||
self.product_contract = False
|
||||
self.client_contract = {}
|
||||
self.sensor_size_by_role = {
|
||||
"rgb": [self.legacy_width, self.legacy_height],
|
||||
"re": [self.legacy_width, self.legacy_height],
|
||||
"nir": [self.legacy_width, self.legacy_height],
|
||||
}
|
||||
self.camera_hardware = {}
|
||||
self.rgb_native_size = [self.legacy_width, self.legacy_height]
|
||||
self.spectral_native_size = {
|
||||
"re": [self.legacy_width, self.legacy_height],
|
||||
"nir": [self.legacy_width, self.legacy_height],
|
||||
}
|
||||
self.bayer_pattern = None
|
||||
self.module_params_sha256 = None
|
||||
|
||||
self._cache_max_age_s = float(cache_max_age_s)
|
||||
self._lock = threading.Lock()
|
||||
|
|
@ -115,6 +154,9 @@ class CameraMultispectral:
|
|||
module_calibration_json = self._resolver_module_params_default()
|
||||
|
||||
self.module_calibration_json = module_calibration_json
|
||||
self.module_params_sha256 = self._sha256_file_safe(
|
||||
self.module_calibration_json
|
||||
)
|
||||
|
||||
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||
ContextoGlobalRedis.CamKey(self.mx_id),
|
||||
|
|
@ -137,8 +179,10 @@ class CameraMultispectral:
|
|||
try:
|
||||
self.client = OakFcc3Client(
|
||||
mx_id=self.mx_id,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
# Fallbacks legado. O Client produto ignora estes valores como
|
||||
# autoridade física e usa o module_params homologado.
|
||||
width=self.legacy_width,
|
||||
height=self.legacy_height,
|
||||
bayer="BGGR",
|
||||
fps=self.fps,
|
||||
frame_type="RAW_BRUTO",
|
||||
|
|
@ -146,15 +190,31 @@ class CameraMultispectral:
|
|||
capture_mode="TRIPLE",
|
||||
raw_policy="require_triple",
|
||||
module_calibration_json=self.module_calibration_json,
|
||||
sync_mode="best",
|
||||
sync_tolerance_ms=25.0,
|
||||
sync_mode="strict",
|
||||
hardware_sync_enabled=False,
|
||||
frame_sync_master="CAM_A",
|
||||
sync_tolerance_ms=15.0,
|
||||
|
||||
imu_modo=self.imu_modo,
|
||||
imu_freq_hz=self.imu_freq_hz,
|
||||
|
||||
# Runtime de campo: não executa a auditoria estatística
|
||||
# evaluate_frame_quality() em todo frame.
|
||||
# O tensor continua passando por todo o processamento científico
|
||||
# necessário (decode, radiometria, flat-field, fusão e resize).
|
||||
evaluate_quality=False,
|
||||
require_product_contract=self.require_product_contract,
|
||||
)
|
||||
|
||||
# O contrato já está disponível antes do start porque o Client
|
||||
# carrega/valida module_params no construtor.
|
||||
self._adotar_contrato_client()
|
||||
|
||||
resp = self.client.start(print_debug=False)
|
||||
|
||||
# Reconfirma depois do Manager abrir e validar o hardware físico.
|
||||
self._adotar_contrato_client()
|
||||
|
||||
# Se mx_id veio None, pega o ID real aberto pelo core antes de criar IMU.
|
||||
try:
|
||||
self.mx_id = str(getattr(self.client, "mx_id", None) or self.mx_id)
|
||||
|
|
@ -235,6 +295,11 @@ class CameraMultispectral:
|
|||
|
||||
self.mostrar_log(f"[CameraMultispectral] Erro ao iniciar módulo: {e}")
|
||||
|
||||
# Produto é fail-closed: o CameraManager precisa receber o motivo
|
||||
# real da falha, e não um objeto parcialmente inicializado.
|
||||
if self.require_product_contract:
|
||||
raise
|
||||
|
||||
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||
ContextoGlobalRedis.CamKey(self.mx_id),
|
||||
mx_id=self.mx_id,
|
||||
|
|
@ -267,40 +332,204 @@ class CameraMultispectral:
|
|||
# Devolve o default mesmo se não existir, para o erro ficar claro no core.
|
||||
return "calibration/module_params.json"
|
||||
|
||||
def _sha256_file_safe(self, path):
|
||||
try:
|
||||
if not path or not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
while True:
|
||||
chunk = f.read(1024 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
h.update(chunk)
|
||||
|
||||
return h.hexdigest()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _normalize_size(value, field_name):
|
||||
if not isinstance(value, (list, tuple)) or len(value) != 2:
|
||||
raise RuntimeError(f"{field_name} inválido: {value!r}")
|
||||
|
||||
w = int(value[0])
|
||||
h = int(value[1])
|
||||
|
||||
if w <= 0 or h <= 0:
|
||||
raise RuntimeError(f"{field_name} inválido: {value!r}")
|
||||
|
||||
return [w, h]
|
||||
|
||||
def _adotar_contrato_client(self):
|
||||
if self.client is None:
|
||||
raise RuntimeError("OakFcc3Client não inicializado")
|
||||
|
||||
getter = getattr(self.client, "get_contract", None)
|
||||
if not callable(getter):
|
||||
if self.require_product_contract:
|
||||
raise RuntimeError(
|
||||
"OakFcc3Client sem get_contract(); versão produto obrigatória."
|
||||
)
|
||||
return
|
||||
|
||||
contract = getter() or {}
|
||||
if not isinstance(contract, dict):
|
||||
raise RuntimeError(
|
||||
f"Contrato do OakFcc3Client inválido: {type(contract)}"
|
||||
)
|
||||
|
||||
product = bool(contract.get("product_contract", False))
|
||||
|
||||
if self.require_product_contract and not product:
|
||||
raise RuntimeError(
|
||||
"CameraMultispectral exige module_params de produção homologado."
|
||||
)
|
||||
|
||||
sizes_raw = contract.get("sensor_size_by_role") or {}
|
||||
sizes = {}
|
||||
|
||||
for role in ("rgb", "re", "nir"):
|
||||
if role not in sizes_raw:
|
||||
if self.require_product_contract:
|
||||
raise RuntimeError(
|
||||
f"Contrato do Client sem sensor_size_by_role.{role}"
|
||||
)
|
||||
continue
|
||||
|
||||
sizes[role] = self._normalize_size(
|
||||
sizes_raw[role],
|
||||
f"sensor_size_by_role.{role}",
|
||||
)
|
||||
|
||||
if "rgb" in sizes:
|
||||
self.rgb_native_size = list(sizes["rgb"])
|
||||
self.width = int(self.rgb_native_size[0])
|
||||
self.height = int(self.rgb_native_size[1])
|
||||
|
||||
if "re" in sizes:
|
||||
self.spectral_native_size["re"] = list(sizes["re"])
|
||||
|
||||
if "nir" in sizes:
|
||||
self.spectral_native_size["nir"] = list(sizes["nir"])
|
||||
|
||||
if sizes:
|
||||
self.sensor_size_by_role = {
|
||||
role: list(size)
|
||||
for role, size in sizes.items()
|
||||
}
|
||||
|
||||
camera_hardware = contract.get("camera_hardware") or {}
|
||||
if isinstance(camera_hardware, dict):
|
||||
self.camera_hardware = json.loads(
|
||||
json.dumps(camera_hardware, default=str)
|
||||
)
|
||||
|
||||
bayer = contract.get("bayer_pattern")
|
||||
if bayer:
|
||||
bayer = str(bayer).upper()
|
||||
if bayer not in ("RGGB", "BGGR", "GRBG", "GBRG"):
|
||||
raise RuntimeError(f"Bayer inválido no contrato: {bayer!r}")
|
||||
self.bayer_pattern = bayer
|
||||
|
||||
default_target = contract.get("default_target_size")
|
||||
if default_target is not None:
|
||||
default_target = self._normalize_size(
|
||||
default_target,
|
||||
"client.default_target_size",
|
||||
)
|
||||
|
||||
if (
|
||||
self.requested_target_size is not None
|
||||
and default_target is not None
|
||||
and list(self.requested_target_size) != list(default_target)
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Resolução IA divergente do module_params: "
|
||||
f"caller={self.requested_target_size}, "
|
||||
f"module_params={default_target}"
|
||||
)
|
||||
|
||||
self.target_size = (
|
||||
list(default_target)
|
||||
if default_target is not None
|
||||
else (
|
||||
list(self.requested_target_size)
|
||||
if self.requested_target_size is not None
|
||||
else list(self.rgb_native_size)
|
||||
)
|
||||
)
|
||||
self.tensor_size = list(self.target_size)
|
||||
self.product_contract = product
|
||||
self.client_contract = json.loads(
|
||||
json.dumps(contract, default=str)
|
||||
)
|
||||
|
||||
def _montar_parametros(self, status=None, start_resp=None):
|
||||
status = status or {}
|
||||
|
||||
altura_camera = 111.0
|
||||
|
||||
# Valores medidos por você anteriormente:
|
||||
# FOV prático: 96 x 157 cm a 111 cm de altura.
|
||||
# FOV prático histórico medido no conjunto mecânico.
|
||||
# É mantido como referência operacional, mas não como calibração óptica.
|
||||
largura_real = 157.0
|
||||
altura_real = 96.0
|
||||
|
||||
cm_por_px_x = largura_real / float(self.width)
|
||||
cm_por_px_y = altura_real / float(self.height)
|
||||
tensor_w = int(self.tensor_size[0])
|
||||
tensor_h = int(self.tensor_size[1])
|
||||
|
||||
# Para o worker, pixel operacional significa pixel do tensor entregue à IA,
|
||||
# não pixel do sensor bruto. A homografia/crop pode reduzir discretamente
|
||||
# a FOV real, então estes valores continuam sendo NOMINAIS.
|
||||
cm_por_px_x = largura_real / float(tensor_w)
|
||||
cm_por_px_y = altura_real / float(tensor_h)
|
||||
|
||||
parametros = {
|
||||
"tipo": "multispectral",
|
||||
"adapter_version": CAMERA_MULTISPECTRAL_VERSION,
|
||||
"product_contract": bool(self.product_contract),
|
||||
"module_calibration_json": self.module_calibration_json,
|
||||
"module_params_sha256": self.module_params_sha256,
|
||||
"frame_type": "RAW_BRUTO",
|
||||
"channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"output_layout": "CHW",
|
||||
"dtype": "float32",
|
||||
"tensor_min": 0.0,
|
||||
"tensor_max": 1.0,
|
||||
"rgb_width": self.width,
|
||||
"rgb_height": self.height,
|
||||
"tensor_width": self.width if self.target_size is None else int(self.target_size[0]),
|
||||
"tensor_height": self.height if self.target_size is None else int(self.target_size[1]),
|
||||
|
||||
"reference_camera": "rgb",
|
||||
"rgb_native_size": list(self.rgb_native_size),
|
||||
"sensor_size_by_role": {
|
||||
role: list(size)
|
||||
for role, size in self.sensor_size_by_role.items()
|
||||
},
|
||||
"camera_hardware": json.loads(
|
||||
json.dumps(self.camera_hardware, default=str)
|
||||
),
|
||||
"bayer_pattern": self.bayer_pattern,
|
||||
|
||||
# Aliases compatíveis, agora explicitamente RGB nativo.
|
||||
"rgb_width": int(self.rgb_native_size[0]),
|
||||
"rgb_height": int(self.rgb_native_size[1]),
|
||||
|
||||
"tensor_size": list(self.tensor_size),
|
||||
"tensor_width": tensor_w,
|
||||
"tensor_height": tensor_h,
|
||||
"fps": self.fps,
|
||||
|
||||
"altura_camera": altura_camera,
|
||||
"largura_real": largura_real,
|
||||
"altura_real": altura_real,
|
||||
"cm_por_px_x": cm_por_px_x,
|
||||
"cm_por_px_y": cm_por_px_y,
|
||||
"cm_por_px_nominal": True,
|
||||
|
||||
"status": status,
|
||||
"start_resp": start_resp,
|
||||
"client_contract": json.loads(
|
||||
json.dumps(self.client_contract, default=str)
|
||||
),
|
||||
}
|
||||
|
||||
return parametros
|
||||
|
|
@ -557,6 +786,15 @@ class CameraMultispectral:
|
|||
f"Tensor multiespectral deveria ter 5 canais, veio shape={tensor.shape}"
|
||||
)
|
||||
|
||||
if self.target_size is not None:
|
||||
expected_w = int(self.target_size[0])
|
||||
expected_h = int(self.target_size[1])
|
||||
if tuple(tensor.shape[1:]) != (expected_h, expected_w):
|
||||
raise RuntimeError(
|
||||
"Tensor final diverge do contrato de resolução: "
|
||||
f"shape={tensor.shape}, esperado=(5,{expected_h},{expected_w})"
|
||||
)
|
||||
|
||||
t_validate_ms = (time.perf_counter() - t0) * 1000.0
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -595,6 +833,13 @@ class CameraMultispectral:
|
|||
"shape": list(tensor.shape),
|
||||
"dtype": str(tensor.dtype),
|
||||
"channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"product_contract": bool(self.product_contract),
|
||||
"tensor_size": list(self.tensor_size),
|
||||
"sensor_size_by_role": {
|
||||
role: list(size)
|
||||
for role, size in self.sensor_size_by_role.items()
|
||||
},
|
||||
"bayer_pattern": self.bayer_pattern,
|
||||
|
||||
"frame_id": int(meta.get("frame_id", 0) or 0),
|
||||
"async_packet_seq": int(
|
||||
|
|
@ -615,6 +860,22 @@ class CameraMultispectral:
|
|||
},
|
||||
}
|
||||
|
||||
#capture_perf = dict(meta.get("capture_perf", {}) or {})
|
||||
#agora_log = time.monotonic()
|
||||
#if not hasattr(self, "_ultimo_log_sync"):
|
||||
# self._ultimo_log_sync = 0.0
|
||||
#if agora_log - self._ultimo_log_sync >= 1.0:
|
||||
# print(
|
||||
# "[OAK_SYNC] "
|
||||
# f"frame={meta.get('frame_id')} | "
|
||||
# f"dt={float(meta.get('sync_dt_ms', 0.0) or 0.0):.3f}ms | "
|
||||
# f"ok={bool(meta.get('sync_ok', False))} | "
|
||||
# f"tol={float(meta.get('sync_tolerance_ms', 0.0) or 0.0):.3f}ms | "
|
||||
# f"seq={capture_perf.get('selected_seq_by_cam')} | "
|
||||
# f"ts={capture_perf.get('selected_ts_by_cam')}"
|
||||
# )
|
||||
# self._ultimo_log_sync = agora_log
|
||||
|
||||
with self._lock:
|
||||
self.ultimo_tensor_multispec = tensor
|
||||
self.ultimo_meta = meta
|
||||
|
|
@ -674,7 +935,10 @@ class CameraMultispectral:
|
|||
|
||||
def requisitar_frame_rgb(self, force: bool = False, max_age_s: float = None):
|
||||
"""
|
||||
Retorna preview BGR uint8 do tensor multiespectral.
|
||||
Retorna preview BGR uint8 gerado sob demanda a partir do Raw5 cacheado.
|
||||
|
||||
O hot path de inferência não paga custo de preview. O preview só é
|
||||
construído quando stream/debug realmente pede um frame RGB.
|
||||
"""
|
||||
try:
|
||||
agora = time.time()
|
||||
|
|
@ -682,27 +946,58 @@ class CameraMultispectral:
|
|||
max_age_s = self._cache_max_age_s
|
||||
|
||||
with self._lock:
|
||||
cache_ok = (
|
||||
preview_ok = (
|
||||
self.ultimo_frame_rgb is not None
|
||||
and self.timestamp_ultimo_frame_rgb is not None
|
||||
and (agora - self.timestamp_ultimo_frame_rgb) < max_age_s
|
||||
)
|
||||
|
||||
if cache_ok and not force:
|
||||
if preview_ok and not force:
|
||||
return self.ultimo_frame_rgb, dict(self._ultimo_resultado_rgb)
|
||||
|
||||
# Atualiza tensor, que por consequência atualiza preview RGB.
|
||||
tensor, res = self.requisitar_tensor_multispec(force=force, max_age_s=max_age_s)
|
||||
tensor_cache = self.ultimo_tensor_multispec
|
||||
ts_tensor = self.timestamp_ultimo_tensor
|
||||
|
||||
tensor_fresco = (
|
||||
tensor_cache is not None
|
||||
and ts_tensor is not None
|
||||
and (agora - ts_tensor) < max_age_s
|
||||
)
|
||||
|
||||
if not tensor_fresco or force:
|
||||
tensor_cache, res = self.requisitar_tensor_multispec(
|
||||
force=force,
|
||||
max_age_s=max_age_s,
|
||||
)
|
||||
|
||||
if tensor_cache is None:
|
||||
return None, {
|
||||
"erro": res.get("erro") or "tensor indisponível para preview",
|
||||
"duracao": res.get("duracao", 0.0),
|
||||
"frame_valido": False,
|
||||
}
|
||||
|
||||
t0 = time.perf_counter()
|
||||
preview = self._tensor_to_preview_bgr(tensor_cache)
|
||||
dur = time.perf_counter() - t0
|
||||
|
||||
if preview is None or preview.size <= 0:
|
||||
raise RuntimeError("Falha ao gerar preview RGB a partir do Raw5.")
|
||||
|
||||
resultado = {
|
||||
"erro": None,
|
||||
"duracao": dur,
|
||||
"frame_valido": True,
|
||||
"origem": "raw5_cache_on_demand",
|
||||
"shape": list(preview.shape),
|
||||
}
|
||||
|
||||
with self._lock:
|
||||
if self.ultimo_frame_rgb is not None:
|
||||
return self.ultimo_frame_rgb, dict(self._ultimo_resultado_rgb)
|
||||
self.ultimo_frame_rgb = preview
|
||||
self.timestamp_ultimo_frame_rgb = time.time()
|
||||
self._ultimo_resultado_rgb = resultado
|
||||
|
||||
return None, {
|
||||
"erro": res.get("erro") or "preview RGB indisponível",
|
||||
"duracao": res.get("duracao", 0.0),
|
||||
"frame_valido": False,
|
||||
}
|
||||
return preview, resultado
|
||||
|
||||
except Exception as e:
|
||||
self.mostrar_log(f"[CameraMultispectral] Erro ao requisitar RGB: {e}")
|
||||
|
|
@ -1341,8 +1636,12 @@ class CameraMultispectral:
|
|||
core = getattr(self.client, "core", None)
|
||||
if core is not None and hasattr(core, "fuse_multispec_cameras"):
|
||||
t0 = time.perf_counter()
|
||||
out = core.fuse_multispec_cameras(decoded_fixed, meta, 5)
|
||||
out = core.resize_tensor_chw(out, target_size=self.target_size)
|
||||
out = core.fuse_multispec_cameras(
|
||||
decoded_fixed,
|
||||
meta,
|
||||
5,
|
||||
target_size=self.target_size,
|
||||
)
|
||||
|
||||
#t_core_build_ms = (time.perf_counter() - t0) * 1000.0
|
||||
#t_total_ms = (time.perf_counter() - t_total0) * 1000.0
|
||||
|
|
@ -1407,6 +1706,16 @@ class CameraMultispectral:
|
|||
meta_item.setdefault("role", role)
|
||||
meta_item.setdefault("socket", info.get("socket", cam_id))
|
||||
meta_item.setdefault("sensor", info.get("sensor"))
|
||||
meta_item.setdefault("width", info.get("width"))
|
||||
meta_item.setdefault("height", info.get("height"))
|
||||
meta_item.setdefault("native_size", info.get("native_size"))
|
||||
meta_item.setdefault("bit_depth", info.get("bit_depth"))
|
||||
meta_item.setdefault("raw_format", info.get("raw_format"))
|
||||
if role == "rgb":
|
||||
meta_item.setdefault(
|
||||
"bayer_pattern",
|
||||
info.get("bayer_pattern") or self.bayer_pattern,
|
||||
)
|
||||
fixed["meta"] = meta_item
|
||||
|
||||
out[cam_id] = fixed
|
||||
|
|
@ -1423,6 +1732,16 @@ class CameraMultispectral:
|
|||
"role": role,
|
||||
"socket": info.get("socket", cam_id),
|
||||
"sensor": info.get("sensor"),
|
||||
"width": info.get("width"),
|
||||
"height": info.get("height"),
|
||||
"native_size": info.get("native_size"),
|
||||
"bit_depth": info.get("bit_depth"),
|
||||
"raw_format": info.get("raw_format"),
|
||||
"bayer_pattern": (
|
||||
info.get("bayer_pattern") or self.bayer_pattern
|
||||
if role == "rgb"
|
||||
else None
|
||||
),
|
||||
"timestamp": (meta.get("timestamps") or {}).get(cam_id) if isinstance(meta, dict) else None,
|
||||
"shape": list(item.shape),
|
||||
"dtype": str(item.dtype),
|
||||
|
|
@ -1441,6 +1760,28 @@ class CameraMultispectral:
|
|||
# Salvamento científico / pós-processamento
|
||||
# ============================================================
|
||||
|
||||
def _processing_diagnostics_json_safe(self):
|
||||
core = getattr(self.client, "core", None) if self.client is not None else None
|
||||
|
||||
if core is None:
|
||||
return None
|
||||
|
||||
payload = {
|
||||
"fusion_result": getattr(core, "last_fusion_result", None),
|
||||
"radiometric_normalization_result": getattr(
|
||||
core,
|
||||
"last_radiometric_normalization_result",
|
||||
None,
|
||||
),
|
||||
"flatfield_result": getattr(core, "last_flatfield_result", None),
|
||||
"frame_quality": getattr(core, "last_frame_quality_result", None),
|
||||
}
|
||||
|
||||
try:
|
||||
return json.loads(json.dumps(payload, default=str))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _ts_name(self) -> str:
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
|
||||
|
||||
|
|
@ -1466,6 +1807,20 @@ class CameraMultispectral:
|
|||
with self._lock:
|
||||
raw_meta = dict(self.ultimo_meta or {})
|
||||
|
||||
# Reforça o contrato científico no próprio stream_meta salvo.
|
||||
raw_meta.setdefault("frame_type", "RAW_BRUTO")
|
||||
raw_meta.setdefault("reference_camera", "rgb")
|
||||
raw_meta.setdefault("bayer_pattern", self.bayer_pattern)
|
||||
raw_meta.setdefault(
|
||||
"sensor_size_by_role",
|
||||
{role: list(size) for role, size in self.sensor_size_by_role.items()},
|
||||
)
|
||||
raw_meta.setdefault(
|
||||
"camera_hardware",
|
||||
json.loads(json.dumps(self.camera_hardware, default=str)),
|
||||
)
|
||||
raw_meta.setdefault("product_contract", bool(self.product_contract))
|
||||
|
||||
required = {"CAM_A", "CAM_B", "CAM_C"}
|
||||
presentes = set(raw_frame.keys())
|
||||
faltando = sorted(required - presentes)
|
||||
|
|
@ -1526,9 +1881,9 @@ class CameraMultispectral:
|
|||
preview = self.client.build_save_preview_from_cam_a(
|
||||
packed_raw_by_camera=raw_frame,
|
||||
meta_stream=raw_meta,
|
||||
sensor_width=self.width,
|
||||
sensor_height=self.height,
|
||||
bayer_pattern="BGGR",
|
||||
sensor_width=int(self.rgb_native_size[0]),
|
||||
sensor_height=int(self.rgb_native_size[1]),
|
||||
bayer_pattern=self.bayer_pattern,
|
||||
)
|
||||
|
||||
if preview is not None:
|
||||
|
|
@ -1641,9 +1996,22 @@ class CameraMultispectral:
|
|||
"mx_id": self.mx_id,
|
||||
"module_calibration_json": self.module_calibration_json,
|
||||
|
||||
"sensor_width": self.width,
|
||||
"sensor_height": self.height,
|
||||
"bayer_pattern": "BGGR",
|
||||
# Compatibilidade root: sensor_width/height representam CAM_A/RGB.
|
||||
"reference_camera": "rgb",
|
||||
"sensor_width": int(self.rgb_native_size[0]),
|
||||
"sensor_height": int(self.rgb_native_size[1]),
|
||||
"sensor_size_by_role": {
|
||||
role: list(size)
|
||||
for role, size in self.sensor_size_by_role.items()
|
||||
},
|
||||
"camera_hardware": json.loads(
|
||||
json.dumps(self.camera_hardware, default=str)
|
||||
),
|
||||
"bayer_pattern": self.bayer_pattern,
|
||||
"tensor_size": list(self.tensor_size),
|
||||
"product_contract": bool(self.product_contract),
|
||||
"camera_adapter_version": CAMERA_MULTISPECTRAL_VERSION,
|
||||
"module_params_sha256": self.module_params_sha256,
|
||||
"fps_target": self.fps,
|
||||
|
||||
"frame_type": "RAW_BRUTO",
|
||||
|
|
@ -1662,6 +2030,10 @@ class CameraMultispectral:
|
|||
"stream_meta": raw_meta,
|
||||
"actual_camera_controls": self.client.get_current_camera_controls() if self.client else None,
|
||||
"radiometric_last_result": self.client.get_radiometric_last_result() if self.client else None,
|
||||
"client_contract": json.loads(
|
||||
json.dumps(self.client_contract, default=str)
|
||||
),
|
||||
"processing_diagnostics": self._processing_diagnostics_json_safe(),
|
||||
|
||||
"resultado": resultado,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ from .radiometric_controller import RadiometricController
|
|||
|
||||
PHYSICAL_CHANNEL_NAMES = ("R", "G", "B", "RE", "NIR")
|
||||
PHYSICAL_CHANNEL_COUNT = len(PHYSICAL_CHANNEL_NAMES)
|
||||
OAK_FCC3_CLIENT_VERSION = "production_v1_2026_08_24"
|
||||
PRODUCT_SCHEMA = "multispec_module_params_v3"
|
||||
ASSEMBLY_SCHEMA = "multispec_module_params_assembly_v1"
|
||||
|
||||
|
||||
class OakFcc3Client:
|
||||
|
|
@ -46,58 +49,105 @@ class OakFcc3Client:
|
|||
mx_id=None,
|
||||
imu_modo="rotation_vector",
|
||||
imu_freq_hz=200,
|
||||
evaluate_quality=True,
|
||||
require_product_contract=False,
|
||||
**kwargs,
|
||||
):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.bayer = bayer
|
||||
self.fps = fps
|
||||
self.frame_type = frame_type
|
||||
self.output_dtype = output_dtype
|
||||
self.capture_mode = capture_mode
|
||||
self.raw_policy = raw_policy
|
||||
# width/height/bayer recebidos do caller ficam apenas como fallback legado.
|
||||
# Em module_params de produção, hardware real + MP são a autoridade.
|
||||
self.legacy_width = int(width)
|
||||
self.legacy_height = int(height)
|
||||
self.legacy_bayer = str(bayer or "BGGR").upper()
|
||||
|
||||
self.fps = float(fps)
|
||||
self.frame_type = str(frame_type).upper()
|
||||
self.output_dtype = str(output_dtype).lower()
|
||||
self.capture_mode = str(capture_mode).upper()
|
||||
self.raw_policy = str(raw_policy).lower()
|
||||
self.module_calibration_json = module_calibration_json
|
||||
self.module_params = self._load_module_params(module_calibration_json)
|
||||
self.require_product_contract = bool(require_product_contract)
|
||||
|
||||
self.module_params = self._load_module_params(
|
||||
module_calibration_json,
|
||||
required=self.require_product_contract,
|
||||
)
|
||||
self.product_contract = self._is_product_module_params(self.module_params)
|
||||
|
||||
if self.require_product_contract and not self.product_contract:
|
||||
raise RuntimeError(
|
||||
"Contrato de produção obrigatório, mas module_params não foi "
|
||||
"gerado pelo assembler oficial."
|
||||
)
|
||||
|
||||
self.fusion_config = self.module_params.get("fusion_config", {}) or {}
|
||||
self.camera_hardware = self._resolve_camera_hardware()
|
||||
self.sensor_size_by_role = self._resolve_sensor_size_by_role()
|
||||
|
||||
rgb_size = self.sensor_size_by_role.get(
|
||||
"rgb",
|
||||
[self.legacy_width, self.legacy_height],
|
||||
)
|
||||
self.rgb_native_width = int(rgb_size[0])
|
||||
self.rgb_native_height = int(rgb_size[1])
|
||||
|
||||
# Mantidos por compatibilidade, mas agora significam RGB de referência.
|
||||
self.width = self.rgb_native_width
|
||||
self.height = self.rgb_native_height
|
||||
|
||||
self.bayer = self._resolve_bayer_pattern()
|
||||
self.default_target_size = self._resolve_default_target_size()
|
||||
|
||||
self.imu_modo = str(imu_modo).strip().lower()
|
||||
self.imu_freq_hz = int(imu_freq_hz)
|
||||
self.evaluate_quality = bool(evaluate_quality)
|
||||
|
||||
self.mx_id = str(mx_id) if mx_id else None
|
||||
|
||||
# Validação antecipada do modo produto. O Manager também repete estes
|
||||
# checks antes de abrir o hardware, de propósito.
|
||||
self._validate_static_product_contract()
|
||||
|
||||
self.svc = OakFcc3Service(
|
||||
timeout=10,
|
||||
fps=fps,
|
||||
width=width,
|
||||
height=height,
|
||||
frame_type=frame_type,
|
||||
output_dtype=output_dtype,
|
||||
capture_mode=capture_mode,
|
||||
raw_policy=raw_policy,
|
||||
fps=self.fps,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
frame_type=self.frame_type,
|
||||
output_dtype=self.output_dtype,
|
||||
capture_mode=self.capture_mode,
|
||||
raw_policy=self.raw_policy,
|
||||
sync_mode=sync_mode,
|
||||
sync_tolerance_ms=sync_tolerance_ms,
|
||||
mx_id=self.mx_id,
|
||||
module_calibration_json=module_calibration_json,
|
||||
|
||||
module_params=self.module_params,
|
||||
require_product_contract=self.require_product_contract,
|
||||
imu_modo=self.imu_modo,
|
||||
imu_freq_hz=self.imu_freq_hz,
|
||||
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self.applied_camera_controls = {}
|
||||
self.radiometric_controller = None
|
||||
self.radiometric_controller_enabled = False
|
||||
|
||||
self.core = RawProcessorCore(
|
||||
sensor_width=width,
|
||||
sensor_height=height,
|
||||
bayer_pattern=bayer,
|
||||
sensor_width=self.rgb_native_width,
|
||||
sensor_height=self.rgb_native_height,
|
||||
bayer_pattern=self.bayer,
|
||||
calibration_json_path=module_calibration_json,
|
||||
)
|
||||
|
||||
# Preview é apenas visual, porém também precisa usar o Bayer/raster
|
||||
# reais do RGB para não mentir sobre a AR0234.
|
||||
self.preview = RawProcessorPreview(
|
||||
sensor_width=width,
|
||||
sensor_height=height,
|
||||
bayer_pattern=bayer,
|
||||
sensor_width=self.rgb_native_width,
|
||||
sensor_height=self.rgb_native_height,
|
||||
bayer_pattern=self.bayer,
|
||||
)
|
||||
self._preview_cache = {
|
||||
(self.rgb_native_width, self.rgb_native_height, self.bayer): self.preview,
|
||||
}
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
|
|
@ -106,19 +156,247 @@ class OakFcc3Client:
|
|||
def __exit__(self, exc_type, exc, tb):
|
||||
self.stop()
|
||||
|
||||
def _load_module_params(self, path):
|
||||
if not path or not os.path.isfile(path):
|
||||
def _load_module_params(self, path, required=False):
|
||||
if not path:
|
||||
if required:
|
||||
raise FileNotFoundError(
|
||||
"module_params obrigatório no contrato de produção."
|
||||
)
|
||||
return {}
|
||||
|
||||
if not os.path.isfile(path):
|
||||
if required:
|
||||
raise FileNotFoundError(
|
||||
f"module_params não encontrado: {path}"
|
||||
)
|
||||
return {}
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
data = json.load(f)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise RuntimeError(
|
||||
f"module_params root deve ser dict/object: {path}"
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _is_product_module_params(module_params):
|
||||
mp = module_params or {}
|
||||
assembly = mp.get("assembly_metadata", {}) or {}
|
||||
return bool(
|
||||
mp.get("schema") == PRODUCT_SCHEMA
|
||||
and assembly.get("schema") == ASSEMBLY_SCHEMA
|
||||
and isinstance(mp.get("camera_hardware"), dict)
|
||||
and isinstance(mp.get("sensor_size_by_role"), dict)
|
||||
and isinstance(mp.get("calibration_provenance"), dict)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_size(value, label):
|
||||
if not (isinstance(value, (list, tuple)) and len(value) == 2):
|
||||
raise RuntimeError(f"{label} deve ser [W,H], recebido={value!r}")
|
||||
|
||||
w = int(value[0])
|
||||
h = int(value[1])
|
||||
if w <= 0 or h <= 0:
|
||||
raise RuntimeError(f"{label} inválido: {value!r}")
|
||||
return [w, h]
|
||||
|
||||
def _resolve_camera_hardware(self):
|
||||
raw = self.module_params.get("camera_hardware", {}) or {}
|
||||
|
||||
if not self.product_contract:
|
||||
return {}
|
||||
|
||||
out = {}
|
||||
expected = {
|
||||
"rgb": ("CAM_A", ("OV9782", "AR0234")),
|
||||
"re": ("CAM_B", ("OV9282",)),
|
||||
"nir": ("CAM_C", ("OV9282",)),
|
||||
}
|
||||
|
||||
for role, (expected_socket, allowed_sensors) in expected.items():
|
||||
item = raw.get(role)
|
||||
if not isinstance(item, dict):
|
||||
raise RuntimeError(f"camera_hardware sem role={role}")
|
||||
|
||||
socket = str(item.get("socket") or item.get("socket_name") or "").upper()
|
||||
sensor = str(item.get("sensor") or item.get("sensor_name") or "").upper()
|
||||
size = self._normalize_size(item.get("size"), f"camera_hardware.{role}.size")
|
||||
|
||||
if socket != expected_socket:
|
||||
raise RuntimeError(
|
||||
f"camera_hardware.{role}.socket={socket!r}, esperado={expected_socket!r}"
|
||||
)
|
||||
|
||||
if sensor not in allowed_sensors:
|
||||
raise RuntimeError(
|
||||
f"camera_hardware.{role}.sensor={sensor!r}, permitidos={allowed_sensors}"
|
||||
)
|
||||
|
||||
out[role] = {
|
||||
"role": role,
|
||||
"socket": socket,
|
||||
"sensor": sensor,
|
||||
"size": size,
|
||||
}
|
||||
|
||||
return out
|
||||
|
||||
def _resolve_sensor_size_by_role(self):
|
||||
if not self.product_contract:
|
||||
return {
|
||||
"rgb": [self.legacy_width, self.legacy_height],
|
||||
"re": [self.legacy_width, self.legacy_height],
|
||||
"nir": [self.legacy_width, self.legacy_height],
|
||||
}
|
||||
|
||||
raw = self.module_params.get("sensor_size_by_role", {}) or {}
|
||||
out = {}
|
||||
|
||||
for role in ("rgb", "re", "nir"):
|
||||
size = self._normalize_size(
|
||||
raw.get(role),
|
||||
f"sensor_size_by_role.{role}",
|
||||
)
|
||||
expected = self.camera_hardware[role]["size"]
|
||||
if size != expected:
|
||||
raise RuntimeError(
|
||||
f"sensor_size_by_role.{role}={size} != camera_hardware={expected}"
|
||||
)
|
||||
out[role] = size
|
||||
|
||||
return out
|
||||
|
||||
def _resolve_bayer_pattern(self):
|
||||
value = self.module_params.get("bayer_pattern") if self.product_contract else None
|
||||
bayer = str(value or self.legacy_bayer).upper()
|
||||
|
||||
if bayer not in ("RGGB", "BGGR", "GRBG", "GBRG"):
|
||||
raise RuntimeError(f"bayer_pattern inválido: {bayer!r}")
|
||||
|
||||
return bayer
|
||||
|
||||
def _resolve_default_target_size(self):
|
||||
value = (self.fusion_config or {}).get("target_size")
|
||||
if value is None:
|
||||
return None
|
||||
return self._normalize_size(value, "fusion_config.target_size")
|
||||
|
||||
def _resolve_target_size(self, target_size):
|
||||
value = target_size if target_size is not None else self.default_target_size
|
||||
if value is None:
|
||||
return None
|
||||
size = self._normalize_size(value, "target_size")
|
||||
|
||||
if self.product_contract and self.default_target_size is not None:
|
||||
if size != self.default_target_size:
|
||||
raise RuntimeError(
|
||||
"target_size solicitado diverge do module_params homologado: "
|
||||
f"requested={size}, calibrated_runtime={self.default_target_size}"
|
||||
)
|
||||
|
||||
return size
|
||||
|
||||
def _validate_static_product_contract(self):
|
||||
if not self.product_contract:
|
||||
return
|
||||
|
||||
if self.frame_type != "RAW_BRUTO":
|
||||
raise RuntimeError(
|
||||
"OakFcc3Client de produção aceita somente frame_type='RAW_BRUTO'."
|
||||
)
|
||||
|
||||
if self.raw_policy != "require_triple":
|
||||
raise RuntimeError(
|
||||
"OakFcc3Client de produção exige raw_policy='require_triple'."
|
||||
)
|
||||
|
||||
if self.capture_mode not in ("TRIPLE", "AUTO"):
|
||||
raise RuntimeError(
|
||||
"OakFcc3Client de produção exige capture_mode TRIPLE/AUTO."
|
||||
)
|
||||
|
||||
expected_mx = (
|
||||
(self.module_params.get("calibration_provenance", {}) or {})
|
||||
.get("device_mx_id")
|
||||
)
|
||||
if expected_mx and self.mx_id and str(expected_mx) != self.mx_id:
|
||||
raise RuntimeError(
|
||||
"MX ID solicitado diverge da calibração homologada: "
|
||||
f"requested={self.mx_id}, calibrated={expected_mx}"
|
||||
)
|
||||
|
||||
def _sync_contract_from_manager_status(self, status):
|
||||
if not isinstance(status, dict):
|
||||
return
|
||||
|
||||
if self.product_contract and not bool(status.get("product_contract", False)):
|
||||
raise RuntimeError(
|
||||
"Client carregou module_params produto, mas Manager não reconheceu o contrato."
|
||||
)
|
||||
|
||||
sizes = status.get("sensor_size_by_role")
|
||||
if isinstance(sizes, dict):
|
||||
normalized = {
|
||||
role: self._normalize_size(sizes.get(role), f"manager.sensor_size_by_role.{role}")
|
||||
for role in ("rgb", "re", "nir")
|
||||
}
|
||||
if self.product_contract and normalized != self.sensor_size_by_role:
|
||||
raise RuntimeError(
|
||||
"Manager e Client discordam sobre sensor_size_by_role: "
|
||||
f"manager={normalized}, client={self.sensor_size_by_role}"
|
||||
)
|
||||
self.sensor_size_by_role = normalized
|
||||
|
||||
bayer = status.get("bayer_pattern")
|
||||
if bayer:
|
||||
bayer = str(bayer).upper()
|
||||
if self.product_contract and bayer != self.bayer:
|
||||
raise RuntimeError(
|
||||
f"Manager Bayer={bayer} != Client/module_params={self.bayer}"
|
||||
)
|
||||
|
||||
def get_contract(self):
|
||||
return {
|
||||
"client_version": OAK_FCC3_CLIENT_VERSION,
|
||||
"product_contract": bool(self.product_contract),
|
||||
"require_product_contract": bool(self.require_product_contract),
|
||||
"sensor_size_by_role": {
|
||||
role: list(size)
|
||||
for role, size in self.sensor_size_by_role.items()
|
||||
},
|
||||
"camera_hardware": json.loads(json.dumps(self.camera_hardware)),
|
||||
"bayer_pattern": self.bayer,
|
||||
"default_target_size": (
|
||||
None if self.default_target_size is None
|
||||
else list(self.default_target_size)
|
||||
),
|
||||
"evaluate_quality": bool(self.evaluate_quality),
|
||||
"radiometric_controller_enabled": bool(self.radiometric_controller_enabled),
|
||||
}
|
||||
|
||||
def apply_module_camera_settings(self):
|
||||
camera_settings = self.module_params.get("camera_settings", {}) or {}
|
||||
|
||||
applied = {}
|
||||
if self.product_contract:
|
||||
missing = [
|
||||
role
|
||||
for role in ("rgb", "re", "nir")
|
||||
if not isinstance(camera_settings.get(role), dict)
|
||||
]
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
f"module_params produto sem camera_settings para: {missing}"
|
||||
)
|
||||
|
||||
for role, settings in camera_settings.items():
|
||||
applied = {}
|
||||
roles = ("rgb", "re", "nir") if self.product_contract else tuple(camera_settings.keys())
|
||||
|
||||
for role in roles:
|
||||
settings = camera_settings.get(role)
|
||||
if not isinstance(settings, dict):
|
||||
continue
|
||||
|
||||
|
|
@ -137,17 +415,43 @@ class OakFcc3Client:
|
|||
}
|
||||
|
||||
self.applied_camera_controls = applied
|
||||
|
||||
if self.product_contract:
|
||||
failed = {
|
||||
role: value
|
||||
for role, value in applied.items()
|
||||
if not bool((value or {}).get("ok", False))
|
||||
}
|
||||
if failed:
|
||||
raise RuntimeError(
|
||||
f"Falha reaplicando camera_settings homologado: {failed}"
|
||||
)
|
||||
|
||||
return applied
|
||||
|
||||
def enable_radiometric_controller(self):
|
||||
cfg = self.module_params.get("radiometric_config", {}) or {}
|
||||
enabled = bool(cfg.get("enabled", False))
|
||||
|
||||
# No produto atual este controller de exposição em campo é OFF.
|
||||
# Não instanciamos um segundo piloto para ficar parado dentro do loop.
|
||||
if not enabled:
|
||||
self.radiometric_controller = None
|
||||
self.radiometric_controller_enabled = False
|
||||
return None
|
||||
|
||||
self.radiometric_controller = RadiometricController(
|
||||
client=self,
|
||||
config_json_path=self.module_calibration_json,
|
||||
)
|
||||
|
||||
self.radiometric_controller.sync_from_camera_controls(self.applied_camera_controls)
|
||||
|
||||
self.radiometric_controller.sync_from_camera_controls(
|
||||
self.applied_camera_controls
|
||||
)
|
||||
self.radiometric_controller.sync_from_actual_camera_controls()
|
||||
|
||||
self.radiometric_controller_enabled = bool(
|
||||
getattr(self.radiometric_controller, "enabled", True)
|
||||
)
|
||||
return self.radiometric_controller
|
||||
|
||||
def update_radiometry(self, decoded, meta=None):
|
||||
|
|
@ -187,17 +491,45 @@ class OakFcc3Client:
|
|||
)
|
||||
|
||||
try:
|
||||
self.mx_id = self.svc.manager.mx_id
|
||||
status = self.svc.get_status()
|
||||
self.mx_id = str(status.get("mx_id") or self.mx_id or "") or None
|
||||
self._sync_contract_from_manager_status(status)
|
||||
except Exception:
|
||||
pass
|
||||
# Se a validação de contrato falhar, fecha hardware antes de propagar.
|
||||
try:
|
||||
self.svc.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
raise
|
||||
|
||||
# O Manager de produção já aplicou estes controles via initialControl.
|
||||
# Reaplicamos após start como confirmação operacional e para manter
|
||||
# compatibilidade com Managers legados durante a migração.
|
||||
applied = self.apply_module_camera_settings()
|
||||
|
||||
self.enable_radiometric_controller()
|
||||
|
||||
if print_debug:
|
||||
print("[OAK CLIENT] START:", resp)
|
||||
print("[OAK CLIENT] CONTRACT:", self.get_contract())
|
||||
print("[OAK CLIENT] APPLIED CAMERA SETTINGS:", applied)
|
||||
|
||||
self.enable_radiometric_controller()
|
||||
if isinstance(resp, dict):
|
||||
resp = dict(resp)
|
||||
resp["client_version"] = OAK_FCC3_CLIENT_VERSION
|
||||
resp["product_contract"] = bool(self.product_contract)
|
||||
resp["sensor_size_by_role"] = {
|
||||
role: list(size)
|
||||
for role, size in self.sensor_size_by_role.items()
|
||||
}
|
||||
resp["bayer_pattern"] = self.bayer
|
||||
resp["default_target_size"] = (
|
||||
None if self.default_target_size is None
|
||||
else list(self.default_target_size)
|
||||
)
|
||||
resp["radiometric_controller_enabled"] = bool(
|
||||
self.radiometric_controller_enabled
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
|
|
@ -209,7 +541,29 @@ class OakFcc3Client:
|
|||
return self.svc.get_device_metrics()
|
||||
|
||||
def get_status(self):
|
||||
return self.svc.get_status()
|
||||
status = self.svc.get_status()
|
||||
if not isinstance(status, dict):
|
||||
status = {}
|
||||
else:
|
||||
status = dict(status)
|
||||
|
||||
status.update({
|
||||
"client_version": OAK_FCC3_CLIENT_VERSION,
|
||||
"client_product_contract": bool(self.product_contract),
|
||||
"client_require_product_contract": bool(self.require_product_contract),
|
||||
"client_sensor_size_by_role": {
|
||||
role: list(size)
|
||||
for role, size in self.sensor_size_by_role.items()
|
||||
},
|
||||
"client_bayer_pattern": self.bayer,
|
||||
"client_default_target_size": (
|
||||
None if self.default_target_size is None
|
||||
else list(self.default_target_size)
|
||||
),
|
||||
"evaluate_quality": bool(self.evaluate_quality),
|
||||
"radiometric_controller_enabled": bool(self.radiometric_controller_enabled),
|
||||
})
|
||||
return status
|
||||
|
||||
def get_next_raw_frame(self, timeout=1.0):
|
||||
return self.svc.capture_frame(timeout=timeout)
|
||||
|
|
@ -224,6 +578,12 @@ class OakFcc3Client:
|
|||
frame_type = str(raw_meta.get("frame_type", self.frame_type)).upper()
|
||||
meta = dict(raw_meta)
|
||||
|
||||
if self.product_contract and frame_type != "RAW_BRUTO":
|
||||
raise RuntimeError(
|
||||
"OakFcc3Client produto recebeu frame_type não canônico: "
|
||||
f"{frame_type!r}. Esperado='RAW_BRUTO'."
|
||||
)
|
||||
|
||||
if frame_type == "RAW_BRUTO":
|
||||
decoded = self.decode_stream_cameras(raw_frame, raw_meta)
|
||||
|
||||
|
|
@ -320,6 +680,25 @@ class OakFcc3Client:
|
|||
|
||||
def build_infer_tensor(self, frame, meta, channels_expected, target_size=None):
|
||||
channels_expected = self._validate_physical_channel_count(channels_expected)
|
||||
target_size = self._resolve_target_size(target_size)
|
||||
|
||||
frame_type = str(
|
||||
(meta or {}).get("frame_type", self.frame_type)
|
||||
if isinstance(meta, dict)
|
||||
else self.frame_type
|
||||
).upper()
|
||||
|
||||
# No runtime quente evitamos o quality audit pesado. Decodificamos e
|
||||
# usamos exatamente o mesmo caminho do CameraMultispectral.
|
||||
if not self.evaluate_quality and frame_type == "RAW_BRUTO":
|
||||
decoded = self.core.decode_stream_cameras(frame, meta)
|
||||
return self.build_infer_tensor_from_decoded(
|
||||
decoded=decoded,
|
||||
meta=meta,
|
||||
channels_expected=channels_expected,
|
||||
target_size=target_size,
|
||||
)
|
||||
|
||||
return self.core.build_infer_tensor_from_stream(
|
||||
frame,
|
||||
meta,
|
||||
|
|
@ -327,19 +706,57 @@ class OakFcc3Client:
|
|||
target_size=target_size,
|
||||
)
|
||||
|
||||
def build_infer_tensor_from_decoded(self, decoded, meta, channels_expected, target_size=None):
|
||||
def build_infer_tensor_from_decoded(
|
||||
self,
|
||||
decoded,
|
||||
meta,
|
||||
channels_expected,
|
||||
target_size=None,
|
||||
evaluate_quality=None,
|
||||
):
|
||||
channels_expected = self._validate_physical_channel_count(channels_expected)
|
||||
tensor = self.core.fuse_multispec_cameras(decoded, meta, channels_expected)
|
||||
tensor = self.core.resize_tensor_chw(tensor, target_size=target_size)
|
||||
target_size = self._resolve_target_size(target_size)
|
||||
|
||||
if evaluate_quality is None:
|
||||
evaluate_quality = self.evaluate_quality
|
||||
evaluate_quality = bool(evaluate_quality)
|
||||
|
||||
# Core novo faz a geometria source->target em uma única etapa.
|
||||
# NÃO redimensionar novamente depois da fusão.
|
||||
tensor = self.core.fuse_multispec_cameras(
|
||||
decoded,
|
||||
meta,
|
||||
channels_expected,
|
||||
target_size=target_size,
|
||||
)
|
||||
|
||||
patch_cfg = getattr(
|
||||
self.core,
|
||||
"patch_normalization_config",
|
||||
{},
|
||||
) or {}
|
||||
|
||||
# Mantém paridade com build_infer_tensor_from_stream: se a calibração
|
||||
# habilitar patch normalization, ela também vale no caminho decoded.
|
||||
patch_cfg = getattr(self.core, "patch_normalization_config", {}) or {}
|
||||
if bool(patch_cfg.get("enabled", False)):
|
||||
if self.product_contract:
|
||||
raise RuntimeError(
|
||||
"patch_normalization não é permitido no contrato produto."
|
||||
)
|
||||
tensor = self.core.apply_patch_normalization_to_tensor(tensor)
|
||||
|
||||
self.core.last_frame_quality_result = self.core.evaluate_frame_quality(tensor)
|
||||
return tensor
|
||||
if evaluate_quality:
|
||||
self.core.last_frame_quality_result = self.core.evaluate_frame_quality(tensor)
|
||||
else:
|
||||
# Evita deixar resultado antigo no objeto e evita percentis no hot path.
|
||||
self.core.last_frame_quality_result = {
|
||||
"status": "skipped",
|
||||
"usable_for_training": None,
|
||||
"reason": "disabled_by_oak_fcc3_client",
|
||||
"client_version": OAK_FCC3_CLIENT_VERSION,
|
||||
}
|
||||
|
||||
return np.ascontiguousarray(
|
||||
tensor.astype(np.float32, copy=False)
|
||||
)
|
||||
|
||||
def decode_stream_cameras(self, frame, meta):
|
||||
if str(meta.get("frame_type", self.frame_type)).upper() == "PREVIEW":
|
||||
|
|
@ -382,12 +799,19 @@ class OakFcc3Client:
|
|||
tensor = np.transpose(rgb01.astype(np.float32), (2, 0, 1))
|
||||
return np.ascontiguousarray(tensor.astype(np.float32, copy=False))
|
||||
|
||||
def build_multispec_tensor(self, decoded, meta=None, target_size=None):
|
||||
def build_multispec_tensor(
|
||||
self,
|
||||
decoded,
|
||||
meta=None,
|
||||
target_size=None,
|
||||
evaluate_quality=None,
|
||||
):
|
||||
tensor = self.build_infer_tensor_from_decoded(
|
||||
decoded=decoded,
|
||||
meta=meta,
|
||||
channels_expected=5,
|
||||
target_size=target_size,
|
||||
evaluate_quality=evaluate_quality,
|
||||
)
|
||||
return np.ascontiguousarray(tensor.astype(np.float32, copy=False))
|
||||
|
||||
|
|
@ -448,10 +872,14 @@ class OakFcc3Client:
|
|||
arr = arr[:, :, 0]
|
||||
|
||||
if bit_depth == 10 and arr.ndim == 2:
|
||||
role_size = self.sensor_size_by_role.get(
|
||||
str(role).lower(),
|
||||
[self.rgb_native_width, self.rgb_native_height],
|
||||
)
|
||||
raw16 = self.core.unpack_raw10_packed(
|
||||
arr,
|
||||
sensor_width=int(info.get("width", self.width)),
|
||||
sensor_height=int(info.get("height", self.height)),
|
||||
sensor_width=int(info.get("width", role_size[0])),
|
||||
sensor_height=int(info.get("height", role_size[1])),
|
||||
)
|
||||
|
||||
if role == "rgb":
|
||||
|
|
@ -513,7 +941,7 @@ class OakFcc3Client:
|
|||
or cam_meta.get("bayer")
|
||||
or stream_meta.get("bayer_pattern")
|
||||
or bayer_pattern
|
||||
or "RGGB"
|
||||
or self.bayer
|
||||
)
|
||||
bayer = str(bayer).upper()
|
||||
|
||||
|
|
@ -545,19 +973,17 @@ class OakFcc3Client:
|
|||
real_w = int(cam_meta.get("width", sensor_width))
|
||||
real_h = int(cam_meta.get("height", sensor_height))
|
||||
|
||||
core = RawProcessorCore(
|
||||
sensor_width=real_w,
|
||||
sensor_height=real_h,
|
||||
bayer_pattern=bayer,
|
||||
)
|
||||
cache_key = (real_w, real_h, bayer)
|
||||
preview = self._preview_cache.get(cache_key)
|
||||
if preview is None:
|
||||
preview = RawProcessorPreview(
|
||||
sensor_width=real_w,
|
||||
sensor_height=real_h,
|
||||
bayer_pattern=bayer,
|
||||
)
|
||||
self._preview_cache[cache_key] = preview
|
||||
|
||||
preview = RawProcessorPreview(
|
||||
sensor_width=real_w,
|
||||
sensor_height=real_h,
|
||||
bayer_pattern=bayer,
|
||||
)
|
||||
|
||||
raw16 = core.unpack_raw10_packed(
|
||||
raw16 = self.core.unpack_raw10_packed(
|
||||
packed,
|
||||
sensor_width=real_w,
|
||||
sensor_height=real_h,
|
||||
|
|
@ -624,6 +1050,11 @@ class OakFcc3Client:
|
|||
|
||||
|
||||
def decode_oak_aligned_multispec(self, frame, meta):
|
||||
if self.product_contract:
|
||||
raise RuntimeError(
|
||||
"MULTISPEC alinhado pela OAK é legado e não faz parte do contrato produto."
|
||||
)
|
||||
|
||||
"""
|
||||
Decodifica frames já alinhados pela OAK.
|
||||
|
||||
|
|
@ -672,6 +1103,11 @@ class OakFcc3Client:
|
|||
return decoded
|
||||
|
||||
def build_multispec_tensor_from_oak_aligned(self, decoded, meta=None):
|
||||
if self.product_contract:
|
||||
raise RuntimeError(
|
||||
"Tensor MULTISPEC pré-alinhado pela OAK é legado no contrato produto."
|
||||
)
|
||||
|
||||
"""
|
||||
Monta CHW [R,G,B,RE,NIR] sem reaplicar homografia.
|
||||
"""
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,95 +1,634 @@
|
|||
# camera_worker/oak_fcc3_core/raw_processor_preview.py
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
RawProcessorPreview - Production
|
||||
================================
|
||||
|
||||
Conversor EXCLUSIVAMENTE visual para RAW Bayer da câmera RGB.
|
||||
|
||||
Este módulo:
|
||||
- NÃO participa do tensor científico;
|
||||
- NÃO altera RAW salvo;
|
||||
- NÃO aplica Flat-Field;
|
||||
- NÃO aplica Radiometric Normalization;
|
||||
- NÃO aplica Homography;
|
||||
- NÃO deve ser usado para treino ou inferência.
|
||||
|
||||
Contrato oficial:
|
||||
CAM_A = RGB
|
||||
OV9782 -> 1280x800
|
||||
AR0234 -> 1920x1200
|
||||
|
||||
A resolução e o Bayer devem vir do module_params/Client já validados.
|
||||
|
||||
Pipeline de preview:
|
||||
RAW Bayer uint16
|
||||
-> robust display levels
|
||||
-> uint8
|
||||
-> demosaic BGR
|
||||
-> gray-world opcional
|
||||
-> contraste opcional
|
||||
-> JPEG/preview
|
||||
|
||||
Importante:
|
||||
O resultado desta classe é BGR, compatível com OpenCV.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from typing import Optional
|
||||
|
||||
|
||||
RAW_PROCESSOR_PREVIEW_VERSION = "production_v1_2026_08_24"
|
||||
|
||||
SUPPORTED_BAYER_PATTERNS = (
|
||||
"RGGB",
|
||||
"BGGR",
|
||||
"GRBG",
|
||||
"GBRG",
|
||||
)
|
||||
|
||||
SUPPORTED_BIT_DEPTHS = (
|
||||
8,
|
||||
10,
|
||||
12,
|
||||
16,
|
||||
)
|
||||
|
||||
|
||||
class RawProcessorPreview:
|
||||
def __init__(self, sensor_width: int, sensor_height: int, bayer_pattern: str = "GBRG"):
|
||||
self.sensor_width = sensor_width
|
||||
self.sensor_height = sensor_height
|
||||
self.bayer_pattern = bayer_pattern.upper()
|
||||
"""
|
||||
Preview RAW Bayer para debug/stream/salvamento visual.
|
||||
|
||||
Parâmetros
|
||||
----------
|
||||
sensor_width:
|
||||
Largura NATIVA do RGB de referência.
|
||||
|
||||
sensor_height:
|
||||
Altura NATIVA do RGB de referência.
|
||||
|
||||
bayer_pattern:
|
||||
Bayer real da CAM_A, vindo do contrato homologado.
|
||||
|
||||
sensor_name:
|
||||
Opcional, somente rastreabilidade.
|
||||
|
||||
strict_shape:
|
||||
Se True, RAW recebido deve possuir exatamente HxW nativo.
|
||||
Recomendado e default no produto.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sensor_width: int,
|
||||
sensor_height: int,
|
||||
bayer_pattern: str = "GBRG",
|
||||
sensor_name: Optional[str] = None,
|
||||
strict_shape: bool = True,
|
||||
):
|
||||
self.sensor_width = int(sensor_width)
|
||||
self.sensor_height = int(sensor_height)
|
||||
|
||||
if self.sensor_width <= 0 or self.sensor_height <= 0:
|
||||
raise ValueError(
|
||||
"Resolução inválida para preview: "
|
||||
f"{self.sensor_width}x{self.sensor_height}"
|
||||
)
|
||||
|
||||
self.bayer_pattern = str(
|
||||
bayer_pattern
|
||||
).strip().upper()
|
||||
|
||||
if self.bayer_pattern not in SUPPORTED_BAYER_PATTERNS:
|
||||
raise ValueError(
|
||||
f"Padrão Bayer não suportado: {self.bayer_pattern}. "
|
||||
f"Suportados={SUPPORTED_BAYER_PATTERNS}"
|
||||
)
|
||||
|
||||
self.sensor_name = (
|
||||
str(sensor_name).strip().upper()
|
||||
if sensor_name
|
||||
else None
|
||||
)
|
||||
|
||||
self.strict_shape = bool(
|
||||
strict_shape
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# Contrato
|
||||
# ============================================================
|
||||
|
||||
def get_contract(self) -> dict:
|
||||
return {
|
||||
"preview_version": RAW_PROCESSOR_PREVIEW_VERSION,
|
||||
"sensor_name": self.sensor_name,
|
||||
"sensor_width": int(
|
||||
self.sensor_width
|
||||
),
|
||||
"sensor_height": int(
|
||||
self.sensor_height
|
||||
),
|
||||
"sensor_size": [
|
||||
int(
|
||||
self.sensor_width
|
||||
),
|
||||
int(
|
||||
self.sensor_height
|
||||
),
|
||||
],
|
||||
"bayer_pattern": (
|
||||
self.bayer_pattern
|
||||
),
|
||||
"strict_shape": bool(
|
||||
self.strict_shape
|
||||
),
|
||||
"output_color_order": "BGR",
|
||||
"scientific_effect": "none",
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Validação
|
||||
# ============================================================
|
||||
|
||||
def _validate_raw(
|
||||
self,
|
||||
raw16: np.ndarray,
|
||||
bit_depth: int,
|
||||
) -> np.ndarray:
|
||||
if raw16 is None:
|
||||
raise ValueError(
|
||||
"RAW de preview é None."
|
||||
)
|
||||
|
||||
raw = np.asarray(
|
||||
raw16
|
||||
)
|
||||
|
||||
if raw.ndim != 2:
|
||||
raise ValueError(
|
||||
"RAW Bayer deve ser 2D HxW; "
|
||||
f"shape={raw.shape}"
|
||||
)
|
||||
|
||||
if self.strict_shape:
|
||||
expected = (
|
||||
self.sensor_height,
|
||||
self.sensor_width,
|
||||
)
|
||||
|
||||
if raw.shape != expected:
|
||||
raise ValueError(
|
||||
"RAW Bayer com shape incompatível: "
|
||||
f"recebido={raw.shape}, esperado={expected}"
|
||||
)
|
||||
|
||||
bit_depth = int(
|
||||
bit_depth
|
||||
)
|
||||
|
||||
if bit_depth not in SUPPORTED_BIT_DEPTHS:
|
||||
raise ValueError(
|
||||
f"bit_depth não suportado: {bit_depth}. "
|
||||
f"Suportados={SUPPORTED_BIT_DEPTHS}"
|
||||
)
|
||||
|
||||
if not np.issubdtype(
|
||||
raw.dtype,
|
||||
np.integer,
|
||||
):
|
||||
raise ValueError(
|
||||
"RAW Bayer para preview deve ser inteiro; "
|
||||
f"dtype={raw.dtype}"
|
||||
)
|
||||
|
||||
return raw
|
||||
|
||||
@staticmethod
|
||||
def _validate_levels(
|
||||
black_level,
|
||||
white_level,
|
||||
max_val,
|
||||
):
|
||||
black = (
|
||||
None
|
||||
if black_level is None
|
||||
else float(
|
||||
black_level
|
||||
)
|
||||
)
|
||||
|
||||
white = (
|
||||
None
|
||||
if white_level is None
|
||||
else float(
|
||||
white_level
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
black is not None
|
||||
and not np.isfinite(
|
||||
black
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
f"black_level inválido: {black_level}"
|
||||
)
|
||||
|
||||
if (
|
||||
white is not None
|
||||
and not np.isfinite(
|
||||
white
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
f"white_level inválido: {white_level}"
|
||||
)
|
||||
|
||||
if black is not None:
|
||||
black = float(
|
||||
np.clip(
|
||||
black,
|
||||
0.0,
|
||||
max_val,
|
||||
)
|
||||
)
|
||||
|
||||
if white is not None:
|
||||
white = float(
|
||||
np.clip(
|
||||
white,
|
||||
0.0,
|
||||
max_val,
|
||||
)
|
||||
)
|
||||
|
||||
return black, white
|
||||
|
||||
# ============================================================
|
||||
# RAW -> display uint8
|
||||
# ============================================================
|
||||
|
||||
def raw16_to_vis8(
|
||||
self, raw16: np.ndarray,
|
||||
black_level: Optional[int] = None,
|
||||
white_level: Optional[int] = None,
|
||||
gamma: float = 2.2,
|
||||
bit_depth: int = 10
|
||||
self,
|
||||
raw16: np.ndarray,
|
||||
black_level: Optional[float] = None,
|
||||
white_level: Optional[float] = None,
|
||||
gamma: float = 2.2,
|
||||
bit_depth: int = 10,
|
||||
auto_low_percentile: float = 0.5,
|
||||
auto_high_percentile: float = 99.5,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Conversão para visualização:
|
||||
- auto-level
|
||||
- gamma
|
||||
Converte RAW Bayer para uint8 de DISPLAY.
|
||||
|
||||
Quando níveis não são informados, usa percentis robustos em vez de
|
||||
min/max para evitar que hot pixels ou pequenos pontos saturados lavem
|
||||
todo o preview.
|
||||
|
||||
Isso é somente estética visual.
|
||||
"""
|
||||
max_val = float((1 << bit_depth) - 1)
|
||||
raw = self._validate_raw(
|
||||
raw16,
|
||||
bit_depth,
|
||||
)
|
||||
|
||||
raw = raw16.astype(np.float32)
|
||||
max_val = float(
|
||||
(1 << int(bit_depth))
|
||||
- 1
|
||||
)
|
||||
|
||||
if black_level is None:
|
||||
black_level = float(raw.min())
|
||||
if white_level is None:
|
||||
white_level = float(raw.max())
|
||||
black, white = (
|
||||
self._validate_levels(
|
||||
black_level,
|
||||
white_level,
|
||||
max_val,
|
||||
)
|
||||
)
|
||||
|
||||
if white_level <= black_level:
|
||||
norm = raw / max_val
|
||||
low_p = float(
|
||||
auto_low_percentile
|
||||
)
|
||||
|
||||
high_p = float(
|
||||
auto_high_percentile
|
||||
)
|
||||
|
||||
if not (
|
||||
0.0 <= low_p
|
||||
< high_p
|
||||
<= 100.0
|
||||
):
|
||||
raise ValueError(
|
||||
"Percentis automáticos inválidos: "
|
||||
f"{low_p}, {high_p}"
|
||||
)
|
||||
|
||||
raw_f = raw.astype(
|
||||
np.float32,
|
||||
copy=False,
|
||||
)
|
||||
|
||||
# Amostragem determinística para previews grandes.
|
||||
# Evita percentil full-frame de 2.3M pixels em todo snapshot.
|
||||
total = raw_f.size
|
||||
max_samples = 250_000
|
||||
|
||||
if total > max_samples:
|
||||
step = max(
|
||||
1,
|
||||
total // max_samples,
|
||||
)
|
||||
|
||||
sample = raw_f.reshape(
|
||||
-1
|
||||
)[::step]
|
||||
else:
|
||||
norm = (raw - black_level) / (white_level - black_level)
|
||||
sample = raw_f.reshape(
|
||||
-1
|
||||
)
|
||||
|
||||
norm = np.clip(norm, 0.0, 1.0)
|
||||
if black is None:
|
||||
black = float(
|
||||
np.percentile(
|
||||
sample,
|
||||
low_p,
|
||||
)
|
||||
)
|
||||
|
||||
if gamma is not None and gamma > 0:
|
||||
norm = np.power(norm, 1.0 / gamma)
|
||||
if white is None:
|
||||
white = float(
|
||||
np.percentile(
|
||||
sample,
|
||||
high_p,
|
||||
)
|
||||
)
|
||||
|
||||
return (norm * 255.0).clip(0, 255).astype(np.uint8)
|
||||
if white <= black:
|
||||
# Caso frame praticamente constante.
|
||||
if max_val <= 0.0:
|
||||
norm = np.zeros_like(
|
||||
raw_f,
|
||||
dtype=np.float32,
|
||||
)
|
||||
else:
|
||||
norm = raw_f / max_val
|
||||
|
||||
else:
|
||||
norm = (
|
||||
raw_f - black
|
||||
) / (
|
||||
white - black
|
||||
)
|
||||
|
||||
norm = np.clip(
|
||||
norm,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
|
||||
if gamma is not None:
|
||||
gamma = float(
|
||||
gamma
|
||||
)
|
||||
|
||||
if not np.isfinite(
|
||||
gamma
|
||||
) or gamma <= 0.0:
|
||||
raise ValueError(
|
||||
f"gamma inválido: {gamma}"
|
||||
)
|
||||
|
||||
norm = np.power(
|
||||
norm,
|
||||
1.0 / gamma,
|
||||
)
|
||||
|
||||
return np.clip(
|
||||
norm * 255.0,
|
||||
0.0,
|
||||
255.0,
|
||||
).astype(
|
||||
np.uint8
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# Bayer
|
||||
# ============================================================
|
||||
|
||||
def _debayer_code(self):
|
||||
"""
|
||||
Retorna código OpenCV que produz BGR.
|
||||
|
||||
A versão antiga usava COLOR_Bayer*2RGB_EA e em seguida tratava o
|
||||
resultado como BGR, podendo trocar vermelho/azul no preview.
|
||||
"""
|
||||
# IMPORTANTE:
|
||||
# Os aliases Bayer do OpenCV são contraintuitivos em relação ao
|
||||
# nome físico 2x2 que usamos no produto. O mapeamento abaixo foi
|
||||
# validado com mosaicos sintéticos de cor conhecida e produz BGR:
|
||||
#
|
||||
# físico RGGB -> OpenCV BayerBG2BGR
|
||||
# físico BGGR -> OpenCV BayerRG2BGR
|
||||
# físico GRBG -> OpenCV BayerGB2BGR
|
||||
# físico GBRG -> OpenCV BayerGR2BGR
|
||||
#
|
||||
# Não "simplifique" este mapa pela semelhança dos nomes.
|
||||
mapping = {
|
||||
"RGGB": cv2.COLOR_BayerRG2RGB_EA,
|
||||
"BGGR": cv2.COLOR_BayerBG2RGB_EA,
|
||||
"GRBG": cv2.COLOR_BayerGR2RGB_EA,
|
||||
"GBRG": cv2.COLOR_BayerGB2RGB_EA,
|
||||
}
|
||||
"RGGB": (
|
||||
cv2.COLOR_BayerBG2BGR_EA
|
||||
),
|
||||
"BGGR": (
|
||||
cv2.COLOR_BayerRG2BGR_EA
|
||||
),
|
||||
"GRBG": (
|
||||
cv2.COLOR_BayerGB2BGR_EA
|
||||
),
|
||||
"GBRG": (
|
||||
cv2.COLOR_BayerGR2BGR_EA
|
||||
),
|
||||
}
|
||||
|
||||
if self.bayer_pattern not in mapping:
|
||||
raise ValueError(f"Padrão Bayer não suportado: {self.bayer_pattern}")
|
||||
return mapping[
|
||||
self.bayer_pattern
|
||||
]
|
||||
|
||||
return mapping[self.bayer_pattern]
|
||||
# ============================================================
|
||||
# Ajustes VISUAIS
|
||||
# ============================================================
|
||||
|
||||
def apply_preview_white_balance(self, bgr: np.ndarray, strength: float = 1.0) -> np.ndarray:
|
||||
@staticmethod
|
||||
def apply_preview_white_balance(
|
||||
bgr: np.ndarray,
|
||||
strength: float = 1.0,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Gray-world simples para deixar o preview mais agradável.
|
||||
Não usar no raw de treino.
|
||||
Gray-world simples somente para deixar o preview legível.
|
||||
|
||||
Não usar no RAW científico.
|
||||
"""
|
||||
img = bgr.astype(np.float32)
|
||||
img = np.asarray(
|
||||
bgr
|
||||
)
|
||||
|
||||
mean_b = float(img[:, :, 0].mean())
|
||||
mean_g = float(img[:, :, 1].mean())
|
||||
mean_r = float(img[:, :, 2].mean())
|
||||
if (
|
||||
img.ndim != 3
|
||||
or img.shape[2] != 3
|
||||
):
|
||||
raise ValueError(
|
||||
"Preview WB exige BGR HxWx3; "
|
||||
f"shape={img.shape}"
|
||||
)
|
||||
|
||||
mean_gray = (mean_b + mean_g + mean_r) / 3.0
|
||||
strength = float(
|
||||
strength
|
||||
)
|
||||
|
||||
if not np.isfinite(
|
||||
strength
|
||||
):
|
||||
raise ValueError(
|
||||
f"strength inválido: {strength}"
|
||||
)
|
||||
|
||||
strength = float(
|
||||
np.clip(
|
||||
strength,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
)
|
||||
|
||||
work = img.astype(
|
||||
np.float32,
|
||||
copy=True,
|
||||
)
|
||||
|
||||
# Amostragem reduz custo em 1920x1200.
|
||||
h, w = work.shape[:2]
|
||||
sample_step = max(
|
||||
1,
|
||||
int(
|
||||
np.sqrt(
|
||||
(h * w)
|
||||
/ 200_000.0
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
sample = work[
|
||||
::sample_step,
|
||||
::sample_step,
|
||||
]
|
||||
|
||||
mean_b = float(
|
||||
sample[:, :, 0].mean()
|
||||
)
|
||||
|
||||
mean_g = float(
|
||||
sample[:, :, 1].mean()
|
||||
)
|
||||
|
||||
mean_r = float(
|
||||
sample[:, :, 2].mean()
|
||||
)
|
||||
|
||||
mean_gray = (
|
||||
mean_b
|
||||
+ mean_g
|
||||
+ mean_r
|
||||
) / 3.0
|
||||
|
||||
eps = 1e-6
|
||||
gain_b = mean_gray / max(mean_b, eps)
|
||||
gain_g = mean_gray / max(mean_g, eps)
|
||||
gain_r = mean_gray / max(mean_r, eps)
|
||||
|
||||
# strength=1 aplica total, strength=0 não aplica
|
||||
gain_b = 1.0 + (gain_b - 1.0) * strength
|
||||
gain_g = 1.0 + (gain_g - 1.0) * strength
|
||||
gain_r = 1.0 + (gain_r - 1.0) * strength
|
||||
gain_b = mean_gray / max(
|
||||
mean_b,
|
||||
eps,
|
||||
)
|
||||
|
||||
img[:, :, 0] *= gain_b
|
||||
img[:, :, 1] *= gain_g
|
||||
img[:, :, 2] *= gain_r
|
||||
gain_g = mean_gray / max(
|
||||
mean_g,
|
||||
eps,
|
||||
)
|
||||
|
||||
return np.clip(img, 0, 255).astype(np.uint8)
|
||||
gain_r = mean_gray / max(
|
||||
mean_r,
|
||||
eps,
|
||||
)
|
||||
|
||||
def apply_preview_contrast(self, bgr: np.ndarray, alpha: float = 1.08, beta: float = 0.0) -> np.ndarray:
|
||||
gain_b = 1.0 + (
|
||||
gain_b - 1.0
|
||||
) * strength
|
||||
|
||||
gain_g = 1.0 + (
|
||||
gain_g - 1.0
|
||||
) * strength
|
||||
|
||||
gain_r = 1.0 + (
|
||||
gain_r - 1.0
|
||||
) * strength
|
||||
|
||||
work[:, :, 0] *= gain_b
|
||||
work[:, :, 1] *= gain_g
|
||||
work[:, :, 2] *= gain_r
|
||||
|
||||
return np.clip(
|
||||
work,
|
||||
0.0,
|
||||
255.0,
|
||||
).astype(
|
||||
np.uint8
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def apply_preview_contrast(
|
||||
bgr: np.ndarray,
|
||||
alpha: float = 1.08,
|
||||
beta: float = 0.0,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Ajuste leve de contraste/brilho para preview.
|
||||
Contraste/brilho de DISPLAY.
|
||||
"""
|
||||
out = cv2.convertScaleAbs(bgr, alpha=alpha, beta=beta)
|
||||
return out
|
||||
alpha = float(
|
||||
alpha
|
||||
)
|
||||
|
||||
beta = float(
|
||||
beta
|
||||
)
|
||||
|
||||
if (
|
||||
not np.isfinite(
|
||||
alpha
|
||||
)
|
||||
or alpha <= 0.0
|
||||
):
|
||||
raise ValueError(
|
||||
f"alpha inválido: {alpha}"
|
||||
)
|
||||
|
||||
if not np.isfinite(
|
||||
beta
|
||||
):
|
||||
raise ValueError(
|
||||
f"beta inválido: {beta}"
|
||||
)
|
||||
|
||||
return cv2.convertScaleAbs(
|
||||
bgr,
|
||||
alpha=alpha,
|
||||
beta=beta,
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# Preview completo
|
||||
# ============================================================
|
||||
|
||||
def raw16_to_preview_bgr(
|
||||
self,
|
||||
|
|
@ -99,28 +638,89 @@ class RawProcessorPreview:
|
|||
apply_wb: bool = True,
|
||||
apply_contrast: bool = True,
|
||||
bit_depth: int = 10,
|
||||
black_level: Optional[float] = None,
|
||||
white_level: Optional[float] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Pipeline de preview bonito:
|
||||
1. auto-level + gamma no mosaico
|
||||
2. demosaic
|
||||
3. white balance simples
|
||||
4. leve contraste final
|
||||
Pipeline visual:
|
||||
1. robust levels + gamma no mosaico
|
||||
2. demosaic BGR
|
||||
3. gray-world opcional
|
||||
4. contraste opcional
|
||||
"""
|
||||
vis8 = self.raw16_to_vis8(raw16, gamma=gamma, bit_depth=bit_depth)
|
||||
bgr = cv2.cvtColor(vis8, self._debayer_code())
|
||||
vis8 = self.raw16_to_vis8(
|
||||
raw16,
|
||||
black_level=black_level,
|
||||
white_level=white_level,
|
||||
gamma=gamma,
|
||||
bit_depth=bit_depth,
|
||||
)
|
||||
|
||||
bgr = cv2.cvtColor(
|
||||
vis8,
|
||||
self._debayer_code(),
|
||||
)
|
||||
|
||||
if apply_wb:
|
||||
bgr = self.apply_preview_white_balance(bgr, strength=wb_strength)
|
||||
bgr = (
|
||||
self.apply_preview_white_balance(
|
||||
bgr,
|
||||
strength=wb_strength,
|
||||
)
|
||||
)
|
||||
|
||||
if apply_contrast:
|
||||
bgr = self.apply_preview_contrast(bgr, alpha=1.08, beta=0.0)
|
||||
bgr = (
|
||||
self.apply_preview_contrast(
|
||||
bgr,
|
||||
alpha=1.08,
|
||||
beta=0.0,
|
||||
)
|
||||
)
|
||||
|
||||
return bgr
|
||||
return np.ascontiguousarray(
|
||||
bgr,
|
||||
dtype=np.uint8,
|
||||
)
|
||||
|
||||
def raw16_to_preview_jpg_bytes(
|
||||
self,
|
||||
raw16: np.ndarray,
|
||||
jpeg_quality: int = 95,
|
||||
**preview_kwargs,
|
||||
) -> bytes:
|
||||
quality = int(
|
||||
jpeg_quality
|
||||
)
|
||||
|
||||
if not (
|
||||
1 <= quality <= 100
|
||||
):
|
||||
raise ValueError(
|
||||
f"jpeg_quality inválido: {quality}"
|
||||
)
|
||||
|
||||
bgr = (
|
||||
self.raw16_to_preview_bgr(
|
||||
raw16,
|
||||
**preview_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
ok, enc = cv2.imencode(
|
||||
".jpg",
|
||||
bgr,
|
||||
[
|
||||
int(
|
||||
cv2.IMWRITE_JPEG_QUALITY
|
||||
),
|
||||
quality,
|
||||
],
|
||||
)
|
||||
|
||||
def raw16_to_preview_jpg_bytes(self, raw16: np.ndarray, jpeg_quality: int = 95) -> bytes:
|
||||
bgr = self.raw16_to_preview_bgr(raw16)
|
||||
ok, enc = cv2.imencode(".jpg", bgr, [int(cv2.IMWRITE_JPEG_QUALITY), int(jpeg_quality)])
|
||||
if not ok:
|
||||
raise RuntimeError("Falha ao codificar preview JPG")
|
||||
raise RuntimeError(
|
||||
"Falha ao codificar preview JPG."
|
||||
)
|
||||
|
||||
return enc.tobytes()
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -149,7 +149,7 @@ class SegformerNavRunner:
|
|||
"trt_fp16_enable": bool(self.config.get("trt_fp16_enable", True)),
|
||||
"trt_engine_cache_enable": bool(self.config.get("trt_engine_cache_enable", True)),
|
||||
"trt_engine_cache_path": str(
|
||||
self.config.get("trt_engine_cache_path", "./trt_cache_visual_worker")
|
||||
self.config.get("trt_engine_cache_path", "./trt_cache_weed_worker")
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1402,13 +1402,17 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
|||
calibrando = ctx["calibrando"]
|
||||
bomba_principal = ctx["bomba_principal"]
|
||||
|
||||
autonomia_corredor_aplicavel = bool(
|
||||
auto
|
||||
and ctx["operacao_iniciada"]
|
||||
and not calibrando
|
||||
and not ctx["finalizando"]
|
||||
)
|
||||
|
||||
herbicida_pode_bloquear = bool(
|
||||
auto and
|
||||
ctx["operacao_iniciada"] and
|
||||
not calibrando and
|
||||
not ctx["finalizando"] and
|
||||
not ctx["pausa"] and
|
||||
not ctx["emergencia"]
|
||||
autonomia_corredor_aplicavel
|
||||
and not ctx["pausa"]
|
||||
and not ctx["emergencia"]
|
||||
)
|
||||
|
||||
em_transicao = self._pulverizacao_em_transicao(ctx)
|
||||
|
|
@ -1491,7 +1495,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
|||
})
|
||||
score = min(score, 90)
|
||||
|
||||
if not ctx["herbicida_ok_corredor"]:
|
||||
if (autonomia_corredor_aplicavel and not ctx["herbicida_ok_corredor"]):
|
||||
motivo = self._texto_ou_padrao(
|
||||
ctx.get("autonomia_motivo"),
|
||||
ctx.get("motivo_herbicida_corredor"),
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
|||
|
||||
motivos_gerais = []
|
||||
condicoes_gerais = []
|
||||
drivers_com_travamento = []
|
||||
saude_total_ativos = 0.0
|
||||
total_drivers_em_uso = 0
|
||||
algum_conectado = False
|
||||
|
|
@ -209,6 +210,17 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
|||
conectado = self._bool(saude_mod.get("conectado_efetivo", False))
|
||||
em_uso = self._bool(saude_mod.get("em_uso", True))
|
||||
|
||||
detalhes_valores = self._as_dict(saude_mod.get("detalhes_valores", {}))
|
||||
if self._bool(detalhes_valores.get("travamento_confirmado", False)):
|
||||
drivers_com_travamento.append({
|
||||
"id": endereco_str,
|
||||
"label": saude_mod.get("label"),
|
||||
"rpm": detalhes_valores.get("rpm", 0),
|
||||
"rpm_sp": detalhes_valores.get("rpm_sp", 0),
|
||||
"corrente_motor": detalhes_valores.get("corrente_motor", 0),
|
||||
"nivel": detalhes_valores.get("travamento_nivel"),
|
||||
})
|
||||
|
||||
if conectado:
|
||||
algum_conectado = True
|
||||
|
||||
|
|
@ -253,6 +265,9 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
|||
"drivers_ativos": ativos,
|
||||
"drivers_em_uso": total_drivers_em_uso,
|
||||
"modelo": "movimentacao_inteligente_por_resposta_comando_valores_e_telemetria",
|
||||
|
||||
"travamento_confirmado": len(drivers_com_travamento) > 0,
|
||||
"drivers_com_travamento": drivers_com_travamento
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -924,6 +939,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
|||
"temperatura_motor": temperatura_motor,
|
||||
"temperatura_driver": temperatura_driver,
|
||||
"score_valores": int(max(0, min(100, round(score)))),
|
||||
|
||||
# Novo contrato
|
||||
"travamento_detectado": possivel_travamento,
|
||||
"travamento_confirmado": estado_travamento["nivel"] == "confirmado",
|
||||
"travamento_nivel": estado_travamento["nivel"],
|
||||
"travamento_duracao_s": estado_travamento.get("duracao_s", 0.0),
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
COMANDO_GRACE_MS = 2000.0
|
||||
COMANDO_ALERTA_MS = 3000.0
|
||||
COMANDO_FALHA_MS = 7000.0
|
||||
COMANDO_RECUPERACAO_MS = 1500.0
|
||||
|
||||
COMPONENTE_RECUPERACAO_GRACE_MS = 3000.0
|
||||
MODULO_RECUPERACAO_GRACE_MS = 10000.0
|
||||
|
|
@ -82,7 +83,10 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
updates[f"sensores.{item['key']}.saude"] = s
|
||||
|
||||
for item in servos:
|
||||
s = self._avaliar_servo(item)
|
||||
s = self._avaliar_servo(
|
||||
item,
|
||||
modulo_disponivel=sen_disponivel,
|
||||
)
|
||||
saude_individual.append(s)
|
||||
updates[f"servos.{item['key']}.saude"] = s
|
||||
|
||||
|
|
@ -92,7 +96,10 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
updates[f"reles.{item['key']}.saude"] = s
|
||||
|
||||
for item in sinaleiros:
|
||||
s = self._avaliar_sinaleiro(item)
|
||||
s = self._avaliar_sinaleiro(
|
||||
item,
|
||||
modulo_disponivel=sen_disponivel,
|
||||
)
|
||||
saude_individual.append(s)
|
||||
updates[f"sinaleiros.{item['key']}.saude"] = s
|
||||
|
||||
|
|
@ -239,12 +246,6 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
corrente_36v = self._valor_sensor(sensores, "AB36V", "Corrente")
|
||||
temp_bateria = self._valor_sensor(sensores, "TBAT", "Temperatura")
|
||||
|
||||
temps_motor = []
|
||||
for s in sensores:
|
||||
label = str(s.get("dados", {}).get("label", ""))
|
||||
if label.startswith("TMV"):
|
||||
temps_motor.append(self._float(self._campo_atual(s["dados"], "Temperatura", 0), 0))
|
||||
|
||||
return {
|
||||
"bms_ligado": bms_ligado,
|
||||
"percent_bateria": percent_bateria,
|
||||
|
|
@ -257,7 +258,6 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
"tensao_36v": tensao_36v,
|
||||
"corrente_36v": corrente_36v,
|
||||
"temp_bateria": temp_bateria,
|
||||
"temperaturas_motor": temps_motor,
|
||||
"debug": {
|
||||
"bms_ligado": bms_ligado,
|
||||
"percent_bateria": percent_bateria,
|
||||
|
|
@ -267,7 +267,6 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
"sensor_ab36v_em_uso": ab36v_em_uso,
|
||||
"tensao_36v": tensao_36v,
|
||||
"corrente_36v": corrente_36v,
|
||||
"temp_motor_max": max(temps_motor) if temps_motor else 0,
|
||||
"temp_bateria": temp_bateria,
|
||||
},
|
||||
}
|
||||
|
|
@ -281,7 +280,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
conectado = self._bool(dados.get("conectado", False))
|
||||
aferir = self._bool(dados.get("aferir", False))
|
||||
mandatorio = self._bool(dados.get("mandatorio", False))
|
||||
em_uso = aferir
|
||||
delegado_ao_mov = str(dados.get("label", "")).upper().startswith("TMV")
|
||||
em_uso = aferir and not delegado_ao_mov
|
||||
motivos = []
|
||||
condicoes = []
|
||||
|
||||
|
|
@ -298,7 +298,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
saude = int(max(0, min(100, round(saude))))
|
||||
status = self._resolver_status_item(conectado, saude, condicoes)
|
||||
debug = {"resposta": resposta_score, "valores": valor_debug, "telemetria": telemetria["parametros"]}
|
||||
debug = {
|
||||
"resposta": resposta_score,
|
||||
"valores": valor_debug,
|
||||
"telemetria": telemetria["parametros"],
|
||||
"avaliacao_delegada_ao_mov": delegado_ao_mov,
|
||||
}
|
||||
return self._item(item, status, saude, motivos, condicoes, em_uso, debug, "sensor")
|
||||
|
||||
def _avaliar_valores_sensor(self, item, ctx, motivos, condicoes):
|
||||
|
|
@ -403,8 +408,6 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
label = str(dados.get("label", ""))
|
||||
if label == "AB36V" and ctx["sensor_ab36v_em_uso"]:
|
||||
return ["Tensao", "Corrente", "Temperatura"]
|
||||
if label.startswith("TMV"):
|
||||
return ["Temperatura"]
|
||||
if label == "TBAT" and not ctx["bms_ligado"]:
|
||||
return ["Temperatura"]
|
||||
if mandatorio:
|
||||
|
|
@ -415,73 +418,254 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
# Atuadores auxiliares
|
||||
# ============================================================
|
||||
|
||||
def _avaliar_servo(self, item):
|
||||
def _avaliar_servo(self, item, modulo_disponivel=True):
|
||||
dados = item["dados"]
|
||||
conectado = self._bool(dados.get("conectado", False))
|
||||
controlar = self._bool(dados.get("controlar", False))
|
||||
if not conectado:
|
||||
return self._item(item, StatusModulo.DESCONECTADO, 0, ["desconectado"], [], controlar, {}, "servo")
|
||||
runtime, suspenso = self._preparar_atuador_auxiliar(
|
||||
item=item,
|
||||
grupo="servo",
|
||||
nome="Servo SEN",
|
||||
modulo_disponivel=modulo_disponivel,
|
||||
)
|
||||
if suspenso is not None:
|
||||
return suspenso
|
||||
|
||||
motivos, condicoes = [], []
|
||||
resposta = self._score_resposta(dados, motivos, condicoes, "Servo SEN")
|
||||
comando, dbg_cmd = self._avaliar_comando_servo(item, motivos, condicoes)
|
||||
telem = self._avaliar_telemetria(dados, motivos, ["ServoAnguloLeitura"] if controlar else [])
|
||||
comando, dbg_cmd = self._avaliar_comando_servo(
|
||||
item,
|
||||
motivos,
|
||||
condicoes,
|
||||
runtime,
|
||||
)
|
||||
telem = self._avaliar_telemetria(
|
||||
dados,
|
||||
motivos,
|
||||
["ServoAnguloLeitura"] if controlar else [],
|
||||
)
|
||||
|
||||
saude = resposta * 0.25 + comando * 0.60 + telem["score"] * 0.15
|
||||
if any(self._is_falha_hardware(c) for c in condicoes):
|
||||
saude = min(saude, 45)
|
||||
saude = int(max(0, min(100, round(saude))))
|
||||
return self._item(item, self._resolver_status_item(conectado, saude, condicoes), saude, motivos, condicoes, controlar, {"resposta": resposta, "comando": dbg_cmd, "telemetria": telem["parametros"]}, "servo")
|
||||
|
||||
def _avaliar_comando_servo(self, item, motivos, condicoes):
|
||||
return self._item(
|
||||
item,
|
||||
self._resolver_status_item(True, saude, condicoes),
|
||||
saude,
|
||||
motivos,
|
||||
condicoes,
|
||||
controlar,
|
||||
{
|
||||
"resposta": resposta,
|
||||
"comando": dbg_cmd,
|
||||
"telemetria": telem["parametros"],
|
||||
},
|
||||
"servo",
|
||||
)
|
||||
|
||||
def _avaliar_comando_servo(self, item, motivos, condicoes, runtime):
|
||||
dados = item["dados"]
|
||||
label = dados.get("label")
|
||||
controlar = self._bool(dados.get("controlar", False))
|
||||
angulo_sp = self._float(dados.get("angulo_desejado", dados.get("angulo_sp", 0)), 0)
|
||||
angulo = self._float(dados.get("angulo_leitura", self._campo_atual(dados, "ServoAnguloLeitura", -1)), -1)
|
||||
ultimo_ms = self._float(dados.get("ultimo_comando_ms", 999999), 999999)
|
||||
tem_idade = ultimo_ms < 900000
|
||||
|
||||
if angulo < 0:
|
||||
motivos.append("Servo sem leitura de ângulo")
|
||||
condicoes.append({
|
||||
"label": "Servo SEN",
|
||||
"valor": angulo,
|
||||
"severidade": 70 if controlar else 35,
|
||||
"classe": "processo",
|
||||
"descricao": f"Servo {label} sem leitura de ângulo válida",
|
||||
"acoes": ["Verificar retorno de ServoAnguloLeitura."],
|
||||
})
|
||||
return (60 if controlar else 85), {"angulo": angulo, "angulo_sp": angulo_sp, "sem_leitura": True}
|
||||
desejado_presente = (
|
||||
"angulo_desejado" in dados or
|
||||
"angulo_sp" in dados
|
||||
)
|
||||
leitura_presente = (
|
||||
"angulo_leitura" in dados or
|
||||
self._campo_existe(dados, "ServoAnguloLeitura")
|
||||
)
|
||||
|
||||
angulo_sp = self._float(
|
||||
dados.get("angulo_desejado", dados.get("angulo_sp", 0.0)),
|
||||
0.0,
|
||||
)
|
||||
angulo = self._float(
|
||||
dados.get(
|
||||
"angulo_leitura",
|
||||
self._campo_atual(dados, "ServoAnguloLeitura", -1.0),
|
||||
),
|
||||
-1.0,
|
||||
)
|
||||
|
||||
evidencia = self._evidencia_comando(dados)
|
||||
debug = {
|
||||
"controlar": controlar,
|
||||
"angulo": angulo,
|
||||
"angulo_sp": angulo_sp,
|
||||
**evidencia,
|
||||
}
|
||||
|
||||
if not controlar:
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
return 100, debug
|
||||
|
||||
if not desejado_presente or not leitura_presente:
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
debug["contrato_comando_completo"] = False
|
||||
return 92, debug
|
||||
|
||||
token_desejado = round(angulo_sp, 3)
|
||||
self._registrar_desejado(runtime, token_desejado)
|
||||
|
||||
erro = abs(angulo - angulo_sp)
|
||||
if not controlar or erro <= self.SERVO_TOL_GRAUS:
|
||||
return 100, {"angulo": angulo, "angulo_sp": angulo_sp, "erro": erro, "controlar": controlar}
|
||||
if tem_idade and ultimo_ms < self.COMANDO_GRACE_MS:
|
||||
return 96, {"angulo": angulo, "angulo_sp": angulo_sp, "erro": erro, "ultimo_comando_ms": ultimo_ms}
|
||||
if erro >= self.SERVO_FALHA_GRAUS and tem_idade and ultimo_ms >= self.COMANDO_FALHA_MS:
|
||||
motivos.append(f"Servo {label} não convergiu para o SP ({angulo:.1f}/{angulo_sp:.1f})")
|
||||
condicoes.append({
|
||||
"label": "Servo SEN",
|
||||
"valor": angulo,
|
||||
"severidade": 92,
|
||||
"classe": "falha_hardware",
|
||||
"descricao": f"Servo {label} não acompanhou o comando de ângulo",
|
||||
"acoes": ["Verificar travamento mecânico ou alimentação do servo."],
|
||||
})
|
||||
return 25, {"angulo": angulo, "angulo_sp": angulo_sp, "erro": erro, "ultimo_comando_ms": ultimo_ms}
|
||||
if erro >= self.SERVO_ALERTA_GRAUS:
|
||||
motivos.append(f"Servo {label} distante do SP ({angulo:.1f}/{angulo_sp:.1f})")
|
||||
condicoes.append({
|
||||
"label": "Servo SEN",
|
||||
"valor": angulo,
|
||||
"severidade": min(89, max(45, int(erro * 4))),
|
||||
"classe": "processo",
|
||||
"descricao": f"Servo {label} distante do ângulo desejado",
|
||||
"acoes": ["Acompanhar se o erro persiste."],
|
||||
})
|
||||
return 70, {"angulo": angulo, "angulo_sp": angulo_sp, "erro": erro, "ultimo_comando_ms": ultimo_ms}
|
||||
motivos.append(f"Servo {label} fora da tolerância momentaneamente ({erro:.1f}°)")
|
||||
return 88, {"angulo": angulo, "angulo_sp": angulo_sp, "erro": erro, "ultimo_comando_ms": ultimo_ms}
|
||||
debug["erro"] = erro
|
||||
|
||||
if erro <= self.SERVO_TOL_GRAUS:
|
||||
return self._confirmar_recuperacao_servo(
|
||||
runtime,
|
||||
label,
|
||||
angulo,
|
||||
angulo_sp,
|
||||
erro,
|
||||
motivos,
|
||||
condicoes,
|
||||
debug,
|
||||
)
|
||||
|
||||
runtime["coerencia_desde"] = None
|
||||
|
||||
if not evidencia["possui_comando"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||
return 96, debug
|
||||
|
||||
if not evidencia["telemetria_pos_comando"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
||||
return 96, debug
|
||||
|
||||
if evidencia["ultimo_comando_ms"] < self.COMANDO_GRACE_MS:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||
return 96, debug
|
||||
|
||||
agora = time.monotonic()
|
||||
tempo_retorno_ms = (
|
||||
agora - runtime["componente_recuperado_em"]
|
||||
) * 1000.0
|
||||
if tempo_retorno_ms < self.COMPONENTE_RECUPERACAO_GRACE_MS:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "estabilização após reconexão"
|
||||
debug["tempo_desde_retorno_componente_ms"] = tempo_retorno_ms
|
||||
return 96, debug
|
||||
|
||||
if runtime["divergencia_desde"] is None:
|
||||
runtime["divergencia_desde"] = agora
|
||||
|
||||
tempo_divergente_ms = (
|
||||
agora - runtime["divergencia_desde"]
|
||||
) * 1000.0
|
||||
debug["tempo_divergente_ms"] = tempo_divergente_ms
|
||||
|
||||
if runtime["nivel_divergencia"] == "falha":
|
||||
return self._falha_servo(
|
||||
label, angulo, angulo_sp, erro,
|
||||
motivos, condicoes, debug,
|
||||
)
|
||||
|
||||
if tempo_divergente_ms < self.COMANDO_ALERTA_MS:
|
||||
return 96, debug
|
||||
|
||||
if (
|
||||
erro >= self.SERVO_FALHA_GRAUS and
|
||||
tempo_divergente_ms >= self.COMANDO_FALHA_MS
|
||||
):
|
||||
runtime["nivel_divergencia"] = "falha"
|
||||
return self._falha_servo(
|
||||
label, angulo, angulo_sp, erro,
|
||||
motivos, condicoes, debug,
|
||||
)
|
||||
|
||||
runtime["nivel_divergencia"] = "alerta"
|
||||
motivos.append(
|
||||
f"Servo {label} distante do SP ({angulo:.1f}/{angulo_sp:.1f})"
|
||||
)
|
||||
condicoes.append({
|
||||
"label": "Servo SEN",
|
||||
"valor": angulo,
|
||||
"severidade": min(89, max(55, int(erro * 4))),
|
||||
"classe": "processo",
|
||||
"descricao": f"Servo {label} não convergiu para o ângulo desejado",
|
||||
"acoes": [
|
||||
"Acompanhar a convergência do servo.",
|
||||
"Verificar esforço mecânico se o erro permanecer.",
|
||||
],
|
||||
})
|
||||
return (68 if erro >= self.SERVO_ALERTA_GRAUS else 82), debug
|
||||
|
||||
def _falha_servo(
|
||||
self,
|
||||
label,
|
||||
angulo,
|
||||
angulo_sp,
|
||||
erro,
|
||||
motivos,
|
||||
condicoes,
|
||||
debug,
|
||||
):
|
||||
motivos.append(
|
||||
f"Servo {label} não convergiu para o SP "
|
||||
f"({angulo:.1f}/{angulo_sp:.1f})"
|
||||
)
|
||||
condicoes.append({
|
||||
"label": "Servo SEN",
|
||||
"valor": angulo,
|
||||
"severidade": 92,
|
||||
"classe": "falha_hardware",
|
||||
"descricao": f"Servo {label} não acompanhou o comando de ângulo",
|
||||
"acoes": [
|
||||
"Verificar travamento mecânico.",
|
||||
"Verificar alimentação e retorno do servo.",
|
||||
],
|
||||
})
|
||||
debug["erro"] = erro
|
||||
return 25, debug
|
||||
|
||||
def _confirmar_recuperacao_servo(
|
||||
self,
|
||||
runtime,
|
||||
label,
|
||||
angulo,
|
||||
angulo_sp,
|
||||
erro,
|
||||
motivos,
|
||||
condicoes,
|
||||
debug,
|
||||
):
|
||||
runtime["divergencia_desde"] = None
|
||||
|
||||
if runtime["nivel_divergencia"] is None:
|
||||
runtime["coerencia_desde"] = None
|
||||
return 100, debug
|
||||
|
||||
agora = time.monotonic()
|
||||
if runtime["coerencia_desde"] is None:
|
||||
runtime["coerencia_desde"] = agora
|
||||
|
||||
tempo_coerente_ms = (
|
||||
agora - runtime["coerencia_desde"]
|
||||
) * 1000.0
|
||||
debug["recuperacao_em_confirmacao"] = True
|
||||
debug["tempo_coerente_ms"] = tempo_coerente_ms
|
||||
|
||||
if tempo_coerente_ms >= self.COMANDO_RECUPERACAO_MS:
|
||||
runtime["nivel_divergencia"] = None
|
||||
runtime["coerencia_desde"] = None
|
||||
return 100, debug
|
||||
|
||||
motivos.append(f"Servo {label} recuperou o SP; confirmando estabilidade")
|
||||
condicoes.append({
|
||||
"label": "Servo SEN",
|
||||
"valor": angulo,
|
||||
"severidade": 55,
|
||||
"classe": "processo",
|
||||
"descricao": f"Servo {label} voltou ao SP e está em confirmação",
|
||||
"acoes": ["Aguardar confirmação da estabilidade do retorno."],
|
||||
})
|
||||
return 78, debug
|
||||
|
||||
def _avaliar_rele(self, item, modulo_disponivel=True):
|
||||
return self._avaliar_saida_binaria(
|
||||
|
|
@ -493,8 +677,11 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
modulo_disponivel=modulo_disponivel,
|
||||
)
|
||||
|
||||
def _avaliar_sinaleiro(self, item):
|
||||
return self._avaliar_sinaleiro_impl(item)
|
||||
def _avaliar_sinaleiro(self, item, modulo_disponivel=True):
|
||||
return self._avaliar_sinaleiro_impl(
|
||||
item,
|
||||
modulo_disponivel=modulo_disponivel,
|
||||
)
|
||||
|
||||
def _avaliar_saida_binaria(
|
||||
self,
|
||||
|
|
@ -672,8 +859,155 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
"componente_recuperado_em": 0.0,
|
||||
|
||||
"divergencia_desde": None,
|
||||
"coerencia_desde": None,
|
||||
"nivel_divergencia": None,
|
||||
"ultimo_desejado": None,
|
||||
})
|
||||
|
||||
def _preparar_atuador_auxiliar(
|
||||
self,
|
||||
item,
|
||||
grupo,
|
||||
nome,
|
||||
modulo_disponivel,
|
||||
):
|
||||
dados = item["dados"]
|
||||
controlar = self._bool(dados.get("controlar", False))
|
||||
conectado = self._bool(dados.get("conectado", False))
|
||||
agora = time.monotonic()
|
||||
runtime = self._runtime_saida_binaria(item)
|
||||
|
||||
if not modulo_disponivel:
|
||||
runtime["modulo_disponivel_anterior"] = False
|
||||
runtime["componente_disponivel_anterior"] = False
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
return runtime, self._item(
|
||||
item,
|
||||
StatusModulo.DESCONECTADO,
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
False,
|
||||
{
|
||||
"avaliacao_suspensa": True,
|
||||
"motivo_suspensao": "Módulo SEN indisponível",
|
||||
},
|
||||
grupo,
|
||||
)
|
||||
|
||||
if not runtime["modulo_disponivel_anterior"]:
|
||||
runtime["modulo_recuperado_em"] = agora
|
||||
runtime["componente_disponivel_anterior"] = False
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
|
||||
runtime["modulo_disponivel_anterior"] = True
|
||||
tempo_retorno_modulo_ms = (
|
||||
agora - runtime["modulo_recuperado_em"]
|
||||
) * 1000.0
|
||||
|
||||
if not conectado:
|
||||
runtime["componente_disponivel_anterior"] = False
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
|
||||
if tempo_retorno_modulo_ms < self.MODULO_RECUPERACAO_GRACE_MS:
|
||||
return runtime, self._item(
|
||||
item,
|
||||
StatusModulo.DESCONECTADO,
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
False,
|
||||
{
|
||||
"avaliacao_suspensa": True,
|
||||
"motivo_suspensao": (
|
||||
"Aguardando reinicialização após retorno do SEN"
|
||||
),
|
||||
"tempo_desde_retorno_modulo_ms": tempo_retorno_modulo_ms,
|
||||
},
|
||||
grupo,
|
||||
)
|
||||
|
||||
return runtime, self._item(
|
||||
item,
|
||||
StatusModulo.DESCONECTADO,
|
||||
0,
|
||||
["desconectado após período de recuperação"],
|
||||
[{
|
||||
"label": nome,
|
||||
"valor": 0,
|
||||
"severidade": 92,
|
||||
"classe": "falha_hardware",
|
||||
"descricao": (
|
||||
f"{dados.get('label')} não reinicializou após o retorno do SEN"
|
||||
),
|
||||
"acoes": [
|
||||
"Verificar configuração do componente.",
|
||||
"Verificar retorno de telemetria.",
|
||||
"Verificar firmware do SEN.",
|
||||
],
|
||||
}],
|
||||
controlar,
|
||||
{
|
||||
"tempo_desde_retorno_modulo_ms": tempo_retorno_modulo_ms,
|
||||
},
|
||||
grupo,
|
||||
)
|
||||
|
||||
if not runtime["componente_disponivel_anterior"]:
|
||||
runtime["componente_recuperado_em"] = agora
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
|
||||
runtime["componente_disponivel_anterior"] = True
|
||||
return runtime, None
|
||||
|
||||
def _evidencia_comando(self, dados):
|
||||
ultimo_comando_ms = self._float(
|
||||
dados.get("ultimo_comando_ms", 999999),
|
||||
999999,
|
||||
)
|
||||
ultima_resposta_ms = self._float(
|
||||
dados.get("ultima_resposta_ms", 999999),
|
||||
999999,
|
||||
)
|
||||
possui_comando = self._bool(
|
||||
dados.get(
|
||||
"possui_comando",
|
||||
ultimo_comando_ms < 900000,
|
||||
)
|
||||
)
|
||||
|
||||
if "telemetria_pos_comando" in dados:
|
||||
telemetria_pos_comando = self._bool(
|
||||
dados.get("telemetria_pos_comando", False)
|
||||
)
|
||||
else:
|
||||
telemetria_pos_comando = (
|
||||
possui_comando and
|
||||
ultima_resposta_ms <= ultimo_comando_ms + 100.0
|
||||
)
|
||||
|
||||
return {
|
||||
"possui_comando": possui_comando,
|
||||
"telemetria_pos_comando": telemetria_pos_comando,
|
||||
"ultimo_comando_ms": ultimo_comando_ms,
|
||||
"ultima_resposta_ms": ultima_resposta_ms,
|
||||
}
|
||||
|
||||
def _registrar_desejado(self, runtime, token_desejado):
|
||||
if runtime.get("ultimo_desejado") == token_desejado:
|
||||
return
|
||||
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
runtime["ultimo_desejado"] = token_desejado
|
||||
|
||||
@staticmethod
|
||||
def _resetar_estado_comando(runtime, manter_desejado=True):
|
||||
runtime["divergencia_desde"] = None
|
||||
runtime["coerencia_desde"] = None
|
||||
runtime["nivel_divergencia"] = None
|
||||
if not manter_desejado:
|
||||
runtime["ultimo_desejado"] = None
|
||||
|
||||
def _avaliar_comando_binario(
|
||||
self,
|
||||
item,
|
||||
|
|
@ -874,57 +1208,213 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
return 30, debug
|
||||
|
||||
def _avaliar_sinaleiro_impl(self, item):
|
||||
def _avaliar_sinaleiro_impl(self, item, modulo_disponivel=True):
|
||||
dados = item["dados"]
|
||||
conectado = self._bool(dados.get("conectado", False))
|
||||
controlar = self._bool(dados.get("controlar", False))
|
||||
if not conectado:
|
||||
return self._item(item, StatusModulo.DESCONECTADO, 0, ["desconectado"], [], controlar, {}, "sinaleiro")
|
||||
runtime, suspenso = self._preparar_atuador_auxiliar(
|
||||
item=item,
|
||||
grupo="sinaleiro",
|
||||
nome="Sinaleiro SEN",
|
||||
modulo_disponivel=modulo_disponivel,
|
||||
)
|
||||
if suspenso is not None:
|
||||
return suspenso
|
||||
|
||||
motivos, condicoes = [], []
|
||||
resposta = self._score_resposta(dados, motivos, condicoes, "Sinaleiro SEN")
|
||||
comando, dbg_cmd = self._avaliar_comando_sinaleiro(item, motivos, condicoes)
|
||||
resposta = self._score_resposta(
|
||||
dados,
|
||||
motivos,
|
||||
condicoes,
|
||||
"Sinaleiro SEN",
|
||||
)
|
||||
comando, dbg_cmd = self._avaliar_comando_sinaleiro(
|
||||
item,
|
||||
motivos,
|
||||
condicoes,
|
||||
runtime,
|
||||
)
|
||||
telem = self._avaliar_telemetria(dados, motivos, [])
|
||||
# Sinaleiro é diagnóstico visual: não derruba operação sozinho.
|
||||
|
||||
# Sinaleiro é diagnóstico visual: uma divergência confirmada
|
||||
# gera alerta, mas não transforma o SEN inteiro em falha.
|
||||
saude = resposta * 0.45 + comando * 0.45 + telem["score"] * 0.10
|
||||
saude = int(max(0, min(100, round(saude))))
|
||||
return self._item(item, self._resolver_status_item(conectado, saude, condicoes), saude, motivos, condicoes, controlar, {"resposta": resposta, "comando": dbg_cmd, "telemetria": telem["parametros"]}, "sinaleiro")
|
||||
|
||||
def _avaliar_comando_sinaleiro(self, item, motivos, condicoes):
|
||||
return self._item(
|
||||
item,
|
||||
self._resolver_status_item(True, saude, condicoes),
|
||||
saude,
|
||||
motivos,
|
||||
condicoes,
|
||||
controlar,
|
||||
{
|
||||
"resposta": resposta,
|
||||
"comando": dbg_cmd,
|
||||
"telemetria": telem["parametros"],
|
||||
},
|
||||
"sinaleiro",
|
||||
)
|
||||
|
||||
def _avaliar_comando_sinaleiro(
|
||||
self,
|
||||
item,
|
||||
motivos,
|
||||
condicoes,
|
||||
runtime,
|
||||
):
|
||||
dados = item["dados"]
|
||||
label = dados.get("label")
|
||||
controlar = self._bool(dados.get("controlar", False))
|
||||
id_sp = self._safe_int(dados.get("status_led_id_sp", dados.get("led_id_sp", -1)), -1)
|
||||
st_sp = self._safe_int(dados.get("status_led_sp", dados.get("led_status_sp", -1)), -1)
|
||||
id_lido = self._safe_int(dados.get("status_led_id", self._campo_atual(dados, "StatusLedID", -1)), -1)
|
||||
st_lido = self._safe_int(dados.get("status_led_leitura", self._campo_atual(dados, "StatusLedLeitura", -1)), -1)
|
||||
ultimo_ms = self._float(dados.get("ultimo_comando_ms", 999999), 999999)
|
||||
|
||||
id_sp = self._safe_int(
|
||||
dados.get("status_led_id_sp", dados.get("led_id_sp", -1)),
|
||||
-1,
|
||||
)
|
||||
st_sp = self._safe_int(
|
||||
dados.get("status_led_sp", dados.get("led_status_sp", -1)),
|
||||
-1,
|
||||
)
|
||||
id_lido = self._safe_int(
|
||||
dados.get(
|
||||
"status_led_id",
|
||||
self._campo_atual(dados, "StatusLedID", -1),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
|
||||
# O publicador C# envia as propriedades em todos os ciclos.
|
||||
# Portanto, presença lógica é determinada pelo valor válido,
|
||||
# não apenas pela existência da chave no dicionário.
|
||||
id_sp_presente = id_sp >= 0
|
||||
status_sp_presente = st_sp >= 0
|
||||
st_lido = self._safe_int(
|
||||
dados.get(
|
||||
"status_led_leitura",
|
||||
self._campo_atual(dados, "StatusLedLeitura", -1),
|
||||
),
|
||||
-1,
|
||||
)
|
||||
|
||||
evidencia = self._evidencia_comando(dados)
|
||||
debug = {
|
||||
"controlar": controlar,
|
||||
"status_lido": st_lido,
|
||||
"status_sp": st_sp,
|
||||
"id_lido": id_lido,
|
||||
"id_sp": id_sp,
|
||||
**evidencia,
|
||||
}
|
||||
|
||||
if not controlar:
|
||||
return 100, {"controlar": False, "status_lido": st_lido, "id_lido": id_lido}
|
||||
if id_sp < 0 and st_sp < 0:
|
||||
return 94, {"contrato_comando_completo": False, "status_lido": st_lido, "id_lido": id_lido}
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
return 100, debug
|
||||
|
||||
if not id_sp_presente and not status_sp_presente:
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
debug["contrato_comando_completo"] = False
|
||||
return 94, debug
|
||||
|
||||
token_desejado = (
|
||||
id_sp if id_sp_presente else None,
|
||||
st_sp if status_sp_presente else None,
|
||||
)
|
||||
self._registrar_desejado(runtime, token_desejado)
|
||||
|
||||
# Se o SP existe, uma leitura ausente (-1) também é divergência.
|
||||
coerente_id = (
|
||||
not id_sp_presente or
|
||||
(id_lido >= 0 and id_lido == id_sp)
|
||||
)
|
||||
coerente_status = (
|
||||
not status_sp_presente or
|
||||
(st_lido >= 0 and st_lido == st_sp)
|
||||
)
|
||||
coerente = coerente_id and coerente_status
|
||||
debug["divergente"] = not coerente
|
||||
|
||||
coerente = True
|
||||
if id_sp >= 0 and id_lido >= 0 and id_sp != id_lido:
|
||||
coerente = False
|
||||
if st_sp >= 0 and st_lido >= 0 and st_sp != st_lido:
|
||||
coerente = False
|
||||
if coerente:
|
||||
return 100, {"status_lido": st_lido, "status_sp": st_sp, "id_lido": id_lido, "id_sp": id_sp}
|
||||
if ultimo_ms < self.COMANDO_GRACE_MS:
|
||||
return 96, {"status_lido": st_lido, "status_sp": st_sp, "id_lido": id_lido, "id_sp": id_sp, "ultimo_comando_ms": ultimo_ms}
|
||||
runtime["divergencia_desde"] = None
|
||||
|
||||
motivos.append(f"Sinaleiro {label} diferente do comportamento desejado")
|
||||
if runtime["nivel_divergencia"] is None:
|
||||
runtime["coerencia_desde"] = None
|
||||
return 100, debug
|
||||
|
||||
agora = time.monotonic()
|
||||
if runtime["coerencia_desde"] is None:
|
||||
runtime["coerencia_desde"] = agora
|
||||
|
||||
tempo_coerente_ms = (
|
||||
agora - runtime["coerencia_desde"]
|
||||
) * 1000.0
|
||||
debug["recuperacao_em_confirmacao"] = True
|
||||
debug["tempo_coerente_ms"] = tempo_coerente_ms
|
||||
|
||||
if tempo_coerente_ms >= self.COMANDO_RECUPERACAO_MS:
|
||||
runtime["nivel_divergencia"] = None
|
||||
runtime["coerencia_desde"] = None
|
||||
return 100, debug
|
||||
|
||||
motivos.append(
|
||||
f"Sinaleiro {label} recuperou o estado; confirmando estabilidade"
|
||||
)
|
||||
return 88, debug
|
||||
|
||||
runtime["coerencia_desde"] = None
|
||||
|
||||
if not evidencia["possui_comando"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||
return 96, debug
|
||||
|
||||
if not evidencia["telemetria_pos_comando"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
||||
return 96, debug
|
||||
|
||||
if evidencia["ultimo_comando_ms"] < self.COMANDO_GRACE_MS:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||
return 96, debug
|
||||
|
||||
agora = time.monotonic()
|
||||
tempo_retorno_ms = (
|
||||
agora - runtime["componente_recuperado_em"]
|
||||
) * 1000.0
|
||||
if tempo_retorno_ms < self.COMPONENTE_RECUPERACAO_GRACE_MS:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "estabilização após reconexão"
|
||||
debug["tempo_desde_retorno_componente_ms"] = tempo_retorno_ms
|
||||
return 96, debug
|
||||
|
||||
if runtime["divergencia_desde"] is None:
|
||||
runtime["divergencia_desde"] = agora
|
||||
|
||||
tempo_divergente_ms = (
|
||||
agora - runtime["divergencia_desde"]
|
||||
) * 1000.0
|
||||
debug["tempo_divergente_ms"] = tempo_divergente_ms
|
||||
|
||||
if tempo_divergente_ms < self.COMANDO_ALERTA_MS:
|
||||
return 96, debug
|
||||
|
||||
runtime["nivel_divergencia"] = "alerta"
|
||||
motivos.append(
|
||||
f"Sinaleiro {label} diferente do comportamento desejado"
|
||||
)
|
||||
condicoes.append({
|
||||
"label": "Sinaleiro SEN",
|
||||
"valor": st_lido,
|
||||
"severidade": 45,
|
||||
"severidade": 55,
|
||||
"classe": "processo",
|
||||
"descricao": f"Sinaleiro {label} não está refletindo o comportamento desejado",
|
||||
"acoes": ["Conferir LED/sinaleiro físico e comando pelo SEN."],
|
||||
"descricao": (
|
||||
f"Sinaleiro {label} não está refletindo o comportamento desejado"
|
||||
),
|
||||
"acoes": [
|
||||
"Conferir LED/sinaleiro físico.",
|
||||
"Conferir comando e retorno publicados pelo SEN.",
|
||||
],
|
||||
})
|
||||
return 82, {"status_lido": st_lido, "status_sp": st_sp, "id_lido": id_lido, "id_sp": id_sp, "ultimo_comando_ms": ultimo_ms}
|
||||
return 78, debug
|
||||
|
||||
# ============================================================
|
||||
# Processo geral
|
||||
|
|
@ -1013,30 +1503,6 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
score = min(score, 60)
|
||||
energia_critica = True
|
||||
|
||||
temp_motor_max = max(ctx["temperaturas_motor"]) if ctx["temperaturas_motor"] else 0
|
||||
if temp_motor_max >= 90:
|
||||
motivos.append(f"Temperatura máxima dos motores crítica ({temp_motor_max:.1f} °C)")
|
||||
condicoes.append({
|
||||
"label": "Temperatura motores",
|
||||
"valor": temp_motor_max,
|
||||
"severidade": 95,
|
||||
"classe": "falha_hardware",
|
||||
"descricao": f"Temperatura de motor crítica ({temp_motor_max:.1f} °C)",
|
||||
"acoes": ["Reduzir carga/parar movimentação e verificar refrigeração."],
|
||||
})
|
||||
score = min(score, 45)
|
||||
elif temp_motor_max >= 80:
|
||||
motivos.append(f"Temperatura máxima dos motores elevada ({temp_motor_max:.1f} °C)")
|
||||
condicoes.append({
|
||||
"label": "Temperatura motores",
|
||||
"valor": temp_motor_max,
|
||||
"severidade": 75,
|
||||
"classe": "risco_operacional",
|
||||
"descricao": f"Temperatura de motor elevada ({temp_motor_max:.1f} °C)",
|
||||
"acoes": ["Reduzir esforço se persistir."],
|
||||
})
|
||||
score = min(score, 75)
|
||||
|
||||
if ctx["temp_bateria"] >= 60:
|
||||
motivos.append(f"Temperatura da bateria crítica ({ctx['temp_bateria']:.1f} °C)")
|
||||
condicoes.append({
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ class RegrasTaticas:
|
|||
# Pulverizador automático.
|
||||
"atu_indisponivel": self._nova_persistencia(),
|
||||
"bomba_cavitada": self._nova_persistencia(),
|
||||
|
||||
# MOV / possível atolamento ou bloqueio mecânico.
|
||||
"mov_travamento": self._nova_persistencia(),
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -51,6 +54,13 @@ class RegrasTaticas:
|
|||
motivos.extend(novos_motivos)
|
||||
freio_necessario = freio_necessario or freio
|
||||
|
||||
novos_motivos, freio = self._avaliar_travamento_mov(
|
||||
controle=controle,
|
||||
operacao=operacao
|
||||
)
|
||||
motivos.extend(novos_motivos)
|
||||
freio_necessario = freio_necessario or freio
|
||||
|
||||
novos_motivos, freio = self._avaliar_oak_parada_por_bloqueio(
|
||||
controle=controle,
|
||||
operacao=operacao
|
||||
|
|
@ -126,6 +136,92 @@ class RegrasTaticas:
|
|||
|
||||
return motivos, freio_necessario
|
||||
|
||||
def _avaliar_travamento_mov(self, controle: dict, operacao: dict):
|
||||
movimento_automatico = self._bool(
|
||||
controle.get("movimento_automatico", False)
|
||||
)
|
||||
|
||||
controle_manual = self._bool(
|
||||
controle.get("controle_manual_acionado", False)
|
||||
)
|
||||
|
||||
status_operacao = self._enum_or(
|
||||
StatusOperacao,
|
||||
operacao.get(
|
||||
"status",
|
||||
StatusOperacao.NaoIniciado.value
|
||||
),
|
||||
StatusOperacao.NaoIniciado
|
||||
)
|
||||
|
||||
pausa = self._bool(operacao.get("pausa", False))
|
||||
emergencia = self._bool(operacao.get("emergencia", False))
|
||||
calibrando = self._bool(operacao.get("calibrando", False))
|
||||
finalizando = self._bool(operacao.get("finalizando", False))
|
||||
|
||||
deve_avaliar = (
|
||||
status_operacao == StatusOperacao.EmAndamento
|
||||
and movimento_automatico
|
||||
and not controle_manual
|
||||
and not pausa
|
||||
and not emergencia
|
||||
and not calibrando
|
||||
and not finalizando
|
||||
)
|
||||
|
||||
if not deve_avaliar:
|
||||
self._resetar_persistencia("mov_travamento")
|
||||
return [], False
|
||||
|
||||
mov, _, operante = self._modulo_operante(
|
||||
T_Code.Mov,
|
||||
aceitar_alerta=True
|
||||
)
|
||||
|
||||
if not operante:
|
||||
# Indisponibilidade do módulo não deve ser confundida com atolamento.
|
||||
self._resetar_persistencia("mov_travamento")
|
||||
return [], False
|
||||
|
||||
saude = self._dict(mov.get("saude", {}))
|
||||
detalhes = self._dict(saude.get("detalhes", {}))
|
||||
|
||||
travamento_health = self._bool(
|
||||
detalhes.get("travamento_confirmado", False)
|
||||
)
|
||||
|
||||
# O health já exige aproximadamente 2 segundos e 3 amostras.
|
||||
# Esta persistência curta apenas protege a regra contra troca de
|
||||
# snapshot ou inconsistência momentânea no Redis.
|
||||
travamento_tatico = self._avaliar_persistencia(
|
||||
nome="mov_travamento",
|
||||
estado_bruto=travamento_health,
|
||||
tempo_acionar=0.30,
|
||||
tempo_liberar=2.00
|
||||
)
|
||||
|
||||
if not travamento_tatico:
|
||||
return [], False
|
||||
|
||||
drivers = detalhes.get("drivers_com_travamento", []) or []
|
||||
|
||||
ids = [
|
||||
str(driver.get("label") or driver.get("id"))
|
||||
for driver in drivers
|
||||
if isinstance(driver, dict)
|
||||
]
|
||||
|
||||
complemento = (
|
||||
f" | motores: {', '.join(ids)}"
|
||||
if ids
|
||||
else ""
|
||||
)
|
||||
|
||||
return [
|
||||
"Possível atolamento ou bloqueio mecânico detectado"
|
||||
f"{complemento}. Operação pausada para intervenção do operador."
|
||||
], True
|
||||
|
||||
def _avaliar_oak_parada_por_bloqueio(self, controle: dict, operacao: dict):
|
||||
"""
|
||||
Bloqueia somente se a função de parada por bloqueio estiver ativa.
|
||||
|
|
|
|||
|
|
@ -630,6 +630,90 @@ class ContextoGlobalRedis:
|
|||
"motivos": motivos_controle_runtime,
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 5.1) Solicitação de intertravamento sistêmico
|
||||
# ------------------------------------------------------------
|
||||
|
||||
parada_seguranca = cls._dict(
|
||||
controle.get("parada_seguranca", {})
|
||||
)
|
||||
|
||||
parada_seguranca_ativa = cls._bool(
|
||||
parada_seguranca.get("ativa", False)
|
||||
)
|
||||
|
||||
codigo_parada_seguranca = str(
|
||||
parada_seguranca.get("codigo", "") or ""
|
||||
).strip().upper()
|
||||
|
||||
codigos_que_solicitam_emergencia = {
|
||||
"MOV_TRAVAMENTO",
|
||||
}
|
||||
|
||||
codigo_valido_emergencia = (
|
||||
codigo_parada_seguranca
|
||||
in codigos_que_solicitam_emergencia
|
||||
)
|
||||
|
||||
solicitacao_da_regra = cls._bool(
|
||||
parada_seguranca.get(
|
||||
"solicitar_emergencia",
|
||||
False
|
||||
)
|
||||
)
|
||||
|
||||
emergencia_sistema_solicitada = bool(
|
||||
parada_seguranca_ativa
|
||||
and codigo_valido_emergencia
|
||||
and solicitacao_da_regra
|
||||
)
|
||||
|
||||
motivo_emergencia_sistema = (
|
||||
str(
|
||||
parada_seguranca.get(
|
||||
"motivo",
|
||||
"Intertravamento sistêmico solicitado"
|
||||
)
|
||||
)
|
||||
if emergencia_sistema_solicitada
|
||||
else None
|
||||
)
|
||||
|
||||
origem_emergencia_sistema = (
|
||||
parada_seguranca.get("origem")
|
||||
if emergencia_sistema_solicitada
|
||||
else None
|
||||
)
|
||||
|
||||
severidade_emergencia_sistema = (
|
||||
cls._int(
|
||||
parada_seguranca.get("severidade", 90),
|
||||
90
|
||||
)
|
||||
if emergencia_sistema_solicitada
|
||||
else 0
|
||||
)
|
||||
|
||||
ts_evento_emergencia_sistema = (
|
||||
parada_seguranca.get("ts_evento")
|
||||
if emergencia_sistema_solicitada
|
||||
else None
|
||||
)
|
||||
|
||||
detalhes_regras["intertravamento_sistema"] = {
|
||||
"ativo": emergencia_sistema_solicitada,
|
||||
"codigo": (
|
||||
codigo_parada_seguranca
|
||||
if emergencia_sistema_solicitada
|
||||
else None
|
||||
),
|
||||
"origem": origem_emergencia_sistema,
|
||||
"severidade": severidade_emergencia_sistema,
|
||||
"motivo": motivo_emergencia_sistema,
|
||||
"ts_evento": ts_evento_emergencia_sistema,
|
||||
"drivers": parada_seguranca.get("drivers", []),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 6) Decisão final
|
||||
# ------------------------------------------------------------
|
||||
|
|
@ -667,6 +751,22 @@ class ContextoGlobalRedis:
|
|||
liberado=liberou,
|
||||
motivo_nao_liberado="\n\n".join(motivos) if not liberou else None,
|
||||
motivos_nao_liberado_lista=motivos,
|
||||
|
||||
emergencia_sistema_solicitada=emergencia_sistema_solicitada,
|
||||
emergencia_sistema={
|
||||
"solicitada": emergencia_sistema_solicitada,
|
||||
"codigo": (
|
||||
codigo_parada_seguranca
|
||||
if emergencia_sistema_solicitada
|
||||
else None
|
||||
),
|
||||
"origem": origem_emergencia_sistema,
|
||||
"severidade": severidade_emergencia_sistema,
|
||||
"motivo": motivo_emergencia_sistema,
|
||||
"ts_evento": ts_evento_emergencia_sistema,
|
||||
"drivers": parada_seguranca.get("drivers", []),
|
||||
},
|
||||
|
||||
operacao_liberacao_debug={
|
||||
"ts": time.time(),
|
||||
"liberado": liberou,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import datetime
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
|
@ -24,6 +26,13 @@ from visual_worker.utils import converter_valores_numpy
|
|||
from shared.perf_monitor import VisualPerfMonitor
|
||||
|
||||
|
||||
CAMERA_MANAGER_VERSION = "production_v1_2026_08_24"
|
||||
|
||||
PRODUCT_ASSEMBLY_SCHEMA = "multispec_module_params_assembly_v1"
|
||||
PRODUCT_MODULE_SCHEMA = "multispec_module_params_v3"
|
||||
PHYSICAL_RAW5_CHANNELS = 5
|
||||
|
||||
|
||||
class CameraManager:
|
||||
"""
|
||||
CameraManager v1 do Weed Worker.
|
||||
|
|
@ -61,6 +70,12 @@ class CameraManager:
|
|||
self.weed_detector = None
|
||||
self.seg_config = None
|
||||
|
||||
# Contrato imutável da sessão produto.
|
||||
# Não faz parte do reset operacional porque descreve modelo + MP + hardware.
|
||||
self._pipeline_contract = {}
|
||||
self._module_params_snapshot = {}
|
||||
self._module_params_path = None
|
||||
|
||||
self.operante = False
|
||||
self.iniciando = False
|
||||
self.debug_visual = False
|
||||
|
|
@ -226,6 +241,488 @@ class CameraManager:
|
|||
self._proxima_inicializacao_monotonic = time.monotonic() + atraso
|
||||
return atraso
|
||||
|
||||
@staticmethod
|
||||
def _normalizar_size_wh(value, field_name):
|
||||
if not isinstance(value, (list, tuple)) or len(value) != 2:
|
||||
raise RuntimeError(
|
||||
f"{field_name} deve ser [W,H]; recebido={value!r}"
|
||||
)
|
||||
|
||||
w = int(value[0])
|
||||
h = int(value[1])
|
||||
|
||||
if w <= 0 or h <= 0:
|
||||
raise RuntimeError(
|
||||
f"{field_name} inválido: {value!r}"
|
||||
)
|
||||
|
||||
return [w, h]
|
||||
|
||||
@staticmethod
|
||||
def _path_normalizado(value):
|
||||
if not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
p = Path(str(value))
|
||||
if p.is_file():
|
||||
return str(p.resolve())
|
||||
|
||||
# Mesmo quando ainda não existe, normaliza sem exigir existência.
|
||||
return str(p.expanduser().absolute())
|
||||
except Exception:
|
||||
return str(value)
|
||||
|
||||
@staticmethod
|
||||
def _carregar_json_obrigatorio(path_value, field_name):
|
||||
if not path_value:
|
||||
raise RuntimeError(f"{field_name} não definido.")
|
||||
|
||||
p = Path(str(path_value))
|
||||
if not p.is_file():
|
||||
raise FileNotFoundError(
|
||||
f"{field_name} não encontrado: {p}"
|
||||
)
|
||||
|
||||
with p.open("r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise RuntimeError(
|
||||
f"{field_name} deve conter JSON object/dict."
|
||||
)
|
||||
|
||||
return p.resolve(), data
|
||||
|
||||
def _obter_tamanho_entrada_modelo(self):
|
||||
if self.model_svc is None:
|
||||
raise RuntimeError("Modelo ONNX ainda não inicializado.")
|
||||
|
||||
shape = list(
|
||||
getattr(self.model_svc, "onnx_input_shape", None)
|
||||
or []
|
||||
)
|
||||
|
||||
if len(shape) != 4:
|
||||
raise RuntimeError(
|
||||
f"ONNX input deve ser BCHW 4D; shape={shape}"
|
||||
)
|
||||
|
||||
h = shape[2]
|
||||
w = shape[3]
|
||||
|
||||
if not isinstance(h, (int, np.integer)) or not isinstance(w, (int, np.integer)):
|
||||
raise RuntimeError(
|
||||
"Produto exige resolução ONNX estática em H/W; "
|
||||
f"shape={shape}"
|
||||
)
|
||||
|
||||
if int(w) <= 0 or int(h) <= 0:
|
||||
raise RuntimeError(
|
||||
f"Resolução ONNX inválida: {shape}"
|
||||
)
|
||||
|
||||
return [int(w), int(h)]
|
||||
|
||||
def _validar_module_params_produto(self, seg_config, mx_id):
|
||||
module_path, mp = self._carregar_json_obrigatorio(
|
||||
seg_config.get("module_calibration_json"),
|
||||
"module_calibration_json",
|
||||
)
|
||||
|
||||
require_product = bool(
|
||||
seg_config.get("require_product_contract", True)
|
||||
)
|
||||
|
||||
if mp.get("schema") != PRODUCT_MODULE_SCHEMA:
|
||||
raise RuntimeError(
|
||||
f"module_params schema inválido: {mp.get('schema')!r}. "
|
||||
f"Esperado={PRODUCT_MODULE_SCHEMA!r}"
|
||||
)
|
||||
|
||||
assembly = mp.get("assembly_metadata", {}) or {}
|
||||
assembly_schema = assembly.get("schema")
|
||||
product_contract = assembly_schema == PRODUCT_ASSEMBLY_SCHEMA
|
||||
|
||||
if require_product and not product_contract:
|
||||
raise RuntimeError(
|
||||
"CameraManager produto exige module_params gerado pelo "
|
||||
"module_params_assembler_production.py. "
|
||||
f"assembly_metadata.schema={assembly_schema!r}"
|
||||
)
|
||||
|
||||
fusion = mp.get("fusion_config", {}) or {}
|
||||
mp_target = self._normalizar_size_wh(
|
||||
fusion.get("target_size"),
|
||||
"module_params.fusion_config.target_size",
|
||||
)
|
||||
|
||||
sizes_raw = mp.get("sensor_size_by_role", {}) or {}
|
||||
hardware = mp.get("camera_hardware", {}) or {}
|
||||
|
||||
sizes = {}
|
||||
for role in ("rgb", "re", "nir"):
|
||||
size = sizes_raw.get(role)
|
||||
|
||||
if size is None:
|
||||
hw_role = hardware.get(role, {}) or {}
|
||||
size = hw_role.get("size")
|
||||
|
||||
if require_product and size is None:
|
||||
raise RuntimeError(
|
||||
f"module_params sem sensor_size_by_role.{role}"
|
||||
)
|
||||
|
||||
if size is not None:
|
||||
sizes[role] = self._normalizar_size_wh(
|
||||
size,
|
||||
f"module_params.sensor_size_by_role.{role}",
|
||||
)
|
||||
|
||||
if require_product and set(sizes) != {"rgb", "re", "nir"}:
|
||||
raise RuntimeError(
|
||||
f"module_params incompleto em sensor_size_by_role: {sizes}"
|
||||
)
|
||||
|
||||
provenance = mp.get("calibration_provenance", {}) or {}
|
||||
calibrated_mx = provenance.get("device_mx_id")
|
||||
|
||||
if calibrated_mx and str(calibrated_mx) != str(mx_id):
|
||||
raise RuntimeError(
|
||||
"MX ID solicitado diverge do módulo calibrado: "
|
||||
f"solicitado={mx_id} calibrado={calibrated_mx}"
|
||||
)
|
||||
|
||||
return {
|
||||
"path": str(module_path),
|
||||
"module_params": mp,
|
||||
"product_contract": product_contract,
|
||||
"require_product_contract": require_product,
|
||||
"target_size": mp_target,
|
||||
"sensor_size_by_role": sizes,
|
||||
"camera_hardware": hardware,
|
||||
"calibrated_mx_id": calibrated_mx,
|
||||
}
|
||||
|
||||
def _validar_contrato_pipeline(self, seg_config, mx_id):
|
||||
"""
|
||||
Fecha a autoridade da resolução antes de abrir a OAK:
|
||||
|
||||
ONNX input [W,H]
|
||||
==
|
||||
module_params.fusion_config.target_size
|
||||
==
|
||||
seg_config.ia_resolution
|
||||
|
||||
camera_width/camera_height deixam de participar da decisão física.
|
||||
"""
|
||||
model_target = self._obter_tamanho_entrada_modelo()
|
||||
mp_info = self._validar_module_params_produto(
|
||||
seg_config,
|
||||
mx_id,
|
||||
)
|
||||
|
||||
mp_target = list(mp_info["target_size"])
|
||||
|
||||
if model_target != mp_target:
|
||||
raise RuntimeError(
|
||||
"Contrato de resolução incompatível entre ONNX e module_params: "
|
||||
f"ONNX={model_target} module_params={mp_target}"
|
||||
)
|
||||
|
||||
cfg_target_raw = seg_config.get("ia_resolution")
|
||||
if cfg_target_raw is not None:
|
||||
cfg_target = self._normalizar_size_wh(
|
||||
cfg_target_raw,
|
||||
"seg_config.ia_resolution",
|
||||
)
|
||||
|
||||
if cfg_target != model_target:
|
||||
raise RuntimeError(
|
||||
"Contrato de resolução incompatível entre config, ONNX e MP: "
|
||||
f"config={cfg_target} ONNX={model_target} MP={mp_target}"
|
||||
)
|
||||
else:
|
||||
cfg_target = list(model_target)
|
||||
|
||||
# Canonicaliza o config em memória. A partir daqui quem consulta
|
||||
# ia_resolution recebe o contrato já validado, não um default solto.
|
||||
seg_config["ia_resolution"] = list(model_target)
|
||||
seg_config["module_calibration_json"] = mp_info["path"]
|
||||
|
||||
model_channels = int(
|
||||
getattr(self.model_svc, "channels", 0)
|
||||
or 0
|
||||
)
|
||||
|
||||
input_names = list(
|
||||
getattr(
|
||||
self.model_svc,
|
||||
"input_channel_names",
|
||||
[],
|
||||
)
|
||||
or []
|
||||
)
|
||||
|
||||
if model_channels <= 0 or len(input_names) != model_channels:
|
||||
raise RuntimeError(
|
||||
"Contrato de canais do modelo inválido: "
|
||||
f"C={model_channels} names={input_names}"
|
||||
)
|
||||
|
||||
physical_allowed = {
|
||||
"R", "G", "B", "RE", "NIR", "NDVI", "NDRE",
|
||||
}
|
||||
|
||||
invalid = [
|
||||
str(x)
|
||||
for x in input_names
|
||||
if str(x).upper() not in physical_allowed
|
||||
]
|
||||
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
f"Canais ONNX não suportados pelo pipeline: {invalid}"
|
||||
)
|
||||
|
||||
rgb_native = (
|
||||
list(mp_info["sensor_size_by_role"].get("rgb"))
|
||||
if mp_info["sensor_size_by_role"].get("rgb") is not None
|
||||
else None
|
||||
)
|
||||
|
||||
if mp_info["require_product_contract"] and rgb_native is None:
|
||||
raise RuntimeError(
|
||||
"Produto exige resolução RGB nativa no module_params."
|
||||
)
|
||||
|
||||
model_path = getattr(
|
||||
self.model_svc,
|
||||
"onnx_path",
|
||||
None,
|
||||
)
|
||||
|
||||
contract = {
|
||||
"validated": True,
|
||||
"camera_manager_version": CAMERA_MANAGER_VERSION,
|
||||
"product_contract": bool(mp_info["product_contract"]),
|
||||
"require_product_contract": bool(
|
||||
mp_info["require_product_contract"]
|
||||
),
|
||||
"tensor_size": list(model_target),
|
||||
"onnx_input_shape": list(
|
||||
getattr(
|
||||
self.model_svc,
|
||||
"onnx_input_shape",
|
||||
[],
|
||||
)
|
||||
or []
|
||||
),
|
||||
"onnx_input_channels": input_names,
|
||||
"onnx_model_path": (
|
||||
str(model_path)
|
||||
if model_path is not None
|
||||
else None
|
||||
),
|
||||
"module_params_path": mp_info["path"],
|
||||
"sensor_size_by_role": dict(
|
||||
mp_info["sensor_size_by_role"]
|
||||
),
|
||||
"camera_hardware": dict(
|
||||
mp_info["camera_hardware"]
|
||||
),
|
||||
"rgb_native_size": rgb_native,
|
||||
"calibrated_mx_id": mp_info[
|
||||
"calibrated_mx_id"
|
||||
],
|
||||
}
|
||||
|
||||
self._pipeline_contract = contract
|
||||
self._module_params_snapshot = mp_info[
|
||||
"module_params"
|
||||
]
|
||||
self._module_params_path = mp_info[
|
||||
"path"
|
||||
]
|
||||
|
||||
self.mostrar_log(
|
||||
"[weed][CONTRACT] "
|
||||
f"tensor={contract['tensor_size']} "
|
||||
f"channels={contract['onnx_input_channels']} "
|
||||
f"product={contract['product_contract']} "
|
||||
f"mp={contract['module_params_path']}"
|
||||
)
|
||||
|
||||
return contract
|
||||
|
||||
def _validar_tensor_camera(self, tensor5, contexto="runtime"):
|
||||
if not isinstance(tensor5, np.ndarray):
|
||||
raise RuntimeError(
|
||||
f"[{contexto}] tensor da câmera deve ser np.ndarray; "
|
||||
f"recebido={type(tensor5)}"
|
||||
)
|
||||
|
||||
if tensor5.ndim != 3:
|
||||
raise RuntimeError(
|
||||
f"[{contexto}] Raw5 deve ser CHW 3D; shape={tensor5.shape}"
|
||||
)
|
||||
|
||||
if int(tensor5.shape[0]) != PHYSICAL_RAW5_CHANNELS:
|
||||
raise RuntimeError(
|
||||
f"[{contexto}] câmera deve entregar Raw5 com C=5; "
|
||||
f"shape={tensor5.shape}"
|
||||
)
|
||||
|
||||
expected = (
|
||||
self._pipeline_contract.get(
|
||||
"tensor_size"
|
||||
)
|
||||
or []
|
||||
)
|
||||
|
||||
if len(expected) == 2:
|
||||
expected_w = int(expected[0])
|
||||
expected_h = int(expected[1])
|
||||
|
||||
if (
|
||||
int(tensor5.shape[2]) != expected_w
|
||||
or int(tensor5.shape[1]) != expected_h
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"[{contexto}] tensor H/W incompatível: "
|
||||
f"recebido={tensor5.shape} "
|
||||
f"esperado=(5,{expected_h},{expected_w})"
|
||||
)
|
||||
|
||||
if tensor5.dtype != np.float32:
|
||||
raise RuntimeError(
|
||||
f"[{contexto}] Raw5 produto deve ser float32; "
|
||||
f"dtype={tensor5.dtype}"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _validar_config_dinamica_contrato(self, cfg):
|
||||
"""
|
||||
Somente parâmetros operacionais podem mudar durante a sessão.
|
||||
Modelo, MP, canais e resolução exigem reinicialização completa.
|
||||
"""
|
||||
if not self._pipeline_contract.get("validated", False):
|
||||
return cfg
|
||||
|
||||
expected_size = list(
|
||||
self._pipeline_contract[
|
||||
"tensor_size"
|
||||
]
|
||||
)
|
||||
|
||||
requested_size = cfg.get(
|
||||
"ia_resolution"
|
||||
)
|
||||
|
||||
if requested_size is not None:
|
||||
requested_size = self._normalizar_size_wh(
|
||||
requested_size,
|
||||
"config dinâmica ia_resolution",
|
||||
)
|
||||
|
||||
if requested_size != expected_size:
|
||||
raise RuntimeError(
|
||||
"ia_resolution não pode mudar com a sessão ativa: "
|
||||
f"atual={expected_size} solicitado={requested_size}. "
|
||||
"Reinicie CameraManager com o novo modelo/MP."
|
||||
)
|
||||
|
||||
cfg["ia_resolution"] = list(
|
||||
expected_size
|
||||
)
|
||||
|
||||
expected_mp = self._pipeline_contract.get(
|
||||
"module_params_path"
|
||||
)
|
||||
|
||||
requested_mp = self._path_normalizado(
|
||||
cfg.get(
|
||||
"module_calibration_json"
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
expected_mp
|
||||
and requested_mp
|
||||
and requested_mp != self._path_normalizado(
|
||||
expected_mp
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
"module_calibration_json não pode mudar com a sessão ativa. "
|
||||
"Feche/reinicialize o CameraManager."
|
||||
)
|
||||
|
||||
current_model = self._path_normalizado(
|
||||
self._pipeline_contract.get(
|
||||
"onnx_model_path"
|
||||
)
|
||||
)
|
||||
|
||||
requested_model = self._path_normalizado(
|
||||
cfg.get(
|
||||
"onnx_model_path"
|
||||
)
|
||||
or cfg.get(
|
||||
"ia_model_path"
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
current_model
|
||||
and requested_model
|
||||
and current_model != requested_model
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Modelo ONNX não pode ser trocado dinamicamente na sessão ativa. "
|
||||
"Feche/reinicialize o CameraManager."
|
||||
)
|
||||
|
||||
requested_channels = cfg.get(
|
||||
"input_channels"
|
||||
)
|
||||
|
||||
if isinstance(
|
||||
requested_channels,
|
||||
str,
|
||||
):
|
||||
requested_channels = [
|
||||
x.strip().upper()
|
||||
for x in requested_channels.split(",")
|
||||
if x.strip()
|
||||
]
|
||||
|
||||
elif requested_channels is not None:
|
||||
requested_channels = [
|
||||
str(x).upper()
|
||||
for x in requested_channels
|
||||
]
|
||||
|
||||
current_channels = [
|
||||
str(x).upper()
|
||||
for x in self._pipeline_contract.get(
|
||||
"onnx_input_channels",
|
||||
[]
|
||||
)
|
||||
]
|
||||
|
||||
if (
|
||||
requested_channels is not None
|
||||
and requested_channels != current_channels
|
||||
):
|
||||
raise RuntimeError(
|
||||
"input_channels não pode mudar com ONNX ativo: "
|
||||
f"atual={current_channels} solicitado={requested_channels}"
|
||||
)
|
||||
|
||||
return cfg
|
||||
|
||||
def inicializar(self, mx_id):
|
||||
if mx_id is None:
|
||||
return False
|
||||
|
|
@ -271,7 +768,20 @@ class CameraManager:
|
|||
self.seg_config.get("posproc_intervalo_min_s", 5.0)
|
||||
)
|
||||
|
||||
camera_nova = self._inicializar_camera(mx_id, self.seg_config)
|
||||
# Produto: valida o modelo e o MP ANTES de abrir a OAK.
|
||||
# Assim não ocupamos USB/pipeline para só depois descobrir
|
||||
# que ONNX, module_params e ia_resolution são incompatíveis.
|
||||
self._inicializar_modelo()
|
||||
self._validar_contrato_pipeline(
|
||||
self.seg_config,
|
||||
mx_id,
|
||||
)
|
||||
|
||||
camera_nova = self._inicializar_camera(
|
||||
mx_id,
|
||||
self.seg_config,
|
||||
)
|
||||
|
||||
if camera_nova is None:
|
||||
atraso = self._registrar_falha_inicializacao()
|
||||
self.mostrar_log(
|
||||
|
|
@ -294,7 +804,6 @@ class CameraManager:
|
|||
self.debug_visual = bool(self.seg_config.get("debug_visual", False))
|
||||
self.debug_perf = bool(self.seg_config.get("debug_perf", False))
|
||||
|
||||
self._inicializar_modelo()
|
||||
self._inicializar_detector()
|
||||
|
||||
self.operante = False
|
||||
|
|
@ -374,15 +883,56 @@ class CameraManager:
|
|||
nova = None
|
||||
|
||||
try:
|
||||
contract = self._pipeline_contract or {}
|
||||
|
||||
if not contract.get("validated", False):
|
||||
raise RuntimeError(
|
||||
"Contrato pipeline não validado antes da câmera."
|
||||
)
|
||||
|
||||
target_size = list(
|
||||
contract["tensor_size"]
|
||||
)
|
||||
|
||||
rgb_native = (
|
||||
contract.get(
|
||||
"rgb_native_size"
|
||||
)
|
||||
or [1280, 800]
|
||||
)
|
||||
|
||||
# camera_width/camera_height do config deixam de ser autoridade.
|
||||
# Estes valores são apenas fallback de compatibilidade do construtor
|
||||
# e vêm do MP homologado.
|
||||
fallback_w = int(
|
||||
rgb_native[0]
|
||||
)
|
||||
fallback_h = int(
|
||||
rgb_native[1]
|
||||
)
|
||||
|
||||
nova = CameraMultispectral(
|
||||
self.mostrar_log,
|
||||
mx_id=mx_id,
|
||||
module_calibration_json=seg_config.get("module_calibration_json"),
|
||||
width=int(seg_config.get("camera_width", 1280)),
|
||||
height=int(seg_config.get("camera_height", 800)),
|
||||
# Default conservador para campo. Se o JSON definir 40, ele será respeitado.
|
||||
fps=int(seg_config.get("camera_fps", 20)),
|
||||
target_size=seg_config.get("ia_resolution", [1024, 640]),
|
||||
module_calibration_json=contract[
|
||||
"module_params_path"
|
||||
],
|
||||
width=fallback_w,
|
||||
height=fallback_h,
|
||||
# FPS continua sendo política operacional.
|
||||
fps=int(
|
||||
seg_config.get(
|
||||
"camera_fps",
|
||||
20,
|
||||
)
|
||||
),
|
||||
target_size=target_size,
|
||||
require_product_contract=bool(
|
||||
contract.get(
|
||||
"require_product_contract",
|
||||
True,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
if not nova.iniciado:
|
||||
|
|
@ -392,6 +942,62 @@ class CameraManager:
|
|||
pass
|
||||
return None
|
||||
|
||||
if bool(
|
||||
contract.get(
|
||||
"require_product_contract",
|
||||
True,
|
||||
)
|
||||
):
|
||||
if not bool(
|
||||
getattr(
|
||||
nova,
|
||||
"product_contract",
|
||||
False,
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
"CameraMultispectral iniciou sem contrato produto."
|
||||
)
|
||||
|
||||
camera_tensor_size = list(
|
||||
getattr(
|
||||
nova,
|
||||
"tensor_size",
|
||||
[],
|
||||
)
|
||||
or []
|
||||
)
|
||||
|
||||
if camera_tensor_size != target_size:
|
||||
raise RuntimeError(
|
||||
"CameraMultispectral divergiu do tamanho final: "
|
||||
f"camera={camera_tensor_size} "
|
||||
f"contrato={target_size}"
|
||||
)
|
||||
|
||||
camera_sizes = dict(
|
||||
getattr(
|
||||
nova,
|
||||
"sensor_size_by_role",
|
||||
{},
|
||||
)
|
||||
or {}
|
||||
)
|
||||
|
||||
expected_sizes = dict(
|
||||
contract.get(
|
||||
"sensor_size_by_role",
|
||||
{},
|
||||
)
|
||||
or {}
|
||||
)
|
||||
|
||||
if camera_sizes != expected_sizes:
|
||||
raise RuntimeError(
|
||||
"CameraMultispectral divergiu do hardware homologado: "
|
||||
f"camera={camera_sizes} esperado={expected_sizes}"
|
||||
)
|
||||
|
||||
return nova
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -400,12 +1006,51 @@ class CameraManager:
|
|||
nova.parar()
|
||||
except Exception:
|
||||
pass
|
||||
self.mostrar_log(f"⚠️ Camera com ID {mx_id} não conectada: {e}")
|
||||
|
||||
self.mostrar_log(
|
||||
f"⚠️ Camera com ID {mx_id} não conectada: {e}"
|
||||
)
|
||||
return None
|
||||
|
||||
def _inicializar_modelo(self):
|
||||
requested = (
|
||||
self.seg_config.get("onnx_model_path")
|
||||
or self.seg_config.get("ia_model_path")
|
||||
or self.seg_config.get("model_path")
|
||||
or self.seg_config.get("onnx_path")
|
||||
)
|
||||
|
||||
if self.model_svc is not None:
|
||||
return
|
||||
current = getattr(
|
||||
self.model_svc,
|
||||
"onnx_path",
|
||||
None,
|
||||
)
|
||||
|
||||
current_norm = self._path_normalizado(
|
||||
current
|
||||
)
|
||||
|
||||
requested_norm = self._path_normalizado(
|
||||
requested
|
||||
)
|
||||
|
||||
if (
|
||||
current_norm
|
||||
and requested_norm
|
||||
and current_norm != requested_norm
|
||||
):
|
||||
self.mostrar_log(
|
||||
"[weed][MODEL] modelo mudou entre sessões; "
|
||||
f"recriando runtime | antigo={current_norm} "
|
||||
f"novo={requested_norm}"
|
||||
)
|
||||
|
||||
# Libera a referência. O ORT/TRT anterior será coletado
|
||||
# quando não houver mais referências ao serviço.
|
||||
self.model_svc = None
|
||||
else:
|
||||
return
|
||||
|
||||
self.model_svc = MultiSpecSegformerService(
|
||||
model_config=self.seg_config,
|
||||
|
|
@ -414,7 +1059,8 @@ class CameraManager:
|
|||
|
||||
self.mostrar_log(
|
||||
f"[weed][MODEL] backend={getattr(self.model_svc, 'runtime_backend', 'onnx')} "
|
||||
f"runtime_mode={getattr(self.model_svc, 'runtime_mode', None)}"
|
||||
f"runtime_mode={getattr(self.model_svc, 'runtime_mode', None)} "
|
||||
f"input_shape={getattr(self.model_svc, 'onnx_input_shape', None)}"
|
||||
)
|
||||
|
||||
def _inicializar_detector(self):
|
||||
|
|
@ -689,6 +1335,11 @@ class CameraManager:
|
|||
time.sleep(0.05)
|
||||
continue
|
||||
|
||||
self._validar_tensor_camera(
|
||||
tensor5,
|
||||
contexto="warmup",
|
||||
)
|
||||
|
||||
frame_id = int(
|
||||
res.get("frame_id")
|
||||
or res.get("async_packet_seq")
|
||||
|
|
@ -910,6 +1561,7 @@ class CameraManager:
|
|||
from weed_worker.config import load_seg_config
|
||||
|
||||
cfg = load_seg_config()
|
||||
cfg = self._validar_config_dinamica_contrato(cfg)
|
||||
self.seg_config = cfg
|
||||
self.qtd_bicos = int(cfg.get("qtd_bicos", self.qtd_bicos or 7) or 7)
|
||||
self.posproc_intervalo_min_s = float(cfg.get("posproc_intervalo_min_s", self.posproc_intervalo_min_s))
|
||||
|
|
@ -974,7 +1626,9 @@ class CameraManager:
|
|||
|
||||
motivos = []
|
||||
|
||||
if self.camera is None:
|
||||
if not self._pipeline_contract.get("validated", False):
|
||||
motivos.append("contrato ONNX/module_params não validado")
|
||||
elif self.camera is None:
|
||||
motivos.append("câmera indisponível")
|
||||
elif self.iniciando:
|
||||
motivos.append("câmera inicializando")
|
||||
|
|
@ -1010,6 +1664,23 @@ class CameraManager:
|
|||
"timeout_tensor_s": timeout_tensor,
|
||||
"timeout_inferencia_s": timeout_inferencia,
|
||||
"timeout_deteccao_s": timeout_deteccao,
|
||||
"contract": {
|
||||
"validated": bool(
|
||||
self._pipeline_contract.get("validated", False)
|
||||
),
|
||||
"product_contract": bool(
|
||||
self._pipeline_contract.get("product_contract", False)
|
||||
),
|
||||
"tensor_size": list(
|
||||
self._pipeline_contract.get("tensor_size", []) or []
|
||||
),
|
||||
"onnx_input_channels": list(
|
||||
self._pipeline_contract.get("onnx_input_channels", []) or []
|
||||
),
|
||||
"module_params_path": self._pipeline_contract.get(
|
||||
"module_params_path"
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
self.pipeline_ia_ok = ok
|
||||
|
|
@ -1017,6 +1688,20 @@ class CameraManager:
|
|||
|
||||
return resultado
|
||||
|
||||
def get_pipeline_contract(self):
|
||||
"""Snapshot somente-leitura do contrato ONNX + MP da sessão atual."""
|
||||
try:
|
||||
return json.loads(
|
||||
json.dumps(
|
||||
self._pipeline_contract,
|
||||
default=str,
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return dict(
|
||||
self._pipeline_contract
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# Caches
|
||||
# ============================================================
|
||||
|
|
@ -1415,6 +2100,11 @@ class CameraManager:
|
|||
time.sleep(0.50 if reiniciou else 0.05)
|
||||
continue
|
||||
|
||||
self._validar_tensor_camera(
|
||||
tensor5,
|
||||
contexto="capture",
|
||||
)
|
||||
|
||||
if not self._sessao_camera_valida(camera_atual, generation):
|
||||
self.perf.inc("tensor_descartado_geracao")
|
||||
continue
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1 @@
|
|||
Coloque seu labelmap.txt real ao lado do EXE.
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"group_root": "C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\dist\\MaskReviewer\\group",
|
||||
"labelmap": "C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\dist\\MaskReviewer\\labelmap.txt"
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
datas = []
|
||||
binaries = []
|
||||
hiddenimports = []
|
||||
tmp_ret = collect_all('cv2')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['MaskReviewer.py'],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='MaskReviewer',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='MaskReviewer',
|
||||
)
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
MASKREVIEWER - PACOTE WINDOWS
|
||||
================================
|
||||
|
||||
OBJETIVO
|
||||
--------
|
||||
Revisar, comparar e eventualmente editar máscaras já separadas pelo auditor.
|
||||
Não carrega modelo, PyTorch, CUDA ou checkpoint.
|
||||
|
||||
PACOTE PARA O ANOTADOR
|
||||
----------------------
|
||||
A estrutura recomendada é:
|
||||
|
||||
MaskReviewer\
|
||||
MaskReviewer.exe
|
||||
_internal\
|
||||
labelmap.txt
|
||||
group\
|
||||
chao\
|
||||
previews\
|
||||
masks\
|
||||
predictions\
|
||||
final_masks\
|
||||
review_order.csv
|
||||
chao_cana\
|
||||
chao_cana_erva\
|
||||
chao_erva\
|
||||
...
|
||||
|
||||
Ao abrir MaskReviewer.exe:
|
||||
|
||||
1. Se "group" estiver ao lado do EXE, ele usa automaticamente.
|
||||
2. Se "labelmap.txt" estiver ao lado do EXE, ele usa automaticamente.
|
||||
3. Se algum deles não existir, abre o seletor do Windows.
|
||||
4. Os caminhos escolhidos ficam lembrados em MaskReviewer.settings.json.
|
||||
5. O estado do lote fica em group\review_choices.csv.
|
||||
6. As escolhas finais ficam em cada:
|
||||
group\<GRUPO>\final_masks\
|
||||
|
||||
NOVO LOTE
|
||||
---------
|
||||
Para mandar uma nova revisão à mesma pessoa:
|
||||
|
||||
1. Ela mantém:
|
||||
MaskReviewer.exe
|
||||
_internal\
|
||||
labelmap.txt
|
||||
|
||||
2. Substitui a pasta:
|
||||
group\
|
||||
|
||||
3. Abre o mesmo EXE.
|
||||
|
||||
Como review_choices.csv fica DENTRO de group\, a sessão nova começa limpa junto
|
||||
com a nova pasta group.
|
||||
|
||||
CONTROLES
|
||||
---------
|
||||
Painel 2x2:
|
||||
1 = máscara humana
|
||||
2 = máscara do modelo
|
||||
3 = REMAP
|
||||
E = editar
|
||||
A/D = anterior/próxima
|
||||
S = pular
|
||||
X = apagar decisão
|
||||
Q/ESC = sair
|
||||
|
||||
A barra "Overlay %" controla a opacidade dos dois overlays ao mesmo tempo.
|
||||
|
||||
Editor:
|
||||
E e depois:
|
||||
1 = humana como base
|
||||
2 = modelo como base
|
||||
3 = começar de chão
|
||||
|
||||
Mouse esquerdo = adiciona ponto
|
||||
Mouse direito = remove último ponto
|
||||
ENTER = preenche polígono
|
||||
C = próxima classe
|
||||
0..9 = ID de classe
|
||||
Z = desfazer
|
||||
R = restaurar base
|
||||
BACKSPACE/DEL = limpar pontos abertos
|
||||
S = salvar
|
||||
ESC/Q = cancelar edição
|
||||
|
||||
COMO GERAR O EXE
|
||||
----------------
|
||||
Execute com duplo clique:
|
||||
|
||||
build_windows.bat
|
||||
|
||||
Recomendado:
|
||||
Windows 10/11
|
||||
Python 3.11 ou 3.12 instalado SOMENTE na máquina de build
|
||||
|
||||
O script cria:
|
||||
dist\MaskReviewer\MaskReviewer.exe
|
||||
|
||||
Depois do build, a máquina do anotador NÃO precisa ter Python.
|
||||
|
||||
POR QUE --ONEDIR?
|
||||
-----------------
|
||||
O build usa PyInstaller --onedir porque OpenCV/Numpy ficam mais confiáveis,
|
||||
iniciam mais rápido e são mais fáceis de diagnosticar do que um EXE --onefile.
|
||||
|
||||
IMPORTANTE
|
||||
----------
|
||||
O MaskReviewer nunca altera o dataset bruto.
|
||||
Ele só escreve final_masks e review_choices.csv dentro do lote enviado.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,77 @@
|
|||
('C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\MaskReviewer.exe',
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-windowed.ico',
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
|
||||
b'"urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo x'
|
||||
b'mlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requested'
|
||||
b'Privileges>\n <requestedExecutionLevel level="asInvoker" uiAccess='
|
||||
b'"false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n '
|
||||
b'<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\n <'
|
||||
b'application>\n <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f'
|
||||
b'0}"/>\n <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>\n '
|
||||
b' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>\n <s'
|
||||
b'upportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>\n <supporte'
|
||||
b'dOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>\n </application>\n <'
|
||||
b'/compatibility>\n <application xmlns="urn:schemas-microsoft-com:asm.v3">'
|
||||
b'\n <windowsSettings>\n <longPathAware xmlns="http://schemas.micros'
|
||||
b'oft.com/SMI/2016/WindowsSettings">true</longPathAware>\n </windowsSett'
|
||||
b'ings>\n </application>\n <dependency>\n <dependentAssembly>\n <ass'
|
||||
b'emblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version='
|
||||
b'"6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" langua'
|
||||
b'ge="*"/>\n </dependentAssembly>\n </dependency>\n</assembly>',
|
||||
True,
|
||||
False,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\MaskReviewer.pkg',
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth__tkinter',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pkgutil',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_multiprocessing',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'PYSOURCE'),
|
||||
('MaskReviewer',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\MaskReviewer.py',
|
||||
'PYSOURCE')],
|
||||
[],
|
||||
False,
|
||||
False,
|
||||
1787944409,
|
||||
[('runw.exe',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\runw.exe',
|
||||
'EXECUTABLE')],
|
||||
'C:\\Python312\\python312.dll')
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,55 @@
|
|||
('C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\MaskReviewer.pkg',
|
||||
{'BINARY': True,
|
||||
'DATA': True,
|
||||
'EXECUTABLE': True,
|
||||
'EXTENSION': True,
|
||||
'PYMODULE': True,
|
||||
'PYSOURCE': True,
|
||||
'PYZ': False,
|
||||
'SPLASH': True,
|
||||
'SYMLINK': False},
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth__tkinter',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_pkgutil',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_multiprocessing',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||
'PYSOURCE'),
|
||||
('MaskReviewer',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\MaskReviewer.py',
|
||||
'PYSOURCE')],
|
||||
'python312.dll',
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,815 @@
|
|||
('C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\build\\MaskReviewer\\PYZ-00.pyz',
|
||||
[('__future__', 'C:\\Python312\\Lib\\__future__.py', 'PYMODULE'),
|
||||
('_aix_support', 'C:\\Python312\\Lib\\_aix_support.py', 'PYMODULE'),
|
||||
('_compat_pickle', 'C:\\Python312\\Lib\\_compat_pickle.py', 'PYMODULE'),
|
||||
('_compression', 'C:\\Python312\\Lib\\_compression.py', 'PYMODULE'),
|
||||
('_py_abc', 'C:\\Python312\\Lib\\_py_abc.py', 'PYMODULE'),
|
||||
('_pydatetime', 'C:\\Python312\\Lib\\_pydatetime.py', 'PYMODULE'),
|
||||
('_pydecimal', 'C:\\Python312\\Lib\\_pydecimal.py', 'PYMODULE'),
|
||||
('_strptime', 'C:\\Python312\\Lib\\_strptime.py', 'PYMODULE'),
|
||||
('_threading_local', 'C:\\Python312\\Lib\\_threading_local.py', 'PYMODULE'),
|
||||
('argparse', 'C:\\Python312\\Lib\\argparse.py', 'PYMODULE'),
|
||||
('ast', 'C:\\Python312\\Lib\\ast.py', 'PYMODULE'),
|
||||
('asyncio', 'C:\\Python312\\Lib\\asyncio\\__init__.py', 'PYMODULE'),
|
||||
('asyncio.base_events',
|
||||
'C:\\Python312\\Lib\\asyncio\\base_events.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.base_futures',
|
||||
'C:\\Python312\\Lib\\asyncio\\base_futures.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.base_subprocess',
|
||||
'C:\\Python312\\Lib\\asyncio\\base_subprocess.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.base_tasks',
|
||||
'C:\\Python312\\Lib\\asyncio\\base_tasks.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.constants',
|
||||
'C:\\Python312\\Lib\\asyncio\\constants.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.coroutines',
|
||||
'C:\\Python312\\Lib\\asyncio\\coroutines.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.events', 'C:\\Python312\\Lib\\asyncio\\events.py', 'PYMODULE'),
|
||||
('asyncio.exceptions',
|
||||
'C:\\Python312\\Lib\\asyncio\\exceptions.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.format_helpers',
|
||||
'C:\\Python312\\Lib\\asyncio\\format_helpers.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.futures', 'C:\\Python312\\Lib\\asyncio\\futures.py', 'PYMODULE'),
|
||||
('asyncio.locks', 'C:\\Python312\\Lib\\asyncio\\locks.py', 'PYMODULE'),
|
||||
('asyncio.log', 'C:\\Python312\\Lib\\asyncio\\log.py', 'PYMODULE'),
|
||||
('asyncio.mixins', 'C:\\Python312\\Lib\\asyncio\\mixins.py', 'PYMODULE'),
|
||||
('asyncio.proactor_events',
|
||||
'C:\\Python312\\Lib\\asyncio\\proactor_events.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.protocols',
|
||||
'C:\\Python312\\Lib\\asyncio\\protocols.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.queues', 'C:\\Python312\\Lib\\asyncio\\queues.py', 'PYMODULE'),
|
||||
('asyncio.runners', 'C:\\Python312\\Lib\\asyncio\\runners.py', 'PYMODULE'),
|
||||
('asyncio.selector_events',
|
||||
'C:\\Python312\\Lib\\asyncio\\selector_events.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.sslproto', 'C:\\Python312\\Lib\\asyncio\\sslproto.py', 'PYMODULE'),
|
||||
('asyncio.staggered',
|
||||
'C:\\Python312\\Lib\\asyncio\\staggered.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.streams', 'C:\\Python312\\Lib\\asyncio\\streams.py', 'PYMODULE'),
|
||||
('asyncio.subprocess',
|
||||
'C:\\Python312\\Lib\\asyncio\\subprocess.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.taskgroups',
|
||||
'C:\\Python312\\Lib\\asyncio\\taskgroups.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.tasks', 'C:\\Python312\\Lib\\asyncio\\tasks.py', 'PYMODULE'),
|
||||
('asyncio.threads', 'C:\\Python312\\Lib\\asyncio\\threads.py', 'PYMODULE'),
|
||||
('asyncio.timeouts', 'C:\\Python312\\Lib\\asyncio\\timeouts.py', 'PYMODULE'),
|
||||
('asyncio.transports',
|
||||
'C:\\Python312\\Lib\\asyncio\\transports.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.trsock', 'C:\\Python312\\Lib\\asyncio\\trsock.py', 'PYMODULE'),
|
||||
('asyncio.unix_events',
|
||||
'C:\\Python312\\Lib\\asyncio\\unix_events.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.windows_events',
|
||||
'C:\\Python312\\Lib\\asyncio\\windows_events.py',
|
||||
'PYMODULE'),
|
||||
('asyncio.windows_utils',
|
||||
'C:\\Python312\\Lib\\asyncio\\windows_utils.py',
|
||||
'PYMODULE'),
|
||||
('base64', 'C:\\Python312\\Lib\\base64.py', 'PYMODULE'),
|
||||
('bdb', 'C:\\Python312\\Lib\\bdb.py', 'PYMODULE'),
|
||||
('bisect', 'C:\\Python312\\Lib\\bisect.py', 'PYMODULE'),
|
||||
('bz2', 'C:\\Python312\\Lib\\bz2.py', 'PYMODULE'),
|
||||
('calendar', 'C:\\Python312\\Lib\\calendar.py', 'PYMODULE'),
|
||||
('cmd', 'C:\\Python312\\Lib\\cmd.py', 'PYMODULE'),
|
||||
('code', 'C:\\Python312\\Lib\\code.py', 'PYMODULE'),
|
||||
('codeop', 'C:\\Python312\\Lib\\codeop.py', 'PYMODULE'),
|
||||
('concurrent', 'C:\\Python312\\Lib\\concurrent\\__init__.py', 'PYMODULE'),
|
||||
('concurrent.futures',
|
||||
'C:\\Python312\\Lib\\concurrent\\futures\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('concurrent.futures._base',
|
||||
'C:\\Python312\\Lib\\concurrent\\futures\\_base.py',
|
||||
'PYMODULE'),
|
||||
('concurrent.futures.process',
|
||||
'C:\\Python312\\Lib\\concurrent\\futures\\process.py',
|
||||
'PYMODULE'),
|
||||
('concurrent.futures.thread',
|
||||
'C:\\Python312\\Lib\\concurrent\\futures\\thread.py',
|
||||
'PYMODULE'),
|
||||
('contextlib', 'C:\\Python312\\Lib\\contextlib.py', 'PYMODULE'),
|
||||
('contextvars', 'C:\\Python312\\Lib\\contextvars.py', 'PYMODULE'),
|
||||
('copy', 'C:\\Python312\\Lib\\copy.py', 'PYMODULE'),
|
||||
('csv', 'C:\\Python312\\Lib\\csv.py', 'PYMODULE'),
|
||||
('ctypes', 'C:\\Python312\\Lib\\ctypes\\__init__.py', 'PYMODULE'),
|
||||
('ctypes._endian', 'C:\\Python312\\Lib\\ctypes\\_endian.py', 'PYMODULE'),
|
||||
('dataclasses', 'C:\\Python312\\Lib\\dataclasses.py', 'PYMODULE'),
|
||||
('datetime', 'C:\\Python312\\Lib\\datetime.py', 'PYMODULE'),
|
||||
('decimal', 'C:\\Python312\\Lib\\decimal.py', 'PYMODULE'),
|
||||
('difflib', 'C:\\Python312\\Lib\\difflib.py', 'PYMODULE'),
|
||||
('dis', 'C:\\Python312\\Lib\\dis.py', 'PYMODULE'),
|
||||
('doctest', 'C:\\Python312\\Lib\\doctest.py', 'PYMODULE'),
|
||||
('email', 'C:\\Python312\\Lib\\email\\__init__.py', 'PYMODULE'),
|
||||
('email._encoded_words',
|
||||
'C:\\Python312\\Lib\\email\\_encoded_words.py',
|
||||
'PYMODULE'),
|
||||
('email._header_value_parser',
|
||||
'C:\\Python312\\Lib\\email\\_header_value_parser.py',
|
||||
'PYMODULE'),
|
||||
('email._parseaddr', 'C:\\Python312\\Lib\\email\\_parseaddr.py', 'PYMODULE'),
|
||||
('email._policybase',
|
||||
'C:\\Python312\\Lib\\email\\_policybase.py',
|
||||
'PYMODULE'),
|
||||
('email.base64mime', 'C:\\Python312\\Lib\\email\\base64mime.py', 'PYMODULE'),
|
||||
('email.charset', 'C:\\Python312\\Lib\\email\\charset.py', 'PYMODULE'),
|
||||
('email.contentmanager',
|
||||
'C:\\Python312\\Lib\\email\\contentmanager.py',
|
||||
'PYMODULE'),
|
||||
('email.encoders', 'C:\\Python312\\Lib\\email\\encoders.py', 'PYMODULE'),
|
||||
('email.errors', 'C:\\Python312\\Lib\\email\\errors.py', 'PYMODULE'),
|
||||
('email.feedparser', 'C:\\Python312\\Lib\\email\\feedparser.py', 'PYMODULE'),
|
||||
('email.generator', 'C:\\Python312\\Lib\\email\\generator.py', 'PYMODULE'),
|
||||
('email.header', 'C:\\Python312\\Lib\\email\\header.py', 'PYMODULE'),
|
||||
('email.headerregistry',
|
||||
'C:\\Python312\\Lib\\email\\headerregistry.py',
|
||||
'PYMODULE'),
|
||||
('email.iterators', 'C:\\Python312\\Lib\\email\\iterators.py', 'PYMODULE'),
|
||||
('email.message', 'C:\\Python312\\Lib\\email\\message.py', 'PYMODULE'),
|
||||
('email.parser', 'C:\\Python312\\Lib\\email\\parser.py', 'PYMODULE'),
|
||||
('email.policy', 'C:\\Python312\\Lib\\email\\policy.py', 'PYMODULE'),
|
||||
('email.quoprimime', 'C:\\Python312\\Lib\\email\\quoprimime.py', 'PYMODULE'),
|
||||
('email.utils', 'C:\\Python312\\Lib\\email\\utils.py', 'PYMODULE'),
|
||||
('fileinput', 'C:\\Python312\\Lib\\fileinput.py', 'PYMODULE'),
|
||||
('fnmatch', 'C:\\Python312\\Lib\\fnmatch.py', 'PYMODULE'),
|
||||
('fractions', 'C:\\Python312\\Lib\\fractions.py', 'PYMODULE'),
|
||||
('ftplib', 'C:\\Python312\\Lib\\ftplib.py', 'PYMODULE'),
|
||||
('getopt', 'C:\\Python312\\Lib\\getopt.py', 'PYMODULE'),
|
||||
('getpass', 'C:\\Python312\\Lib\\getpass.py', 'PYMODULE'),
|
||||
('gettext', 'C:\\Python312\\Lib\\gettext.py', 'PYMODULE'),
|
||||
('glob', 'C:\\Python312\\Lib\\glob.py', 'PYMODULE'),
|
||||
('gzip', 'C:\\Python312\\Lib\\gzip.py', 'PYMODULE'),
|
||||
('hashlib', 'C:\\Python312\\Lib\\hashlib.py', 'PYMODULE'),
|
||||
('hmac', 'C:\\Python312\\Lib\\hmac.py', 'PYMODULE'),
|
||||
('html', 'C:\\Python312\\Lib\\html\\__init__.py', 'PYMODULE'),
|
||||
('html.entities', 'C:\\Python312\\Lib\\html\\entities.py', 'PYMODULE'),
|
||||
('http', 'C:\\Python312\\Lib\\http\\__init__.py', 'PYMODULE'),
|
||||
('http.client', 'C:\\Python312\\Lib\\http\\client.py', 'PYMODULE'),
|
||||
('http.cookiejar', 'C:\\Python312\\Lib\\http\\cookiejar.py', 'PYMODULE'),
|
||||
('http.server', 'C:\\Python312\\Lib\\http\\server.py', 'PYMODULE'),
|
||||
('importlib', 'C:\\Python312\\Lib\\importlib\\__init__.py', 'PYMODULE'),
|
||||
('importlib._abc', 'C:\\Python312\\Lib\\importlib\\_abc.py', 'PYMODULE'),
|
||||
('importlib._bootstrap',
|
||||
'C:\\Python312\\Lib\\importlib\\_bootstrap.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap_external',
|
||||
'C:\\Python312\\Lib\\importlib\\_bootstrap_external.py',
|
||||
'PYMODULE'),
|
||||
('importlib.abc', 'C:\\Python312\\Lib\\importlib\\abc.py', 'PYMODULE'),
|
||||
('importlib.machinery',
|
||||
'C:\\Python312\\Lib\\importlib\\machinery.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._adapters',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._collections',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\_collections.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._functools',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\_functools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._itertools',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._meta',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\_meta.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._text',
|
||||
'C:\\Python312\\Lib\\importlib\\metadata\\_text.py',
|
||||
'PYMODULE'),
|
||||
('importlib.readers',
|
||||
'C:\\Python312\\Lib\\importlib\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._adapters',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._common',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\_common.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._itertools',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._legacy',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\_legacy.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.abc',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.readers',
|
||||
'C:\\Python312\\Lib\\importlib\\resources\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.util', 'C:\\Python312\\Lib\\importlib\\util.py', 'PYMODULE'),
|
||||
('inspect', 'C:\\Python312\\Lib\\inspect.py', 'PYMODULE'),
|
||||
('ipaddress', 'C:\\Python312\\Lib\\ipaddress.py', 'PYMODULE'),
|
||||
('json', 'C:\\Python312\\Lib\\json\\__init__.py', 'PYMODULE'),
|
||||
('json.decoder', 'C:\\Python312\\Lib\\json\\decoder.py', 'PYMODULE'),
|
||||
('json.encoder', 'C:\\Python312\\Lib\\json\\encoder.py', 'PYMODULE'),
|
||||
('json.scanner', 'C:\\Python312\\Lib\\json\\scanner.py', 'PYMODULE'),
|
||||
('logging', 'C:\\Python312\\Lib\\logging\\__init__.py', 'PYMODULE'),
|
||||
('lzma', 'C:\\Python312\\Lib\\lzma.py', 'PYMODULE'),
|
||||
('mimetypes', 'C:\\Python312\\Lib\\mimetypes.py', 'PYMODULE'),
|
||||
('multiprocessing',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.connection',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\connection.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.context',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\context.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.dummy',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\dummy\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.dummy.connection',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\dummy\\connection.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.forkserver',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\forkserver.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.heap',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\heap.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.managers',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\managers.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.pool',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\pool.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.popen_fork',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\popen_fork.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.popen_forkserver',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\popen_forkserver.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.popen_spawn_posix',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\popen_spawn_posix.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.popen_spawn_win32',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\popen_spawn_win32.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.process',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\process.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.queues',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\queues.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.reduction',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\reduction.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.resource_sharer',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\resource_sharer.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.resource_tracker',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\resource_tracker.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.shared_memory',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\shared_memory.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.sharedctypes',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\sharedctypes.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.spawn',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\spawn.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.synchronize',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\synchronize.py',
|
||||
'PYMODULE'),
|
||||
('multiprocessing.util',
|
||||
'C:\\Python312\\Lib\\multiprocessing\\util.py',
|
||||
'PYMODULE'),
|
||||
('netrc', 'C:\\Python312\\Lib\\netrc.py', 'PYMODULE'),
|
||||
('nturl2path', 'C:\\Python312\\Lib\\nturl2path.py', 'PYMODULE'),
|
||||
('numbers', 'C:\\Python312\\Lib\\numbers.py', 'PYMODULE'),
|
||||
('numpy',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.__config__',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\__config__.py',
|
||||
'PYMODULE'),
|
||||
('numpy._array_api_info',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_array_api_info.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._add_newdocs',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_add_newdocs.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._add_newdocs_scalars',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_add_newdocs_scalars.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._asarray',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_asarray.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._dtype',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_dtype.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._dtype_ctypes',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_dtype_ctypes.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._exceptions',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_exceptions.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._internal',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_internal.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._methods',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_methods.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._string_helpers',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_string_helpers.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._type_aliases',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_type_aliases.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core._ufunc_config',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\_ufunc_config.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.arrayprint',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\arrayprint.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.defchararray',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\defchararray.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.einsumfunc',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\einsumfunc.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.fromnumeric',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\fromnumeric.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.function_base',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\function_base.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.getlimits',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\getlimits.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.memmap',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\memmap.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.multiarray',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\multiarray.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.numeric',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\numeric.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.numerictypes',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\numerictypes.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.overrides',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\overrides.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.printoptions',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\printoptions.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.records',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\records.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.shape_base',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\shape_base.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.strings',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\strings.py',
|
||||
'PYMODULE'),
|
||||
('numpy._core.umath',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_core\\umath.py',
|
||||
'PYMODULE'),
|
||||
('numpy._distributor_init',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_distributor_init.py',
|
||||
'PYMODULE'),
|
||||
('numpy._expired_attrs_2_0',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_expired_attrs_2_0.py',
|
||||
'PYMODULE'),
|
||||
('numpy._globals',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_globals.py',
|
||||
'PYMODULE'),
|
||||
('numpy._pytesttester',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_pytesttester.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._add_docstring',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_add_docstring.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._array_like',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_array_like.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._char_codes',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_char_codes.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._dtype_like',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_dtype_like.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._nbit',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_nbit.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._nbit_base',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_nbit_base.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._nested_sequence',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_nested_sequence.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._scalars',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_scalars.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._shape',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_shape.py',
|
||||
'PYMODULE'),
|
||||
('numpy._typing._ufunc',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_typing\\_ufunc.py',
|
||||
'PYMODULE'),
|
||||
('numpy._utils',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_utils\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy._utils._conversions',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_utils\\_conversions.py',
|
||||
'PYMODULE'),
|
||||
('numpy._utils._inspect',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\_utils\\_inspect.py',
|
||||
'PYMODULE'),
|
||||
('numpy.char',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\char\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.core',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\core\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.core._utils',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\core\\_utils.py',
|
||||
'PYMODULE'),
|
||||
('numpy.core.multiarray',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\core\\multiarray.py',
|
||||
'PYMODULE'),
|
||||
('numpy.ctypeslib',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\ctypeslib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.ctypeslib._ctypeslib',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\ctypeslib\\_ctypeslib.py',
|
||||
'PYMODULE'),
|
||||
('numpy.dtypes',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\dtypes.py',
|
||||
'PYMODULE'),
|
||||
('numpy.exceptions',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\exceptions.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.__version__',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\__version__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py._backends',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\_backends\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py._backends._backend',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\_backends\\_backend.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py._backends._meson',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\_backends\\_meson.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py._isocbind',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\_isocbind.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.auxfuncs',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\auxfuncs.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.capi_maps',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\capi_maps.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.cb_rules',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\cb_rules.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.cfuncs',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\cfuncs.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.common_rules',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\common_rules.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.crackfortran',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\crackfortran.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.diagnose',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\diagnose.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.f2py2e',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\f2py2e.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.f90mod_rules',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\f90mod_rules.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.func2subr',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\func2subr.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.rules',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\rules.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.symbolic',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\symbolic.py',
|
||||
'PYMODULE'),
|
||||
('numpy.f2py.use_rules',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\f2py\\use_rules.py',
|
||||
'PYMODULE'),
|
||||
('numpy.fft',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\fft\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.fft._helper',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\fft\\_helper.py',
|
||||
'PYMODULE'),
|
||||
('numpy.fft._pocketfft',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\fft\\_pocketfft.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._array_utils_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_array_utils_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._arraypad_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_arraypad_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._arraysetops_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_arraysetops_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._arrayterator_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_arrayterator_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._datasource',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_datasource.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._format_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_format_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._function_base_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_function_base_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._histograms_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_histograms_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._index_tricks_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_index_tricks_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._iotools',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_iotools.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._nanfunctions_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_nanfunctions_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._npyio_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_npyio_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._polynomial_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_polynomial_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._scimath_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_scimath_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._shape_base_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_shape_base_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._stride_tricks_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_stride_tricks_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._twodim_base_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_twodim_base_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._type_check_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_type_check_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._ufunclike_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_ufunclike_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._utils_impl',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_utils_impl.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib._version',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\_version.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.array_utils',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\array_utils.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.format',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\format.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.introspect',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\introspect.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.mixins',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\mixins.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.npyio',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\npyio.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.recfunctions',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\recfunctions.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.scimath',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\scimath.py',
|
||||
'PYMODULE'),
|
||||
('numpy.lib.stride_tricks',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\lib\\stride_tricks.py',
|
||||
'PYMODULE'),
|
||||
('numpy.linalg',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\linalg\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.linalg._linalg',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\linalg\\_linalg.py',
|
||||
'PYMODULE'),
|
||||
('numpy.ma',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\ma\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.ma.core',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\ma\\core.py',
|
||||
'PYMODULE'),
|
||||
('numpy.ma.extras',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\ma\\extras.py',
|
||||
'PYMODULE'),
|
||||
('numpy.ma.mrecords',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\ma\\mrecords.py',
|
||||
'PYMODULE'),
|
||||
('numpy.matlib',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\matlib.py',
|
||||
'PYMODULE'),
|
||||
('numpy.matrixlib',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\matrixlib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.matrixlib.defmatrix',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\matrixlib\\defmatrix.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial._polybase',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\_polybase.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.chebyshev',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\chebyshev.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.hermite',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\hermite.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.hermite_e',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\hermite_e.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.laguerre',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\laguerre.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.legendre',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\legendre.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.polynomial',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\polynomial.py',
|
||||
'PYMODULE'),
|
||||
('numpy.polynomial.polyutils',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\polynomial\\polyutils.py',
|
||||
'PYMODULE'),
|
||||
('numpy.random',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\random\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.random._pickle',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\random\\_pickle.py',
|
||||
'PYMODULE'),
|
||||
('numpy.rec',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\rec\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.strings',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\strings\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.testing',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\testing\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.testing._private',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\testing\\_private\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.testing._private.extbuild',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\testing\\_private\\extbuild.py',
|
||||
'PYMODULE'),
|
||||
('numpy.testing._private.utils',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\testing\\_private\\utils.py',
|
||||
'PYMODULE'),
|
||||
('numpy.testing.overrides',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\testing\\overrides.py',
|
||||
'PYMODULE'),
|
||||
('numpy.typing',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\typing\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('numpy.version',
|
||||
'C:\\ZendionInc\\agrobot_base\\Python\\OAK\\datasets\\oak-fcc-3\\MaskReviewer_package\\.build_venv\\Lib\\site-packages\\numpy\\version.py',
|
||||
'PYMODULE'),
|
||||
('opcode', 'C:\\Python312\\Lib\\opcode.py', 'PYMODULE'),
|
||||
('pathlib', 'C:\\Python312\\Lib\\pathlib.py', 'PYMODULE'),
|
||||
('pdb', 'C:\\Python312\\Lib\\pdb.py', 'PYMODULE'),
|
||||
('pickle', 'C:\\Python312\\Lib\\pickle.py', 'PYMODULE'),
|
||||
('pkgutil', 'C:\\Python312\\Lib\\pkgutil.py', 'PYMODULE'),
|
||||
('platform', 'C:\\Python312\\Lib\\platform.py', 'PYMODULE'),
|
||||
('pprint', 'C:\\Python312\\Lib\\pprint.py', 'PYMODULE'),
|
||||
('py_compile', 'C:\\Python312\\Lib\\py_compile.py', 'PYMODULE'),
|
||||
('pydoc', 'C:\\Python312\\Lib\\pydoc.py', 'PYMODULE'),
|
||||
('pydoc_data', 'C:\\Python312\\Lib\\pydoc_data\\__init__.py', 'PYMODULE'),
|
||||
('pydoc_data.topics',
|
||||
'C:\\Python312\\Lib\\pydoc_data\\topics.py',
|
||||
'PYMODULE'),
|
||||
('queue', 'C:\\Python312\\Lib\\queue.py', 'PYMODULE'),
|
||||
('quopri', 'C:\\Python312\\Lib\\quopri.py', 'PYMODULE'),
|
||||
('random', 'C:\\Python312\\Lib\\random.py', 'PYMODULE'),
|
||||
('runpy', 'C:\\Python312\\Lib\\runpy.py', 'PYMODULE'),
|
||||
('secrets', 'C:\\Python312\\Lib\\secrets.py', 'PYMODULE'),
|
||||
('selectors', 'C:\\Python312\\Lib\\selectors.py', 'PYMODULE'),
|
||||
('shlex', 'C:\\Python312\\Lib\\shlex.py', 'PYMODULE'),
|
||||
('shutil', 'C:\\Python312\\Lib\\shutil.py', 'PYMODULE'),
|
||||
('signal', 'C:\\Python312\\Lib\\signal.py', 'PYMODULE'),
|
||||
('socket', 'C:\\Python312\\Lib\\socket.py', 'PYMODULE'),
|
||||
('socketserver', 'C:\\Python312\\Lib\\socketserver.py', 'PYMODULE'),
|
||||
('ssl', 'C:\\Python312\\Lib\\ssl.py', 'PYMODULE'),
|
||||
('statistics', 'C:\\Python312\\Lib\\statistics.py', 'PYMODULE'),
|
||||
('string', 'C:\\Python312\\Lib\\string.py', 'PYMODULE'),
|
||||
('stringprep', 'C:\\Python312\\Lib\\stringprep.py', 'PYMODULE'),
|
||||
('subprocess', 'C:\\Python312\\Lib\\subprocess.py', 'PYMODULE'),
|
||||
('sysconfig', 'C:\\Python312\\Lib\\sysconfig.py', 'PYMODULE'),
|
||||
('tarfile', 'C:\\Python312\\Lib\\tarfile.py', 'PYMODULE'),
|
||||
('tempfile', 'C:\\Python312\\Lib\\tempfile.py', 'PYMODULE'),
|
||||
('textwrap', 'C:\\Python312\\Lib\\textwrap.py', 'PYMODULE'),
|
||||
('threading', 'C:\\Python312\\Lib\\threading.py', 'PYMODULE'),
|
||||
('tkinter', 'C:\\Python312\\Lib\\tkinter\\__init__.py', 'PYMODULE'),
|
||||
('tkinter.commondialog',
|
||||
'C:\\Python312\\Lib\\tkinter\\commondialog.py',
|
||||
'PYMODULE'),
|
||||
('tkinter.constants',
|
||||
'C:\\Python312\\Lib\\tkinter\\constants.py',
|
||||
'PYMODULE'),
|
||||
('tkinter.dialog', 'C:\\Python312\\Lib\\tkinter\\dialog.py', 'PYMODULE'),
|
||||
('tkinter.filedialog',
|
||||
'C:\\Python312\\Lib\\tkinter\\filedialog.py',
|
||||
'PYMODULE'),
|
||||
('tkinter.messagebox',
|
||||
'C:\\Python312\\Lib\\tkinter\\messagebox.py',
|
||||
'PYMODULE'),
|
||||
('tkinter.simpledialog',
|
||||
'C:\\Python312\\Lib\\tkinter\\simpledialog.py',
|
||||
'PYMODULE'),
|
||||
('token', 'C:\\Python312\\Lib\\token.py', 'PYMODULE'),
|
||||
('tokenize', 'C:\\Python312\\Lib\\tokenize.py', 'PYMODULE'),
|
||||
('tracemalloc', 'C:\\Python312\\Lib\\tracemalloc.py', 'PYMODULE'),
|
||||
('tty', 'C:\\Python312\\Lib\\tty.py', 'PYMODULE'),
|
||||
('typing', 'C:\\Python312\\Lib\\typing.py', 'PYMODULE'),
|
||||
('unittest', 'C:\\Python312\\Lib\\unittest\\__init__.py', 'PYMODULE'),
|
||||
('unittest._log', 'C:\\Python312\\Lib\\unittest\\_log.py', 'PYMODULE'),
|
||||
('unittest.async_case',
|
||||
'C:\\Python312\\Lib\\unittest\\async_case.py',
|
||||
'PYMODULE'),
|
||||
('unittest.case', 'C:\\Python312\\Lib\\unittest\\case.py', 'PYMODULE'),
|
||||
('unittest.loader', 'C:\\Python312\\Lib\\unittest\\loader.py', 'PYMODULE'),
|
||||
('unittest.main', 'C:\\Python312\\Lib\\unittest\\main.py', 'PYMODULE'),
|
||||
('unittest.result', 'C:\\Python312\\Lib\\unittest\\result.py', 'PYMODULE'),
|
||||
('unittest.runner', 'C:\\Python312\\Lib\\unittest\\runner.py', 'PYMODULE'),
|
||||
('unittest.signals', 'C:\\Python312\\Lib\\unittest\\signals.py', 'PYMODULE'),
|
||||
('unittest.suite', 'C:\\Python312\\Lib\\unittest\\suite.py', 'PYMODULE'),
|
||||
('unittest.util', 'C:\\Python312\\Lib\\unittest\\util.py', 'PYMODULE'),
|
||||
('urllib', 'C:\\Python312\\Lib\\urllib\\__init__.py', 'PYMODULE'),
|
||||
('urllib.error', 'C:\\Python312\\Lib\\urllib\\error.py', 'PYMODULE'),
|
||||
('urllib.parse', 'C:\\Python312\\Lib\\urllib\\parse.py', 'PYMODULE'),
|
||||
('urllib.request', 'C:\\Python312\\Lib\\urllib\\request.py', 'PYMODULE'),
|
||||
('urllib.response', 'C:\\Python312\\Lib\\urllib\\response.py', 'PYMODULE'),
|
||||
('webbrowser', 'C:\\Python312\\Lib\\webbrowser.py', 'PYMODULE'),
|
||||
('xml', 'C:\\Python312\\Lib\\xml\\__init__.py', 'PYMODULE'),
|
||||
('xml.parsers', 'C:\\Python312\\Lib\\xml\\parsers\\__init__.py', 'PYMODULE'),
|
||||
('xml.parsers.expat',
|
||||
'C:\\Python312\\Lib\\xml\\parsers\\expat.py',
|
||||
'PYMODULE'),
|
||||
('xml.sax', 'C:\\Python312\\Lib\\xml\\sax\\__init__.py', 'PYMODULE'),
|
||||
('xml.sax._exceptions',
|
||||
'C:\\Python312\\Lib\\xml\\sax\\_exceptions.py',
|
||||
'PYMODULE'),
|
||||
('xml.sax.expatreader',
|
||||
'C:\\Python312\\Lib\\xml\\sax\\expatreader.py',
|
||||
'PYMODULE'),
|
||||
('xml.sax.handler', 'C:\\Python312\\Lib\\xml\\sax\\handler.py', 'PYMODULE'),
|
||||
('xml.sax.saxutils', 'C:\\Python312\\Lib\\xml\\sax\\saxutils.py', 'PYMODULE'),
|
||||
('xml.sax.xmlreader',
|
||||
'C:\\Python312\\Lib\\xml\\sax\\xmlreader.py',
|
||||
'PYMODULE'),
|
||||
('xmlrpc', 'C:\\Python312\\Lib\\xmlrpc\\__init__.py', 'PYMODULE'),
|
||||
('xmlrpc.client', 'C:\\Python312\\Lib\\xmlrpc\\client.py', 'PYMODULE'),
|
||||
('zipfile', 'C:\\Python312\\Lib\\zipfile\\__init__.py', 'PYMODULE'),
|
||||
('zipfile._path',
|
||||
'C:\\Python312\\Lib\\zipfile\\_path\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile._path.glob',
|
||||
'C:\\Python312\\Lib\\zipfile\\_path\\glob.py',
|
||||
'PYMODULE'),
|
||||
('zipimport', 'C:\\Python312\\Lib\\zipimport.py', 'PYMODULE')])
|
||||
Binary file not shown.
|
|
@ -0,0 +1,223 @@
|
|||
|
||||
This file lists modules PyInstaller was not able to find. This does not
|
||||
necessarily mean these modules are required for running your program. Both
|
||||
Python's standard library and 3rd-party Python packages often conditionally
|
||||
import optional modules, some of which may be available only on certain
|
||||
platforms.
|
||||
|
||||
Types of import:
|
||||
* top-level: imported at the top-level - look at these first
|
||||
* conditional: imported within an if-statement
|
||||
* delayed: imported within a function
|
||||
* optional: imported within a try-except-statement
|
||||
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), http.server (delayed, optional), netrc (delayed, conditional), getpass (delayed)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
||||
missing module named fcntl - imported by subprocess (optional)
|
||||
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
||||
missing module named _scproxy - imported by urllib.request (conditional)
|
||||
missing module named termios - imported by tty (top-level), getpass (optional)
|
||||
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
||||
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
||||
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named pyimod02_importers - imported by C:\ZendionInc\agrobot_base\Python\OAK\datasets\oak-fcc-3\MaskReviewer_package\.build_venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
|
||||
missing module named _dummy_thread - imported by numpy._core.arrayprint (optional)
|
||||
missing module named typing_extensions - imported by numpy._typing._nested_sequence (conditional), numpy.random.bit_generator (top-level)
|
||||
missing module named charset_normalizer - imported by numpy.f2py.crackfortran (optional)
|
||||
missing module named vms_lib - imported by platform (delayed, optional)
|
||||
missing module named 'java.lang' - imported by platform (delayed, optional)
|
||||
missing module named java - imported by platform (delayed)
|
||||
missing module named _winreg - imported by platform (delayed, optional)
|
||||
missing module named psutil - imported by numpy.testing._private.utils (delayed, optional)
|
||||
missing module named readline - imported by cmd (delayed, conditional, optional), code (delayed, conditional, optional), pdb (delayed, optional)
|
||||
missing module named win32pdh - imported by numpy.testing._private.utils (delayed, conditional)
|
||||
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
||||
missing module named _typeshed - imported by numpy.random._common (top-level), numpy.random.bit_generator (top-level)
|
||||
missing module named threadpoolctl - imported by numpy.lib._utils_impl (delayed, optional)
|
||||
missing module named numpy._core.zeros - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.void - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.vecmat - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.vecdot - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.ushort - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.unsignedinteger - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.ulonglong - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.ulong - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.uintp - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.uintc - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.uint64 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.uint32 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.uint16 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.uint - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.ubyte - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.trunc - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.true_divide - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.transpose - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._function_base_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.trace - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.timedelta64 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.tensordot - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.tanh - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.tan - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.swapaxes - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.sum - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.subtract - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.str_ - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.square - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.sqrt - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level)
|
||||
missing module named numpy._core.spacing - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.sort - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.sinh - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.single - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.signedinteger - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.signbit - imported by numpy._core (conditional), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.sign - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.short - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.rint - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.right_shift - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.remainder - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.reciprocal - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level)
|
||||
missing module named numpy._core.radians - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.rad2deg - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.prod - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.power - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.positive - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.pi - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.outer - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.ones - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.object_ - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.number - imported by numpy._core (conditional), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.not_equal - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.nextafter - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.newaxis - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.negative - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.ndarray - imported by numpy._core (top-level), numpy.lib._utils_impl (top-level), numpy (conditional), numpy.testing._private.utils (top-level)
|
||||
missing module named numpy._core.multiply - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.moveaxis - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.modf - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.mod - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.minimum - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.maximum - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.matvec - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.matrix_transpose - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.matmul - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.longlong - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.longdouble - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.long - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.logical_xor - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.logical_or - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.logical_not - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.logical_and - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.logaddexp2 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.logaddexp - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.log10 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.log2 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.log1p - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.log - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.linspace - imported by numpy._core (top-level), numpy.lib._index_tricks_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.less_equal - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.less - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.left_shift - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.ldexp - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.lcm - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.isscalar - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.isnan - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.isfinite - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.intp - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy._array_api_info (top-level), numpy.testing._private.utils (top-level)
|
||||
missing module named numpy._core.integer - imported by numpy._core (conditional), numpy (conditional), numpy.fft._helper (top-level)
|
||||
missing module named numpy._core.intc - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.int64 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.int32 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.int16 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.int8 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.inf - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.inexact - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.iinfo - imported by numpy._core (top-level), numpy.lib._twodim_base_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.hypot - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.hstack - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.heaviside - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.half - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.greater_equal - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.greater - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.gcd - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.frompyfunc - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.frexp - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.fmod - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.fmin - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.fmax - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.floor_divide - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.floor - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.floating - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.float_power - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.float32 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level), numpy.testing._private.utils (top-level)
|
||||
missing module named numpy._core.float16 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.finfo - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.fabs - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.expm1 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.exp2 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.exp - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.euler_gamma - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.errstate - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.equal - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.empty_like - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level)
|
||||
missing module named numpy._core.empty - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._helper (top-level), numpy.testing._private.utils (top-level)
|
||||
missing module named numpy._core.e - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.double - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.dot - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.divmod - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.divide - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.diagonal - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.degrees - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.deg2rad - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.datetime64 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.csingle - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.cross - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.count_nonzero - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.cosh - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.cos - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.copysign - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.conjugate - imported by numpy._core (conditional), numpy (conditional), numpy.fft._pocketfft (top-level)
|
||||
missing module named numpy._core.conj - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.complexfloating - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.complex64 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
||||
missing module named numpy._core.clongdouble - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.character - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.ceil - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.cdouble - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.cbrt - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.bytes_ - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.byte - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.bool_ - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.bitwise_xor - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.bitwise_or - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.bitwise_count - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.bitwise_and - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.atleast_3d - imported by numpy._core (top-level), numpy.lib._shape_base_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.atleast_2d - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.atleast_1d - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
||||
missing module named numpy._core.asarray - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._array_utils_impl (top-level), numpy (conditional), numpy.fft._helper (top-level), numpy.fft._pocketfft (top-level)
|
||||
missing module named numpy._core.asanyarray - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.array - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional), numpy.testing._private.utils (top-level)
|
||||
missing module named numpy._core.argsort - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.arctanh - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.arctan2 - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.arctan - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.arcsinh - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.arcsin - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.arccosh - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.arccos - imported by numpy._core (conditional), numpy (conditional)
|
||||
missing module named numpy._core.amin - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.amax - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named numpy._core.all - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.testing._private.utils (delayed)
|
||||
missing module named numpy._core.add - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
||||
missing module named yaml - imported by numpy.__config__ (delayed)
|
||||
missing module named numpy._distributor_init_local - imported by numpy (optional), numpy._distributor_init (optional)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,81 @@
|
|||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo ============================================================
|
||||
echo BUILD MASKREVIEWER WINDOWS
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
where py >nul 2>nul
|
||||
if %errorlevel%==0 (
|
||||
set PY=py
|
||||
) else (
|
||||
set PY=python
|
||||
)
|
||||
|
||||
%PY% --version
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo [ERRO] Python nao encontrado.
|
||||
echo Instale Python 3.11 ou 3.12 para Windows e tente novamente.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist ".build_venv\Scripts\python.exe" (
|
||||
echo [1/4] Criando ambiente de build...
|
||||
%PY% -m venv .build_venv
|
||||
if errorlevel 1 goto :erro
|
||||
)
|
||||
|
||||
call ".build_venv\Scripts\activate.bat"
|
||||
|
||||
echo [2/4] Instalando dependencias de build...
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install pyinstaller numpy opencv-python
|
||||
if errorlevel 1 goto :erro
|
||||
|
||||
echo [3/4] Limpando builds antigos...
|
||||
if exist build rmdir /s /q build
|
||||
if exist dist rmdir /s /q dist
|
||||
if exist MaskReviewer.spec del /q MaskReviewer.spec
|
||||
|
||||
echo [4/4] Gerando EXE...
|
||||
python -m PyInstaller ^
|
||||
--noconfirm ^
|
||||
--clean ^
|
||||
--onedir ^
|
||||
--windowed ^
|
||||
--name MaskReviewer ^
|
||||
--collect-all cv2 ^
|
||||
MaskReviewer.py
|
||||
|
||||
if errorlevel 1 goto :erro
|
||||
|
||||
echo.
|
||||
echo ============================================================
|
||||
echo BUILD CONCLUIDO
|
||||
echo ============================================================
|
||||
echo Pasta gerada:
|
||||
echo %CD%\dist\MaskReviewer
|
||||
echo.
|
||||
echo Coloque ao lado do MaskReviewer.exe:
|
||||
echo labelmap.txt
|
||||
echo group\
|
||||
echo.
|
||||
echo Estrutura final recomendada:
|
||||
echo MaskReviewer\
|
||||
echo MaskReviewer.exe
|
||||
echo _internal\
|
||||
echo labelmap.txt
|
||||
echo group\
|
||||
echo.
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
:erro
|
||||
echo.
|
||||
echo [ERRO] Falha no build.
|
||||
pause
|
||||
exit /b 1
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
where py >nul 2>nul
|
||||
if %errorlevel%==0 (
|
||||
set PY=py
|
||||
) else (
|
||||
set PY=python
|
||||
)
|
||||
|
||||
%PY% -c "import cv2, numpy" >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo Instalando numpy e opencv-python...
|
||||
%PY% -m pip install numpy opencv-python
|
||||
)
|
||||
|
||||
%PY% MaskReviewer.py
|
||||
if errorlevel 1 pause
|
||||
|
|
@ -8,7 +8,8 @@ Exporta o checkpoint PyTorch do SegFormer Multi-Head OAK-FCC-3 para ONNX.
|
|||
|
||||
Exemplo:
|
||||
|
||||
python _10_export_onnx.py --config config.json --checkpoint backup/segformer_b1/2026_08_17/stacked_raw5_ndvi_ndre/best_score.pt --out backup/segformer_b1/2026_08_17/stacked_raw5_ndvi_ndre/best_score.onnx --include-norm --postprocess argmax_fullres
|
||||
python .\_10_export_onnx.py --config config.json --checkpoint backup/segformer_b1/2026_08_17/stacked_raw5_ndvi_ndre/best_score.pt --out backup/segformer_b1/2026_08_17/stacked_raw5_ndvi_ndre/best_score.onnx --include-norm --postprocess argmax_fullres
|
||||
python .\_10_export_onnx.py --config .\config.json --train-script .\_8_train_multihead_v2.py --opset 17 --device cuda --include-norm --postprocess argmax_fullres
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ para o SegFormer OAK-FCC-3 Multi-Head.
|
|||
Exemplo:
|
||||
|
||||
python _11_validate_onnx.py --config config.json --max_samples 20 --device cuda --onnx_provider cuda --torch_no_amp
|
||||
python .\_11_validate_onnx.py --config .\config.json --train-script .\_8_train_multihead_v2.py --max_samples 50 --device cuda --onnx_provider cuda --torch_no_amp
|
||||
|
||||
Para validar o ONNX com saída já redimensionada:
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -25,10 +25,7 @@ Mostra:
|
|||
|
||||
Exemplo:
|
||||
|
||||
python .\\_9_test_infer_multihead.py ^
|
||||
--config config.json ^
|
||||
--split_folder val ^
|
||||
--ckpt backup\segformer_b1\test_multi\stacked_raw5_multihead\best_score.pt
|
||||
python .\\_9_test_multihead.py --config config.json --split_folder val --ckpt backup\segformer_b1\test_multi\stacked_raw5_multihead\best_score.pt
|
||||
|
||||
Controles:
|
||||
D / seta direita : próxima amostra
|
||||
|
|
@ -634,7 +631,20 @@ def collect_samples(root: Path, heads_config: Dict[str, dict], require_masks: bo
|
|||
missing = []
|
||||
|
||||
for head_name, hcfg in heads_config.items():
|
||||
mask_dir = group_dir / str(hcfg.get("mask_dir", "masks"))
|
||||
mask_dir_name = str(hcfg.get("mask_dir", "masks"))
|
||||
is_derived = (
|
||||
bool(hcfg.get("derived", False))
|
||||
or mask_dir_name == "__derived_target__"
|
||||
)
|
||||
|
||||
# Heads derivadas não possuem máscara física.
|
||||
# O GT target será construído posteriormente a partir de:
|
||||
# vegetation == 1 AND cana == 0.
|
||||
if is_derived:
|
||||
masks[head_name] = None
|
||||
continue
|
||||
|
||||
mask_dir = group_dir / mask_dir_name
|
||||
mask_path = mask_dir / f"{base}.npy"
|
||||
|
||||
if mask_path.exists():
|
||||
|
|
@ -2002,14 +2012,25 @@ def main():
|
|||
sample_metrics[head_name] = {"iou": iou, "miou": miou, "acc": acc}
|
||||
metric_lines.append(f"{head_name}: mIoU={miou:.3f} acc={acc:.3f}")
|
||||
|
||||
if gt_target is not None:
|
||||
cm_t = confusion_matrix_np(pred_target, gt_target, 2, ignore_id)
|
||||
if gt_target is not None and pred_target_op is not None:
|
||||
cm_t = confusion_matrix_np(pred_target_op, gt_target, 2, ignore_id)
|
||||
iou_t, miou_t, acc_t = metrics_from_cm(cm_t)
|
||||
sample_metrics["target_op"] = {"iou": iou_t, "miou": miou_t, "acc": acc_t}
|
||||
metric_lines.append(f"target_op: IoU_alvo={iou_t[1]:.3f} acc={acc_t:.3f}")
|
||||
|
||||
sample_metrics["target_op"] = {
|
||||
"iou": iou_t,
|
||||
"miou": miou_t,
|
||||
"acc": acc_t,
|
||||
}
|
||||
|
||||
metric_lines.append(
|
||||
f"target_op: IoU_alvo={iou_t[1]:.3f} acc={acc_t:.3f}"
|
||||
)
|
||||
|
||||
if "target" in sample_metrics:
|
||||
iou_head = sample_metrics["target"]["iou"]
|
||||
metric_lines.append(f"target_head: IoU_alvo={iou_head[1]:.3f}")
|
||||
metric_lines.append(
|
||||
f"target_head: IoU_alvo={iou_head[1]:.3f}"
|
||||
)
|
||||
|
||||
if idx not in visited:
|
||||
for head_name, pred in preds.items():
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,18 +1,18 @@
|
|||
{
|
||||
"camera": "oak-fcc-3",
|
||||
"modelo": "segformer_b1",
|
||||
"model_name": "2026_08_18",
|
||||
"model_name": "2026_08_28_linear_demosaic",
|
||||
"main_class_name": "erva",
|
||||
"es_classes": "",
|
||||
"model_to_use": "geral",
|
||||
"raw_size": [1280, 800],
|
||||
"resolucao": [640, 400],
|
||||
"resolucao": [960, 600],
|
||||
"roi_inicio": 0.0,
|
||||
"roi_tamanho": 1.0,
|
||||
"shaves": 3,
|
||||
"source_channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"channels": 7,
|
||||
"input_channels": ["R", "G", "B", "RE", "NIR", "NDVI", "NDRE"],
|
||||
"channels": 5,
|
||||
"input_channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"derived_channels": {
|
||||
"epsilon": 1e-6,
|
||||
"clip_min": -1.0,
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
},
|
||||
"backbone": "nvidia/mit-b1",
|
||||
"fusion_mode": "stacked",
|
||||
"stats_source_tag": "stacked_raw5_ndvi_ndre",
|
||||
"stats_source_tag": "stacked_raw5",
|
||||
"module_params_json": "calibration/module_params.json",
|
||||
"ckpt_test": "best_score",
|
||||
"multi_head": true,
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
"mask_dir": "masks_vegetation",
|
||||
"classes": {"background": 0, "vegetation": 1},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.20
|
||||
"loss_weight": 0.15
|
||||
},
|
||||
"cana": {
|
||||
"enabled": true,
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
"mask_dir": "masks_cana",
|
||||
"classes": {"not_cana": 0, "cana": 1},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.25
|
||||
"loss_weight": 0.35
|
||||
},
|
||||
"target": {
|
||||
"enabled": true,
|
||||
|
|
@ -59,23 +59,158 @@
|
|||
"mask_dir": "__derived_target__",
|
||||
"classes": {"background": 0, "target": 1},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.35,
|
||||
"loss_weight": 0.30,
|
||||
"derived": true
|
||||
}
|
||||
},
|
||||
"target_distillation": {
|
||||
"enabled": true,
|
||||
"hard_weight": 0.85,
|
||||
"distill_weight": 0.15,
|
||||
"rampup_enabled": true,
|
||||
"start_epoch": 8,
|
||||
"rampup_epochs": 12,
|
||||
"w_sem_erva": 0.45,
|
||||
"w_veg_not_cana": 0.35,
|
||||
"w_veg_suppressed": 0.20,
|
||||
"cana_suppression_power": 1.5,
|
||||
"teacher_min": 0.0,
|
||||
"teacher_max": 1.0,
|
||||
"detach_teacher": true
|
||||
"training_v2": {
|
||||
"model": {
|
||||
"decoder_mode": "shared_light",
|
||||
"spectral_input_init": "zero_extra"
|
||||
},
|
||||
"augmentation": {
|
||||
"enabled": true,
|
||||
"horizontal_flip_p": 0.5,
|
||||
"vertical_flip_p": 0.0,
|
||||
"affine_p": 0.7,
|
||||
"rotate_deg": 5.0,
|
||||
"scale_min": 0.9,
|
||||
"scale_max": 1.1,
|
||||
"translate_frac": 0.04,
|
||||
"crop_p": 0.45,
|
||||
"crop_scale_min": 0.7,
|
||||
"crop_scale_max": 1.0,
|
||||
"crop_focus_target_p": 0.55,
|
||||
"crop_focus_cana_p": 0.25,
|
||||
"global_gain_p": 0.35,
|
||||
"global_gain_min": 0.92,
|
||||
"global_gain_max": 1.08,
|
||||
"band_gain_p": 0.25,
|
||||
"band_gain_min": 0.96,
|
||||
"band_gain_max": 1.04,
|
||||
"rgb_gamma_p": 0.2,
|
||||
"rgb_gamma_min": 0.94,
|
||||
"rgb_gamma_max": 1.06,
|
||||
"noise_p": 0.2,
|
||||
"noise_sigma_min": 0.001,
|
||||
"noise_sigma_max": 0.008,
|
||||
"blur_p": 0.12,
|
||||
"blur_kernel": 3,
|
||||
"sensor_channel_dropout_p": 0.0,
|
||||
"clip_physical": true
|
||||
},
|
||||
"sampler": {
|
||||
"mode": "diverse",
|
||||
"samples_per_epoch": 0,
|
||||
"tiny_target_pct": 0.005,
|
||||
"small_target_pct": 0.02,
|
||||
"medium_target_pct": 0.1
|
||||
},
|
||||
"class_weighting": {
|
||||
"method": "log_inverse",
|
||||
"log_offset": 1.02,
|
||||
"power": 0.5,
|
||||
"min_weight": 0.25,
|
||||
"max_weight": 4.0
|
||||
},
|
||||
"loss": {
|
||||
"dice_reduction": "per_image",
|
||||
"dice_smooth": 1.0,
|
||||
"boundary_weight": 0.0,
|
||||
"ohem_ratio": 0.0,
|
||||
"safety": {
|
||||
"enabled": true,
|
||||
"weight": 0.08,
|
||||
"cana_weight": 1.0,
|
||||
"ground_weight": 0.2
|
||||
}
|
||||
},
|
||||
"optimizer": {
|
||||
"encoder_lr": null,
|
||||
"patch_lr_mult": 2.0,
|
||||
"heads_lr_mult": 5.0,
|
||||
"weight_decay": null,
|
||||
"no_decay_bias": true,
|
||||
"no_decay_norm": true,
|
||||
"betas": [
|
||||
0.9,
|
||||
0.999
|
||||
],
|
||||
"eps": 1e-08
|
||||
},
|
||||
"scheduler": {
|
||||
"mode": "poly",
|
||||
"warmup_ratio": 0.05,
|
||||
"warmup_start_factor": 0.1,
|
||||
"poly_power": 1.0,
|
||||
"min_lr_ratio": 0.02
|
||||
},
|
||||
"optimization": {
|
||||
"grad_clip_norm": 1.0,
|
||||
"matmul_precision": "high",
|
||||
"cudnn_benchmark": true,
|
||||
"persistent_workers": true,
|
||||
"prefetch_factor": 2
|
||||
},
|
||||
"target_distillation": {
|
||||
"enabled": false,
|
||||
"mode": "cross_head",
|
||||
"start_epoch": 8,
|
||||
"rampup_epochs": 12,
|
||||
"hard_weight": 0.75,
|
||||
"distill_weight": 0.25,
|
||||
"teacher_confidence_min": 0.6,
|
||||
"detach_teacher": true,
|
||||
"w_sem_erva": 0.45,
|
||||
"w_veg_not_cana": 0.35,
|
||||
"w_veg_suppressed": 0.2,
|
||||
"cana_suppression_power": 1.5
|
||||
},
|
||||
"metrics": {
|
||||
"target_thresholds": [
|
||||
0.3,
|
||||
0.4,
|
||||
0.5,
|
||||
0.6,
|
||||
0.7,
|
||||
0.8,
|
||||
0.9
|
||||
],
|
||||
"ece_bins": 15,
|
||||
"scenario_metrics": true,
|
||||
"group_metrics": true,
|
||||
"rich_train_metrics": false,
|
||||
"operational_threshold": {
|
||||
"max_cana_spray_rate": 0.02,
|
||||
"max_ground_spray_rate": 0.03,
|
||||
"max_weed_miss_rate": 0.2,
|
||||
"score_weights": {
|
||||
"target_iou": 0.35,
|
||||
"target_f1": 0.2,
|
||||
"cana_safety": 0.25,
|
||||
"ground_safety": 0.1,
|
||||
"weed_recall": 0.1
|
||||
}
|
||||
}
|
||||
},
|
||||
"selection_score": {
|
||||
"target_iou": 0.4,
|
||||
"cana_iou": 0.2,
|
||||
"target_f1": 0.1,
|
||||
"vegetation_miou": 0.1,
|
||||
"semantic_miou": 0.05,
|
||||
"cana_safety": 0.15
|
||||
},
|
||||
"checkpoint": {
|
||||
"early_stop_min_delta": 0.0005,
|
||||
"save_best_safety": true,
|
||||
"save_best_legacy": true,
|
||||
"save_best_operational": true
|
||||
},
|
||||
"data": {
|
||||
"validate_npy_content": true,
|
||||
"skip_corrupt_samples": true,
|
||||
"max_corrupt_fraction": 0.005
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,620 +0,0 @@
|
|||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
import torch
|
||||
except Exception:
|
||||
torch = None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Ajuste de import local
|
||||
# ============================================================
|
||||
|
||||
THIS_FILE = Path(__file__).resolve()
|
||||
|
||||
# Esperado:
|
||||
# .../Python/Scripts/workers/camera_worker/oak_fcc3_core/benchmark_raw_bruto_scientific.py
|
||||
WORKERS_DIR = THIS_FILE.parents[2]
|
||||
|
||||
if str(WORKERS_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(WORKERS_DIR))
|
||||
|
||||
from camera_worker.oak_fcc3_core.oak_fcc3_client import OakFcc3Client
|
||||
|
||||
try:
|
||||
from camera_worker.oak_fcc3_core.segformer_service import MultiSpecSegformerService
|
||||
except Exception:
|
||||
MultiSpecSegformerService = None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Utils
|
||||
# ============================================================
|
||||
|
||||
def now_ms():
|
||||
return time.perf_counter() * 1000.0
|
||||
|
||||
|
||||
def mean(xs):
|
||||
return float(np.mean(xs)) if xs else 0.0
|
||||
|
||||
|
||||
def p95(xs):
|
||||
return float(np.percentile(xs, 95)) if xs else 0.0
|
||||
|
||||
|
||||
def maxv(xs):
|
||||
return float(np.max(xs)) if xs else 0.0
|
||||
|
||||
|
||||
def last_mean(xs, n=30):
|
||||
return float(np.mean(xs[-n:])) if xs else 0.0
|
||||
|
||||
|
||||
def last_max(xs, n=30):
|
||||
return float(np.max(xs[-n:])) if xs else 0.0
|
||||
|
||||
|
||||
def load_json_if_exists(path):
|
||||
if not path:
|
||||
return None
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def parse_float_list(s, expected=5, default=None):
|
||||
if s is None:
|
||||
return default
|
||||
|
||||
vals = [float(x.strip()) for x in str(s).split(",") if x.strip() != ""]
|
||||
|
||||
if len(vals) != expected:
|
||||
raise ValueError(f"Esperado {expected} valores, veio {len(vals)}: {s}")
|
||||
|
||||
return np.array(vals, dtype=np.float32)
|
||||
|
||||
|
||||
def extract_mean_std_from_model_config(cfg):
|
||||
if not isinstance(cfg, dict):
|
||||
return None, None
|
||||
|
||||
mean_cfg = cfg.get("mean") or cfg.get("channel_mean") or cfg.get("norm_mean")
|
||||
std_cfg = cfg.get("std") or cfg.get("channel_std") or cfg.get("norm_std")
|
||||
|
||||
norm = cfg.get("normalization") or cfg.get("norm") or {}
|
||||
|
||||
if mean_cfg is None and isinstance(norm, dict):
|
||||
mean_cfg = norm.get("mean") or norm.get("channel_mean")
|
||||
|
||||
if std_cfg is None and isinstance(norm, dict):
|
||||
std_cfg = norm.get("std") or norm.get("channel_std")
|
||||
|
||||
if mean_cfg is None or std_cfg is None:
|
||||
return None, None
|
||||
|
||||
mean_arr = np.array(mean_cfg, dtype=np.float32)
|
||||
std_arr = np.array(std_cfg, dtype=np.float32)
|
||||
|
||||
if mean_arr.size != 5 or std_arr.size != 5:
|
||||
return None, None
|
||||
|
||||
return mean_arr, std_arr
|
||||
|
||||
|
||||
def tensor_stats(tensor):
|
||||
names = ["R", "G", "B", "RE", "NIR"]
|
||||
out = {}
|
||||
|
||||
for i, name in enumerate(names):
|
||||
ch = tensor[i].astype(np.float32)
|
||||
out[name] = {
|
||||
"min": float(np.min(ch)),
|
||||
"p01": float(np.percentile(ch, 1)),
|
||||
"p50": float(np.percentile(ch, 50)),
|
||||
"p99": float(np.percentile(ch, 99)),
|
||||
"max": float(np.max(ch)),
|
||||
"mean": float(np.mean(ch)),
|
||||
"std": float(np.std(ch)),
|
||||
}
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def print_tensor_stats(label, tensor):
|
||||
print("============================================")
|
||||
print(f"[{label}] TENSOR")
|
||||
print(f"shape={tensor.shape} dtype={tensor.dtype}")
|
||||
|
||||
stats = tensor_stats(tensor)
|
||||
for ch, s in stats.items():
|
||||
print(
|
||||
f"{ch:>3} | "
|
||||
f"min={s['min']:.4f} "
|
||||
f"p01={s['p01']:.4f} "
|
||||
f"p50={s['p50']:.4f} "
|
||||
f"p99={s['p99']:.4f} "
|
||||
f"max={s['max']:.4f} "
|
||||
f"mean={s['mean']:.4f} "
|
||||
f"std={s['std']:.4f}"
|
||||
)
|
||||
|
||||
print("============================================")
|
||||
|
||||
|
||||
def to_u8_01(arr):
|
||||
arr = np.asarray(arr, dtype=np.float32)
|
||||
arr = np.nan_to_num(arr, nan=0.0, posinf=1.0, neginf=0.0)
|
||||
arr = np.clip(arr, 0.0, 1.0)
|
||||
return (arr * 255.0).astype(np.uint8)
|
||||
|
||||
|
||||
def make_panel(tensor):
|
||||
rgb = np.stack([tensor[0], tensor[1], tensor[2]], axis=2)
|
||||
rgb_bgr = cv2.cvtColor(to_u8_01(rgb), cv2.COLOR_RGB2BGR)
|
||||
|
||||
re_bgr = cv2.cvtColor(to_u8_01(tensor[3]), cv2.COLOR_GRAY2BGR)
|
||||
nir_bgr = cv2.cvtColor(to_u8_01(tensor[4]), cv2.COLOR_GRAY2BGR)
|
||||
|
||||
false_rgb = np.stack(
|
||||
[
|
||||
tensor[4], # visual R = NIR
|
||||
tensor[3], # visual G = RE
|
||||
tensor[0], # visual B = R real
|
||||
],
|
||||
axis=2,
|
||||
)
|
||||
false_bgr = cv2.cvtColor(to_u8_01(false_rgb), cv2.COLOR_RGB2BGR)
|
||||
|
||||
def title(img, text):
|
||||
h, w = img.shape[:2]
|
||||
bar_h = 34
|
||||
bar = np.zeros((bar_h, w, 3), dtype=np.uint8)
|
||||
cv2.putText(
|
||||
bar,
|
||||
text,
|
||||
(10, 24),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
0.7,
|
||||
(255, 255, 255),
|
||||
2,
|
||||
cv2.LINE_AA,
|
||||
)
|
||||
return np.vstack([bar, img])
|
||||
|
||||
rgb_bgr = title(rgb_bgr, "RGB")
|
||||
re_bgr = title(re_bgr, "RE")
|
||||
nir_bgr = title(nir_bgr, "NIR")
|
||||
false_bgr = title(false_bgr, "Falso color NIR/RE/R")
|
||||
|
||||
top = np.hstack([rgb_bgr, re_bgr])
|
||||
bottom = np.hstack([nir_bgr, false_bgr])
|
||||
|
||||
return np.vstack([top, bottom])
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Benchmark processor
|
||||
# ============================================================
|
||||
|
||||
class ScientificBenchmark:
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
self.target_size = (int(args.width), int(args.height))
|
||||
|
||||
self.model_cfg = load_json_if_exists(args.model_config_json)
|
||||
mean_cfg, std_cfg = extract_mean_std_from_model_config(self.model_cfg)
|
||||
|
||||
if args.model_mean is not None:
|
||||
self.mean = parse_float_list(args.model_mean, expected=5)
|
||||
elif mean_cfg is not None:
|
||||
self.mean = mean_cfg
|
||||
else:
|
||||
self.mean = np.array([0.5, 0.5, 0.5, 0.5, 0.5], dtype=np.float32)
|
||||
|
||||
if args.model_std is not None:
|
||||
self.std = parse_float_list(args.model_std, expected=5)
|
||||
elif std_cfg is not None:
|
||||
self.std = std_cfg
|
||||
else:
|
||||
self.std = np.array([0.25, 0.25, 0.25, 0.25, 0.25], dtype=np.float32)
|
||||
|
||||
self.mean_chw = self.mean[:, None, None].astype(np.float32)
|
||||
self.std_chw = self.std[:, None, None].astype(np.float32)
|
||||
|
||||
self.device = None
|
||||
if torch is not None and torch.cuda.is_available():
|
||||
self.device = torch.device("cuda")
|
||||
elif torch is not None:
|
||||
self.device = torch.device("cpu")
|
||||
|
||||
self.model_svc = None
|
||||
|
||||
if args.run_model:
|
||||
if MultiSpecSegformerService is None:
|
||||
raise RuntimeError("MultiSpecSegformerService não pôde ser importado.")
|
||||
|
||||
if not isinstance(self.model_cfg, dict):
|
||||
raise RuntimeError("--run_model requer --model_config_json válido.")
|
||||
|
||||
self.model_svc = MultiSpecSegformerService(
|
||||
model_config=self.model_cfg,
|
||||
mostrar_log=print,
|
||||
)
|
||||
|
||||
dummy = np.zeros((5, args.height, args.width), dtype=np.float32)
|
||||
|
||||
for _ in range(max(0, int(args.warmup_model))):
|
||||
self.model_svc.infer_tensor_fast(dummy, keep_probs=False)
|
||||
|
||||
print(f"[BENCH] Warmup modelo concluído: {args.warmup_model}x")
|
||||
|
||||
def process_once(self, client):
|
||||
"""
|
||||
Mede uma iteração completa do fluxo científico.
|
||||
"""
|
||||
times = {
|
||||
"capture_ms": 0.0,
|
||||
"decode_ms": 0.0,
|
||||
"controller_ms": 0.0,
|
||||
"fuse_total_ms": 0.0,
|
||||
"fuse_dark_ms": 0.0,
|
||||
"fuse_radnorm_ms": 0.0,
|
||||
"fuse_flat_ms": 0.0,
|
||||
"fuse_prepare_ms": 0.0,
|
||||
"fuse_warp_ms": 0.0,
|
||||
"fuse_crop_resize_ms": 0.0,
|
||||
"fuse_concat_ms": 0.0,
|
||||
"model_norm_ms": 0.0,
|
||||
"torch_ms": 0.0,
|
||||
"infer_ms": 0.0,
|
||||
"total_ms": 0.0,
|
||||
"sync_dt_ms": 0.0,
|
||||
}
|
||||
|
||||
t_total0 = now_ms()
|
||||
|
||||
# ========================================================
|
||||
# 1. Captura RAW_BRUTO
|
||||
# ========================================================
|
||||
t0 = now_ms()
|
||||
raw_frame, raw_meta = client.get_next_raw_frame(timeout=self.args.timeout)
|
||||
times["capture_ms"] = now_ms() - t0
|
||||
|
||||
#cp = raw_meta.get("capture_perf", {})
|
||||
#print(
|
||||
# "[CAP_ASYNC] "
|
||||
# f"get_wait={cp.get('async_get_wait_ms',0):.2f}ms "
|
||||
# f"age={cp.get('async_packet_age_ms',0):.2f}ms "
|
||||
# f"seq={cp.get('async_packet_seq')} "
|
||||
# f"thread_wait={cp.get('wait_total_ms',0):.2f}ms "
|
||||
# f"sleep={cp.get('sleep_ms',0):.2f}ms/{cp.get('sleep_count',0)} "
|
||||
# f"drain={cp.get('drain_total_ms',0):.2f}ms "
|
||||
# f"copy={cp.get('drain_frombuffer_copy_ms',0):.2f}ms "
|
||||
# f"status={cp.get('async_status',{})}"
|
||||
#)
|
||||
|
||||
times["sync_dt_ms"] = float(raw_meta.get("sync_dt_ms", 0.0) or 0.0)
|
||||
|
||||
# ========================================================
|
||||
# 2. Decode RAW10 packed -> float científico por câmera
|
||||
# ========================================================
|
||||
t0 = now_ms()
|
||||
decoded = client.decode_stream_cameras(raw_frame, raw_meta)
|
||||
times["decode_ms"] = now_ms() - t0
|
||||
|
||||
# ========================================================
|
||||
# 3. RadiometricController update, se ativo
|
||||
# Isso NÃO é a radiometric_normalization do tensor.
|
||||
# É o controlador de exposição/ganho.
|
||||
# ========================================================
|
||||
t0 = now_ms()
|
||||
client.update_radiometry(decoded, raw_meta)
|
||||
times["controller_ms"] = now_ms() - t0
|
||||
|
||||
# ========================================================
|
||||
# 4. Fusão científica no RawProcessorCore
|
||||
# dark + radnorm + flat + homografia + crop/resize + concat
|
||||
# ========================================================
|
||||
t0 = now_ms()
|
||||
tensor = client.build_infer_tensor_from_decoded(
|
||||
decoded=decoded,
|
||||
meta=raw_meta,
|
||||
channels_expected=5,
|
||||
target_size=self.target_size,
|
||||
)
|
||||
times["fuse_total_ms"] = now_ms() - t0
|
||||
|
||||
# Pega detalhamento interno do core
|
||||
try:
|
||||
perf = (client.core.last_fusion_result or {}).get("perf", {}) or {}
|
||||
times["fuse_dark_ms"] = float(perf.get("dark_ms", 0.0) or 0.0)
|
||||
times["fuse_radnorm_ms"] = float(perf.get("radnorm_ms", 0.0) or 0.0)
|
||||
times["fuse_flat_ms"] = float(perf.get("flat_ms", 0.0) or 0.0)
|
||||
times["fuse_prepare_ms"] = float(perf.get("prepare_ms", 0.0) or 0.0)
|
||||
times["fuse_warp_ms"] = float(perf.get("warp_total_ms", 0.0) or 0.0)
|
||||
times["fuse_crop_resize_ms"] = float(perf.get("crop_resize_ms", 0.0) or 0.0)
|
||||
times["fuse_concat_ms"] = float(perf.get("concat_ms", 0.0) or 0.0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ========================================================
|
||||
# 5. Normalização do modelo, opcional
|
||||
# ========================================================
|
||||
if self.args.simulate_model_norm:
|
||||
t0 = now_ms()
|
||||
tensor = (tensor - self.mean_chw) / np.maximum(self.std_chw, 1e-6)
|
||||
tensor = np.ascontiguousarray(tensor, dtype=np.float32)
|
||||
times["model_norm_ms"] = now_ms() - t0
|
||||
|
||||
# ========================================================
|
||||
# 6. Transferência para torch/cuda, opcional
|
||||
# ========================================================
|
||||
if self.args.to_torch:
|
||||
if torch is None:
|
||||
raise RuntimeError("--to_torch requer torch instalado.")
|
||||
|
||||
t0 = now_ms()
|
||||
x = torch.from_numpy(tensor).unsqueeze(0).to(self.device, non_blocking=True)
|
||||
|
||||
if self.device is not None and self.device.type == "cuda":
|
||||
torch.cuda.synchronize()
|
||||
|
||||
times["torch_ms"] = now_ms() - t0
|
||||
|
||||
# ========================================================
|
||||
# 7. Inferência real, opcional
|
||||
# ========================================================
|
||||
pred = None
|
||||
if self.args.run_model:
|
||||
t0 = now_ms()
|
||||
pred = self.model_svc.infer_tensor_fast(tensor, keep_probs=False)
|
||||
times["infer_ms"] = now_ms() - t0
|
||||
|
||||
times["total_ms"] = now_ms() - t_total0
|
||||
|
||||
return tensor, pred, raw_frame, raw_meta, decoded, times
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Benchmark científico OAK-FCC-3 RAW_BRUTO -> tensor multiespectral final."
|
||||
)
|
||||
|
||||
ap.add_argument(
|
||||
"--module_params",
|
||||
default=r"C:\ZendionInc\agrobot_base\Python\OAK\datasets\oak-fcc-3\calibration\module_params.json",
|
||||
help="Caminho do module_params.json.",
|
||||
)
|
||||
|
||||
ap.add_argument("--width", type=int, default=1024, help="Largura final do tensor.")
|
||||
ap.add_argument("--height", type=int, default=640, help="Altura final do tensor.")
|
||||
ap.add_argument("--fps", type=float, default=30.0)
|
||||
ap.add_argument("--seconds", type=float, default=20.0)
|
||||
ap.add_argument("--timeout", type=float, default=3.0)
|
||||
ap.add_argument("--warmup", type=int, default=5)
|
||||
ap.add_argument("--mx_id", default=None)
|
||||
ap.add_argument("--sync_tolerance_ms", type=float, default=25.0)
|
||||
ap.add_argument("--buffer_size", type=int, default=8)
|
||||
|
||||
ap.add_argument("--simulate_model_norm", action="store_true")
|
||||
ap.add_argument("--model_mean", default=None)
|
||||
ap.add_argument("--model_std", default=None)
|
||||
ap.add_argument("--to_torch", action="store_true")
|
||||
|
||||
ap.add_argument("--run_model", action="store_true")
|
||||
ap.add_argument("--model_config_json", default=None)
|
||||
ap.add_argument("--warmup_model", type=int, default=3)
|
||||
|
||||
ap.add_argument("--save_debug", action="store_true")
|
||||
ap.add_argument("--debug_dir", default="raw_bruto_scientific_benchmark")
|
||||
ap.add_argument("--show", action="store_true")
|
||||
ap.add_argument("--display_scale", type=float, default=0.65)
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
bench = ScientificBenchmark(args)
|
||||
|
||||
client = OakFcc3Client(
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
fps=args.fps,
|
||||
frame_type="RAW_BRUTO",
|
||||
output_dtype="uint8",
|
||||
capture_mode="TRIPLE",
|
||||
raw_policy="require_triple",
|
||||
module_calibration_json=args.module_params,
|
||||
sync_tolerance_ms=args.sync_tolerance_ms,
|
||||
buffer_size=args.buffer_size,
|
||||
mx_id=args.mx_id,
|
||||
)
|
||||
|
||||
client.core.warmup_numba_raw10_decode()
|
||||
|
||||
samples = {
|
||||
"capture_ms": [],
|
||||
"decode_ms": [],
|
||||
"controller_ms": [],
|
||||
"fuse_total_ms": [],
|
||||
"fuse_dark_ms": [],
|
||||
"fuse_radnorm_ms": [],
|
||||
"fuse_flat_ms": [],
|
||||
"fuse_prepare_ms": [],
|
||||
"fuse_warp_ms": [],
|
||||
"fuse_crop_resize_ms": [],
|
||||
"fuse_concat_ms": [],
|
||||
"model_norm_ms": [],
|
||||
"torch_ms": [],
|
||||
"infer_ms": [],
|
||||
"total_ms": [],
|
||||
"sync_dt_ms": [],
|
||||
}
|
||||
|
||||
n_frames = 0
|
||||
t_start = time.perf_counter()
|
||||
t_last_log = t_start
|
||||
|
||||
last_tensor = None
|
||||
last_meta = None
|
||||
|
||||
try:
|
||||
client.start(print_debug=True)
|
||||
|
||||
# Warmup de câmera/controlador/filas
|
||||
print(f"[BENCH] Warmup frames: {args.warmup}")
|
||||
for _ in range(max(0, int(args.warmup))):
|
||||
try:
|
||||
bench.process_once(client)
|
||||
except Exception as e:
|
||||
print(f"[WARN] warmup falhou: {type(e).__name__}: {e}")
|
||||
time.sleep(0.02)
|
||||
|
||||
print("============================================")
|
||||
print("[BENCH] Iniciando benchmark científico RAW_BRUTO")
|
||||
print(f"target tensor : (5,{args.height},{args.width})")
|
||||
print(f"duration : {args.seconds}s")
|
||||
print("============================================")
|
||||
|
||||
while True:
|
||||
now = time.perf_counter()
|
||||
elapsed = now - t_start
|
||||
|
||||
if elapsed >= args.seconds:
|
||||
break
|
||||
|
||||
try:
|
||||
tensor, pred, raw_frame, raw_meta, decoded, times = bench.process_once(client)
|
||||
except TimeoutError as e:
|
||||
print(f"[TIMEOUT] {e}")
|
||||
continue
|
||||
|
||||
n_frames += 1
|
||||
last_tensor = tensor
|
||||
last_meta = raw_meta
|
||||
|
||||
for k in samples:
|
||||
samples[k].append(float(times.get(k, 0.0) or 0.0))
|
||||
|
||||
if now - t_last_log >= 1.0:
|
||||
elapsed = now - t_start
|
||||
fps = n_frames / max(elapsed, 1e-6)
|
||||
|
||||
print(
|
||||
"[RAW_SCI_PERF] "
|
||||
f"elapsed={elapsed:.1f}s "
|
||||
f"frames={n_frames} "
|
||||
f"fps={fps:.2f} "
|
||||
f"sync={last_mean(samples['sync_dt_ms']):.2f}ms "
|
||||
f"capture={last_mean(samples['capture_ms']):.2f}ms "
|
||||
f"decode={last_mean(samples['decode_ms']):.2f}ms "
|
||||
f"controller={last_mean(samples['controller_ms']):.2f}ms "
|
||||
f"fuse={last_mean(samples['fuse_total_ms']):.2f}ms "
|
||||
f"radnorm={last_mean(samples['fuse_radnorm_ms']):.2f}ms "
|
||||
f"flat={last_mean(samples['fuse_flat_ms']):.2f}ms "
|
||||
f"warp={last_mean(samples['fuse_warp_ms']):.2f}ms "
|
||||
f"crop_resize={last_mean(samples['fuse_crop_resize_ms']):.2f}ms "
|
||||
f"concat={last_mean(samples['fuse_concat_ms']):.2f}ms "
|
||||
f"model_norm={last_mean(samples['model_norm_ms']):.2f}ms "
|
||||
f"torch={last_mean(samples['torch_ms']):.2f}ms "
|
||||
f"infer={last_mean(samples['infer_ms']):.2f}ms "
|
||||
f"total={last_mean(samples['total_ms']):.2f}ms "
|
||||
f"tensor_shape={tuple(tensor.shape)}"
|
||||
)
|
||||
|
||||
t_last_log = now
|
||||
|
||||
elapsed_total = time.perf_counter() - t_start
|
||||
fps_total = n_frames / max(elapsed_total, 1e-6)
|
||||
|
||||
print("============================================")
|
||||
print("RESULTADO FINAL RAW_BRUTO CIENTÍFICO")
|
||||
print(f"elapsed : {elapsed_total:.2f}s")
|
||||
print(f"frames : {n_frames}")
|
||||
print(f"fps : {fps_total:.2f}")
|
||||
print("--------------------------------------------")
|
||||
|
||||
def print_metric(name):
|
||||
xs = samples[name]
|
||||
print(
|
||||
f"{name:18s} "
|
||||
f"mean={mean(xs):8.2f}ms "
|
||||
f"p95={p95(xs):8.2f}ms "
|
||||
f"max={maxv(xs):8.2f}ms"
|
||||
)
|
||||
|
||||
for name in [
|
||||
"sync_dt_ms",
|
||||
"capture_ms",
|
||||
"decode_ms",
|
||||
"controller_ms",
|
||||
"fuse_total_ms",
|
||||
"fuse_dark_ms",
|
||||
"fuse_radnorm_ms",
|
||||
"fuse_flat_ms",
|
||||
"fuse_prepare_ms",
|
||||
"fuse_warp_ms",
|
||||
"fuse_crop_resize_ms",
|
||||
"fuse_concat_ms",
|
||||
"model_norm_ms",
|
||||
"torch_ms",
|
||||
"infer_ms",
|
||||
"total_ms",
|
||||
]:
|
||||
print_metric(name)
|
||||
|
||||
print("============================================")
|
||||
|
||||
if last_tensor is not None:
|
||||
print_tensor_stats("LAST RAW_BRUTO SCI", last_tensor)
|
||||
|
||||
if args.save_debug:
|
||||
debug_dir = Path(args.debug_dir)
|
||||
debug_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
np.save(str(debug_dir / "last_tensor.npy"), last_tensor)
|
||||
|
||||
stats_path = debug_dir / "last_tensor_stats.json"
|
||||
with open(stats_path, "w", encoding="utf-8") as f:
|
||||
json.dump(tensor_stats(last_tensor), f, indent=2, ensure_ascii=False)
|
||||
|
||||
panel = make_panel(last_tensor)
|
||||
cv2.imwrite(str(debug_dir / "last_tensor_panel.png"), panel)
|
||||
|
||||
if last_meta is not None:
|
||||
with open(debug_dir / "last_meta.json", "w", encoding="utf-8") as f:
|
||||
json.dump(last_meta, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"[SAVE] Debug salvo em: {debug_dir}")
|
||||
|
||||
if args.show:
|
||||
panel = make_panel(last_tensor)
|
||||
|
||||
if args.display_scale and abs(args.display_scale - 1.0) > 1e-6:
|
||||
new_w = max(1, int(panel.shape[1] * args.display_scale))
|
||||
new_h = max(1, int(panel.shape[0] * args.display_scale))
|
||||
panel = cv2.resize(panel, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
||||
|
||||
cv2.imshow("RAW_BRUTO scientific tensor", panel)
|
||||
print("[INFO] Pressione qualquer tecla para fechar.")
|
||||
cv2.waitKey(0)
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
finally:
|
||||
try:
|
||||
client.stop()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,645 +0,0 @@
|
|||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# ============================================================
|
||||
# Ajuste de import local
|
||||
# ============================================================
|
||||
THIS_FILE = Path(__file__).resolve()
|
||||
WORKERS_DIR = THIS_FILE.parents[2]
|
||||
if str(WORKERS_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(WORKERS_DIR))
|
||||
|
||||
from camera_worker.oak_fcc3_core.oak_fcc3_client import OakFcc3Client
|
||||
|
||||
try:
|
||||
from camera_worker.oak_fcc3_core.segformer_service import MultiSpecSegformerService
|
||||
except Exception:
|
||||
MultiSpecSegformerService = None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FPS / utilidades
|
||||
# ============================================================
|
||||
class FpsMeter:
|
||||
def __init__(self, alpha=0.15):
|
||||
self.alpha = float(alpha)
|
||||
self.last_ts = None
|
||||
self.fps = 0.0
|
||||
|
||||
def tick(self):
|
||||
now = time.perf_counter()
|
||||
if self.last_ts is not None:
|
||||
dt = now - self.last_ts
|
||||
if dt > 1e-9:
|
||||
inst = 1.0 / dt
|
||||
self.fps = inst if self.fps <= 0 else (1.0 - self.alpha) * self.fps + self.alpha * inst
|
||||
self.last_ts = now
|
||||
return self.fps
|
||||
|
||||
|
||||
def load_json_if_exists(path):
|
||||
if not path:
|
||||
return None
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def init_model_service(args):
|
||||
"""
|
||||
Mesmo contrato do benchmark científico:
|
||||
--run_model exige --model_config_json
|
||||
MultiSpecSegformerService(model_config=cfg).infer_tensor_fast(tensor, keep_probs=False)
|
||||
"""
|
||||
if not args.run_model:
|
||||
return None
|
||||
|
||||
if MultiSpecSegformerService is None:
|
||||
raise RuntimeError("MultiSpecSegformerService não pôde ser importado.")
|
||||
|
||||
model_cfg = load_json_if_exists(args.model_config_json)
|
||||
if not isinstance(model_cfg, dict):
|
||||
raise RuntimeError("--run_model requer --model_config_json válido.")
|
||||
|
||||
svc = MultiSpecSegformerService(
|
||||
model_config=model_cfg,
|
||||
mostrar_log=print,
|
||||
)
|
||||
|
||||
dummy = np.zeros((5, int(args.height), int(args.width)), dtype=np.float32)
|
||||
for _ in range(max(0, int(args.warmup_model))):
|
||||
svc.infer_tensor_fast(dummy, keep_probs=False)
|
||||
|
||||
print(f"[MODEL] Warmup concluído: {args.warmup_model}x")
|
||||
return svc
|
||||
|
||||
|
||||
def to_u8_01(arr, auto_level=False):
|
||||
arr = np.asarray(arr, dtype=np.float32)
|
||||
arr = np.nan_to_num(arr, nan=0.0, posinf=1.0, neginf=0.0)
|
||||
|
||||
if auto_level:
|
||||
p1 = float(np.percentile(arr, 1))
|
||||
p99 = float(np.percentile(arr, 99))
|
||||
den = max(p99 - p1, 1e-6)
|
||||
arr = (arr - p1) / den
|
||||
|
||||
arr = np.clip(arr, 0.0, 1.0)
|
||||
return (arr * 255.0).astype(np.uint8)
|
||||
|
||||
|
||||
def ensure_bgr(img, auto_level=False):
|
||||
img = np.asarray(img)
|
||||
|
||||
if img.ndim == 2:
|
||||
g = to_u8_01(img, auto_level=auto_level)
|
||||
return cv2.cvtColor(g, cv2.COLOR_GRAY2BGR)
|
||||
|
||||
if img.ndim == 3 and img.shape[2] == 3:
|
||||
u8 = to_u8_01(img, auto_level=auto_level)
|
||||
# decoded/tensor RGB vem em RGB; OpenCV mostra BGR
|
||||
return cv2.cvtColor(u8, cv2.COLOR_RGB2BGR)
|
||||
|
||||
raise RuntimeError(f"Imagem inválida para visualização: shape={img.shape}")
|
||||
|
||||
|
||||
def tensor_rgb_to_bgr(tensor, auto_level=False):
|
||||
rgb = np.stack([tensor[0], tensor[1], tensor[2]], axis=2)
|
||||
return ensure_bgr(rgb, auto_level=auto_level)
|
||||
|
||||
|
||||
def tensor_channel_to_bgr(tensor, idx, auto_level=False):
|
||||
return ensure_bgr(tensor[idx], auto_level=auto_level)
|
||||
|
||||
|
||||
def add_title(img, title, color=(255, 255, 255)):
|
||||
out = img.copy()
|
||||
h, w = out.shape[:2]
|
||||
bar_h = 34
|
||||
bar = np.zeros((bar_h, w, 3), dtype=np.uint8)
|
||||
cv2.putText(bar, str(title), (10, 23), cv2.FONT_HERSHEY_SIMPLEX, 0.62, color, 2, cv2.LINE_AA)
|
||||
return np.vstack([bar, out])
|
||||
|
||||
|
||||
def add_hud(panel, lines):
|
||||
out = panel.copy()
|
||||
x, y = 12, 45
|
||||
for line in lines:
|
||||
cv2.putText(out, line, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.62, (0, 255, 255), 2, cv2.LINE_AA)
|
||||
y += 24
|
||||
return out
|
||||
|
||||
|
||||
def resize_tile(img, tile_w, tile_h):
|
||||
return cv2.resize(img, (int(tile_w), int(tile_h)), interpolation=cv2.INTER_AREA)
|
||||
|
||||
|
||||
def get_cam_by_role(decoded, role):
|
||||
role = str(role).lower()
|
||||
for cam_id, item in decoded.items():
|
||||
r = str(item.get("role") or item.get("meta", {}).get("role") or "").lower()
|
||||
if r == role:
|
||||
return cam_id
|
||||
return None
|
||||
|
||||
|
||||
def decoded_raw_tiles(decoded, tile_w, tile_h, auto_level=False):
|
||||
"""
|
||||
Retorna tiles RAW/decoded para RGB, RE, NIR antes da fusão final.
|
||||
Aqui 'RAW_BRUTO' significa o conteúdo decodificado vindo das câmeras, ainda no espaço nativo.
|
||||
"""
|
||||
tiles = {}
|
||||
|
||||
rgb_id = get_cam_by_role(decoded, "rgb")
|
||||
re_id = get_cam_by_role(decoded, "re")
|
||||
nir_id = get_cam_by_role(decoded, "nir")
|
||||
|
||||
if rgb_id is not None:
|
||||
img = decoded[rgb_id]["image"]
|
||||
tiles["rgb"] = resize_tile(ensure_bgr(img, auto_level=auto_level), tile_w, tile_h)
|
||||
else:
|
||||
tiles["rgb"] = np.zeros((tile_h, tile_w, 3), dtype=np.uint8)
|
||||
|
||||
if re_id is not None:
|
||||
img = decoded[re_id]["image"]
|
||||
tiles["re"] = resize_tile(ensure_bgr(img, auto_level=auto_level), tile_w, tile_h)
|
||||
else:
|
||||
tiles["re"] = np.zeros((tile_h, tile_w, 3), dtype=np.uint8)
|
||||
|
||||
if nir_id is not None:
|
||||
img = decoded[nir_id]["image"]
|
||||
tiles["nir"] = resize_tile(ensure_bgr(img, auto_level=auto_level), tile_w, tile_h)
|
||||
else:
|
||||
tiles["nir"] = np.zeros((tile_h, tile_w, 3), dtype=np.uint8)
|
||||
|
||||
return tiles
|
||||
|
||||
|
||||
def tensor_tiles(tensor, tile_w, tile_h, auto_level=False):
|
||||
return {
|
||||
"rgb": resize_tile(tensor_rgb_to_bgr(tensor, auto_level=auto_level), tile_w, tile_h),
|
||||
"re": resize_tile(tensor_channel_to_bgr(tensor, 3, auto_level=auto_level), tile_w, tile_h),
|
||||
"nir": resize_tile(tensor_channel_to_bgr(tensor, 4, auto_level=auto_level), tile_w, tile_h),
|
||||
}
|
||||
|
||||
|
||||
def colorize_label_map(label_map, num_classes=None):
|
||||
label = np.asarray(label_map)
|
||||
if label.ndim == 3:
|
||||
label = np.argmax(label, axis=0)
|
||||
label = label.astype(np.int32)
|
||||
|
||||
if num_classes is None:
|
||||
num_classes = int(max(1, label.max() + 1))
|
||||
|
||||
# Paleta simples e estável. BGR.
|
||||
palette = np.array([
|
||||
[40, 40, 40],
|
||||
[60, 180, 60],
|
||||
[60, 60, 220],
|
||||
[220, 180, 60],
|
||||
[180, 60, 180],
|
||||
[180, 180, 60],
|
||||
[60, 180, 180],
|
||||
[220, 220, 220],
|
||||
], dtype=np.uint8)
|
||||
|
||||
out = palette[label % len(palette)]
|
||||
return out
|
||||
|
||||
|
||||
def try_extract_prediction_tiles(pred, target_w, target_h):
|
||||
"""
|
||||
Tentativa genérica. Adapte aqui se o benchmark tiver nomes específicos das cabeças.
|
||||
Retorna até 3 tiles BGR: semântica/head0/head1.
|
||||
"""
|
||||
if pred is None:
|
||||
blank = np.zeros((target_h, target_w, 3), dtype=np.uint8)
|
||||
return [blank, blank.copy(), blank.copy()], ["Pred vazio", "Head 1", "Head 2"]
|
||||
|
||||
candidates = []
|
||||
names = []
|
||||
|
||||
if isinstance(pred, dict):
|
||||
# nomes comuns
|
||||
for key in ("mask", "pred_mask", "class_map", "semantic", "semantic_mask", "segmentation"):
|
||||
if key in pred:
|
||||
candidates.append(pred[key])
|
||||
names.append(key)
|
||||
|
||||
heads = pred.get("heads") or pred.get("head_outputs") or pred.get("predictions")
|
||||
if isinstance(heads, dict):
|
||||
for k, v in heads.items():
|
||||
candidates.append(v)
|
||||
names.append(str(k))
|
||||
elif isinstance(heads, (list, tuple)):
|
||||
for i, v in enumerate(heads):
|
||||
candidates.append(v)
|
||||
names.append(f"head_{i}")
|
||||
else:
|
||||
candidates.append(pred)
|
||||
names.append("prediction")
|
||||
|
||||
tiles = []
|
||||
out_names = []
|
||||
|
||||
for name, arr in zip(names, candidates):
|
||||
arr = np.asarray(arr)
|
||||
|
||||
# remove batch se existir
|
||||
if arr.ndim == 4 and arr.shape[0] == 1:
|
||||
arr = arr[0]
|
||||
|
||||
if arr.ndim == 3:
|
||||
# CHW logits/probs ou HWC RGB/probs
|
||||
if arr.shape[0] <= 32:
|
||||
vis = colorize_label_map(np.argmax(arr, axis=0), num_classes=arr.shape[0])
|
||||
elif arr.shape[2] in (1, 3):
|
||||
vis = ensure_bgr(arr[:, :, 0] if arr.shape[2] == 1 else arr, auto_level=True)
|
||||
else:
|
||||
vis = ensure_bgr(np.max(arr, axis=2), auto_level=True)
|
||||
elif arr.ndim == 2:
|
||||
# se parecer label map, colore; se parecer float, cinza auto-level
|
||||
if np.issubdtype(arr.dtype, np.integer) and int(np.max(arr)) <= 64:
|
||||
vis = colorize_label_map(arr)
|
||||
else:
|
||||
vis = ensure_bgr(arr, auto_level=True)
|
||||
elif arr.ndim == 1:
|
||||
# vetor de classe/score: desenha texto
|
||||
vis = np.zeros((target_h, target_w, 3), dtype=np.uint8)
|
||||
txt = np.array2string(arr[:8], precision=2, separator=", ")
|
||||
cv2.putText(vis, txt[:80], (10, target_h // 2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)
|
||||
else:
|
||||
continue
|
||||
|
||||
vis = resize_tile(vis, target_w, target_h)
|
||||
tiles.append(vis)
|
||||
out_names.append(name)
|
||||
|
||||
if len(tiles) >= 3:
|
||||
break
|
||||
|
||||
while len(tiles) < 3:
|
||||
tiles.append(np.zeros((target_h, target_w, 3), dtype=np.uint8))
|
||||
out_names.append(f"pred_{len(tiles)}")
|
||||
|
||||
return tiles[:3], out_names[:3]
|
||||
|
||||
|
||||
def try_run_model(model_svc, tensor):
|
||||
"""
|
||||
Inferência real, igual ao benchmark científico.
|
||||
Mantém esta função isolada para adaptar fácil caso o retorno do modelo mude.
|
||||
"""
|
||||
if model_svc is None:
|
||||
return None, "model_svc_none"
|
||||
|
||||
if hasattr(model_svc, "infer_tensor_fast"):
|
||||
return model_svc.infer_tensor_fast(tensor, keep_probs=False), None
|
||||
|
||||
if hasattr(model_svc, "infer"):
|
||||
return model_svc.infer(tensor), None
|
||||
|
||||
return None, "model_svc_sem_infer"
|
||||
|
||||
|
||||
def build_grid(raw_tiles, final_tiles, pred_tiles=None, pred_names=None, tile_w=420, tile_h=260):
|
||||
rows = []
|
||||
row_defs = [
|
||||
("RGB", "rgb"),
|
||||
("RE", "re"),
|
||||
("NIR", "nir"),
|
||||
]
|
||||
|
||||
for i, (label, key) in enumerate(row_defs):
|
||||
left = add_title(raw_tiles[key], f"RAW_BRUTO decoded {label}")
|
||||
mid = add_title(final_tiles[key], f"Tensor final {label}")
|
||||
|
||||
cells = [left, mid]
|
||||
|
||||
if pred_tiles is not None:
|
||||
name = pred_names[i] if pred_names and i < len(pred_names) else f"Pred {i}"
|
||||
cells.append(add_title(pred_tiles[i], name))
|
||||
|
||||
# iguala altura após título
|
||||
h_min = min(c.shape[0] for c in cells)
|
||||
norm = [cv2.resize(c, (tile_w, h_min), interpolation=cv2.INTER_AREA) if c.shape[1] != tile_w or c.shape[0] != h_min else c for c in cells]
|
||||
rows.append(np.hstack(norm))
|
||||
|
||||
return np.vstack(rows)
|
||||
|
||||
|
||||
def get_core_perf(client):
|
||||
for attr in ("raw_processor", "processor", "core", "raw_processor_core"):
|
||||
obj = getattr(client, attr, None)
|
||||
if obj is not None and getattr(obj, "last_fusion_result", None) is not None:
|
||||
return obj.last_fusion_result.get("perf", {}) or {}
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Estado compartilhado / workers assíncronos
|
||||
# ============================================================
|
||||
class SharedState:
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
self.running = True
|
||||
self.latest_decoded = None
|
||||
self.latest_meta = None
|
||||
self.latest_tensor = None
|
||||
self.latest_perf = {}
|
||||
self.latest_pred = None
|
||||
self.latest_model_warn = None
|
||||
self.latest_model_ms = 0.0
|
||||
self.latest_error = None
|
||||
self.tensor_fps = FpsMeter()
|
||||
self.model_fps = FpsMeter()
|
||||
self.preview_fps = FpsMeter()
|
||||
self.tensor_seq = 0
|
||||
self.model_seq = 0
|
||||
|
||||
def stop(self):
|
||||
with self.lock:
|
||||
self.running = False
|
||||
|
||||
def is_running(self):
|
||||
with self.lock:
|
||||
return bool(self.running)
|
||||
|
||||
|
||||
def tensor_worker(client, state, args):
|
||||
"""
|
||||
Roda no talo: captura RAW_BRUTO, monta tensor final e atualiza cache.
|
||||
Não depende do FPS da janela.
|
||||
"""
|
||||
while state.is_running():
|
||||
try:
|
||||
frame, meta, decoded = client.get_next_decoded(timeout=args.timeout)
|
||||
if not isinstance(frame, dict):
|
||||
raise RuntimeError(f"RAW_BRUTO esperado como dict. Veio {type(frame)}")
|
||||
|
||||
tensor = client.build_infer_tensor_from_decoded(
|
||||
decoded=decoded,
|
||||
meta=meta,
|
||||
channels_expected=5,
|
||||
target_size=(args.width, args.height),
|
||||
)
|
||||
tensor = np.ascontiguousarray(tensor.astype(np.float32, copy=False))
|
||||
perf = get_core_perf(client)
|
||||
fps = state.tensor_fps.tick()
|
||||
|
||||
with state.lock:
|
||||
state.latest_decoded = decoded
|
||||
state.latest_meta = meta
|
||||
state.latest_tensor = tensor
|
||||
state.latest_perf = dict(perf or {})
|
||||
state.tensor_seq += 1
|
||||
state.latest_error = None
|
||||
|
||||
except Exception as e:
|
||||
with state.lock:
|
||||
state.latest_error = f"tensor_worker: {type(e).__name__}: {e}"
|
||||
time.sleep(0.02)
|
||||
|
||||
|
||||
def model_worker(model_svc, state, args):
|
||||
"""
|
||||
Opcional: roda inferência no último tensor disponível.
|
||||
Não bloqueia o worker de tensor nem a janela.
|
||||
"""
|
||||
last_seq = -1
|
||||
|
||||
while state.is_running():
|
||||
with state.lock:
|
||||
tensor = None if state.latest_tensor is None else state.latest_tensor.copy()
|
||||
seq = state.tensor_seq
|
||||
|
||||
if tensor is None or seq == last_seq:
|
||||
time.sleep(0.005)
|
||||
continue
|
||||
|
||||
last_seq = seq
|
||||
|
||||
try:
|
||||
t0_model = time.perf_counter()
|
||||
pred, warn = try_run_model(model_svc, tensor)
|
||||
model_ms = (time.perf_counter() - t0_model) * 1000.0
|
||||
if warn is None:
|
||||
state.model_fps.tick()
|
||||
|
||||
with state.lock:
|
||||
state.latest_pred = pred
|
||||
state.latest_model_warn = warn
|
||||
state.latest_model_ms = float(model_ms)
|
||||
state.model_seq += 1
|
||||
|
||||
except Exception as e:
|
||||
with state.lock:
|
||||
state.latest_model_warn = f"model_worker: {type(e).__name__}: {e}"
|
||||
time.sleep(0.02)
|
||||
|
||||
|
||||
def snapshot_state(state):
|
||||
"""
|
||||
Copia referências do cache para desenhar. A janela só roda na cadência do preview.
|
||||
"""
|
||||
with state.lock:
|
||||
return {
|
||||
"decoded": state.latest_decoded,
|
||||
"meta": state.latest_meta,
|
||||
"tensor": state.latest_tensor,
|
||||
"perf": dict(state.latest_perf or {}),
|
||||
"pred": state.latest_pred,
|
||||
"model_warn": state.latest_model_warn,
|
||||
"model_ms": state.latest_model_ms,
|
||||
"error": state.latest_error,
|
||||
"tensor_fps": state.tensor_fps.fps,
|
||||
"model_fps": state.model_fps.fps,
|
||||
"tensor_seq": state.tensor_seq,
|
||||
"model_seq": state.model_seq,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main loop assíncrono
|
||||
# ============================================================
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Preview assíncrono RAW_BRUTO decoded vs tensor final multispectral.")
|
||||
ap.add_argument("--module_params", default=r"C:\ZendionInc\agrobot_base\Python\OAK\datasets\oak-fcc-3\calibration\module_params.json")
|
||||
ap.add_argument("--width", type=int, default=1024, help="Largura final do tensor.")
|
||||
ap.add_argument("--height", type=int, default=640, help="Altura final do tensor.")
|
||||
ap.add_argument("--fps", type=float, default=40.0, help="FPS alvo da câmera.")
|
||||
ap.add_argument("--preview_fps", type=float, default=5.0, help="FPS da janela OpenCV apenas.")
|
||||
ap.add_argument("--timeout", type=float, default=2.0)
|
||||
ap.add_argument("--warmup", type=int, default=5)
|
||||
ap.add_argument("--mx_id", default=None)
|
||||
ap.add_argument("--display_scale", type=float, default=0.75)
|
||||
ap.add_argument("--tile_w", type=int, default=420)
|
||||
ap.add_argument("--tile_h", type=int, default=260)
|
||||
ap.add_argument("--auto_level_raw", action="store_true")
|
||||
ap.add_argument("--auto_level_tensor", action="store_true")
|
||||
ap.add_argument("--run_model", action="store_true", help="Roda inferência em thread separada usando o último tensor cacheado.")
|
||||
ap.add_argument("--model_config_json", default=None, help="JSON de configuração do modelo SegFormer, igual ao benchmark.")
|
||||
ap.add_argument("--warmup_model", type=int, default=3, help="Número de inferências dummy para aquecer o modelo.")
|
||||
ap.add_argument("--save_last", default=None)
|
||||
args = ap.parse_args()
|
||||
|
||||
client = OakFcc3Client(
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
fps=args.fps,
|
||||
frame_type="RAW_BRUTO",
|
||||
capture_mode="TRIPLE",
|
||||
raw_policy="require_triple",
|
||||
module_calibration_json=args.module_params,
|
||||
mx_id=args.mx_id,
|
||||
)
|
||||
|
||||
model_svc = init_model_service(args) if args.run_model else None
|
||||
|
||||
state = SharedState()
|
||||
last_panel = None
|
||||
last_warn_ts = 0.0
|
||||
last_seq_drawn = -1
|
||||
|
||||
try:
|
||||
client.start(print_debug=True)
|
||||
|
||||
for _ in range(max(0, int(args.warmup))):
|
||||
try:
|
||||
client.get_next_decoded(timeout=args.timeout)
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.03)
|
||||
|
||||
tw = threading.Thread(target=tensor_worker, args=(client, state, args), daemon=True)
|
||||
tw.start()
|
||||
|
||||
mw = None
|
||||
if args.run_model:
|
||||
mw = threading.Thread(target=model_worker, args=(model_svc, state, args), daemon=True)
|
||||
mw.start()
|
||||
|
||||
min_period = 1.0 / max(float(args.preview_fps), 0.1)
|
||||
next_draw_ts = 0.0
|
||||
|
||||
print("[INFO] Preview assíncrono iniciado. Pressione Q ou ESC para sair.")
|
||||
print("[INFO] Tensor FPS = geração real do tensor. Preview FPS = janela. Model FPS = inferência, se habilitada.")
|
||||
|
||||
while True:
|
||||
now = time.perf_counter()
|
||||
if now < next_draw_ts:
|
||||
time.sleep(min(0.005, next_draw_ts - now))
|
||||
key = cv2.waitKey(1) & 0xFF
|
||||
if key in (27, ord('q'), ord('Q')):
|
||||
break
|
||||
continue
|
||||
|
||||
next_draw_ts = now + min_period
|
||||
snap = snapshot_state(state)
|
||||
|
||||
if snap["tensor"] is None or snap["decoded"] is None:
|
||||
blank = np.zeros((360, 900, 3), dtype=np.uint8)
|
||||
cv2.putText(blank, "Aguardando primeiro tensor...", (30, 180), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255,255,255), 2, cv2.LINE_AA)
|
||||
cv2.imshow("OAK RAW_BRUTO vs Tensor Final", blank)
|
||||
key = cv2.waitKey(1) & 0xFF
|
||||
if key in (27, ord('q'), ord('Q')):
|
||||
break
|
||||
continue
|
||||
|
||||
# Desenha só usando o cache. Não captura nem monta tensor aqui.
|
||||
t_draw0 = time.perf_counter()
|
||||
raw_tiles = decoded_raw_tiles(
|
||||
snap["decoded"],
|
||||
tile_w=args.tile_w,
|
||||
tile_h=args.tile_h,
|
||||
auto_level=args.auto_level_raw,
|
||||
)
|
||||
final_tiles = tensor_tiles(
|
||||
snap["tensor"],
|
||||
tile_w=args.tile_w,
|
||||
tile_h=args.tile_h,
|
||||
auto_level=args.auto_level_tensor,
|
||||
)
|
||||
|
||||
pred_tiles = None
|
||||
pred_names = None
|
||||
if args.run_model:
|
||||
pred_tiles, pred_names = try_extract_prediction_tiles(
|
||||
snap["pred"],
|
||||
target_w=args.tile_w,
|
||||
target_h=args.tile_h,
|
||||
)
|
||||
if snap["model_warn"]:
|
||||
tnow = time.time()
|
||||
if tnow - last_warn_ts > 2.0:
|
||||
print(f"[WARN][MODEL] {snap['model_warn']}")
|
||||
last_warn_ts = tnow
|
||||
|
||||
panel = build_grid(
|
||||
raw_tiles=raw_tiles,
|
||||
final_tiles=final_tiles,
|
||||
pred_tiles=pred_tiles,
|
||||
pred_names=pred_names,
|
||||
tile_w=args.tile_w,
|
||||
tile_h=args.tile_h,
|
||||
)
|
||||
|
||||
preview_fps = state.preview_fps.tick()
|
||||
draw_ms = (time.perf_counter() - t_draw0) * 1000.0
|
||||
perf = snap["perf"]
|
||||
meta = snap["meta"] or {}
|
||||
tensor_seq = int(snap["tensor_seq"])
|
||||
dropped_for_preview = max(0, tensor_seq - last_seq_drawn - 1) if last_seq_drawn >= 0 else 0
|
||||
last_seq_drawn = tensor_seq
|
||||
|
||||
hud = [
|
||||
f"Preview FPS: {preview_fps:.1f} | Tensor FPS: {snap['tensor_fps']:.1f} | Model FPS: {snap['model_fps']:.1f} | infer={snap.get('model_ms', 0.0):.1f}ms",
|
||||
f"draw={draw_ms:.1f}ms flat={perf.get('flat_ms', 0):.1f} warp={perf.get('warp_total_ms', 0):.1f} crop={perf.get('crop_resize_ms', 0):.1f} fuse={perf.get('total_ms', 0):.1f}",
|
||||
f"seq={tensor_seq} skipped_preview={dropped_for_preview} frame_type={meta.get('frame_type')} run_model={args.run_model}",
|
||||
]
|
||||
if snap["error"]:
|
||||
hud.append(str(snap["error"])[:120])
|
||||
|
||||
panel = add_hud(panel, hud)
|
||||
last_panel = panel
|
||||
|
||||
disp = panel
|
||||
if args.display_scale and abs(args.display_scale - 1.0) > 1e-6:
|
||||
disp = cv2.resize(
|
||||
disp,
|
||||
(max(1, int(disp.shape[1] * args.display_scale)), max(1, int(disp.shape[0] * args.display_scale))),
|
||||
interpolation=cv2.INTER_AREA,
|
||||
)
|
||||
|
||||
cv2.imshow("OAK RAW_BRUTO vs Tensor Final", disp)
|
||||
key = cv2.waitKey(1) & 0xFF
|
||||
if key in (27, ord('q'), ord('Q')):
|
||||
break
|
||||
|
||||
finally:
|
||||
state.stop()
|
||||
time.sleep(0.05)
|
||||
|
||||
if args.save_last and last_panel is not None:
|
||||
out_path = Path(args.save_last)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cv2.imwrite(str(out_path), last_panel)
|
||||
print(f"[SAVE] {out_path}")
|
||||
|
||||
try:
|
||||
client.stop()
|
||||
except Exception:
|
||||
pass
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -46,6 +46,7 @@ class OakFcc3Client:
|
|||
mx_id=None,
|
||||
imu_modo="rotation_vector",
|
||||
imu_freq_hz=200,
|
||||
evaluate_quality=True,
|
||||
**kwargs,
|
||||
):
|
||||
self.width = width
|
||||
|
|
@ -63,6 +64,16 @@ class OakFcc3Client:
|
|||
self.imu_modo = str(imu_modo).strip().lower()
|
||||
self.imu_freq_hz = int(imu_freq_hz)
|
||||
|
||||
# Auditoria radiométrica completa do Raw5.
|
||||
#
|
||||
# True mantém o comportamento histórico e é útil para captura científica,
|
||||
# normalize/auditoria e ferramentas offline.
|
||||
#
|
||||
# No runtime em tempo real deve ficar False: evaluate_frame_quality()
|
||||
# calcula estatísticas/percentis pesados e não faz parte da montagem
|
||||
# necessária para a inferência.
|
||||
self.evaluate_quality = bool(evaluate_quality)
|
||||
|
||||
self.mx_id = str(mx_id) if mx_id else None
|
||||
|
||||
self.svc = OakFcc3Service(
|
||||
|
|
@ -327,8 +338,33 @@ class OakFcc3Client:
|
|||
target_size=target_size,
|
||||
)
|
||||
|
||||
def build_infer_tensor_from_decoded(self, decoded, meta, channels_expected, target_size=None):
|
||||
def build_infer_tensor_from_decoded(
|
||||
self,
|
||||
decoded,
|
||||
meta,
|
||||
channels_expected,
|
||||
target_size=None,
|
||||
evaluate_quality=None,
|
||||
):
|
||||
"""
|
||||
Monta o Raw5 físico a partir das câmeras já decodificadas.
|
||||
|
||||
evaluate_quality:
|
||||
- None -> usa self.evaluate_quality
|
||||
- True -> executa evaluate_frame_quality() e atualiza
|
||||
core.last_frame_quality_result
|
||||
- False -> não executa a auditoria pesada e limpa
|
||||
core.last_frame_quality_result
|
||||
|
||||
A flag altera somente a auditoria de qualidade. Não altera decode,
|
||||
radiometria, flat-field, homografia, crop/resize ou patch normalization.
|
||||
"""
|
||||
channels_expected = self._validate_physical_channel_count(channels_expected)
|
||||
|
||||
if evaluate_quality is None:
|
||||
evaluate_quality = self.evaluate_quality
|
||||
evaluate_quality = bool(evaluate_quality)
|
||||
|
||||
tensor = self.core.fuse_multispec_cameras(decoded, meta, channels_expected)
|
||||
tensor = self.core.resize_tensor_chw(tensor, target_size=target_size)
|
||||
|
||||
|
|
@ -338,7 +374,12 @@ class OakFcc3Client:
|
|||
if bool(patch_cfg.get("enabled", False)):
|
||||
tensor = self.core.apply_patch_normalization_to_tensor(tensor)
|
||||
|
||||
self.core.last_frame_quality_result = self.core.evaluate_frame_quality(tensor)
|
||||
if evaluate_quality:
|
||||
self.core.last_frame_quality_result = self.core.evaluate_frame_quality(tensor)
|
||||
else:
|
||||
# Evita deixar um resultado antigo parecer referente ao frame atual.
|
||||
self.core.last_frame_quality_result = None
|
||||
|
||||
return tensor
|
||||
|
||||
def decode_stream_cameras(self, frame, meta):
|
||||
|
|
@ -382,12 +423,19 @@ class OakFcc3Client:
|
|||
tensor = np.transpose(rgb01.astype(np.float32), (2, 0, 1))
|
||||
return np.ascontiguousarray(tensor.astype(np.float32, copy=False))
|
||||
|
||||
def build_multispec_tensor(self, decoded, meta=None, target_size=None):
|
||||
def build_multispec_tensor(
|
||||
self,
|
||||
decoded,
|
||||
meta=None,
|
||||
target_size=None,
|
||||
evaluate_quality=None,
|
||||
):
|
||||
tensor = self.build_infer_tensor_from_decoded(
|
||||
decoded=decoded,
|
||||
meta=meta,
|
||||
channels_expected=5,
|
||||
target_size=target_size,
|
||||
evaluate_quality=evaluate_quality,
|
||||
)
|
||||
return np.ascontiguousarray(tensor.astype(np.float32, copy=False))
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import cv2
|
|||
import depthai as dai
|
||||
import numpy as np
|
||||
|
||||
from itertools import product
|
||||
|
||||
|
||||
class OakFcc3Manager:
|
||||
"""
|
||||
|
|
@ -43,6 +45,8 @@ class OakFcc3Manager:
|
|||
raw_policy="allow_single",
|
||||
roles=None,
|
||||
sync_mode="best",
|
||||
hardware_sync_enabled=True,
|
||||
frame_sync_master="CAM_A",
|
||||
sync_tolerance_ms=12.0,
|
||||
buffer_size=8,
|
||||
only_camera=None,
|
||||
|
|
@ -52,6 +56,9 @@ class OakFcc3Manager:
|
|||
imu_modo="rotation_vector",
|
||||
imu_freq_hz=200,
|
||||
):
|
||||
self.hardware_sync_enabled = hardware_sync_enabled
|
||||
self.frame_sync_master = frame_sync_master
|
||||
|
||||
self.fps = fps
|
||||
|
||||
# Para compatibilidade, mantemos width/height.
|
||||
|
|
@ -432,6 +439,49 @@ class OakFcc3Manager:
|
|||
# Pipeline creation, MULTISPEC aligned on OAK
|
||||
# ============================================================
|
||||
|
||||
def _aplicar_frame_sync(self, cam, cam_id):
|
||||
cam_id_normalizado = str(cam_id).strip().upper()
|
||||
master = str(self.frame_sync_master).strip().upper()
|
||||
|
||||
if not self.hardware_sync_enabled:
|
||||
print(
|
||||
f"[OAK FSYNC] cam={cam_id_normalizado} "
|
||||
f"habilitado=False modo=DISABLED"
|
||||
)
|
||||
return
|
||||
|
||||
cameras_validas = {"CAM_A", "CAM_B", "CAM_C"}
|
||||
|
||||
if master not in cameras_validas:
|
||||
raise ValueError(
|
||||
f"frame_sync_master inválido: {self.frame_sync_master}. "
|
||||
f"Esperado: CAM_A, CAM_B ou CAM_C."
|
||||
)
|
||||
|
||||
if cam_id_normalizado not in cameras_validas:
|
||||
print(
|
||||
f"[OAK FSYNC] Câmera ignorada: "
|
||||
f"cam_id={cam_id_normalizado}"
|
||||
)
|
||||
return
|
||||
|
||||
if cam_id_normalizado == master:
|
||||
modo = dai.CameraControl.FrameSyncMode.OUTPUT
|
||||
nome_modo = "OUTPUT"
|
||||
else:
|
||||
modo = dai.CameraControl.FrameSyncMode.INPUT
|
||||
nome_modo = "INPUT"
|
||||
|
||||
cam.initialControl.setFrameSyncMode(modo)
|
||||
|
||||
print(
|
||||
f"[OAK FSYNC] "
|
||||
f"cam={cam_id_normalizado} "
|
||||
f"master={master} "
|
||||
f"habilitado=True "
|
||||
f"modo={nome_modo}"
|
||||
)
|
||||
|
||||
def _create_color_camera_multispec(self, socket):
|
||||
cam = self.pipeline.create(dai.node.ColorCamera)
|
||||
cam.setBoardSocket(socket)
|
||||
|
|
@ -591,6 +641,7 @@ class OakFcc3Manager:
|
|||
raise RuntimeError(f"Role não suportada no MULTISPEC: cam_id={cam_id}, role={role}")
|
||||
|
||||
self.apply_initial_camera_controls_to_node(cam, cam_id)
|
||||
self._aplicar_frame_sync(cam, cam_id)
|
||||
|
||||
xin_ctrl = self.pipeline.create(dai.node.XLinkIn)
|
||||
xin_ctrl.setStreamName(f"{cam_id}_ctrl")
|
||||
|
|
@ -871,6 +922,7 @@ class OakFcc3Manager:
|
|||
)
|
||||
|
||||
self.apply_initial_camera_controls_to_node(cam, socket_name)
|
||||
self._aplicar_frame_sync(cam, socket_name)
|
||||
|
||||
xin_ctrl = self.pipeline.create(dai.node.XLinkIn)
|
||||
xin_ctrl.setStreamName(f"{socket_name}_ctrl")
|
||||
|
|
@ -1241,6 +1293,41 @@ class OakFcc3Manager:
|
|||
f"Tolerância atual={self.sync_tolerance_ms} ms."
|
||||
)
|
||||
|
||||
def _encontrar_melhor_tripleta(self, required_cam_ids):
|
||||
listas = [
|
||||
list(self.buffers[cam_id])
|
||||
for cam_id in required_cam_ids
|
||||
]
|
||||
|
||||
melhor_selecao = None
|
||||
melhor_score = None
|
||||
|
||||
for combinacao in product(*listas):
|
||||
timestamps = [
|
||||
item["timestamp"]
|
||||
for item in combinacao
|
||||
]
|
||||
|
||||
menor_ts = min(timestamps)
|
||||
maior_ts = max(timestamps)
|
||||
spread_ms = (maior_ts - menor_ts) * 1000.0
|
||||
|
||||
# Primeiro prioriza menor dispersão.
|
||||
# Em empate, prefere o pacote mais recente.
|
||||
score = (
|
||||
spread_ms,
|
||||
-menor_ts,
|
||||
)
|
||||
|
||||
if melhor_score is None or score < melhor_score:
|
||||
melhor_score = score
|
||||
melhor_selecao = {
|
||||
cam_id: item
|
||||
for cam_id, item in zip(required_cam_ids, combinacao)
|
||||
}
|
||||
|
||||
return melhor_selecao
|
||||
|
||||
|
||||
def _extract_frame_controls(self, msg):
|
||||
controls = {
|
||||
|
|
@ -1330,9 +1417,19 @@ class OakFcc3Manager:
|
|||
|
||||
t0_ts = self._cap_now_ms()
|
||||
try:
|
||||
ts = msg.getTimestamp().total_seconds()
|
||||
ts_start = msg.getTimestampDevice(
|
||||
dai.CameraExposureOffset.START
|
||||
).total_seconds()
|
||||
|
||||
ts_end = msg.getTimestampDevice(
|
||||
dai.CameraExposureOffset.END
|
||||
).total_seconds()
|
||||
|
||||
except Exception:
|
||||
ts = time.time()
|
||||
ts_start = msg.getTimestamp().total_seconds()
|
||||
ts_end = ts_start
|
||||
|
||||
ts = ts_start
|
||||
if perf is not None:
|
||||
perf["drain_get_timestamp_ms"] += self._cap_now_ms() - t0_ts
|
||||
|
||||
|
|
@ -1395,7 +1492,9 @@ class OakFcc3Manager:
|
|||
|
||||
self.buffers[cam_id].append({
|
||||
"frame": frame,
|
||||
"timestamp": ts,
|
||||
"timestamp": ts_start,
|
||||
"timestamp_start": ts_start,
|
||||
"timestamp_end": ts_end,
|
||||
"controls": frame_controls,
|
||||
})
|
||||
|
||||
|
|
@ -1420,34 +1519,25 @@ class OakFcc3Manager:
|
|||
def _try_get_synced_packet(self, perf=None):
|
||||
required_cam_ids = self._get_required_cam_ids()
|
||||
|
||||
if not required_cam_ids:
|
||||
if perf is not None:
|
||||
perf["wait_reason"] = "no_required_cameras"
|
||||
return None
|
||||
|
||||
# Todas as câmeras precisam ter pelo menos um frame disponível.
|
||||
for cam_id in required_cam_ids:
|
||||
if cam_id not in self.buffers or len(self.buffers[cam_id]) == 0:
|
||||
if perf is not None:
|
||||
perf["wait_reason"] = f"empty_buffer:{cam_id}"
|
||||
return None
|
||||
|
||||
ref_cam_id = min(required_cam_ids, key=lambda cid: len(self.buffers[cid]))
|
||||
ref_item = self.buffers[ref_cam_id][0]
|
||||
ref_ts = ref_item["timestamp"]
|
||||
# Procura a melhor combinação entre todos os frames disponíveis.
|
||||
selected = self._encontrar_melhor_tripleta(required_cam_ids)
|
||||
|
||||
selected = {}
|
||||
|
||||
for cam_id in required_cam_ids:
|
||||
best_item = None
|
||||
best_dt = None
|
||||
|
||||
for item in self.buffers[cam_id]:
|
||||
dt = abs(item["timestamp"] - ref_ts)
|
||||
if best_dt is None or dt < best_dt:
|
||||
best_dt = dt
|
||||
best_item = item
|
||||
|
||||
if best_item is None:
|
||||
if perf is not None:
|
||||
perf["wait_reason"] = f"no_best_item:{cam_id}"
|
||||
return None
|
||||
|
||||
selected[cam_id] = best_item
|
||||
if not selected or len(selected) != len(required_cam_ids):
|
||||
if perf is not None:
|
||||
perf["wait_reason"] = "no_valid_selection"
|
||||
return None
|
||||
|
||||
timestamps = {
|
||||
cam_id: item["timestamp"]
|
||||
|
|
@ -1459,44 +1549,80 @@ class OakFcc3Manager:
|
|||
for cam_id, item in selected.items()
|
||||
}
|
||||
|
||||
ts_values = list(timestamps.values())
|
||||
|
||||
sync_dt_ms = (
|
||||
(max(ts_values) - min(ts_values)) * 1000.0
|
||||
if len(ts_values) >= 2
|
||||
else 0.0
|
||||
)
|
||||
|
||||
sync_ok = sync_dt_ms <= self.sync_tolerance_ms
|
||||
|
||||
if perf is not None:
|
||||
perf["selected_ts_by_cam"] = {cam_id: float(ts) for cam_id, ts in timestamps.items()}
|
||||
perf["selected_ts_by_cam"] = {
|
||||
cam_id: float(ts)
|
||||
for cam_id, ts in timestamps.items()
|
||||
}
|
||||
|
||||
perf["selected_seq_by_cam"] = {
|
||||
cam_id: item.get("controls", {}).get("sequence_num")
|
||||
for cam_id, item in selected.items()
|
||||
}
|
||||
|
||||
ts_values = list(timestamps.values())
|
||||
sync_dt_ms = (max(ts_values) - min(ts_values)) * 1000.0 if len(ts_values) >= 2 else 0.0
|
||||
sync_ok = sync_dt_ms <= self.sync_tolerance_ms
|
||||
perf["sync_dt_ms"] = float(sync_dt_ms)
|
||||
perf["sync_ok"] = bool(sync_ok)
|
||||
|
||||
modo_sync = str(self.sync_mode).strip().lower()
|
||||
|
||||
# No modo estrito, nunca entrega uma tripleta fora da tolerância.
|
||||
if not sync_ok and modo_sync == "strict":
|
||||
#Remove o frame globalmente mais antigo entre as cabeças
|
||||
#dos buffers. Frames futuros somente estarão mais distantes
|
||||
#desse frame, então ele não conseguirá formar uma combinação
|
||||
#melhor posteriormente.
|
||||
oldest_cam_id = min(
|
||||
required_cam_ids,
|
||||
key=lambda cam_id: self.buffers[cam_id][0]["timestamp"]
|
||||
)
|
||||
|
||||
dropped_item = self.buffers[oldest_cam_id].popleft()
|
||||
|
||||
if not sync_ok and self.sync_mode == "strict":
|
||||
oldest_cam_id = min(timestamps, key=timestamps.get)
|
||||
if len(self.buffers[oldest_cam_id]) > 0:
|
||||
self.buffers[oldest_cam_id].popleft()
|
||||
if perf is not None:
|
||||
perf["wait_reason"] = f"strict_drop_oldest:{oldest_cam_id}"
|
||||
perf["sync_dt_ms"] = float(sync_dt_ms)
|
||||
perf["sync_ok"] = False
|
||||
perf["dropped_timestamp"] = float(dropped_item["timestamp"])
|
||||
|
||||
return None
|
||||
|
||||
# Em best/best_effort, entrega a melhor combinação disponível,
|
||||
# mesmo quando estiver fora da tolerância.
|
||||
frames = {
|
||||
cam_id: item["frame"]
|
||||
for cam_id, item in selected.items()
|
||||
}
|
||||
|
||||
# Consome todos os frames anteriores e o próprio frame selecionado.
|
||||
for cam_id, used_item in selected.items():
|
||||
while len(self.buffers[cam_id]) > 0:
|
||||
while self.buffers[cam_id]:
|
||||
item = self.buffers[cam_id].popleft()
|
||||
|
||||
if item is used_item:
|
||||
break
|
||||
|
||||
if perf is not None:
|
||||
perf["wait_reason"] = "synced_selected"
|
||||
perf["sync_dt_ms"] = float(sync_dt_ms)
|
||||
perf["sync_ok"] = bool(sync_ok)
|
||||
perf["wait_reason"] = (
|
||||
"synced_selected"
|
||||
if sync_ok
|
||||
else "best_effort_selected_outside_tolerance"
|
||||
)
|
||||
|
||||
return frames, timestamps, sync_dt_ms, sync_ok, frame_controls
|
||||
return (
|
||||
frames,
|
||||
timestamps,
|
||||
sync_dt_ms,
|
||||
sync_ok,
|
||||
frame_controls,
|
||||
)
|
||||
|
||||
def _get_available_cam_ids_ordered(self):
|
||||
role_order = ["rgb", "re", "nir"]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,761 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
camera_startup_profile_production.py
|
||||
====================================
|
||||
|
||||
Gerador/auditor FINAL de produção para o bloco `camera_settings`
|
||||
do módulo multiespectral.
|
||||
|
||||
IMPORTANTE
|
||||
----------
|
||||
Isto NÃO é uma calibração óptica/radiométrica nova.
|
||||
|
||||
O script antigo "Sensor Calibration Tool" misturava:
|
||||
- camera_settings;
|
||||
- ajustes manuais AE/AWB/exposure/gain;
|
||||
- ROIs de cana/erva/solo;
|
||||
- guidance heurístico;
|
||||
- rgb_calibration manual;
|
||||
- snapshots/offline samples.
|
||||
|
||||
Na arquitetura final de produto essas responsabilidades já foram separadas.
|
||||
|
||||
Esta ferramenta preserva SOMENTE o que ainda é necessário ao runtime:
|
||||
camera_settings
|
||||
|
||||
e força explicitamente:
|
||||
rgb_calibration.enabled = false
|
||||
gains = 1,1,1
|
||||
|
||||
para impedir que ganhos RGB históricos/experimentais vazem para o tensor.
|
||||
|
||||
Dependência
|
||||
-----------
|
||||
A ferramenta lê o artefato homologado da etapa radiométrica:
|
||||
|
||||
calibration/radiometry_calibration_v5.json
|
||||
|
||||
e reaproveita:
|
||||
radiometric_normalization.reference_controls
|
||||
|
||||
como valores de fallback do startup.
|
||||
|
||||
Política normal de produto
|
||||
--------------------------
|
||||
RGB:
|
||||
AE = ON
|
||||
AWB = ON
|
||||
exposure_time_us = referência radiométrica (fallback)
|
||||
analogue_gain = ISO_ref / 100
|
||||
colour_gains = [1.0, 1.0]
|
||||
|
||||
RE:
|
||||
AE = ON
|
||||
AWB = OFF
|
||||
exposure_time_us = referência radiométrica (fallback)
|
||||
analogue_gain = ISO_ref / 100
|
||||
|
||||
NIR:
|
||||
AE = ON
|
||||
AWB = OFF
|
||||
exposure_time_us = referência radiométrica (fallback)
|
||||
analogue_gain = ISO_ref / 100
|
||||
|
||||
Por que manter os valores manuais se AE está ON?
|
||||
-------------------------------------------------
|
||||
No manager atual, com AE ligado, exposure/gain do JSON não são forçados
|
||||
fisicamente. Eles ficam armazenados como estado/fallback para uma eventual
|
||||
transição futura para manual.
|
||||
|
||||
Assim:
|
||||
- o campo continua adaptando exposição automaticamente;
|
||||
- a normalização radiométrica usa os controles reais de cada frame;
|
||||
- se AE for desabilitado em algum fluxo futuro, o fallback já é coerente
|
||||
com a calibração radiométrica do módulo.
|
||||
|
||||
Saída
|
||||
-----
|
||||
calibration/camera_startup_profile_v3.json
|
||||
|
||||
Contém:
|
||||
module_params_fragment.camera_settings
|
||||
module_params_fragment.rgb_calibration
|
||||
|
||||
O assembler final do module_params continua sendo a autoridade de merge.
|
||||
|
||||
Fail-closed
|
||||
-----------
|
||||
- exige CAM_A = OV9782 ou AR0234;
|
||||
- exige CAM_B/C = OV9282;
|
||||
- exige artefato radiométrico promovido;
|
||||
- exige hardware_signature compatível;
|
||||
- exige reference_controls válidos para rgb/re/nir;
|
||||
- não aceita ISO inválido;
|
||||
- não ativa rgb_calibration;
|
||||
- preserva backup do ativo anterior.
|
||||
|
||||
Uso
|
||||
---
|
||||
python camera_startup_profile_production.py
|
||||
|
||||
Com rastreabilidade:
|
||||
python camera_startup_profile_production.py ^
|
||||
--module-id MOD_MS_001 ^
|
||||
--operator Diego
|
||||
|
||||
Somente auditoria, sem salvar:
|
||||
python camera_startup_profile_production.py --check-only
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, List
|
||||
|
||||
import depthai as dai
|
||||
|
||||
|
||||
SCHEMA = "multispec_camera_startup_profile_v3"
|
||||
ISO_BASE = 100.0
|
||||
|
||||
ROLES = ("rgb", "re", "nir")
|
||||
|
||||
PRODUCT_TOPOLOGY = {
|
||||
"rgb": {
|
||||
"socket": "CAM_A",
|
||||
"allowed_sensors": ("OV9782", "AR0234"),
|
||||
},
|
||||
"re": {
|
||||
"socket": "CAM_B",
|
||||
"allowed_sensors": ("OV9282",),
|
||||
},
|
||||
"nir": {
|
||||
"socket": "CAM_C",
|
||||
"allowed_sensors": ("OV9282",),
|
||||
},
|
||||
}
|
||||
|
||||
SENSOR_NATIVE_SIZE = {
|
||||
"OV9782": (1280, 800),
|
||||
"AR0234": (1920, 1200),
|
||||
"OV9282": (1280, 800),
|
||||
}
|
||||
|
||||
|
||||
def now_str() -> str:
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def stamp() -> str:
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||||
|
||||
|
||||
def ensure_dir(path: str | Path):
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def load_json(path: str | Path) -> dict:
|
||||
p = Path(path)
|
||||
if not p.is_file():
|
||||
raise FileNotFoundError(f"Arquivo não encontrado: {p}")
|
||||
|
||||
with p.open("r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_json_atomic(path: str | Path, data: dict):
|
||||
p = Path(path)
|
||||
ensure_dir(p.parent)
|
||||
|
||||
tmp = p.with_suffix(p.suffix + ".tmp")
|
||||
|
||||
with tmp.open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
|
||||
os.replace(tmp, p)
|
||||
|
||||
|
||||
def socket_name(socket) -> str:
|
||||
name = getattr(socket, "name", None)
|
||||
if name:
|
||||
return str(name)
|
||||
|
||||
text = str(socket)
|
||||
for candidate in ("CAM_A", "CAM_B", "CAM_C", "CAM_D"):
|
||||
if candidate in text:
|
||||
return candidate
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def supported_types(feature) -> List[str]:
|
||||
return [
|
||||
str(v).upper()
|
||||
for v in (getattr(feature, "supportedTypes", []) or [])
|
||||
]
|
||||
|
||||
|
||||
def feature_is_color(feature) -> bool:
|
||||
types = supported_types(feature)
|
||||
sensor = str(getattr(feature, "sensorName", "") or "").upper()
|
||||
|
||||
if any("COLOR" in x for x in types):
|
||||
return True
|
||||
if any("MONO" in x for x in types):
|
||||
return False
|
||||
|
||||
return sensor in {"OV9782", "AR0234"}
|
||||
|
||||
|
||||
def feature_is_mono(feature) -> bool:
|
||||
types = supported_types(feature)
|
||||
|
||||
if any("MONO" in x for x in types):
|
||||
return True
|
||||
if any("COLOR" in x for x in types):
|
||||
return False
|
||||
|
||||
return not feature_is_color(feature)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CameraInfo:
|
||||
role: str
|
||||
socket: str
|
||||
sensor: str
|
||||
width: int
|
||||
height: int
|
||||
is_color: bool
|
||||
is_mono: bool
|
||||
|
||||
|
||||
def discover_hardware(mx_id: Optional[str]):
|
||||
info = dai.DeviceInfo(mx_id) if mx_id else None
|
||||
ctx = dai.Device(info) if info is not None else dai.Device()
|
||||
|
||||
with ctx as device:
|
||||
actual_mx = None
|
||||
|
||||
for method_name in ("getMxId", "getDeviceId"):
|
||||
fn = getattr(device, method_name, None)
|
||||
if callable(fn):
|
||||
try:
|
||||
value = fn()
|
||||
if value:
|
||||
actual_mx = str(value)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
usb_speed = None
|
||||
try:
|
||||
usb_speed = str(device.getUsbSpeed())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
rows = []
|
||||
|
||||
for f in device.getConnectedCameraFeatures():
|
||||
rows.append({
|
||||
"socket": socket_name(f.socket),
|
||||
"sensor": str(getattr(f, "sensorName", "") or "").upper(),
|
||||
"width": int(getattr(f, "width", 0) or 0),
|
||||
"height": int(getattr(f, "height", 0) or 0),
|
||||
"is_color": feature_is_color(f),
|
||||
"is_mono": feature_is_mono(f),
|
||||
"supported_types": supported_types(f),
|
||||
})
|
||||
|
||||
return rows, actual_mx, usb_speed
|
||||
|
||||
|
||||
def validate_and_resolve_hardware(rows: list[dict]) -> Dict[str, CameraInfo]:
|
||||
by_socket = {row["socket"]: row for row in rows}
|
||||
resolved = {}
|
||||
errors = []
|
||||
|
||||
for role in ROLES:
|
||||
contract = PRODUCT_TOPOLOGY[role]
|
||||
sock = contract["socket"]
|
||||
|
||||
row = by_socket.get(sock)
|
||||
if row is None:
|
||||
errors.append(f"{role.upper()}: {sock} ausente")
|
||||
continue
|
||||
|
||||
sensor = row["sensor"]
|
||||
|
||||
if sensor not in contract["allowed_sensors"]:
|
||||
errors.append(
|
||||
f"{role.upper()}: {sock} sensor={sensor}, "
|
||||
f"permitidos={contract['allowed_sensors']}"
|
||||
)
|
||||
continue
|
||||
|
||||
expected_w, expected_h = SENSOR_NATIVE_SIZE[sensor]
|
||||
|
||||
if row["width"] and row["height"]:
|
||||
if (row["width"], row["height"]) != (expected_w, expected_h):
|
||||
errors.append(
|
||||
f"{role.upper()}: {sensor} anunciou "
|
||||
f"{row['width']}x{row['height']}, esperado "
|
||||
f"{expected_w}x{expected_h}"
|
||||
)
|
||||
|
||||
if role == "rgb" and not row["is_color"]:
|
||||
errors.append(f"RGB: {sock}/{sensor} não anunciado COLOR")
|
||||
|
||||
if role in ("re", "nir") and not row["is_mono"]:
|
||||
errors.append(f"{role.upper()}: {sock}/{sensor} não anunciado MONO")
|
||||
|
||||
resolved[role] = CameraInfo(
|
||||
role=role,
|
||||
socket=sock,
|
||||
sensor=sensor,
|
||||
width=expected_w,
|
||||
height=expected_h,
|
||||
is_color=bool(row["is_color"]),
|
||||
is_mono=bool(row["is_mono"]),
|
||||
)
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(
|
||||
"Topologia de produto inválida:\n - "
|
||||
+ "\n - ".join(errors)
|
||||
)
|
||||
|
||||
return resolved
|
||||
|
||||
|
||||
def hardware_signature_from_resolved(resolved: Dict[str, CameraInfo]) -> dict:
|
||||
return {
|
||||
role: {
|
||||
"socket": resolved[role].socket,
|
||||
"sensor": resolved[role].sensor,
|
||||
"size": [resolved[role].width, resolved[role].height],
|
||||
}
|
||||
for role in ROLES
|
||||
}
|
||||
|
||||
|
||||
def normalize_signature(sig: dict) -> dict:
|
||||
"""
|
||||
Aceita assinatura no formato dos calibradores de produção.
|
||||
Retorna apenas role/socket/sensor/size para comparação robusta.
|
||||
"""
|
||||
out = {}
|
||||
|
||||
for role in ROLES:
|
||||
item = (sig or {}).get(role, {}) or {}
|
||||
|
||||
socket = (
|
||||
item.get("socket")
|
||||
or item.get("socket_name")
|
||||
)
|
||||
|
||||
sensor = (
|
||||
item.get("sensor")
|
||||
or item.get("sensor_name")
|
||||
)
|
||||
|
||||
size = item.get("size")
|
||||
|
||||
if size is None:
|
||||
width = item.get("width")
|
||||
height = item.get("height")
|
||||
if width is not None and height is not None:
|
||||
size = [int(width), int(height)]
|
||||
|
||||
if socket is None or sensor is None or size is None:
|
||||
raise RuntimeError(
|
||||
f"hardware_signature incompleta para {role}: {item}"
|
||||
)
|
||||
|
||||
out[role] = {
|
||||
"socket": str(socket),
|
||||
"sensor": str(sensor).upper(),
|
||||
"size": [int(size[0]), int(size[1])],
|
||||
}
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def extract_radiometric_fragment(data: dict) -> dict:
|
||||
"""
|
||||
O artefato radiométrico de produção guarda:
|
||||
module_params_fragment.radiometric_normalization
|
||||
"""
|
||||
fragment = data.get("module_params_fragment", {}) or {}
|
||||
rn = fragment.get("radiometric_normalization")
|
||||
|
||||
if not isinstance(rn, dict):
|
||||
raise RuntimeError(
|
||||
"Artefato radiométrico sem "
|
||||
"module_params_fragment.radiometric_normalization"
|
||||
)
|
||||
|
||||
if not rn.get("enabled", False):
|
||||
raise RuntimeError(
|
||||
"radiometric_normalization do artefato não está enabled=true"
|
||||
)
|
||||
|
||||
return rn
|
||||
|
||||
|
||||
def validate_radiometry_artifact(
|
||||
data: dict,
|
||||
current_signature: dict,
|
||||
):
|
||||
schema = str(data.get("schema", ""))
|
||||
|
||||
if schema != "multispec_radiometric_calibration_v5":
|
||||
raise RuntimeError(
|
||||
f"Schema radiométrico inesperado: {schema!r}. "
|
||||
"Esperado 'multispec_radiometric_calibration_v5'."
|
||||
)
|
||||
|
||||
status = str(data.get("status", "")).lower()
|
||||
|
||||
if status not in ("good", "warning"):
|
||||
raise RuntimeError(
|
||||
f"Radiometria não homologada para consumo: status={status!r}"
|
||||
)
|
||||
|
||||
if not bool(data.get("promoted", False)):
|
||||
raise RuntimeError(
|
||||
"Artefato radiométrico não foi promovido."
|
||||
)
|
||||
|
||||
artifact_sig = normalize_signature(
|
||||
data.get("hardware_signature", {})
|
||||
)
|
||||
|
||||
if artifact_sig != current_signature:
|
||||
raise RuntimeError(
|
||||
"Hardware conectado não corresponde ao hardware da radiometria.\n"
|
||||
f"Atual : {current_signature}\n"
|
||||
f"Rad : {artifact_sig}"
|
||||
)
|
||||
|
||||
rn = extract_radiometric_fragment(data)
|
||||
|
||||
refs = rn.get("reference_controls", {}) or {}
|
||||
|
||||
for role in ROLES:
|
||||
ref = refs.get(role)
|
||||
|
||||
if not isinstance(ref, dict):
|
||||
raise RuntimeError(
|
||||
f"reference_controls ausente para {role}"
|
||||
)
|
||||
|
||||
exp = ref.get("exposure_time_us")
|
||||
iso = ref.get("sensitivity_iso")
|
||||
|
||||
if exp is None or float(exp) <= 0:
|
||||
raise RuntimeError(
|
||||
f"reference exposure inválida para {role}: {exp}"
|
||||
)
|
||||
|
||||
if iso is None or float(iso) < 50:
|
||||
raise RuntimeError(
|
||||
f"reference ISO inválido para {role}: {iso}"
|
||||
)
|
||||
|
||||
return rn
|
||||
|
||||
|
||||
def iso_to_analogue_gain(iso: float) -> float:
|
||||
"""
|
||||
O manager atual representa analogue_gain aproximadamente como ISO/100.
|
||||
"""
|
||||
gain = float(iso) / ISO_BASE
|
||||
return max(1.0, gain)
|
||||
|
||||
|
||||
def build_camera_settings(reference_controls: dict) -> dict:
|
||||
settings = {}
|
||||
|
||||
for role in ROLES:
|
||||
ref = reference_controls[role]
|
||||
|
||||
exp = int(round(float(ref["exposure_time_us"])))
|
||||
iso = float(ref["sensitivity_iso"])
|
||||
gain = iso_to_analogue_gain(iso)
|
||||
|
||||
if role == "rgb":
|
||||
settings[role] = {
|
||||
"ae_enable": True,
|
||||
"awb_enable": True,
|
||||
"exposure_time_us": exp,
|
||||
"analogue_gain": gain,
|
||||
"colour_gains": [1.0, 1.0],
|
||||
}
|
||||
else:
|
||||
settings[role] = {
|
||||
"ae_enable": True,
|
||||
"awb_enable": False,
|
||||
"exposure_time_us": exp,
|
||||
"analogue_gain": gain,
|
||||
"colour_gains": None,
|
||||
}
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def build_rgb_calibration_policy() -> dict:
|
||||
"""
|
||||
Produto final mantém correção multiplicativa RGB desligada.
|
||||
Se um dia ela for homologada, deve existir um calibrador separado e
|
||||
dataset/runtime precisam usar o mesmo contrato.
|
||||
"""
|
||||
return {
|
||||
"enabled": False,
|
||||
"gains": {
|
||||
"R": 1.0,
|
||||
"G": 1.0,
|
||||
"B": 1.0,
|
||||
},
|
||||
"policy": "disabled_product_default",
|
||||
"reason": (
|
||||
"No validated RGB tensor-gain calibration is currently active. "
|
||||
"Historical manual gains are intentionally discarded."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def compare_existing_module_params(
|
||||
module_params_path: Optional[str],
|
||||
generated_fragment: dict,
|
||||
) -> dict:
|
||||
if not module_params_path:
|
||||
return {
|
||||
"available": False,
|
||||
"path": None,
|
||||
}
|
||||
|
||||
p = Path(module_params_path)
|
||||
|
||||
if not p.is_file():
|
||||
return {
|
||||
"available": False,
|
||||
"path": str(p),
|
||||
}
|
||||
|
||||
current = load_json(p)
|
||||
|
||||
current_camera = current.get("camera_settings")
|
||||
current_rgb = current.get("rgb_calibration")
|
||||
|
||||
generated_camera = generated_fragment["camera_settings"]
|
||||
generated_rgb = generated_fragment["rgb_calibration"]
|
||||
|
||||
return {
|
||||
"available": True,
|
||||
"path": str(p),
|
||||
"schema": current.get("schema"),
|
||||
"camera_settings_equal": current_camera == generated_camera,
|
||||
"rgb_calibration_equal": current_rgb == generated_rgb,
|
||||
"current_camera_settings": current_camera,
|
||||
"generated_camera_settings": generated_camera,
|
||||
"current_rgb_calibration": current_rgb,
|
||||
"generated_rgb_calibration": generated_rgb,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Gerador/auditor de produção para camera_settings e "
|
||||
"política rgb_calibration do módulo multiespectral."
|
||||
),
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--mx-id",
|
||||
default=None,
|
||||
help="MX ID da OAK. Vazio usa o primeiro dispositivo disponível.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--radiometry-json",
|
||||
default="calibration/radiometry_calibration_v5.json",
|
||||
help="Artefato radiométrico homologado.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--out-json",
|
||||
default="calibration/camera_startup_profile_v3.json",
|
||||
help="Artefato ativo desta etapa.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--compare-module-params",
|
||||
default="calibration/module_params.json",
|
||||
help="Opcional: audita diferenças contra module_params existente.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--module-id",
|
||||
default="",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--operator",
|
||||
default="",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--notes",
|
||||
default="",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--check-only",
|
||||
action="store_true",
|
||||
help="Valida e imprime o fragmento, mas não salva.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
rows, actual_mx, usb_speed = discover_hardware(args.mx_id)
|
||||
resolved = validate_and_resolve_hardware(rows)
|
||||
|
||||
current_signature = hardware_signature_from_resolved(resolved)
|
||||
|
||||
radiometry = load_json(args.radiometry_json)
|
||||
|
||||
rn = validate_radiometry_artifact(
|
||||
radiometry,
|
||||
current_signature,
|
||||
)
|
||||
|
||||
reference_controls = rn["reference_controls"]
|
||||
|
||||
camera_settings = build_camera_settings(reference_controls)
|
||||
rgb_calibration = build_rgb_calibration_policy()
|
||||
|
||||
module_params_fragment = {
|
||||
"camera_settings": camera_settings,
|
||||
"rgb_calibration": {
|
||||
"enabled": False,
|
||||
"gains": {
|
||||
"R": 1.0,
|
||||
"G": 1.0,
|
||||
"B": 1.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
audit = compare_existing_module_params(
|
||||
args.compare_module_params,
|
||||
module_params_fragment,
|
||||
)
|
||||
|
||||
payload = {
|
||||
"schema": SCHEMA,
|
||||
"created_at": now_str(),
|
||||
"status": "good",
|
||||
"promoted": not args.check_only,
|
||||
"runtime_effect": (
|
||||
"startup_camera_policy_and_explicit_rgb_gain_disable"
|
||||
),
|
||||
"device_mx_id": actual_mx,
|
||||
"usb_speed": usb_speed,
|
||||
"hardware_inventory": rows,
|
||||
"hardware_signature": current_signature,
|
||||
"source_radiometry": {
|
||||
"path": str(args.radiometry_json),
|
||||
"schema": radiometry.get("schema"),
|
||||
"session_id": radiometry.get("session_id"),
|
||||
"status": radiometry.get("status"),
|
||||
"promoted": radiometry.get("promoted"),
|
||||
"reference_controls": reference_controls,
|
||||
},
|
||||
"startup_policy": {
|
||||
"mode": "ae_with_calibrated_manual_fallback",
|
||||
"rgb_awb": "auto_for_isp_preview_only",
|
||||
"spectral_awb": "off_not_applicable",
|
||||
"manual_fallback_source": (
|
||||
"radiometric_normalization.reference_controls"
|
||||
),
|
||||
"note": (
|
||||
"With AE enabled, manager stores exposure/gain as fallback "
|
||||
"and does not force them manually at startup."
|
||||
),
|
||||
},
|
||||
"rgb_calibration_policy": rgb_calibration,
|
||||
"module_params_fragment": module_params_fragment,
|
||||
"existing_module_params_audit": audit,
|
||||
"traceability": {
|
||||
"module_id": args.module_id or None,
|
||||
"operator": args.operator or None,
|
||||
},
|
||||
"notes": args.notes or "",
|
||||
}
|
||||
|
||||
print("=" * 82)
|
||||
print("CAMERA STARTUP PROFILE - PRODUCTION")
|
||||
print(f"MX ID : {actual_mx}")
|
||||
print(f"USB : {usb_speed}")
|
||||
print("-" * 82)
|
||||
|
||||
for role in ROLES:
|
||||
c = resolved[role]
|
||||
s = camera_settings[role]
|
||||
|
||||
print(
|
||||
f"{c.socket} -> {role.upper():3s} | "
|
||||
f"{c.sensor:8s} | {c.width}x{c.height} | "
|
||||
f"AE={s['ae_enable']} AWB={s['awb_enable']} | "
|
||||
f"fallback={s['exposure_time_us']}us gain={s['analogue_gain']:.3f}"
|
||||
)
|
||||
|
||||
print("-" * 82)
|
||||
print("rgb_calibration: DISABLED | gains = 1.0 / 1.0 / 1.0")
|
||||
|
||||
if audit.get("available"):
|
||||
print(
|
||||
"module_params audit: "
|
||||
f"camera_settings_equal={audit['camera_settings_equal']} | "
|
||||
f"rgb_calibration_equal={audit['rgb_calibration_equal']}"
|
||||
)
|
||||
else:
|
||||
print("module_params audit: arquivo não disponível")
|
||||
|
||||
if args.check_only:
|
||||
print("[CHECK-ONLY] Nenhum arquivo alterado.")
|
||||
print("=" * 82)
|
||||
return
|
||||
|
||||
out = Path(args.out_json)
|
||||
ensure_dir(out.parent)
|
||||
|
||||
if out.exists():
|
||||
backup = out.with_name(
|
||||
out.stem
|
||||
+ f".backup_{stamp()}"
|
||||
+ out.suffix
|
||||
)
|
||||
shutil.copy2(out, backup)
|
||||
|
||||
save_json_atomic(out, payload)
|
||||
|
||||
print(f"[PASS] Artefato salvo: {out}")
|
||||
print("[ASSEMBLER] Consumir module_params_fragment deste JSON.")
|
||||
print("=" * 82)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,654 +0,0 @@
|
|||
import json
|
||||
import argparse
|
||||
import os
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Helpers
|
||||
# ============================================================
|
||||
|
||||
|
||||
def now_str():
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def load_json(path, required=True):
|
||||
if not path or not os.path.isfile(path):
|
||||
if required:
|
||||
raise FileNotFoundError(f"Arquivo não encontrado: {path}")
|
||||
return {}
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_json(path, data):
|
||||
out_dir = os.path.dirname(os.path.abspath(path))
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
def rel_or_abs(path):
|
||||
"""
|
||||
Mantém o caminho como veio, mas normaliza separadores.
|
||||
Isso evita quebrar projetos Windows/Linux e deixa o module_params legível.
|
||||
"""
|
||||
if path is None:
|
||||
return None
|
||||
return str(path).replace("\\", "/")
|
||||
|
||||
|
||||
def deep_merge(base, update, *, skip_none=True):
|
||||
"""
|
||||
Merge recursivo seguro.
|
||||
|
||||
- dict + dict: combina recursivamente.
|
||||
- listas/escalares: valor novo substitui o antigo.
|
||||
- None: por padrão NÃO apaga valor antigo, para evitar perder calibração quando
|
||||
um arquivo fonte não conhece determinada chave.
|
||||
"""
|
||||
if not isinstance(base, dict):
|
||||
base = {}
|
||||
out = deepcopy(base)
|
||||
|
||||
if not isinstance(update, dict):
|
||||
return out
|
||||
|
||||
for key, value in update.items():
|
||||
if value is None and skip_none:
|
||||
continue
|
||||
|
||||
if isinstance(value, dict) and isinstance(out.get(key), dict):
|
||||
out[key] = deep_merge(out[key], value, skip_none=skip_none)
|
||||
else:
|
||||
out[key] = deepcopy(value)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def first_dict(*values):
|
||||
for value in values:
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Defaults coerentes com RawProcessorCore + module_params atual
|
||||
# ============================================================
|
||||
|
||||
|
||||
DEFAULT_RGB_PROCESSING = {
|
||||
"mode": "bayer_planes",
|
||||
}
|
||||
|
||||
|
||||
DEFAULT_PATCH_NORMALIZATION = {
|
||||
"enabled": True,
|
||||
"apply_when_metering_mode": "reference_patches",
|
||||
"apply_stage": "after_fusion",
|
||||
"method": "gray_scale_with_white_guard",
|
||||
"space": "multispec_tensor",
|
||||
"targets_by_patch_channel": {
|
||||
"black": {
|
||||
"R": 0.06,
|
||||
"G": 0.06,
|
||||
"B": 0.06,
|
||||
"RE": 0.06,
|
||||
"NIR": 0.06,
|
||||
},
|
||||
"gray": {
|
||||
"R": 0.34,
|
||||
"G": 0.34,
|
||||
"B": 0.34,
|
||||
"RE": 0.24,
|
||||
"NIR": 0.30,
|
||||
},
|
||||
"white": {
|
||||
"R": 0.78,
|
||||
"G": 0.78,
|
||||
"B": 0.78,
|
||||
"RE": 0.78,
|
||||
"NIR": 0.78,
|
||||
},
|
||||
},
|
||||
"white_guard_max": 0.92,
|
||||
"white_guard_max_by_channel": {
|
||||
"R": 0.92,
|
||||
"G": 0.92,
|
||||
"B": 0.92,
|
||||
"RE": 0.88,
|
||||
"NIR": 0.88,
|
||||
},
|
||||
"scale_min": 0.35,
|
||||
"scale_max": 2.5,
|
||||
"clip_output": True,
|
||||
"require_valid_gray": True,
|
||||
"use_black_for_offset": False,
|
||||
"save_patch_stats": True,
|
||||
"rgb_saturation_guard_enabled": True,
|
||||
"rgb_saturation_guard_mode": "fade_strength",
|
||||
"rgb_saturation_soft_start": 0.88,
|
||||
"rgb_saturation_hard": 0.97,
|
||||
"rgb_saturation_threshold": 0.97,
|
||||
}
|
||||
|
||||
|
||||
DEFAULT_FLATFIELD_RUNTIME = {
|
||||
"strength": 0.35,
|
||||
"strength_by_channel": {
|
||||
"R": 0.9,
|
||||
"G": 0.9,
|
||||
"B": 0.9,
|
||||
"RE": 0.25,
|
||||
"NIR": 0.25,
|
||||
},
|
||||
"gain_min_runtime": 0.75,
|
||||
"gain_max_runtime": 1.35,
|
||||
"runtime_smooth_ksize": 81,
|
||||
"saturation_guard_enabled": True,
|
||||
"saturation_guard_mode": "fade_strength",
|
||||
"saturation_guard_threshold": 0.97,
|
||||
"saturation_guard_soft_start": 0.88,
|
||||
"saturation_guard_hard": 0.97,
|
||||
}
|
||||
|
||||
|
||||
DEFAULT_RADIOMETRIC_NORMALIZATION = {
|
||||
"enabled": False,
|
||||
"method": "exposure_gain_reference",
|
||||
"apply_stage": "after_dark_before_flat_gain",
|
||||
"reference_controls": {
|
||||
"rgb": {"exposure_time_us": 3000, "analogue_gain": 1.0},
|
||||
"re": {"exposure_time_us": 7000, "analogue_gain": 1.0},
|
||||
"nir": {"exposure_time_us": 7000, "analogue_gain": 1.0},
|
||||
},
|
||||
"clip_output": True,
|
||||
}
|
||||
|
||||
|
||||
DEFAULT_RADIOMETRIC_CONFIG = {
|
||||
"enabled": True,
|
||||
"interval_s": 0.25,
|
||||
"verbose": True,
|
||||
"metering_mode": "reference_patches",
|
||||
"spectral_control_mode": "shared",
|
||||
"control_metric": "p50",
|
||||
"target_value": 0.5,
|
||||
"deadband": 0.055,
|
||||
"p95_limit": 0.975,
|
||||
"saturation_limit_pct": 5.0,
|
||||
"alpha": 0.18,
|
||||
"exp_step_gain": 0.55,
|
||||
"prefer_exposure": True,
|
||||
"exp_min_us": 100,
|
||||
"exp_max_us": 80000,
|
||||
"gain_min": 1.0,
|
||||
"gain_max": 4.0,
|
||||
"exp_apply_threshold_us": 15,
|
||||
"gain_apply_threshold": 0.05,
|
||||
"apply_same_spectral_to_both": True,
|
||||
"spectral_roles": ["re", "nir"],
|
||||
"dark_limit_pct": 35.0,
|
||||
"control_strategy": "ratio",
|
||||
"ratio_alpha": 0.42,
|
||||
"ratio_min": 0.72,
|
||||
"ratio_max": 1.38,
|
||||
"reduce_fast_factor": 0.8,
|
||||
"factor_min": 0.62,
|
||||
"factor_max": 1.42,
|
||||
"gain_return_enabled": True,
|
||||
"gain_reduce_on_saturation": True,
|
||||
"gain_increase_required_cycles": 3,
|
||||
"gain_decrease_required_cycles": 1,
|
||||
"gain_step_up": 0.3,
|
||||
"gain_step_down": 0.5,
|
||||
"gain_hard_reset_on_saturation": False,
|
||||
"exp_high_ratio_for_gain": 0.95,
|
||||
"exp_low_ratio_for_gain_return": 0.75,
|
||||
"role_limits": {
|
||||
"rgb": {"exp_min_us": 100, "exp_max_us": 80000, "gain_min": 1.0, "gain_max": 2.0},
|
||||
"re": {"exp_min_us": 100, "exp_max_us": 3500, "gain_min": 1.0, "gain_max": 2.0},
|
||||
"nir": {"exp_min_us": 100, "exp_max_us": 3500, "gain_min": 1.0, "gain_max": 2.0},
|
||||
},
|
||||
"ready_required_cycles": 3,
|
||||
"patch_control_mode": "gray_primary",
|
||||
"patch_require_order": True,
|
||||
"patch_min_separation": 0.08,
|
||||
"patch_white_sat_limit_pct": 5.0,
|
||||
"patch_white_p95_limit": 0.985,
|
||||
"patch_black_dark_limit_pct": 80.0,
|
||||
"patch_black_max_p50": 0.2,
|
||||
"patch_gray_min_p50": 0.08,
|
||||
"patch_gray_max_p50": 0.85,
|
||||
"patch_roi_contract": "multi_roi_by_role_v1",
|
||||
"patch_roi_reduce_method": "median_valid_rois",
|
||||
"patch_roi_outlier_reject": True,
|
||||
"patch_roi_max_p50_delta": 0.12,
|
||||
"global_saturation_guard_enabled": True,
|
||||
"global_guard_roi_pct": {"x0": 0.05, "y0": 0.05, "x1": 0.95, "y1": 0.76},
|
||||
"global_guard_sat_threshold": 0.985,
|
||||
"global_guard_near_sat_threshold": 0.94,
|
||||
"global_guard_sat_pct_soft": 0.50,
|
||||
"global_guard_sat_pct_hard": 1.5,
|
||||
"global_guard_sat_pct_extreme": 5.0,
|
||||
"global_guard_blob_pct_soft": 0.20,
|
||||
"global_guard_blob_pct_hard": 0.80,
|
||||
"global_guard_blob_pct_extreme": 2.2,
|
||||
"global_guard_min_blob_px": 48,
|
||||
"global_guard_downsample_max_side": 320,
|
||||
"global_guard_reduce_factor_soft": 0.96,
|
||||
"global_guard_reduce_factor_hard": 0.82,
|
||||
"global_guard_reduce_factor_extreme": 0.62,
|
||||
"sun_guard_enabled": True,
|
||||
"sun_guard_p99_threshold": 0.96,
|
||||
"sun_guard_near_sat_pct_threshold": 2.0,
|
||||
"sun_guard_freeze_increase_cycles": 1,
|
||||
"sun_guard_allow_decrease": True,
|
||||
"guard_force_apply_enabled": True,
|
||||
"guard_force_apply_soft": False,
|
||||
"guard_force_apply_hard": True,
|
||||
"guard_force_apply_extreme": True,
|
||||
"guard_force_apply_on_patch_saturation": True,
|
||||
"guard_freeze_cycles_soft": 1,
|
||||
"guard_freeze_cycles_hard": 2,
|
||||
"guard_freeze_cycles_extreme": 3,
|
||||
"guard_reapply_min_exp_on_emergency": True,
|
||||
"guard_min_exp_margin_us": 80,
|
||||
"patch_two_roi_soften_risk": True,
|
||||
"patch_two_roi_white_risk_percentile": 75,
|
||||
"patch_two_roi_other_risk_percentile": 50,
|
||||
"patch_white_single_roi_saturation_reject": True,
|
||||
"patch_white_roi_reject_sat_pct": 5.0,
|
||||
"patch_white_roi_reject_p95": 0.995,
|
||||
}
|
||||
|
||||
|
||||
def default_module_template():
|
||||
return {
|
||||
"schema": "multispec_module_params_v3",
|
||||
"saved_at": now_str(),
|
||||
"frame_type": "RAW_BRUTO",
|
||||
"capture_mode_requested": "AUTO",
|
||||
"capture_mode_effective": "AUTO",
|
||||
"raw_policy": "allow_single",
|
||||
"sensor_width": 1280,
|
||||
"sensor_height": 800,
|
||||
"bayer_pattern": "BGGR",
|
||||
"rgb_processing": deepcopy(DEFAULT_RGB_PROCESSING),
|
||||
"camera_settings": {},
|
||||
"fusion_config": {
|
||||
"alignment_mode": "manual_affine",
|
||||
"baseline_mm": 75.0,
|
||||
"reference_camera": "rgb",
|
||||
"manual_offsets": {
|
||||
"re": {"dx": 0, "dy": 0, "theta_deg": 0.0},
|
||||
"nir": {"dx": 0, "dy": 0, "theta_deg": 0.0},
|
||||
},
|
||||
"homographies": {
|
||||
"re_to_rgb": None,
|
||||
"nir_to_rgb": None,
|
||||
},
|
||||
"crop_valid_common": True,
|
||||
"resize_after_crop": True,
|
||||
"target_size": None,
|
||||
},
|
||||
"radiometric_config": deepcopy(DEFAULT_RADIOMETRIC_CONFIG),
|
||||
"radiometric_normalization": deepcopy(DEFAULT_RADIOMETRIC_NORMALIZATION),
|
||||
"patch_normalization": deepcopy(DEFAULT_PATCH_NORMALIZATION),
|
||||
"rgb_calibration": {
|
||||
"enabled": False,
|
||||
"gains": {"R": 1.0, "G": 1.0, "B": 1.0},
|
||||
},
|
||||
"flatfield_config": deep_merge(
|
||||
{
|
||||
"enabled": False,
|
||||
"reason": "flatfield não informado ou arquivo inexistente",
|
||||
"subtract_dark": True,
|
||||
"apply_before_fusion": True,
|
||||
"apply_after_decode": True,
|
||||
"apply_space": "native_camera_space",
|
||||
"map_type": "gain",
|
||||
"channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"channel_maps": {},
|
||||
"clip_output": True,
|
||||
},
|
||||
DEFAULT_FLATFIELD_RUNTIME,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Builders / normalizers
|
||||
# ============================================================
|
||||
|
||||
|
||||
def build_flatfield_config(flatfield_json_path, flatfield_data, previous_flatfield_config=None):
|
||||
"""
|
||||
Espera o JSON gerado pelo flatfield_calibration_tool_v2.py.
|
||||
|
||||
Importante: usa previous_flatfield_config como base para preservar knobs runtime
|
||||
que não existem no arquivo de calibração do flat-field, como strength,
|
||||
gain_min_runtime, runtime_smooth_ksize e saturation_guard_*.
|
||||
"""
|
||||
base = deep_merge(
|
||||
deep_merge({}, previous_flatfield_config or {}),
|
||||
DEFAULT_FLATFIELD_RUNTIME,
|
||||
)
|
||||
|
||||
if not isinstance(flatfield_data, dict):
|
||||
return deep_merge(base, {
|
||||
"enabled": False,
|
||||
"reason": "flatfield_json ausente ou inválido",
|
||||
})
|
||||
|
||||
outputs = flatfield_data.get("outputs", {}) or {}
|
||||
maps = flatfield_data.get("maps", {}) or {}
|
||||
|
||||
npz_path = outputs.get("npz")
|
||||
if not npz_path:
|
||||
base_name, _ = os.path.splitext(flatfield_json_path)
|
||||
npz_path = base_name + ".npz"
|
||||
|
||||
channels = flatfield_data.get("channels") or base.get("channels") or ["R", "G", "B", "RE", "NIR"]
|
||||
|
||||
channel_maps = {}
|
||||
previous_channel_maps = base.get("channel_maps", {}) or {}
|
||||
for ch in channels:
|
||||
m = maps.get(ch, {}) or {}
|
||||
prev = previous_channel_maps.get(ch, {}) or {}
|
||||
channel_maps[ch] = deep_merge(prev, {
|
||||
"gain_key": m.get("gain_key", f"gain_{ch}"),
|
||||
"flat_norm_key": m.get("flat_norm_key", f"flat_norm_{ch}"),
|
||||
"white_median_key": m.get("white_median_key", f"white_median_{ch}"),
|
||||
"dark_median_key": m.get("dark_median_key", f"dark_median_{ch}"),
|
||||
"shape": m.get("shape"),
|
||||
"gain_min": m.get("gain_min"),
|
||||
"gain_max": m.get("gain_max"),
|
||||
"gain_mean": m.get("gain_mean"),
|
||||
"gain_std": m.get("gain_std"),
|
||||
})
|
||||
|
||||
generated = {
|
||||
"enabled": True,
|
||||
"subtract_dark": True,
|
||||
"schema": flatfield_data.get("schema", "multispec_flatfield_v1"),
|
||||
"created_at": flatfield_data.get("created_at"),
|
||||
"json_file": rel_or_abs(flatfield_json_path),
|
||||
"npz_file": rel_or_abs(npz_path),
|
||||
"apply_before_fusion": True,
|
||||
"apply_after_decode": True,
|
||||
"apply_space": "native_camera_space",
|
||||
"map_type": "gain",
|
||||
"formula": "channel_corrected = max(channel_linear - dark, 0) * gain_map",
|
||||
"channels": channels,
|
||||
"channel_maps": channel_maps,
|
||||
"exp_gain_correct_during_flat_capture": bool(flatfield_data.get("exp_gain_correct", False)),
|
||||
"smooth_ksize": flatfield_data.get("smooth_ksize"),
|
||||
"min_gain": flatfield_data.get("min_gain"),
|
||||
"max_gain": flatfield_data.get("max_gain"),
|
||||
"notes": flatfield_data.get("notes", ""),
|
||||
}
|
||||
|
||||
return deep_merge(base, generated)
|
||||
|
||||
|
||||
def pick_radiometric_config(radiometric_data: dict, selected_profile: str | None = None):
|
||||
if not isinstance(radiometric_data, dict):
|
||||
return None
|
||||
|
||||
root_cfg = radiometric_data.get("radiometric_config")
|
||||
if isinstance(root_cfg, dict):
|
||||
return root_cfg
|
||||
|
||||
active_profile = radiometric_data.get("active_profile")
|
||||
if active_profile in ("global_scene_mode", "three_reference_patches_mode"):
|
||||
cfg = radiometric_data.get(active_profile, {}).get("radiometric_config")
|
||||
if isinstance(cfg, dict):
|
||||
return cfg
|
||||
|
||||
if selected_profile:
|
||||
cfg = radiometric_data.get(selected_profile, {}).get("radiometric_config")
|
||||
if isinstance(cfg, dict):
|
||||
return cfg
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def normalize_patch_normalization_contract(base_patch_config, incoming_patch_config=None):
|
||||
"""
|
||||
Garante o contrato atual do RawProcessorCore.
|
||||
|
||||
- Sempre tem targets_by_patch_channel.
|
||||
- Preserva white_guard_max_by_channel.
|
||||
- Preserva rgb_saturation_guard_*.
|
||||
- Remove a chave legada targets, porque ela não é usada pelo core atual.
|
||||
"""
|
||||
cfg = deep_merge(DEFAULT_PATCH_NORMALIZATION, base_patch_config or {})
|
||||
cfg = deep_merge(cfg, incoming_patch_config or {})
|
||||
|
||||
legacy_targets = cfg.pop("targets", None)
|
||||
if isinstance(legacy_targets, dict) and "targets_by_patch_channel" not in cfg:
|
||||
# Fallback conservador. Na prática, com DEFAULT_PATCH_NORMALIZATION acima,
|
||||
# normalmente não entra aqui. Mantido só para arquivos muito antigos.
|
||||
t = deepcopy(DEFAULT_PATCH_NORMALIZATION["targets_by_patch_channel"])
|
||||
for patch_type in ("black", "gray", "white"):
|
||||
if patch_type in legacy_targets:
|
||||
scalar = legacy_targets.get(patch_type)
|
||||
try:
|
||||
scalar = float(scalar)
|
||||
for ch in ("R", "G", "B", "RE", "NIR"):
|
||||
t[patch_type][ch] = scalar
|
||||
except Exception:
|
||||
pass
|
||||
cfg["targets_by_patch_channel"] = t
|
||||
|
||||
cfg = deep_merge(DEFAULT_PATCH_NORMALIZATION, cfg)
|
||||
return cfg
|
||||
|
||||
|
||||
def normalize_radiometric_config(base_rad_config, incoming_rad_config=None):
|
||||
cfg = deep_merge(DEFAULT_RADIOMETRIC_CONFIG, base_rad_config or {})
|
||||
cfg = deep_merge(cfg, incoming_rad_config or {})
|
||||
return cfg
|
||||
|
||||
|
||||
def normalize_radiometric_normalization(base_config, incoming_config=None):
|
||||
cfg = deep_merge(DEFAULT_RADIOMETRIC_NORMALIZATION, base_config or {})
|
||||
cfg = deep_merge(cfg, incoming_config or {})
|
||||
cfg["enabled"] = bool(cfg.get("enabled", False))
|
||||
return cfg
|
||||
|
||||
|
||||
def build_fusion_config(fusion_data, previous_fusion_config=None):
|
||||
base = previous_fusion_config or {}
|
||||
generated = {
|
||||
"alignment_mode": fusion_data.get("alignment_mode"),
|
||||
"baseline_mm": fusion_data.get("baseline_mm"),
|
||||
"reference_camera": fusion_data.get("reference_camera"),
|
||||
"manual_offsets": fusion_data.get("manual_offsets"),
|
||||
"homographies": fusion_data.get("homographies"),
|
||||
"crop_valid_common": fusion_data.get("crop_valid_common"),
|
||||
"resize_after_crop": fusion_data.get("resize_after_crop"),
|
||||
"target_size": fusion_data.get("target_size"),
|
||||
}
|
||||
cfg = deep_merge(default_module_template()["fusion_config"], base)
|
||||
cfg = deep_merge(cfg, generated)
|
||||
return cfg
|
||||
|
||||
|
||||
def load_base_module(args):
|
||||
"""
|
||||
Carrega defaults do module_params atual.
|
||||
|
||||
Prioridade:
|
||||
1. --base_module_json, se informado.
|
||||
2. --out, se já existir.
|
||||
3. template interno coerente com o contrato atual.
|
||||
"""
|
||||
candidates = []
|
||||
if args.base_module_json:
|
||||
candidates.append(args.base_module_json)
|
||||
if args.out:
|
||||
candidates.append(args.out)
|
||||
|
||||
for path in candidates:
|
||||
if path and os.path.isfile(path):
|
||||
print(f"[INFO] Usando module_params base: {path}")
|
||||
return deep_merge(default_module_template(), load_json(path, required=True))
|
||||
|
||||
print("[WARN] Nenhum module_params base encontrado. Usando defaults internos.")
|
||||
return default_module_template()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Monta o module_params.json preservando o contrato atual do RawProcessorCore.",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument("--camera_json", default="calibration/sensor_calibration.json")
|
||||
parser.add_argument("--fusion_json", default="calibration/manual_offsets.json")
|
||||
parser.add_argument("--radiometric_json", default="calibration/radiometric_config.json")
|
||||
parser.add_argument(
|
||||
"--radiometric_profile",
|
||||
default="global_scene_mode",
|
||||
choices=["global_scene_mode", "three_reference_patches_mode"],
|
||||
)
|
||||
parser.add_argument("--flatfield_json", default="calibration/flatfield_maps_v1.json")
|
||||
parser.add_argument("--disable_flatfield", action="store_true")
|
||||
parser.add_argument("--base_module_json", default=None, help="module_params atual usado como defaults antes de sobrescrever")
|
||||
parser.add_argument("--out", default="calibration/module_params.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
module_base = load_base_module(args)
|
||||
|
||||
cam_data = load_json(args.camera_json, required=True)
|
||||
fusion_data = load_json(args.fusion_json, required=True)
|
||||
radiometric_data = load_json(args.radiometric_json, required=False) if args.radiometric_json else {}
|
||||
|
||||
# =========================
|
||||
# MODULE PARAMS FINAL
|
||||
# =========================
|
||||
module_params = deepcopy(module_base)
|
||||
module_params["schema"] = "multispec_module_params_v3"
|
||||
module_params["saved_at"] = now_str()
|
||||
|
||||
# =========================
|
||||
# ROOT / CAMERA
|
||||
# =========================
|
||||
root_updates = {
|
||||
"frame_type": cam_data.get("frame_type", fusion_data.get("frame_type")),
|
||||
"capture_mode_requested": cam_data.get("capture_mode_requested"),
|
||||
"capture_mode_effective": cam_data.get("capture_mode_effective"),
|
||||
"raw_policy": cam_data.get("raw_policy"),
|
||||
"sensor_width": cam_data.get("sensor_width", fusion_data.get("sensor_width")),
|
||||
"sensor_height": cam_data.get("sensor_height", fusion_data.get("sensor_height")),
|
||||
"bayer_pattern": cam_data.get("bayer_pattern", fusion_data.get("bayer_pattern")),
|
||||
}
|
||||
module_params = deep_merge(module_params, root_updates)
|
||||
|
||||
module_params["rgb_processing"] = deep_merge(
|
||||
deep_merge(DEFAULT_RGB_PROCESSING, module_base.get("rgb_processing", {})),
|
||||
cam_data.get("rgb_processing") if isinstance(cam_data.get("rgb_processing"), dict) else {},
|
||||
)
|
||||
|
||||
camera_settings = cam_data.get("camera_settings")
|
||||
if not isinstance(camera_settings, dict):
|
||||
camera_settings = module_base.get("camera_settings")
|
||||
if not isinstance(camera_settings, dict):
|
||||
raise RuntimeError("camera_json sem camera_settings válido e sem fallback no module_params base")
|
||||
module_params["camera_settings"] = camera_settings
|
||||
|
||||
module_params["rgb_calibration"] = deep_merge(
|
||||
module_base.get("rgb_calibration", {}),
|
||||
cam_data.get("rgb_calibration") if isinstance(cam_data.get("rgb_calibration"), dict) else {},
|
||||
)
|
||||
|
||||
# =========================
|
||||
# FUSION
|
||||
# =========================
|
||||
module_params["fusion_config"] = build_fusion_config(
|
||||
fusion_data,
|
||||
previous_fusion_config=module_base.get("fusion_config", {}),
|
||||
)
|
||||
|
||||
# =========================
|
||||
# RADIOMETRIC
|
||||
# =========================
|
||||
incoming_rad = pick_radiometric_config(radiometric_data, selected_profile=args.radiometric_profile)
|
||||
if not isinstance(incoming_rad, dict):
|
||||
incoming_rad = cam_data.get("radiometric_config") if isinstance(cam_data.get("radiometric_config"), dict) else {}
|
||||
|
||||
module_params["radiometric_config"] = normalize_radiometric_config(
|
||||
module_base.get("radiometric_config", {}),
|
||||
incoming_rad,
|
||||
)
|
||||
|
||||
incoming_rad_norm = first_dict(
|
||||
radiometric_data.get("radiometric_normalization") if isinstance(radiometric_data, dict) else None,
|
||||
cam_data.get("radiometric_normalization") if isinstance(cam_data, dict) else None,
|
||||
) or {}
|
||||
module_params["radiometric_normalization"] = normalize_radiometric_normalization(
|
||||
module_base.get("radiometric_normalization", {}),
|
||||
incoming_rad_norm,
|
||||
)
|
||||
|
||||
incoming_patch_norm = first_dict(
|
||||
radiometric_data.get("patch_normalization") if isinstance(radiometric_data, dict) else None,
|
||||
cam_data.get("patch_normalization") if isinstance(cam_data, dict) else None,
|
||||
) or {}
|
||||
module_params["patch_normalization"] = normalize_patch_normalization_contract(
|
||||
module_base.get("patch_normalization", {}),
|
||||
incoming_patch_norm,
|
||||
)
|
||||
|
||||
# =========================
|
||||
# FLATFIELD
|
||||
# =========================
|
||||
previous_flatfield = module_base.get("flatfield_config", {}) or {}
|
||||
if args.disable_flatfield:
|
||||
module_params["flatfield_config"] = deep_merge(previous_flatfield, {
|
||||
"enabled": False,
|
||||
"reason": "desabilitado via --disable_flatfield",
|
||||
})
|
||||
elif args.flatfield_json and os.path.isfile(args.flatfield_json):
|
||||
flatfield_data = load_json(args.flatfield_json, required=True)
|
||||
module_params["flatfield_config"] = build_flatfield_config(
|
||||
args.flatfield_json,
|
||||
flatfield_data,
|
||||
previous_flatfield_config=previous_flatfield,
|
||||
)
|
||||
else:
|
||||
# Não achou novo flatfield: preserva o anterior se já existia.
|
||||
module_params["flatfield_config"] = deep_merge(previous_flatfield, DEFAULT_FLATFIELD_RUNTIME)
|
||||
if not module_params["flatfield_config"].get("enabled", False):
|
||||
module_params["flatfield_config"]["reason"] = "flatfield não informado ou arquivo inexistente"
|
||||
|
||||
# =========================
|
||||
# Save
|
||||
# =========================
|
||||
save_json(args.out, module_params)
|
||||
|
||||
print(f"[OK] module_params gerado em: {args.out}")
|
||||
print("[OK] contrato preservado: rgb_processing, patch_normalization, radiometric_config e knobs runtime do flatfield")
|
||||
|
||||
flat_cfg = module_params.get("flatfield_config", {}) or {}
|
||||
if flat_cfg.get("enabled"):
|
||||
print(f"[OK] flatfield habilitado: {flat_cfg.get('npz_file')}")
|
||||
else:
|
||||
print(f"[WARN] flatfield desabilitado: {flat_cfg.get('reason')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue