diff --git a/GUI_draft_live.py b/GUI_draft_live.py
index 83011b489fa48e972168fed6ae0cb0b6cd934f8f..90089d8827ccae9665dc4797e7fba4e5bba094d0 100644
--- a/GUI_draft_live.py
+++ b/GUI_draft_live.py
@@ -1,5 +1,6 @@
 import sys
 import math
+import csv  # <-- Added
 import numpy as np
 
 # For smoothing the path
@@ -8,14 +9,242 @@ from scipy.signal import savgol_filter
 from PyQt5.QtWidgets import (
     QApplication, QMainWindow, QGraphicsView, QGraphicsScene,
     QGraphicsEllipseItem, QGraphicsPixmapItem, QPushButton,
-    QHBoxLayout, QVBoxLayout, QWidget, QFileDialog, QGraphicsTextItem
+    QHBoxLayout, QVBoxLayout, QWidget, QFileDialog, QGraphicsTextItem,
+    QSlider, QLabel, QCheckBox, QGridLayout, QSizePolicy
 )
-from PyQt5.QtGui import QPixmap, QPen, QBrush, QColor, QFont
-from PyQt5.QtCore import Qt, QRectF
+from PyQt5.QtGui import QPixmap, QPen, QBrush, QColor, QFont, QImage
+from PyQt5.QtCore import Qt, QRectF, QSize
+
+# live_wire.py must contain something like:
+#   from skimage import exposure
+#   from skimage.filters import gaussian
+#   def preprocess_image(image, sigma=3, clip_limit=0.01): ...
+#   def compute_cost_image(path, user_radius, sigma=3, clip_limit=0.01): ...
+#   def find_path(cost_image, points): ...
+#   ...
+from live_wire import compute_cost_image, find_path, preprocess_image
+
+
+# ------------------------------------------------------------------------
+# A pan & zoom QGraphicsView
+# ------------------------------------------------------------------------
+class PanZoomGraphicsView(QGraphicsView):
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.setDragMode(QGraphicsView.NoDrag)  # We'll handle panning manually
+        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
+        self._panning = False
+        self._pan_start = None
+
+        # Let it expand in layouts
+        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+
+    def wheelEvent(self, event):
+        """ Zoom in/out with mouse wheel. """
+        zoom_in_factor = 1.25
+        zoom_out_factor = 1 / zoom_in_factor
+        if event.angleDelta().y() > 0:
+            self.scale(zoom_in_factor, zoom_in_factor)
+        else:
+            self.scale(zoom_out_factor, zoom_out_factor)
+        event.accept()
+
+    def mousePressEvent(self, event):
+        """ If left button: Start panning (unless overridden). """
+        if event.button() == Qt.LeftButton:
+            self._panning = True
+            self._pan_start = event.pos()
+            self.setCursor(Qt.ClosedHandCursor)
+        super().mousePressEvent(event)
+
+    def mouseMoveEvent(self, event):
+        """ If panning, translate the scene. """
+        if self._panning and self._pan_start is not None:
+            delta = event.pos() - self._pan_start
+            self._pan_start = event.pos()
+            self.translate(delta.x(), delta.y())
+        super().mouseMoveEvent(event)
+
+    def mouseReleaseEvent(self, event):
+        """ End panning. """
+        if event.button() == Qt.LeftButton:
+            self._panning = False
+            self.setCursor(Qt.ArrowCursor)
+        super().mouseReleaseEvent(event)
+
 
-from live_wire import compute_cost_image, find_path
+# ------------------------------------------------------------------------
+# A specialized PanZoomGraphicsView for the circle editor
+# ------------------------------------------------------------------------
+class CircleEditorGraphicsView(PanZoomGraphicsView):
+    def __init__(self, circle_editor_widget, parent=None):
+        super().__init__(parent)
+        self._circle_editor_widget = circle_editor_widget
+
+    def mousePressEvent(self, event):
+        if event.button() == Qt.LeftButton:
+            # Check if user clicked on the circle item
+            clicked_item = self.itemAt(event.pos())
+            if clicked_item is not None:
+                # climb up parent chain
+                it = clicked_item
+                while it is not None and not hasattr(it, "boundingRect"):
+                    it = it.parentItem()
+
+                if isinstance(it, DraggableCircleItem):
+                    # Let normal item-dragging occur, no pan
+                    return QGraphicsView.mousePressEvent(self, event)
+        super().mousePressEvent(event)
+
+    def wheelEvent(self, event):
+        """
+        If the mouse is hovering over the circle, we adjust the circle's radius
+        instead of zooming the image.
+        """
+        pos_in_widget = event.pos()
+        item_under = self.itemAt(pos_in_widget)
+        if item_under is not None:
+            it = item_under
+            while it is not None and not hasattr(it, "boundingRect"):
+                it = it.parentItem()
+
+            if isinstance(it, DraggableCircleItem):
+                delta = event.angleDelta().y()
+                step = 1 if delta > 0 else -1
+                old_r = it.radius()
+                new_r = max(1, old_r + step)
+                it.set_radius(new_r)
+                self._circle_editor_widget.update_slider_value(new_r)
+                event.accept()
+                return
 
+        super().wheelEvent(event)
 
+
+# ------------------------------------------------------------------------
+# Draggable circle item (centered at (x, y) with radius)
+# ------------------------------------------------------------------------
+class DraggableCircleItem(QGraphicsEllipseItem):
+    def __init__(self, x, y, radius=20, color=Qt.red, parent=None):
+        super().__init__(0, 0, 2*radius, 2*radius, parent)
+        self._r = radius
+
+        pen = QPen(color)
+        brush = QBrush(color)
+        self.setPen(pen)
+        self.setBrush(brush)
+
+        # Enable item-based dragging
+        self.setFlags(QGraphicsEllipseItem.ItemIsMovable |
+                      QGraphicsEllipseItem.ItemIsSelectable |
+                      QGraphicsEllipseItem.ItemSendsScenePositionChanges)
+
+        # Position so that (x, y) is the center
+        self.setPos(x - radius, y - radius)
+
+    def set_radius(self, r):
+        old_center = self.sceneBoundingRect().center()
+        self._r = r
+        self.setRect(0, 0, 2*r, 2*r)
+        new_center = self.sceneBoundingRect().center()
+        diff_x = old_center.x() - new_center.x()
+        diff_y = old_center.y() - new_center.y()
+        self.moveBy(diff_x, diff_y)
+
+    def radius(self):
+        return self._r
+
+
+# ------------------------------------------------------------------------
+# Circle editor widget with slider + done
+# ------------------------------------------------------------------------
+class CircleEditorWidget(QWidget):
+    def __init__(self, pixmap, init_radius=20, done_callback=None, parent=None):
+        super().__init__(parent)
+        self._pixmap = pixmap
+        self._done_callback = done_callback
+        self._init_radius = init_radius
+
+        layout = QVBoxLayout(self)
+        self.setLayout(layout)
+
+        #
+        # 1) ADD A CENTERED LABEL ABOVE THE IMAGE, WITH BIGGER FONT
+        #
+        label_instructions = QLabel("Scale the dot to be of the size of your ridge")
+        label_instructions.setAlignment(Qt.AlignCenter)
+        big_font = QFont("Arial", 20)
+        big_font.setBold(True)
+        label_instructions.setFont(big_font)
+        layout.addWidget(label_instructions)
+
+        #
+        # 2) THE SPECIALIZED GRAPHICS VIEW THAT SHOWS THE IMAGE
+        #
+        self._graphics_view = CircleEditorGraphicsView(circle_editor_widget=self)
+        self._scene = QGraphicsScene(self)
+        self._graphics_view.setScene(self._scene)
+        layout.addWidget(self._graphics_view)
+
+        # Show the image
+        self._image_item = QGraphicsPixmapItem(self._pixmap)
+        self._scene.addItem(self._image_item)
+
+        # Put circle in center
+        cx = self._pixmap.width() / 2
+        cy = self._pixmap.height() / 2
+        self._circle_item = DraggableCircleItem(cx, cy, radius=self._init_radius, color=Qt.red)
+        self._scene.addItem(self._circle_item)
+
+        # Fit in view
+        self._graphics_view.setSceneRect(QRectF(self._pixmap.rect()))
+        self._graphics_view.fitInView(self._image_item, Qt.KeepAspectRatio)
+
+        #
+        # 3) CONTROLS BELOW
+        #
+        bottom_layout = QHBoxLayout()
+        layout.addLayout(bottom_layout)
+
+        # label + slider
+        self._lbl_size = QLabel(f"size ({self._init_radius})")
+        bottom_layout.addWidget(self._lbl_size)
+
+        self._slider = QSlider(Qt.Horizontal)
+        self._slider.setRange(1, 200)
+        self._slider.setValue(self._init_radius)
+        bottom_layout.addWidget(self._slider)
+
+        # done button
+        self._btn_done = QPushButton("Done")
+        bottom_layout.addWidget(self._btn_done)
+
+        # Connect signals
+        self._slider.valueChanged.connect(self._on_slider_changed)
+        self._btn_done.clicked.connect(self._on_done_clicked)
+
+    def _on_slider_changed(self, value):
+        self._circle_item.set_radius(value)
+        self._lbl_size.setText(f"size ({value})")
+
+    def _on_done_clicked(self):
+        final_radius = self._circle_item.radius()
+        if self._done_callback is not None:
+            self._done_callback(final_radius)
+
+    def update_slider_value(self, new_radius):
+        self._slider.blockSignals(True)
+        self._slider.setValue(new_radius)
+        self._slider.blockSignals(False)
+        self._lbl_size.setText(f"size ({new_radius})")
+
+    def sizeHint(self):
+        return QSize(800, 600)
+
+
+# ------------------------------------------------------------------------
+# Labeled point item
+# ------------------------------------------------------------------------
 class LabeledPointItem(QGraphicsEllipseItem):
     def __init__(self, x, y, label="", radius=4, color=Qt.red, removable=True, z_value=0, parent=None):
         super().__init__(0, 0, 2*radius, 2*radius, parent)
@@ -84,25 +313,23 @@ class LabeledPointItem(QGraphicsEllipseItem):
         return self._removable
 
 
-class ImageGraphicsView(QGraphicsView):
+# ------------------------------------------------------------------------
+# The original ImageGraphicsView with pan & zoom
+# ------------------------------------------------------------------------
+class ImageGraphicsView(PanZoomGraphicsView):
     def __init__(self, parent=None):
         super().__init__(parent)
         self.scene = QGraphicsScene(self)
         self.setScene(self.scene)
 
-        # Zoom around mouse pointer
-        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
-
         # Image display
         self.image_item = QGraphicsPixmapItem()
         self.scene.addItem(self.image_item)
 
         self.anchor_points = []    # List[(x, y)]
-        self.point_items = []      # LabeledPointItem objects
-        self.full_path_points = [] # QGraphicsEllipseItems for the path
-
-        # We'll store the entire path coords (smoothed) for reference
-        self._full_path_xy = []
+        self.point_items = []      # LabeledPointItem
+        self.full_path_points = [] # QGraphicsEllipseItems for path
+        self._full_path_xy = []    # entire path coords (smoothed)
 
         self.dot_radius = 4
         self.path_radius = 1
@@ -110,10 +337,6 @@ class ImageGraphicsView(QGraphicsView):
         self._img_w = 0
         self._img_h = 0
 
-        # Pan/Drag
-        self.setDragMode(QGraphicsView.ScrollHandDrag)
-        self.viewport().setCursor(Qt.ArrowCursor)
-
         self._mouse_pressed = False
         self._press_view_pos = None
         self._drag_threshold = 5
@@ -126,6 +349,29 @@ class ImageGraphicsView(QGraphicsView):
         self.cost_image_original = None
         self.cost_image = None
 
+        # Rainbow toggle => start with OFF
+        self._rainbow_enabled = False
+
+        # Smoothing parameters
+        self._savgol_window_length = 7
+
+    def set_rainbow_enabled(self, enabled: bool):
+        self._rainbow_enabled = enabled
+        self._rebuild_full_path()
+
+    def toggle_rainbow(self):
+        self._rainbow_enabled = not self._rainbow_enabled
+        self._rebuild_full_path()
+
+    def set_savgol_window_length(self, wlen: int):
+        if wlen < 3:
+            wlen = 3
+        if wlen % 2 == 0:
+            wlen += 1
+        self._savgol_window_length = wlen
+
+        self._rebuild_full_path()
+
     # --------------------------------------------------------------------
     # LOADING
     # --------------------------------------------------------------------
@@ -152,18 +398,17 @@ class ImageGraphicsView(QGraphicsView):
     # ANCHOR POINTS
     # --------------------------------------------------------------------
     def _insert_anchor_point(self, idx, x, y, label="", removable=True, z_val=0, radius=4):
-        """Insert anchor at index=idx (or -1 => before E). Clamps x,y to image bounds."""
         x_clamped = self._clamp(x, radius, self._img_w - radius)
         y_clamped = self._clamp(y, radius, self._img_h - radius)
 
         if idx < 0:
+            # Insert before E if there's at least 2 anchors
             if len(self.anchor_points) >= 2:
                 idx = len(self.anchor_points) - 1
             else:
                 idx = len(self.anchor_points)
 
         self.anchor_points.insert(idx, (x_clamped, y_clamped))
-
         color = Qt.green if label in ("S", "E") else Qt.red
         item = LabeledPointItem(x_clamped, y_clamped,
                                 label=label, radius=radius, color=color,
@@ -172,33 +417,28 @@ class ImageGraphicsView(QGraphicsView):
         self.scene.addItem(item)
 
     def _add_guide_point(self, x, y):
-        """User clicked => find the correct sub-path, insert the point in that sub-path."""
+        # Ensure we clamp properly
         x_clamped = self._clamp(x, self.dot_radius, self._img_w - self.dot_radius)
         y_clamped = self._clamp(y, self.dot_radius, self._img_h - self.dot_radius)
 
         self._revert_cost_to_original()
 
         if not self._full_path_xy:
-            # If there's no existing path built, we just insert normally
             self._insert_anchor_point(-1, x_clamped, y_clamped,
                                       label="", removable=True, z_val=1, radius=self.dot_radius)
         else:
-            # Insert the new anchor in between the correct anchors,
-            # by finding nearest coordinate in _full_path_xy, then
-            # walking left+right until we find bounding anchors.
             self._insert_anchor_between_subpath(x_clamped, y_clamped)
 
         self._apply_all_guide_points_to_cost()
         self._rebuild_full_path()
 
     def _insert_anchor_between_subpath(self, x_new, y_new):
-        """Find the subpath bounding (x_new,y_new) and insert the new anchor accordingly."""
-        # If no path, fallback
+        # If somehow we have no path yet
         if not self._full_path_xy:
             self._insert_anchor_point(-1, x_new, y_new)
             return
 
-        # 1) Find nearest coordinate in the path
+        # Find nearest point in the current full path
         best_idx = None
         best_d2 = float('inf')
         for i, (px, py) in enumerate(self._full_path_xy):
@@ -210,26 +450,21 @@ class ImageGraphicsView(QGraphicsView):
                 best_idx = i
 
         if best_idx is None:
-            # fallback
             self._insert_anchor_point(-1, x_new, y_new)
             return
 
-        # 2) Identify bounding anchors by walking left / right
         def approx_equal(xa, ya, xb, yb, tol=1e-3):
             return (abs(xa - xb) < tol) and (abs(ya - yb) < tol)
 
         def is_anchor(coord):
             cx, cy = coord
-            # check if (cx,cy) is approx any anchor in self.anchor_points
             for (ax, ay) in self.anchor_points:
                 if approx_equal(ax, ay, cx, cy):
                     return True
             return False
 
+        # Walk left
         left_anchor_pt = None
-        right_anchor_pt = None
-
-        # walk left
         iL = best_idx
         while iL >= 0:
             px, py = self._full_path_xy[iL]
@@ -238,7 +473,8 @@ class ImageGraphicsView(QGraphicsView):
                 break
             iL -= 1
 
-        # walk right
+        # Walk right
+        right_anchor_pt = None
         iR = best_idx
         while iR < len(self._full_path_xy):
             px, py = self._full_path_xy[iR]
@@ -247,12 +483,16 @@ class ImageGraphicsView(QGraphicsView):
                 break
             iR += 1
 
+        # If we can't find distinct anchors on left & right,
+        # just insert before E.
         if not left_anchor_pt or not right_anchor_pt:
-            # If we can't find bounding anchors => fallback
+            self._insert_anchor_point(-1, x_new, y_new)
+            return
+        if left_anchor_pt == right_anchor_pt:
             self._insert_anchor_point(-1, x_new, y_new)
             return
 
-        # 3) Find which anchor_points indices correspond to left_anchor_pt, right_anchor_pt
+        # Convert anchor coords -> anchor_points indices
         left_idx = None
         right_idx = None
         for i, (ax, ay) in enumerate(self.anchor_points):
@@ -262,23 +502,14 @@ class ImageGraphicsView(QGraphicsView):
                 right_idx = i
 
         if left_idx is None or right_idx is None:
-            # fallback
             self._insert_anchor_point(-1, x_new, y_new)
             return
 
-        # We want the new anchor to be inserted right after left_idx,
-        # so that the subpath between left_idx and right_idx
-        # is effectively subdivided.
-        # This ensures anchor_points = [..., left_anchor, new_point, ..., right_anchor, ...]
-        insert_idx = right_idx
-        # But if left_idx < right_idx => we do insert_idx=left_idx+1
-        # in case we want them consecutive.
+        # Insert between them
         if left_idx < right_idx:
             insert_idx = left_idx + 1
         else:
-            # means the path might be reversed, or there's some tricky indexing
-            # We'll just do min or max
-            insert_idx = max(right_idx, left_idx)
+            insert_idx = right_idx + 1
 
         self._insert_anchor_point(insert_idx, x_new, y_new, label="", removable=True,
                                   z_val=1, radius=self.dot_radius)
@@ -320,8 +551,6 @@ class ImageGraphicsView(QGraphicsView):
     # PATH BUILDING
     # --------------------------------------------------------------------
     def _rebuild_full_path(self):
-        """Compute subpaths between anchors, smooth them, store all coords, display all of them."""
-        # Clear old path visuals
         for item in self.full_path_points:
             self.scene.removeItem(item)
         self.full_path_points.clear()
@@ -338,33 +567,42 @@ class ImageGraphicsView(QGraphicsView):
             if i == 0:
                 big_xy.extend(sub_xy)
             else:
-                # avoid repeating the shared anchor
                 if len(sub_xy) > 1:
                     big_xy.extend(sub_xy[1:])
 
-        # Smooth if we have enough points
-        if len(big_xy) >= 7:
+        if len(big_xy) >= self._savgol_window_length:
             arr_xy = np.array(big_xy)
-            smoothed = savgol_filter(arr_xy, window_length=7, polyorder=1, axis=0)
+            smoothed = savgol_filter(
+                arr_xy,
+                window_length=self._savgol_window_length,
+                polyorder=2,
+                axis=0
+            )
             big_xy = smoothed.tolist()
 
-        # We now store the entire path in _full_path_xy
         self._full_path_xy = big_xy[:]
 
-        # Display ALL points
-        for (px, py) in big_xy:
-            path_item = LabeledPointItem(px, py, label="", radius=self.path_radius,
-                                         color=Qt.magenta, removable=False, z_value=0)
+        n_points = len(big_xy)
+        for i, (px, py) in enumerate(big_xy):
+            fraction = i / (n_points - 1) if n_points > 1 else 0
+            color = Qt.red
+            if self._rainbow_enabled:
+                color = self._rainbow_color(fraction)
+
+            path_item = LabeledPointItem(px, py, label="",
+                                         radius=self.path_radius,
+                                         color=color,
+                                         removable=False,
+                                         z_value=0)
             self.full_path_points.append(path_item)
             self.scene.addItem(path_item)
 
-        # Keep S/E on top
+        # Keep anchor labels on top
         for p_item in self.point_items:
             if p_item._text_item:
                 p_item.setZValue(100)
 
     def _compute_subpath_xy(self, xA, yA, xB, yB):
-        """Return the raw path from (xA,yA)->(xB,yB)."""
         if self.cost_image is None:
             return []
         h, w = self.cost_image.shape
@@ -379,8 +617,15 @@ class ImageGraphicsView(QGraphicsView):
         except ValueError as e:
             print("Error in find_path:", e)
             return []
+        # Convert from (row, col) to (x, y)
         return [(c, r) for (r, c) in path_rc]
 
+    def _rainbow_color(self, fraction):
+        hue = int(300 * fraction)
+        saturation = 255
+        value = 255
+        return QColor.fromHsv(hue, saturation, value)
+
     # --------------------------------------------------------------------
     # MOUSE EVENTS
     # --------------------------------------------------------------------
@@ -390,81 +635,65 @@ class ImageGraphicsView(QGraphicsView):
             self._was_dragging = False
             self._press_view_pos = event.pos()
 
-            # See if user is clicking near an existing anchor => drag it
             idx = self._find_item_near(event.pos(), threshold=10)
             if idx is not None:
                 self._dragging_idx = idx
                 self._drag_counter = 0
-
                 scene_pos = self.mapToScene(event.pos())
                 px, py = self.point_items[idx].get_pos()
                 self._drag_offset = (scene_pos.x() - px, scene_pos.y() - py)
-                self.setDragMode(QGraphicsView.NoDrag)
-                self.viewport().setCursor(Qt.ClosedHandCursor)
+                self.setCursor(Qt.ClosedHandCursor)
                 return
-            else:
-                # no anchor => we'll add a new point
-                self.setDragMode(QGraphicsView.ScrollHandDrag)
-                self.viewport().setCursor(Qt.ClosedHandCursor)
 
         elif event.button() == Qt.RightButton:
-            # Right-click => remove anchor if removable
             self._remove_point_by_click(event.pos())
 
         super().mousePressEvent(event)
 
     def mouseMoveEvent(self, event):
         if self._dragging_idx is not None:
-            # Dragging anchor
             scene_pos = self.mapToScene(event.pos())
             x_new = scene_pos.x() - self._drag_offset[0]
             y_new = scene_pos.y() - self._drag_offset[1]
 
-            # clamp so user can't drag outside
             r = self.point_items[self._dragging_idx]._r
             x_clamped = self._clamp(x_new, r, self._img_w - r)
             y_clamped = self._clamp(y_new, r, self._img_h - r)
             self.point_items[self._dragging_idx].set_pos(x_clamped, y_clamped)
 
             self._drag_counter += 1
+            # Update path every 4 moves
             if self._drag_counter >= 4:
-                # partial path update
                 self._drag_counter = 0
                 self._revert_cost_to_original()
                 self._apply_all_guide_points_to_cost()
                 self.anchor_points[self._dragging_idx] = (x_clamped, y_clamped)
                 self._rebuild_full_path()
-
         else:
             if self._mouse_pressed and (event.buttons() & Qt.LeftButton):
                 dist = (event.pos() - self._press_view_pos).manhattanLength()
                 if dist > self._drag_threshold:
                     self._was_dragging = True
 
-            super().mouseMoveEvent(event)
+        super().mouseMoveEvent(event)
 
     def mouseReleaseEvent(self, event):
         super().mouseReleaseEvent(event)
         if event.button() == Qt.LeftButton and self._mouse_pressed:
             self._mouse_pressed = False
-            self.viewport().setCursor(Qt.ArrowCursor)
+            self.setCursor(Qt.ArrowCursor)
 
             if self._dragging_idx is not None:
-                # finished dragging => final update
                 idx = self._dragging_idx
                 self._dragging_idx = None
                 self._drag_offset = (0, 0)
-                self.setDragMode(QGraphicsView.ScrollHandDrag)
-
                 newX, newY = self.point_items[idx].get_pos()
                 self.anchor_points[idx] = (newX, newY)
-
                 self._revert_cost_to_original()
                 self._apply_all_guide_points_to_cost()
                 self._rebuild_full_path()
-
             else:
-                # If user wasn't dragging => add new guide point
+                # No drag => add point
                 if not self._was_dragging:
                     scene_pos = self.mapToScene(event.pos())
                     x, y = scene_pos.x(), scene_pos.y()
@@ -476,7 +705,6 @@ class ImageGraphicsView(QGraphicsView):
         idx = self._find_item_near(view_pos, threshold=10)
         if idx is None:
             return
-        # skip if S/E
         if not self.point_items[idx].is_removable():
             return
 
@@ -503,18 +731,6 @@ class ImageGraphicsView(QGraphicsView):
             return closest_idx
         return None
 
-    # --------------------------------------------------------------------
-    # ZOOM
-    # --------------------------------------------------------------------
-    def wheelEvent(self, event):
-        zoom_in_factor = 1.25
-        zoom_out_factor = 1 / zoom_in_factor
-        if event.angleDelta().y() > 0:
-            self.scale(zoom_in_factor, zoom_in_factor)
-        else:
-            self.scale(zoom_out_factor, zoom_out_factor)
-        event.accept()
-
     # --------------------------------------------------------------------
     # UTILS
     # --------------------------------------------------------------------
@@ -533,7 +749,6 @@ class ImageGraphicsView(QGraphicsView):
         self._full_path_xy.clear()
 
     def clear_guide_points(self):
-        """Remove all removable anchors, keep S/E. Rebuild path."""
         i = 0
         while i < len(self.anchor_points):
             if self.point_items[i].is_removable():
@@ -553,42 +768,287 @@ class ImageGraphicsView(QGraphicsView):
         self._rebuild_full_path()
 
     def get_full_path_xy(self):
-        """Return the entire path (x,y) array after smoothing."""
         return self._full_path_xy
 
 
+# ------------------------------------------------------------------------
+# Advanced Settings Widget
+# ------------------------------------------------------------------------
+class AdvancedSettingsWidget(QWidget):
+    """
+    Shows toggle rainbow, circle editor, line smoothing slider, contrast slider,
+    plus two image previews (contrasted-blurred and cost).
+    The images should maintain aspect ratio upon resize.
+    """
+    def __init__(self, main_window, parent=None):
+        super().__init__(parent)
+        self._main_window = main_window
+
+        self._last_cb_pix = None   # store QPixmap for contrasted-blurred
+        self._last_cost_pix = None # store QPixmap for cost
+
+        main_layout = QVBoxLayout()
+        self.setLayout(main_layout)
+
+        # A small grid for controls
+        controls_layout = QGridLayout()
+
+        # 1) Rainbow toggle
+        self.btn_toggle_rainbow = QPushButton("Toggle Rainbow")
+        self.btn_toggle_rainbow.clicked.connect(self._on_toggle_rainbow)
+        controls_layout.addWidget(self.btn_toggle_rainbow, 0, 0)
+
+        # 2) Circle editor
+        self.btn_circle_editor = QPushButton("Calibrate Kernel Size")
+        self.btn_circle_editor.clicked.connect(self._main_window.open_circle_editor)
+        controls_layout.addWidget(self.btn_circle_editor, 0, 1)
+
+        # 3) Line smoothing slider + label
+        self._lab_smoothing = QLabel("Line smoothing (3)")
+        controls_layout.addWidget(self._lab_smoothing, 1, 0)
+        self.line_smoothing_slider = QSlider(Qt.Horizontal)
+        self.line_smoothing_slider.setRange(3, 51)
+        self.line_smoothing_slider.setValue(3)
+        self.line_smoothing_slider.valueChanged.connect(self._on_line_smoothing_slider)
+        controls_layout.addWidget(self.line_smoothing_slider, 1, 1)
+
+        # 4) Contrast slider + label
+        self._lab_contrast = QLabel("Contrast (0.01)")
+        controls_layout.addWidget(self._lab_contrast, 2, 0)
+        self.contrast_slider = QSlider(Qt.Horizontal)
+        self.contrast_slider.setRange(1, 20)
+        self.contrast_slider.setValue(1)  # i.e. 0.01
+        self.contrast_slider.setSingleStep(1)
+        self.contrast_slider.valueChanged.connect(self._on_contrast_slider)
+        controls_layout.addWidget(self.contrast_slider, 2, 1)
+
+        main_layout.addLayout(controls_layout)
+
+        # We'll set a minimum width so that the main window expands
+        # rather than overlapping the image
+        self.setMinimumWidth(350)
+
+        # Now a vertical layout for the two images, each with a label above it
+        images_layout = QVBoxLayout()
+
+        # 1) Contrasted-blurred label + image
+        self.label_cb_title = QLabel("Contrasted Blurred Image")
+        self.label_cb_title.setAlignment(Qt.AlignCenter)
+        images_layout.addWidget(self.label_cb_title)
+
+        self.label_contrasted_blurred = QLabel()
+        self.label_contrasted_blurred.setAlignment(Qt.AlignCenter)
+        self.label_contrasted_blurred.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+        images_layout.addWidget(self.label_contrasted_blurred)
+
+        # 2) Cost image label + image
+        self.label_cost_title = QLabel("Current COST IMAGE")
+        self.label_cost_title.setAlignment(Qt.AlignCenter)
+        images_layout.addWidget(self.label_cost_title)
+
+        self.label_cost_image = QLabel()
+        self.label_cost_image.setAlignment(Qt.AlignCenter)
+        self.label_cost_image.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+        images_layout.addWidget(self.label_cost_image)
+
+        main_layout.addLayout(images_layout)
+
+    def showEvent(self, event):
+        """ When shown, ask parent to resize to accommodate. """
+        super().showEvent(event)
+        if self.parentWidget():
+            self.parentWidget().adjustSize()
+
+    def resizeEvent(self, event):
+        """
+        Keep the images at correct aspect ratio by re-scaling
+        our stored pixmaps to the new label sizes.
+        """
+        super().resizeEvent(event)
+        self._update_labels()
+
+    def _update_labels(self):
+        if self._last_cb_pix is not None:
+            scaled_cb = self._last_cb_pix.scaled(
+                self.label_contrasted_blurred.size(),
+                Qt.KeepAspectRatio,
+                Qt.SmoothTransformation
+            )
+            self.label_contrasted_blurred.setPixmap(scaled_cb)
+
+        if self._last_cost_pix is not None:
+            scaled_cost = self._last_cost_pix.scaled(
+                self.label_cost_image.size(),
+                Qt.KeepAspectRatio,
+                Qt.SmoothTransformation
+            )
+            self.label_cost_image.setPixmap(scaled_cost)
+
+    def _on_toggle_rainbow(self):
+        self._main_window.toggle_rainbow()
+
+    def _on_line_smoothing_slider(self, value):
+        self._lab_smoothing.setText(f"Line smoothing ({value})")
+        self._main_window.image_view.set_savgol_window_length(value)
+
+    def _on_contrast_slider(self, value):
+        clip_limit = value / 100.0
+        self._lab_contrast.setText(f"Contrast ({clip_limit:.2f})")
+        self._main_window.update_contrast(clip_limit)
+
+    def update_displays(self, contrasted_img_np, cost_img_np):
+        """
+        Called by main_window to refresh the two images in the advanced panel.
+        We'll store them as QPixmaps, then do the re-scale in _update_labels().
+        """
+        cb_pix = self._np_array_to_qpixmap(contrasted_img_np)
+        cost_pix = self._np_array_to_qpixmap(cost_img_np, normalize=True)
+
+        self._last_cb_pix = cb_pix
+        self._last_cost_pix = cost_pix
+        self._update_labels()
+
+    def _np_array_to_qpixmap(self, arr, normalize=False):
+        if arr is None:
+            return None
+        arr_ = arr.copy()
+        if normalize:
+            mn, mx = arr_.min(), arr_.max()
+            if abs(mx - mn) < 1e-12:
+                arr_[:] = 0
+            else:
+                arr_ = (arr_ - mn) / (mx - mn)
+        arr_ = np.clip(arr_, 0, 1)
+        arr_255 = (arr_ * 255).astype(np.uint8)
+
+        h, w = arr_255.shape
+        qimage = QImage(arr_255.data, w, h, w, QImage.Format_Grayscale8)
+        return QPixmap.fromImage(qimage)
+
+
+# ------------------------------------------------------------------------
+# Main Window
+# ------------------------------------------------------------------------
 class MainWindow(QMainWindow):
     def __init__(self):
         super().__init__()
         self.setWindowTitle("Test GUI")
 
-        main_widget = QWidget()
-        main_layout = QVBoxLayout(main_widget)
+        self._last_loaded_pixmap = None
+        self._circle_calibrated_radius = 6
+        self._last_loaded_file_path = None
+
+        # For the contrast slider
+        self._current_clip_limit = 0.01
+
+        # Outer widget + layout
+        self._main_widget = QWidget()
+        self._main_layout = QHBoxLayout(self._main_widget)
+
+        # The "left" part: container for the image area + its controls
+        self._left_panel = QVBoxLayout()
+
+        # We'll make a container widget for the left panel, so we can set stretches:
+        self._left_container = QWidget()
+        self._left_container.setLayout(self._left_panel)
+
+        # Now we add them to the main layout with 70%:30% ratio
+        self._main_layout.addWidget(self._left_container, 7)  # 70%
+        
+        # We haven't added the advanced widget yet, but we'll do so with ratio=3 => 30%
+        self._advanced_widget = AdvancedSettingsWidget(self)
+        # Hide it initially
+        self._advanced_widget.hide()
+        self._main_layout.addWidget(self._advanced_widget, 3)
 
+        self.setCentralWidget(self._main_widget)
+
+        # The image view
         self.image_view = ImageGraphicsView()
-        main_layout.addWidget(self.image_view)
+        self._left_panel.addWidget(self.image_view)
 
-        # Buttons layout
+        # Button row
         btn_layout = QHBoxLayout()
-
-        # Load Image
         self.btn_load_image = QPushButton("Load Image")
         self.btn_load_image.clicked.connect(self.load_image)
         btn_layout.addWidget(self.btn_load_image)
 
-        # Export Path
         self.btn_export_path = QPushButton("Export Path")
         self.btn_export_path.clicked.connect(self.export_path)
         btn_layout.addWidget(self.btn_export_path)
 
-        # Clear Points
         self.btn_clear_points = QPushButton("Clear Points")
         self.btn_clear_points.clicked.connect(self.clear_points)
         btn_layout.addWidget(self.btn_clear_points)
 
-        main_layout.addLayout(btn_layout)
-        self.setCentralWidget(main_widget)
-        self.resize(900, 600)
+        # "Advanced Settings" toggle
+        self.btn_advanced = QPushButton("Advanced Settings")
+        self.btn_advanced.setCheckable(True)
+        self.btn_advanced.clicked.connect(self._toggle_advanced_settings)
+        btn_layout.addWidget(self.btn_advanced)
+
+        self._left_panel.addLayout(btn_layout)
+
+        self.resize(1000, 600)
+        self._old_central_widget = None
+        self._editor = None
+
+    def _toggle_advanced_settings(self, checked):
+        if checked:
+            self._advanced_widget.show()
+        else:
+            self._advanced_widget.hide()
+        # Force re-layout
+        self.adjustSize()
+
+    def open_circle_editor(self):
+        """ Replace central widget with circle editor. """
+        if not self._last_loaded_pixmap:
+            print("No image loaded yet! Cannot open circle editor.")
+            return
+
+        old_widget = self.takeCentralWidget()
+        self._old_central_widget = old_widget
+
+        init_radius = self._circle_calibrated_radius
+        editor = CircleEditorWidget(
+            pixmap=self._last_loaded_pixmap,
+            init_radius=init_radius,
+            done_callback=self._on_circle_editor_done
+        )
+        self._editor = editor
+        self.setCentralWidget(editor)
+
+    def _on_circle_editor_done(self, final_radius):
+        self._circle_calibrated_radius = final_radius
+        print(f"Circle Editor done. Radius = {final_radius}")
+
+        if self._last_loaded_file_path:
+            cost_img = compute_cost_image(
+                self._last_loaded_file_path,
+                self._circle_calibrated_radius,
+                clip_limit=self._current_clip_limit
+            )
+            self.image_view.cost_image_original = cost_img
+            self.image_view.cost_image = cost_img.copy()
+            self.image_view._apply_all_guide_points_to_cost()
+            self.image_view._rebuild_full_path()
+            self._update_advanced_images()
+
+        editor_widget = self.takeCentralWidget()
+        if editor_widget is not None:
+            editor_widget.setParent(None)
+
+        if self._old_central_widget is not None:
+            self.setCentralWidget(self._old_central_widget)
+            self._old_central_widget = None
+
+        if self._editor is not None:
+            self._editor.deleteLater()
+            self._editor = None
+
+    def toggle_rainbow(self):
+        self.image_view.toggle_rainbow()
 
     def load_image(self):
         options = QFileDialog.Options()
@@ -599,27 +1059,109 @@ class MainWindow(QMainWindow):
         )
         if file_path:
             self.image_view.load_image(file_path)
-            cost_img = compute_cost_image(file_path)
+
+            cost_img = compute_cost_image(
+                file_path,
+                self._circle_calibrated_radius,
+                clip_limit=self._current_clip_limit
+            )
+            self.image_view.cost_image_original = cost_img
+            self.image_view.cost_image = cost_img.copy()
+
+            pm = QPixmap(file_path)
+            if not pm.isNull():
+                self._last_loaded_pixmap = pm
+
+            self._last_loaded_file_path = file_path
+            self._update_advanced_images()
+
+    def update_contrast(self, clip_limit):
+        self._current_clip_limit = clip_limit
+        if self._last_loaded_file_path:
+            cost_img = compute_cost_image(
+                self._last_loaded_file_path,
+                self._circle_calibrated_radius,
+                clip_limit=clip_limit
+            )
             self.image_view.cost_image_original = cost_img
             self.image_view.cost_image = cost_img.copy()
+            self.image_view._apply_all_guide_points_to_cost()
+            self.image_view._rebuild_full_path()
+
+        self._update_advanced_images()
+
+    def _update_advanced_images(self):
+        if not self._last_loaded_pixmap:
+            return
+        pm_np = self._qpixmap_to_gray_float(self._last_loaded_pixmap)
+        contrasted_blurred = preprocess_image(
+            pm_np,
+            sigma=3,
+            clip_limit=self._current_clip_limit
+        )
+        cost_img_np = self.image_view.cost_image
+        self._advanced_widget.update_displays(contrasted_blurred, cost_img_np)
+
+    def _qpixmap_to_gray_float(self, qpix):
+        img = qpix.toImage()
+        img = img.convertToFormat(QImage.Format_ARGB32)
+        ptr = img.bits()
+        ptr.setsize(img.byteCount())
+        arr = np.frombuffer(ptr, np.uint8).reshape((img.height(), img.width(), 4))
+        rgb = arr[..., :3].astype(np.float32)
+        gray = rgb.mean(axis=2) / 255.0
+        return gray
 
     def export_path(self):
-        """Export the full path (x,y) as a .npy file."""
+        """
+        Exports the path as a CSV in the format: x, y, TYPE,
+        ensuring that each anchor influences exactly one path point.
+        """
         full_xy = self.image_view.get_full_path_xy()
         if not full_xy:
             print("No path to export.")
             return
 
+        # We'll consider each anchor point as "USER-PLACED".
+        # But unlike a distance-threshold approach, we assign each anchor
+        # to exactly one closest path point.
+        anchor_points = self.image_view.anchor_points
+
+        # For each anchor, find the index of the closest path point
+        user_placed_indices = set()
+        for ax, ay in anchor_points:
+            min_dist = float('inf')
+            closest_idx = None
+            for i, (px, py) in enumerate(full_xy):
+                dist = math.hypot(px - ax, py - ay)
+                if dist < min_dist:
+                    min_dist = dist
+                    closest_idx = i
+            if closest_idx is not None:
+                user_placed_indices.add(closest_idx)
+
+        # Ask user for the CSV filename
         options = QFileDialog.Options()
         file_path, _ = QFileDialog.getSaveFileName(
             self, "Export Path", "",
-            "NumPy Files (*.npy);;All Files (*)",
+            "CSV Files (*.csv);;All Files (*)",
             options=options
         )
-        if file_path:
-            arr = np.array(full_xy)
-            np.save(file_path, arr)
-            print(f"Exported path with {len(arr)} points to {file_path}")
+        if not file_path:
+            return
+
+        import csv
+        with open(file_path, 'w', newline='') as csvfile:
+            writer = csv.writer(csvfile)
+            writer.writerow(["x", "y", "TYPE"])
+
+            for i, (x, y) in enumerate(full_xy):
+                ptype = "USER-PLACED" if i in user_placed_indices else "PATH"
+                writer.writerow([x, y, ptype])
+
+        print(f"Exported path with {len(full_xy)} points to {file_path}")
+
+
 
     def clear_points(self):
         self.image_view.clear_guide_points()
diff --git a/data/agamodonPath.npy b/data/agamodonPath.npy
new file mode 100644
index 0000000000000000000000000000000000000000..5081e86649a488510afbf0606cf8d9465c813a70
Binary files /dev/null and b/data/agamodonPath.npy differ
diff --git a/data/agamodonPoints.npy b/data/agamodonPoints.npy
new file mode 100644
index 0000000000000000000000000000000000000000..75ec4b2945d7e7a49b6b72de2fddeca8bcee6615
Binary files /dev/null and b/data/agamodonPoints.npy differ
diff --git a/data/agamodon_slice.png b/data/agamodon_slice.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa58983b107d16300d3425d4150d64bb568de087
Binary files /dev/null and b/data/agamodon_slice.png differ
diff --git a/data/angustifronsPoints.npy b/data/angustifronsPoints.npy
new file mode 100644
index 0000000000000000000000000000000000000000..188f483b5c7a7433d144acd9e2787678e9fceccd
Binary files /dev/null and b/data/angustifronsPoints.npy differ
diff --git a/data/angustifrons_slice.png b/data/angustifrons_slice.png
new file mode 100644
index 0000000000000000000000000000000000000000..21323872af3c72094cf891a9ecec4d74bf82fdff
Binary files /dev/null and b/data/angustifrons_slice.png differ
diff --git a/data/baikaPoints.npy b/data/baikaPoints.npy
new file mode 100644
index 0000000000000000000000000000000000000000..e5c79954c80b6cd87d793e8c03830da3619c268c
Binary files /dev/null and b/data/baikaPoints.npy differ
diff --git a/data/bipesPoints.npy b/data/bipesPoints.npy
new file mode 100644
index 0000000000000000000000000000000000000000..166d2be24b69db24a4e5488ddea7261c5165b2b5
Binary files /dev/null and b/data/bipesPoints.npy differ
diff --git a/data/bipes_slice.png b/data/bipes_slice.png
new file mode 100644
index 0000000000000000000000000000000000000000..c99746284766bd5f1694e6ac12c0adef08b8f87c
Binary files /dev/null and b/data/bipes_slice.png differ
diff --git a/data/exportedPath.npy b/data/exportedPath.npy
new file mode 100644
index 0000000000000000000000000000000000000000..a2d546e367aa3e18efefe88ecbdca9a09c7205cd
Binary files /dev/null and b/data/exportedPath.npy differ
diff --git a/data/test_image.jpg b/data/test_image.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..632e369fa39c002c0eb0f96dac18b22be08cf418
Binary files /dev/null and b/data/test_image.jpg differ
diff --git a/live_wire.py b/live_wire.py
index e42f7d5412c94fbf86cc2408a66da67ac62b92a9..71fcc28db2f933eb64aaff8428d92f1cdca60b3a 100644
--- a/live_wire.py
+++ b/live_wire.py
@@ -1,4 +1,3 @@
-import time
 import cv2
 import numpy as np
 import matplotlib.pyplot as plt
@@ -8,62 +7,40 @@ from skimage.feature import canny
 from skimage.graph import route_through_array
 from scipy.signal import convolve2d
 
-'''
-### Canny Edge cost image
-def compute_cost_image(path, sigma=3):
-
-    ### Load image
-    image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
-    
-    # Apply histogram equalization
-    image_contrasted = exposure.equalize_adapthist(image, clip_limit=0.01)
+### Disk live wire cost image
 
-    # Apply smoothing
-    smoothed_img = gaussian(image_contrasted, sigma=sigma)
+def compute_disk_size(user_radius, upscale_factor=1.2):
+    return int(np.ceil(upscale_factor * 2 * user_radius + 1) // 2 * 2 + 1)
 
-    # Apply Canny edge detection
-    canny_img = canny(smoothed_img)
 
-    # Create cost image
-    cost_img = 1.0 / (canny_img + 1e-5)  # Invert edges: higher cost where edges are stronger
+def load_image(path):
+    return cv2.imread(path, cv2.IMREAD_GRAYSCALE)
 
-    return cost_img
+def preprocess_image(image, sigma=3, clip_limit=0.01):
+    # Apply histogram equalization
+    image_contrasted = exposure.equalize_adapthist(image, clip_limit=clip_limit)
 
-def find_path(cost_image, points):
+    # Apply smoothing
+    smoothed_img = gaussian(image_contrasted, sigma=sigma)
 
-    if len(points) != 2:
-        raise ValueError("Points should be a list of 2 points: seed and target.")
-    
-    seed_rc, target_rc = points
+    return smoothed_img
 
-    path_rc, cost = route_through_array(
-        cost_image, 
-        start=seed_rc, 
-        end=target_rc, 
-        fully_connected=True
-    )
 
-    return path_rc
-'''
+def compute_cost_image(path, user_radius, sigma=3, clip_limit=0.01):
 
-### Disk live wire cost image
-def compute_cost_image(path, sigma=3, disk_size=15):
+    disk_size = compute_disk_size(user_radius)
 
     ### Load image
-    image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
-
-    # Apply histogram equalization
-    image_contrasted = exposure.equalize_adapthist(image, clip_limit=0.01)
+    image = load_image(path)
 
     # Apply smoothing
-    smoothed_img = gaussian(image_contrasted, sigma=sigma)
+    smoothed_img = preprocess_image(image, sigma=sigma, clip_limit=clip_limit)
 
     # Apply Canny edge detection
     canny_img = canny(smoothed_img)
 
     # Do disk thing
     binary_img = canny_img
-    k_size = 17
     kernel = circle_edge_kernel(k_size=disk_size)
     convolved = convolve2d(binary_img, kernel, mode='same', boundary='fill')
 
@@ -128,12 +105,7 @@ def circle_edge_kernel(k_size=5, radius=None):
     return kernel
 
 
-
-
-
-
-
-# Other functions 
+# Other functions (to be implemented?)
 def downscale(img, points, scale_percent):
     """
     Downsample `img` to `scale_percent` size and scale the given points accordingly.
@@ -160,39 +132,4 @@ def downscale(img, points, scale_percent):
         scaled_seed_xy = (int(seed_xy[0] * scale_x), int(seed_xy[1] * scale_y))
         scaled_target_xy = (int(target_xy[0] * scale_x), int(target_xy[1] * scale_y))
 
-        return downsampled_img, (scaled_seed_xy, scaled_target_xy)
-
-def compute_cost(image, sigma=3.0, epsilon=1e-5):
-    """
-    Smooth the image, run Canny edge detection, then invert the edge map into a cost image.
-    """
-
-    # Apply histogram equalization
-    image_contrasted = exposure.equalize_adapthist(image, clip_limit=0.01)
-
-    # Apply smoothing
-    smoothed_img = gaussian(image_contrasted, sigma=sigma)
-
-    # Apply Canny edge detection
-    canny_img = canny(smoothed_img)
-
-    # Create cost image
-    cost_img = 1.0 / (canny_img + epsilon)  # Invert edges: higher cost where edges are stronger
-
-    return cost_img, canny_img
-
-def backtrack_pixels_on_image(img_color, path_coords, bgr_color=(0, 0, 255)):
-    """
-    Color the path on the (already converted BGR) image in the specified color.
-    `path_coords` should be a list of (row, col) or (y, x).
-    """
-    for (row, col) in path_coords:
-        img_color[row, col] = bgr_color
-    return img_color
-
-def export_path(path_coords, path_name):
-    """
-    Export the path to a np array.
-    """
-    np.save(path_name, path_coords)
-    return None
+        return downsampled_img, (scaled_seed_xy, scaled_target_xy)
\ No newline at end of file