Skip to content

Commit 889a5d1

Browse files
Merge remote-tracking branch 'upstream' into ai_server
2 parents 1604b54 + 29d3713 commit 889a5d1

File tree

5 files changed

+172
-38
lines changed

5 files changed

+172
-38
lines changed

db/zm_create.sql.in

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -892,14 +892,14 @@ CREATE TABLE `ZonePresets` (
892892
`CheckMethod` enum('AlarmedPixels','FilteredPixels','Blobs') NOT NULL default 'Blobs',
893893
`MinPixelThreshold` smallint(5) unsigned default NULL,
894894
`MaxPixelThreshold` smallint(5) unsigned default NULL,
895-
`MinAlarmPixels` int(10) unsigned default NULL,
896-
`MaxAlarmPixels` int(10) unsigned default NULL,
895+
`MinAlarmPixels` DECIMAL(7,2) unsigned default NULL,
896+
`MaxAlarmPixels` DECIMAL(7,2) unsigned default NULL,
897897
`FilterX` tinyint(3) unsigned default NULL,
898898
`FilterY` tinyint(3) unsigned default NULL,
899-
`MinFilterPixels` int(10) unsigned default NULL,
900-
`MaxFilterPixels` int(10) unsigned default NULL,
901-
`MinBlobPixels` int(10) unsigned default NULL,
902-
`MaxBlobPixels` int(10) unsigned default NULL,
899+
`MinFilterPixels` DECIMAL(7,2) unsigned default NULL,
900+
`MaxFilterPixels` DECIMAL(7,2) unsigned default NULL,
901+
`MinBlobPixels` DECIMAL(7,2) unsigned default NULL,
902+
`MaxBlobPixels` DECIMAL(7,2) unsigned default NULL,
903903
`MinBlobs` smallint(5) unsigned default NULL,
904904
`MaxBlobs` smallint(5) unsigned default NULL,
905905
`OverloadFrames` smallint(5) unsigned NOT NULL default '0',
@@ -926,14 +926,14 @@ CREATE TABLE `Zones` (
926926
`CheckMethod` enum('AlarmedPixels','FilteredPixels','Blobs') NOT NULL default 'Blobs',
927927
`MinPixelThreshold` smallint(5) unsigned default NULL,
928928
`MaxPixelThreshold` smallint(5) unsigned default NULL,
929-
`MinAlarmPixels` int(10) unsigned default NULL,
930-
`MaxAlarmPixels` int(10) unsigned default NULL,
929+
`MinAlarmPixels` DECIMAL(7,2) unsigned default NULL,
930+
`MaxAlarmPixels` DECIMAL(7,2) unsigned default NULL,
931931
`FilterX` tinyint(3) unsigned default NULL,
932932
`FilterY` tinyint(3) unsigned default NULL,
933-
`MinFilterPixels` int(10) unsigned default NULL,
934-
`MaxFilterPixels` int(10) unsigned default NULL,
935-
`MinBlobPixels` int(10) unsigned default NULL,
936-
`MaxBlobPixels` int(10) unsigned default NULL,
933+
`MinFilterPixels` DECIMAL(7,2) unsigned default NULL,
934+
`MaxFilterPixels` DECIMAL(7,2) unsigned default NULL,
935+
`MinBlobPixels` DECIMAL(7,2) unsigned default NULL,
936+
`MaxBlobPixels` DECIMAL(7,2) unsigned default NULL,
937937
`MinBlobs` smallint(5) unsigned default NULL,
938938
`MaxBlobs` smallint(5) unsigned default NULL,
939939
`OverloadFrames` smallint(5) unsigned NOT NULL default '0',

db/zm_update-1.39.3.sql

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,105 @@
1+
-- Convert zone threshold fields (MinAlarmPixels, etc.) from pixel counts
2+
-- to percentages of zone area, matching the coordinate percentage migration
3+
-- done in zm_update-1.39.2.sql.
4+
--
5+
6+
-- First convert existing pixel count values to percentages WHILE columns
7+
-- are still INT (values 0-100 fit in INT; ALTER to DECIMAL would fail on
8+
-- large pixel counts that exceed DECIMAL(7,2) max of 99999.99).
9+
--
10+
-- Zone pixel area = (Zones.Area * Monitors.Width * Monitors.Height) / 10000
11+
-- where Zones.Area is in percentage-space (0-10000) from zm_update-1.39.2.sql.
12+
-- new_percent = old_pixel_count * 100 / zone_pixel_area
13+
-- = old_pixel_count * 1000000 / (Zones.Area * Monitors.Width * Monitors.Height)
14+
--
15+
-- Only convert zones with percentage coordinates (contain '.') that still have
16+
-- pixel-scale threshold values (> 100 means it can't be a percentage).
17+
18+
UPDATE Zones z
19+
JOIN Monitors m ON z.MonitorId = m.Id
20+
SET
21+
z.MinAlarmPixels = CASE
22+
WHEN z.MinAlarmPixels IS NULL THEN NULL
23+
WHEN z.MinAlarmPixels = 0 THEN 0
24+
WHEN z.Area > 0 AND m.Width > 0 AND m.Height > 0
25+
THEN LEAST(ROUND(z.MinAlarmPixels * 1000000.0 / (z.Area * m.Width * m.Height)), 100)
26+
ELSE z.MinAlarmPixels END,
27+
z.MaxAlarmPixels = CASE
28+
WHEN z.MaxAlarmPixels IS NULL THEN NULL
29+
WHEN z.MaxAlarmPixels = 0 THEN 0
30+
WHEN z.Area > 0 AND m.Width > 0 AND m.Height > 0
31+
THEN LEAST(ROUND(z.MaxAlarmPixels * 1000000.0 / (z.Area * m.Width * m.Height)), 100)
32+
ELSE z.MaxAlarmPixels END,
33+
z.MinFilterPixels = CASE
34+
WHEN z.MinFilterPixels IS NULL THEN NULL
35+
WHEN z.MinFilterPixels = 0 THEN 0
36+
WHEN z.Area > 0 AND m.Width > 0 AND m.Height > 0
37+
THEN LEAST(ROUND(z.MinFilterPixels * 1000000.0 / (z.Area * m.Width * m.Height)), 100)
38+
ELSE z.MinFilterPixels END,
39+
z.MaxFilterPixels = CASE
40+
WHEN z.MaxFilterPixels IS NULL THEN NULL
41+
WHEN z.MaxFilterPixels = 0 THEN 0
42+
WHEN z.Area > 0 AND m.Width > 0 AND m.Height > 0
43+
THEN LEAST(ROUND(z.MaxFilterPixels * 1000000.0 / (z.Area * m.Width * m.Height)), 100)
44+
ELSE z.MaxFilterPixels END,
45+
z.MinBlobPixels = CASE
46+
WHEN z.MinBlobPixels IS NULL THEN NULL
47+
WHEN z.MinBlobPixels = 0 THEN 0
48+
WHEN z.Area > 0 AND m.Width > 0 AND m.Height > 0
49+
THEN LEAST(ROUND(z.MinBlobPixels * 1000000.0 / (z.Area * m.Width * m.Height)), 100)
50+
ELSE z.MinBlobPixels END,
51+
z.MaxBlobPixels = CASE
52+
WHEN z.MaxBlobPixels IS NULL THEN NULL
53+
WHEN z.MaxBlobPixels = 0 THEN 0
54+
WHEN z.Area > 0 AND m.Width > 0 AND m.Height > 0
55+
THEN LEAST(ROUND(z.MaxBlobPixels * 1000000.0 / (z.Area * m.Width * m.Height)), 100)
56+
ELSE z.MaxBlobPixels END
57+
WHERE z.Coords LIKE '%.%'
58+
AND (z.MinAlarmPixels > 100 OR z.MaxAlarmPixels > 100
59+
OR z.MinFilterPixels > 100 OR z.MaxFilterPixels > 100
60+
OR z.MinBlobPixels > 100 OR z.MaxBlobPixels > 100);
61+
62+
-- Now change threshold columns from int to DECIMAL(7,2) to store percentages
63+
-- with 2 decimal places (e.g. 25.50 = 25.50% of zone area).
64+
-- Values are now 0-100 from the UPDATE above, so they fit in DECIMAL(7,2).
65+
66+
SET @s = (SELECT IF(
67+
(SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
68+
AND table_name = 'Zones' AND column_name = 'MinAlarmPixels'
69+
) = 'decimal',
70+
"SELECT 'Zones threshold columns already DECIMAL'",
71+
"ALTER TABLE `Zones`
72+
MODIFY `MinAlarmPixels` DECIMAL(7,2) unsigned default NULL,
73+
MODIFY `MaxAlarmPixels` DECIMAL(7,2) unsigned default NULL,
74+
MODIFY `MinFilterPixels` DECIMAL(7,2) unsigned default NULL,
75+
MODIFY `MaxFilterPixels` DECIMAL(7,2) unsigned default NULL,
76+
MODIFY `MinBlobPixels` DECIMAL(7,2) unsigned default NULL,
77+
MODIFY `MaxBlobPixels` DECIMAL(7,2) unsigned default NULL"
78+
));
79+
80+
PREPARE stmt FROM @s;
81+
EXECUTE stmt;
82+
DEALLOCATE PREPARE stmt;
83+
84+
-- Also update ZonePresets table column types for consistency
85+
SET @s = (SELECT IF(
86+
(SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
87+
AND table_name = 'ZonePresets' AND column_name = 'MinAlarmPixels'
88+
) = 'decimal',
89+
"SELECT 'ZonePresets threshold columns already DECIMAL'",
90+
"ALTER TABLE `ZonePresets`
91+
MODIFY `MinAlarmPixels` DECIMAL(7,2) unsigned default NULL,
92+
MODIFY `MaxAlarmPixels` DECIMAL(7,2) unsigned default NULL,
93+
MODIFY `MinFilterPixels` DECIMAL(7,2) unsigned default NULL,
94+
MODIFY `MaxFilterPixels` DECIMAL(7,2) unsigned default NULL,
95+
MODIFY `MinBlobPixels` DECIMAL(7,2) unsigned default NULL,
96+
MODIFY `MaxBlobPixels` DECIMAL(7,2) unsigned default NULL"
97+
));
98+
99+
PREPARE stmt FROM @s;
100+
EXECUTE stmt;
101+
DEALLOCATE PREPARE stmt;
102+
1103
--
2104
-- Add Menu_Items table for customizable navbar/sidebar menu
3105
--

src/zm_zone.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -983,21 +983,21 @@ std::vector<Zone> Zone::Load(const std::shared_ptr<Monitor> &monitor) {
983983
col++;
984984
int MaxPixelThreshold = dbrow[col]?atoi(dbrow[col]):0;
985985
col++;
986-
int MinAlarmPixels = dbrow[col]?atoi(dbrow[col]):0;
986+
double MinAlarmPixels_pct = dbrow[col] ? atof(dbrow[col]) : 0;
987987
col++;
988-
int MaxAlarmPixels = dbrow[col]?atoi(dbrow[col]):0;
988+
double MaxAlarmPixels_pct = dbrow[col] ? atof(dbrow[col]) : 0;
989989
col++;
990990
int FilterX = dbrow[col]?atoi(dbrow[col]):0;
991991
col++;
992992
int FilterY = dbrow[col]?atoi(dbrow[col]):0;
993993
col++;
994-
int MinFilterPixels = dbrow[col]?atoi(dbrow[col]):0;
994+
double MinFilterPixels_pct = dbrow[col] ? atof(dbrow[col]) : 0;
995995
col++;
996-
int MaxFilterPixels = dbrow[col]?atoi(dbrow[col]):0;
996+
double MaxFilterPixels_pct = dbrow[col] ? atof(dbrow[col]) : 0;
997997
col++;
998-
int MinBlobPixels = dbrow[col]?atoi(dbrow[col]):0;
998+
double MinBlobPixels_pct = dbrow[col] ? atof(dbrow[col]) : 0;
999999
col++;
1000-
int MaxBlobPixels = dbrow[col]?atoi(dbrow[col]):0;
1000+
double MaxBlobPixels_pct = dbrow[col] ? atof(dbrow[col]) : 0;
10011001
col++;
10021002
int MinBlobs = dbrow[col]?atoi(dbrow[col]):0;
10031003
col++;
@@ -1031,6 +1031,27 @@ std::vector<Zone> Zone::Load(const std::shared_ptr<Monitor> &monitor) {
10311031
}
10321032
}
10331033

1034+
// Convert threshold values from DB format to pixel counts for runtime use.
1035+
// Percentage coordinates: thresholds are stored as % of zone area, convert to pixels.
1036+
// Legacy pixel coordinates: thresholds are already pixel counts.
1037+
int MinAlarmPixels, MaxAlarmPixels, MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels;
1038+
if (strchr(Coords, '.') && polygon.Area() > 0) {
1039+
int zpa = polygon.Area();
1040+
MinAlarmPixels = MinAlarmPixels_pct > 0 ? static_cast<int>(MinAlarmPixels_pct * zpa / 100.0 + 0.5) : 0;
1041+
MaxAlarmPixels = MaxAlarmPixels_pct > 0 ? static_cast<int>(MaxAlarmPixels_pct * zpa / 100.0 + 0.5) : 0;
1042+
MinFilterPixels = MinFilterPixels_pct > 0 ? static_cast<int>(MinFilterPixels_pct * zpa / 100.0 + 0.5) : 0;
1043+
MaxFilterPixels = MaxFilterPixels_pct > 0 ? static_cast<int>(MaxFilterPixels_pct * zpa / 100.0 + 0.5) : 0;
1044+
MinBlobPixels = MinBlobPixels_pct > 0 ? static_cast<int>(MinBlobPixels_pct * zpa / 100.0 + 0.5) : 0;
1045+
MaxBlobPixels = MaxBlobPixels_pct > 0 ? static_cast<int>(MaxBlobPixels_pct * zpa / 100.0 + 0.5) : 0;
1046+
} else {
1047+
MinAlarmPixels = static_cast<int>(MinAlarmPixels_pct);
1048+
MaxAlarmPixels = static_cast<int>(MaxAlarmPixels_pct);
1049+
MinFilterPixels = static_cast<int>(MinFilterPixels_pct);
1050+
MaxFilterPixels = static_cast<int>(MaxFilterPixels_pct);
1051+
MinBlobPixels = static_cast<int>(MinBlobPixels_pct);
1052+
MaxBlobPixels = static_cast<int>(MaxBlobPixels_pct);
1053+
}
1054+
10341055
if (atoi(dbrow[2]) == Zone::INACTIVE) {
10351056
zones.emplace_back(monitor, Id, Name, polygon);
10361057
} else if (atoi(dbrow[2]) == Zone::PRIVACY) {

web/includes/actions/zone.php

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,9 @@
3131
$zone = array();
3232
}
3333

34-
if ( $_REQUEST['newZone']['Units'] == 'Percent' ) {
35-
// Convert percentage thresholds to pixel counts using actual monitor pixel area
36-
$pixelArea = $monitor->ViewWidth() * $monitor->ViewHeight();
37-
foreach (array(
38-
'MinAlarmPixels','MaxAlarmPixels',
39-
'MinFilterPixels','MaxFilterPixels',
40-
'MinBlobPixels','MaxBlobPixels'
41-
) as $field ) {
42-
if ( isset($_REQUEST['newZone'][$field]) and $_REQUEST['newZone'][$field] )
43-
$_REQUEST['newZone'][$field] = intval(($_REQUEST['newZone'][$field]*$pixelArea)/100);
44-
}
45-
}
34+
// Threshold fields (MinAlarmPixels, etc.) are always submitted as percentages
35+
// of zone area by the JavaScript submitForm() function. If displaying in Pixels
36+
// mode, submitForm() converts back to percentages before submitting.
4637

4738
unset($_REQUEST['newZone']['Points']);
4839

web/skins/classic/views/js/zone.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ function submitForm(form) {
7171
form.elements['newZone[Coords]'].value = getCoordString();
7272
form.elements['newZone[Area]'].value = zone.Area;
7373

74+
// DB stores threshold values as percentages of zone area.
75+
// If displaying in Pixels mode, convert back to percentages before submitting.
76+
if (form.elements['newZone[Units]'].value == 'Pixels') {
77+
var pixelArea = Math.round(zone.Area / monitorArea * monitorPixelArea);
78+
toPercent(form.elements['newZone[MinAlarmPixels]'], pixelArea);
79+
toPercent(form.elements['newZone[MaxAlarmPixels]'], pixelArea);
80+
toPercent(form.elements['newZone[MinFilterPixels]'], pixelArea);
81+
toPercent(form.elements['newZone[MaxFilterPixels]'], pixelArea);
82+
toPercent(form.elements['newZone[MinBlobPixels]'], pixelArea);
83+
toPercent(form.elements['newZone[MaxBlobPixels]'], pixelArea);
84+
}
85+
7486
form.submit();
7587
}
7688

@@ -211,8 +223,8 @@ function toPercent(field, maxValue) {
211223

212224
function applyZoneUnits() {
213225
// zone.Area is in percentage-space (0-10000 for full frame)
214-
// Threshold fields are stored as pixel counts in the DB
215-
// Convert to pixel area for threshold display conversions
226+
// Threshold fields are stored as percentages of zone area in the DB
227+
// pixelArea is zone's actual pixel area, used for converting between display modes
216228
var pixelArea = Math.round(zone.Area / monitorArea * monitorPixelArea);
217229

218230
var form = document.zoneForm;
@@ -237,11 +249,11 @@ function applyZoneUnits() {
237249

238250
function limitRange(field, minValue, maxValue) {
239251
if ( field.value != '' ) {
240-
field.value = constrainValue(
241-
parseFloat(field.value),
242-
parseInt(minValue),
243-
parseInt(maxValue)
244-
);
252+
var currentValue = parseFloat(field.value);
253+
var constrainedValue = constrainValue(currentValue, parseInt(minValue), parseInt(maxValue));
254+
if ( constrainedValue !== currentValue ) {
255+
field.value = constrainedValue;
256+
}
245257
}
246258
}
247259

@@ -669,7 +681,15 @@ function initPage() {
669681
applyZoneType();
670682

671683
if ( form.elements['newZone[Units]'].value == 'Percent' ) {
672-
applyZoneUnits();
684+
// DB stores threshold values as percentages of zone area.
685+
// In Percent mode, values are already correct; just set Area display and field attributes.
686+
form.elements['newZone[Area]'].value = Math.round(zone.Area / monitorArea * 100);
687+
var thresholdFields = ['MinAlarmPixels', 'MaxAlarmPixels', 'MinFilterPixels', 'MaxFilterPixels', 'MinBlobPixels', 'MaxBlobPixels'];
688+
for (var i = 0; i < thresholdFields.length; i++) {
689+
var field = form.elements['newZone[' + thresholdFields[i] + ']'];
690+
field.setAttribute('step', 'any');
691+
field.setAttribute('max', 100);
692+
}
673693
}
674694

675695
applyCheckMethod();

0 commit comments

Comments
 (0)