Skip to content

Commit

Permalink
feat: initial work on status
Browse files Browse the repository at this point in the history
monitor to display detections with smooth animations
  • Loading branch information
stakach committed Feb 13, 2024
1 parent fa2ca6a commit 5ffd579
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 55 deletions.
4 changes: 2 additions & 2 deletions shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ version: 2.0
shards:
action-controller:
git: https://github.com/spider-gazelle/action-controller.git
version: 7.3.0
version: 7.3.1

ameba:
git: https://github.com/veelenga/ameba.git
version: 1.6.0
version: 1.6.1

backtracer:
git: https://github.com/sija/backtracer.cr.git
Expand Down
1 change: 0 additions & 1 deletion shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ dependencies:
# The server framework
action-controller:
github: spider-gazelle/action-controller
version: ~> 7.0

# AI processing
tflite_pipeline:
Expand Down
1 change: 1 addition & 0 deletions src/constants.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module EdgeAI
COOKIE_SESSION_SECRET = ENV["COOKIE_SESSION_SECRET"]? || "4f74c0b358d5bab4000dd3c75465dc2c"

PIPELINE_CONFIG = "./config/config.yml"
PIPELINE_STATUS = "./config/"

def self.running_in_production?
IS_PRODUCTION
Expand Down
23 changes: 7 additions & 16 deletions src/controllers/configuration.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require "./application"

# methods for viewing and updating the configuration of the device
class EdgeAI::Configuration < EdgeAI::Base
base "/api/edge/ai"
base "/api/edge/ai/config"

alias Pipeline = TensorflowLite::Pipeline::Configuration::Pipeline

Expand All @@ -20,13 +20,13 @@ class EdgeAI::Configuration < EdgeAI::Base
end

# view the current configuration
@[AC::Route::GET("/config")]
@[AC::Route::GET("/")]
def index : Array(Pipeline)
PIPELINE_MUTEX.synchronize { PIPELINES.values }
end

# add a new video pipeline
@[AC::Route::POST("/config", body: :pipeline, status_code: HTTP::Status::CREATED)]
@[AC::Route::POST("/", body: :pipeline, status_code: HTTP::Status::CREATED)]
def create(pipeline : Pipeline) : Pipeline
id = UUID.random.to_s
save_config(id) do
Expand All @@ -37,15 +37,15 @@ class EdgeAI::Configuration < EdgeAI::Base
end

# clear the current configurations
@[AC::Route::POST("/config/clear")]
@[AC::Route::POST("/clear")]
def clear_all : Array(String)
ids = PIPELINE_MUTEX.synchronize { PIPELINES.keys }
ids.each { |id| destroy(id) }
ids
end

# view the current configuration
@[AC::Route::GET("/config/:id")]
@[AC::Route::GET("/:id")]
def show(
@[AC::Param::Info(description: "the id of the video stream", example: "ba714f86-cac6-42c7-8956-bcf5105e1b81")]
id : String
Expand All @@ -54,7 +54,7 @@ class EdgeAI::Configuration < EdgeAI::Base
end

# replace the configuration with new configuration
@[AC::Route::PUT("/config/:id", body: :pipeline)]
@[AC::Route::PUT("/:id", body: :pipeline)]
def update(
@[AC::Param::Info(description: "the id of the video stream", example: "ba714f86-cac6-42c7-8956-bcf5105e1b81")]
id : String,
Expand All @@ -71,7 +71,7 @@ class EdgeAI::Configuration < EdgeAI::Base
end

# remove a pipeline from the device
@[AC::Route::DELETE("/config/:id", status_code: HTTP::Status::ACCEPTED)]
@[AC::Route::DELETE("/:id", status_code: HTTP::Status::ACCEPTED)]
def destroy(
@[AC::Param::Info(description: "the id of the video stream", example: "ba714f86-cac6-42c7-8956-bcf5105e1b81")]
id : String
Expand All @@ -94,13 +94,4 @@ class EdgeAI::Configuration < EdgeAI::Base
sockets.each(&.close)
end
end

# this file is built as part of the docker build
OPENAPI = YAML.parse(File.exists?("openapi.yml") ? File.read("openapi.yml") : "{}")

# returns the OpenAPI representation of this service
@[AC::Route::GET("/openapi")]
def openapi : YAML::Any
OPENAPI
end
end
45 changes: 45 additions & 0 deletions src/controllers/status.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require "./application"

class EdgeAI::Status < EdgeAI::Base
base "/api/edge/ai"

# status of all configured pipelines
@[AC::Route::GET("/status")]
def index : Hash(String, PipelineStatus)
keys = Configuration::PIPELINE_MUTEX.synchronize { Configuration::PIPELINES.keys }
status = {} of String => PipelineStatus
keys.each { |id| status[id] = get_status(id) }
status
end

# status of the specified pipeline
@[AC::Route::GET("/status/:id")]
def show(
@[AC::Param::Info(description: "the id of the video stream", example: "ba714f86-cac6-42c7-8956-bcf5105e1b81")]
id : String
) : PipelineStatus
existing = Configuration::PIPELINE_MUTEX.synchronize do
# ensure the stream still exists
Configuration::PIPELINES[id]?
end
raise AC::Error::NotFound.new("stream #{id} was removed") unless existing

get_status id
end

def get_status(id : String) : PipelineStatus
PipelineStatus.from_yaml File.read(File.join(PIPELINE_STATUS, "#{id}.yml"))
rescue error
Log.warn(exception: error) { "issue reading status file" }
PipelineStatus.new(status_available: false)
end

# this file is built as part of the docker build
OPENAPI = YAML.parse(File.exists?("openapi.yml") ? File.read("openapi.yml") : "{}")

# returns the OpenAPI representation of this service
@[AC::Route::GET("/openapi")]
def openapi : YAML::Any
OPENAPI
end
end
8 changes: 4 additions & 4 deletions src/interface.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ module EdgeAI
OptionParser.parse(ARGV.dup) do |parser|
parser.banner = "Usage: #{PROGRAM_NAME} [arguments]"

parser.on("-b HOST", "--bind=HOST", "Specifies the server host") { |h| host = h }
parser.on("-p PORT", "--port=PORT", "Specifies the server port") { |p| port = p.to_i }
parser.on("-b HOST", "--bind=HOST", "Specifies the server host") { |set_host| host = set_host }
parser.on("-p PORT", "--port=PORT", "Specifies the server port") { |set_port| port = set_port.to_i }

parser.on("-w COUNT", "--workers=COUNT", "Specifies the number of processes to handle requests") do |w|
process_count = w.to_i
parser.on("-w COUNT", "--workers=COUNT", "Specifies the number of processes to handle requests") do |workers|
process_count = workers.to_i
end

parser.on("-r", "--routes", "List the application routes") do
Expand Down
18 changes: 18 additions & 0 deletions src/models/pipeline_status.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "json"
require "yaml"

class PipelineStatus
include JSON::Serializable
include YAML::Serializable

# hash of subsystem => issue description
property errors : Hash(String, String) = {} of String => String
property warnings : Hash(String, String) = {} of String => String

property? status_available : Bool = true
property last_updated : Time

def initialize(@status_available = true, @errors = {} of String => String, @warnings = {} of String => String)
@last_updated = Time.utc
end
end
106 changes: 74 additions & 32 deletions www/monitor.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
width: 100%;
height: 100%;
}

#detections > div {
transition: top 0.2s ease, left 0.2s ease, width 0.2s ease, height 0.2s ease;
}
</style>
</head>
<body>
Expand Down Expand Up @@ -160,22 +164,68 @@
rect_height = (rect_height - sizes.letter_box * 2)
rect_width = (rect_width - sizes.pillar_box * 2)

// Clear the container
detections.innerHTML = "";

// Add the FPS Counter
let label = document.createElement("span");
// Set the label style
label.style.position = "absolute";
label.style.backgroundColor = "blue";
label.style.color = "white";
label.style.padding = "2px";
label.style.top = "0";
label.style.right = "0";
// Ensure FPS span exists
has_span = Array.from(detections.children)
.filter(child => child.tagName === 'SPAN');

if (has_span.length === 0) {
let label = document.createElement("span");
// Set the label style
label.style.position = "absolute";
label.style.backgroundColor = "blue";
label.style.color = "white";
label.style.padding = "2px";
label.style.top = "0";
label.style.right = "0";

// Append the box to the container
detections.appendChild(label);
has_span.push(label);
}

// Set the label text
label.textContent = "FPS: " + data.fps.toFixed(2);
// Append the box to the container
detections.appendChild(label);
has_span[0].textContent = "FPS: " + data.fps.toFixed(2);

// get the new ids
const new_detection_ids = Array.from(data.detections)
.filter(detection => detection.type === "object" && detection.score > 0.02)
.map(detection => detection.uuid);

// get existing ids
const existing_ids = Array.from(detections.children)
.filter(child => child.tagName === 'DIV')
.map(div => div.id);

// remove divs that are not part of the update
existing_ids.forEach(id => {
if (!new_detection_ids.includes(id)) {
const elementToRemove = document.getElementById(id);
detections.removeChild(elementToRemove);
}
});

// ensure all the divs exist in the document
new_detection_ids.forEach(id => {
if (!existing_ids.includes(id)) {
// This is a stub for adding a new div, replace with your actual implementation
const box = document.createElement('div');
box.id = id;
box.style.position = "absolute";

// Append the label to the box
let label = document.createElement("span");
label.style.position = "absolute";
label.style.color = "white";
label.style.textShadow = "1px 1px 2px #000000";
label.style.padding = "2px";
label.style.bottom = "100%";
label.style.left = "0";
box.appendChild(label);

// Append the div to the detection element
detections.appendChild(box);
}
});

// Loop through the detections
for (var i = 0; i < data.detections.length; i++) {
Expand All @@ -196,32 +246,24 @@
let bottom = (letter_box + sizes.letter_box) + rect.top + detection.bottom * rect_height;
let right = (pillar_box + sizes.pillar_box) + rect.left + detection.right * rect_width;
// Create a div element for the box
let box = document.createElement("div");
let box = Array.from(detections.children)
.filter(div => div.id === detection.uuid)[0];


// let box = document.createElement("div");
let colour = uuidToWebSafeColor(detection.uuid);
// Set the box style
box.style.position = "absolute";
box.style.border = "2px solid " + colour;
box.style.top = top + "px";
box.style.left = left + "px";
box.style.width = (right - left) + "px";
box.style.height = (bottom - top) + "px";

// Create a span element for the label
label = document.createElement("span");
// Set the label style
label.style.position = "absolute";
label.style.backgroundColor = colour;
label.style.color = "white";
label.style.textShadow = "1px 1px 2px #000000";
label.style.padding = "2px";
label.style.bottom = "100%";
label.style.left = "0";
// Set the label text
// Get the label
// label = document.createElement("span");
label = box.firstElementChild;
label.textContent = detection.label + " (" + detection.score.toFixed(2) + ")";
// Append the label to the box
box.appendChild(label);
// Append the box to the container
detections.appendChild(box);
label.style.backgroundColor = colour;
}
};
</script>
Expand Down

0 comments on commit 5ffd579

Please sign in to comment.