{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"in progress. todo:\n- show actual network and compute loss value","metadata":{}},{"cell_type":"code","source":"comment = '''\n\nreference paper:\n- Numerical Coordinate Regression with Convolutional Neural Networks - Aiden Nibali, ARVIX 2018\nhttps://arxiv.org/abs/1801.07372\n\n\nusage:\n- one ECG image has 13 lead signal series \n- input image 1x3xHxW\n- truth series 1xCxL (C=13)\n- truth_mask 1xCxHxW (C=13)\n\ncoord = torch.arange(H)\ny0 = torch.tensor([13 y0 values)] #zero mv line\npixel_to_mV = 1/(2*39.348837209302324)  \n\n\nprobability = UNET(rectified ECG image)  #softmax per pixel for 13+1 class, 1 is for background\n#probability is 1x(C+1)xHxW\n \nmsel_loss, snr_loss = regression_loss(\n\tprobability, coord, truth_series, \n\ty0,  \n\tpixel_to_mV\n)\njs_loss = regularize_loss(probability, truth_mask)\n\n\n'''","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-04T17:16:45.322434Z","iopub.execute_input":"2025-11-04T17:16:45.322778Z","iopub.status.idle":"2025-11-04T17:16:45.329610Z","shell.execute_reply.started":"2025-11-04T17:16:45.322756Z","shell.execute_reply":"2025-11-04T17:16:45.328587Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport numpy as np\n\n\n# direct regression loss from mask probability\ndef normalize_prob(x, dim=2, eps=1e-8):\n\t# Ensure proper distributions that sum to 1 along `dim`\n\ts = x.sum(dim=dim, keepdim=True)\n\treturn x / (s + eps)\n\ndef js_divergence(p, q, dim=-1, eps=1e-8):\n\t\"\"\"\n\tp, q: probability tensors (already normalized along `dim`)\n\tReturns JS divergence reduced over `dim` (keeps other dims).\n\t\"\"\"\n\tm = 0.5 * (p + q)\n\tkl_pm = (p * (torch.log(p + eps) - torch.log(m + eps))).sum(dim=dim)\n\tkl_qm = (q * (torch.log(q + eps) - torch.log(m + eps))).sum(dim=dim)\n\treturn 0.5 * (kl_pm + kl_qm)\n\n# Jensen-Shannon divergence\ndef regularize_loss(probability, truth): #todo: add mask\n\n\tEPS = 1e-6\n\n\tprobability = probability[:, 1:] #exclude background\n\tB,C,H,W = probability.shape\n\n\tp = normalize_prob(probability, dim=2, eps=EPS)\n\tq = normalize_prob(truth, dim=2, eps=EPS)\n\tjs = js_divergence(p, q, dim=2, eps=EPS)  # summed over `dim`\n\n\tjs_loss = js.mean() #or js.sum()\n\treturn js_loss\n\n# ---------------------------------------------------------\n# todo: mV to ??? to make regression target well-behave at backprop. std(target)=1\ndef regression_loss(\n\tprobability, coord, truth,\n\ty0, #dc pulse\n\tpixel_to_mV\n):\n\t# ECG signal = (y0-y)*pixel_to_mV\n\t# y0 is zero mV line\n\n\tEPS = 1e-6\n\n\tprobability = probability[:, 1:] #exclude background\n\tB,C,L = truth.shape\n\tB,C,H,W = probability.shape\n\tcoord = coord.reshape(1,1,H,1)\n\n\tprobability = probability / (probability.sum(dim=2, keepdim=True) + EPS)\n\tpredict = (y0-(probability*coord).sum(2))*pixel_to_mV  #B,C,W\n\tpredict = F.interpolate(predict, size=L, mode='linear', align_corners=False) #B,C,L\n\tmsel_loss = F.mse_loss(predict, truth)\n\n\t#---\n\t#snr\n\tsignal = (truth)**2\n\tnoise  = (predict-truth)**2\n\tsnr = signal / (noise+EPS)\n\tsnr_db =  10 * torch.log10(snr+EPS)\n\tsnr_loss = -snr_db.mean()\n\n\treturn msel_loss, snr_loss","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}