diff --git a/ai-ecg-master/README.md b/ai-ecg-master/README.md index 66c7021..f373aeb 100644 --- a/ai-ecg-master/README.md +++ b/ai-ecg-master/README.md @@ -21,11 +21,11 @@ In this case, takes as input the raw ECG data (sampled at 200 Hz), highly optimi + pip install pickle + sudo apt install python3-tk -## Setup OpneVINO env +## Setup OpenVINO env source /opt/intel/openvino/bin/setupvars.sh ## Start AI-ECG demo -python ie_ecg_eval.py -d +python ie_ecg_eval.py -d ## Screenshot ![alt text](ecg3.png) diff --git a/ai-ecg-master/ecg_menu.py b/ai-ecg-master/ecg_menu.py index 8ffba5c..fab8ea5 100644 --- a/ai-ecg-master/ecg_menu.py +++ b/ai-ecg-master/ecg_menu.py @@ -18,13 +18,12 @@ def __init__(self, fontsize=14, labelcolor='black', bgcolor='yellow', self.bgcolor_rgb = colors.to_rgba(bgcolor)[:3] class MenuItem(artist.Artist): - parser = mathtext.MathTextParser("Bitmap") padx = 10 - pady = 5 + pady = 10 def __init__(self, fig, labelstr, props=None, hoverprops=None, on_select=None): - artist.Artist.__init__(self) + super().__init__() self.set_figure(fig) self.labelstr = labelstr @@ -40,23 +39,20 @@ def __init__(self, fig, labelstr, props=None, hoverprops=None, self.on_select = on_select - x, self.depth = self.parser.to_mask( - labelstr, fontsize=props.fontsize, dpi=fig.dpi) + # Use fig.text for label rendering (figure coordinates) + # Initial position will be updated in set_extent + self.label = fig.text(0, 0, labelstr, + fontsize=props.fontsize, + color=props.labelcolor, + verticalalignment='top', + horizontalalignment='left', + zorder=10) - if props.fontsize != hoverprops.fontsize: - raise NotImplementedError( - 'support for different font sizes not implemented') + # Estimate label size (approximate) + self.labelwidth = props.fontsize * len(labelstr) // 2 + self.labelheight = props.fontsize + 4 + self.depth = self.labelheight - self.labelwidth = x.shape[1] - self.labelheight = x.shape[0] - - self.labelArray = np.zeros((x.shape[0], x.shape[1], 4)) - self.labelArray[:, :, -1] = x/255. - - self.label = image.FigureImage(fig, origin='upper') - self.label.set_array(self.labelArray) - - # we'll update these later self.rect = patches.Rectangle((0, 0), 1, 1) self.set_hover_props(False) @@ -72,14 +68,23 @@ def check_select(self, event): self.on_select(self) def set_extent(self, x, y, w, h): - #print(x, y, w, h) self.rect.set_x(x) self.rect.set_y(y) self.rect.set_width(w) self.rect.set_height(h) - - self.label.ox = x + self.padx - self.label.oy = y - self.depth + self.pady/2. + self.rect.set_edgecolor('none') + self.rect.set_linewidth(0) + + # Center label on box using normalized figure coordinates + fig = self.figure + fig_width, fig_height = fig.get_size_inches() * fig.dpi + center_x = x + w / 2 + center_y = y + h / 2 + norm_x = center_x / fig_width + norm_y = center_y / fig_height + self.label.set_position((norm_x, norm_y)) + self.label.set_horizontalalignment('center') + self.label.set_verticalalignment('center') self.hover = False @@ -93,11 +98,8 @@ def set_hover_props(self, b): else: props = self.props - r, g, b = props.labelcolor_rgb - self.labelArray[:, :, 0] = r - self.labelArray[:, :, 1] = g - self.labelArray[:, :, 2] = b - self.label.set_array(self.labelArray) + self.label.set_color(props.labelcolor) + self.label.set_fontsize(props.fontsize) self.rect.set(facecolor=props.bgcolor, alpha=props.alpha) def set_hover(self, event): @@ -116,37 +118,61 @@ def set_hover(self, event): class Menu(object): def __init__(self, fig, menuitems, x0, y0): self.figure = fig - fig.suppressComposite = True - self.menuitems = menuitems self.numitems = len(menuitems) - maxw = max(item.labelwidth for item in menuitems) - maxh = max(item.labelheight for item in menuitems) - - totalh = self.numitems*maxh + (self.numitems + 1)*2*MenuItem.pady - - x0 = x0 - y0 = y0 - - width = maxw + 2*MenuItem.padx - height = maxh + MenuItem.pady - - for item in menuitems: - left = x0 - bottom = y0 - maxh - MenuItem.pady - - item.set_extent(left, bottom, width, height) - - fig.artists.append(item) - x0 += maxw + MenuItem.padx + 15 + # Create an overlay axes for the menu bar + self.menu_ax = fig.add_axes([0, 0.94, 1, 0.06], frameon=False) + self.menu_ax.set_xticks([]) + self.menu_ax.set_yticks([]) + self.menu_ax.set_xlim(0, 1) + self.menu_ax.set_ylim(0, 1) + self.menu_ax.set_facecolor('none') + + n = self.numitems + button_width = 1.0 / n + button_height = 1.0 + for i, item in enumerate(menuitems): + left = i * button_width + # Set extent in axes coordinates + x_ax = left + y_ax = 0 + w_ax = button_width + h_ax = button_height + # Draw rectangle and label in menu_ax + rect = patches.Rectangle((x_ax, y_ax), w_ax, h_ax, facecolor=item.props.bgcolor, alpha=item.props.alpha, edgecolor='none', zorder=1) + self.menu_ax.add_patch(rect) + item.rect = rect + label = self.menu_ax.text(x_ax + w_ax/2, y_ax + h_ax/2, item.labelstr, + fontsize=item.props.fontsize, + color=item.props.labelcolor, + ha='center', va='center', zorder=2) + item.label = label + item._menu_ax = self.menu_ax fig.canvas.mpl_connect('motion_notify_event', self.on_move) + fig.canvas.mpl_connect('button_release_event', self.on_click) def on_move(self, event): - draw = False + if event.inaxes != self.menu_ax: + return + for item in self.menuitems: + contains, _ = item.rect.contains(event) + if contains: + item.label.set_color(item.hoverprops.labelcolor) + item.rect.set_facecolor(item.hoverprops.bgcolor) + item.rect.set_alpha(item.hoverprops.alpha) + else: + item.label.set_color(item.props.labelcolor) + item.rect.set_facecolor(item.props.bgcolor) + item.rect.set_alpha(item.props.alpha) + self.figure.canvas.draw_idle() + + def on_click(self, event): + if event.inaxes != self.menu_ax: + return for item in self.menuitems: - draw = item.set_hover(event) - if draw: - self.figure.canvas.draw() + contains, _ = item.rect.contains(event) + if contains and item.on_select: + item.on_select(item) break diff --git a/ai-ecg-master/ie_ecg_eval.py b/ai-ecg-master/ie_ecg_eval.py index f6801ee..342c75f 100644 --- a/ai-ecg-master/ie_ecg_eval.py +++ b/ai-ecg-master/ie_ecg_eval.py @@ -17,7 +17,7 @@ import threading from cpuinfo import get_cpu_info from matplotlib.widgets import Button -from openvino.inference_engine import IENetwork, IECore +from openvino import Core ecg_height = 8960 @@ -128,7 +128,7 @@ def build_argparser(): "kernels implementations.", type=str, default=None) args.add_argument("-pp", "--plugin_dir", help="Optional. Path to a plugin folder", type=str, default=None) args.add_argument("-d", "--device", - help="Optional. Specify the target device to infer on; CPU, GPU, FPGA, HDDL or MYRIAD is " + help="Optional. Specify the target device to infer on; CPU, GPU, FPGA, NPU is " "acceptable. The demo will look for a suitable plugin for device specified. " "Default value is CPU", default="CPU", type=str) args.add_argument("--labels", help="Optional. Path to labels mapping file", default=None, type=str) @@ -140,7 +140,12 @@ def build_argparser(): return parser def on_select(item): + # Remove previous plot axes before rendering a new one + if hasattr(fig, 'last_ax1') and fig.last_ax1 in fig.axes: + fig.delaxes(fig.last_ax1) + fig.last_ax1 = None ax1 = fig.add_subplot(gs[2, :]) + fig.last_ax1 = ax1 ax2 = fig.add_subplot(gs[1, 3]) image = plt.imread("openvino-logo.png") ax2.axis('off') @@ -162,7 +167,8 @@ def on_select(item): log.info("Input ecg file shape: {}".format(input_ecg.shape)) input_ecg_plot = np.squeeze(input_ecg) - + # Clear previous plot and avoid double rendering + ax1.cla() # raw signal plot Fs = 1000 N = len(input_ecg_plot) @@ -182,72 +188,95 @@ def on_select(item): model_bin = os.path.splitext(model_xml)[0] + ".bin" # Plugin initialization for specified device and load extensions library if specified log.info("OpenVINO Initializing plugin for {} device...".format(args.device)) - ie = IECore() + ie = Core() # Read IR log.info("OpenVINO Reading IR...") - net = IENetwork(model=model_xml, weights=model_bin) - assert len(net.inputs.keys()) == 1, "Demo supports only single input topologies" + # NOTE: IENetwork is removed in OpenVINO 2025+, use ie.read_network() + net = ie.read_model(model=model_xml, weights=model_bin) + # NOTE: input/output info via net.inputs/outputs is deprecated, use compiled_model.input()/output() + assert len(net.inputs) == 1, "Demo supports only single input topologies" - #if args.cpu_extension and 'CPU' in args.device: - # ie.add_extension(args.cpu_extension, "CPU") config = { 'PERF_COUNT' : ('YES' if args.perf_counts else 'NO')} device_nstreams = parseValuePerDevice(args.device, None) + # NOTE: set_config API may differ in OpenVINO 2025+, check documentation if needed if ('Async' in (item.labelstr)) and ('CPU' in (args.device)): - ie.set_config({'CPU_THROUGHPUT_STREAMS': str(device_nstreams.get(args.device)) - if args.device in device_nstreams.keys() - else 'CPU_THROUGHPUT_AUTO' }, args.device) - device_nstreams[args.device] = int(ie.get_config(args.device, 'CPU_THROUGHPUT_STREAMS')) + # Check if CPU_THROUGHPUT_STREAMS is supported before setting/getting + supported_props = ie.get_property(args.device, 'SUPPORTED_PROPERTIES') + if 'CPU_THROUGHPUT_STREAMS' in supported_props: + ie.set_property(args.device, {'CPU_THROUGHPUT_STREAMS': str(device_nstreams.get(args.device)) + if args.device in device_nstreams.keys() + else 'CPU_THROUGHPUT_AUTO'}) + device_nstreams[args.device] = int(ie.get_property(args.device, 'CPU_THROUGHPUT_STREAMS')) #prepare input blob - input_blob = next(iter(net.inputs)) + # NOTE: input_blob = compiled_model.input() in OpenVINO 2025+ #load IR to plugin log.info("Loading network with plugin...") - - n, h, w = net.inputs[input_blob].shape - log.info("Network input shape: {}".format(net.inputs[input_blob].shape)) + + # NOTE: Use compile_model instead of load_network if 'Async' in (item.labelstr): - exec_net = ie.load_network(net, - args.device, - config=config, - num_requests=12) - infer_requests = exec_net.requests - request_queue = InferRequestsQueue(infer_requests) + # Async API is different in OpenVINO 2025+, refactor needed for full async support + compiled_model = ie.compile_model(net, args.device, config) + # NOTE: Async requests should use compiled_model.create_infer_request() and callbacks + # For now, fallback to sync for demonstration else: - exec_net = ie.load_network(net,args.device) - output_blob = next(iter(net.outputs)) - del net + compiled_model = ie.compile_model(net, args.device) + + input_blob = compiled_model.input(0) + output_blob = compiled_model.output(0) #Do infer inf_start = time.time() if 'Async' in (item.labelstr): - for i in range(12): - infer_request = request_queue.getIdleRequest() - if not infer_request: - raise Exception("No idle Infer Requests!") - infer_request.startAsync({input_blob: input_ecg}) - request_queue.waitAll() + # NOTE: Async infer API is different, needs refactor for OpenVINO 2025+ + # For demonstration, use sync infer + res = compiled_model([input_ecg]) else: + res = compiled_model([input_ecg]) - res = exec_net.infer({input_blob: input_ecg}) - inf_end = time.time() - + + # NOTE: Output extraction is different if 'Async' in (item.labelstr): det_time = (inf_end - inf_start)/12 - res = exec_net.requests[0].outputs[output_blob] + # res = compiled_model.output(0) # If using async, would need to gather results from requests + res = res[output_blob] else: det_time = inf_end - inf_start res = res[output_blob] - del exec_net + del compiled_model print("[Performance] each inference time:{} ms".format(det_time*1000)) - prediction = sst.mode(np.argmax(res, axis=2).squeeze())[0][0] + + mode_result = sst.mode(np.argmax(res, axis=2).squeeze(), keepdims=True) + if hasattr(mode_result.mode, 'item'): + prediction = mode_result.mode.item() + else: + prediction = mode_result.mode[0] print(prediction) result = preproc.int_to_class[prediction] + # Store previous runs for overlay + if not hasattr(fig, 'run_history'): + fig.run_history = [] + # Color cycle for overlays + overlay_colors = ['b', 'g', 'r', 'm', 'y', 'k', 'c'] + # Append current run to history + fig.run_history.append((ts, input_ecg_plot, item.labelstr)) + # Clear plot only on first run + if len(fig.run_history) == 1: + ax1.cla() + # Overlay all runs + for idx, (ts_run, ecg_run, label_run) in enumerate(fig.run_history): + color = overlay_colors[idx % len(overlay_colors)] + ax1.plot(ts_run, ecg_run, label=label_run, lw=2, color=color) + ax1.set_ylabel('Amplitude') + ax1.set_title("ECG Raw signal: length - {}, Freq - 1000 Hz".format(ecg_h)) + ax1.legend(loc='upper right') + # Only update xlabel for latest run ax1.set_xlabel('File: {}, Intel OpenVINO Infer_perf for each input: {}ms, classification_result: {}'.format(item.labelstr, det_time*1000, result), fontsize=15, color="c", fontweight='bold') ax1.grid() @@ -256,18 +285,24 @@ def on_select(item): if __name__ == '__main__': - + + def handle_close(event): + import sys + import matplotlib.pyplot as plt + plt.close('all') + sys.exit(0) fig = plt.figure(figsize=(15,12)) - fig.suptitle('Select ECG file of The Physionet 2017 Challenge from below list:', color="#009999", fontsize=18, fontweight='bold') + fig.canvas.mpl_connect('close_event', handle_close) + # Move title below the menu bar + fig.text(0.5, 0.92, 'Select ECG file of The Physionet 2017 Challenge from below list:', + color="#009999", fontsize=18, fontweight='bold', ha='center', va='top') widths = [1, 1, 1, 1] - heights = [1, 1, 8, 7] + heights = [1, 1, 6, 2] # Reduce plot height, add info area gs = gridspec.GridSpec(ncols=4, nrows=4, width_ratios=widths, height_ratios=heights, figure=fig) - ax = plt.gca() #Menu - props = ecg_menu.ItemProperties(labelcolor='black', bgcolor='#00cc66', fontsize=15, alpha=0.2) - hoverprops = ecg_menu.ItemProperties(labelcolor='white', bgcolor='#4c0099', - fontsize=15, alpha=0.2) + props = ecg_menu.ItemProperties(labelcolor='white', bgcolor='#66ff99', fontsize=18, alpha=1.0) + hoverprops = ecg_menu.ItemProperties(labelcolor='white', bgcolor='#33cc77', fontsize=18, alpha=1.0) menuitems = [] for label in ('A00001.mat','A00005.mat','A00008.mat','A00022.mat','A00125.mat', 'Async 12 inputs', 'clear'): @@ -275,16 +310,13 @@ def on_select(item): menuitems.append(item) menu = ecg_menu.Menu(fig, menuitems, 50, 1100) - - #The Physionet 2017 Challenge - - + # Info text area below the plot + info_ax = fig.add_subplot(gs[3, :]) + info_ax.axis('off') info = get_cpu_info() t = "CPU info: " +info['brand_raw'] + ", num of core(s): " +str(info['count']) - t1 = ("In this Challenge, we treat all non-AF abnormal rhythms as a single " - "class and require the Challenge entrant to classify the rhythms as:" - ) + "class and require the Challenge entrant to classify the rhythms as:") t2 = ("1) N - Normal sinus rhythm") t3 = ("2) A - Atrial Fibrillation (AF)") t4 = ("3) O - Other rhythm") @@ -292,19 +324,32 @@ def on_select(item): t6 = ("*Algo refer to: Stanford Machine Learning Group ECG classification DNN model") t7 = ("https://stanfordmlgroup.github.io/projects/ecg2/") t8 = ("Demo created by: Zhao, Zhen (Fiona), VMC, IoTG, Intel") - ax.text(.5, .25, t, fontsize=16, style='oblique', ha='center', va='top', wrap=True, color="#0066cc") - ax.text(.5, .2, t1, fontsize=16, style='oblique', ha='center', va='top', wrap=True) - ax.text(.0, .14, t2, fontsize=16, style='oblique', ha='left', va='top', wrap=True) - ax.text(.0, .11, t3, fontsize=16, style='oblique', ha='left', va='top', wrap=True, color="#cc0066") - ax.text(.0, .08, t4, fontsize=16, style='oblique', ha='left', va='top', wrap=True, color="#6600cc") - ax.text(.0, .05, t5, fontsize=16, style='oblique', ha='left', va='top', wrap=True) - ax.text(1, .0, t6, fontsize=10, style='oblique', ha='right', va='top', wrap=True) - ax.text(1, -.03, t7, fontsize=10, style='oblique', ha='right', va='top', wrap=True, color='b') - ax.text(1, -.08, t8, fontsize=12, style='oblique', ha='right', va='top', wrap=True, color='c', fontweight='bold') + # Render info text in the info_ax area with dynamic vertical spacing + info_lines = [ + (t, 16, 'center', 'top', '#0066cc'), + (t1, 16, 'center', 'top', 'black'), + (t2, 16, 'left', 'top', 'black'), + (t3, 16, 'left', 'top', '#cc0066'), + (t4, 16, 'left', 'top', '#6600cc'), + (t5, 16, 'left', 'top', 'black'), + (t6, 10, 'right', 'top', 'black'), + (t7, 10, 'right', 'top', 'b'), + (t8, 12, 'right', 'top', 'c'), + ] + n_lines = len(info_lines) + # Use evenly spaced y positions from top to bottom, with horizontal padding + left_pad = 0.08 + right_pad = 0.92 + for idx, (text, fontsize, ha, va, color) in enumerate(info_lines): + y = 0.95 - idx * (0.9 / (n_lines-1)) # spread lines evenly from y=0.95 to y=0.05 + x = 0.5 if ha=='center' else (left_pad if ha=='left' else right_pad) + info_ax.text(x, y, text, + fontsize=fontsize, style='oblique', ha=ha, va=va, wrap=True, color=color, + fontweight='bold' if idx==8 else 'normal') plt.axis('off') plt.show() - #out = ecg.ecg(signal=input_ecg_plot, sampling_rate=1000., show=True) + #out = ecg.ecg(signal=input_ecg_plot, sampling_rate=1000., show=True) diff --git a/ai-ecg-master/load.py b/ai-ecg-master/load.py index d3f4037..c5c8a18 100644 --- a/ai-ecg-master/load.py +++ b/ai-ecg-master/load.py @@ -1,7 +1,3 @@ -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import - import json import numpy as np import os @@ -44,7 +40,7 @@ def load_ecg(record): elif os.path.splitext(record)[1] == ".mat": ecg = sio.loadmat(record)['val'].squeeze() else: # Assumes binary 16 bit integers - with open(record, 'r') as fid: + with open(record, 'rb') as fid: ecg = np.fromfile(fid, dtype=np.int16) trunc_samp = STEP * int(len(ecg) / STEP) diff --git a/ai-ecg-master/preproc.bin b/ai-ecg-master/preproc.bin index 03c5c09..e871dfd 100644 Binary files a/ai-ecg-master/preproc.bin and b/ai-ecg-master/preproc.bin differ diff --git a/ai-ecg-master/util.py b/ai-ecg-master/util.py index 4ecd4c2..400fba9 100644 --- a/ai-ecg-master/util.py +++ b/ai-ecg-master/util.py @@ -4,10 +4,10 @@ def load(dirname): preproc_f = os.path.join(dirname, "preproc.bin") with open(preproc_f, 'rb') as fid: - preproc = pickle.load(fid, encoding="latin1") + preproc = pickle.load(fid) return preproc def save(preproc, dirname): preproc_f = os.path.join(dirname, "preproc.bin") - with open(preproc_f, 'w') as fid: + with open(preproc_f, 'wb') as fid: pickle.dump(preproc, fid)