correction du centrage des vecteurs de zone (cercle) pour le plotting
This commit is contained in:
@@ -53,6 +53,7 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
double _targetCenterX = 0.5;
|
double _targetCenterX = 0.5;
|
||||||
double _targetCenterY = 0.5;
|
double _targetCenterY = 0.5;
|
||||||
double _targetRadius = 0.4;
|
double _targetRadius = 0.4;
|
||||||
|
double _targetInnerRadius = 0.04;
|
||||||
int _ringCount = 10;
|
int _ringCount = 10;
|
||||||
List<double>? _ringRadii; // Individual ring radii multipliers
|
List<double>? _ringRadii; // Individual ring radii multipliers
|
||||||
double _imageAspectRatio = 1.0; // width / height
|
double _imageAspectRatio = 1.0; // width / height
|
||||||
@@ -83,6 +84,7 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
double get targetCenterX => _targetCenterX;
|
double get targetCenterX => _targetCenterX;
|
||||||
double get targetCenterY => _targetCenterY;
|
double get targetCenterY => _targetCenterY;
|
||||||
double get targetRadius => _targetRadius;
|
double get targetRadius => _targetRadius;
|
||||||
|
double get targetInnerRadius => _targetInnerRadius;
|
||||||
int get ringCount => _ringCount;
|
int get ringCount => _ringCount;
|
||||||
List<double>? get ringRadii =>
|
List<double>? get ringRadii =>
|
||||||
_ringRadii != null ? List.unmodifiable(_ringRadii!) : null;
|
_ringRadii != null ? List.unmodifiable(_ringRadii!) : null;
|
||||||
@@ -138,6 +140,7 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
_targetCenterX = 0.5;
|
_targetCenterX = 0.5;
|
||||||
_targetCenterY = 0.5;
|
_targetCenterY = 0.5;
|
||||||
_targetRadius = 0.4;
|
_targetRadius = 0.4;
|
||||||
|
_targetInnerRadius = 0.04;
|
||||||
|
|
||||||
// Initialize empty shots list
|
// Initialize empty shots list
|
||||||
_shots = [];
|
_shots = [];
|
||||||
@@ -160,6 +163,7 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
_targetCenterX = result.centerX;
|
_targetCenterX = result.centerX;
|
||||||
_targetCenterY = result.centerY;
|
_targetCenterY = result.centerY;
|
||||||
_targetRadius = result.radius;
|
_targetRadius = result.radius;
|
||||||
|
_targetInnerRadius = result.radius * 0.1;
|
||||||
|
|
||||||
// Create shots from detected impacts
|
// Create shots from detected impacts
|
||||||
_shots = result.impacts.map((impact) {
|
_shots = result.impacts.map((impact) {
|
||||||
@@ -488,12 +492,14 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
void adjustTargetPosition(
|
void adjustTargetPosition(
|
||||||
double centerX,
|
double centerX,
|
||||||
double centerY,
|
double centerY,
|
||||||
|
double innerRadius,
|
||||||
double radius, {
|
double radius, {
|
||||||
int? ringCount,
|
int? ringCount,
|
||||||
List<double>? ringRadii,
|
List<double>? ringRadii,
|
||||||
}) {
|
}) {
|
||||||
_targetCenterX = centerX;
|
_targetCenterX = centerX;
|
||||||
_targetCenterY = centerY;
|
_targetCenterY = centerY;
|
||||||
|
_targetInnerRadius = innerRadius;
|
||||||
_targetRadius = radius;
|
_targetRadius = radius;
|
||||||
if (ringCount != null) {
|
if (ringCount != null) {
|
||||||
_ringCount = ringCount;
|
_ringCount = ringCount;
|
||||||
@@ -520,7 +526,12 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
final result = await _opencvTargetService.detectTarget(_imagePath!);
|
final result = await _opencvTargetService.detectTarget(_imagePath!);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
adjustTargetPosition(result.centerX, result.centerY, result.radius);
|
adjustTargetPosition(
|
||||||
|
result.centerX,
|
||||||
|
result.centerY,
|
||||||
|
result.radius * 0.1,
|
||||||
|
result.radius,
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -687,6 +698,7 @@ class AnalysisProvider extends ChangeNotifier {
|
|||||||
_targetCenterX = 0.5;
|
_targetCenterX = 0.5;
|
||||||
_targetCenterY = 0.5;
|
_targetCenterY = 0.5;
|
||||||
_targetRadius = 0.4;
|
_targetRadius = 0.4;
|
||||||
|
_targetInnerRadius = 0.04;
|
||||||
_ringCount = 10;
|
_ringCount = 10;
|
||||||
_ringRadii = null;
|
_ringRadii = null;
|
||||||
_imageAspectRatio = 1.0;
|
_imageAspectRatio = 1.0;
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Auto-calibrate button
|
// Auto-calibrate button
|
||||||
/*SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -334,7 +334,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),*/
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Ring count slider
|
// Ring count slider
|
||||||
Row(
|
Row(
|
||||||
@@ -361,6 +361,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
provider.adjustTargetPosition(
|
provider.adjustTargetPosition(
|
||||||
provider.targetCenterX,
|
provider.targetCenterX,
|
||||||
provider.targetCenterY,
|
provider.targetCenterY,
|
||||||
|
provider.targetInnerRadius,
|
||||||
provider.targetRadius,
|
provider.targetRadius,
|
||||||
ringCount: value.round(),
|
ringCount: value.round(),
|
||||||
);
|
);
|
||||||
@@ -411,6 +412,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
provider.adjustTargetPosition(
|
provider.adjustTargetPosition(
|
||||||
provider.targetCenterX,
|
provider.targetCenterX,
|
||||||
provider.targetCenterY,
|
provider.targetCenterY,
|
||||||
|
provider.targetInnerRadius,
|
||||||
value,
|
value,
|
||||||
ringCount: provider.ringCount,
|
ringCount: provider.ringCount,
|
||||||
);
|
);
|
||||||
@@ -535,6 +537,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
initialCenterX: provider.targetCenterX,
|
initialCenterX: provider.targetCenterX,
|
||||||
initialCenterY: provider.targetCenterY,
|
initialCenterY: provider.targetCenterY,
|
||||||
initialRadius: provider.targetRadius,
|
initialRadius: provider.targetRadius,
|
||||||
|
initialInnerRadius: provider.targetInnerRadius,
|
||||||
initialRingCount: provider.ringCount,
|
initialRingCount: provider.ringCount,
|
||||||
initialRingRadii: provider.ringRadii,
|
initialRingRadii: provider.ringRadii,
|
||||||
targetType: provider.targetType!,
|
targetType: provider.targetType!,
|
||||||
@@ -542,6 +545,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
(
|
(
|
||||||
centerX,
|
centerX,
|
||||||
centerY,
|
centerY,
|
||||||
|
innerRadius,
|
||||||
radius,
|
radius,
|
||||||
ringCount, {
|
ringCount, {
|
||||||
List<double>? ringRadii,
|
List<double>? ringRadii,
|
||||||
@@ -549,6 +553,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
provider.adjustTargetPosition(
|
provider.adjustTargetPosition(
|
||||||
centerX,
|
centerX,
|
||||||
centerY,
|
centerY,
|
||||||
|
innerRadius,
|
||||||
radius,
|
radius,
|
||||||
ringCount: ringCount,
|
ringCount: ringCount,
|
||||||
ringRadii: ringRadii,
|
ringRadii: ringRadii,
|
||||||
|
|||||||
@@ -13,16 +13,26 @@ class TargetCalibration extends StatefulWidget {
|
|||||||
final double initialCenterX;
|
final double initialCenterX;
|
||||||
final double initialCenterY;
|
final double initialCenterY;
|
||||||
final double initialRadius;
|
final double initialRadius;
|
||||||
|
final double initialInnerRadius;
|
||||||
final int initialRingCount;
|
final int initialRingCount;
|
||||||
final TargetType targetType;
|
final TargetType targetType;
|
||||||
final List<double>? initialRingRadii;
|
final List<double>? initialRingRadii;
|
||||||
final Function(double centerX, double centerY, double radius, int ringCount, {List<double>? ringRadii}) onCalibrationChanged;
|
final Function(
|
||||||
|
double centerX,
|
||||||
|
double centerY,
|
||||||
|
double innerRadius,
|
||||||
|
double radius,
|
||||||
|
int ringCount, {
|
||||||
|
List<double>? ringRadii,
|
||||||
|
})
|
||||||
|
onCalibrationChanged;
|
||||||
|
|
||||||
const TargetCalibration({
|
const TargetCalibration({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialCenterX,
|
required this.initialCenterX,
|
||||||
required this.initialCenterY,
|
required this.initialCenterY,
|
||||||
required this.initialRadius,
|
required this.initialRadius,
|
||||||
|
required this.initialInnerRadius,
|
||||||
this.initialRingCount = 10,
|
this.initialRingCount = 10,
|
||||||
required this.targetType,
|
required this.targetType,
|
||||||
this.initialRingRadii,
|
this.initialRingRadii,
|
||||||
@@ -37,11 +47,13 @@ class _TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
late double _centerX;
|
late double _centerX;
|
||||||
late double _centerY;
|
late double _centerY;
|
||||||
late double _radius;
|
late double _radius;
|
||||||
|
late double _innerRadius;
|
||||||
late int _ringCount;
|
late int _ringCount;
|
||||||
late List<double> _ringRadii;
|
late List<double> _ringRadii;
|
||||||
|
|
||||||
bool _isDraggingCenter = false;
|
bool _isDraggingCenter = false;
|
||||||
bool _isDraggingRadius = false;
|
bool _isDraggingRadius = false;
|
||||||
|
bool _isDraggingInnerRadius = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -49,28 +61,57 @@ class _TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
_centerX = widget.initialCenterX;
|
_centerX = widget.initialCenterX;
|
||||||
_centerY = widget.initialCenterY;
|
_centerY = widget.initialCenterY;
|
||||||
_radius = widget.initialRadius;
|
_radius = widget.initialRadius;
|
||||||
|
_innerRadius = widget.initialInnerRadius;
|
||||||
_ringCount = widget.initialRingCount;
|
_ringCount = widget.initialRingCount;
|
||||||
_initRingRadii();
|
_initRingRadii();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initRingRadii() {
|
void _initRingRadii() {
|
||||||
if (widget.initialRingRadii != null && widget.initialRingRadii!.length == _ringCount) {
|
if (widget.initialRingRadii != null &&
|
||||||
|
widget.initialRingRadii!.length == _ringCount) {
|
||||||
_ringRadii = List.from(widget.initialRingRadii!);
|
_ringRadii = List.from(widget.initialRingRadii!);
|
||||||
} else {
|
} else {
|
||||||
// Initialize with default proportional radii
|
// Initialize with default proportional radii interpolated between inner and outer
|
||||||
_ringRadii = List.generate(_ringCount, (i) => (i + 1) / _ringCount);
|
_ringRadii = List.generate(_ringCount, (i) {
|
||||||
|
if (_ringCount <= 1) return 1.0;
|
||||||
|
final ratio = _innerRadius / _radius;
|
||||||
|
return ratio + (1.0 - ratio) * i / (_ringCount - 1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(TargetCalibration oldWidget) {
|
void didUpdateWidget(TargetCalibration oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
bool shouldReinit = false;
|
||||||
|
|
||||||
|
if (widget.initialCenterX != oldWidget.initialCenterX &&
|
||||||
|
!_isDraggingCenter) {
|
||||||
|
_centerX = widget.initialCenterX;
|
||||||
|
}
|
||||||
|
if (widget.initialCenterY != oldWidget.initialCenterY &&
|
||||||
|
!_isDraggingCenter) {
|
||||||
|
_centerY = widget.initialCenterY;
|
||||||
|
}
|
||||||
if (widget.initialRingCount != oldWidget.initialRingCount) {
|
if (widget.initialRingCount != oldWidget.initialRingCount) {
|
||||||
_ringCount = widget.initialRingCount;
|
_ringCount = widget.initialRingCount;
|
||||||
_initRingRadii();
|
shouldReinit = true;
|
||||||
}
|
}
|
||||||
if (widget.initialRadius != oldWidget.initialRadius && !_isDraggingRadius) {
|
if (widget.initialRadius != oldWidget.initialRadius && !_isDraggingRadius) {
|
||||||
_radius = widget.initialRadius;
|
_radius = widget.initialRadius;
|
||||||
|
shouldReinit = true;
|
||||||
|
}
|
||||||
|
if (widget.initialInnerRadius != oldWidget.initialInnerRadius &&
|
||||||
|
!_isDraggingInnerRadius) {
|
||||||
|
_innerRadius = widget.initialInnerRadius;
|
||||||
|
shouldReinit = true;
|
||||||
|
}
|
||||||
|
if (widget.initialRingRadii != oldWidget.initialRingRadii) {
|
||||||
|
shouldReinit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldReinit) {
|
||||||
|
_initRingRadii();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +131,13 @@ class _TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
centerX: _centerX,
|
centerX: _centerX,
|
||||||
centerY: _centerY,
|
centerY: _centerY,
|
||||||
radius: _radius,
|
radius: _radius,
|
||||||
|
innerRadius: _innerRadius,
|
||||||
ringCount: _ringCount,
|
ringCount: _ringCount,
|
||||||
ringRadii: _ringRadii,
|
ringRadii: _ringRadii,
|
||||||
targetType: widget.targetType,
|
targetType: widget.targetType,
|
||||||
isDraggingCenter: _isDraggingCenter,
|
isDraggingCenter: _isDraggingCenter,
|
||||||
isDraggingRadius: _isDraggingRadius,
|
isDraggingRadius: _isDraggingRadius,
|
||||||
|
isDraggingInnerRadius: _isDraggingInnerRadius,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -109,21 +152,42 @@ class _TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
// Check if tapping on center handle
|
// Check if tapping on center handle
|
||||||
final distToCenter = _distance(tapX, tapY, _centerX, _centerY);
|
final distToCenter = _distance(tapX, tapY, _centerX, _centerY);
|
||||||
|
|
||||||
// Check if tapping on radius handle (on the right edge of the outermost circle)
|
// Check if tapping on outer radius handle
|
||||||
final minDim = math.min(size.width, size.height);
|
final minDim = math.min(size.width, size.height);
|
||||||
final outerRadius = _radius * (_ringRadii.isNotEmpty ? _ringRadii.last : 1.0);
|
final outerRadius = _radius;
|
||||||
final radiusHandleX = _centerX + outerRadius * minDim / size.width;
|
final radiusHandleX = _centerX + outerRadius * minDim / size.width;
|
||||||
final radiusHandleY = _centerY;
|
final radiusHandleY = _centerY;
|
||||||
final distToRadiusHandle = _distance(tapX, tapY, radiusHandleX.clamp(0.0, 1.0), radiusHandleY.clamp(0.0, 1.0));
|
final distToOuterHandle = _distance(
|
||||||
|
tapX,
|
||||||
|
tapY,
|
||||||
|
radiusHandleX.clamp(0.0, 1.0),
|
||||||
|
radiusHandleY.clamp(0.0, 1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if tapping on inner radius handle (top edge of innermost circle)
|
||||||
|
final actualInnerRadius = _innerRadius;
|
||||||
|
final innerHandleX = _centerX;
|
||||||
|
final innerHandleY = _centerY - actualInnerRadius * minDim / size.height;
|
||||||
|
final distToInnerHandle = _distance(
|
||||||
|
tapX,
|
||||||
|
tapY,
|
||||||
|
innerHandleX.clamp(0.0, 1.0),
|
||||||
|
innerHandleY.clamp(0.0, 1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Increase touch target size slightly for handles
|
||||||
if (distToCenter < 0.05) {
|
if (distToCenter < 0.05) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDraggingCenter = true;
|
_isDraggingCenter = true;
|
||||||
});
|
});
|
||||||
} else if (distToRadiusHandle < 0.05) {
|
} else if (distToOuterHandle < 0.05) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDraggingRadius = true;
|
_isDraggingRadius = true;
|
||||||
});
|
});
|
||||||
|
} else if (distToInnerHandle < 0.05) {
|
||||||
|
setState(() {
|
||||||
|
_isDraggingInnerRadius = true;
|
||||||
|
});
|
||||||
} else if (distToCenter < _radius + 0.02) {
|
} else if (distToCenter < _radius + 0.02) {
|
||||||
// Tapping inside the target - move center
|
// Tapping inside the target - move center
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -143,19 +207,36 @@ class _TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
_centerX = _centerX + deltaX;
|
_centerX = _centerX + deltaX;
|
||||||
_centerY = _centerY + deltaY;
|
_centerY = _centerY + deltaY;
|
||||||
} else if (_isDraggingRadius) {
|
} else if (_isDraggingRadius) {
|
||||||
// Adjust outer radius (scales all rings proportionally)
|
// Adjust outer radius
|
||||||
final newRadius = _radius + deltaX * (size.width / minDim);
|
final newRadius = _radius + deltaX * (size.width / minDim);
|
||||||
_radius = newRadius.clamp(0.05, 3.0);
|
_radius = newRadius.clamp(math.max(0.05, _innerRadius + 0.01), 3.0);
|
||||||
|
_initRingRadii(); // Recalculate linear separation
|
||||||
|
} else if (_isDraggingInnerRadius) {
|
||||||
|
// Adjust inner radius (sliding up reduces Y, so deltaY is negative when growing. Thus we subtract deltaY)
|
||||||
|
final newInnerRadius = _innerRadius - deltaY * (size.height / minDim);
|
||||||
|
_innerRadius = newInnerRadius.clamp(
|
||||||
|
0.01,
|
||||||
|
math.max(0.01, _radius - 0.01),
|
||||||
|
);
|
||||||
|
_initRingRadii(); // Recalculate linear separation
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.onCalibrationChanged(_centerX, _centerY, _radius, _ringCount, ringRadii: _ringRadii);
|
widget.onCalibrationChanged(
|
||||||
|
_centerX,
|
||||||
|
_centerY,
|
||||||
|
_innerRadius,
|
||||||
|
_radius,
|
||||||
|
_ringCount,
|
||||||
|
ringRadii: _ringRadii,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPanEnd() {
|
void _onPanEnd() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDraggingCenter = false;
|
_isDraggingCenter = false;
|
||||||
_isDraggingRadius = false;
|
_isDraggingRadius = false;
|
||||||
|
_isDraggingInnerRadius = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,21 +251,25 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
final double centerX;
|
final double centerX;
|
||||||
final double centerY;
|
final double centerY;
|
||||||
final double radius;
|
final double radius;
|
||||||
|
final double innerRadius;
|
||||||
final int ringCount;
|
final int ringCount;
|
||||||
final List<double> ringRadii;
|
final List<double> ringRadii;
|
||||||
final TargetType targetType;
|
final TargetType targetType;
|
||||||
final bool isDraggingCenter;
|
final bool isDraggingCenter;
|
||||||
final bool isDraggingRadius;
|
final bool isDraggingRadius;
|
||||||
|
final bool isDraggingInnerRadius;
|
||||||
|
|
||||||
_CalibrationPainter({
|
_CalibrationPainter({
|
||||||
required this.centerX,
|
required this.centerX,
|
||||||
required this.centerY,
|
required this.centerY,
|
||||||
required this.radius,
|
required this.radius,
|
||||||
|
required this.innerRadius,
|
||||||
required this.ringCount,
|
required this.ringCount,
|
||||||
required this.ringRadii,
|
required this.ringRadii,
|
||||||
required this.targetType,
|
required this.targetType,
|
||||||
required this.isDraggingCenter,
|
required this.isDraggingCenter,
|
||||||
required this.isDraggingRadius,
|
required this.isDraggingRadius,
|
||||||
|
required this.isDraggingInnerRadius,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -192,6 +277,7 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
final centerPx = Offset(centerX * size.width, centerY * size.height);
|
final centerPx = Offset(centerX * size.width, centerY * size.height);
|
||||||
final minDim = size.width < size.height ? size.width : size.height;
|
final minDim = size.width < size.height ? size.width : size.height;
|
||||||
final baseRadiusPx = radius * minDim;
|
final baseRadiusPx = radius * minDim;
|
||||||
|
final innerRadiusPx = innerRadius * minDim;
|
||||||
|
|
||||||
if (targetType == TargetType.concentric) {
|
if (targetType == TargetType.concentric) {
|
||||||
_drawConcentricZones(canvas, size, centerPx, baseRadiusPx);
|
_drawConcentricZones(canvas, size, centerPx, baseRadiusPx);
|
||||||
@@ -199,17 +285,42 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
_drawSilhouetteZones(canvas, size, centerPx, baseRadiusPx);
|
_drawSilhouetteZones(canvas, size, centerPx, baseRadiusPx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fullscreen crosshairs when dragging center
|
||||||
|
if (isDraggingCenter) {
|
||||||
|
final crosshairLinePaint = Paint()
|
||||||
|
..color = AppTheme.successColor.withValues(alpha: 0.5)
|
||||||
|
..strokeWidth = 1;
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(0, centerPx.dy),
|
||||||
|
Offset(size.width, centerPx.dy),
|
||||||
|
crosshairLinePaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(centerPx.dx, 0),
|
||||||
|
Offset(centerPx.dx, size.height),
|
||||||
|
crosshairLinePaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw center handle
|
// Draw center handle
|
||||||
_drawCenterHandle(canvas, centerPx);
|
_drawCenterHandle(canvas, centerPx);
|
||||||
|
|
||||||
// Draw radius handle (for outer ring)
|
// Draw radius handle (for outer ring)
|
||||||
_drawRadiusHandle(canvas, size, centerPx, baseRadiusPx);
|
_drawRadiusHandle(canvas, size, centerPx, baseRadiusPx);
|
||||||
|
|
||||||
|
// Draw inner radius handle
|
||||||
|
_drawInnerRadiusHandle(canvas, size, centerPx, innerRadiusPx);
|
||||||
|
|
||||||
// Draw instructions
|
// Draw instructions
|
||||||
_drawInstructions(canvas, size);
|
_drawInstructions(canvas, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawConcentricZones(Canvas canvas, Size size, Offset center, double baseRadius) {
|
void _drawConcentricZones(
|
||||||
|
Canvas canvas,
|
||||||
|
Size size,
|
||||||
|
Offset center,
|
||||||
|
double baseRadius,
|
||||||
|
) {
|
||||||
// Generate colors for zones
|
// Generate colors for zones
|
||||||
List<Color> zoneColors = [];
|
List<Color> zoneColors = [];
|
||||||
for (int i = 0; i < ringCount; i++) {
|
for (int i = 0; i < ringCount; i++) {
|
||||||
@@ -235,7 +346,9 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
|
|
||||||
// Draw from outside to inside
|
// Draw from outside to inside
|
||||||
for (int i = ringCount - 1; i >= 0; i--) {
|
for (int i = ringCount - 1; i >= 0; i--) {
|
||||||
final ringRadius = ringRadii.length > i ? ringRadii[i] : (i + 1) / ringCount;
|
final ringRadius = ringRadii.length > i
|
||||||
|
? ringRadii[i]
|
||||||
|
: (i + 1) / ringCount;
|
||||||
final zoneRadius = baseRadius * ringRadius;
|
final zoneRadius = baseRadius * ringRadius;
|
||||||
|
|
||||||
zonePaint.color = zoneColors[i];
|
zonePaint.color = zoneColors[i];
|
||||||
@@ -244,12 +357,12 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw zone labels (only if within visible area)
|
// Draw zone labels (only if within visible area)
|
||||||
final textPainter = TextPainter(
|
final textPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (int i = 0; i < ringCount; i++) {
|
for (int i = 0; i < ringCount; i++) {
|
||||||
final ringRadius = ringRadii.length > i ? ringRadii[i] : (i + 1) / ringCount;
|
final ringRadius = ringRadii.length > i
|
||||||
|
? ringRadii[i]
|
||||||
|
: (i + 1) / ringCount;
|
||||||
final prevRingRadius = i > 0
|
final prevRingRadius = i > 0
|
||||||
? (ringRadii.length > i - 1 ? ringRadii[i - 1] : i / ringCount)
|
? (ringRadii.length > i - 1 ? ringRadii[i - 1] : i / ringCount)
|
||||||
: 0.0;
|
: 0.0;
|
||||||
@@ -268,9 +381,7 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
color: Colors.white.withValues(alpha: 0.9),
|
color: Colors.white.withValues(alpha: 0.9),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
shadows: const [
|
shadows: const [Shadow(color: Colors.black, blurRadius: 2)],
|
||||||
Shadow(color: Colors.black, blurRadius: 2),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
textPainter.layout();
|
textPainter.layout();
|
||||||
@@ -278,14 +389,24 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
// Draw label on the right side of each zone
|
// Draw label on the right side of each zone
|
||||||
final labelY = center.dy - textPainter.height / 2;
|
final labelY = center.dy - textPainter.height / 2;
|
||||||
if (labelY >= 0 && labelY <= size.height) {
|
if (labelY >= 0 && labelY <= size.height) {
|
||||||
textPainter.paint(canvas, Offset(labelX - textPainter.width / 2, labelY));
|
textPainter.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(labelX - textPainter.width / 2, labelY),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawSilhouetteZones(Canvas canvas, Size size, Offset center, double radius) {
|
void _drawSilhouetteZones(
|
||||||
|
Canvas canvas,
|
||||||
|
Size size,
|
||||||
|
Offset center,
|
||||||
|
double radius,
|
||||||
|
) {
|
||||||
// Simplified silhouette zones
|
// Simplified silhouette zones
|
||||||
final paint = Paint()..style = PaintingStyle.stroke..strokeWidth = 2;
|
final paint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2;
|
||||||
|
|
||||||
// Draw silhouette outline (simplified as rectangle for now)
|
// Draw silhouette outline (simplified as rectangle for now)
|
||||||
final silhouetteWidth = radius * 0.8;
|
final silhouetteWidth = radius * 0.8;
|
||||||
@@ -293,7 +414,11 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
|
|
||||||
paint.color = Colors.green.withValues(alpha: 0.5);
|
paint.color = Colors.green.withValues(alpha: 0.5);
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
Rect.fromCenter(center: center, width: silhouetteWidth, height: silhouetteHeight),
|
Rect.fromCenter(
|
||||||
|
center: center,
|
||||||
|
width: silhouetteWidth,
|
||||||
|
height: silhouetteHeight,
|
||||||
|
),
|
||||||
paint,
|
paint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -316,17 +441,36 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
final crossPaint = Paint()
|
final crossPaint = Paint()
|
||||||
..color = isDraggingCenter ? AppTheme.successColor : AppTheme.primaryColor
|
..color = isDraggingCenter ? AppTheme.successColor : AppTheme.primaryColor
|
||||||
..strokeWidth = 2;
|
..strokeWidth = 2;
|
||||||
canvas.drawLine(Offset(center.dx - 20, center.dy), Offset(center.dx - 8, center.dy), crossPaint);
|
canvas.drawLine(
|
||||||
canvas.drawLine(Offset(center.dx + 8, center.dy), Offset(center.dx + 20, center.dy), crossPaint);
|
Offset(center.dx - 20, center.dy),
|
||||||
canvas.drawLine(Offset(center.dx, center.dy - 20), Offset(center.dx, center.dy - 8), crossPaint);
|
Offset(center.dx - 8, center.dy),
|
||||||
canvas.drawLine(Offset(center.dx, center.dy + 8), Offset(center.dx, center.dy + 20), crossPaint);
|
crossPaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(center.dx + 8, center.dy),
|
||||||
|
Offset(center.dx + 20, center.dy),
|
||||||
|
crossPaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(center.dx, center.dy - 20),
|
||||||
|
Offset(center.dx, center.dy - 8),
|
||||||
|
crossPaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(center.dx, center.dy + 8),
|
||||||
|
Offset(center.dx, center.dy + 20),
|
||||||
|
crossPaint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawRadiusHandle(Canvas canvas, Size size, Offset center, double baseRadius) {
|
void _drawRadiusHandle(
|
||||||
|
Canvas canvas,
|
||||||
|
Size size,
|
||||||
|
Offset center,
|
||||||
|
double baseRadius,
|
||||||
|
) {
|
||||||
// Radius handle on the right edge of the outermost ring
|
// Radius handle on the right edge of the outermost ring
|
||||||
final outerRingRadius = ringRadii.isNotEmpty ? ringRadii.last : 1.0;
|
final actualHandleX = center.dx + baseRadius;
|
||||||
final actualRadius = baseRadius * outerRingRadius;
|
|
||||||
final actualHandleX = center.dx + actualRadius;
|
|
||||||
final clampedHandleX = actualHandleX.clamp(20.0, size.width - 20);
|
final clampedHandleX = actualHandleX.clamp(20.0, size.width - 20);
|
||||||
final clampedHandleY = center.dy.clamp(20.0, size.height - 20);
|
final clampedHandleY = center.dy.clamp(20.0, size.height - 20);
|
||||||
final handlePos = Offset(clampedHandleX, clampedHandleY);
|
final handlePos = Offset(clampedHandleX, clampedHandleY);
|
||||||
@@ -376,7 +520,7 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
// Label
|
// Label
|
||||||
final textPainter = TextPainter(
|
final textPainter = TextPainter(
|
||||||
text: const TextSpan(
|
text: const TextSpan(
|
||||||
text: 'RAYON',
|
text: 'EXT.',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
@@ -392,6 +536,78 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _drawInnerRadiusHandle(
|
||||||
|
Canvas canvas,
|
||||||
|
Size size,
|
||||||
|
Offset center,
|
||||||
|
double innerRadiusPx,
|
||||||
|
) {
|
||||||
|
// Inner radius handle on the top edge of the innermost ring
|
||||||
|
final actualHandleY = center.dy - innerRadiusPx;
|
||||||
|
final clampedHandleX = center.dx.clamp(20.0, size.width - 20);
|
||||||
|
final clampedHandleY = actualHandleY.clamp(20.0, size.height - 20);
|
||||||
|
final handlePos = Offset(clampedHandleX, clampedHandleY);
|
||||||
|
|
||||||
|
final isClamped = actualHandleY < 20.0;
|
||||||
|
|
||||||
|
final paint = Paint()
|
||||||
|
..color = isDraggingInnerRadius
|
||||||
|
? AppTheme.successColor
|
||||||
|
: (isClamped ? Colors.orange : Colors.purpleAccent)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
// Draw handle
|
||||||
|
canvas.drawCircle(handlePos, 14, paint);
|
||||||
|
|
||||||
|
// Up/Down arrow indicators
|
||||||
|
final arrowPaint = Paint()
|
||||||
|
..color = Colors.white
|
||||||
|
..strokeWidth = 2
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
// Up arrow
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(handlePos.dx, handlePos.dy - 4),
|
||||||
|
Offset(handlePos.dx - 4, handlePos.dy - 8),
|
||||||
|
arrowPaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(handlePos.dx, handlePos.dy - 4),
|
||||||
|
Offset(handlePos.dx + 4, handlePos.dy - 8),
|
||||||
|
arrowPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Down arrow
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(handlePos.dx, handlePos.dy + 4),
|
||||||
|
Offset(handlePos.dx - 4, handlePos.dy + 8),
|
||||||
|
arrowPaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(handlePos.dx, handlePos.dy + 4),
|
||||||
|
Offset(handlePos.dx + 4, handlePos.dy + 8),
|
||||||
|
arrowPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Label
|
||||||
|
final textPainter = TextPainter(
|
||||||
|
text: const TextSpan(
|
||||||
|
text: 'INT.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 8,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
textPainter.layout();
|
||||||
|
textPainter.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(handlePos.dx - textPainter.width / 2, handlePos.dy - 24),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _drawInstructions(Canvas canvas, Size size) {
|
void _drawInstructions(Canvas canvas, Size size) {
|
||||||
const instruction = 'Deplacez le centre ou ajustez le rayon';
|
const instruction = 'Deplacez le centre ou ajustez le rayon';
|
||||||
|
|
||||||
@@ -418,9 +634,11 @@ class _CalibrationPainter extends CustomPainter {
|
|||||||
return centerX != oldDelegate.centerX ||
|
return centerX != oldDelegate.centerX ||
|
||||||
centerY != oldDelegate.centerY ||
|
centerY != oldDelegate.centerY ||
|
||||||
radius != oldDelegate.radius ||
|
radius != oldDelegate.radius ||
|
||||||
|
innerRadius != oldDelegate.innerRadius ||
|
||||||
ringCount != oldDelegate.ringCount ||
|
ringCount != oldDelegate.ringCount ||
|
||||||
isDraggingCenter != oldDelegate.isDraggingCenter ||
|
isDraggingCenter != oldDelegate.isDraggingCenter ||
|
||||||
isDraggingRadius != oldDelegate.isDraggingRadius ||
|
isDraggingRadius != oldDelegate.isDraggingRadius ||
|
||||||
|
isDraggingInnerRadius != oldDelegate.isDraggingInnerRadius ||
|
||||||
ringRadii != oldDelegate.ringRadii;
|
ringRadii != oldDelegate.ringRadii;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ class OpenCVTargetService {
|
|||||||
final circles = cv.HoughCircles(
|
final circles = cv.HoughCircles(
|
||||||
blurred,
|
blurred,
|
||||||
cv.HOUGH_GRADIENT,
|
cv.HOUGH_GRADIENT,
|
||||||
1, // dp: Inverse ratio of the accumulator resolution to the image resolution
|
1, // dp
|
||||||
(img.rows / 8)
|
(img.rows / 16)
|
||||||
.toDouble(), // minDist: Minimum distance between the centers of the detected circles
|
.toDouble(), // minDist decreased to allow more rings in same general area
|
||||||
param1: 100, // param1: Gradient value for Canny edge detection
|
param1: 100, // Canny edge detection
|
||||||
param2:
|
param2:
|
||||||
30, // param2: Accumulator threshold for the circle centers at the detection stage
|
60, // Accumulator threshold (higher = fewer false circles, more accurate)
|
||||||
minRadius: img.cols ~/ 20, // minRadius
|
minRadius: img.cols ~/ 20,
|
||||||
maxRadius: img.cols ~/ 2, // maxRadius
|
maxRadius: img.cols ~/ 2,
|
||||||
);
|
);
|
||||||
|
|
||||||
// HoughCircles returns a Mat of shape (1, N, 3) where N is number of circles.
|
// HoughCircles returns a Mat of shape (1, N, 3) where N is number of circles.
|
||||||
@@ -79,8 +79,8 @@ class OpenCVTargetService {
|
|||||||
cv.HOUGH_GRADIENT,
|
cv.HOUGH_GRADIENT,
|
||||||
1,
|
1,
|
||||||
(img.rows / 8).toDouble(),
|
(img.rows / 8).toDouble(),
|
||||||
param1: 50,
|
param1: 100,
|
||||||
param2: 20,
|
param2: 40,
|
||||||
minRadius: img.cols ~/ 20,
|
minRadius: img.cols ~/ 20,
|
||||||
maxRadius: img.cols ~/ 2,
|
maxRadius: img.cols ~/ 2,
|
||||||
);
|
);
|
||||||
@@ -137,18 +137,22 @@ class OpenCVTargetService {
|
|||||||
for (final circle in detected) {
|
for (final circle in detected) {
|
||||||
bool added = false;
|
bool added = false;
|
||||||
for (final cluster in clusters) {
|
for (final cluster in clusters) {
|
||||||
// Check distance to cluster center (average of existing)
|
// Calculate the actual center of the cluster based on the smallest circle (the likely bullseye)
|
||||||
double clusterX = 0;
|
double clusterCenterX = cluster.first.x;
|
||||||
double clusterY = 0;
|
double clusterCenterY = cluster.first.y;
|
||||||
|
double minRadiusInCluster = cluster.first.r;
|
||||||
|
|
||||||
for (final c in cluster) {
|
for (final c in cluster) {
|
||||||
clusterX += c.x;
|
if (c.r < minRadiusInCluster) {
|
||||||
clusterY += c.y;
|
minRadiusInCluster = c.r;
|
||||||
|
clusterCenterX = c.x;
|
||||||
|
clusterCenterY = c.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clusterX /= cluster.length;
|
|
||||||
clusterY /= cluster.length;
|
|
||||||
|
|
||||||
final dist = math.sqrt(
|
final dist = math.sqrt(
|
||||||
math.pow(circle.x - clusterX, 2) + math.pow(circle.y - clusterY, 2),
|
math.pow(circle.x - clusterCenterX, 2) +
|
||||||
|
math.pow(circle.y - clusterCenterY, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (dist < tolerance) {
|
if (dist < tolerance) {
|
||||||
@@ -171,10 +175,10 @@ class OpenCVTargetService {
|
|||||||
|
|
||||||
for (final cluster in clusters) {
|
for (final cluster in clusters) {
|
||||||
// Score calculation
|
// Score calculation
|
||||||
// Base score = number of circles * 10
|
// Base score = number of circles squared (heavily favor concentric rings)
|
||||||
double score = cluster.length * 10.0;
|
double score = math.pow(cluster.length, 2).toDouble() * 10.0;
|
||||||
|
|
||||||
// Penalize distance from center
|
// Small penalty for distance from center (only as tie-breaker)
|
||||||
double cx = 0, cy = 0;
|
double cx = 0, cy = 0;
|
||||||
for (final c in cluster) {
|
for (final c in cluster) {
|
||||||
cx += c.x;
|
cx += c.x;
|
||||||
@@ -188,7 +192,8 @@ class OpenCVTargetService {
|
|||||||
);
|
);
|
||||||
final relDist = distFromCenter / math.min(width, height);
|
final relDist = distFromCenter / math.min(width, height);
|
||||||
|
|
||||||
score -= relDist * 5.0; // Moderate penalty for off-center
|
score -=
|
||||||
|
relDist * 2.0; // Very minor penalty so we don't snap to screen center
|
||||||
|
|
||||||
// Penalize very small clusters if they are just noise
|
// Penalize very small clusters if they are just noise
|
||||||
// (Optional: check if radii are somewhat distributed?)
|
// (Optional: check if radii are somewhat distributed?)
|
||||||
|
|||||||
Reference in New Issue
Block a user